Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 43 additions & 10 deletions apps/web-app/app/components/PartnerCard.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
<template>
<ActiveCard padding="none" class="flex flex-col gap-2.5 group">
<img
:src="partner.avatarUrl ?? undefined"
alt=""
class="aspect-square w-full rounded-lg duration-200"
:class="{ 'opacity-75 grayscale group-hover:grayscale-0 group-hover:opacity-100': imagesMode === 'grayscale' }"
>
<div class="relative">
<img
:src="partner.avatarUrl ?? undefined"
alt=""
class="aspect-square w-full rounded-lg duration-200"
:class="{ 'opacity-75 grayscale group-hover:grayscale-0 group-hover:opacity-100': imagesMode === 'grayscale' }"
>

<div class="absolute top-2 left-0 right-0 w-full">
<div class="mx-2 px-2 py-1 bg-default/97 rounded-lg flex flex-row items-center gap-1.5">
<UIcon name="i-lucide-scroll-text" class="shrink-0 size-5 text-secondary" />

<UProgress
v-model="agreementProgress"
size="md"
color="secondary"
/>
</div>
</div>
</div>

<div class="min-h-20 h-full px-2.5 pb-2 flex flex-col gap-2.5">
<div class="flex flex-row items-center gap-2">
Expand All @@ -23,7 +37,11 @@
</div>

<p class="text-sm/4 text-muted line-clamp-3">
{{ partner.legal }}
{{ partner.legalEntity?.name }}
</p>

<p class="text-sm/4 text-error line-clamp-3">
{{ partner?.legal }}
</p>

<p class="text-sm/4 text-muted line-clamp-3">
Expand All @@ -34,11 +52,26 @@
</template>

<script setup lang="ts">
import type { Partner } from '@roll-stack/database'
import type { Partner, PartnerAgreement, PartnerLegalEntity } from '@roll-stack/database'

defineProps<{
partner: Partner
const { partner } = defineProps<{
partner: Partner & {
legalEntity: PartnerLegalEntity | null
activeAgreement: PartnerAgreement | null
}
}>()

const { imagesMode } = useApp()

const agreementProgress = computed(() => {
if (!partner?.activeAgreement?.willEndAt || !partner?.activeAgreement?.concludedAt) {
return 0
}

const now = new Date()
const concludedAt = new Date(partner.activeAgreement.concludedAt)
const willEndAt = new Date(partner.activeAgreement.willEndAt)

return Math.floor(100 - ((now.getTime() - concludedAt.getTime()) / (willEndAt.getTime() - concludedAt.getTime())) * 100)
})
</script>
151 changes: 115 additions & 36 deletions apps/web-app/app/pages/partner/[id]/index.vue
Original file line number Diff line number Diff line change
@@ -1,58 +1,137 @@
<template>
<Content>
<div class="grid grid-cols-1 gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
<div class="flex flex-col gap-2.5">
<img
:src="partner?.avatarUrl ?? undefined"
alt=""
class="w-full rounded-lg"
>
</div>

<div class="flex flex-col gap-2.5">
<div class="flex flex-row items-center gap-1.5">
<PartnerPrestigeBadge
:prestige="partner?.prestige ?? 0"
size="lg"
class="group-hover:scale-125 duration-200"
/>
<h3 class="text-xl md:text-2xl font-semibold">
Престиж
<div class="grid grid-cols-1 gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
<UCard variant="subtle" class="col-span-1">
<div class="flex flex-col gap-2.5">
<div class="flex flex-col items-start gap-2">
<div class="flex flex-row items-center gap-3.5">
<img
:src="partner?.avatarUrl ?? undefined"
alt=""
class="aspect-square size-20 rounded-lg"
>
<h2 class="text-xl md:text-2xl/7 font-semibold">
{{ partner?.name }} {{ partner?.surname }}
</h2>
</div>

<p class="text-base">
{{ partner?.priceLevel }} уровень цен
</p>

<p class="text-base">
{{ partner?.city }}
</p>
</div>
</div>
</UCard>

<UCard class="col-span-1">
<div class="flex flex-col gap-2.5">
<div class="flex flex-row items-center gap-1.5">
<PartnerPrestigeBadge
:prestige="partner?.prestige ?? 0"
size="lg"
class="group-hover:scale-125 duration-200"
/>
<h3 class="text-xl md:text-2xl font-semibold">
Престиж
</h3>
</div>
<p class="text-muted leading-5">
Престиж не является статичным - он может как укрепляться, так и утрачиваться в зависимости от действий Партнера, его достижений и общественного восприятия.
</p>
</div>
</UCard>

<UCard v-if="partner?.legalEntity" class="col-span-2">
<div class="flex flex-col gap-2.5">
<UIcon name="i-lucide-scale" class="size-16 text-muted/25" />

<h3 class="text-xl md:text-xl/6 font-semibold">
{{ partner.legalEntity.name }}
</h3>

<div>
<p>ИНН {{ partner.legalEntity.inn }}</p>
<p>ОГРНИП {{ partner.legalEntity.ogrnip }}</p>
</div>

<p class="text-muted">
{{ partner.legalEntity.comment }}
</p>
</div>
<p class="text-muted leading-5">
Престиж не является статичным - он может как укрепляться, так и утрачиваться в зависимости от действий Партнера, его достижений и общественного восприятия.
</p>
</div>
</div>
</UCard>

<div class="flex flex-col items-start gap-2">
<h2 class="text-xl md:text-3xl font-bold">
{{ partner?.name }} {{ partner?.surname }}
</h2>
<UCard
v-if="partner?.activeAgreement"
variant="subtle"
class="col-span-2"
>
<div class="flex flex-col gap-3">
<div class="flex flex-row items-start gap-3.5">
<UIcon name="i-lucide-scroll-text" class="shrink-0 size-16 text-secondary" />

<p class="text-lg">
{{ partner?.legal }}
</p>
<UProgress
v-model="agreementProgress"
size="lg"
color="secondary"
status
/>
</div>

<p class="text-base">
{{ partner?.priceLevel }} уровень цен
</p>
<h3 class="text-xl md:text-xl/6 font-semibold">
Договор №{{ partner.activeAgreement.internalId }}
</h3>

<div>
<p v-if="partner.activeAgreement.willEndAt">
Заключен до {{ format(new Date(partner.activeAgreement.willEndAt), 'd MMMM yyyy', { locale: ru }) }}
</p>
<p>Роялти: {{ partner.activeAgreement.royalty }}%</p>
<p>Мин. роялти: {{ partner.activeAgreement.minRoyaltyPerMonth }} ₽ / месяц</p>

<p v-if="partner.activeAgreement.marketingFee">
Маркетинговый сбор: {{ partner.activeAgreement.marketingFee }}%
</p>
<p v-if="partner.activeAgreement.minMarketingFeePerMonth">
Мин. маркетинговый сбор: {{ partner.activeAgreement.minMarketingFeePerMonth }} ₽ / месяц
</p>

<p class="text-base">
{{ partner?.city }}
</p>
<p>Паушальный взнос: {{ partner.activeAgreement.lumpSumPayment }} ₽</p>
</div>

<p class="text-muted">
{{ partner.activeAgreement.comment }}
</p>
</div>
</UCard>
</div>
</Content>
</template>

<script setup lang="ts">
import { format } from 'date-fns'
import { ru } from 'date-fns/locale/ru'

const { t } = useI18n()
const { params } = useRoute('partner-id')

const partnerStore = usePartnerStore()
const partner = computed(() => partnerStore.partners.find((partner) => partner.id === params.id))

const agreementProgress = computed(() => {
if (!partner.value?.activeAgreement?.willEndAt || !partner.value?.activeAgreement?.concludedAt) {
return 0
}

const now = new Date()
const concludedAt = new Date(partner.value.activeAgreement.concludedAt)
const willEndAt = new Date(partner.value.activeAgreement.willEndAt)

return Math.floor(100 - ((now.getTime() - concludedAt.getTime()) / (willEndAt.getTime() - concludedAt.getTime())) * 100)
})

useHead({
title: t('common.partner'),
})
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Kitchen, Partner } from '@roll-stack/database'
import type { Kitchen, Partner, PartnerAgreement, PartnerLegalEntity } from '@roll-stack/database'

type PartnerWithData = Partner & {
kitchens: Kitchen[]
legalEntity: PartnerLegalEntity | null
activeAgreement: PartnerAgreement | null
}

export const usePartnerStore = defineStore('partner', () => {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion apps/web-app/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default defineNuxtConfig({
strategy: 'no_prefix',
},
pinia: {
storesDirs: ['./stores/**'],
storesDirs: ['./app/stores/**'],
},
nitro: {
experimental: {
Expand Down
2 changes: 2 additions & 0 deletions packages/database/src/repository/partner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export class Partner {
where: (partners, { eq }) => eq(partners.isActive, true),
with: {
kitchens: true,
legalEntity: true,
activeAgreement: true,
},
})
}
Expand Down
53 changes: 51 additions & 2 deletions packages/database/src/tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,36 @@ export const partners = pgTable('partners', {
name: varchar('name').notNull(),
surname: varchar('surname').notNull().default(''),
avatarUrl: varchar('avatar_url'),
legal: varchar('legal'),
city: varchar('city'),
legal: varchar('legal'),
legalEntityId: cuid2('legal_entity_id').references(() => partnerLegalEntities.id),
activeAgreementId: cuid2('active_agreement_id').references(() => partnerAgreements.id),
})

export const partnerLegalEntities = pgTable('partner_legal_entities', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(),
name: varchar('name').notNull(),
inn: varchar('inn').notNull(),
ogrnip: varchar('ogrnip'),
comment: varchar('comment'),
})

export const partnerAgreements = pgTable('partner_agreements', {
id: cuid2('id').defaultRandom().primaryKey(),
createdAt: timestamp('created_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(),
updatedAt: timestamp('updated_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(),
concludedAt: timestamp('concluded_at', { precision: 3, withTimezone: true, mode: 'string' }),
willEndAt: timestamp('will_end_at', { precision: 3, withTimezone: true, mode: 'string' }),
internalId: varchar('internal_id').notNull(),
royalty: numeric('royalty', { mode: 'number' }).notNull().default(0),
minRoyaltyPerMonth: numeric('min_royalty_per_month', { mode: 'number' }).notNull().default(0),
marketingFee: numeric('marketing_fee', { mode: 'number' }).notNull().default(0),
minMarketingFeePerMonth: numeric('min_marketing_fee_per_month', { mode: 'number' }).notNull().default(0),
lumpSumPayment: numeric('lump_sum_payment', { mode: 'number' }).notNull().default(0),
comment: varchar('comment'),
legalEntityId: cuid2('legal_entity_id').references(() => partnerLegalEntities.id),
})

export const chats = pgTable('chats', {
Expand Down Expand Up @@ -539,8 +567,29 @@ export const userRelations = relations(users, ({ many, one }) => ({
postComments: many(postComments),
}))

export const partnerRelations = relations(partners, ({ many }) => ({
export const partnerRelations = relations(partners, ({ many, one }) => ({
kitchens: many(kitchens),
legalEntity: one(partnerLegalEntities, {
fields: [partners.legalEntityId],
references: [partnerLegalEntities.id],
}),
activeAgreement: one(partnerAgreements, {
fields: [partners.activeAgreementId],
references: [partnerAgreements.id],
}),
}))

export const partnerLegalEntityRelations = relations(partnerLegalEntities, ({ many }) => ({
partners: many(partners),
agreements: many(partnerAgreements),
}))

export const partnerAgreementRelations = relations(partnerAgreements, ({ one, many }) => ({
legalEntity: one(partnerLegalEntities, {
fields: [partnerAgreements.legalEntityId],
references: [partnerLegalEntities.id],
}),
partners: many(partners),
}))

export const chatRelations = relations(chats, ({ many, one }) => ({
Expand Down
6 changes: 6 additions & 0 deletions packages/database/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export type UserDraft = InferInsertModel<typeof tables.users>
export type Partner = InferSelectModel<typeof tables.partners>
export type PartnerDraft = InferInsertModel<typeof tables.partners>

export type PartnerLegalEntity = InferSelectModel<typeof tables.partnerLegalEntities>
export type PartnerLegalEntityDraft = InferInsertModel<typeof tables.partnerLegalEntities>

export type PartnerAgreement = InferSelectModel<typeof tables.partnerAgreements>
export type PartnerAgreementDraft = InferInsertModel<typeof tables.partnerAgreements>

export type Chat = InferSelectModel<typeof tables.chats>
export type ChatDraft = InferInsertModel<typeof tables.chats>

Expand Down
Loading