Modif_GestionDoublonsGTARH_Fonctionnel_V1

This commit is contained in:
2025-12-02 17:57:33 +01:00
parent 721c02759f
commit a7a0e51c7a
96 changed files with 27161 additions and 0 deletions
+887
View File
@@ -0,0 +1,887 @@
import React, { useState, useEffect, useCallback } from 'react';
import {
Calendar, Lock, Unlock, Download, Menu, AlertCircle,
ChevronLeft, ChevronRight, FileText, RefreshCw,
Eye, Search, Users, CheckCircle, XCircle, Clock, LogOut, ArrowLeft, Filter
} from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
const CompteRenduRH = () => {
const navigate = useNavigate();
const [user, setUser] = useState<any>(null);
const [token, setToken] = useState<string>('');
// États
const [currentDate, setCurrentDate] = useState(new Date());
const [viewMode, setViewMode] = useState('mois');
const [isLoading, setIsLoading] = useState(true);
const [collaborateurs, setCollaborateurs] = useState<any[]>([]);
const [selectedCollab, setSelectedCollab] = useState<any>(null);
const [compteRenduData, setCompteRenduData] = useState<any[]>([]);
const [mensuelData, setMensuelData] = useState<any>(null);
const [statsGlobales, setStatsGlobales] = useState<any>(null);
const [searchTerm, setSearchTerm] = useState('');
const [showDetailModal, setShowDetailModal] = useState(false);
const [selectedJour, setSelectedJour] = useState<any>(null);
// 🆕 NOUVEAUX ÉTATS POUR LE FILTRE
const [services, setServices] = useState<any[]>([]);
const [selectedService, setSelectedService] = useState<string>('all');
const annee = currentDate.getFullYear();
const mois = currentDate.getMonth() + 1;
// ✅ ÉTAPE 1 : Charger l'utilisateur et vérifier l'accès
useEffect(() => {
const userData = localStorage.getItem('user');
const userToken = localStorage.getItem('token');
if (!userData || !userToken) {
toast.error("Session expirée, reconnexion nécessaire");
navigate('/');
return;
}
const parsedUser = JSON.parse(userData);
if (!['RH', 'Admin'].includes(parsedUser?.role)) {
toast.error("Accès réservé aux RH et Administrateurs");
navigate('/dashboard');
return;
}
setUser(parsedUser);
setToken(userToken);
}, [navigate]);
// 🆕 ÉTAPE 1.5 : Charger la liste des services
useEffect(() => {
if (!token) return;
const loadServices = async () => {
try {
const response = await fetch('/api/compte-rendu/services', {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const data = await response.json();
console.log('📋 Services disponibles:', data);
setServices(data);
}
} catch (error) {
console.error('❌ Erreur chargement services:', error);
}
};
loadServices();
}, [token]);
// ✅ ÉTAPE 2 : Charger les collaborateurs (avec filtre service côté client)
useEffect(() => {
if (!token) return;
const loadCollaborateurs = async () => {
console.log('📥 Chargement des collaborateurs...');
setIsLoading(true);
try {
// ✅ Charger TOUS les employés
const response = await fetch('/api/employes', {
headers: {
'Authorization': `Bearer ${token}`
}
});
console.log('📊 Réponse API employes:', response.status);
if (!response.ok) {
const errorText = await response.text();
console.error('❌ Erreur API:', errorText);
throw new Error(`Erreur ${response.status}: ${errorText}`);
}
const data = await response.json();
console.log('📋 Collaborateurs reçus:', data.length);
// ✅ Filtrer les forfaits jour actifs
let forfaitJour = data.filter((emp: any) => {
const isForfaitJour = emp.TypeContrat === 'forfait_jour';
const isActif = emp.Actif === 1 || emp.Actif === true || emp.Actif === null;
return isForfaitJour && isActif;
});
// 🆕 APPLIQUER LE FILTRE SERVICE CÔTÉ CLIENT
if (selectedService && selectedService !== 'all') {
forfaitJour = forfaitJour.filter((emp: any) =>
emp.ServiceId?.toString() === selectedService.toString() ||
emp.serviceId?.toString() === selectedService.toString()
);
console.log(`🔍 Filtré par service ${selectedService}: ${forfaitJour.length} collaborateurs`);
}
console.log('✅ Forfaits jour actifs:', forfaitJour.length);
setCollaborateurs(forfaitJour);
} catch (error: any) {
console.error('❌ Erreur chargement collaborateurs:', error);
toast.error('Erreur lors du chargement des collaborateurs: ' + error.message);
setCollaborateurs([]);
} finally {
setIsLoading(false);
}
};
loadCollaborateurs();
}, [token, selectedService]); // 🆕 Recharger quand le service change
// ✅ ÉTAPE 3 : Charger les stats globales
useEffect(() => {
if (!token || collaborateurs.length === 0) return;
const loadStatsGlobales = async () => {
console.log('📊 Chargement stats globales...');
try {
let url = `/api/compte-rendu/stats?annee=${annee}&mois=${mois}`;
if (selectedService && selectedService !== 'all') {
url += `&service_id=${selectedService}`;
}
const response = await fetch(url, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (response.ok) {
const data = await response.json();
console.log('✅ Stats globales:', data);
setStatsGlobales(data);
} else {
console.warn('⚠️ Stats non disponibles, utilisation valeurs par défaut');
setStatsGlobales({
totalCollaborateurs: collaborateurs.length,
mensuelsValides: 0,
mensuelsEnAttente: collaborateurs.length
});
}
} catch (error) {
console.error('❌ Erreur stats globales:', error);
setStatsGlobales({
totalCollaborateurs: collaborateurs.length,
mensuelsValides: 0,
mensuelsEnAttente: collaborateurs.length
});
}
};
loadStatsGlobales();
}, [token, collaborateurs.length, annee, mois, selectedService]);
// ✅ ÉTAPE 4 : Charger le compte-rendu quand on sélectionne un collaborateur
useEffect(() => {
if (!selectedCollab || !token) return;
const loadCompteRendu = async () => {
setIsLoading(true);
console.log(`📥 Chargement compte-rendu pour collaborateur ${selectedCollab.id}`);
try {
const response = await fetch(
`/api/compte-rendu-activites?user_id=${selectedCollab.id}&annee=${annee}&mois=${mois}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
if (!response.ok) {
throw new Error(`Erreur ${response.status}`);
}
const data = await response.json();
if (data.success) {
setCompteRenduData(data.jours || []);
setMensuelData(data.mensuel);
console.log('✅ Compte-rendu chargé:', data.jours?.length, 'jours');
} else {
toast.error(data.message || 'Erreur lors du chargement');
}
} catch (error: any) {
console.error('❌ Erreur chargement compte-rendu:', error);
toast.error('Erreur lors du chargement du compte-rendu');
setCompteRenduData([]);
setMensuelData(null);
} finally {
setIsLoading(false);
}
};
loadCompteRendu();
}, [selectedCollab, annee, mois, token]);
// Verrouiller/Déverrouiller un mois
const handleToggleLock = async (unlock = false) => {
if (!selectedCollab || !token) return;
const action = unlock ? 'déverrouiller' : 'verrouiller';
const monthNames = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
if (!confirm(`Confirmer ${action} le mois de ${monthNames[mois - 1]} ${annee} pour ${selectedCollab.nom} ?`)) {
return;
}
try {
const endpoint = unlock
? '/api/compte-rendu/deverrouiller'
: '/api/compte-rendu/verrouiller';
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
collaborateur_id: selectedCollab.id,
annee: annee,
mois: mois
})
});
const data = await response.json();
if (data.success) {
toast.success(`Mois ${action === 'verrouiller' ? 'verrouillé' : 'déverrouillé'} avec succès`);
// Recharger les données
const reloadResponse = await fetch(
`/api/compte-rendu-activites?user_id=${selectedCollab.id}&annee=${annee}&mois=${mois}`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);
const reloadData = await reloadResponse.json();
if (reloadData.success) {
setCompteRenduData(reloadData.jours || []);
setMensuelData(reloadData.mensuel);
}
// Recharger les stats
let statsUrl = `/api/compte-rendu/stats?annee=${annee}&mois=${mois}`;
if (selectedService && selectedService !== 'all') {
statsUrl += `&service_id=${selectedService}`;
}
const statsResponse = await fetch(statsUrl, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (statsResponse.ok) {
const statsData = await statsResponse.json();
setStatsGlobales(statsData);
}
} else {
toast.error(data.message || 'Erreur');
}
} catch (error) {
console.error(`Erreur ${action}:`, error);
toast.error('Erreur serveur');
}
};
// Export PDF
const handleExportPDF = async () => {
if (!selectedCollab || !token) return;
try {
const response = await fetch(
`/api/export-compte-rendu-pdf?user_id=${selectedCollab.id}&annee=${annee}&mois=${mois}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
if (!response.ok) {
throw new Error('Erreur lors de l\'export PDF');
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
const data = await response.json();
console.log('📄 Données PDF:', data);
toast.info(data.message || 'Export PDF en cours de développement');
const dataWindow = window.open('', '_blank');
if (dataWindow) {
dataWindow.document.write(`
<html>
<head>
<title>Compte-rendu - ${selectedCollab.prenom} ${selectedCollab.nom}</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
h1 { color: #0ea5e9; }
.info { background: #f0f9ff; padding: 15px; border-radius: 8px; margin: 10px 0; }
.label { font-weight: bold; }
</style>
</head>
<body>
<h1>Compte-rendu d'activités</h1>
<div class="info">
<p><span class="label">Collaborateur:</span> ${data.data?.collaborateur?.prenom} ${data.data?.collaborateur?.nom}</p>
<p><span class="label">Email:</span> ${data.data?.collaborateur?.email}</p>
<p><span class="label">Service:</span> ${data.data?.collaborateur?.service || 'Non défini'}</p>
<p><span class="label">Période:</span> ${data.data?.periode}</p>
</div>
<h2>Résumé</h2>
<div class="info">
<p><span class="label">Jours travaillés:</span> ${data.data?.mensuel?.NbJoursTravailles || 0}</p>
<p><span class="label">Non-respect repos quotidien:</span> ${data.data?.mensuel?.NbJoursNonRespectsReposQuotidien || 0}</p>
<p><span class="label">Non-respect repos hebdo:</span> ${data.data?.mensuel?.NbJoursNonRespectsReposHebdo || 0}</p>
<p><span class="label">Statut:</span> ${data.data?.mensuel?.Verrouille ? '🔒 Verrouillé' : '🔓 Ouvert'}</p>
</div>
<p style="color: #6b7280; font-size: 12px; margin-top: 20px;">
Export généré le ${data.data?.dateExport}<br>
Note: La génération PDF sera implémentée prochainement
</p>
</body>
</html>
`);
}
} else {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `compte-rendu-${selectedCollab.nom}-${annee}-${String(mois).padStart(2, '0')}.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
toast.success('PDF téléchargé avec succès');
}
} catch (error: any) {
console.error('❌ Erreur export PDF:', error);
toast.error('Erreur lors de l\'export PDF: ' + error.message);
}
};
// Navigation mois
const navigateMonth = (direction: string) => {
setCurrentDate(prev => {
const newDate = new Date(prev);
if (direction === 'prev') {
newDate.setMonth(prev.getMonth() - 1);
} else {
newDate.setMonth(prev.getMonth() + 1);
}
return newDate;
});
};
// Ouvrir le détail d'un jour
const handleViewDetail = (jourData: any) => {
setSelectedJour(jourData);
setShowDetailModal(true);
};
// 🆕 Gérer le changement de service
const handleServiceChange = (serviceId: string) => {
setSelectedService(serviceId);
setSelectedCollab(null); // Réinitialiser le collaborateur sélectionné
};
// Filtrer les collaborateurs par recherche
const filteredCollaborateurs = collaborateurs.filter(collab =>
collab.nom?.toLowerCase().includes(searchTerm.toLowerCase()) ||
collab.prenom?.toLowerCase().includes(searchTerm.toLowerCase()) ||
collab.email?.toLowerCase().includes(searchTerm.toLowerCase())
);
const monthNames = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
return new Date(dateStr).toLocaleDateString('fr-FR');
};
const isVerrouille = () => {
return mensuelData?.Verrouille === true || mensuelData?.Verrouille === 1;
};
// ✅ Affichage du loader pendant le chargement initial
if (isLoading && collaborateurs.length === 0) {
return (
<div className="min-h-screen bg-gradient-subtle flex items-center justify-center">
<div className="text-center">
<RefreshCw className="w-12 h-12 animate-spin text-cyan-600 mx-auto mb-4" />
<p className="text-gray-600">Chargement des collaborateurs...</p>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-gradient-subtle">
{/* Header */}
<header className="bg-card border-b border-border shadow-card">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<Button
variant="ghost"
size="sm"
onClick={() => navigate('/dashboard')}
>
<ArrowLeft className="w-4 h-4 mr-2" />
Retour
</Button>
<div>
<h1 className="text-2xl font-bold text-foreground">
Gestion des Comptes-Rendus d'Activités
</h1>
<p className="text-sm text-muted-foreground">
Suivi et validation des forfaits jour
</p>
</div>
</div>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* 🆕 FILTRE PAR SERVICE */}
<Card className="shadow-card mb-6">
<CardContent className="p-4">
<div className="flex items-center gap-4">
<Filter className="w-5 h-5 text-gray-600" />
<div className="flex-1">
<label className="text-sm font-medium text-gray-700 mb-2 block">
Filtrer par service
</label>
<select
value={selectedService}
onChange={(e) => handleServiceChange(e.target.value)}
className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
>
<option value="all">Tous les services</option>
{services.map(service => (
<option key={service.Id} value={service.Id.toString()}>
{service.Nom} ({service.nombreForfaitJour} forfait{service.nombreForfaitJour > 1 ? 's' : ''} jour)
</option>
))}
</select>
</div>
</div>
</CardContent>
</Card>
{/* Stats globales */}
{statsGlobales && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<Card className="shadow-card">
<CardContent className="p-6">
<div className="flex items-center gap-3">
<div className="p-3 bg-blue-100 rounded-lg">
<Users className="w-6 h-6 text-blue-600" />
</div>
<div>
<p className="text-sm text-muted-foreground">
Forfaits jour
{selectedService !== 'all' && (
<span className="ml-1 text-xs text-blue-600">(filtré)</span>
)}
</p>
<p className="text-2xl font-bold text-foreground">
{statsGlobales.totalCollaborateurs}
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="shadow-card">
<CardContent className="p-6">
<div className="flex items-center gap-3">
<div className="p-3 bg-green-100 rounded-lg">
<CheckCircle className="w-6 h-6 text-green-600" />
</div>
<div>
<p className="text-sm text-muted-foreground">Mois validés</p>
<p className="text-2xl font-bold text-foreground">
{statsGlobales.mensuelsValides}
</p>
</div>
</div>
</CardContent>
</Card>
<Card className="shadow-card">
<CardContent className="p-6">
<div className="flex items-center gap-3">
<div className="p-3 bg-orange-100 rounded-lg">
<Clock className="w-6 h-6 text-orange-600" />
</div>
<div>
<p className="text-sm text-muted-foreground">En attente</p>
<p className="text-2xl font-bold text-foreground">
{statsGlobales.mensuelsEnAttente}
</p>
</div>
</div>
</CardContent>
</Card>
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Liste des collaborateurs */}
<div className="lg:col-span-1">
<Card className="shadow-card">
<CardHeader className="bg-gradient-to-r from-cyan-500 to-blue-500 text-white">
<h2 className="text-lg font-bold flex items-center gap-2">
<Users className="w-5 h-5" />
Collaborateurs ({collaborateurs.length})
{selectedService !== 'all' && (
<span className="text-xs bg-white/20 px-2 py-1 rounded">
Filtré
</span>
)}
</h2>
</CardHeader>
{/* Recherche */}
<CardContent className="p-4 border-b">
<div className="relative">
<Search className="w-5 h-5 absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
<input
type="text"
placeholder="Rechercher..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500"
/>
</div>
</CardContent>
{/* Liste */}
<div className="overflow-y-auto max-h-[600px]">
{filteredCollaborateurs.length === 0 ? (
<div className="p-4 text-center text-muted-foreground">
{searchTerm ? 'Aucun résultat' : 'Aucun collaborateur en forfait jour'}
</div>
) : (
filteredCollaborateurs.map(collab => (
<div
key={collab.id}
onClick={() => setSelectedCollab(collab)}
className={`
p-4 border-b cursor-pointer transition-all
hover:bg-gray-50
${selectedCollab?.id === collab.id ? 'bg-blue-50 border-l-4 border-blue-500' : ''}
`}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<p className="font-semibold text-foreground">
{collab.prenom} {collab.nom}
</p>
<p className="text-sm text-muted-foreground">
{collab.email}
</p>
<p className="text-xs text-muted-foreground mt-1">
{collab.service || 'Service non défini'}
</p>
</div>
</div>
</div>
))
)}
</div>
</Card>
</div>
{/* Détails du compte-rendu */}
<div className="lg:col-span-2">
{!selectedCollab ? (
<Card className="shadow-card p-8 text-center">
<Users className="w-16 h-16 text-gray-300 mx-auto mb-4" />
<p className="text-muted-foreground">
Sélectionnez un collaborateur pour voir ses comptes-rendus
</p>
</Card>
) : (
<div className="space-y-6">
{/* En-tête collaborateur */}
<Card className="shadow-card">
<CardContent className="p-6">
<div className="flex items-center justify-between mb-4">
<div>
<h2 className="text-xl font-bold text-foreground">
{selectedCollab.prenom} {selectedCollab.nom}
</h2>
<p className="text-sm text-muted-foreground">
{selectedCollab.email}
</p>
</div>
<div className="flex items-center gap-2">
{isVerrouille() ? (
<span className="flex items-center gap-2 px-3 py-1.5 bg-red-100 text-red-700 rounded-lg text-sm font-medium">
<Lock className="w-4 h-4" />
Verrouillé
</span>
) : (
<span className="flex items-center gap-2 px-3 py-1.5 bg-green-100 text-green-700 rounded-lg text-sm font-medium">
<Unlock className="w-4 h-4" />
Ouvert
</span>
)}
</div>
</div>
{/* Navigation mois */}
<div className="flex items-center justify-between mb-4">
<button
onClick={() => navigateMonth('prev')}
className="p-2 hover:bg-gray-100 rounded"
>
<ChevronLeft className="w-5 h-5 text-blue-600" />
</button>
<h3 className="text-lg font-semibold">
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
</h3>
<button
onClick={() => navigateMonth('next')}
className="p-2 hover:bg-gray-100 rounded"
>
<ChevronRight className="w-5 h-5 text-blue-600" />
</button>
</div>
{/* Actions */}
<div className="flex gap-3">
{isVerrouille() ? (
<Button
onClick={() => handleToggleLock(true)}
className="flex-1 bg-orange-600 hover:bg-orange-700"
title="Permettre au collaborateur de modifier ses saisies"
>
<Unlock className="w-4 h-4 mr-2" />
Déverrouiller pour modification
</Button>
) : (
<Button
onClick={() => handleToggleLock(false)}
className="flex-1 bg-green-600 hover:bg-green-700"
title="Verrouiller le mois"
>
<Lock className="w-4 h-4 mr-2" />
Verrouiller
</Button>
)}
<Button
onClick={handleExportPDF}
className="flex-1 bg-purple-600 hover:bg-purple-700"
>
<Download className="w-4 h-4 mr-2" />
Export PDF
</Button>
</div>
</CardContent>
</Card>
{/* Stats du mois */}
{mensuelData && (
<Card className="shadow-card">
<CardContent className="p-6">
<h3 className="text-lg font-bold text-foreground mb-4">
Statistiques du mois
</h3>
<div className="grid grid-cols-3 gap-4">
<div className="text-center">
<p className="text-2xl font-bold text-blue-600">
{mensuelData.NbJoursTravailles || 0}
</p>
<p className="text-sm text-muted-foreground">Jours travaillés</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-orange-600">
{mensuelData.NbJoursNonRespectsReposQuotidien || 0}
</p>
<p className="text-sm text-muted-foreground">Non-respect repos quotidien</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-red-600">
{mensuelData.NbJoursNonRespectsReposHebdo || 0}
</p>
<p className="text-sm text-muted-foreground">Non-respect repos hebdo</p>
</div>
</div>
</CardContent>
</Card>
)}
{/* Liste des jours */}
<Card className="shadow-card">
<CardHeader className="bg-gray-50 border-b">
<h3 className="font-bold text-foreground">
Jours saisis ({compteRenduData.length})
</h3>
</CardHeader>
<div className="divide-y max-h-[500px] overflow-y-auto">
{compteRenduData.length === 0 ? (
<div className="p-8 text-center text-muted-foreground">
<FileText className="w-12 h-12 mx-auto mb-3 opacity-30" />
<p>Aucune saisie pour ce mois</p>
</div>
) : (
compteRenduData.map((jour, index) => (
<div
key={index}
className="p-4 hover:bg-gray-50 transition-all"
>
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="flex items-center gap-3">
<p className="font-semibold text-foreground">
{formatDate(jour.JourDate)}
</p>
{jour.JourTravaille ? (
<span className="px-2 py-1 bg-green-100 text-green-700 text-xs rounded">
Travaillé
</span>
) : (
<span className="px-2 py-1 bg-gray-100 text-gray-700 text-xs rounded">
Non travaillé
</span>
)}
{!jour.ReposQuotidienRespect && (
<span className="px-2 py-1 bg-orange-100 text-orange-700 text-xs rounded">
⚠️ Repos quotidien
</span>
)}
{!jour.ReposHebdomadaireRespect && (
<span className="px-2 py-1 bg-red-100 text-red-700 text-xs rounded">
⚠️ Repos hebdo
</span>
)}
</div>
{jour.CommentaireRepos && (
<p className="text-sm text-muted-foreground mt-2">
💬 {jour.CommentaireRepos}
</p>
)}
</div>
<Button
variant="ghost"
size="sm"
onClick={() => handleViewDetail(jour)}
>
<Eye className="w-4 h-4" />
</Button>
</div>
</div>
))
)}
</div>
</Card>
</div>
)}
</div>
</div>
</main>
{/* Modal détail jour */}
{showDetailModal && selectedJour && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<Card className="w-full max-w-md">
<CardHeader className="bg-gradient-to-r from-cyan-500 to-blue-500 text-white">
<div className="flex items-center justify-between">
<h3 className="font-bold">
Détail du {formatDate(selectedJour.JourDate)}
</h3>
<button
onClick={() => setShowDetailModal(false)}
className="text-white hover:bg-white/20 p-1 rounded"
>
<XCircle className="w-5 h-5" />
</button>
</div>
</CardHeader>
<CardContent className="p-6 space-y-4">
<div>
<p className="text-sm text-muted-foreground">Statut</p>
<p className="font-semibold">
{selectedJour.JourTravaille ? ' Jour travaillé' : ' Jour non travaillé'}
</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Repos quotidien respecté</p>
<p className="font-semibold">
{selectedJour.ReposQuotidienRespect ? ' Oui' : ' Non'}
</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Repos hebdomadaire respecté</p>
<p className="font-semibold">
{selectedJour.ReposHebdomadaireRespect ? ' Oui' : ' Non'}
</p>
</div>
{selectedJour.CommentaireRepos && (
<div>
<p className="text-sm text-muted-foreground">Commentaire</p>
<p className="text-sm bg-gray-50 p-3 rounded">
{selectedJour.CommentaireRepos}
</p>
</div>
)}
<div>
<p className="text-sm text-muted-foreground">Date de saisie</p>
<p className="text-sm">
{selectedJour.DateSaisie ? formatDate(selectedJour.DateSaisie) : 'Non disponible'}
</p>
</div>
{selectedJour.DateModification && (
<div>
<p className="text-sm text-muted-foreground">Dernière modification</p>
<p className="text-sm">
{formatDate(selectedJour.DateModification)}
</p>
</div>
)}
</CardContent>
</Card>
</div>
)}
</div>
);
};
export default CompteRenduRH;