Skip to content

Commit 3c94343

Browse files
authored
feat: date range picker for charts (#29)
1 parent 9344da9 commit 3c94343

4 files changed

Lines changed: 151 additions & 8 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<template>
2+
<UPopover :content="{ align: 'start' }" :modal="true">
3+
<UButton
4+
color="neutral"
5+
variant="outline"
6+
icon="i-lucide-calendar"
7+
size="lg"
8+
class="data-[state=open]:bg-elevated group"
9+
>
10+
<span class="truncate">
11+
<template v-if="selected.start">
12+
<template v-if="selected.end">
13+
{{ df.format(selected.start) }} - {{ df.format(selected.end) }}
14+
</template>
15+
<template v-else>
16+
{{ df.format(selected.start) }}
17+
</template>
18+
</template>
19+
<template v-else>
20+
Выберите даты
21+
</template>
22+
</span>
23+
24+
<template #trailing>
25+
<UIcon name="i-lucide-chevron-down" class="shrink-0 text-dimmed size-5 group-data-[state=open]:rotate-180 transition-transform duration-200" />
26+
</template>
27+
</UButton>
28+
29+
<template #content>
30+
<div class="flex items-stretch sm:divide-x divide-default">
31+
<div class="hidden sm:flex flex-col justify-center">
32+
<UButton
33+
v-for="(range, index) in ranges"
34+
:key="index"
35+
:label="range.label"
36+
color="neutral"
37+
variant="ghost"
38+
class="rounded-none px-4"
39+
:class="[isRangeSelected(range) ? 'bg-elevated' : 'hover:bg-elevated/50']"
40+
truncate
41+
@click="selectRange(range)"
42+
/>
43+
</div>
44+
45+
<UCalendar
46+
v-model="calendarRange"
47+
class="p-2"
48+
:number-of-months="2"
49+
range
50+
/>
51+
</div>
52+
</template>
53+
</UPopover>
54+
</template>
55+
56+
<script setup lang="ts">
57+
import type { Range } from '#shared/types'
58+
import { CalendarDate, DateFormatter, getLocalTimeZone, today } from '@internationalized/date'
59+
60+
const df = new DateFormatter('ru-RU', {
61+
dateStyle: 'full',
62+
})
63+
64+
const selected = defineModel<Range>({ required: true })
65+
66+
const ranges = [
67+
{ label: 'Последние 7 дней', days: 7 - 1 },
68+
{ label: 'Последние 14 дней', days: 14 - 1 },
69+
{ label: 'Последние 30 дней', days: 30 - 1 },
70+
{ label: 'Последние 3 месяца', days: 90 - 1 },
71+
{ label: 'Последние 6 месяцев', days: 180 - 1 },
72+
{ label: 'Последний год', days: 365 - 1 },
73+
]
74+
75+
function toCalendarDate(date: Date) {
76+
return new CalendarDate(
77+
date.getFullYear(),
78+
date.getMonth() + 1,
79+
date.getDate(),
80+
)
81+
}
82+
83+
const calendarRange = computed({
84+
get: () => ({
85+
start: selected.value.start ? toCalendarDate(selected.value.start) : undefined,
86+
end: selected.value.end ? toCalendarDate(selected.value.end) : undefined,
87+
}),
88+
set: (newValue: { start: CalendarDate | null, end: CalendarDate | null }) => {
89+
selected.value = {
90+
start: newValue.start ? newValue.start.toDate(getLocalTimeZone()) : new Date(),
91+
end: newValue.end ? newValue.end.toDate(getLocalTimeZone()) : new Date(),
92+
}
93+
},
94+
})
95+
96+
function isRangeSelected(range: { days?: number, months?: number, years?: number }) {
97+
if (!selected.value.start || !selected.value.end) {
98+
return false
99+
}
100+
101+
const currentDate = today(getLocalTimeZone())
102+
let startDate = currentDate.copy()
103+
104+
if (range.days) {
105+
startDate = startDate.subtract({ days: range.days })
106+
} else if (range.months) {
107+
startDate = startDate.subtract({ months: range.months })
108+
} else if (range.years) {
109+
startDate = startDate.subtract({ years: range.years })
110+
}
111+
112+
const selectedStart = toCalendarDate(selected.value.start)
113+
const selectedEnd = toCalendarDate(selected.value.end)
114+
115+
return selectedStart.compare(startDate) === 0 && selectedEnd.compare(currentDate) === 0
116+
}
117+
118+
function selectRange(range: { days?: number, months?: number, years?: number }) {
119+
const endDate = today(getLocalTimeZone())
120+
let startDate = endDate.copy()
121+
122+
if (range.days) {
123+
startDate = startDate.subtract({ days: range.days })
124+
} else if (range.months) {
125+
startDate = startDate.subtract({ months: range.months })
126+
} else if (range.years) {
127+
startDate = startDate.subtract({ years: range.years })
128+
}
129+
130+
selected.value = {
131+
start: startDate.toDate(getLocalTimeZone()),
132+
end: endDate.toDate(getLocalTimeZone()),
133+
}
134+
}
135+
</script>

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
:data="data"
1616
:padding="{ top: 40 }"
1717
:width="width"
18-
class="h-80"
18+
class="h-72"
1919
>
2020
<VisLine
2121
:x="x"
@@ -103,7 +103,8 @@ const lineDashArray = (_: DataRecord, i: number) => [i === 0 ? undefined : 3]
103103
104104
const total = computed(() => {
105105
const count = data.value.filter((d) => d.averageCheck).length
106-
return Math.floor(data.value.reduce((acc: number, { averageCheck }) => acc + averageCheck, 0) / count)
106+
const avg = data.value.reduce((acc: number, { averageCheck }) => acc + averageCheck, 0)
107+
return avg > 0 && count > 0 ? Math.floor(avg / count) : 0
107108
})
108109
109110
const formatNumber = new Intl.NumberFormat('ru', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 }).format
@@ -124,7 +125,7 @@ function xTicks(i: number) {
124125
return formatDate(data.value[i].date)
125126
}
126127
127-
const template = (d: DataRecord) => `<strong>${formatDate(d.date)}, ${format(d.date, 'eeee', { locale: ru })}</strong><br> Средний чек: ${formatNumber(d.averageCheck)}<br> Среднее по сети: ${formatNumber(d.commonAverageCheck)}`
128+
const template = (d: DataRecord) => `<strong>${formatDate(d.date)}, ${format(d.date, 'eeee', { locale: ru })}</strong><br> Средний чек: ${formatNumber(d.averageCheck)}<br> Средний по сети: ${formatNumber(d.commonAverageCheck)}`
128129
</script>
129130

130131
<style scoped>

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
:data="data"
1616
:padding="{ top: 40 }"
1717
:width="width"
18-
class="h-80"
18+
class="h-72"
1919
>
2020
<VisLine
2121
:x="x"
@@ -56,13 +56,14 @@ type DataRecord = {
5656
date: Date
5757
total: number
5858
checks: number
59+
averageCheck: number
5960
commonTotal: number
6061
}
6162
6263
const { period, range, values } = defineProps<{
6364
period: Period
6465
range: Range
65-
values: { date: string, total: number, checks: number, commonTotal: number }[]
66+
values: { date: string, total: number, checks: number, averageCheck: number, commonTotal: number }[]
6667
}>()
6768
6869
const cardRef = useTemplateRef<HTMLElement | null>('cardRef')
@@ -86,6 +87,7 @@ watch([() => period, () => range, () => values], () => {
8687
date,
8788
total: value?.total ?? 0,
8889
checks: value?.checks ?? 0,
90+
averageCheck: value?.averageCheck ?? 0,
8991
commonTotal: value?.commonTotal ?? 0,
9092
}
9193
})
@@ -121,7 +123,7 @@ function xTicks(i: number) {
121123
return formatDate(data.value[i].date)
122124
}
123125
124-
const template = (d: DataRecord) => `<strong>${formatDate(d.date)}, ${format(d.date, 'eeee', { locale: ru })}</strong><br> ${d.checks} ${pluralizationRu(d.checks, ['чек', 'чека', 'чеков'])}<br> Выручка: ${formatNumber(d.total)}<br> Среднее по сети: ${formatNumber(d.commonTotal)}`
126+
const template = (d: DataRecord) => `<strong>${formatDate(d.date)}, ${format(d.date, 'eeee', { locale: ru })}</strong><br> ${d.checks} ${pluralizationRu(d.checks, ['чек', 'чека', 'чеков'])}, средний ${formatNumber(d.averageCheck)}<br> Выручка: ${formatNumber(d.total)}<br> Средняя по сети: ${formatNumber(d.commonTotal)}`
125127
</script>
126128

127129
<style scoped>

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<template>
22
<Content>
3+
<div>
4+
<DateRangePicker v-model="range" />
5+
</div>
6+
37
<ChartKitchenRevenue
48
:period="period"
59
:range="range"
@@ -22,9 +26,10 @@ const { params } = useRoute('kitchen-id')
2226
2327
const { data } = useFetch(`/api/kitchen/id/${params.id}/revenue`)
2428
29+
const today = new Date()
2530
const range = shallowRef<Range>({
26-
start: sub(new Date(), { days: 89 }),
27-
end: new Date(),
31+
start: sub(today, { days: 14 - 1 }),
32+
end: today,
2833
})
2934
const period = ref<Period>('daily')
3035
</script>

0 commit comments

Comments
 (0)