overhauled calendar header
This commit is contained in:
@@ -1,10 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch, nextTick, computed } from 'vue'
|
import { ref, watch, nextTick, computed, onMounted } from 'vue'
|
||||||
import FullCalendar from '@fullcalendar/vue3'
|
import FullCalendar from '@fullcalendar/vue3'
|
||||||
import dayGridPlugin from '@fullcalendar/daygrid'
|
import dayGridPlugin from '@fullcalendar/daygrid'
|
||||||
import interactionPlugin from '@fullcalendar/interaction'
|
import interactionPlugin from '@fullcalendar/interaction'
|
||||||
import { X, Clock, MapPin, User, ListTodo } from 'lucide-vue-next'
|
import { X, Clock, MapPin, User, ListTodo, ChevronLeft, ChevronRight, Plus } from 'lucide-vue-next'
|
||||||
|
|
||||||
|
const monthLabels = [
|
||||||
|
'January', 'February', 'March', 'April', 'May', 'June',
|
||||||
|
'July', 'August', 'September', 'October', 'November', 'December'
|
||||||
|
]
|
||||||
|
|
||||||
|
const selectedMonth = ref<number>(new Date().getMonth())
|
||||||
|
const selectedYear = ref<number>(new Date().getFullYear())
|
||||||
|
const years = Array.from({ length: 41 }, (_, i) => selectedYear.value - 20 + i) // +/- 20 yrs
|
||||||
|
|
||||||
|
function api() {
|
||||||
|
return calendarRef.value?.getApi()
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep dropdowns in sync whenever the calendar navigates
|
||||||
|
function onDatesSet() {
|
||||||
|
const d = api()?.getDate() ?? new Date()
|
||||||
|
selectedMonth.value = d.getMonth()
|
||||||
|
selectedYear.value = d.getFullYear()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function goPrev() { api()?.prev() }
|
||||||
|
function goNext() { api()?.next() }
|
||||||
|
function goToday() { api()?.today() }
|
||||||
|
|
||||||
|
// jump to the selected month/year
|
||||||
|
function goToSelectedDate() {
|
||||||
|
api()?.gotoDate(new Date(selectedYear.value, selectedMonth.value, 1))
|
||||||
|
}
|
||||||
|
|
||||||
type CalEvent = {
|
type CalEvent = {
|
||||||
id: string
|
id: string
|
||||||
@@ -52,8 +81,8 @@ const calendarOptions = ref({
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
expandRows: true,
|
expandRows: true,
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
left: 'prev,next today',
|
left: '',
|
||||||
center: 'title',
|
center: '',
|
||||||
right: ''
|
right: ''
|
||||||
},
|
},
|
||||||
events,
|
events,
|
||||||
@@ -98,6 +127,8 @@ const calendarOptions = ref({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
watch(panelOpen, async () => {
|
watch(panelOpen, async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
calendarRef.value?.getApi().updateSize()
|
calendarRef.value?.getApi().updateSize()
|
||||||
@@ -121,6 +152,15 @@ const whenText = computed(() => {
|
|||||||
: `${startFmt.format(s)}`
|
: `${startFmt.format(s)}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function onCreateEvent() {
|
||||||
|
const iso = new Date().toISOString().slice(0, 10) // YYYY-MM-DD
|
||||||
|
onDateClick({ dateStr: iso })
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
onDatesSet()
|
||||||
|
})
|
||||||
|
|
||||||
const ext = computed(() => activeEvent.value?.extendedProps ?? {})
|
const ext = computed(() => activeEvent.value?.extendedProps ?? {})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -128,6 +168,55 @@ const ext = computed(() => activeEvent.value?.extendedProps ?? {})
|
|||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div class="flex-1 min-h-0 mt-5">
|
<div class="flex-1 min-h-0 mt-5">
|
||||||
<div class="h-[80vh] min-h-0">
|
<div class="h-[80vh] min-h-0">
|
||||||
|
<!-- calendar header -->
|
||||||
|
<div class="flex items-center justify-between mx-5">
|
||||||
|
<!-- Left: title + pickers -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<!-- <h2 class="text-xl font-semibold tracking-tight">
|
||||||
|
{{ monthLabels[selectedMonth] }} {{ selectedYear }}
|
||||||
|
</h2> -->
|
||||||
|
|
||||||
|
<!-- Month dropdown -->
|
||||||
|
<select v-model.number="selectedMonth" @change="goToSelectedDate"
|
||||||
|
class="ml-2 rounded-md border bg-transparent px-2 py-1 text-sm" aria-label="Select month">
|
||||||
|
<option v-for="(m, i) in monthLabels" :key="m" :value="i" class="bg-card">
|
||||||
|
{{ m }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Year dropdown -->
|
||||||
|
<select v-model.number="selectedYear" @change="goToSelectedDate"
|
||||||
|
class="rounded-md border bg-transparent px-2 py-1 text-sm" aria-label="Select year">
|
||||||
|
<option v-for="y in years" :key="y" :value="y" class="bg-card">
|
||||||
|
{{ y }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right: nav + today + create -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button
|
||||||
|
class="inline-flex h-8 w-8 items-center justify-center rounded-md border hover:bg-muted/40"
|
||||||
|
aria-label="Previous month" @click="goPrev">
|
||||||
|
<ChevronLeft class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="inline-flex h-8 w-8 items-center justify-center rounded-md border hover:bg-muted/40"
|
||||||
|
aria-label="Next month" @click="goNext">
|
||||||
|
<ChevronRight class="h-4 w-4" />
|
||||||
|
</button>
|
||||||
|
<button class="ml-1 rounded-md border px-3 py-1.5 text-sm hover:bg-muted/40" @click="goToday">
|
||||||
|
Today
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="ml-1 inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:opacity-90"
|
||||||
|
@click="onCreateEvent">
|
||||||
|
<Plus class="h-4 w-4" />
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
<FullCalendar ref="calendarRef" :options="calendarOptions" />
|
<FullCalendar ref="calendarRef" :options="calendarOptions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user