espace-paie-odentas/components/DatesQuantityModal.tsx

239 lines
7.5 KiB
TypeScript

"use client";
import React, { useState, useMemo } 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;
}) => void;
selectedDates: string[]; // format input "12/10, 13/10, ..."
dateType: "representations" | "repetitions" | "jours_travail"; // Type de dates pour déterminer le libellé
minDate?: string;
maxDate?: string;
}
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,
}: 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<string, number | string> = {};
selectedIsos.forEach((iso) => {
map[iso] = 1;
});
return map;
}, [selectedIsos]);
const [quantities, setQuantities] = useState<Record<string, number | string>>(initialQuantities);
// Erreur de validation
const [validationError, setValidationError] = useState<string>("");
// 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)";
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)";
default:
return "";
}
}
// 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 "";
return formatQuantifiedDates(selectedIsos, quantities, dateType);
}, [selectedIsos, quantities, dateType]);
const handleQuantityChange = (iso: string, value: string) => {
const numValue = value === "" ? "" : parseInt(value) || 0;
// Validation : min 1, max 3
if (value !== "" && (numValue < 1 || numValue > 3)) {
setValidationError(`La quantité doit être entre 1 et 3`);
return;
} else {
setValidationError("");
}
setQuantities({ ...quantities, [iso]: numValue });
};
const handleApply = () => {
// Vérifier que toutes les quantités sont > 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;
}
}
if (!pdfFormatted) {
setValidationError("Erreur lors du formatage des dates");
return;
}
onApply({
selectedDates: selectedDates,
hasMultiMonth: selectedIsos.length > 0 && checkMultiMonth(selectedIsos),
pdfFormatted,
});
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 */}
<div className="fixed inset-0 bg-black/30 z-40" onClick={onClose} />
{/* Modale */}
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-md max-h-[80vh] flex flex-col">
{/* Header */}
<div className="flex items-center justify-between p-4 border-b bg-gradient-to-r from-indigo-50 to-purple-50">
<h2 className="text-lg font-semibold text-slate-900">
Indiquez les quantités
</h2>
<button
onClick={onClose}
className="p-1 hover:bg-white/50 rounded-lg transition"
>
<X className="w-5 h-5 text-slate-500" />
</button>
</div>
{/* Contenu */}
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{validationError && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-sm text-red-800">
{validationError}
</div>
)}
{dateGroups.length === 0 ? (
<div className="text-center py-8 text-slate-500">
Aucune date sélectionnée
</div>
) : (
// Afficher la liste des dates individuelles pour permettre quantité par date
selectedIsos.map((iso) => (
<div key={iso} className="p-3 border rounded-lg bg-slate-50 space-y-2">
<div className="text-sm font-medium text-slate-700">{formatDateFr(iso)}</div>
<div className="flex items-center gap-2">
<Input
type="number"
min={1}
max={3}
placeholder="1-3"
value={quantities[iso] ?? ""}
onChange={(e) => handleQuantityChange(iso, e.target.value)}
className="w-20"
/>
<span className="text-sm text-slate-600">{getUnitLabel(dateType, false)}</span>
</div>
</div>
))
)}
{/* Aperçu du texte généré */}
{pdfFormatted && (
<div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
<div className="text-xs font-semibold text-blue-900 mb-1">
Aperçu:
</div>
<div className="text-sm text-blue-800 font-mono break-words">
{pdfFormatted}
</div>
</div>
)}
</div>
{/* Footer */}
<div className="flex gap-2 p-4 border-t bg-slate-50">
<Button
variant="outline"
onClick={onClose}
className="flex-1"
>
Annuler
</Button>
<Button
onClick={handleApply}
disabled={!pdfFormatted}
className="flex-1"
>
Appliquer
</Button>
</div>
</div>
</div>
</>
);
}