Merge branch 'features/nuxt-supabase' into features/nuxt
This commit is contained in:
@@ -7,10 +7,12 @@ import CenturyLayout from './state/centennially/Layout.vue'
|
||||
import DecadeLayout from './state/decennially/Layout.vue'
|
||||
import YearLayout from './state/yearly/Layout.vue'
|
||||
|
||||
const { isAdvancedSearchOpen, isEditEventModalOpen } = storeToRefs(useCalendar())
|
||||
|
||||
const route = useRoute()
|
||||
const worldId = route.params.id
|
||||
|
||||
const { setMonths, setDefaultDate, currentConfig, selectedDate, jumpToDate } = useCalendar()
|
||||
const { setCalendarId, setMonths, setDefaultDate, currentConfig, selectedDate, jumpToDate } = useCalendar()
|
||||
const { setEvents } = useCalendarEvents()
|
||||
const { setCharacters } = useCharacters()
|
||||
|
||||
@@ -20,6 +22,12 @@ const { data: characters, pending: charPending, refresh: charRefresh } = await u
|
||||
if (!calendar.value) {
|
||||
await calRefresh()
|
||||
} else {
|
||||
if (calendar.value?.data?.id) {
|
||||
setCalendarId(calendar.value?.data?.id)
|
||||
}
|
||||
if (calendar.value?.data?.months) {
|
||||
setMonths(calendar.value?.data?.months)
|
||||
}
|
||||
if (calendar.value?.data?.months) {
|
||||
setMonths(calendar.value?.data?.months)
|
||||
}
|
||||
@@ -40,6 +48,9 @@ if (!characters.value) {
|
||||
|
||||
watch(calPending, (newStatus) => {
|
||||
if (!newStatus) {
|
||||
if (calendar.value?.data?.id) {
|
||||
setCalendarId(calendar.value?.data?.id)
|
||||
}
|
||||
if (calendar.value?.data?.months) {
|
||||
setMonths(calendar.value?.data?.months)
|
||||
}
|
||||
@@ -97,5 +108,8 @@ onMounted(() => {
|
||||
</KeepAlive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
|
||||
<CalendarFormUpdateEvent v-model:model-value="isEditEventModalOpen" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,15 +17,15 @@ const isStartEvent = spansMultipleDays && areDatesIdentical(props.tileDate, prop
|
||||
const isEndEvent =
|
||||
spansMultipleDays && props.event.endDate && areDatesIdentical(props.tileDate, props.event.endDate)
|
||||
|
||||
const isPopoverOpen = ref<boolean>(false)
|
||||
const isPopoverDetailsOpen = ref<boolean>(false)
|
||||
|
||||
function handleClosePopover() {
|
||||
isPopoverOpen.value = false
|
||||
isPopoverDetailsOpen.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiPopover v-model:open="isPopoverOpen">
|
||||
<UiPopover v-model:open="isPopoverDetailsOpen">
|
||||
<UiPopoverTrigger as-child>
|
||||
<button
|
||||
class="text-xs px-2 py-1 block w-full text-left rounded-sm whitespace-nowrap overflow-hidden text-ellipsis"
|
||||
@@ -60,7 +60,6 @@ function handleClosePopover() {
|
||||
</UiPopoverTrigger>
|
||||
|
||||
<CalendarEventDetails
|
||||
v-once
|
||||
:event
|
||||
:spans-multiple-days
|
||||
:is-start-event
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
import type { RPGDate } from '@/models/Date'
|
||||
import type { CalendarEvent } from '@/models/CalendarEvent'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { useCalendarEvents } from '@/stores/EventStore'
|
||||
|
||||
import {
|
||||
PhHourglassMedium,
|
||||
PhAlarm,
|
||||
PhHourglassHigh,
|
||||
PhHourglassLow,
|
||||
PhArrowBendDoubleUpLeft,
|
||||
PhArrowBendDoubleUpRight
|
||||
PhDotsThreeOutlineVertical
|
||||
} from '@phosphor-icons/vue'
|
||||
|
||||
const { defaultDate, getFormattedDateTitle, jumpToDate } = useCalendar()
|
||||
const { getRelativeEventFromEvent } = useCalendarEvents()
|
||||
const { defaultDate, getFormattedDateTitle, jumpToDate, revealEditEventModal, getRelativeString } = useCalendar()
|
||||
const { deleteEvent } = useCalendarEvents()
|
||||
const { lastActiveEvent } = storeToRefs(useCalendarEvents())
|
||||
|
||||
const props = defineProps<{
|
||||
event: CalendarEvent
|
||||
@@ -23,11 +22,10 @@ const props = defineProps<{
|
||||
isEndEvent?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'query:close-popover'): void
|
||||
}>()
|
||||
// Ref for the popover
|
||||
const eventDetails = ref<HTMLElement>()
|
||||
|
||||
const { getRelativeString } = useCalendar()
|
||||
const emit = defineEmits(['query:close-popover'])
|
||||
|
||||
const dateDifference: string = getRelativeString(defaultDate, props.event.startDate)
|
||||
const dateDuration: string | null = props.event.endDate
|
||||
@@ -39,14 +37,15 @@ function handleJumpToDate(date: RPGDate) {
|
||||
emit('query:close-popover')
|
||||
}
|
||||
|
||||
function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
||||
try {
|
||||
const { targetDate } = getRelativeEventFromEvent(props.event, position, props.isEndEvent)
|
||||
/**
|
||||
* Edit event
|
||||
*/
|
||||
const commandMenuOpened = ref(false)
|
||||
|
||||
handleJumpToDate(targetDate)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
function deployEditModal() {
|
||||
lastActiveEvent.value = { ...props.event }
|
||||
revealEditEventModal()
|
||||
commandMenuOpened.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -80,22 +79,24 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
||||
'border-yellow-600': event.category?.name === 'commerce'
|
||||
}"
|
||||
>
|
||||
<div class="grid gap-1">
|
||||
<div class="text-lg font-semibold">
|
||||
{{ event.title }}
|
||||
</div>
|
||||
<div ref="eventDetails" class="grid gap-1">
|
||||
<header class="pr-12">
|
||||
<div class="text-lg font-semibold">
|
||||
{{ event.title }}
|
||||
</div>
|
||||
|
||||
<div class="mb-1">
|
||||
<template v-if="!event.endDate">
|
||||
<p class="font-semibold">{{ getFormattedDateTitle(event.startDate, true) }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="font-semibold">
|
||||
Du {{ getFormattedDateTitle(event.startDate, true) }} au
|
||||
{{ getFormattedDateTitle(event.endDate, true) }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mb-1">
|
||||
<template v-if="!event.endDate">
|
||||
<p class="font-semibold">{{ getFormattedDateTitle(event.startDate, true) }}</p>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p class="font-semibold">
|
||||
Du {{ getFormattedDateTitle(event.startDate, true) }} au
|
||||
{{ getFormattedDateTitle(event.endDate, true) }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="mb-1 space-y-1">
|
||||
<p class="text-sm italic opacity-75 flex items-center gap-1">
|
||||
@@ -133,7 +134,27 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<nav v-if="event.startDate && event.endDate" class="absolute -top-2 right-2 flex gap-2">
|
||||
<menu class="absolute top-4 right-4">
|
||||
<UiPopover v-model:open="commandMenuOpened">
|
||||
<UiPopoverTrigger as-child>
|
||||
<UiButton size="icon" variant="ghost">
|
||||
<PhDotsThreeOutlineVertical size="20" weight="fill" />
|
||||
</UiButton>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent class="w-fit p-0" align="start" side="right" :collision-padding="20" >
|
||||
<UiCommand>
|
||||
<UiCommandList>
|
||||
<UiCommandGroup>
|
||||
<UiCommandItem value="edit-event" @select="deployEditModal"> Modifier </UiCommandItem>
|
||||
<UiCommandItem value="delete-event" @select="deleteEvent(event.id!)"> Supprimer </UiCommandItem>
|
||||
</UiCommandGroup>
|
||||
</UiCommandList>
|
||||
</UiCommand>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</menu>
|
||||
|
||||
<nav v-if="event.startDate && event.endDate" class="mt-2 flex gap-2">
|
||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child>
|
||||
<button
|
||||
class="flex gap-1"
|
||||
@@ -153,27 +174,6 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
|
||||
</button>
|
||||
</UiBadge>
|
||||
</nav>
|
||||
|
||||
<nav class="mt-2 flex gap-2">
|
||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child>
|
||||
<button
|
||||
class="flex gap-1"
|
||||
title="Évènement précédent"
|
||||
@click="handleGotoRelativeEvent('prev')"
|
||||
>
|
||||
<PhArrowBendDoubleUpLeft size="16" weight="fill" /> Précédent
|
||||
</button>
|
||||
</UiBadge>
|
||||
<UiBadge class="hover:opacity-100 hover:bg-slate-300" as-child title="Naviguer à la fin">
|
||||
<button
|
||||
class="flex gap-1"
|
||||
title="Évènement suivant"
|
||||
@click="handleGotoRelativeEvent('next')"
|
||||
>
|
||||
<PhArrowBendDoubleUpRight size="16" weight="fill" /> Suivant
|
||||
</button>
|
||||
</UiBadge>
|
||||
</nav>
|
||||
</UiPopoverContent>
|
||||
</template>
|
||||
|
||||
|
||||
126
components/calendar/form/CreateEvent.vue
Normal file
126
components/calendar/form/CreateEvent.vue
Normal file
@@ -0,0 +1,126 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RPGDate } from '~/models/Date';
|
||||
|
||||
import {
|
||||
PhAlarm
|
||||
} from '@phosphor-icons/vue'
|
||||
|
||||
const { eventSkeleton } = storeToRefs(useCalendarEvents())
|
||||
const { resetSkeleton, submitSkeleton } = useCalendarEvents()
|
||||
const popoverOpen = ref(false)
|
||||
|
||||
const formErrors = reactive<{ message: string | null }>({
|
||||
message: null
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
date?: RPGDate
|
||||
btnClass?: string
|
||||
}>()
|
||||
|
||||
/**
|
||||
* Opens event creation's popover
|
||||
*/
|
||||
function openEventCreatePopover() {
|
||||
resetSkeleton()
|
||||
|
||||
popoverOpen.value = true
|
||||
|
||||
// Set skeleton initial startDate if it's known
|
||||
if (props.date) {
|
||||
eventSkeleton.value.startDate = { ...props.date } // We need to clone it otherwise the props ends up mutating (?)
|
||||
}
|
||||
}
|
||||
|
||||
// Watch the popover state
|
||||
// And reset the skeleton if it has been dismissed
|
||||
watch(popoverOpen, (hasOpened, _o) => {
|
||||
if (!hasOpened) {
|
||||
resetSkeleton()
|
||||
}
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await submitSkeleton()
|
||||
|
||||
popoverOpen.value = false
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
formErrors.message = err.message
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiPopover v-model:open="popoverOpen">
|
||||
<UiPopoverTrigger as-child>
|
||||
<button :class="btnClass" @click="openEventCreatePopover()" />
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent
|
||||
:align="'center'"
|
||||
:side="'right'"
|
||||
:collision-padding="60"
|
||||
class="pl-3 min-w-96 bg-slate-900 border-slate-800"
|
||||
>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="grid grid-cols-2 gap-y-4">
|
||||
<div class="col-span-2 pl-8">
|
||||
<input
|
||||
id="new-event-title"
|
||||
v-model="eventSkeleton.title"
|
||||
type="text"
|
||||
name="new-event-title"
|
||||
required
|
||||
placeholder="Titre de l'évènement"
|
||||
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 pl-8">
|
||||
<textarea
|
||||
id="new-event-description"
|
||||
v-model="eventSkeleton.description"
|
||||
name="new-event-description"
|
||||
placeholder="Description brève de l'évènement"
|
||||
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<PhAlarm size="18" weight="fill" />
|
||||
|
||||
<CalendarInputRPGDate
|
||||
v-model:model-value="eventSkeleton.startDate"
|
||||
placeholder="Date de début"
|
||||
:initial-date="props.date"
|
||||
:required="true"
|
||||
/>
|
||||
|
||||
<span>—</span>
|
||||
|
||||
<CalendarInputRPGDate
|
||||
v-model:model-value="eventSkeleton.endDate"
|
||||
placeholder="Date de fin"
|
||||
:initial-date="props.date"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-red-500 pl-8">
|
||||
<span class="text-sm">
|
||||
{{ formErrors.message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<UiButton size="sm">
|
||||
Sauvegarder
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</template>
|
||||
112
components/calendar/form/UpdateEvent.vue
Normal file
112
components/calendar/form/UpdateEvent.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<script lang="ts" setup>
|
||||
import { PhAlarm } from '@phosphor-icons/vue'
|
||||
import { VisuallyHidden } from 'radix-vue'
|
||||
|
||||
const isModalOpened = defineModel<boolean>({ default: false })
|
||||
const { resetSkeleton, updateEventFromSkeleton } = useCalendarEvents()
|
||||
const { eventSkeleton, lastActiveEvent } = storeToRefs(useCalendarEvents())
|
||||
|
||||
const formErrors = reactive<{ message: string | null }>({
|
||||
message: null
|
||||
})
|
||||
|
||||
// Watch the popover state
|
||||
watch(isModalOpened, (hasOpened, _o) => {
|
||||
if (hasOpened && lastActiveEvent.value) {
|
||||
eventSkeleton.value = { ...lastActiveEvent.value }
|
||||
} else {
|
||||
resetSkeleton()
|
||||
}
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await updateEventFromSkeleton()
|
||||
|
||||
isModalOpened.value = false
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
formErrors.message = err.message
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiDialog v-model:open="isModalOpened" :modal="true">
|
||||
<UiDialogContent
|
||||
:align="'center'"
|
||||
:side="'right'"
|
||||
:collision-padding="60"
|
||||
:disable-outside-pointer-events="true"
|
||||
:trap-focus="true"
|
||||
class="pl-3 min-w-96 bg-slate-900 border-slate-800"
|
||||
>
|
||||
<VisuallyHidden>
|
||||
<UiDialogTitle> Modifier l'évènement</UiDialogTitle>
|
||||
|
||||
<UiDialogDescription>
|
||||
Mettre à jour les données de l'évènement
|
||||
</UiDialogDescription>
|
||||
</VisuallyHidden>
|
||||
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="grid grid-cols-2 gap-y-4">
|
||||
<div class="col-span-2 ml-8">
|
||||
<input
|
||||
id="new-event-title"
|
||||
v-model="eventSkeleton.title"
|
||||
type="text"
|
||||
name="new-event-title"
|
||||
required
|
||||
placeholder="Titre de l'évènement"
|
||||
class="w-full -my-1 py-1 -mx-1 px-1 text-lg border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600">
|
||||
</div>
|
||||
|
||||
<div class="col-span-2 ml-8">
|
||||
<textarea
|
||||
id="new-event-description"
|
||||
v-model="eventSkeleton.description"
|
||||
name="new-event-description"
|
||||
placeholder="Description brève de l'évènement"
|
||||
class="w-full -my-1 py-1 -mx-1 px-1 min-h-24 max-h-36 text-sm border-b-[1px] bg-transparent focus-visible:outline-none focus-visible:border-blue-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<PhAlarm size="18" weight="fill" />
|
||||
|
||||
<CalendarInputRPGDate
|
||||
v-model:model-value="eventSkeleton.startDate"
|
||||
placeholder="Date de début"
|
||||
:initial-date="lastActiveEvent?.startDate"
|
||||
:required="true"
|
||||
/>
|
||||
|
||||
<span>—</span>
|
||||
|
||||
<CalendarInputRPGDate
|
||||
v-model:model-value="eventSkeleton.endDate"
|
||||
placeholder="Date de fin"
|
||||
:initial-date="lastActiveEvent?.endDate"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-red-500 ml-8">
|
||||
<span class="text-sm">
|
||||
{{ formErrors.message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<UiButton size="sm" type="submit">
|
||||
Sauvegarder
|
||||
</UiButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</UiDialogContent>
|
||||
</UiDialog>
|
||||
</template>
|
||||
166
components/calendar/input/RPGDate.vue
Normal file
166
components/calendar/input/RPGDate.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RPGDate } from '~/models/Date';
|
||||
|
||||
import {
|
||||
PhXCircle
|
||||
} from '@phosphor-icons/vue'
|
||||
|
||||
const model = defineModel<RPGDate | null>()
|
||||
|
||||
const id = useId()
|
||||
|
||||
const props = defineProps<{
|
||||
initialDate?: RPGDate
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
}>()
|
||||
|
||||
const { getFormattedDateTitle, getMonthName, defaultDate } = useCalendar()
|
||||
const { sortedMonths } = storeToRefs(useCalendar())
|
||||
|
||||
const popoverOpened = ref<boolean>(false)
|
||||
|
||||
const inputPlaceholder = computed<string>(() => {
|
||||
if (model.value) {
|
||||
return getFormattedDateTitle(model.value, true)
|
||||
} else if (props.placeholder) {
|
||||
return props.placeholder
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
const initialDateValue = props.initialDate ? { ...props.initialDate } : null
|
||||
|
||||
const monthTitle = computed<string>(() => {
|
||||
if (model.value) {
|
||||
return getMonthName(model.value.month)
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
const hasValue = computed<boolean>(() => !!model.value)
|
||||
const monthData = computed(() => {
|
||||
if (model.value) {
|
||||
return sortedMonths.value[model.value.month]
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
function setModelDay(day: number) {
|
||||
if (model.value) {
|
||||
model.value.day = day
|
||||
}
|
||||
}
|
||||
|
||||
function handleMonthChange(e: string) {
|
||||
const monthId = Number(e)
|
||||
|
||||
if (model.value) {
|
||||
model.value.month = monthId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click if the date is null
|
||||
*/
|
||||
function handleNullClick() {
|
||||
// Early return
|
||||
if (model.value) return
|
||||
|
||||
if (!model.value) {
|
||||
if (props.initialDate) {
|
||||
model.value = initialDateValue
|
||||
} else {
|
||||
model.value = { ...defaultDate }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleValueReset() {
|
||||
// Early return
|
||||
if (!model.value) return
|
||||
|
||||
model.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiPopover v-model:open="popoverOpened">
|
||||
<UiPopoverTrigger>
|
||||
<div class="flex">
|
||||
<UiButton
|
||||
size='sm'
|
||||
variant="outline"
|
||||
:class="{
|
||||
'text-slate-500': !hasValue,
|
||||
'rounded-tr-none rounded-br-none': model
|
||||
}"
|
||||
@click.prevent="handleNullClick"
|
||||
>
|
||||
{{ inputPlaceholder }}
|
||||
</UiButton>
|
||||
<UiButton
|
||||
v-if="model && !required"
|
||||
size='sm'
|
||||
variant="outline"
|
||||
class="rounded-tl-none rounded-bl-none px-2"
|
||||
@click.prevent="handleValueReset"
|
||||
>
|
||||
<PhXCircle size="14" />
|
||||
</UiButton>
|
||||
</div>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent
|
||||
v-if="model"
|
||||
align="start"
|
||||
side="bottom"
|
||||
class="bg-slate-900 border-slate-800"
|
||||
>
|
||||
<div class="grid grid-cols-2 items-center gap-x-2 gap-y-3">
|
||||
<UiSelect @update:model-value="handleMonthChange">
|
||||
<UiSelectTrigger>
|
||||
<UiSelectValue :placeholder="monthTitle" />
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent>
|
||||
<UiSelectGroup class="max-h-[50vh]">
|
||||
<UiSelectItem v-for="(month, i) in sortedMonths" :key="`popover-select-item-${id}-${i}`" :value="i.toString()">
|
||||
{{ month.name }}
|
||||
</UiSelectItem>
|
||||
</UiSelectGroup>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
|
||||
<UiInput v-model="model.year" type="number" />
|
||||
|
||||
<!-- <nav class="flex items-center justify-end gap-2">
|
||||
<UiButton variant="outline" size="icon" class="w-8 h-8" @click="decrementModelMonth()">
|
||||
<PhCaretLeft size="14" />
|
||||
</UiButton>
|
||||
<UiButton variant="outline" size="icon" class="w-8 h-8" @click="incrementModelMonth()">
|
||||
<PhCaretRight size="14" />
|
||||
</UiButton>
|
||||
</nav> -->
|
||||
|
||||
<hr class="col-span-2" >
|
||||
|
||||
<div class="col-span-2">
|
||||
<div v-if="monthData" class="grid grid-cols-7 gap-1">
|
||||
<button
|
||||
v-for="day in monthData.days"
|
||||
:key="`popover-day-grid-${id}-${day}`"
|
||||
class="aspect-square rounded-full text-[.8em] transition-colors hover:bg-slate-700"
|
||||
:class="{'bg-blue-500 hover:bg-blue-700': day === model.day}"
|
||||
@click="setModelDay(day)"
|
||||
>
|
||||
{{ day }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</template>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { RPGDate } from '@/models/Date'
|
||||
import type { CalendarEvent } from '@/models/CalendarEvent'
|
||||
import { useCalendar } from '@/stores/CalendarStore'
|
||||
import { useCalendarEvents } from '@/stores/EventStore'
|
||||
import { useElementBounding } from '@vueuse/core'
|
||||
@@ -7,7 +8,6 @@ import { storeToRefs } from 'pinia'
|
||||
import { computed, ref, type ComputedRef } from 'vue'
|
||||
|
||||
import CalendarEventButton from '../../CalendarEvent.vue'
|
||||
import type { CalendarEvent } from '~/models/CalendarEvent'
|
||||
|
||||
const props = defineProps<{
|
||||
date: RPGDate
|
||||
@@ -21,6 +21,9 @@ const { defaultDate, selectDate, areDatesIdentical } = useCalendar()
|
||||
const { selectedDate } = storeToRefs(useCalendar())
|
||||
const { currentEvents } = storeToRefs(useCalendarEvents())
|
||||
|
||||
/**
|
||||
* All events with a startDate / endDate that starts or ends on the tile
|
||||
*/
|
||||
const eventsForTheDay = computed(() => {
|
||||
return currentEvents.value.filter((currentEvent) => {
|
||||
return (
|
||||
@@ -30,10 +33,16 @@ const eventsForTheDay = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Is the tile on today's date ?
|
||||
*/
|
||||
const isDefaultDate = computed(() => {
|
||||
return areDatesIdentical(props.date, defaultDate)
|
||||
})
|
||||
|
||||
/**
|
||||
* Is the tile on the hightlighted date ?
|
||||
*/
|
||||
const isSelectedDate = computed(() => {
|
||||
return areDatesIdentical(props.date, selectedDate.value)
|
||||
})
|
||||
@@ -49,12 +58,19 @@ const numberOfEventsToFit: ComputedRef<number> = computed(() => {
|
||||
return Math.trunc((tileHeight.value - (tileListTop.value - tileTop.value)) / 40)
|
||||
})
|
||||
|
||||
const eventsToDisplay: ComputedRef<CalendarEvent[]> = computed(() => {
|
||||
/**
|
||||
* Events that can fit in the tile's space
|
||||
*/
|
||||
const eventsToDisplay: ComputedRef<CalendarEvent[]> = computed<CalendarEvent[]>(() => {
|
||||
return [...eventsForTheDay.value].splice(0, numberOfEventsToFit.value)
|
||||
})
|
||||
const eventsNotDisplayed = computed(
|
||||
() => eventsForTheDay.value.length - eventsToDisplay.value.length
|
||||
)
|
||||
|
||||
/**
|
||||
* Remaining events that are not displayed
|
||||
*
|
||||
* Used if not all events can be seen in the tile's space
|
||||
*/
|
||||
const eventsNotDisplayed: ComputedRef<number> = computed<number>(() => eventsForTheDay.value.length - eventsToDisplay.value.length)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -66,9 +82,6 @@ const eventsNotDisplayed = computed(
|
||||
'text-slate-300': !props.faded
|
||||
}"
|
||||
>
|
||||
<!-- Used for "display all events" -->
|
||||
<button class="absolute inset-0 w-full h-full cursor-default z-0" />
|
||||
|
||||
<button
|
||||
class="relative z-10 group block w-full text-center cursor-pointer"
|
||||
@click="selectDate(date)"
|
||||
@@ -86,47 +99,55 @@ const eventsNotDisplayed = computed(
|
||||
</ClientOnly>
|
||||
</button>
|
||||
|
||||
<ul
|
||||
ref="calendarEventsList"
|
||||
class="absolute top-12 bottom-2 inset-x-2 grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity"
|
||||
:class="{
|
||||
'opacity-40': props.faded && !isSelectedDate
|
||||
}"
|
||||
>
|
||||
<li v-for="event in eventsToDisplay" :key="event.title" class="grid pointer-events-auto">
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
<ClientOnly>
|
||||
<ul
|
||||
ref="calendarEventsList"
|
||||
class="absolute top-12 bottom-2 inset-x-2 grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity"
|
||||
:class="{
|
||||
'opacity-40': props.faded && !isSelectedDate
|
||||
}"
|
||||
>
|
||||
<TransitionGroup name="event">
|
||||
<li v-for="event in eventsToDisplay" :key="event.title" class="grid pointer-events-auto">
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
</TransitionGroup>
|
||||
|
||||
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
|
||||
<UiPopover>
|
||||
<UiPopoverTrigger as-child>
|
||||
<button
|
||||
class="text-xs px-2 py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:bg-slate-800"
|
||||
>
|
||||
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
|
||||
</button>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent class="w-80" :align="'center'" :side="'right'">
|
||||
<div class="text-center mb-4">
|
||||
<span
|
||||
class="inline-flex w-12 h-12 aspect-square items-center justify-center text-lg font-semibold text-slate-300 bg-slate-800 rounded-full"
|
||||
<li v-if="eventsNotDisplayed > 0" class="pointer-events-auto">
|
||||
<UiPopover>
|
||||
<UiPopoverTrigger as-child>
|
||||
<button
|
||||
class="text-xs px-2 py-1 block w-full text-left font-bold rounded-sm whitespace-nowrap overflow-hidden text-ellipsis cursor-pointer transition-colors hover:bg-slate-800"
|
||||
>
|
||||
{{ date.day }}
|
||||
</span>
|
||||
</div>
|
||||
<ul class="grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity">
|
||||
<li
|
||||
v-for="event in eventsForTheDay"
|
||||
:key="event.title"
|
||||
class="grid pointer-events-auto"
|
||||
>
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
</ul>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</li>
|
||||
</ul>
|
||||
{{ eventsNotDisplayed }} autre{{ eventsNotDisplayed > 1 ? 's' : '' }}
|
||||
</button>
|
||||
</UiPopoverTrigger>
|
||||
<UiPopoverContent class="w-80" :align="'center'" :side="'right'">
|
||||
<div class="text-center mb-4">
|
||||
<span
|
||||
class="inline-flex w-12 h-12 aspect-square items-center justify-center text-lg font-semibold text-slate-300 bg-slate-800 rounded-full"
|
||||
>
|
||||
{{ date.day }}
|
||||
</span>
|
||||
</div>
|
||||
<ul class="grid auto-rows-min gap-1 z-10 pointer-events-none transition-opacity">
|
||||
<li
|
||||
v-for="event in eventsForTheDay"
|
||||
:key="event.title"
|
||||
class="grid pointer-events-auto"
|
||||
>
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
</ul>
|
||||
</UiPopoverContent>
|
||||
</UiPopover>
|
||||
</li>
|
||||
</ul>
|
||||
</ClientOnly>
|
||||
|
||||
<ClientOnly>
|
||||
<CalendarFormCreateEvent :date btn-class="absolute inset-0 w-full h-full cursor-default z-0" />
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -140,4 +161,21 @@ const eventsNotDisplayed = computed(
|
||||
border-top-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.event-enter-active {
|
||||
transition: all 120ms ease-in-out;
|
||||
}
|
||||
.event-leave-active {
|
||||
transition: all 180ms ease-in-out;
|
||||
}
|
||||
.event-enter-from,
|
||||
.event-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.event-enter-from {
|
||||
transform: translateX(.15rem);
|
||||
}
|
||||
.event-leave-to {
|
||||
transform: translateX(-.5rem);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,7 @@ const moveCalendarRight = useThrottleFn(() => {
|
||||
<div class="grid grid-cols-10" @wheel="handleWheel">
|
||||
<CalendarStateMonthlyDayTile
|
||||
v-for="day in currentMonthData.days"
|
||||
:key="day"
|
||||
:key="`layout-month-grid-${day}`"
|
||||
:date="{
|
||||
day: day,
|
||||
month: currentDate.currentMonth,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { z } from 'zod'
|
||||
import type { Category } from './Category'
|
||||
import type { RPGDate } from './Date'
|
||||
import { dateSchema, type RPGDate } from './Date'
|
||||
|
||||
export interface CalendarEvent {
|
||||
id: number
|
||||
id?: number
|
||||
title: string
|
||||
startDate: RPGDate
|
||||
endDate?: RPGDate
|
||||
@@ -13,6 +14,19 @@ export interface CalendarEvent {
|
||||
wiki?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Body validation schema for post requests (insert and update)
|
||||
*/
|
||||
export const postEventBodySchema = z.object({
|
||||
event: z.object({
|
||||
title: z.string(),
|
||||
description: z.string().optional().nullable(),
|
||||
startDate: dateSchema.required(),
|
||||
endDate: dateSchema.optional().nullable()
|
||||
}),
|
||||
calendarId: z.number({ coerce: true }).int().positive()
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function isCalendarEvent(object: any): object is CalendarEvent {
|
||||
return 'startDate' in object
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export interface RPGDate {
|
||||
day: number
|
||||
month: number
|
||||
@@ -6,23 +8,8 @@ export interface RPGDate {
|
||||
|
||||
export type RPGDateOrder = 'asc' | 'desc'
|
||||
|
||||
// export function getRelativeDate(baseDate: RPGDate, relativeDate: RPGDate) {
|
||||
// let newDay: number
|
||||
// let newMonth: number
|
||||
// let newYear: number
|
||||
// let newPeriod: RPGPeriod
|
||||
|
||||
// const differenceInDays = getDifferenceInDays(baseDate, relativeDate)
|
||||
|
||||
// // console.log(baseDate, substractionDate)
|
||||
// console.log(getRelativeString(baseDate, relativeDate))
|
||||
|
||||
// return differenceInDays
|
||||
|
||||
// // return {
|
||||
// // day: newDay,
|
||||
// // month: newMonth,
|
||||
// // year: newYear,
|
||||
// // period: newPeriod
|
||||
// // }
|
||||
// }
|
||||
export const dateSchema = z.object({
|
||||
day: z.number().int().positive(),
|
||||
month: z.number().int(),
|
||||
year: z.number().int()
|
||||
})
|
||||
|
||||
@@ -18,7 +18,6 @@ watch(user, (n, _o) => {
|
||||
})
|
||||
|
||||
const { revealAdvancedSearch } = useCalendar()
|
||||
const { isAdvancedSearchOpen } = storeToRefs(useCalendar())
|
||||
|
||||
const sidebarMenu: MenuItem[] = [
|
||||
{
|
||||
@@ -34,7 +33,5 @@ const sidebarMenu: MenuItem[] = [
|
||||
<Sidebar :menu-items="sidebarMenu" />
|
||||
|
||||
<Calendar />
|
||||
|
||||
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
41
server/api/calendars/events/[id].delete.ts
Normal file
41
server/api/calendars/events/[id].delete.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { z } from 'zod'
|
||||
import { serverSupabaseClient } from "#supabase/server"
|
||||
import type { CalendarEvent } from "~/models/CalendarEvent"
|
||||
|
||||
const paramsSchema = z.object({
|
||||
id: z.number({ coerce: true }).positive().int()
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const client = await serverSupabaseClient(event)
|
||||
|
||||
const { data: params, error: paramsError} = await getValidatedRouterParams(event, paramsSchema.safeParse)
|
||||
|
||||
if (paramsError) {
|
||||
throw createError({
|
||||
cause: 'Utilisateur',
|
||||
fatal: false,
|
||||
message: "L'identifiant de l'évènement est manquant ou mal renseigné.",
|
||||
status: 401,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await client
|
||||
.from('calendar_events')
|
||||
.delete()
|
||||
.eq('id', params.id)
|
||||
.single<CalendarEvent>()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
throw createError({
|
||||
cause: 'Serveur',
|
||||
status: 500,
|
||||
fatal: false,
|
||||
message: 'Une erreur inconnue est survenue.'
|
||||
})
|
||||
}
|
||||
})
|
||||
63
server/api/calendars/events/[id].patch.ts
Normal file
63
server/api/calendars/events/[id].patch.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { z } from 'zod'
|
||||
import { serverSupabaseClient } from "#supabase/server"
|
||||
import { postEventBodySchema, type CalendarEvent } from "~/models/CalendarEvent"
|
||||
|
||||
const paramsSchema = z.object({
|
||||
id: z.number({ coerce: true }).positive().int()
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const client = await serverSupabaseClient(event)
|
||||
|
||||
const { data: params, error: paramsError} = await getValidatedRouterParams(event, paramsSchema.safeParse)
|
||||
const { data: bodyData, error: bodyError } = await readValidatedBody(event, body => postEventBodySchema.safeParse(body))
|
||||
|
||||
if (paramsError) {
|
||||
throw createError({
|
||||
cause: 'Utilisateur',
|
||||
fatal: false,
|
||||
message: "L'identifiant de l'évènement est manquant ou mal renseigné.",
|
||||
status: 401,
|
||||
})
|
||||
}
|
||||
|
||||
if (bodyError) {
|
||||
console.log(bodyData)
|
||||
throw createError({
|
||||
cause: 'Utilisateur',
|
||||
fatal: false,
|
||||
message: "Le schéma de la requête n'est pas complet ou mal renseigné.",
|
||||
status: 401,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await client
|
||||
.from('calendar_events')
|
||||
.update({ start_date: bodyData?.event.startDate, end_date: bodyData.event.endDate, title: bodyData?.event.title, description: bodyData.event.description, calendar_id: bodyData?.calendarId } as never)
|
||||
.eq('id', params.id)
|
||||
.select(`
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
hidden,
|
||||
startDate:start_date,
|
||||
endDate:end_date,
|
||||
wiki,
|
||||
category:calendar_event_categories!calendar_events_category_fkey (*),
|
||||
secondaryCategories:calendar_event_categories!calendar_event_categories_links (*)
|
||||
`)
|
||||
.single<CalendarEvent>()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
throw createError({
|
||||
cause: 'Serveur',
|
||||
status: 500,
|
||||
fatal: false,
|
||||
message: 'Une erreur inconnue est survenue.'
|
||||
})
|
||||
}
|
||||
})
|
||||
46
server/api/calendars/events/create.post.ts
Normal file
46
server/api/calendars/events/create.post.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { serverSupabaseClient } from "#supabase/server";
|
||||
import type { CalendarEvent } from "@/models/CalendarEvent";
|
||||
import { postEventBodySchema } from "@/models/CalendarEvent";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const client = await serverSupabaseClient(event)
|
||||
const { data: bodyData, error: schemaError } = await readValidatedBody(event, body => postEventBodySchema.safeParse(body))
|
||||
|
||||
if (schemaError) {
|
||||
throw createError({
|
||||
cause: 'Utilisateur',
|
||||
fatal: false,
|
||||
message: "Le schéma de la requête n'est pas complet ou mal renseigné.",
|
||||
status: 401,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const { data, error } = await client
|
||||
.from('calendar_events')
|
||||
.insert({ start_date: bodyData?.event.startDate, end_date: bodyData.event.endDate, title: bodyData?.event.title, description: bodyData.event.description, calendar_id: bodyData?.calendarId } as never)
|
||||
.select(`
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
hidden,
|
||||
startDate:start_date,
|
||||
endDate:end_date,
|
||||
wiki,
|
||||
category:calendar_event_categories!calendar_events_category_fkey (*),
|
||||
secondaryCategories:calendar_event_categories!calendar_event_categories_links (*)
|
||||
`)
|
||||
.single<CalendarEvent>()
|
||||
|
||||
if (error) throw error
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
throw createError({
|
||||
cause: 'Serveur',
|
||||
status: 500,
|
||||
fatal: false,
|
||||
message: 'Une erreur inconnue est survenue.'
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -36,6 +36,11 @@ export const useCalendar = defineStore('calendar', () => {
|
||||
'century'
|
||||
])
|
||||
|
||||
const calendarId = ref<number>(0)
|
||||
|
||||
function setCalendarId(data: number) {
|
||||
calendarId.value = data
|
||||
}
|
||||
/**
|
||||
* Month list (queried from API)
|
||||
*/
|
||||
@@ -178,7 +183,6 @@ export const useCalendar = defineStore('calendar', () => {
|
||||
/**
|
||||
* Moves the current date forward one month
|
||||
*/
|
||||
|
||||
function incrementMonth(): void {
|
||||
let newValue = Number(params.month) + 1
|
||||
|
||||
@@ -290,13 +294,19 @@ export const useCalendar = defineStore('calendar', () => {
|
||||
*/
|
||||
const isAdvancedSearchOpen: Ref<boolean> = ref<boolean>(false)
|
||||
|
||||
/**
|
||||
* Opens the search modal
|
||||
*/
|
||||
function revealAdvancedSearch() {
|
||||
isAdvancedSearchOpen.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* State for advanced search modal
|
||||
*/
|
||||
const isEditEventModalOpen: Ref<boolean> = ref<boolean>(false)
|
||||
|
||||
function revealEditEventModal() {
|
||||
isEditEventModalOpen.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the active viewType
|
||||
*
|
||||
@@ -432,9 +442,17 @@ export const useCalendar = defineStore('calendar', () => {
|
||||
* @returns 1 means the first date comes before the second, -1 means the second comes before the first, and 0 if they're identical
|
||||
*/
|
||||
function compareDates(a: RPGDate, b: RPGDate, order: RPGDateOrder = 'desc'): number {
|
||||
// Reverses the order if specified
|
||||
// Reverses the order if specified
|
||||
const orderFactor: number = order === 'desc' ? 1 : -1
|
||||
|
||||
// If somehow one of the date isn't available
|
||||
if (!b) {
|
||||
return -1 * orderFactor
|
||||
}
|
||||
if (!a) {
|
||||
return 1 * orderFactor
|
||||
}
|
||||
|
||||
// Compare years
|
||||
if (a.year < b.year) return -1 * orderFactor
|
||||
if (a.year > b.year) return 1 * orderFactor
|
||||
@@ -598,6 +616,8 @@ export const useCalendar = defineStore('calendar', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
calendarId,
|
||||
setCalendarId,
|
||||
months,
|
||||
setMonths,
|
||||
sortedMonths,
|
||||
@@ -628,11 +648,13 @@ export const useCalendar = defineStore('calendar', () => {
|
||||
getViewTypeTitle,
|
||||
isCurrentScreenActive,
|
||||
isAdvancedSearchOpen,
|
||||
revealAdvancedSearch,
|
||||
isEditEventModalOpen,
|
||||
revealEditEventModal,
|
||||
convertDateToDays,
|
||||
getDifferenceInDays,
|
||||
areDatesIdentical,
|
||||
compareDates,
|
||||
getRelativeString,
|
||||
revealAdvancedSearch
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,7 +5,8 @@ import { ref, watch, type Ref } from 'vue'
|
||||
import { useCalendar } from './CalendarStore'
|
||||
|
||||
export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
const { currentDate, currentConfig, convertDateToDays, compareDates } = useCalendar()
|
||||
const { currentDate, defaultDate, currentConfig, convertDateToDays, compareDates } = useCalendar()
|
||||
const { calendarId } = storeToRefs(useCalendar())
|
||||
|
||||
const baseEvents = ref<CalendarEvent[]>([])
|
||||
|
||||
@@ -13,17 +14,19 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
baseEvents.value = data
|
||||
}
|
||||
|
||||
// Order base events by dates
|
||||
const allEvents = computed(() => baseEvents.value.sort((a, b) => {
|
||||
return compareDates(a.startDate, b.startDate, 'desc')
|
||||
}))
|
||||
|
||||
// Gets all current event in its default state
|
||||
const currentEvents: Ref<CalendarEvent[]> = ref(computeCurrentEvents())
|
||||
const currentEvents: Ref<CalendarEvent[]> = ref([])
|
||||
|
||||
// Watch for currentDate changes
|
||||
// Watch for currentDate or events' list changes
|
||||
// This is deep because we're watching an array, and changes need to trigger and mutations like .push and .splice
|
||||
watch([currentDate, allEvents], () => {
|
||||
currentEvents.value = computeCurrentEvents()
|
||||
})
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
/**
|
||||
* Determines if the event can appear in the front end
|
||||
@@ -34,9 +37,6 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
* @returns Whether the event should appear in the current view
|
||||
*/
|
||||
function shouldEventBeDisplayed(event: CalendarEvent): boolean {
|
||||
// const eventStartDateToDays = convertDateToDays(event.startDate)
|
||||
// const eventEndDateToDays: number = event.endDate ? convertDateToDays(event.endDate) : 0
|
||||
|
||||
const isEventOnCurrentScreen =
|
||||
(event.startDate.year === currentDate.currentYear &&
|
||||
event.startDate.month === currentDate.currentMonth) ||
|
||||
@@ -44,25 +44,6 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
event.endDate.year === currentDate.currentYear &&
|
||||
event.endDate.month === currentDate.currentMonth)
|
||||
|
||||
// Check whether the event is on the last 8 tiles
|
||||
// This is to allow leap events from appearing on the last 8 tiles
|
||||
//
|
||||
// This is not used for now
|
||||
//
|
||||
// const firstDayOfCurrentMonth = convertDateToDays({
|
||||
// day: 1,
|
||||
// month: currentDate.currentMonth,
|
||||
// year: currentDate.currentYear
|
||||
// })
|
||||
// const lastDayOfCurrentMonth = firstDayOfCurrentMonth + daysPerMonth
|
||||
// const last8Tiles = lastDayOfCurrentMonth + 8
|
||||
|
||||
// const isEventOnNext8Tiles =
|
||||
// (eventStartDateToDays <= last8Tiles && eventStartDateToDays >= lastDayOfCurrentMonth) ||
|
||||
// (Boolean(event.endDate) &&
|
||||
// eventEndDateToDays <= last8Tiles &&
|
||||
// eventEndDateToDays >= lastDayOfCurrentMonth)
|
||||
|
||||
switch (currentConfig.viewType) {
|
||||
case 'month':
|
||||
return isEventOnCurrentScreen!
|
||||
@@ -97,8 +78,7 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* From a base event, gets the next or previous in the timeline
|
||||
* @todo **This should probably be extracted to function FIRST with dates only, as the initialIsEnd param is only used at the beggining to establish a pivot**
|
||||
* From a base event, gets the next or previous one in the timeline
|
||||
*
|
||||
* @param event The event at a given position in the data
|
||||
* @param position Whether we should get the next or previous event
|
||||
@@ -120,6 +100,13 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
return getRelativeEventFromDate(dateToParse, position)
|
||||
}
|
||||
|
||||
/**
|
||||
* From a date, gets the next or previous event in the timeline
|
||||
*
|
||||
* @param date The starting date from which to get the next event
|
||||
* @param position Whether we should get the next or previous event
|
||||
* @returns The next event in chronological order
|
||||
*/
|
||||
function getRelativeEventFromDate(
|
||||
date: RPGDate,
|
||||
position: 'next' | 'prev' = 'next'
|
||||
@@ -156,27 +143,6 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// // Special case : If we query the first one but it already is
|
||||
// if (position === 'prev' && t[0].distance === 0) {
|
||||
// const targetDate =
|
||||
// t[0].targetKey === 'startDate' ? t[0].eventData.startDate : t[0].eventData.endDate!
|
||||
// return {
|
||||
// event: t[0].eventData,
|
||||
// targetDate: targetDate
|
||||
// }
|
||||
// }
|
||||
// // Special case : If we query the last one but it already is
|
||||
// if (position === 'next' && t[t.length - 1].distance === 0) {
|
||||
// const targetDate =
|
||||
// t[t.length - 1].targetKey === 'startDate'
|
||||
// ? t[t.length - 1].eventData.startDate
|
||||
// : t[t.length - 1].eventData.endDate!
|
||||
// return {
|
||||
// event: t[t.length - 1].eventData,
|
||||
// targetDate: targetDate
|
||||
// }
|
||||
// }
|
||||
|
||||
// Based on the direction, either ignore negative distance (past) or positive distance (future)
|
||||
t = t.filter((i) => {
|
||||
return position === 'next' ? i.distance > 0 : i.distance < 0
|
||||
@@ -199,5 +165,59 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
}
|
||||
}
|
||||
|
||||
return { allEvents, setEvents, currentEvents, getRelativeEventFromDate, getRelativeEventFromEvent }
|
||||
/**
|
||||
* EVENT CREATION FUNCTIONS
|
||||
*/
|
||||
const lastActiveEvent = ref<CalendarEvent | null>()
|
||||
/**
|
||||
* Dummy event to hold creation data
|
||||
*/
|
||||
const eventSkeleton: Ref<CalendarEvent> = ref<CalendarEvent>({ title: '', startDate: defaultDate })
|
||||
|
||||
/**
|
||||
* Resets the dummy event data
|
||||
*/
|
||||
function resetSkeleton() {
|
||||
eventSkeleton.value = { title: '', startDate: defaultDate }
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the skeleton event and creates a real event from its data
|
||||
*
|
||||
* We assume it's been sanitized by the caller
|
||||
*/
|
||||
async function submitSkeleton() {
|
||||
try {
|
||||
const res = await $fetch('/api/calendars/events/create', { method: 'POST', body: { event : eventSkeleton.value, calendarId: calendarId.value }})
|
||||
baseEvents.value.push(res)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateEventFromSkeleton() {
|
||||
try {
|
||||
const res = await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: 'PATCH', body: { event : eventSkeleton.value, calendarId: calendarId.value }})
|
||||
const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
|
||||
baseEvents.value[eventIndex] = res
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteEvent(eventId: number) {
|
||||
if (!eventId) {
|
||||
throw new Error('ID of the event is required')
|
||||
}
|
||||
|
||||
try {
|
||||
await $fetch(`/api/calendars/events/${eventId}`, { method: 'DELETE' })
|
||||
const eventIndex = baseEvents.value.findIndex(e => e.id === eventId)
|
||||
baseEvents.value.splice(eventIndex, 1)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
return { allEvents, setEvents, currentEvents, getRelativeEventFromDate, getRelativeEventFromEvent, eventSkeleton, resetSkeleton, submitSkeleton, lastActiveEvent, updateEventFromSkeleton, deleteEvent }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user