"use client"; import React, { useState, useMemo, useEffect } from "react"; import { X } from "lucide-react"; import { convertIsoDatesToGroups, formatDateFr, parseFrenchedDate, formatQuantifiedDates, } from "@/lib/dateFormatter"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; interface DatesQuantityModalProps { isOpen: boolean; onClose: () => void; onApply: (result: { selectedDates: string[]; hasMultiMonth: boolean; pdfFormatted: string; globalQuantity?: number; globalDuration?: "3" | "4"; totalHours?: number; // Total des heures saisies (pour jours_travail) totalQuantities?: number; // Somme de toutes les quantités saisies par jour }) => void; selectedDates: string[]; // format input "12/10, 13/10, ..." dateType: "representations" | "repetitions" | "jours_travail" | "heures_repetitions"; // Type de dates pour déterminer le libellé minDate?: string; maxDate?: string; allowSkipHoursByDay?: boolean; // Permet de cocher "Ne pas appliquer d'heures par jour" repetitionDuration?: "3" | "4"; // Durée des services de répétition (3 ou 4 heures) } interface DateGroupWithQuantity { displayFr: string; // "le 12/10" ou "du 14/10 au 17/10" startIso: string; endIso?: string; isRange: boolean; quantity: number | string; unit: string; // "représentation(s)", "service(s) de répétition", "heure(s)" } export default function DatesQuantityModal({ isOpen, onClose, onApply, selectedDates, dateType, minDate, maxDate, allowSkipHoursByDay = false, repetitionDuration, }: DatesQuantityModalProps) { const yearContext = minDate || maxDate || new Date().toISOString().slice(0, 10); // Convertir les dates sélectionnées en ISO pour grouper const selectedIsos = useMemo(() => { return selectedDates .map((d) => parseFrenchedDate(d.trim(), yearContext)) .filter((iso) => iso.length === 10); }, [selectedDates, yearContext]); // Générer les groupes de dates const dateGroups = useMemo(() => convertIsoDatesToGroups(selectedIsos), [selectedIsos]); // État pour les quantités saisies par date ISO (object { iso: qty }) const initialQuantities = useMemo(() => { const map: Record = {}; selectedIsos.forEach((iso) => { map[iso] = 1; }); return map; }, [selectedIsos]); const [quantities, setQuantities] = useState>(initialQuantities); // État pour la checkbox "Ne pas appliquer d'heures par jour" const [skipHoursByDay, setSkipHoursByDay] = useState(false); // État pour le nombre global saisi quand skipHoursByDay est coché const [globalQuantity, setGlobalQuantity] = useState(""); // État pour la durée des services quand c'est des répétitions sans détail const [globalDuration, setGlobalDuration] = useState<"3" | "4">(repetitionDuration || "4"); // État pour le champ "Appliquer à toutes les dates" const [applyToAllValue, setApplyToAllValue] = useState(""); // Erreur de validation const [validationError, setValidationError] = useState(""); // Réinitialiser les quantités quand on coche "Ne pas appliquer d'heures par jour" useEffect(() => { if (skipHoursByDay) { // Vider toutes les quantités const emptyQuantities: Record = {}; selectedIsos.forEach((iso) => { emptyQuantities[iso] = ""; }); setQuantities(emptyQuantities); setApplyToAllValue(""); setValidationError(""); } else { // Réinitialiser le nombre global setGlobalQuantity(""); } }, [skipHoursByDay, selectedIsos]); // Déterminer le libellé et les unités disponibles function getDefaultUnit(type: string, isRange: boolean): string { switch (type) { case "representations": return "représentation(s)"; case "repetitions": return "service(s) de répétition"; case "jours_travail": return isRange ? "heure(s) par jour" : "heure(s)"; case "heures_repetitions": return isRange ? "heure(s) par jour" : "heure(s)"; default: return ""; } } function getUnitLabel(type: string, isRange: boolean): string { switch (type) { case "representations": return "représentation(s)"; case "repetitions": return "service(s) de répétition"; case "jours_travail": return isRange ? "heure(s) par jour" : "heure(s)"; case "heures_repetitions": return isRange ? "heure(s) par jour" : "heure(s)"; default: return ""; } } function getSkipCheckboxLabel(type: string): { title: string; description: string } { switch (type) { case "representations": return { title: "Ne pas appliquer de représentations par jour", description: "Vous pouvez choisir d'afficher le détail des représentations par jour sur le contrat de travail, ou uniquement le nombre global de représentations." }; case "repetitions": return { title: "Ne pas appliquer de répétitions par jour", description: "Vous pouvez choisir d'afficher le détail des répétitions par jour sur le contrat de travail, ou uniquement le nombre global de répétitions." }; case "jours_travail": case "heures_repetitions": default: return { title: "Ne pas appliquer d'heures par jour", description: "Vous pouvez choisir d'afficher le détail des heures de travail par jour sur le contrat de travail, ou uniquement le nombre global d'heures." }; } } const skipCheckboxText = getSkipCheckboxLabel(dateType); // Générer le texte formaté au format PDFMonkey en regroupant consécutifs avec même qty const pdfFormatted = useMemo(() => { if (!selectedIsos || selectedIsos.length === 0) return ""; // Si on ne veut pas de détails par jour, générer un format simplifié if (skipHoursByDay) { // Grouper les dates consécutives sans quantités const groups = convertIsoDatesToGroups(selectedIsos); const parts = groups.map(g => { if (g.type === "range") { // Le displayFr est déjà formaté comme "du DD/MM au DD/MM" // On le garde tel quel, pas besoin de manipulation return g.displayFr; } // Pour les dates isolées : "le DD/MM" return g.displayFr; }); return parts.join(' ; ') + '.'; } return formatQuantifiedDates(selectedIsos, quantities, dateType, repetitionDuration); }, [selectedIsos, quantities, dateType, skipHoursByDay, repetitionDuration]); const handleQuantityChange = (iso: string, value: string) => { // Gestion de la valeur vide: on permet l'effacement temporaire if (value === "") { setValidationError(""); setQuantities({ ...quantities, [iso]: "" }); return; } const n = parseInt(value, 10) || 0; // Validation selon le type de dates if (dateType === "heures_repetitions" || dateType === "jours_travail") { // Pour les heures, accepter n'importe quel nombre >= 1 if (n < 1) { setValidationError(`La quantité doit être au moins 1`); return; } } else { // Pour les représentations et services de répétition : limiter à 1-3 if (n < 1 || n > 3) { setValidationError(`La quantité doit être entre 1 et 3`); return; } } setValidationError(""); setQuantities({ ...quantities, [iso]: n }); }; const handleApplyToAll = () => { if (!applyToAllValue || applyToAllValue === "") { setValidationError("Veuillez saisir une quantité"); return; } const n = parseInt(applyToAllValue, 10); // Validation selon le type de dates if (dateType === "heures_repetitions" || dateType === "jours_travail") { if (n < 1) { setValidationError(`La quantité doit être au moins 1`); return; } } else { if (n < 1 || n > 3) { setValidationError(`La quantité doit être entre 1 et 3`); return; } } // Appliquer la valeur à toutes les dates const newQuantities: Record = {}; selectedIsos.forEach((iso) => { newQuantities[iso] = n; }); setQuantities(newQuantities); setValidationError(""); setApplyToAllValue(""); // Réinitialiser le champ }; const handleApply = () => { let globalQty: number | undefined = undefined; let globalDur: "3" | "4" | undefined = undefined; let totalHrs: number | undefined = undefined; let totalQtys: number | undefined = undefined; // Si on ne veut pas d'heures par jour, valider le nombre global if (skipHoursByDay) { const qty = parseInt(globalQuantity, 10); if (!globalQuantity || isNaN(qty) || qty < 1) { setValidationError("Veuillez saisir un nombre global valide (>= 1)"); return; } globalQty = qty; // Pour les répétitions, récupérer aussi la durée if (dateType === "repetitions") { globalDur = globalDuration; } } else { // Vérifier que toutes les quantités sont > 0 ET calculer la somme totale let sum = 0; for (const iso of selectedIsos) { const qty = quantities[iso]; if (!qty || qty === "" || (typeof qty === "number" && qty < 1)) { setValidationError("Toutes les quantités doivent être >= 1"); return; } // Calculer le total de TOUTES les quantités const qtyNum = typeof qty === "number" ? qty : parseInt(String(qty), 10); if (!isNaN(qtyNum)) { sum += qtyNum; } } // Stocker la somme totale pour les représentations/répétitions if (dateType === "representations" || dateType === "repetitions") { totalQtys = sum; } // Si c'est des jours de travail, on retourne le total d'heures if (dateType === "jours_travail" && sum > 0) { totalHrs = sum; } } if (!pdfFormatted) { setValidationError("Erreur lors du formatage des dates"); return; } onApply({ selectedDates: selectedDates, hasMultiMonth: selectedIsos.length > 0 && checkMultiMonth(selectedIsos), pdfFormatted, globalQuantity: globalQty, globalDuration: dateType === "repetitions" ? globalDuration : globalDur, totalHours: totalHrs, totalQuantities: totalQtys, }); onClose(); }; function checkMultiMonth(isos: string[]): boolean { if (isos.length <= 1) return false; const months = new Set(isos.map((iso) => iso.slice(0, 7))); // YYYY-MM return months.size > 1; } if (!isOpen) return null; return ( <> {/* Overlay */}
{ e.stopPropagation(); onClose(); }} /> {/* Modale */}
e.stopPropagation()} > {/* Header */}

Indiquez les quantités

{/* Contenu */}
{/* Card option pour ne pas appliquer d'heures par jour */} {allowSkipHoursByDay && (
{/* Champ de saisie du nombre global si coché */} {skipHoursByDay && (
setGlobalQuantity(e.target.value)} className="bg-white" /> {/* Durée pour les répétitions */} {dateType === "repetitions" && (
)}
)}
)} {validationError && (
{validationError}
)} {/* Champ pour appliquer à toutes les dates */} {!skipHoursByDay && selectedIsos.length > 1 && (
Appliquer la même quantité à toutes les dates
setApplyToAllValue(e.target.value)} className="w-24" /> {getUnitLabel(dateType, false)}
)} {/* Durée des services de répétitions */} {dateType === "repetitions" && (
Durée des services de répétitions
La durée des services de répétitions dépend de votre CCN et du type de profession. Consultez les minima pour plus d'infos ou contactez-nous en cas de doute.
)} {dateGroups.length === 0 ? (
Aucune date sélectionnée
) : skipHoursByDay ? ( // Si on ne veut pas d'heures par jour, afficher juste la liste des dates sans input
Dates sélectionnées :
{selectedIsos.map((iso) => (
{formatDateFr(iso)}
))}
) : ( // Afficher la liste des dates individuelles pour permettre quantité par date selectedIsos.map((iso) => (
{formatDateFr(iso)}
handleQuantityChange(iso, e.target.value)} className="w-20" /> {getUnitLabel(dateType, false)}
)) )} {/* Aperçu du texte généré */} {pdfFormatted && (
Aperçu:
{pdfFormatted}
)}
{/* Footer */}
); }