df531908f3
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
111 lines
3.0 KiB
Vue
111 lines
3.0 KiB
Vue
<script setup lang="ts" generic="S extends TimeSpanLabel">
|
|
import { computed } from 'vue';
|
|
import { spanToDays, type TimeSpanLabel } from '@/types/spans';
|
|
import { daysVisibleRange, shiftLocalDays, shiftLocalMonths } from '@/utils/date';
|
|
|
|
const props = defineProps<{
|
|
mode: 'days' | 'month';
|
|
currentDate: Date;
|
|
spans?: readonly S[];
|
|
selectedSpan?: S;
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
'update:current-date': [date: Date];
|
|
'update:span': [span: S];
|
|
}>();
|
|
|
|
const spanDays = computed(() => props.selectedSpan ? spanToDays(props.selectedSpan) : 1);
|
|
|
|
function previousPeriod() {
|
|
emit('update:current-date', props.mode === 'month'
|
|
? shiftLocalMonths(props.currentDate, -1)
|
|
: shiftLocalDays(props.currentDate, -spanDays.value));
|
|
}
|
|
|
|
function nextPeriod() {
|
|
emit('update:current-date', props.mode === 'month'
|
|
? shiftLocalMonths(props.currentDate, 1)
|
|
: shiftLocalDays(props.currentDate, spanDays.value));
|
|
}
|
|
|
|
function goToToday() {
|
|
emit('update:current-date', new Date());
|
|
}
|
|
|
|
const periodLabel = computed(() => {
|
|
if (props.mode === 'month') {
|
|
return props.currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
|
}
|
|
|
|
const range = daysVisibleRange(props.currentDate, spanDays.value);
|
|
const start = new Date(range.start);
|
|
const end = shiftLocalDays(new Date(range.end), -1);
|
|
const formatOptions: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' };
|
|
|
|
if (spanDays.value === 1) {
|
|
return start.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
|
}
|
|
|
|
if (start.getMonth() === end.getMonth()) {
|
|
return `${start.toLocaleDateString('en-US', formatOptions)} - ${end.getDate()}, ${start.getFullYear()}`;
|
|
}
|
|
|
|
return `${start.toLocaleDateString('en-US', formatOptions)} - ${end.toLocaleDateString('en-US', formatOptions)}, ${end.getFullYear()}`;
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<div class="view-controls">
|
|
<div class="view-navigation">
|
|
<v-btn size="x-small" variant="text" icon="mdi-chevron-left" @click="previousPeriod" />
|
|
<v-btn size="x-small" variant="tonal" @click="goToToday">Today</v-btn>
|
|
<v-btn size="x-small" variant="text" icon="mdi-chevron-right" @click="nextPeriod" />
|
|
<span class="current-period">{{ periodLabel }}</span>
|
|
</div>
|
|
<v-spacer />
|
|
<div v-if="spans?.length" class="view-selector">
|
|
<v-btn
|
|
v-for="span in spans"
|
|
:key="span"
|
|
size="x-small"
|
|
variant="text"
|
|
:color="selectedSpan === span ? 'primary' : undefined"
|
|
@click="emit('update:span', span)"
|
|
>
|
|
{{ span.toUpperCase() }}
|
|
</v-btn>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.view-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 4px 8px;
|
|
border-bottom: 1px solid rgb(var(--v-border-color));
|
|
background-color: rgb(var(--v-theme-surface));
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.view-navigation {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.current-period {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
margin-left: 8px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.view-selector {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
</style>
|