"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 { 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 }) => { 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 }) => { 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"] }) }, }) }