Added post component for events
This needs RLS disabled, will work on that later, I don't like postgre
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 } = 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,7 @@ onMounted(() => {
|
||||
</KeepAlive>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<CalendarSearch v-model:model-value="isAdvancedSearchOpen" />
|
||||
</div>
|
||||
</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>
|
||||
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,53 @@ 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
|
||||
}"
|
||||
>
|
||||
<li v-for="event in eventsToDisplay" :key="event.title" class="grid pointer-events-auto">
|
||||
<CalendarEventButton :event :tile-date="date" />
|
||||
</li>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
@@ -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(),
|
||||
startDate: dateSchema.required(),
|
||||
endDate: dateSchema.optional()
|
||||
}),
|
||||
calendarId: z.number().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>
|
||||
|
||||
36
server/api/calendars/events/create.post.ts
Normal file
36
server/api/calendars/events/create.post.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
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()
|
||||
.returns<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
|
||||
|
||||
@@ -598,6 +602,8 @@ export const useCalendar = defineStore('calendar', () => {
|
||||
}
|
||||
|
||||
return {
|
||||
calendarId,
|
||||
setCalendarId,
|
||||
months,
|
||||
setMonths,
|
||||
sortedMonths,
|
||||
|
||||
@@ -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[]>([])
|
||||
|
||||
@@ -97,8 +98,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 +120,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 +163,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 +185,29 @@ export const useCalendarEvents = defineStore('calendar-events', () => {
|
||||
}
|
||||
}
|
||||
|
||||
return { allEvents, setEvents, currentEvents, getRelativeEventFromDate, getRelativeEventFromEvent }
|
||||
/**
|
||||
* EVENT CREATION FUNCTIONS
|
||||
*/
|
||||
/**
|
||||
* 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() {
|
||||
return await $fetch(`/api/calendars/events/create`, { method: 'POST', body: { event : eventSkeleton.value, calendarId: calendarId.value }})
|
||||
}
|
||||
|
||||
return { allEvents, setEvents, currentEvents, getRelativeEventFromDate, getRelativeEventFromEvent, eventSkeleton, resetSkeleton, submitSkeleton }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user