import { useState, useEffect } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Calendar, ArrowLeft, Save, Plus, Trash2, AlertCircle } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { toast } from "sonner"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertDescription } from "@/components/ui/alert"; interface TypeConge { id: number; nom: string; couleur: string; } interface LigneConge { typeId: number; nombreJours: number; } interface Employe { id: number; nom: string; prenom: string; email: string; service: string; soldeCP?: number; soldeRTT?: number; } const SaisieManuelle = () => { const navigate = useNavigate(); const [employes, setEmployes] = useState([]); const [typesConge, setTypesConge] = useState([]); const [loading, setLoading] = useState(false); const [employeSelectionne, setEmployeSelectionne] = useState(null); const [formData, setFormData] = useState({ employeId: "", dateDebut: "", dateFin: "", commentaire: "" }); const [lignesConge, setLignesConge] = useState([ { typeId: 0, nombreJours: 0 } ]); useEffect(() => { chargerDonnees(); }, []); const chargerDonnees = async () => { try { const token = localStorage.getItem('token'); // ✅ Charger les employés const respEmployes = await fetch('/api/employes', { headers: { 'Authorization': `Bearer ${token}` } }); if (!respEmployes.ok) { throw new Error(`Erreur HTTP ${respEmployes.status}`); } const dataEmployes = await respEmployes.json(); // ✅ LOG DÉBOGAGE console.log('📊 Employés reçus:', dataEmployes); console.log(' Type:', Array.isArray(dataEmployes) ? 'Array' : typeof dataEmployes); console.log(' Longueur:', dataEmployes?.length); // ✅ VÉRIFICATION : S'assurer que c'est un tableau if (Array.isArray(dataEmployes)) { // ✅ Filtrer les entrées invalides const employesValides = dataEmployes.filter(emp => emp && emp.id !== undefined && emp.nom && emp.prenom ); console.log(' Employés valides:', employesValides.length); setEmployes(employesValides); } else { console.error('❌ dataEmployes n\'est pas un tableau:', dataEmployes); toast.error("Format de données invalide"); setEmployes([]); } // ✅ Charger les types de congé const respTypes = await fetch('/api/types-conge', { headers: { 'Authorization': `Bearer ${token}` } }); if (!respTypes.ok) { throw new Error(`Erreur HTTP ${respTypes.status}`); } const dataTypes = await respTypes.json(); if (Array.isArray(dataTypes)) { setTypesConge(dataTypes); } else { console.error('❌ dataTypes n\'est pas un tableau:', dataTypes); setTypesConge([]); } } catch (error) { console.error('❌ Erreur chargement données:', error); toast.error("Erreur lors du chargement des données"); setEmployes([]); setTypesConge([]); } }; const ajouterLigne = () => { const joursOuvres = calculerJoursOuvres(formData.dateDebut, formData.dateFin); setLignesConge([...lignesConge, { typeId: 0, nombreJours: joursOuvres }]); }; const supprimerLigne = (index: number) => { if (lignesConge.length > 1) { setLignesConge(lignesConge.filter((_, i) => i !== index)); } }; const modifierLigne = (index: number, field: keyof LigneConge, value: any) => { const nouvelles = [...lignesConge]; nouvelles[index] = { ...nouvelles[index], [field]: value }; setLignesConge(nouvelles); }; // Mettre à jour automatiquement tous les nombres de jours quand les dates changent const handleDateChange = (field: 'dateDebut' | 'dateFin', value: string) => { const nouvellesData = { ...formData, [field]: value }; setFormData(nouvellesData); // Recalculer les jours pour toutes les lignes if (nouvellesData.dateDebut && nouvellesData.dateFin) { const joursOuvres = calculerJoursOuvres(nouvellesData.dateDebut, nouvellesData.dateFin); const nouvellesLignes = lignesConge.map(ligne => ({ ...ligne, nombreJours: ligne.nombreJours === 0 ? joursOuvres : ligne.nombreJours })); setLignesConge(nouvellesLignes); } }; const calculerTotalJours = () => { return lignesConge.reduce((total, ligne) => total + (ligne.nombreJours || 0), 0); }; const calculerJoursOuvres = (debut: string, fin: string) => { if (!debut || !fin) return 0; const dateDebut = new Date(debut); const dateFin = new Date(fin); let jours = 0; for (let d = new Date(dateDebut); d <= dateFin; d.setDate(d.getDate() + 1)) { const jour = d.getDay(); if (jour !== 0 && jour !== 6) { jours++; } } return jours; }; const handleEmployeChange = (employeId: string) => { setFormData({ ...formData, employeId }); const employe = employes.find(e => e.id.toString() === employeId); setEmployeSelectionne(employe || null); }; const calculerJoursParType = (typeId: number) => { return lignesConge .filter(ligne => ligne.typeId === typeId) .reduce((total, ligne) => total + (ligne.nombreJours || 0), 0); }; const obtenirSoldeParType = (typeId: number): number => { if (!employeSelectionne) return 0; if (typeId === 1) return employeSelectionne.soldeCP ?? 0; if (typeId === 2) return employeSelectionne.soldeRTT ?? 0; return 0; }; const obtenirNomType = (typeId: number): string => { const type = typesConge.find(t => t.id === typeId); return type?.nom || ''; }; const calculerRestantParType = (typeId: number): number => { const solde = obtenirSoldeParType(typeId); const saisis = calculerJoursParType(typeId); return solde - saisis; }; const verifierDepassement = () => { if (!employeSelectionne) return false; const joursCP = calculerJoursParType(1); const joursRTT = calculerJoursParType(2); return (joursCP > (employeSelectionne.soldeCP ?? 0)) || (joursRTT > (employeSelectionne.soldeRTT ?? 0)); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.employeId || !formData.dateDebut || !formData.dateFin) { toast.error("Veuillez remplir tous les champs obligatoires"); return; } // Vérifier que au moins une ligne a des données valides const lignesValides = lignesConge.filter(l => l.typeId > 0 && l.nombreJours > 0); if (lignesValides.length === 0) { toast.error("Veuillez remplir au moins une ligne avec un type de congé et un nombre de jours"); return; } if (verifierDepassement()) { toast.warning("⚠️ Attention: dépassement des compteurs disponibles"); } setLoading(true); try { const token = localStorage.getItem('token'); const response = await fetch('/api/demandes', { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ collaborateurId: formData.employeId, dateDebut: formData.dateDebut, dateFin: formData.dateFin, typesConge: lignesValides.map(l => ({ typeId: l.typeId, nombreJours: l.nombreJours })), commentaire: formData.commentaire }) }); if (response.ok) { toast.success("Demande enregistrée avec succès"); navigate("/dashboard"); } else { throw new Error('Erreur lors de l\'enregistrement'); } } catch (error) { toast.error("Erreur lors de l'enregistrement"); } finally { setLoading(false); } }; const joursOuvres = calculerJoursOuvres(formData.dateDebut, formData.dateFin); const totalJours = calculerTotalJours(); const aDepassement = verifierDepassement(); return (

Saisie manuelle

Nouvelle demande de congé Enregistrer manuellement une demande d'absence ou de récupération

Informations générales

{/* ✅ SECTION EMPLOYÉ CORRIGÉE */}
{/* ✅ Indicateur de chargement */} {employes.length === 0 && (

Chargement des collaborateurs...

)} {employes.length > 0 && (

{employes.length} collaborateur{employes.length > 1 ? 's' : ''} disponible{employes.length > 1 ? 's' : ''}

)}
{employeSelectionne && (

Compteurs disponibles

Congés Payés (CP)

{employeSelectionne.soldeCP ?? 0}

jours
{calculerJoursParType(1) > 0 && (

Saisie en cours: {calculerJoursParType(1)}j {calculerJoursParType(1) > (employeSelectionne.soldeCP ?? 0) && ( ⚠️ Dépassement! )}

)}

RTT

{employeSelectionne.soldeRTT ?? 0}

jours
{calculerJoursParType(2) > 0 && (

Saisie en cours: {calculerJoursParType(2)}j {calculerJoursParType(2) > (employeSelectionne.soldeRTT ?? 0) && ( ⚠️ Dépassement! )}

)}
)}
handleDateChange('dateDebut', e.target.value)} required />
handleDateChange('dateFin', e.target.value)} required />
{formData.dateDebut && formData.dateFin && joursOuvres > 0 && (

Jours ouvrés de la période

{joursOuvres}

Total saisi

{totalJours}

{employeSelectionne && totalJours > 0 && (

Répartition par type

{calculerJoursParType(1) > 0 && (
CP:
{calculerJoursParType(1)}j Restants: {calculerRestantParType(1)}j
)} {calculerJoursParType(2) > 0 && (
RTT:
{calculerJoursParType(2)}j Restants: {calculerRestantParType(2)}j
)} {lignesConge.filter(l => l.typeId > 2 && l.nombreJours > 0).map((ligne, idx) => { const typeNom = obtenirNomType(ligne.typeId); return typeNom ? (
{typeNom}: {calculerJoursParType(ligne.typeId)}j
) : null; })}
)}
)}

Types de congé

{lignesConge.map((ligne, index) => { const soldeDisponible = obtenirSoldeParType(ligne.typeId); const dejaSaisi = calculerJoursParType(ligne.typeId); const restant = soldeDisponible - dejaSaisi; const enDepassement = restant < 0; return (
modifierLigne(index, 'nombreJours', parseFloat(e.target.value) || 0)} placeholder={joursOuvres > 0 ? joursOuvres.toString() : "0"} className={enDepassement && ligne.nombreJours > 0 ? "border-destructive" : ""} />
{/* Indicateur de solde pour cette ligne */} {employeSelectionne && ligne.typeId > 0 && ligne.nombreJours > 0 && (
{obtenirNomType(ligne.typeId)}: {soldeDisponible}j disponibles {dejaSaisi > 0 && ( <> {' '}→ {dejaSaisi}j saisis = {restant}j restants {enDepassement && ' ⚠️'} )}
)}
); })}
{aDepassement && ( Attention: La saisie dépasse les compteurs disponibles du collaborateur. La demande sera tout de même enregistrée mais nécessitera une validation. )}