migrate daterange picker to shadcn component

This commit is contained in:
Gregor Vostrak
2025-05-13 16:22:04 +02:00
parent f9c3f42289
commit 9620c89545

View File

@@ -1,157 +1,259 @@
<script setup lang="ts"> <script setup lang="ts">
import { CalendarIcon } from '@heroicons/vue/20/solid';
import Dropdown from '@/packages/ui/src/Input/Dropdown.vue';
import DatePicker from '@/packages/ui/src/Input/DatePicker.vue';
import { import {
formatDateLocalized, Popover,
PopoverContent,
PopoverTrigger,
} from '@/Components/ui/popover';
import { RangeCalendar } from '@/Components/ui/range-calendar';
import {
CalendarDate,
getLocalTimeZone,
} from '@internationalized/date';
import { CalendarIcon } from 'lucide-vue-next';
import { computed, ref, inject, type ComputedRef, watch } from 'vue';
import { twMerge } from 'tailwind-merge';
import {
getDayJsInstance, getDayJsInstance,
getLocalizedDayJs, getLocalizedDayJs,
} from '@/packages/ui/src/utils/time'; } from '@/packages/ui/src/utils/time';
import { ref, inject, type ComputedRef } from 'vue'; import { formatDateLocalized } from '@/packages/ui/src/utils/time';
import { type Organization } from '@/packages/api/src'; import { type Organization } from '@/packages/api/src';
const start = defineModel('start', { default: '' });
const end = defineModel('end', { default: '' });
const emit = defineEmits(['submit']); const props = defineProps<{
start: string;
end: string;
}>();
const emit = defineEmits<{
(e: 'update:start', value: string): void;
(e: 'update:end', value: string): void;
(e: 'submit'): void;
}>();
interface CalendarDateRange {
start: CalendarDate | undefined;
end: CalendarDate | undefined;
}
const today = computed(() => {
const now = getDayJsInstance()();
return new CalendarDate(now.year(), now.month() + 1, now.date());
});
const modelValue = computed<CalendarDateRange>({
get: () => ({
start: props.start
? new CalendarDate(
getLocalizedDayJs(props.start).year(),
getLocalizedDayJs(props.start).month() + 1,
getLocalizedDayJs(props.start).date()
)
: undefined,
end: props.end
? new CalendarDate(
getLocalizedDayJs(props.end).year(),
getLocalizedDayJs(props.end).month() + 1,
getLocalizedDayJs(props.end).date()
)
: undefined,
}),
set: (newValue) => {
if (newValue.start) {
const date = newValue.start.toDate(getLocalTimeZone());
emit('update:start', getDayJsInstance()(date).format('YYYY-MM-DD'));
}
if (newValue.end) {
const date = newValue.end.toDate(getLocalTimeZone());
emit('update:end', getDayJsInstance()(date).format('YYYY-MM-DD'));
}
},
});
const open = ref(false); const open = ref(false);
function setToday() { function setToday() {
start.value = getLocalizedDayJs().startOf('day').format(); emit(
end.value = getLocalizedDayJs().endOf('day').format(); 'update:start',
emit('submit'); getLocalizedDayJs().startOf('day').format('YYYY-MM-DD')
);
emit('update:end', getLocalizedDayJs().endOf('day').format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setThisWeek() { function setThisWeek() {
start.value = getLocalizedDayJs().startOf('week').format(); emit(
end.value = getLocalizedDayJs().endOf('week').format(); 'update:start',
emit('submit'); getLocalizedDayJs().startOf('week').format('YYYY-MM-DD')
);
emit('update:end', getLocalizedDayJs().endOf('week').format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setLastWeek() { function setLastWeek() {
start.value = getLocalizedDayJs() emit(
.subtract(1, 'week') 'update:start',
.startOf('week') getLocalizedDayJs()
.format(); .subtract(1, 'week')
end.value = getLocalizedDayJs().subtract(1, 'week').endOf('week').format(); .startOf('week')
emit('submit'); .format('YYYY-MM-DD')
);
emit(
'update:end',
getLocalizedDayJs()
.subtract(1, 'week')
.endOf('week')
.format('YYYY-MM-DD')
);
open.value = false; open.value = false;
} }
function setLast14Days() { function setLast14Days() {
start.value = getLocalizedDayJs().subtract(14, 'days').format(); emit(
end.value = getLocalizedDayJs().format(); 'update:start',
emit('submit'); getLocalizedDayJs().subtract(14, 'days').format('YYYY-MM-DD')
);
emit('update:end', getLocalizedDayJs().format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setThisMonth() { function setThisMonth() {
start.value = getLocalizedDayJs().startOf('month').format(); emit(
end.value = getLocalizedDayJs().endOf('month').format(); 'update:start',
emit('submit'); getLocalizedDayJs().startOf('month').format('YYYY-MM-DD')
);
emit('update:end', getLocalizedDayJs().endOf('month').format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setLastMonth() { function setLastMonth() {
start.value = getLocalizedDayJs() emit(
.subtract(1, 'month') 'update:start',
.startOf('month') getLocalizedDayJs()
.format(); .subtract(1, 'month')
end.value = getLocalizedDayJs() .startOf('month')
.subtract(1, 'month') .format('YYYY-MM-DD')
.endOf('month') );
.format(); emit(
emit('submit'); 'update:end',
getLocalizedDayJs()
.subtract(1, 'month')
.endOf('month')
.format('YYYY-MM-DD')
);
open.value = false; open.value = false;
} }
function setLast30Days() { function setLast30Days() {
start.value = getLocalizedDayJs().subtract(30, 'days').format(); emit(
end.value = getLocalizedDayJs().format(); 'update:start',
emit('submit'); getLocalizedDayJs().subtract(30, 'days').format('YYYY-MM-DD')
);
emit('update:end', getLocalizedDayJs().format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setLast90Days() { function setLast90Days() {
start.value = getDayJsInstance()().subtract(90, 'days').format(); emit(
end.value = getDayJsInstance()().format(); 'update:start',
emit('submit'); getDayJsInstance()().subtract(90, 'days').format('YYYY-MM-DD')
);
emit('update:end', getDayJsInstance()().format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setLast12Months() { function setLast12Months() {
start.value = getLocalizedDayJs().subtract(12, 'months').format(); emit(
end.value = getLocalizedDayJs().format(); 'update:start',
emit('submit'); getLocalizedDayJs().subtract(12, 'months').format('YYYY-MM-DD')
);
emit('update:end', getLocalizedDayJs().format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setThisYear() { function setThisYear() {
start.value = getLocalizedDayJs().startOf('year').format(); emit(
end.value = getLocalizedDayJs().endOf('year').format(); 'update:start',
emit('submit'); getLocalizedDayJs().startOf('year').format('YYYY-MM-DD')
);
emit('update:end', getLocalizedDayJs().endOf('year').format('YYYY-MM-DD'));
open.value = false; open.value = false;
} }
function setLastYear() { function setLastYear() {
start.value = getLocalizedDayJs() emit(
.subtract(1, 'year') 'update:start',
.startOf('year') getLocalizedDayJs()
.format(); .subtract(1, 'year')
end.value = getLocalizedDayJs().subtract(1, 'year').endOf('year').format(); .startOf('year')
emit('submit'); .format('YYYY-MM-DD')
);
emit(
'update:end',
getLocalizedDayJs()
.subtract(1, 'year')
.endOf('year')
.format('YYYY-MM-DD')
);
open.value = false; open.value = false;
} }
const organization = inject<ComputedRef<Organization>>('organization'); const organization = inject<ComputedRef<Organization>>('organization');
watch(open, (value) => {
if (value === false) {
emit('submit');
}
});
</script> </script>
<template> <template>
<Dropdown <Popover v-model:open="open">
v-model="open" <PopoverTrigger as-child>
:close-on-content-click="false"
align="end"
@submit="emit('submit')">
<template #trigger>
<button <button
class="px-2 py-1 bg-input-background border border-input-border font-medium rounded-lg flex items-center space-x-2"> :class="
<CalendarIcon class="w-5"></CalendarIcon> twMerge(
<div class="text-text-primary"> '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',
{{ formatDateLocalized(start, organization?.date_format) }} !modelValue && 'text-muted-foreground'
<span class="px-1.5 text-text-secondary">-</span> )
{{ formatDateLocalized(end, organization?.date_format) }} ">
</div> <CalendarIcon class="mr-2 h-4 w-4" />
<template v-if="modelValue.start">
<template v-if="modelValue.end">
{{ formatDateLocalized(modelValue.start.toString(), organization?.date_format) }}
-
{{ formatDateLocalized(modelValue.end.toString(), organization?.date_format) }}
</template>
<template v-else>
{{ formatDateLocalized(modelValue.start.toString(), organization?.date_format) }}
</template>
</template>
<template v-else> Pick a date </template>
</button> </button>
</template> </PopoverTrigger>
<template #content> <PopoverContent class="w-auto p-0">
<div class="overflow-hidden w-[330px] px-3 py-1.5"> <div class="flex divide-x divide-border-secondary">
<div <div
class="flex divide-x divide-border-secondary justify-between"> 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">
<div <button @click="setToday">Today</button>
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"> <button @click="setThisWeek">This Week</button>
<button @click="setToday">Today</button> <button @click="setLastWeek">Last Week</button>
<button @click="setThisWeek">This Week</button> <button @click="setLast14Days">Last 14 days</button>
<button @click="setLastWeek">Last Week</button> <button @click="setThisMonth">This Month</button>
<button @click="setLast14Days">Last 14 days</button> <button @click="setLastMonth">Last Month</button>
<button @click="setThisMonth">This Month</button> <button @click="setLast30Days">Last 30 days</button>
<button @click="setLastMonth">Last Month</button> <button @click="setLast90Days">Last 90 days</button>
<button @click="setLast30Days">Last 30 days</button> <button @click="setLast12Months">Last 12 months</button>
<button @click="setLast90Days">Last 90 days</button> <button @click="setThisYear">This year</button>
<button @click="setLast12Months">Last 12 months</button> <button @click="setLastYear">Last year</button>
<button @click="setThisYear">This year</button> </div>
<button @click="setLastYear">Last year</button> <div class="pl-2">
</div> <RangeCalendar
<div class="pl-5"> v-model="modelValue"
<div class="space-y-1 flex-col flex items-start"> initial-focus
<div class="text-xs font-semibold text-text-secondary"> :number-of-months="2"
Start Date :max-value="today" />
</div>
<DatePicker v-model="start"></DatePicker>
</div>
<div class="mt-2 space-y-1 flex-col flex items-start">
<div class="text-sm font-medium text-text-secondary">
End Date
</div>
<DatePicker v-model="end"></DatePicker>
</div>
</div>
</div> </div>
</div> </div>
</template> </PopoverContent>
</Dropdown> </Popover>
</template> </template>
<style scoped></style>