-
- {{ event.title }}
-
+
+
+
+ {{ event.title }}
+
-
-
- {{ getFormattedDateTitle(event.startDate, true) }}
-
-
-
- Du {{ getFormattedDateTitle(event.startDate, true) }} au
- {{ getFormattedDateTitle(event.endDate, true) }}
-
-
-
+
+
+ {{ getFormattedDateTitle(event.startDate, true) }}
+
+
+
+ Du {{ getFormattedDateTitle(event.startDate, true) }} au
+ {{ getFormattedDateTitle(event.endDate, true) }}
+
+
+
+
@@ -133,7 +134,27 @@ function handleGotoRelativeEvent(position: 'next' | 'prev' = 'next') {
-
@@ -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);
+}
diff --git a/components/calendar/state/monthly/Layout.vue b/components/calendar/state/monthly/Layout.vue
index ba7066b..06ce384 100644
--- a/components/calendar/state/monthly/Layout.vue
+++ b/components/calendar/state/monthly/Layout.vue
@@ -27,7 +27,7 @@ const moveCalendarRight = useThrottleFn(() => {
{
})
const { revealAdvancedSearch } = useCalendar()
-const { isAdvancedSearchOpen } = storeToRefs(useCalendar())
const sidebarMenu: MenuItem[] = [
{
@@ -34,7 +33,5 @@ const sidebarMenu: MenuItem[] = [
-
-
diff --git a/server/api/calendars/events/[id].delete.ts b/server/api/calendars/events/[id].delete.ts
new file mode 100644
index 0000000..7a15913
--- /dev/null
+++ b/server/api/calendars/events/[id].delete.ts
@@ -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
()
+
+ if (error) throw error
+
+ return data
+ } catch (err) {
+ throw createError({
+ cause: 'Serveur',
+ status: 500,
+ fatal: false,
+ message: 'Une erreur inconnue est survenue.'
+ })
+ }
+})
diff --git a/server/api/calendars/events/[id].patch.ts b/server/api/calendars/events/[id].patch.ts
new file mode 100644
index 0000000..0e63993
--- /dev/null
+++ b/server/api/calendars/events/[id].patch.ts
@@ -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()
+
+ if (error) throw error
+
+ return data
+ } catch (err) {
+ throw createError({
+ cause: 'Serveur',
+ status: 500,
+ fatal: false,
+ message: 'Une erreur inconnue est survenue.'
+ })
+ }
+})
diff --git a/server/api/calendars/events/create.post.ts b/server/api/calendars/events/create.post.ts
new file mode 100644
index 0000000..250f2cc
--- /dev/null
+++ b/server/api/calendars/events/create.post.ts
@@ -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()
+
+ if (error) throw error
+
+ return data
+ } catch (err) {
+ throw createError({
+ cause: 'Serveur',
+ status: 500,
+ fatal: false,
+ message: 'Une erreur inconnue est survenue.'
+ })
+ }
+})
diff --git a/server/api/events/query.get.ts b/server/api/calendars/events/query.get.ts
similarity index 100%
rename from server/api/events/query.get.ts
rename to server/api/calendars/events/query.get.ts
diff --git a/stores/CalendarStore.ts b/stores/CalendarStore.ts
index be8fd99..1b7fe75 100644
--- a/stores/CalendarStore.ts
+++ b/stores/CalendarStore.ts
@@ -36,6 +36,11 @@ export const useCalendar = defineStore('calendar', () => {
'century'
])
+ const calendarId = ref(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 = ref(false)
- /**
- * Opens the search modal
- */
function revealAdvancedSearch() {
isAdvancedSearchOpen.value = true
}
+ /**
+ * State for advanced search modal
+ */
+ const isEditEventModalOpen: Ref = ref(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
}
})
diff --git a/stores/EventStore.ts b/stores/EventStore.ts
index 3ed9002..d703b5a 100644
--- a/stores/EventStore.ts
+++ b/stores/EventStore.ts
@@ -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([])
@@ -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 = ref(computeCurrentEvents())
+ const currentEvents: Ref = 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()
+ /**
+ * Dummy event to hold creation data
+ */
+ const eventSkeleton: Ref = ref({ 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 }
})