mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-17 20:52:14 +01:00
migrate daterange picker to shadcn component
This commit is contained in:
@@ -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>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user