Skip to content

Commit cd17718

Browse files
authored
feat: revenue graph (#25)
* feat: revenue graph * chore: update
1 parent 2378644 commit cd17718

6 files changed

Lines changed: 1311 additions & 6 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
class="h-96"
18+
:width="width"
19+
>
20+
<VisLine
21+
:x="x"
22+
:y="y"
23+
color="var(--ui-secondary)"
24+
/>
25+
<VisArea
26+
:x="x"
27+
:y="y"
28+
color="var(--ui-secondary)"
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-secondary)"
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+
total: number
57+
checks: number
58+
}
59+
60+
const { period, range, values } = defineProps<{
61+
period: Period
62+
range: Range
63+
values: { date: string, total: number, checks: number }[]
64+
}>()
65+
66+
const cardRef = useTemplateRef<HTMLElement | null>('cardRef')
67+
68+
const { width } = useElementSize(cardRef)
69+
70+
const data = ref<DataRecord[]>([])
71+
72+
watch([() => period, () => range, () => values], () => {
73+
const dates = ({
74+
daily: eachDayOfInterval,
75+
weekly: eachWeekOfInterval,
76+
monthly: eachMonthOfInterval,
77+
} as Record<Period, typeof eachDayOfInterval>)[period](range)
78+
79+
data.value = dates.map((date) => {
80+
const dateStr = format(date, 'yyyy-MM-dd')
81+
const value = values.find((d) => d.date.startsWith(dateStr))
82+
83+
return {
84+
date,
85+
total: value?.total ?? 0,
86+
checks: value?.checks ?? 0,
87+
}
88+
})
89+
}, { immediate: true })
90+
91+
const x = (_: DataRecord, i: number) => i
92+
const y = (d: DataRecord) => d.total
93+
94+
const total = computed(() => data.value.reduce((acc: number, { total }) => acc + total, 0))
95+
96+
const formatNumber = new Intl.NumberFormat('ru', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 }).format
97+
98+
function formatDate(date: Date): string {
99+
return ({
100+
daily: format(date, 'd MMMM', { locale: ru }),
101+
weekly: format(date, 'd MMMM', { locale: ru }),
102+
monthly: format(date, 'MMMM yyy', { locale: ru }),
103+
})[period]
104+
}
105+
106+
function xTicks(i: number) {
107+
if (i === 0 || i === data.value.length - 1 || !data.value[i]) {
108+
return ''
109+
}
110+
111+
return formatDate(data.value[i].date)
112+
}
113+
114+
const template = (d: DataRecord) => `${formatDate(d.date)}: ${formatNumber(d.total)}, ${d.checks} ${pluralizationRu(d.checks, ['чек', 'чека', 'чеков'])}`
115+
</script>
116+
117+
<style scoped>
118+
.unovis-xy-container {
119+
--vis-crosshair-line-stroke-color: var(--ui-secondary);
120+
--vis-crosshair-circle-stroke-color: var(--ui-bg);
121+
122+
--vis-axis-grid-color: var(--ui-border);
123+
--vis-axis-tick-color: var(--ui-border);
124+
--vis-axis-tick-label-color: var(--ui-text-dimmed);
125+
126+
--vis-tooltip-background-color: var(--ui-bg);
127+
--vis-tooltip-border-color: var(--ui-border);
128+
--vis-tooltip-text-color: var(--ui-text-highlighted);
129+
}
130+
</style>
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
<template>
22
<Content>
3-
<div>{{ data }}</div>
3+
<ChartKitchenRevenue
4+
:period="period"
5+
:range="range"
6+
:values="data ?? []"
7+
/>
48
</Content>
59
</template>
610

711
<script setup lang="ts">
12+
import type { Period, Range } from '#shared/types'
13+
import { sub } from 'date-fns'
14+
815
const { params } = useRoute('kitchen-id')
16+
917
const { data } = useFetch(`/api/kitchen/id/${params.id}/revenue`)
18+
19+
const range = shallowRef<Range>({
20+
start: sub(new Date(), { days: 14 }),
21+
end: new Date(),
22+
})
23+
const period = ref<Period>('daily')
1024
</script>

apps/web-app/shared/types/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,10 @@ export type PrintWithData = Print & {
4444
export type KitchenWithData = Kitchen & {
4545
feedbackPoints: FeedbackPoint[]
4646
}
47+
48+
export type Period = 'daily' | 'weekly' | 'monthly'
49+
50+
export interface Range {
51+
start: Date
52+
end: Date
53+
}

packages/ui/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"@internationalized/date": "catalog:",
1212
"@nuxt/ui": "catalog:",
1313
"@nuxtjs/i18n": "catalog:",
14+
"@unovis/ts": "catalog:",
15+
"@unovis/vue": "catalog:",
1416
"@vueuse/core": "catalog:",
1517
"@vueuse/integrations": "catalog:",
1618
"@vueuse/nuxt": "catalog:",

0 commit comments

Comments
 (0)