mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-10 01:02:15 +01:00
225 lines
8.1 KiB
Vue
225 lines
8.1 KiB
Vue
<script setup lang="ts">
|
|
import { Popover, PopoverContent, PopoverTrigger } from '../popover';
|
|
import Button from '../Buttons/Button.vue';
|
|
import { RangeCalendar } from '../range-calendar';
|
|
import { CalendarDate } from '@internationalized/date';
|
|
import { CalendarIcon } from '@lucide/vue';
|
|
import { computed, ref, inject, type ComputedRef, watch } from 'vue';
|
|
import { twMerge } from 'tailwind-merge';
|
|
import {
|
|
getDayJsInstance,
|
|
getLocalizedDayJs,
|
|
firstDayIndex,
|
|
type WeekStartDay,
|
|
} from '@/packages/ui/src/utils/time';
|
|
import { type Organization } from '@/packages/api/src';
|
|
import { getUserTimezone } from '@/packages/ui/src/utils/settings';
|
|
import { formatDate } from '@/packages/ui/src/utils/time';
|
|
|
|
const weekStartsOn = computed((): WeekStartDay => firstDayIndex.value as WeekStartDay);
|
|
|
|
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(getUserTimezone());
|
|
emit('update:start', getLocalizedDayJs(date.toString()).format());
|
|
}
|
|
if (newValue.end) {
|
|
const date = newValue.end.toDate(getUserTimezone());
|
|
emit('update:end', getLocalizedDayJs(date.toString()).format());
|
|
} else {
|
|
emit('update:end', '');
|
|
}
|
|
},
|
|
});
|
|
|
|
const open = ref(false);
|
|
|
|
function setToday() {
|
|
emit('update:start', getLocalizedDayJs().startOf('day').format());
|
|
emit('update:end', getLocalizedDayJs().endOf('day').format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setThisWeek() {
|
|
emit('update:start', getLocalizedDayJs().startOf('week').format());
|
|
emit('update:end', getLocalizedDayJs().endOf('week').format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setLastWeek() {
|
|
emit('update:start', getLocalizedDayJs().subtract(1, 'week').startOf('week').format());
|
|
emit('update:end', getLocalizedDayJs().subtract(1, 'week').endOf('week').format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setLast14Days() {
|
|
emit('update:start', getLocalizedDayJs().subtract(14, 'days').format());
|
|
emit('update:end', getLocalizedDayJs().format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setThisMonth() {
|
|
emit('update:start', getLocalizedDayJs().startOf('month').format());
|
|
emit('update:end', getLocalizedDayJs().endOf('month').format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setLastMonth() {
|
|
emit('update:start', getLocalizedDayJs().subtract(1, 'month').startOf('month').format());
|
|
emit('update:end', getLocalizedDayJs().subtract(1, 'month').endOf('month').format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setLast30Days() {
|
|
emit('update:start', getLocalizedDayJs().subtract(30, 'days').format());
|
|
emit('update:end', getLocalizedDayJs().format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setLast90Days() {
|
|
emit('update:start', getDayJsInstance()().subtract(90, 'days').format());
|
|
emit('update:end', getDayJsInstance()().format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setLast12Months() {
|
|
emit('update:start', getLocalizedDayJs().subtract(12, 'months').format());
|
|
emit('update:end', getLocalizedDayJs().format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setThisYear() {
|
|
emit('update:start', getLocalizedDayJs().startOf('year').format());
|
|
emit('update:end', getLocalizedDayJs().endOf('year').format());
|
|
open.value = false;
|
|
}
|
|
|
|
function setLastYear() {
|
|
emit('update:start', getLocalizedDayJs().subtract(1, 'year').startOf('year').format());
|
|
emit('update:end', getLocalizedDayJs().subtract(1, 'year').endOf('year').format());
|
|
open.value = false;
|
|
}
|
|
|
|
const organization = inject<ComputedRef<Organization>>('organization');
|
|
|
|
watch(open, (value) => {
|
|
if (value === false) {
|
|
emit('submit');
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<Popover v-model:open="open">
|
|
<PopoverTrigger as-child>
|
|
<Button
|
|
variant="outline"
|
|
:class="
|
|
twMerge(
|
|
'flex w-full items-center justify-between whitespace-nowrap h-[34px] text-start',
|
|
!modelValue && 'text-muted-foreground'
|
|
)
|
|
">
|
|
<CalendarIcon class="-ml-0.5 text-text-quaternary h-4 w-4" />
|
|
<template v-if="modelValue.start">
|
|
<template v-if="modelValue.end">
|
|
{{ formatDate(modelValue.start.toString(), organization?.date_format) }}
|
|
-
|
|
{{ formatDate(modelValue.end.toString(), organization?.date_format) }}
|
|
</template>
|
|
<template v-else>
|
|
{{ formatDate(modelValue.start.toString(), organization?.date_format) }}
|
|
</template>
|
|
</template>
|
|
<template v-else> Pick a date </template>
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent class="w-auto p-0">
|
|
<div class="flex divide-x divide-border-secondary">
|
|
<div
|
|
class="text-text-primary text-sm flex flex-col space-y-0.5 items-start py-2 px-2">
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setToday"
|
|
>Today</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setThisWeek"
|
|
>This Week</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setLastWeek"
|
|
>Last Week</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setLast14Days"
|
|
>Last 14 days</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setThisMonth"
|
|
>This Month</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setLastMonth"
|
|
>Last Month</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setLast30Days"
|
|
>Last 30 days</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setLast90Days"
|
|
>Last 90 days</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setLast12Months"
|
|
>Last 12 months</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setThisYear"
|
|
>This year</Button
|
|
>
|
|
<Button variant="ghost" size="sm" class="justify-start" @click="setLastYear"
|
|
>Last year</Button
|
|
>
|
|
</div>
|
|
<div class="pl-2">
|
|
<RangeCalendar
|
|
v-model="modelValue"
|
|
initial-focus
|
|
:number-of-months="2"
|
|
:max-value="today"
|
|
:week-starts-on="weekStartsOn" />
|
|
</div>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
</template>
|