import { useState } from "react"; import DashboardLayout from "@/components/DashboardLayout"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { trpc } from "@/lib/trpc"; import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Filter, Download } from "lucide-react"; import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isSameDay, parseISO, addMonths, subMonths, startOfWeek, endOfWeek, addWeeks, subWeeks, addDays, subDays } from "date-fns"; import { fr } from "date-fns/locale"; type ViewMode = 'month' | 'week' | 'day'; export default function AdminCalendrier() { const [currentDate, setCurrentDate] = useState(new Date()); const [selectedSequence, setSelectedSequence] = useState(null); const [isDetailOpen, setIsDetailOpen] = useState(false); const [viewMode, setViewMode] = useState('month'); const [filterFormation, setFilterFormation] = useState('all'); const [filterLieu, setFilterLieu] = useState('all'); const [filterPublicCible, setFilterPublicCible] = useState('all'); const { data: sequences, isLoading } = trpc.sequences.list.useQuery(); const { data: formations } = trpc.formations.list.useQuery(); // Filtrer les séquences selon les critères const filteredSequences = sequences?.filter((seq) => { if (filterFormation !== 'all' && seq.formationId !== parseInt(filterFormation)) return false; if (filterLieu !== 'all' && seq.lieu !== filterLieu) return false; if (filterPublicCible !== 'all' && seq.publicCible !== filterPublicCible) return false; return true; }) || []; // Extraire les lieux uniques pour le filtre const uniqueLieux = Array.from(new Set(sequences?.map(s => s.lieu).filter(Boolean))) as string[]; // Extraire les publics cibles uniques const uniquePublicsCibles = Array.from(new Set(sequences?.map(s => s.publicCible).filter(Boolean))) as string[]; const monthStart = startOfMonth(currentDate); const monthEnd = endOfMonth(currentDate); const daysInMonth = eachDayOfInterval({ start: monthStart, end: monthEnd }); // Obtenir le premier jour de la semaine (lundi = 0) const firstDayOfWeek = (monthStart.getDay() + 6) % 7; // Créer un tableau avec les jours vides au début const calendarDays = [ ...Array(firstDayOfWeek).fill(null), ...daysInMonth, ]; const getSequencesForDay = (day: Date) => { if (!filteredSequences) return []; return filteredSequences.filter((seq) => { if (!seq.dates || seq.dates.length === 0) return false; return seq.dates.some((dateForm: any) => { // Gérer le cas où dateDebut est déjà un objet Date ou une chaîne let dateDebut: Date; if (typeof dateForm.dateDebut === 'string') { dateDebut = parseISO(dateForm.dateDebut); } else if (dateForm.dateDebut instanceof Date) { dateDebut = dateForm.dateDebut; } else { return false; } return isSameDay(dateDebut, day); }); }); }; const handlePrevious = () => { if (viewMode === 'month') { setCurrentDate(subMonths(currentDate, 1)); } else if (viewMode === 'week') { setCurrentDate(subWeeks(currentDate, 1)); } else { setCurrentDate(subDays(currentDate, 1)); } }; const handleNext = () => { if (viewMode === 'month') { setCurrentDate(addMonths(currentDate, 1)); } else if (viewMode === 'week') { setCurrentDate(addWeeks(currentDate, 1)); } else { setCurrentDate(addDays(currentDate, 1)); } }; const handleSequenceClick = (sequence: any) => { setSelectedSequence(sequence); setIsDetailOpen(true); }; const handleExportPDF = () => { window.print(); }; const handleExportICal = () => { const icalContent = [ 'BEGIN:VCALENDAR', 'VERSION:2.0', 'PRODID:-//Formation Manager Itinova//FR', 'CALSCALE:GREGORIAN', 'METHOD:PUBLISH', 'X-WR-CALNAME:Calendrier des formations', 'X-WR-TIMEZONE:Europe/Paris', ]; filteredSequences.forEach((seq) => { if (seq.dates && seq.dates.length > 0) { seq.dates.forEach((dateForm: any) => { const dateDebut = typeof dateForm.dateDebut === 'string' ? parseISO(dateForm.dateDebut) : dateForm.dateDebut; const dateFin = typeof dateForm.dateFin === 'string' ? parseISO(dateForm.dateFin) : dateForm.dateFin; const dtstart = format(dateDebut, "yyyyMMdd'T'HHmmss"); const dtend = format(dateFin, "yyyyMMdd'T'HHmmss"); icalContent.push( 'BEGIN:VEVENT', `UID:${seq.id}-${dateForm.id || Math.random()}@formation-manager.itinova`, `DTSTAMP:${format(new Date(), "yyyyMMdd'T'HHmmss'Z'")}`, `DTSTART:${dtstart}`, `DTEND:${dtend}`, `SUMMARY:${seq.nom}`, `DESCRIPTION:${seq.formation?.nom || 'Formation'} - ${seq.nbInscrits || 0}/${seq.capaciteMax} inscrits`, `LOCATION:${seq.lieu}`, 'STATUS:CONFIRMED', 'SEQUENCE:0', 'END:VEVENT' ); }); } }); icalContent.push('END:VCALENDAR'); const blob = new Blob([icalContent.join('\r\n')], { type: 'text/calendar;charset=utf-8' }); const link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = `calendrier-formations-${format(currentDate, "yyyy-MM")}.ics`; link.click(); }; const getPublicCibleLabel = (publicCible: string) => { const labels: Record = { directeurs: "Directeurs", chefs_service: "Chefs de service", tous: "Tous", autre: "Autre", }; return labels[publicCible] || publicCible; }; const getStatutBadge = (statut: string) => { const badges: Record = { ouverte: "bg-green-100 text-green-800", fermee: "bg-red-100 text-red-800", annulee: "bg-gray-100 text-gray-800", }; return badges[statut] || "bg-blue-100 text-blue-800"; }; return (

Calendrier des séquences

Vue mensuelle de toutes les séquences planifiées

{/* Filtres */}
Calendrier des séquences Cliquez sur une séquence pour voir les détails
{/* Barre de filtres */}
Filtres :
{(filterFormation !== 'all' || filterLieu !== 'all' || filterPublicCible !== 'all') && ( )}
{/* Navigation et exports */}

{viewMode === 'month' && format(currentDate, "MMMM yyyy", { locale: fr })} {viewMode === 'week' && `Semaine du ${format(startOfWeek(currentDate, { locale: fr }), "d MMM", { locale: fr })}`} {viewMode === 'day' && format(currentDate, "EEEE d MMMM yyyy", { locale: fr })}

{isLoading ? (
Chargement...
) : viewMode === 'month' ? (
{/* En-têtes des jours */} {["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"].map((day) => (
{day}
))} {/* Jours du calendrier */} {calendarDays.map((day, index) => { if (!day) { return
; } const daySequences = getSequencesForDay(day); const isToday = isSameDay(day, new Date()); const dayKey = format(day, "yyyy-MM-dd"); return (
{format(day, "d")}
{daySequences.map((seq) => ( ))}
); })}
) : viewMode === 'week' ? (
Heure
{Array.from({ length: 7 }).map((_, i) => { const day = addDays(startOfWeek(currentDate, { locale: fr }), i); return (
{format(day, "EEE", { locale: fr })}
{format(day, "d MMM", { locale: fr })}
); })}
Vue hebdomadaire - Affichage des séquences par jour
{Array.from({ length: 7 }).map((_, i) => { const day = addDays(startOfWeek(currentDate, { locale: fr }), i); const daySequences = getSequencesForDay(day); return daySequences.length > 0 ? (

{format(day, "EEEE d MMMM", { locale: fr })}

{daySequences.map((seq) => ( ))}
) : null; })}
) : (

{format(currentDate, "EEEE d MMMM yyyy", { locale: fr })}

{getSequencesForDay(currentDate).length > 0 ? (
{getSequencesForDay(currentDate).map((seq) => ( ))}
) : (
Aucune séquence prévue pour ce jour
)}
)} {/* Dialog de détails */} Détails de la séquence Informations complètes sur la séquence sélectionnée {selectedSequence && (

{selectedSequence.formation?.nom || "Sans titre"}

{selectedSequence.formation?.description || "Aucune description"}

Lieu

{selectedSequence.lieu}

Public cible

{getPublicCibleLabel(selectedSequence.publicCible)}

Capacité

{selectedSequence.nbInscrits || 0} / {selectedSequence.capaciteMax}

Statut

{selectedSequence.statut}
{selectedSequence.formateur && (

Formateur

{typeof selectedSequence.formateur === 'object' && selectedSequence.formateur !== null ? selectedSequence.formateur.nom || String(selectedSequence.formateur) : selectedSequence.formateur }

)} {selectedSequence.dateBloquage && (

Date de blocage

{typeof selectedSequence.dateBloquage === 'string' ? format(parseISO(selectedSequence.dateBloquage), "dd/MM/yyyy", { locale: fr }) : format(selectedSequence.dateBloquage, "dd/MM/yyyy", { locale: fr }) }

)}
{selectedSequence.dates && selectedSequence.dates.length > 0 && (

Dates de formation

{selectedSequence.dates.map((dateForm: any, index: number) => { const dateDebut = typeof dateForm.dateDebut === 'string' ? parseISO(dateForm.dateDebut) : dateForm.dateDebut; const dateFin = typeof dateForm.dateFin === 'string' ? parseISO(dateForm.dateFin) : dateForm.dateFin; const dateKey = `${selectedSequence.id}-date-${index}`; return (
Du {format(dateDebut, "dd/MM/yyyy", { locale: fr })} au{" "} {format(dateFin, "dd/MM/yyyy", { locale: fr })}
); })}
)}
)}
); }