Skip to content

Commit 5925f1c

Browse files
committed
feat: average check
1 parent c110c7a commit 5925f1c

7 files changed

Lines changed: 198 additions & 3 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<template>
2+
<UCard ref="cardRef" :ui="{ root: 'overflow-visible', body: '!px-0 !pt-0 !pb-3' }">
3+
<template #header>
4+
<div>
5+
<p class="text-xs text-muted uppercase mb-1.5">
6+
Средний чек за {{ data.length }} {{ pluralizationRu(data.length, ['день', 'дня', 'дней']) }}
7+
</p>
8+
<p class="text-3xl text-highlighted font-semibold">
9+
{{ formatNumber(total) }}
10+
</p>
11+
</div>
12+
</template>
13+
14+
<VisXYContainer
15+
:data="data"
16+
:padding="{ top: 40 }"
17+
:width="width"
18+
class="h-80"
19+
>
20+
<VisLine
21+
:x="x"
22+
:y="y"
23+
color="var(--ui-info)"
24+
/>
25+
<VisArea
26+
:x="x"
27+
:y="y"
28+
color="var(--ui-info)"
29+
:opacity="0.1"
30+
/>
31+
32+
<VisAxis
33+
type="x"
34+
:x="x"
35+
:tick-format="xTicks"
36+
/>
37+
38+
<VisCrosshair
39+
color="var(--ui-info)"
40+
:template="template"
41+
/>
42+
43+
<VisTooltip />
44+
</VisXYContainer>
45+
</UCard>
46+
</template>
47+
48+
<script setup lang="ts">
49+
import type { Period, Range } from '#shared/types'
50+
import { VisArea, VisAxis, VisCrosshair, VisLine, VisTooltip, VisXYContainer } from '@unovis/vue'
51+
import { eachDayOfInterval, eachMonthOfInterval, eachWeekOfInterval, format } from 'date-fns'
52+
import { ru } from 'date-fns/locale'
53+
54+
type DataRecord = {
55+
date: Date
56+
checks: number
57+
averageCheck: number
58+
commonAverageCheck: number
59+
}
60+
61+
const { period, range, values } = defineProps<{
62+
period: Period
63+
range: Range
64+
values: { date: string, checks: number, averageCheck: number, commonAverageCheck: number }[]
65+
}>()
66+
67+
const cardRef = useTemplateRef<HTMLElement | null>('cardRef')
68+
69+
const { width } = useElementSize(cardRef)
70+
71+
const data = ref<DataRecord[]>([])
72+
73+
watch([() => period, () => range, () => values], () => {
74+
const dates = ({
75+
daily: eachDayOfInterval,
76+
weekly: eachWeekOfInterval,
77+
monthly: eachMonthOfInterval,
78+
} as Record<Period, typeof eachDayOfInterval>)[period](range)
79+
80+
data.value = dates.map((date) => {
81+
const dateStr = format(date, 'yyyy-MM-dd')
82+
const value = values.find((d) => d.date.startsWith(dateStr))
83+
84+
return {
85+
date,
86+
checks: value?.checks ?? 0,
87+
averageCheck: value?.averageCheck ?? 0,
88+
commonAverageCheck: value?.commonAverageCheck ?? 0,
89+
}
90+
})
91+
}, { immediate: true })
92+
93+
const x = (_: DataRecord, i: number) => i
94+
const y = (d: DataRecord) => d.averageCheck
95+
96+
const total = computed(() => {
97+
const count = data.value.filter((d) => d.averageCheck).length
98+
return Math.floor(data.value.reduce((acc: number, { averageCheck }) => acc + averageCheck, 0) / count)
99+
})
100+
101+
const formatNumber = new Intl.NumberFormat('ru', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 }).format
102+
103+
function formatDate(date: Date): string {
104+
return ({
105+
daily: format(date, 'd MMMM', { locale: ru }),
106+
weekly: format(date, 'd MMMM', { locale: ru }),
107+
monthly: format(date, 'MMMM yyy', { locale: ru }),
108+
})[period]
109+
}
110+
111+
function xTicks(i: number) {
112+
if (i === 0 || i === data.value.length - 1 || !data.value[i]) {
113+
return ''
114+
}
115+
116+
return formatDate(data.value[i].date)
117+
}
118+
119+
const template = (d: DataRecord) => `${formatDate(d.date)}: ${formatNumber(d.averageCheck)}`
120+
</script>
121+
122+
<style scoped>
123+
.unovis-xy-container {
124+
--vis-crosshair-line-stroke-color: var(--ui-info);
125+
--vis-crosshair-circle-stroke-color: var(--ui-bg);
126+
127+
--vis-axis-grid-color: var(--ui-border);
128+
--vis-axis-tick-color: var(--ui-border);
129+
--vis-axis-tick-label-color: var(--ui-text-dimmed);
130+
131+
--vis-tooltip-background-color: var(--ui-bg);
132+
--vis-tooltip-border-color: var(--ui-border);
133+
--vis-tooltip-text-color: var(--ui-text-highlighted);
134+
}
135+
</style>

apps/web-app/app/components/chart/KitchenRevenue.client.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
<VisXYContainer
1515
:data="data"
1616
:padding="{ top: 40 }"
17-
class="h-96"
1817
:width="width"
18+
class="h-80"
1919
>
2020
<VisLine
2121
:x="x"

apps/web-app/app/pages/kitchen/[id]/finance.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
:range="range"
66
:values="data ?? []"
77
/>
8+
9+
<ChartKitchenChecks
10+
:period="period"
11+
:range="range"
12+
:values="data ?? []"
13+
/>
814
</Content>
915
</template>
1016

@@ -17,7 +23,7 @@ const { params } = useRoute('kitchen-id')
1723
const { data } = useFetch(`/api/kitchen/id/${params.id}/revenue`)
1824
1925
const range = shallowRef<Range>({
20-
start: sub(new Date(), { days: 14 }),
26+
start: sub(new Date(), { days: 89 }),
2127
end: new Date(),
2228
})
2329
const period = ref<Period>('daily')

apps/web-app/nuxt.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default defineNuxtConfig({
3737
tasks: true,
3838
},
3939
scheduledTasks: {
40-
'* * * * *': ['task:auto-create'], // Every minute
40+
'* * * * *': ['task:auto-create', 'kitchen:average-update'], // Every minute
4141
'0 * * * *': ['kitchen:revenue-update'], // Every hour
4242
'0 0 * * *': ['kitchen:rating-update'], // Every day
4343
},
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { repository } from '@roll-stack/database'
2+
3+
export default defineTask({
4+
meta: {
5+
name: 'kitchen:average-update',
6+
description: 'Update average data of kitchens',
7+
},
8+
async run() {
9+
try {
10+
const revenuesToUpdate = await repository.kitchen.listRevenuesToUpdate()
11+
for (const revenue of revenuesToUpdate) {
12+
// Average check
13+
const averageCheck = Math.round(revenue.total / revenue.checks)
14+
15+
// common
16+
const allRevenuesThisPeriod = await repository.kitchen.listRevenuesForDate(revenue.date)
17+
const commonAverageCheck = Math.round(allRevenuesThisPeriod.reduce((acc, curr) => acc + curr.averageCheck, 0) / allRevenuesThisPeriod.length)
18+
const commonTotal = Math.round(allRevenuesThisPeriod.reduce((acc, curr) => acc + curr.total, 0) / allRevenuesThisPeriod.length)
19+
20+
await repository.kitchen.updateRevenue(revenue.id, {
21+
averageCheck,
22+
commonAverageCheck,
23+
commonTotal,
24+
})
25+
}
26+
} catch (error) {
27+
errorResolver(error)
28+
}
29+
30+
return { result: true }
31+
},
32+
})

packages/database/src/repository/kitchen.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,25 @@ export class Kitchen {
5454
})
5555
}
5656

57+
static async listRevenuesForDate(date: string) {
58+
return useDatabase().query.kitchenRevenues.findMany({
59+
where: (revenues, { and }) => and(
60+
sql`date(${revenues.date}) >= date(${date})`,
61+
sql`date(${revenues.date}) <= date(${date})`,
62+
),
63+
})
64+
}
65+
66+
static async listRevenuesToUpdate() {
67+
return useDatabase().query.kitchenRevenues.findMany({
68+
where: (revenues, { eq, or }) => or(
69+
eq(revenues.averageCheck, 0),
70+
eq(revenues.commonTotal, 0),
71+
),
72+
limit: 50,
73+
})
74+
}
75+
5776
static async create(data: KitchenDraft) {
5877
const [kitchen] = await useDatabase().insert(kitchens).values(data).returning()
5978
return kitchen

packages/database/src/tables.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,10 @@ export const kitchenRevenues = pgTable('kitchen_revenues', {
420420
updatedAt: timestamp('updated_at', { precision: 3, withTimezone: true, mode: 'string' }).notNull().defaultNow(),
421421
date: date('date', { mode: 'string' }).notNull(),
422422
total: numeric('total', { mode: 'number' }).notNull().default(0),
423+
commonTotal: numeric('common_total', { mode: 'number' }).notNull().default(0),
423424
checks: integer('checks').notNull().default(0),
425+
averageCheck: numeric('average_check', { mode: 'number' }).notNull().default(0),
426+
commonAverageCheck: numeric('common_average_check', { mode: 'number' }).notNull().default(0),
424427
kitchenId: cuid2('kitchen_id').notNull().references(() => kitchens.id),
425428
})
426429

0 commit comments

Comments
 (0)