modification concernant l'affichage de type de congés dans la pagedemande+Affichage du service du collabrateur et son rôle+ version mobile de la page demande

This commit is contained in:
2025-08-11 17:19:49 +02:00
parent f5ee031efc
commit 871f166457
8 changed files with 558 additions and 764 deletions

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Link, useLocation } from 'react-router-dom';
import { LogOut, Calendar, Home, FileText, Building2, Menu, X, Users } from 'lucide-react';
import { LogOut, Calendar, Home, FileText, Building2, X, Users } from 'lucide-react';
import { useAuth } from '../context/AuthContext';
const Sidebar = ({ isOpen, onToggle }) => {
@@ -9,9 +9,19 @@ const Sidebar = ({ isOpen, onToggle }) => {
const isActive = (path) => location.pathname === path;
const getRoleBadgeClass = (role) => {
switch (role) {
case 'Admin':
return 'bg-red-100 text-red-800';
case 'Manager':
return 'bg-green-100 text-green-800';
default:
return 'bg-blue-100 text-blue-800';
}
};
return (
<>
{/* Mobile overlay */}
{isOpen && (
<div
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
@@ -19,22 +29,18 @@ const Sidebar = ({ isOpen, onToggle }) => {
/>
)}
{/* Sidebar */}
<div className={`
fixed inset-y-0 left-0 z-50 w-60 bg-white border-r border-gray-200 min-h-screen flex flex-col transform transition-transform duration-300 ease-in-out
${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
`}>
{/* Mobile close button */}
fixed inset-y-0 left-0 z-50 w-60 bg-white border-r border-gray-200 min-h-screen flex flex-col transform transition-transform duration-300 ease-in-out
${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
`}>
{/* Bouton fermer (mobile) */}
<div className="lg:hidden flex justify-end p-4">
<button
onClick={onToggle}
className="p-2 rounded-lg hover:bg-gray-100"
>
<button onClick={onToggle} className="p-2 rounded-lg hover:bg-gray-100">
<X className="w-6 h-6" />
</button>
</div>
{/* Logo Section */}
{/* Logo */}
<div className="p-6 border-b border-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
@@ -47,7 +53,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
</div>
</div>
{/* User Info */}
{/* Infos utilisateur */}
<div className="p-4 lg:p-6 border-b border-gray-100">
<div className="flex flex-col items-center text-center">
<img
@@ -56,73 +62,69 @@ const Sidebar = ({ isOpen, onToggle }) => {
className="w-12 h-12 lg:w-16 lg:h-16 rounded-full object-cover mb-3"
/>
<div>
<p className="font-semibold text-gray-900 text-sm lg:text-base">{user?.name || "Utilisateur"}</p>
<p className="text-xs lg:text-sm text-gray-500">{user?.department || "Service"}</p>
<span className="inline-block mt-2 px-3 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full">
Employé
</span>
<p className="font-semibold text-gray-900 text-sm lg:text-base">
{user?.name || "Utilisateur"}
</p>
<p className="text-xs lg:text-sm text-gray-500">
{user?.service || "Service non défini"}
</p>
{user?.role && (
<span className={`inline-block mt-2 px-3 py-1 text-xs font-medium rounded-full ${getRoleBadgeClass(user.role)}`}>
{user.role}
</span>
)}
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 p-4">
<div className="space-y-2">
<nav className="flex-1 p-4 space-y-2">
<Link
to="/dashboard"
onClick={() => window.innerWidth < 1024 && onToggle()}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/dashboard") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
}`}
>
<Home className="w-5 h-5" />
<span className="font-medium">Dashboard</span>
</Link>
<Link
to="/demandes"
onClick={() => window.innerWidth < 1024 && onToggle()}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/demandes") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
}`}
>
<FileText className="w-5 h-5" />
<span className="font-medium">Demandes</span>
</Link>
<Link
to="/calendrier"
onClick={() => window.innerWidth < 1024 && onToggle()}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/calendrier") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
}`}
>
<Calendar className="w-5 h-5" />
<span className="font-medium">Calendrier</span>
</Link>
{(user?.role === 'Manager' || user?.role === 'Admin' || user?.role === 'Employe') && (
<Link
to="/dashboard"
to="/manager"
onClick={() => window.innerWidth < 1024 && onToggle()}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/dashboard")
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
: "text-gray-700 hover:bg-gray-50"
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/manager") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
}`}
>
<Home className="w-5 h-5" />
<span className="font-medium">Dashboard</span>
<Users className="w-5 h-5" />
<span className="font-medium">
{user?.role === 'Employe' ? 'Mon équipe' : 'Équipe'}
</span>
</Link>
<Link
to="/demandes"
onClick={() => window.innerWidth < 1024 && onToggle()}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/demandes")
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
: "text-gray-700 hover:bg-gray-50"
}`}
>
<FileText className="w-5 h-5" />
<span className="font-medium">Demandes</span>
</Link>
<Link
to="/calendrier"
onClick={() => window.innerWidth < 1024 && onToggle()}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/calendrier")
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
: "text-gray-700 hover:bg-gray-50"
}`}
>
<Calendar className="w-5 h-5" />
<span className="font-medium">Calendrier</span>
</Link>
{(user?.role === 'Manager' || user?.role === 'Admin' || user?.role === 'Employe') && (
<Link
to="/manager"
onClick={() => window.innerWidth < 1024 && onToggle()}
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/manager")
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
: "text-gray-700 hover:bg-gray-50"
}`}
>
<Users className="w-5 h-5" />
<span className="font-medium">
{user?.role === 'Employe' ? 'Mon équipe' : 'Équipe'}
</span>
</Link>
)}
</div>
)}
</nav>
{/* Logout Button */}
{/* Bouton déconnexion */}
<div className="p-4 border-t border-gray-100">
<button
onClick={logout}
@@ -137,4 +139,4 @@ const Sidebar = ({ isOpen, onToggle }) => {
);
};
export default Sidebar;
export default Sidebar;

View File

@@ -15,13 +15,12 @@ export const AuthProvider = ({ children }) => {
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Vérifier si l'utilisateur est déjà connecté
const savedUser = localStorage.getItem('user');
if (savedUser) {
try {
setUser(JSON.parse(savedUser));
} catch (error) {
console.error('Erreur lors du parsing de l\'utilisateur sauvegardé:', error);
console.error("Erreur parsing utilisateur sauvegardé:", error);
localStorage.removeItem('user');
}
}
@@ -30,7 +29,6 @@ export const AuthProvider = ({ children }) => {
const login = async (email, password) => {
try {
// Tester plusieurs URLs possibles selon la configuration locale
const possibleUrls = [
'http://localhost/GTA/project/public/login.php',
'http://localhost:80/GTA/project/public/login.php',
@@ -39,29 +37,16 @@ export const AuthProvider = ({ children }) => {
];
let response = null;
let lastError = null;
for (const url of possibleUrls) {
try {
console.log(' Test URL:', url);
console.log("Test URL:", url);
response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
mot_de_passe: password
}),
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, mot_de_passe: password }),
});
if (response.ok) {
console.log(' URL qui fonctionne:', url);
break;
}
} catch (error) {
lastError = error;
console.log(' URL échouée:', url, error.message);
if (response.ok) break;
} catch {
continue;
}
}
@@ -71,35 +56,34 @@ export const AuthProvider = ({ children }) => {
}
const text = await response.text();
console.log(' Réponse brute:', text);
// Vérifier si la réponse est du JSON valide
let data;
try {
data = JSON.parse(text);
} catch (parseError) {
console.error(' Réponse non-JSON:', text.substring(0, 200));
throw new Error('Le serveur PHP ne répond pas correctement. Vérifiez que PHP est démarré.');
} catch {
console.error("Réponse non-JSON:", text.substring(0, 200));
throw new Error("Le serveur PHP ne répond pas correctement.");
}
if (data.success) {
const userData = {
id: data.user.id,
name: data.user.prenom + ' ' + data.user.nom,
name: `${data.user.prenom} ${data.user.nom}`,
prenom: data.user.prenom,
nom: data.user.nom,
email: data.user.email,
role: data.user.role || 'Employe'
role: data.user.role || 'Employe',
service: data.user.service || 'Non défini'
};
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
return true;
} else {
console.error(' Échec connexion:', data.message);
console.error("Échec connexion:", data.message);
return false;
}
} catch (error) {
console.error('Erreur de connexion:', error);
console.error("Erreur de connexion:", error);
return false;
}
};
@@ -109,12 +93,7 @@ export const AuthProvider = ({ children }) => {
localStorage.removeItem('user');
};
const value = {
user,
login,
logout,
isLoading
};
const value = { user, login, logout, isLoading };
return (
<AuthContext.Provider value={value}>
@@ -123,4 +102,4 @@ export const AuthProvider = ({ children }) => {
);
};
export default AuthContext;
export default AuthContext;

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useAuth } from '../context/AuthContext';
import Sidebar from '../components/Sidebar';
import { Calendar as CalendarIcon, Clock, Plus, Settings, RefreshCw, Search, Filter, Eye, Edit, Trash2, Menu, X } from 'lucide-react';
import { Plus, Search, Filter, Eye, Menu, X } from 'lucide-react';
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
const Requests = () => {
@@ -17,12 +17,11 @@ const Requests = () => {
});
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
const [showAdminPanel, setShowAdminPanel] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [allRequests, setAllRequests] = useState([]);
const [filteredRequests, setFilteredRequests] = useState([]);
const [selectedRequest, setSelectedRequest] = useState(null); // 👈 Nouveau
const [selectedRequest, setSelectedRequest] = useState(null);
const [searchTerm, setSearchTerm] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
@@ -31,6 +30,8 @@ const Requests = () => {
const [currentPage, setCurrentPage] = useState(1);
const [requestsPerPage] = useState(10);
const [showFilters, setShowFilters] = useState(false);
useEffect(() => {
if (user?.id) {
fetchLeaveCounters();
@@ -70,14 +71,12 @@ const Requests = () => {
try {
const response = await fetch(`http://localhost/GTA/project/public/getLeaveCounters.php?user_id=${user.id}`);
const text = await response.text();
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error('Le serveur PHP ne répond pas correctement');
}
if (data.success) {
setLeaveCounters(data.counters);
} else {
@@ -93,8 +92,12 @@ const Requests = () => {
const url = `http://localhost/GTA/project/public/getRequests.php?user_id=${user.id}`;
const response = await fetch(url);
const text = await response.text();
const data = JSON.parse(text);
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error('Le serveur PHP ne répond pas correctement');
}
if (data.success) {
setAllRequests(data.requests || []);
} else {
@@ -107,33 +110,6 @@ const Requests = () => {
}
};
const handleResetCounters = async () => {
if (!confirm('Réinitialiser les compteurs ? Cette action est irréversible.')) return;
try {
const response = await fetch('http://localhost/GTA/project/public/resetLeaveCounters.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ manual_reset: true }),
});
const data = await response.json();
if (data.success) {
alert('Réinitialisation réussie.');
fetchLeaveCounters();
} else {
alert(`Erreur : ${data.message}`);
}
} catch {
alert('Erreur de connexion au serveur');
}
};
const openManualResetPage = () => {
window.open('http://localhost/GTA/project/public/manualResetCounters.php', '_blank');
};
const getStatusColor = (status) => {
switch (status) {
case 'Approuvé':
@@ -157,7 +133,6 @@ const Requests = () => {
const indexOfFirstRequest = indexOfLastRequest - requestsPerPage;
const currentRequests = filteredRequests.slice(indexOfFirstRequest, indexOfLastRequest);
const totalPages = Math.ceil(filteredRequests.length / requestsPerPage);
const paginate = (pageNumber) => setCurrentPage(pageNumber);
if (isLoading) {
return (
@@ -178,33 +153,37 @@ const Requests = () => {
<Sidebar isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
<div className="flex-1 lg:ml-60 p-4 lg:p-8">
{/* Bouton mobile */}
<div className="lg:hidden mb-4">
{/* Mobile top bar */}
<div className="lg:hidden flex justify-between items-center mb-4">
<button onClick={() => setSidebarOpen(true)} className="p-2 rounded-lg bg-white shadow-sm border border-gray-200">
<Menu className="w-6 h-6" />
</button>
<div className="flex items-center gap-2">
<button onClick={() => setShowFilters(!showFilters)} className="p-2 rounded-lg bg-white shadow-sm border border-gray-200 flex items-center gap-2">
<Filter className="w-4 h-4" /> <span className="text-sm">Filtres</span>
</button>
<button onClick={() => setShowNewRequestModal(true)} className="p-2 rounded-lg bg-blue-600 text-white">
<Plus className="w-4 h-4" />
</button>
</div>
</div>
{/* Header */}
<div className="flex justify-between items-center mb-8">
<div className="flex justify-between items-center mb-4 lg:mb-8">
<div>
<h1 className="text-2xl lg:text-3xl font-bold text-gray-900 mb-2">
Mes Demandes de Congés
</h1>
<p className="text-sm lg:text-base text-gray-600">Gérez toutes vos demandes de congés</p>
<h1 className="text-2xl lg:text-3xl font-bold text-gray-900 mb-1">Mes Demandes de Congés</h1>
<p className="text-sm text-gray-600">Gérez toutes vos demandes de congés</p>
</div>
<div className="hidden lg:flex items-center gap-3">
<button onClick={() => setShowNewRequestModal(true)} className="bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center gap-2">
<Plus className="w-4 h-4" /> Nouvelle demande
</button>
</div>
<button
onClick={() => setShowNewRequestModal(true)}
className="bg-blue-600 text-white px-3 lg:px-6 py-2 lg:py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors flex items-center gap-2"
>
<Plus className="w-5 h-5" />
<span className="hidden sm:inline">Nouvelle demande</span>
<span className="sm:hidden">Nouveau</span>
</button>
</div>
{/* Filtres */}
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 lg:p-6 mb-6">
{/* Filters panel (mobile toggle + desktop always visible) */}
<div className={`${showFilters ? 'block' : 'hidden'} lg:block bg-white rounded-xl shadow-sm border border-gray-100 p-4 lg:p-6 mb-6`}>
<div className="flex flex-col lg:flex-row gap-4">
<div className="flex-1 relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
@@ -217,30 +196,20 @@ const Requests = () => {
/>
</div>
<div className="flex gap-3">
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-lg text-sm"
>
<option value="all">Tous les statuts</option>
<option value="En attente">En attente</option>
<option value="Validée">Validée</option>
<option value="Refusée">Refusée</option>
</select>
<select value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)} className="px-3 py-2 border border-gray-300 rounded-lg text-sm">
<option value="all">Tous les statuts</option>
<option value="En attente">En attente</option>
<option value="Validée">Validée</option>
<option value="Refusée">Refusée</option>
</select>
<select
value={typeFilter}
onChange={(e) => setTypeFilter(e.target.value)}
className="px-3 py-2 border border-gray-300 rounded-lg text-sm"
>
<option value="all">Tous les types</option>
<option value="Congés payés">Congés payés</option>
<option value="RTT">RTT</option>
<option value="Congé maladie">Congé maladie</option>
<option value="Autres">Autres types</option>
</select>
</div>
<select value={typeFilter} onChange={(e) => setTypeFilter(e.target.value)} className="px-3 py-2 border border-gray-300 rounded-lg text-sm">
<option value="all">Tous les types</option>
<option value="Congés payés">Congés payés</option>
<option value="RTT">RTT</option>
<option value="Congé maladie">Congé maladie</option>
<option value="Autres">Autres types</option>
</select>
</div>
<div className="mt-4 text-sm text-gray-600">
@@ -249,17 +218,15 @@ const Requests = () => {
</div>
</div>
{/* Tableau + Détails */}
{/* Main content: left = list/table, right = details (desktop) */}
<div className="flex flex-col lg:flex-row gap-6">
{/* Tableau des demandes */}
{/* Left: table (desktop) + cards (mobile) */}
<div className="flex-1 bg-white rounded-xl shadow-sm border border-gray-100">
<div className="p-6 border-b border-gray-100">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">Toutes mes demandes</h2>
<div className="flex items-center gap-2 text-sm text-gray-500">
<Filter className="w-4 h-4" />
Page {currentPage} sur {totalPages || 1}
</div>
<div className="p-6 border-b border-gray-100 flex items-center justify-between">
<h2 className="text-xl font-semibold text-gray-900">Toutes mes demandes</h2>
<div className="flex items-center gap-2 text-sm text-gray-500">
<Filter className="w-4 h-4" />
<span>Page {currentPage} / {totalPages || 1}</span>
</div>
</div>
@@ -267,102 +234,193 @@ const Requests = () => {
{currentRequests.length === 0 ? (
<div className="text-center py-8 text-gray-600">Aucune demande à afficher.</div>
) : (
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-3 px-4 text-gray-700">Type</th>
<th className="text-left py-3 px-4 text-gray-700">Dates</th>
<th className="text-left py-3 px-4 text-gray-700">Jours</th>
<th className="text-left py-3 px-4 text-gray-700">Statut</th>
<th className="text-left py-3 px-4 text-gray-700">Soumis</th>
<th className="text-left py-3 px-4 text-gray-700">Actions</th>
</tr>
</thead>
<tbody>
{currentRequests.map(request => (
<tr key={request.id} className="border-b hover:bg-gray-50">
<td className="py-4 px-4">{request.type}</td>
<td className="py-4 px-4">{request.dateDisplay}</td>
<td className="py-4 px-4">{request.days}</td>
<td className="py-4 px-4">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(request.status)}`}>
<>
{/* Desktop table */}
<div className="hidden lg:block overflow-x-auto">
<table className="w-full">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-3 px-4 text-gray-700">Type</th>
<th className="text-left py-3 px-4 text-gray-700">Dates</th>
<th className="text-left py-3 px-4 text-gray-700">Jours</th>
<th className="text-left py-3 px-4 text-gray-700">Statut</th>
<th className="text-left py-3 px-4 text-gray-700">Soumis</th>
<th className="text-left py-3 px-4 text-gray-700">Actions</th>
</tr>
</thead>
<tbody>
{currentRequests.map(request => (
<tr key={request.id} className="border-b hover:bg-gray-50">
<td className="py-4 px-4">
{request.type.split(',').map((t, i) => (
<span key={i} className="inline-block bg-gray-100 text-gray-800 text-[11px] px-2 py-0.5 rounded-full mr-1">
{t.trim()}
</span>
))}
</td>
<td className="py-4 px-4">{request.dateDisplay}</td>
<td className="py-4 px-4">{request.days}</td>
<td className="py-4 px-4">
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(request.status)}`}>
{request.status}
</span>
</td>
<td className="py-4 px-4">{request.submittedDisplay}</td>
<td className="py-4 px-4">
<button onClick={() => handleViewRequest(request)} className="text-blue-600 hover:underline text-sm flex items-center gap-1">
<Eye className="w-4 h-4" /> Voir
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Mobile cards */}
<div className="lg:hidden space-y-4">
{currentRequests.map(request => (
<div key={request.id} className="bg-white rounded-lg shadow-sm p-4 border border-gray-200">
<div className="flex justify-between items-start">
<div className="flex-1">
<div className="flex flex-wrap gap-1 mb-2">
{request.type.split(',').map((t, i) => (
<span key={i} className="bg-gray-100 text-gray-800 text-[11px] px-2 py-0.5 rounded-full">
{t.trim()}
</span>
))}
</div>
<p className="text-sm text-gray-600">{request.dateDisplay}</p>
<p className="text-sm font-medium mt-1">{request.days} jour{request.days > 1 ? 's' : ''}</p>
</div>
<div className="ml-3">
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(request.status)}`}>
{request.status}
</span>
</td>
<td className="py-4 px-4">{request.submittedDisplay}</td>
<td className="py-4 px-4">
<button onClick={() => handleViewRequest(request)} className="text-blue-600 hover:underline text-sm flex items-center gap-1">
<Eye className="w-4 h-4" /> Voir
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
<div className="mt-3 flex justify-between items-center text-sm">
<span className="text-gray-500">{request.submittedDisplay}</span>
<button onClick={() => handleViewRequest(request)} className="text-blue-600 flex items-center gap-1">
<Eye className="w-4 h-4" /> Voir
</button>
</div>
</div>
))}
</div>
</>
)}
</div>
</div>
{/* Détails */}
{selectedRequest && (
<div className="w-full lg:max-w-sm bg-white rounded-xl shadow-md border border-gray-100 p-6 h-fit sticky top-20 self-start">
<div className="flex justify-between items-start mb-6">
<h3 className="text-lg font-semibold">Détails de la demande</h3>
<button onClick={handleCloseDetails} className="text-gray-500 hover:text-gray-800 p-2">
<X className="w-4 h-4" />
</button>
</div>
{/* Right: details (desktop) */}
<div className="hidden lg:block w-full lg:max-w-sm">
{selectedRequest ? (
<div className="bg-white rounded-xl shadow-md border border-gray-100 p-6 sticky top-20">
<div className="flex justify-between items-start mb-6">
<h3 className="text-lg font-semibold">Détails de la demande</h3>
<button onClick={handleCloseDetails} className="text-gray-500 hover:text-gray-800 p-2">
<X className="w-4 h-4" />
</button>
</div>
<div className="space-y-4 text-sm text-gray-700">
<div>
<p className="text-gray-500">Type</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.type}</p>
</div>
<div>
<p className="text-gray-500">Dates</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.dateDisplay}</p>
</div>
<div>
<p className="text-gray-500">Nombre de jours</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.days}</p>
</div>
<div>
<p className="text-gray-500">Statut</p>
<span className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(selectedRequest.status)}`}>
{selectedRequest.status}
</span>
</div>
{selectedRequest.reason && (
<div className="space-y-4 text-sm text-gray-700">
<div>
<p className="text-gray-500">Motif</p>
<p className="italic">{selectedRequest.reason}</p>
<p className="text-gray-500">Type</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.type}</p>
</div>
)}
{selectedRequest.fileUrl && (
<div>
<p className="text-gray-500">Arrêt maladie</p>
<a
href={selectedRequest.fileUrl}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline flex items-center gap-2"
>
<Eye className="w-4 h-4" />
Voir le fichier
</a>
<p className="text-gray-500">Dates</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.dateDisplay}</p>
</div>
)}
<div>
<p className="text-gray-500">Nombre de jours</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.days}</p>
</div>
<div>
<p className="text-gray-500">Statut</p>
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(selectedRequest.status)}`}>
{selectedRequest.status}
</span>
</div>
{selectedRequest.reason && (
<div>
<p className="text-gray-500">Motif</p>
<p className="italic">{selectedRequest.reason}</p>
</div>
)}
{selectedRequest.fileUrl && (
<div>
<p className="text-gray-500">Arrêt maladie</p>
<a href={selectedRequest.fileUrl} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline flex items-center gap-2">
<Eye className="w-4 h-4" /> Voir le fichier
</a>
</div>
)}
</div>
</div>
</div>
)}
) : (
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 text-sm text-gray-500">
Sélectionnez une demande pour voir les détails
</div>
)}
</div>
</div>
{/* Modal */}
{/* Mobile details modal */}
{selectedRequest && (
<div className="fixed inset-0 z-50 lg:hidden">
<div className="absolute inset-0 bg-black bg-opacity-40" onClick={handleCloseDetails}></div>
<div className="absolute inset-0 flex items-center justify-center p-4">
<div className="bg-white w-full max-w-md h-full overflow-y-auto rounded-lg p-5">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold">Détails</h3>
<button onClick={handleCloseDetails} className="text-gray-500">
<X className="w-6 h-6" />
</button>
</div>
<div className="space-y-4 text-sm text-gray-700">
<div>
<p className="text-gray-500">Type</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.type}</p>
</div>
<div>
<p className="text-gray-500">Dates</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.dateDisplay}</p>
</div>
<div>
<p className="text-gray-500">Nombre de jours</p>
<p className="text-base font-medium text-gray-900">{selectedRequest.days}</p>
</div>
<div>
<p className="text-gray-500">Statut</p>
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(selectedRequest.status)}`}>
{selectedRequest.status}
</span>
</div>
{selectedRequest.reason && (
<div>
<p className="text-gray-500">Motif</p>
<p className="italic">{selectedRequest.reason}</p>
</div>
)}
{selectedRequest.fileUrl && (
<div>
<p className="text-gray-500">Arrêt maladie</p>
<a href={selectedRequest.fileUrl} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline flex items-center gap-2">
<Eye className="w-4 h-4" /> Voir le fichier
</a>
</div>
)}
</div>
</div>
</div>
</div>
)}
{/* New request modal */}
{showNewRequestModal && (
<NewLeaveRequestModal
onClose={() => setShowNewRequestModal(false)}