Files
RenewCMS/web/src/components/ActivityDatePresets.vue
T

165 lines
4.1 KiB
Vue

<script setup lang="ts">
import { t } from '@/i18n'
import { computed, ref, watch } from 'vue'
import { CalendarDate, parseDate } from '@internationalized/date'
import { RANGE_KEYS, rangeLabel, type RangeKey } from '@/lib/activity'
import { SEGMENT_GROUP, segmentClass } from '@/lib/segmentStyles'
interface RangeChange {
range: RangeKey
from: string
to: string
}
interface CalendarRange {
start?: CalendarDate
end?: CalendarDate
}
const props = defineProps<{
range: RangeKey
from: string
to: string
block?: boolean
}>()
const emit = defineEmits<{
change: [value: RangeChange]
}>()
const popoverOpen = ref(false)
const customPending = ref(false)
const draftFrom = ref(props.from)
const draftTo = ref(props.to)
const presets = RANGE_KEYS.filter((key) => key !== 'custom')
const customSelected = computed(
() => props.range === 'custom' || popoverOpen.value || customPending.value,
)
const showFields = computed(() => props.block && customSelected.value)
function parse(value: string) {
try {
return parseDate(value)
} catch {
return undefined
}
}
const calendarValue = computed<CalendarRange>(() => ({
start: parse(props.from),
end: parse(props.to),
}))
watch(
() => [props.from, props.to],
([from, to]) => {
draftFrom.value = from ?? ''
draftTo.value = to ?? ''
},
)
function choose(range: RangeKey) {
customPending.value = false
emit('change', { range, from: '', to: '' })
}
function chooseCustom() {
if (props.block) customPending.value = true
}
function applyCustom(from: string, to: string) {
customPending.value = false
emit(
'change',
from <= to ? { range: 'custom', from, to } : { range: 'custom', from: to, to: from },
)
}
function onCalendar(value: CalendarRange | null) {
if (!value?.start || !value.end) return
popoverOpen.value = false
applyCustom(value.start.toString(), value.end.toString())
}
function onField(side: 'from' | 'to', value: string | number | null | undefined) {
const next = typeof value === 'string' ? value : ''
if (side === 'from') draftFrom.value = next
else draftTo.value = next
if (draftFrom.value && draftTo.value) applyCustom(draftFrom.value, draftTo.value)
}
</script>
<template>
<div class="flex flex-col gap-2" :class="block ? 'w-full' : ''">
<div role="group" :aria-label="t('Dates')" :class="[SEGMENT_GROUP, block ? 'w-full' : '']">
<UButton
v-for="key in presets"
:key="key"
color="neutral"
:variant="range === key && !customSelected ? 'soft' : 'outline'"
:aria-pressed="range === key && !customSelected"
:class="segmentClass(range === key && !customSelected, block)"
@click="choose(key)"
>
{{ rangeLabel(key) }}
</UButton>
<UButton
v-if="block"
color="neutral"
:variant="customSelected ? 'soft' : 'outline'"
icon="i-lucide-calendar"
:aria-pressed="customSelected"
:class="segmentClass(customSelected, block)"
:ui="{ leadingIcon: 'size-3.5' }"
@click="chooseCustom"
>
{{ rangeLabel('custom') }}
</UButton>
<UPopover v-else v-model:open="popoverOpen" :content="{ align: 'end' }">
<UButton
color="neutral"
:variant="customSelected ? 'soft' : 'outline'"
icon="i-lucide-calendar"
:aria-pressed="customSelected"
:class="segmentClass(customSelected)"
:ui="{ leadingIcon: 'size-3.5' }"
>
{{ rangeLabel('custom') }}
</UButton>
<template #content>
<UCalendar
range
:model-value="calendarValue"
:number-of-months="1"
class="p-2"
@update:model-value="onCalendar($event as CalendarRange | null)"
/>
</template>
</UPopover>
</div>
<div v-if="showFields" class="flex items-center gap-2">
<UInput
type="date"
icon="i-lucide-calendar"
:model-value="draftFrom"
:aria-label="t('Start date')"
class="min-w-0 flex-1"
:ui="{ base: 'h-11 w-full min-w-0 text-lg' }"
@update:model-value="onField('from', $event)"
/>
<UInput
type="date"
icon="i-lucide-calendar"
:model-value="draftTo"
:aria-label="t('End date')"
class="min-w-0 flex-1"
:ui="{ base: 'h-11 w-full min-w-0 text-lg' }"
@update:model-value="onField('to', $event)"
/>
</div>
</div>
</template>