"use client" import { useEffect, useMemo, useState } from "react" import { Link2, Pencil, Plus, Trash2 } from "lucide-react" import { toast } from "sonner" import { AccountAvatar } from "@/components/gmail/account-avatar" import { Button } from "@/components/ui/button" import { Checkbox } from "@/components/ui/checkbox" import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { AGENDA_COLOR_PALETTE } from "@/lib/agenda/agenda-colors" import { isCalendarEnabledForAccount, isReservedAgendaViewName } from "@/lib/agenda/agenda-calendar-visibility" import { calendarColor } from "@/lib/agenda/agenda-events" import { useAgendaSettingsStore } from "@/lib/agenda/agenda-store" import type { AgendaCalendarView, AgendaExternalCalendar } from "@/lib/agenda/agenda-settings-types" import { useMergedAgendaCalendars } from "@/lib/agenda/use-visible-agenda-calendars" import { useLabels } from "@/lib/api/hooks/use-folder-label-queries" import { useMailAccounts } from "@/lib/api/hooks/use-mail-queries" import { MAIL_SETTINGS_PAGE_MASONRY_SECTION_CLASS } from "@/lib/mail-chrome-classes" import { MAIL_SETTINGS_BASE_PATH } from "@/lib/mail-settings/settings-nav" import { cn } from "@/lib/utils" function CalendarCheckboxRow({ calendarId, label, color, checked, onToggle, }: { calendarId: string label: string color: string checked: boolean onToggle: () => void }) { return ( ) } export function ExternalCalendarDialog({ open, onOpenChange, calendar, accounts, }: { open: boolean onOpenChange: (open: boolean) => void calendar: AgendaExternalCalendar | null accounts: { id: string; name: string; email: string }[] }) { const addExternalCalendar = useAgendaSettingsStore((s) => s.addExternalCalendar) const updateExternalCalendar = useAgendaSettingsStore((s) => s.updateExternalCalendar) const [name, setName] = useState("") const [url, setUrl] = useState("") const [color, setColor] = useState(AGENDA_COLOR_PALETTE[0].value) const [accountId, setAccountId] = useState("all") useEffect(() => { if (!open) return setName(calendar?.display_name ?? "") setUrl(calendar?.url ?? "") setColor(calendar?.color ?? AGENDA_COLOR_PALETTE[0].value) setAccountId(calendar?.account_id ?? "all") }, [open, calendar]) const submit = () => { const displayName = name.trim() const feedUrl = url.trim() if (!displayName || !feedUrl) return try { const parsed = new URL(feedUrl) if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { toast.error("L'URL doit commencer par http:// ou https://") return } } catch { toast.error("URL iCal invalide") return } const payload = { display_name: displayName, url: feedUrl, color, account_id: accountId === "all" ? null : accountId, } if (calendar) { updateExternalCalendar(calendar.id, payload) toast.success("Calendrier externe mis à jour") } else { addExternalCalendar(payload) toast.success("Calendrier externe ajouté") } onOpenChange(false) } return ( {calendar ? "Modifier le calendrier externe" : "Ajouter un calendrier iCal"}
setName(e.target.value)} placeholder="Calendrier externe" />
setUrl(e.target.value)} placeholder="https://…/calendar.ics" />
{AGENDA_COLOR_PALETTE.map((entry) => (
) } export function CalendarViewDialog({ open, onOpenChange, view, calendarOptions, labelOptions, }: { open: boolean onOpenChange: (open: boolean) => void view: AgendaCalendarView | null calendarOptions: { id: string; label: string; color: string }[] labelOptions: { id: string; label: string }[] }) { const addCalendarView = useAgendaSettingsStore((s) => s.addCalendarView) const updateCalendarView = useAgendaSettingsStore((s) => s.updateCalendarView) const [name, setName] = useState("") const [calendarIds, setCalendarIds] = useState([]) const [labelIds, setLabelIds] = useState([]) useEffect(() => { if (!open) return setName(view?.name ?? "") setCalendarIds(view?.calendar_ids ?? []) setLabelIds(view?.label_ids ?? []) }, [open, view]) const toggleCalendar = (calendarId: string) => { setCalendarIds((current) => current.includes(calendarId) ? current.filter((id) => id !== calendarId) : [...current, calendarId], ) } const toggleLabel = (labelId: string) => { setLabelIds((current) => current.includes(labelId) ? current.filter((id) => id !== labelId) : [...current, labelId], ) } const submit = () => { const trimmed = name.trim() if (!trimmed) return if (isReservedAgendaViewName(trimmed)) { toast.error('« Tous les agendas » est réservé à l\'affichage sans vue.') return } if (calendarIds.length === 0) { toast.error("Sélectionnez au moins un agenda") return } const payload = { name: trimmed, calendar_ids: calendarIds, label_ids: labelIds, } if (view) { updateCalendarView(view.id, payload) toast.success("Vue mise à jour") } else { addCalendarView(payload) toast.success("Vue créée") } onOpenChange(false) } return ( {view ? "Modifier la vue" : "Nouvelle vue d'agendas"}
setName(e.target.value)} placeholder="Travail, Perso…" />
{calendarOptions.length === 0 ? (

Aucun agenda disponible.

) : ( calendarOptions.map((calendar) => ( toggleCalendar(calendar.id)} /> )) )}

Pour filtrer les invitations importées depuis le mail (optionnel).

{labelOptions.length === 0 ? (

Aucun libellé.

) : ( labelOptions.map((label) => ( )) )}
) } export function AgendaCalendarsSettingsFields({ variant = "panel", }: { variant?: "panel" | "page" }) { const { data: accounts = [] } = useMailAccounts() const { data: labels = [] } = useLabels() const { calendars, externalCalendars } = useMergedAgendaCalendars() const accountEnabledCalendarIds = useAgendaSettingsStore( (s) => s.accountEnabledCalendarIds, ) const toggleAccountCalendar = useAgendaSettingsStore((s) => s.toggleAccountCalendar) const removeExternalCalendar = useAgendaSettingsStore((s) => s.removeExternalCalendar) const calendarViews = useAgendaSettingsStore((s) => s.calendarViews) const defaultCalendarViewId = useAgendaSettingsStore((s) => s.defaultCalendarViewId) const setDefaultCalendarViewId = useAgendaSettingsStore((s) => s.setDefaultCalendarViewId) const removeCalendarView = useAgendaSettingsStore((s) => s.removeCalendarView) const [externalDialogOpen, setExternalDialogOpen] = useState(false) const [editingExternal, setEditingExternal] = useState( null, ) const [viewDialogOpen, setViewDialogOpen] = useState(false) const [editingView, setEditingView] = useState(null) const allCalendarIds = useMemo( () => calendars.map((calendar) => calendar.id), [calendars], ) const calendarOptions = useMemo( () => calendars.map((calendar) => ({ id: calendar.id, label: calendar.display_name, color: calendarColor(calendar), })), [calendars], ) const labelOptions = useMemo( () => labels.map((label) => ({ id: label.id, label: label.name })), [labels], ) const isPage = variant === "page" const sectionShell = (position: "first" | "rest") => cn( "space-y-3", isPage ? MAIL_SETTINGS_PAGE_MASONRY_SECTION_CLASS : cn("px-4 py-3", position === "rest" && "border-t border-border"), ) return ( <>

Agendas par compte mail

Choisissez les agendas visibles pour chaque compte Ultimail.

{accounts.length === 0 ? (

Aucun compte mail connecté.{" "} Ajouter un compte

) : (
{accounts.map((account) => (

{account.name}

{account.email}

{calendars.length === 0 ? (

Aucun agenda.

) : ( calendars.map((calendar) => ( toggleAccountCalendar(account.id, calendar.id, allCalendarIds) } /> )) )}
))}
)}

Calendriers externes

Abonnez un flux iCal (Google, Outlook, etc.).

{externalCalendars.length === 0 ? (

Aucun calendrier externe.

) : (
    {externalCalendars.map((calendar) => (
  • {calendar.display_name}

    {calendar.url}

  • ))}
)}

Vues d'agendas

Regroupez des agendas et libellés, puis ouvrez une vue depuis la barre latérale.

{calendarViews.length === 0 ? (

Aucune vue enregistrée.

) : (
    {calendarViews.map((view) => (
  • {view.name}

    {view.calendar_ids.length} agenda {view.calendar_ids.length > 1 ? "s" : ""} {view.label_ids.length > 0 ? ` · ${view.label_ids.length} libellé${view.label_ids.length > 1 ? "s" : ""}` : ""}

  • ))}
)}
) }