ultisuite-client/lib/api/hooks/use-calendar-mutations.ts
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- Introduced turbopack alias for canvas in next.config.mjs.
- Updated package.json scripts for development and branding tasks.
- Added new dependencies for Tiptap extensions.
- Implemented new demo layouts for agenda, contacts, drive, and mail applications.
- Enhanced globals.css for improved theming and splash screen animations.
- Added OAuth callback handling for drive mounts.
- Updated layout components to integrate new demo shells and improve structure.
2026-06-12 19:10:24 +02:00

219 lines
6.4 KiB
TypeScript

"use client"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { apiClient } from "@/lib/api/client"
import { formatICSDateOnly, formatICSDateTimeUTC } from "@/lib/agenda/agenda-date"
import type { AgendaApiEvent, AgendaEventDraft } from "@/lib/agenda/agenda-types"
import { useIsDemoAgenda } from "@/lib/demo/demo-agenda-context"
import { DEMO_AGENDA_QUERY_ROOT } from "@/lib/demo/demo-agenda-bootstrap"
import { useDemoAgendaStore } from "@/lib/demo/demo-agenda-store"
function uidFromEventPath(path?: string): string | undefined {
if (!path) return undefined
const match = /\/([^/]+)\.ics$/i.exec(path.trim())
return match?.[1]
}
function eventApiPath(davPath: string): string {
return `/calendar/events/${davPath.replace(/^\/+/, "")}`
}
/** Convertit un brouillon UI en payload API (dates ICS). */
export function draftToApiEvent(
draft: AgendaEventDraft,
existing?: AgendaApiEvent,
opts?: { uid?: string; meetUrl?: string | null },
): Partial<AgendaApiEvent> {
const start = draft.allDay
? formatICSDateOnly(draft.start)
: formatICSDateTimeUTC(draft.start)
const end = draft.allDay
? formatICSDateOnly(draft.end)
: formatICSDateTimeUTC(draft.end)
let meet_url = existing?.meet_url ?? ""
if (opts?.meetUrl !== undefined) {
meet_url = opts.meetUrl ?? ""
}
return {
uid: opts?.uid ?? existing?.uid ?? uidFromEventPath(existing?.path),
summary: draft.title.trim() || "(Sans titre)",
description: draft.description ?? existing?.description ?? "",
location: draft.location ?? existing?.location ?? "",
start,
end,
all_day: draft.allDay,
attendees: draft.attendees ?? existing?.attendees ?? [],
organizer: existing?.organizer,
meet_url,
color: draft.color ?? existing?.color,
rrule: draft.rrule ?? existing?.rrule ?? "",
exdates: existing?.exdates,
}
}
function useInvalidateAgenda() {
const queryClient = useQueryClient()
const isDemoAgenda = useIsDemoAgenda()
return () => {
if (isDemoAgenda) {
useDemoAgendaStore.getState().bump()
void queryClient.invalidateQueries({ queryKey: DEMO_AGENDA_QUERY_ROOT })
return
}
void queryClient.invalidateQueries({ queryKey: ["agenda", "events"] })
}
}
export function useCreateAgendaEvent() {
const invalidate = useInvalidateAgenda()
const isDemoAgenda = useIsDemoAgenda()
return useMutation({
mutationFn: async ({
calendarId,
event,
}: {
calendarId: string
event: Partial<AgendaApiEvent>
}) => {
if (isDemoAgenda) {
const uid = event.uid ?? `demo-event-${crypto.randomUUID().slice(0, 8)}`
useDemoAgendaStore.getState().upsertEvent(calendarId, {
...event,
uid,
} as AgendaApiEvent)
return
}
return apiClient.post(`/calendar/${encodeURIComponent(calendarId)}/events`, event)
},
onSuccess: invalidate,
})
}
export function useUpdateAgendaEvent() {
const invalidate = useInvalidateAgenda()
const isDemoAgenda = useIsDemoAgenda()
return useMutation({
mutationFn: async ({
path,
etag,
event,
}: {
path: string
etag: string
event: Partial<AgendaApiEvent>
}) => {
if (isDemoAgenda) {
const calendarId = path.match(/\/calendars\/([^/]+)\//)?.[1] ?? "personal"
useDemoAgendaStore.getState().upsertEvent(calendarId, {
...event,
path,
etag: etag || `"demo-${event.uid ?? path}"`,
} as AgendaApiEvent)
return { etag: `"demo-v2"` }
}
return apiClient.put<{ etag: string }>(eventApiPath(path), event, {
"If-Match": etag || "*",
})
},
onSuccess: invalidate,
})
}
export function useDeleteAgendaEvent() {
const invalidate = useInvalidateAgenda()
const isDemoAgenda = useIsDemoAgenda()
return useMutation({
mutationFn: async ({ path }: { path: string }) => {
if (isDemoAgenda) {
useDemoAgendaStore.getState().deleteEvent(path)
return
}
return apiClient.delete(eventApiPath(path))
},
onSuccess: invalidate,
})
}
export function useRespondAgendaInvitation() {
const invalidate = useInvalidateAgenda()
return useMutation({
mutationFn: ({
path,
response,
etag,
}: {
path: string
response: "accepted" | "declined" | "tentative"
etag?: string
}) =>
apiClient.post<{ etag: string }>(
`/calendar/events/response/${path.replace(/^\/+/, "")}`,
{ response, if_match: etag ?? "" },
),
onSuccess: invalidate,
})
}
export function useCreateAgendaMeetLink() {
const invalidate = useInvalidateAgenda()
const isDemoAgenda = useIsDemoAgenda()
return useMutation({
mutationFn: async ({ path, etag }: { path: string; etag?: string }) => {
if (isDemoAgenda) {
const meetUrl = `https://meet.demo.ulti/${path.split("/").pop()?.replace(".ics", "") ?? "room"}`
const calendarId = path.match(/\/calendars\/([^/]+)\//)?.[1] ?? "personal"
useDemoAgendaStore.getState().upsertEvent(calendarId, {
path,
etag: etag ?? `"demo-meet"`,
meet_url: meetUrl,
} as AgendaApiEvent)
return { meet_url: meetUrl, etag: `"demo-meet"` }
}
return apiClient.post<{ meet_url: string; etag: string }>(
`/calendar/events/meet-link/${path.replace(/^\/+/, "")}`,
{ if_match: etag ?? "" },
)
},
onSuccess: invalidate,
})
}
export function useCreateAgendaCalendar() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (input: { display_name: string; color?: string }) =>
apiClient.post<{ id: string }>("/calendar", input),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["agenda", "calendars"] })
},
})
}
export function useUpdateAgendaCalendar() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({
id,
...input
}: {
id: string
display_name?: string
color?: string
}) => apiClient.patch(`/calendar/${encodeURIComponent(id)}`, input),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["agenda"] })
},
})
}
export function useDeleteAgendaCalendar() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id }: { id: string }) =>
apiClient.delete(`/calendar/${encodeURIComponent(id)}`),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["agenda"] })
},
})
}