Skip to content

Commit 01a616c

Browse files
authored
feat: users page on partner dir (#57)
* feat: users page on partner dir * chore: some updates * chore: update * chore: lib update
1 parent 19c848f commit 01a616c

11 files changed

Lines changed: 741 additions & 272 deletions

File tree

apps/web-app/app/components/PartnerAgreementCard.vue

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@
4545
<p>Паушальный взнос: {{ agreement.lumpSumPayment }} ₽</p>
4646
</div>
4747

48-
<p class="text-muted">
48+
<p v-if="agreement.comment" class="text-muted">
4949
{{ agreement.comment }}
5050
</p>
5151

52-
<div class="flex flex-col gap-1.5">
52+
<div v-if="agreement.files.length" class="flex flex-col gap-1.5">
5353
<UButton
5454
v-for="file in agreement.files"
5555
:key="file.id"
@@ -60,9 +60,8 @@
6060
variant="subtle"
6161
color="neutral"
6262
icon="i-lucide-file-symlink"
63-
>
64-
{{ file.name }}
65-
</UButton>
63+
:label="file.name"
64+
/>
6665
</div>
6766
</div>
6867
</UCard>
@@ -79,17 +78,5 @@ const { agreement } = defineProps<{ agreement: PartnerAgreement & { files: Partn
7978
const overlay = useOverlay()
8079
const modalUpdatePartnerAgreement = overlay.create(ModalUpdatePartnerAgreement)
8180
82-
const agreementProgress = computed(() => {
83-
if (!agreement?.willEndAt || !agreement?.concludedAt) {
84-
return 0
85-
}
86-
87-
const now = new Date()
88-
const concludedAt = new Date(agreement.concludedAt)
89-
const willEndAt = new Date(agreement.willEndAt)
90-
91-
const res = Math.floor(100 - ((now.getTime() - concludedAt.getTime()) / (willEndAt.getTime() - concludedAt.getTime())) * 100)
92-
93-
return res > 0 ? res : 0
94-
})
81+
const agreementProgress = computed(() => getAgreementProgressPercentLeft(agreement.concludedAt, agreement.willEndAt))
9582
</script>

apps/web-app/app/components/PartnerCard.vue

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -90,17 +90,5 @@ const otherUsers = computed(() => partner.users.filter((user) => user.type !== '
9090
9191
const minimalAgreement = computed(() => partner.legalEntity?.agreements.filter((agreement) => agreement.isActive).toSorted((a, b) => new Date(a.willEndAt ?? '').getTime() - new Date(b.willEndAt ?? '').getTime())[0])
9292
93-
const agreementProgress = computed(() => {
94-
if (!minimalAgreement.value?.willEndAt || !minimalAgreement.value?.concludedAt) {
95-
return 0
96-
}
97-
98-
const now = new Date()
99-
const concludedAt = new Date(minimalAgreement.value.concludedAt)
100-
const willEndAt = new Date(minimalAgreement.value.willEndAt)
101-
102-
const res = Math.floor(100 - ((now.getTime() - concludedAt.getTime()) / (willEndAt.getTime() - concludedAt.getTime())) * 100)
103-
104-
return res > 0 ? res : 0
105-
})
93+
const agreementProgress = computed(() => getAgreementProgressPercentLeft(minimalAgreement.value?.concludedAt, minimalAgreement.value?.willEndAt))
10694
</script>

apps/web-app/app/components/PartnerLegalEntityCard.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
<div>
1111
<p>ИНН {{ entity.inn }}</p>
12-
<p>ОГРНИП {{ entity.ogrnip }}</p>
12+
<p v-if="entity.ogrnip">
13+
ОГРНИП {{ entity.ogrnip }}
14+
</p>
1315
</div>
1416

15-
<p class="text-muted">
17+
<p v-if="entity.comment" class="text-muted">
1618
{{ entity.comment }}
1719
</p>
1820
</div>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<template>
2+
<ActiveCard
3+
padding="none"
4+
class="flex flex-col gap-2.5 group"
5+
>
6+
<div class="relative">
7+
<img
8+
:src="user.avatarUrl ?? undefined"
9+
alt=""
10+
class="aspect-square w-full rounded-lg duration-200"
11+
:class="{ 'opacity-75 grayscale group-hover:grayscale-0 group-hover:opacity-100': imagesMode === 'grayscale' }"
12+
>
13+
14+
<div class="absolute top-2 left-2">
15+
<UBadge
16+
color="neutral"
17+
variant="solid"
18+
size="lg"
19+
class="rounded-lg"
20+
:label="getUserTypeLabel(user.type)"
21+
/>
22+
</div>
23+
</div>
24+
25+
<div class="min-h-20 h-full px-2.5 pb-2 flex flex-col gap-2.5">
26+
<div class="flex flex-col gap-1">
27+
<h3 class="text-base/5 font-bold">
28+
{{ user.name }} {{ user.surname }}
29+
</h3>
30+
31+
<p class="text-sm/4 text-muted line-clamp-3">
32+
{{ user.caption }}
33+
</p>
34+
</div>
35+
</div>
36+
</ActiveCard>
37+
</template>
38+
39+
<script setup lang="ts">
40+
import type { User } from '@roll-stack/database'
41+
42+
defineProps<{
43+
user: User
44+
}>()
45+
46+
const { imagesMode } = useApp()
47+
</script>

apps/web-app/app/pages/partner/[id].vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,28 @@ const submenuItems = computed(() => [
3737
exact: true,
3838
},
3939
{
40-
label: 'Кухни',
40+
label: t('app.menu.kitchens'),
4141
to: `/partner/${partner.value?.id}/kitchens`,
4242
icon: 'i-lucide-map-pinned',
4343
badge: partner.value?.kitchens.length,
4444
},
4545
{
46-
label: 'Договора',
46+
label: t('app.menu.agreements'),
4747
to: `/partner/${partner.value?.id}/agreement`,
4848
icon: 'i-lucide-scroll-text',
4949
badge: partner.value?.legalEntity?.agreements.length,
5050
},
5151
{
52-
label: 'Юр. лицо',
52+
label: t('app.partner-legal-entity.title'),
5353
to: `/partner/${partner.value?.id}/legal`,
5454
icon: 'i-lucide-scale',
5555
},
56+
{
57+
label: t('app.menu.staff'),
58+
to: `/partner/${partner.value?.id}/staff`,
59+
icon: 'i-lucide-contact-round',
60+
badge: partner.value?.users.length,
61+
},
5662
])
5763
5864
useHead({

apps/web-app/app/pages/partner/[id]/index.vue

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
<template>
22
<Content>
3-
<div class="grid grid-cols-1 gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5">
3+
<div class="grid grid-cols-1 gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-5 2xl:grid-cols-6">
44
<UCard>
55
<div class="shrink-0 w-full flex flex-col gap-2">
6-
<div class="shrink-0 flex flex-row items-center gap-3.5">
7-
<img
8-
:src="partner?.avatarUrl ?? undefined"
9-
alt=""
10-
class="aspect-square size-20 rounded-lg"
11-
>
12-
<h2 class="text-xl md:text-2xl/7 font-semibold">
13-
{{ partner?.name }} {{ partner?.surname }}
14-
</h2>
15-
</div>
6+
<UIcon name="i-lucide-handshake" class="size-16 text-muted/25" />
167

17-
<p class="text-base">
8+
<h3 class="text-xl md:text-xl/6 font-semibold">
189
{{ partner?.priceLevel }} уровень цен
19-
</p>
10+
</h3>
2011

2112
<p class="text-base/5">
2213
{{ partner?.city }}
@@ -32,16 +23,20 @@
3223
size="lg"
3324
class="group-hover:scale-125 duration-200"
3425
/>
35-
<h3 class="text-xl md:text-2xl font-semibold">
36-
Престиж
37-
</h3>
3826
</div>
27+
28+
<h3 class="text-xl md:text-xl/6 font-semibold">
29+
{{ t('app.menu.prestige') }}
30+
</h3>
31+
3932
<p class="text-base/5">
40-
Престиж не является статичным - он может как укрепляться, так и утрачиваться в зависимости от действий Партнера, его достижений и общественного восприятия.
33+
Может как укрепляться, так и утрачиваться в зависимости от действий Партнера, его достижений и общественного восприятия.
4134
</p>
4235
</div>
4336
</UCard>
4437

38+
<UserCard v-if="partnerUser" :user="partnerUser" />
39+
4540
<div class="lg:col-span-2">
4641
<PartnerLegalEntityCard :partner-id="partner?.id ?? ''" :entity="partner?.legalEntity" />
4742
</div>
@@ -64,6 +59,8 @@ const { params } = useRoute('partner-id')
6459
const partnerStore = usePartnerStore()
6560
const partner = computed(() => partnerStore.partners.find((partner) => partner.id === params.id))
6661
62+
const partnerUser = computed(() => partner.value?.users.filter((user) => user.type === 'partner')[0])
63+
6764
useHead({
6865
title: t('common.partner'),
6966
})
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<template>
2+
<Content>
3+
<div class="grid grid-cols-1 gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-5 2xl:grid-cols-6">
4+
<UserCard
5+
v-for="staff in partner?.users"
6+
:key="staff.id"
7+
:user="staff"
8+
/>
9+
</div>
10+
</Content>
11+
</template>
12+
13+
<script setup lang="ts">
14+
const { t } = useI18n()
15+
const { params } = useRoute('partner-id')
16+
17+
const partnerStore = usePartnerStore()
18+
const partner = computed(() => partnerStore.partners.find((partner) => partner.id === params.id))
19+
20+
useHead({
21+
title: t('app.menu.staff'),
22+
})
23+
</script>

apps/web-app/i18n/locales/ru-RU.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@
126126
"prints": "Материалы для печати",
127127
"kitchens": "Кухни",
128128
"ticket": "Тикет",
129-
"tickets": "Тикеты"
129+
"tickets": "Тикеты",
130+
"staff": "Сотрудники",
131+
"agreements": "Договоры",
132+
"prestige": "Престиж"
130133
},
131134
"product": {
132135
"available-for-purchase": "Доступен для покупки",

apps/web-app/shared/utils/helpers.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { User } from '@roll-stack/database'
12
import type { Resolution } from '../services/task'
23

34
export function getResolutionForSelect(): { value: Resolution, label: string, icon: string }[] {
@@ -30,6 +31,40 @@ export function getResolutionIcon(resolution: Resolution) {
3031
}
3132
}
3233

34+
export function getUserTypeLabel(type: User['type']): string {
35+
switch (type) {
36+
case 'partner':
37+
return 'Партнер'
38+
case 'staff':
39+
return 'Сотрудник сети'
40+
default:
41+
return ''
42+
}
43+
}
44+
45+
export function getAgreementProgressPercentLeft(concludedAt?: string | null, willEndAt?: string | null): number {
46+
if (!concludedAt || !willEndAt) {
47+
return 0
48+
}
49+
50+
const nowMs = Date.now()
51+
const concludedAtMs = new Date(concludedAt).getTime()
52+
const willEndAtMs = new Date(willEndAt).getTime()
53+
if (Number.isNaN(concludedAtMs) || Number.isNaN(willEndAtMs)) {
54+
return 0
55+
}
56+
if (willEndAtMs <= concludedAtMs) {
57+
return 0
58+
}
59+
60+
const total = willEndAtMs - concludedAtMs
61+
const remaining = willEndAtMs - nowMs
62+
const remainingPct = (remaining / total) * 100
63+
const res = Math.floor(remainingPct)
64+
65+
return Math.min(100, Math.max(0, res))
66+
}
67+
3368
export function pluralizationRu(int: number, array: [string, string, string]): string {
3469
const n = Math.abs(int)
3570

0 commit comments

Comments
 (0)