From 637a5cd7e3246e394cf7076b0469b5dc7046c7dc Mon Sep 17 00:00:00 2001
From: Alexis <35.alexis.pele@gmail.com>
Date: Fri, 28 Mar 2025 20:54:12 +0100
Subject: [PATCH] Added sample behaviour for category modal
---
assets/main.css | 2 +-
components/calendar/Calendar.vue | 1 +
components/calendar/CalendarMenu.vue | 8 +-
components/calendar/CategoriesCTA.vue | 23 ++
.../{CalendarOptions.vue => OptionsCTA.vue} | 27 +-
components/calendar/dialog/Categories.vue | 36 +++
.../ui/alert-dialog/AlertDialogContent.vue | 2 +-
components/ui/button/index.ts | 2 +-
components/ui/dialog/DialogContent.vue | 2 +-
components/ui/dialog/DialogScrollContent.vue | 2 +-
.../ui/dropdown-menu/DropdownMenuArrow.vue | 13 +
components/ui/dropdown-menu/index.ts | 1 +
i18n.config.ts | 4 +-
stores/CalendarStore.ts | 15 +-
stores/EventStore.ts | 306 ------------------
15 files changed, 104 insertions(+), 340 deletions(-)
create mode 100644 components/calendar/CategoriesCTA.vue
rename components/calendar/{CalendarOptions.vue => OptionsCTA.vue} (74%)
create mode 100644 components/calendar/dialog/Categories.vue
create mode 100644 components/ui/dropdown-menu/DropdownMenuArrow.vue
delete mode 100644 stores/EventStore.ts
diff --git a/assets/main.css b/assets/main.css
index a480816..579d8b4 100644
--- a/assets/main.css
+++ b/assets/main.css
@@ -22,7 +22,7 @@
--primary: 245 58% 51%;
--primary-foreground: 0 0% 100%;
- --secondary: 210 40% 96.1%;
+ --secondary: 210 50% 95%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
diff --git a/components/calendar/Calendar.vue b/components/calendar/Calendar.vue
index 07234d7..49c6411 100644
--- a/components/calendar/Calendar.vue
+++ b/components/calendar/Calendar.vue
@@ -52,6 +52,7 @@ onMounted(() => {
+
diff --git a/components/calendar/CalendarMenu.vue b/components/calendar/CalendarMenu.vue
index 3c29a07..b8dce29 100644
--- a/components/calendar/CalendarMenu.vue
+++ b/components/calendar/CalendarMenu.vue
@@ -2,7 +2,6 @@
import { useCalendar } from "@/stores/CalendarStore"
import { PhMagnifyingGlass } from "@phosphor-icons/vue"
-import CalendarOptions from "./CalendarOptions.vue"
const { revealAdvancedSearch } = useCalendar()
const { isReadOnly } = storeToRefs(useCalendar())
@@ -13,7 +12,7 @@ const { isReadOnly } = storeToRefs(useCalendar())
diff --git a/components/calendar/CategoriesCTA.vue b/components/calendar/CategoriesCTA.vue
new file mode 100644
index 0000000..17f1066
--- /dev/null
+++ b/components/calendar/CategoriesCTA.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('entity.calendar.seeCategories') }}
+
+
+
+
+
diff --git a/components/calendar/CalendarOptions.vue b/components/calendar/OptionsCTA.vue
similarity index 74%
rename from components/calendar/CalendarOptions.vue
rename to components/calendar/OptionsCTA.vue
index 8628365..7a61982 100644
--- a/components/calendar/CalendarOptions.vue
+++ b/components/calendar/OptionsCTA.vue
@@ -1,32 +1,23 @@
-
+
+
+
{{ $t('entity.calendar.seeOptions') }}
@@ -54,16 +45,6 @@ const viewTypeTitle = computed(() => getViewTypeTitle(currentConfig.viewType))
-
-
-
-
-
- {{ $t('entity.category.namePlural') }}
-
-
-
-
diff --git a/components/calendar/dialog/Categories.vue b/components/calendar/dialog/Categories.vue
new file mode 100644
index 0000000..d63b49e
--- /dev/null
+++ b/components/calendar/dialog/Categories.vue
@@ -0,0 +1,36 @@
+
+
+
+
+ e.preventDefault()"
+ >
+
+
+ Gestion des catégories
+
+
+
+
+
+
+
+ catégories là
+
+
+
diff --git a/components/ui/alert-dialog/AlertDialogContent.vue b/components/ui/alert-dialog/AlertDialogContent.vue
index 776974c..ef4d8ec 100644
--- a/components/ui/alert-dialog/AlertDialogContent.vue
+++ b/components/ui/alert-dialog/AlertDialogContent.vue
@@ -25,7 +25,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
+import { DropdownMenuArrow, type DropdownMenuArrowProps, useForwardProps } from "radix-vue"
+
+const props = defineProps()
+
+const forwardedProps = useForwardProps(props)
+
+
+
+
+
+
+
diff --git a/components/ui/dropdown-menu/index.ts b/components/ui/dropdown-menu/index.ts
index 7e62fd1..b56627d 100644
--- a/components/ui/dropdown-menu/index.ts
+++ b/components/ui/dropdown-menu/index.ts
@@ -1,6 +1,7 @@
export { DropdownMenuPortal } from "radix-vue"
export { default as DropdownMenu } from "./DropdownMenu.vue"
+export { default as DropdownMenuArrow } from "./DropdownMenuArrow.vue"
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue"
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"
diff --git a/i18n.config.ts b/i18n.config.ts
index 175d7a1..13d913f 100644
--- a/i18n.config.ts
+++ b/i18n.config.ts
@@ -148,6 +148,7 @@ export default defineI18nConfig(() => ({
backToList: "Go back to calendars",
isLoading: "Calendar is loading…",
hasXEvents: "{count} available events",
+ seeCategories: "Modify categories",
seeOptions: "Calendar options",
date: {
start: "Start date",
@@ -395,7 +396,7 @@ export default defineI18nConfig(() => ({
search: "Rechercher les catégories",
notFoundAny: "Aucune catégorie trouvée.",
addPrimary: "Ajouter une catégorie principale",
- addSecondaries: "Ajouter des catégories secondaires"
+ addSecondaries: "Ajouter des catégories secondaires",
},
isLoading: "Chargement en cours…",
addDescription: "Ajouter une description",
@@ -450,6 +451,7 @@ export default defineI18nConfig(() => ({
backToList: "Retourner aux calendriers",
isLoading: "Chargement du calendrier…",
hasXEvents: "{count} évènements disponibles",
+ seeCategories: "Gestion des catégories",
seeOptions: "Options du calendrier",
date: {
start: "Date de début",
diff --git a/stores/CalendarStore.ts b/stores/CalendarStore.ts
index 1eab643..57a94fe 100644
--- a/stores/CalendarStore.ts
+++ b/stores/CalendarStore.ts
@@ -819,7 +819,7 @@ export const useCalendar = defineStore("calendar", () => {
}
/**
- * State for event modal edition
+ * State for event modal deletion
*/
const isDeleteEventModalOpen = ref(false)
@@ -910,6 +910,15 @@ export const useCalendar = defineStore("calendar", () => {
}
}
+ /**
+ * State for categories modal
+ */
+ const isCategoriesModalOpen = ref(false)
+
+ function toggleCategoriesModal(state: boolean) {
+ isCategoriesModalOpen.value = state
+ }
+
return {
isReadOnly,
setReadStatus,
@@ -969,6 +978,8 @@ export const useCalendar = defineStore("calendar", () => {
isEditEventModalOpen,
revealEditEventModal,
isDeleteEventModalOpen,
- revealDeleteEventModal
+ revealDeleteEventModal,
+ isCategoriesModalOpen,
+ toggleCategoriesModal
}
})
diff --git a/stores/EventStore.ts b/stores/EventStore.ts
deleted file mode 100644
index 935c59c..0000000
--- a/stores/EventStore.ts
+++ /dev/null
@@ -1,306 +0,0 @@
-import type { CalendarEvent } from "@/models/CalendarEvent"
-import type { RPGDate } from "@/models/Date"
-import { defineStore } from "pinia"
-import { ref, watch, type Ref } from "vue"
-import type { Category } from "~/models/Category"
-import { useCalendar } from "./CalendarStore"
-
-export const useCalendarEvents = defineStore("calendar-events", () => {
- const { activeCalendar, defaultDate, currentConfig, convertDateToDays, compareDates } = useCalendar()
- const { currentRPGDate } = storeToRefs(useCalendar())
-
- const baseEvents = ref([])
-
- async function fetchCalendarEvents(calendarId: number) {
- try {
- const res = await $fetch("/api/calendars/events/query", { query: { calendarId } })
-
- const eventData = res.data as CalendarEvent[]
-
- if (!eventData.length) return
-
- baseEvents.value = eventData
- } catch (err) {
- console.log(err)
- }
- }
-
- const categories = ref([])
-
- function setCategories(data: Category[]) {
- categories.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([])
-
- // 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([currentRPGDate, allEvents], () => {
- currentEvents.value = computeCurrentEvents()
- }, { deep: true, immediate: true })
-
- /**
- * Determines if the event can appear in the front end
- *
- * This function takes into consideration the viewType of the calendar config
- *
- * @param event The event to analyze
- * @returns Whether the event should appear in the current view
- */
- function shouldEventBeDisplayed(event: CalendarEvent): boolean {
- const isEventOnCurrentScreen =
- (event.startDate.year === currentRPGDate.value.day &&
- event.startDate.month === currentRPGDate.value.month) ||
- (event.endDate &&
- event.endDate.year === currentRPGDate.value.year &&
- event.endDate.month === currentRPGDate.value.month)
-
- switch (currentConfig.viewType) {
- case "month":
- return isEventOnCurrentScreen!
-
- case "year":
- return event.startDate.year === currentRPGDate.value.year
-
- case "decade":
- return (
- event.startDate.year >= currentRPGDate.value.year &&
- event.startDate.year <= currentRPGDate.value.year + 10
- )
-
- case "century":
- return (
- event.startDate.year >= currentRPGDate.value.year &&
- event.startDate.year <= currentRPGDate.value.year + 100
- )
-
- default:
- return false
- }
- }
-
- /**
- * Fetches all the current events for the current view
- *
- * @returns A list of events that can appear in the current view
- */
- function computeCurrentEvents(): CalendarEvent[] {
- return allEvents.value.filter((event) => shouldEventBeDisplayed(event))
- }
-
- /**
- * 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
- * @returns The next event in chronological order
- */
- function getRelativeEventFromEvent(
- event: CalendarEvent,
- position: "next" | "prev" = "next",
- initialIsEnd: boolean = false
- ): { event: CalendarEvent; targetDate: RPGDate } {
- let dateToParse: RPGDate // Day value of the date that the user interacted with
-
- if (initialIsEnd && event.endDate) {
- dateToParse = event.endDate
- } else {
- dateToParse = event.startDate
- }
-
- 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"
- ): { event: CalendarEvent; targetDate: RPGDate } {
- const pivotValue = convertDateToDays(date)
- let t: { eventData: CalendarEvent; distance: number; targetKey: "startDate" | "endDate" }[] = []
-
- // Loop over all event once to convert the structure to a usable one
- for (let i = 0; i < allEvents.value.length; i++) {
- const e: CalendarEvent = allEvents.value[i]
-
- // Estimate distance from pivot
- const startDateDays: number = convertDateToDays(e.startDate)
- const startDistance: number = startDateDays - pivotValue
-
- // Push startDate to comparator array
- t.push({
- eventData: e,
- distance: startDistance,
- targetKey: "startDate"
- })
-
- // Check the same things for endDate
- if (e.endDate) {
- const endDateDays: number = convertDateToDays(e.endDate)
- const endDistance: number = endDateDays - pivotValue
-
- // Push optional endDate to comparator array
- t.push({
- eventData: e,
- distance: endDistance,
- targetKey: "endDate"
- })
- }
- }
-
- // 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
- })
-
- if (!t.length) {
- throw new Error(
- "Aucun évènement suivant ou précédent trouvé ; Peut-être l'évènement se situe au début ou à la fin du calendrier ?"
- )
- }
-
- // Get event with remaining minimum distance
- const closestEvent = t.reduce((a, b) => {
- return Math.abs(b.distance) < Math.abs(a.distance) ? b : a
- })
-
- return {
- event: closestEvent.eventData,
- targetDate: closestEvent.eventData[closestEvent.targetKey]!
- }
- }
-
- /**
- * State for event modal edition
- */
- const isEditEventModalOpen: Ref = ref(false)
-
- function revealEditEventModal() {
- isEditEventModalOpen.value = true
- }
-
- /**
- * State for event modal edition
- */
- const isDeleteEventModalOpen: Ref = ref(false)
-
- function revealDeleteEventModal() {
- isDeleteEventModalOpen.value = true
- }
-
- /**
- * EVENT CREATION FUNCTIONS
- */
- const lastActiveEvent = ref()
- const isCreatingEvent = ref(false)
- const isUpdatingEvent = ref(false)
- const isDeletingEvent = ref(false)
- const operationInProgress = computed(() => isCreatingEvent.value || isUpdatingEvent.value || isDeletingEvent.value)
- let abortController: AbortController | null = null
-
- /**
- * 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() {
- abortController = new AbortController()
- isCreatingEvent.value = true
-
- try {
- const res = await $fetch("/api/calendars/events/create", { method: "POST", body: { event : eventSkeleton.value, calendarId: activeCalendar?.id }, signal: abortController.signal })
-
- baseEvents.value.push(res)
- } catch (err) {
- console.log(err)
- } finally {
- abortController = null
- isCreatingEvent.value = false
- }
- }
-
- async function updateEventFromSkeleton() {
- abortController = new AbortController()
- isUpdatingEvent.value = true
-
- const res = await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: "PATCH", body: { event : eventSkeleton.value, calendarId: activeCalendar?.id }, signal: abortController.signal })
-
- const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
- baseEvents.value[eventIndex] = res
-
- abortController = null
- isUpdatingEvent.value = false
- }
-
- async function deleteEventFromSkeleton() {
- abortController = new AbortController()
- isDeletingEvent.value = true
-
- try {
- await $fetch(`/api/calendars/events/${eventSkeleton.value.id}`, { method: "DELETE", signal: abortController.signal })
-
- const eventIndex = baseEvents.value.findIndex(e => e.id === eventSkeleton.value.id)
- baseEvents.value.splice(eventIndex, 1)
- } catch (err) {
- console.log(err)
- } finally {
- abortController = null
- isDeletingEvent.value = false
- }
- }
-
- function cancelLatestRequest() {
- if (abortController) {
- abortController.abort()
- }
- }
-
- return {
- allEvents,
- fetchCalendarEvents,
- categories,
- setCategories,
- currentEvents,
- getRelativeEventFromDate,
- getRelativeEventFromEvent,
- cancelLatestRequest,
- isCreatingEvent,
- isUpdatingEvent,
- isDeletingEvent,
- operationInProgress,
- eventSkeleton,
- resetSkeleton,
- submitSkeleton,
- lastActiveEvent,
- updateEventFromSkeleton,
- deleteEventFromSkeleton,
- isEditEventModalOpen,
- revealEditEventModal,
- isDeleteEventModalOpen,
- revealDeleteEventModal
- }
-})