ultisuite-client/components/agenda/agenda-sidebar.tsx
R3D347HR4Y 3bbf3691b0
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
bordel c'est beau
2026-06-11 10:10:39 +02:00

239 lines
8.4 KiB
TypeScript

"use client"
import { useState } from "react"
import { toast } from "sonner"
import { MoreVertical, Pencil, Plus, Trash2 } from "lucide-react"
import { AgendaCalendarDialog } from "@/components/agenda/agenda-calendar-dialog"
import { AgendaMiniMonth } from "@/components/agenda/agenda-mini-month"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { Skeleton } from "@/components/ui/skeleton"
import { useAgendaCalendars } from "@/lib/api/hooks/use-calendar-queries"
import { useDeleteAgendaCalendar } from "@/lib/api/hooks/use-calendar-mutations"
import { calendarColor } from "@/lib/agenda/agenda-events"
import { useAgendaSettingsStore, useAgendaUIStore } from "@/lib/agenda/agenda-store"
import type { AgendaCalendar } from "@/lib/agenda/agenda-types"
import { useIsMobile } from "@/hooks/use-mobile"
import { cn } from "@/lib/utils"
export function AgendaSidebar({
selectedDate,
onSelectDate,
onCreateEvent,
}: {
selectedDate: Date
onSelectDate: (date: Date) => void
onCreateEvent: () => void
}) {
const isMobile = useIsMobile()
const sidebarCollapsed = useAgendaUIStore((s) => s.sidebarCollapsed)
const setSidebarCollapsed = useAgendaUIStore((s) => s.setSidebarCollapsed)
const hiddenIds = useAgendaSettingsStore((s) => s.hiddenCalendarIds)
const toggleCalendar = useAgendaSettingsStore((s) => s.toggleCalendarVisible)
const { data: calendars, isLoading } = useAgendaCalendars()
const deleteMutation = useDeleteAgendaCalendar()
const [calendarDialogOpen, setCalendarDialogOpen] = useState(false)
const [editingCalendar, setEditingCalendar] = useState<AgendaCalendar | null>(null)
const [deletingCalendar, setDeletingCalendar] = useState<AgendaCalendar | null>(null)
const open = !sidebarCollapsed
const confirmDelete = async () => {
if (!deletingCalendar) return
try {
await deleteMutation.mutateAsync({ id: deletingCalendar.id })
toast.success(`Agenda « ${deletingCalendar.display_name} » supprimé`)
} catch {
toast.error("Impossible de supprimer cet agenda")
} finally {
setDeletingCalendar(null)
}
}
return (
<>
<aside
className={cn(
"flex h-full w-64 shrink-0 flex-col gap-4 overflow-y-auto bg-app-canvas px-3 pt-3 pb-4",
isMobile
? cn(
"fixed inset-y-0 left-0 z-50 shadow-xl transition-transform duration-200 ease-linear",
open ? "translate-x-0" : "-translate-x-full pointer-events-none",
)
: cn(!open && "hidden"),
)}
aria-hidden={isMobile && !open}
>
<Button
className="h-13 w-fit gap-3 rounded-2xl border border-border/60 bg-card px-5 text-[0.95rem] font-medium text-foreground shadow-md hover:bg-mail-nav-hover hover:shadow-lg"
variant="ghost"
onClick={() => {
onCreateEvent()
if (isMobile) setSidebarCollapsed(true)
}}
>
<Plus className="size-6 text-primary" />
Créer
</Button>
<AgendaMiniMonth
selected={selectedDate}
onSelect={(d) => {
onSelectDate(d)
if (isMobile) setSidebarCollapsed(true)
}}
/>
<div className="flex min-h-0 flex-col gap-0.5">
<div className="flex items-center justify-between pr-1 pl-2">
<span className="py-1 text-sm font-medium text-foreground/90">
Mes agendas
</span>
<Button
variant="ghost"
size="icon"
className="size-7 rounded-full text-muted-foreground"
aria-label="Créer un agenda"
onClick={() => {
setEditingCalendar(null)
setCalendarDialogOpen(true)
}}
>
<Plus className="size-4" />
</Button>
</div>
{isLoading && (
<div className="flex flex-col gap-2 px-2 py-1">
<Skeleton className="h-5 w-40" />
<Skeleton className="h-5 w-32" />
</div>
)}
{(calendars ?? []).map((cal) => {
const color = calendarColor(cal)
const visible = !hiddenIds.includes(cal.id)
return (
<div
key={cal.id}
className="group flex items-center gap-2.5 rounded-lg px-2 py-1.5 hover:bg-mail-nav-hover"
>
<label className="flex min-w-0 flex-1 cursor-pointer items-center gap-2.5">
<input
type="checkbox"
checked={visible}
onChange={() => toggleCalendar(cal.id)}
className="peer sr-only"
/>
<span
aria-hidden
className={cn(
"flex size-4.5 shrink-0 items-center justify-center rounded-[5px] border-2 transition-colors",
)}
style={{
borderColor: color,
backgroundColor: visible ? color : "transparent",
}}
>
{visible && (
<svg viewBox="0 0 24 24" className="size-3.5 text-white" fill="none">
<path
d="M5 13l4 4L19 7"
stroke="currentColor"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
</span>
<span className="truncate text-sm text-foreground/85">
{cal.display_name}
</span>
</label>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="size-7 shrink-0 rounded-full text-muted-foreground opacity-0 group-hover:opacity-100 data-[state=open]:opacity-100"
aria-label={`Options de ${cal.display_name}`}
>
<MoreVertical className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-44">
<DropdownMenuItem
onSelect={() => {
setEditingCalendar(cal)
setCalendarDialogOpen(true)
}}
>
<Pencil className="size-4" /> Modifier
</DropdownMenuItem>
<DropdownMenuItem
variant="destructive"
onSelect={() => setDeletingCalendar(cal)}
>
<Trash2 className="size-4" /> Supprimer
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
)
})}
</div>
</aside>
<AgendaCalendarDialog
open={calendarDialogOpen}
onOpenChange={setCalendarDialogOpen}
calendar={editingCalendar}
/>
<AlertDialog
open={deletingCalendar !== null}
onOpenChange={(o) => {
if (!o) setDeletingCalendar(null)
}}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
Supprimer « {deletingCalendar?.display_name} » ?
</AlertDialogTitle>
<AlertDialogDescription>
Tous les événements de cet agenda seront définitivement supprimés.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Annuler</AlertDialogCancel>
<AlertDialogAction
className="bg-destructive text-white hover:bg-destructive/90"
onClick={() => void confirmDelete()}
>
Supprimer
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}