From 3c1b20cee905ae20acca7d173bb42f9eaa5b4958 Mon Sep 17 00:00:00 2001 From: alma Date: Wed, 14 Jan 2026 14:06:20 +0100 Subject: [PATCH] Agenda Sync refactor --- components/calendar/calendar-client.tsx | 259 +++++++++++++++++++++++- 1 file changed, 256 insertions(+), 3 deletions(-) diff --git a/components/calendar/calendar-client.tsx b/components/calendar/calendar-client.tsx index a2e9311..68dcdcf 100644 --- a/components/calendar/calendar-client.tsx +++ b/components/calendar/calendar-client.tsx @@ -26,7 +26,9 @@ import { MapPin, Tag, ChevronDown, - ChevronUp + ChevronUp, + RefreshCw, + Link as LinkIcon } from "lucide-react"; import { Calendar, Event } from "@prisma/client"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; @@ -114,18 +116,40 @@ interface CalendarDialogProps { onClose: () => void; onSave: (calendarData: Partial) => Promise; onDelete?: (calendarId: string) => Promise; + onSyncSetup?: (calendarId: string, mailCredentialId: string, externalCalendarUrl: string) => Promise; initialData?: Partial; + syncConfig?: { + id: string; + provider: string; + syncEnabled: boolean; + lastSyncAt: Date | null; + mailCredential?: { + id: string; + email: string; + display_name: string | null; + } | null; + } | null; } -function CalendarDialog({ open, onClose, onSave, onDelete, initialData }: CalendarDialogProps) { +function CalendarDialog({ open, onClose, onSave, onDelete, onSyncSetup, initialData, syncConfig }: CalendarDialogProps) { const [name, setName] = useState(initialData?.name || ""); const [color, setColor] = useState(initialData?.color || "#4f46e5"); const [description, setDescription] = useState(initialData?.description || ""); const [isSubmitting, setIsSubmitting] = useState(false); const [customColorMode, setCustomColorMode] = useState(false); + // Sync state + const [showSyncSection, setShowSyncSection] = useState(false); + const [availableAccounts, setAvailableAccounts] = useState>([]); + const [selectedAccountId, setSelectedAccountId] = useState(""); + const [availableCalendars, setAvailableCalendars] = useState>([]); + const [selectedCalendarUrl, setSelectedCalendarUrl] = useState(""); + const [isDiscovering, setIsDiscovering] = useState(false); + const [isSettingUpSync, setIsSettingUpSync] = useState(false); + const isMainCalendar = initialData?.name === "Calendrier principal"; const isMissionOrGroupCalendar = initialData?.name?.startsWith("Mission:") || initialData?.name?.startsWith("Groupe:"); + const isPrivateCalendar = !isMissionOrGroupCalendar && (initialData?.name === "Privée" || initialData?.name === "Default" || !initialData?.name); useEffect(() => { if (open) { @@ -133,8 +157,81 @@ function CalendarDialog({ open, onClose, onSave, onDelete, initialData }: Calend setColor(initialData?.color || "#4f46e5"); setDescription(initialData?.description || ""); setCustomColorMode(!colorPalette.includes(initialData?.color || "#4f46e5")); + setShowSyncSection(false); + setSelectedAccountId(""); + setAvailableCalendars([]); + setSelectedCalendarUrl(""); + + // Load available accounts for sync + if (isPrivateCalendar && !syncConfig) { + loadAvailableAccounts(); + } } - }, [open, initialData]); + }, [open, initialData, syncConfig, isPrivateCalendar]); + + const loadAvailableAccounts = async () => { + try { + const response = await fetch("/api/courrier/account-list"); + if (response.ok) { + const data = await response.json(); + if (data.success && data.accounts) { + // Filter Infomaniak accounts only + const infomaniakAccounts = data.accounts.filter((acc: any) => + acc.host && acc.host.includes('infomaniak') + ); + setAvailableAccounts(infomaniakAccounts.map((acc: any) => ({ + id: acc.id, + email: acc.email, + display_name: acc.display_name + }))); + } + } + } catch (error) { + console.error("Error loading accounts:", error); + } + }; + + const handleDiscoverCalendars = async () => { + if (!selectedAccountId) return; + + setIsDiscovering(true); + try { + const response = await fetch("/api/calendars/sync/discover", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ mailCredentialId: selectedAccountId }), + }); + + if (response.ok) { + const data = await response.json(); + setAvailableCalendars(data.calendars || []); + } else { + const error = await response.json(); + alert(error.error || "Erreur lors de la découverte des calendriers"); + } + } catch (error) { + console.error("Error discovering calendars:", error); + alert("Erreur lors de la découverte des calendriers"); + } finally { + setIsDiscovering(false); + } + }; + + const handleSetupSync = async () => { + if (!initialData?.id || !selectedAccountId || !selectedCalendarUrl || !onSyncSetup) return; + + setIsSettingUpSync(true); + try { + await onSyncSetup(initialData.id, selectedAccountId, selectedCalendarUrl); + setShowSyncSection(false); + alert("Synchronisation configurée avec succès !"); + } catch (error) { + console.error("Error setting up sync:", error); + alert("Erreur lors de la configuration de la synchronisation"); + } finally { + setIsSettingUpSync(false); + } + }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -284,6 +381,137 @@ function CalendarDialog({ open, onClose, onSave, onDelete, initialData }: Calend className="rounded-lg border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 bg-white text-gray-900" /> + + {/* Sync Section for Private Calendars */} + {isPrivateCalendar && initialData?.id && ( +
+ {syncConfig?.syncEnabled ? ( +
+
+
+

Synchronisation active

+

+ {syncConfig.mailCredential?.display_name || syncConfig.mailCredential?.email || "Compte"} +

+ {syncConfig.lastSyncAt && ( +

+ Dernière sync: {new Date(syncConfig.lastSyncAt).toLocaleString('fr-FR')} +

+ )} +
+ + + Sync + +
+
+ ) : ( + <> +
+ + +
+ + {showSyncSection && ( +
+
+ + +
+ + {selectedAccountId && ( + <> + + + {availableCalendars.length > 0 && ( +
+ + +
+ )} + + {selectedCalendarUrl && ( + + )} + + )} +
+ )} + + )} +
+ )} @@ -1274,7 +1502,32 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend onClose={() => setIsCalendarModalOpen(false)} onSave={handleCalendarSave} onDelete={handleCalendarDelete} + onSyncSetup={async (calendarId, mailCredentialId, externalCalendarUrl) => { + try { + const response = await fetch("/api/calendars/sync", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + calendarId, + mailCredentialId, + externalCalendarUrl, + provider: "infomaniak", + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Erreur lors de la configuration"); + } + + await fetchCalendars(); + } catch (error) { + console.error("Error setting up sync:", error); + throw error; + } + }} initialData={selectedCalendar || undefined} + syncConfig={selectedCalendar ? (calendars.find(c => c.id === selectedCalendar.id) as CalendarWithMission)?.syncConfig || null : null} /> {/* Event dialog */}