Skip to content

Commit 9620c89

Browse files
committed
migrate daterange picker to shadcn component
1 parent f9c3f42 commit 9620c89

File tree

1 file changed

+204
-102
lines changed

1 file changed

+204
-102
lines changed

resources/js/packages/ui/src/Input/DateRangePicker.vue

Lines changed: 204 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,157 +1,259 @@
11
<script setup lang="ts">
2-
import { CalendarIcon } from '@heroicons/vue/20/solid';
3-
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
4-
import DatePicker from '@/packages/ui/src/Input/DatePicker.vue';
52
import {
6-
formatDateLocalized,
3+
Popover,
4+
PopoverContent,
5+
PopoverTrigger,
6+
} from '@/Components/ui/popover';
7+
import { RangeCalendar } from '@/Components/ui/range-calendar';
8+
import {
9+
CalendarDate,
10+
getLocalTimeZone,
11+
} from '@internationalized/date';
12+
import { CalendarIcon } from 'lucide-vue-next';
13+
import { computed, ref, inject, type ComputedRef, watch } from 'vue';
14+
import { twMerge } from 'tailwind-merge';
15+
import {
716
getDayJsInstance,
817
getLocalizedDayJs,
918
} from '@/packages/ui/src/utils/time';
10-
import { ref, inject, type ComputedRef } from 'vue';
19+
import { formatDateLocalized } from '@/packages/ui/src/utils/time';
1120
import { type Organization } from '@/packages/api/src';
12-
const start = defineModel('start', { default: '' });
13-
const end = defineModel('end', { default: '' });
1421
15-
const emit = defineEmits(['submit']);
22+
const props = defineProps<{
23+
start: string;
24+
end: string;
25+
}>();
26+
27+
const emit = defineEmits<{
28+
(e: 'update:start', value: string): void;
29+
(e: 'update:end', value: string): void;
30+
(e: 'submit'): void;
31+
}>();
32+
33+
interface CalendarDateRange {
34+
start: CalendarDate | undefined;
35+
end: CalendarDate | undefined;
36+
}
37+
38+
const today = computed(() => {
39+
const now = getDayJsInstance()();
40+
return new CalendarDate(now.year(), now.month() + 1, now.date());
41+
});
42+
43+
const modelValue = computed<CalendarDateRange>({
44+
get: () => ({
45+
start: props.start
46+
? new CalendarDate(
47+
getLocalizedDayJs(props.start).year(),
48+
getLocalizedDayJs(props.start).month() + 1,
49+
getLocalizedDayJs(props.start).date()
50+
)
51+
: undefined,
52+
end: props.end
53+
? new CalendarDate(
54+
getLocalizedDayJs(props.end).year(),
55+
getLocalizedDayJs(props.end).month() + 1,
56+
getLocalizedDayJs(props.end).date()
57+
)
58+
: undefined,
59+
}),
60+
set: (newValue) => {
61+
if (newValue.start) {
62+
const date = newValue.start.toDate(getLocalTimeZone());
63+
emit('update:start', getDayJsInstance()(date).format('YYYY-MM-DD'));
64+
}
65+
if (newValue.end) {
66+
const date = newValue.end.toDate(getLocalTimeZone());
67+
emit('update:end', getDayJsInstance()(date).format('YYYY-MM-DD'));
68+
}
69+
},
70+
});
1671
1772
const open = ref(false);
1873
1974
function setToday() {
20-
start.value = getLocalizedDayJs().startOf('day').format();
21-
end.value = getLocalizedDayJs().endOf('day').format();
22-
emit('submit');
75+
emit(
76+
'update:start',
77+
getLocalizedDayJs().startOf('day').format('YYYY-MM-DD')
78+
);
79+
emit('update:end', getLocalizedDayJs().endOf('day').format('YYYY-MM-DD'));
2380
open.value = false;
2481
}
2582
2683
function setThisWeek() {
27-
start.value = getLocalizedDayJs().startOf('week').format();
28-
end.value = getLocalizedDayJs().endOf('week').format();
29-
emit('submit');
84+
emit(
85+
'update:start',
86+
getLocalizedDayJs().startOf('week').format('YYYY-MM-DD')
87+
);
88+
emit('update:end', getLocalizedDayJs().endOf('week').format('YYYY-MM-DD'));
3089
open.value = false;
3190
}
91+
3292
function setLastWeek() {
33-
start.value = getLocalizedDayJs()
34-
.subtract(1, 'week')
35-
.startOf('week')
36-
.format();
37-
end.value = getLocalizedDayJs().subtract(1, 'week').endOf('week').format();
38-
emit('submit');
93+
emit(
94+
'update:start',
95+
getLocalizedDayJs()
96+
.subtract(1, 'week')
97+
.startOf('week')
98+
.format('YYYY-MM-DD')
99+
);
100+
emit(
101+
'update:end',
102+
getLocalizedDayJs()
103+
.subtract(1, 'week')
104+
.endOf('week')
105+
.format('YYYY-MM-DD')
106+
);
39107
open.value = false;
40108
}
109+
41110
function setLast14Days() {
42-
start.value = getLocalizedDayJs().subtract(14, 'days').format();
43-
end.value = getLocalizedDayJs().format();
44-
emit('submit');
111+
emit(
112+
'update:start',
113+
getLocalizedDayJs().subtract(14, 'days').format('YYYY-MM-DD')
114+
);
115+
emit('update:end', getLocalizedDayJs().format('YYYY-MM-DD'));
45116
open.value = false;
46117
}
118+
47119
function setThisMonth() {
48-
start.value = getLocalizedDayJs().startOf('month').format();
49-
end.value = getLocalizedDayJs().endOf('month').format();
50-
emit('submit');
120+
emit(
121+
'update:start',
122+
getLocalizedDayJs().startOf('month').format('YYYY-MM-DD')
123+
);
124+
emit('update:end', getLocalizedDayJs().endOf('month').format('YYYY-MM-DD'));
51125
open.value = false;
52126
}
127+
53128
function setLastMonth() {
54-
start.value = getLocalizedDayJs()
55-
.subtract(1, 'month')
56-
.startOf('month')
57-
.format();
58-
end.value = getLocalizedDayJs()
59-
.subtract(1, 'month')
60-
.endOf('month')
61-
.format();
62-
emit('submit');
129+
emit(
130+
'update:start',
131+
getLocalizedDayJs()
132+
.subtract(1, 'month')
133+
.startOf('month')
134+
.format('YYYY-MM-DD')
135+
);
136+
emit(
137+
'update:end',
138+
getLocalizedDayJs()
139+
.subtract(1, 'month')
140+
.endOf('month')
141+
.format('YYYY-MM-DD')
142+
);
63143
open.value = false;
64144
}
145+
65146
function setLast30Days() {
66-
start.value = getLocalizedDayJs().subtract(30, 'days').format();
67-
end.value = getLocalizedDayJs().format();
68-
emit('submit');
147+
emit(
148+
'update:start',
149+
getLocalizedDayJs().subtract(30, 'days').format('YYYY-MM-DD')
150+
);
151+
emit('update:end', getLocalizedDayJs().format('YYYY-MM-DD'));
69152
open.value = false;
70153
}
154+
71155
function setLast90Days() {
72-
start.value = getDayJsInstance()().subtract(90, 'days').format();
73-
end.value = getDayJsInstance()().format();
74-
emit('submit');
156+
emit(
157+
'update:start',
158+
getDayJsInstance()().subtract(90, 'days').format('YYYY-MM-DD')
159+
);
160+
emit('update:end', getDayJsInstance()().format('YYYY-MM-DD'));
75161
open.value = false;
76162
}
163+
77164
function setLast12Months() {
78-
start.value = getLocalizedDayJs().subtract(12, 'months').format();
79-
end.value = getLocalizedDayJs().format();
80-
emit('submit');
165+
emit(
166+
'update:start',
167+
getLocalizedDayJs().subtract(12, 'months').format('YYYY-MM-DD')
168+
);
169+
emit('update:end', getLocalizedDayJs().format('YYYY-MM-DD'));
81170
open.value = false;
82171
}
172+
83173
function setThisYear() {
84-
start.value = getLocalizedDayJs().startOf('year').format();
85-
end.value = getLocalizedDayJs().endOf('year').format();
86-
emit('submit');
174+
emit(
175+
'update:start',
176+
getLocalizedDayJs().startOf('year').format('YYYY-MM-DD')
177+
);
178+
emit('update:end', getLocalizedDayJs().endOf('year').format('YYYY-MM-DD'));
87179
open.value = false;
88180
}
181+
89182
function setLastYear() {
90-
start.value = getLocalizedDayJs()
91-
.subtract(1, 'year')
92-
.startOf('year')
93-
.format();
94-
end.value = getLocalizedDayJs().subtract(1, 'year').endOf('year').format();
95-
emit('submit');
183+
emit(
184+
'update:start',
185+
getLocalizedDayJs()
186+
.subtract(1, 'year')
187+
.startOf('year')
188+
.format('YYYY-MM-DD')
189+
);
190+
emit(
191+
'update:end',
192+
getLocalizedDayJs()
193+
.subtract(1, 'year')
194+
.endOf('year')
195+
.format('YYYY-MM-DD')
196+
);
96197
open.value = false;
97198
}
98199
99200
const organization = inject<ComputedRef<Organization>>('organization');
201+
202+
watch(open, (value) => {
203+
if (value === false) {
204+
emit('submit');
205+
}
206+
});
100207
</script>
101208

102209
<template>
103-
<Dropdown
104-
v-model="open"
105-
:close-on-content-click="false"
106-
align="end"
107-
@submit="emit('submit')">
108-
<template #trigger>
210+
<Popover v-model:open="open">
211+
<PopoverTrigger as-child>
109212
<button
110-
class="px-2 py-1 bg-input-background border border-input-border font-medium rounded-lg flex items-center space-x-2">
111-
<CalendarIcon class="w-5"></CalendarIcon>
112-
<div class="text-text-primary">
113-
{{ formatDateLocalized(start, organization?.date_format) }}
114-
<span class="px-1.5 text-text-secondary">-</span>
115-
{{ formatDateLocalized(end, organization?.date_format) }}
116-
</div>
213+
:class="
214+
twMerge(
215+
'flex w-full items-center justify-between whitespace-nowrap rounded-md border border-input-border bg-input-background px-3 h-[34px] shadow-sm data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:truncate text-start',
216+
!modelValue && 'text-muted-foreground'
217+
)
218+
">
219+
<CalendarIcon class="mr-2 h-4 w-4" />
220+
<template v-if="modelValue.start">
221+
<template v-if="modelValue.end">
222+
{{ formatDateLocalized(modelValue.start.toString(), organization?.date_format) }}
223+
-
224+
{{ formatDateLocalized(modelValue.end.toString(), organization?.date_format) }}
225+
</template>
226+
<template v-else>
227+
{{ formatDateLocalized(modelValue.start.toString(), organization?.date_format) }}
228+
</template>
229+
</template>
230+
<template v-else> Pick a date </template>
117231
</button>
118-
</template>
119-
<template #content>
120-
<div class="overflow-hidden w-[330px] px-3 py-1.5">
232+
</PopoverTrigger>
233+
<PopoverContent class="w-auto p-0">
234+
<div class="flex divide-x divide-border-secondary">
121235
<div
122-
class="flex divide-x divide-border-secondary justify-between">
123-
<div
124-
class="text-text-primary text-sm flex flex-col space-y-0.5 items-start py-2 [&_button:hover]:bg-tertiary [&_button]:rounded [&_button]:px-2 [&_button]:py-1">
125-
<button @click="setToday">Today</button>
126-
<button @click="setThisWeek">This Week</button>
127-
<button @click="setLastWeek">Last Week</button>
128-
<button @click="setLast14Days">Last 14 days</button>
129-
<button @click="setThisMonth">This Month</button>
130-
<button @click="setLastMonth">Last Month</button>
131-
<button @click="setLast30Days">Last 30 days</button>
132-
<button @click="setLast90Days">Last 90 days</button>
133-
<button @click="setLast12Months">Last 12 months</button>
134-
<button @click="setThisYear">This year</button>
135-
<button @click="setLastYear">Last year</button>
136-
</div>
137-
<div class="pl-5">
138-
<div class="space-y-1 flex-col flex items-start">
139-
<div class="text-xs font-semibold text-text-secondary">
140-
Start Date
141-
</div>
142-
<DatePicker v-model="start"></DatePicker>
143-
</div>
144-
<div class="mt-2 space-y-1 flex-col flex items-start">
145-
<div class="text-sm font-medium text-text-secondary">
146-
End Date
147-
</div>
148-
<DatePicker v-model="end"></DatePicker>
149-
</div>
150-
</div>
236+
class="text-text-primary text-sm flex flex-col space-y-0.5 items-start py-2 px-2 [&_button:hover]:bg-tertiary [&_button]:rounded [&_button]:px-2 [&_button]:py-1">
237+
<button @click="setToday">Today</button>
238+
<button @click="setThisWeek">This Week</button>
239+
<button @click="setLastWeek">Last Week</button>
240+
<button @click="setLast14Days">Last 14 days</button>
241+
<button @click="setThisMonth">This Month</button>
242+
<button @click="setLastMonth">Last Month</button>
243+
<button @click="setLast30Days">Last 30 days</button>
244+
<button @click="setLast90Days">Last 90 days</button>
245+
<button @click="setLast12Months">Last 12 months</button>
246+
<button @click="setThisYear">This year</button>
247+
<button @click="setLastYear">Last year</button>
248+
</div>
249+
<div class="pl-2">
250+
<RangeCalendar
251+
v-model="modelValue"
252+
initial-focus
253+
:number-of-months="2"
254+
:max-value="today" />
151255
</div>
152256
</div>
153-
</template>
154-
</Dropdown>
257+
</PopoverContent>
258+
</Popover>
155259
</template>
156-
157-
<style scoped></style>

0 commit comments

Comments
 (0)