Ajoutez des fichiers projet.
This commit is contained in:
408
project/src/pages/Dashboard.jsx
Normal file
408
project/src/pages/Dashboard.jsx
Normal file
@@ -0,0 +1,408 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import Sidebar from '../components/Sidebar';
|
||||
import { Calendar as CalendarIcon, Clock, Users, TrendingUp, Plus, Settings, RefreshCw, Menu, FileText } from 'lucide-react';
|
||||
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
|
||||
|
||||
const Dashboard = () => {
|
||||
const { user } = useAuth();
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const [leaveCounters, setLeaveCounters] = useState({
|
||||
availableCP: 0,
|
||||
availableRTT: 0,
|
||||
availableABS: 0,
|
||||
rttInProcess: 0,
|
||||
absenteism: 0
|
||||
});
|
||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||
const [showAdminPanel, setShowAdminPanel] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [recentRequests, setRecentRequests] = useState([]);
|
||||
const [allRequests, setAllRequests] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.id) {
|
||||
fetchLeaveCounters();
|
||||
fetchAllRequests();
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const fetchLeaveCounters = async () => {
|
||||
try {
|
||||
const url = `http://localhost/GTA/project/public/getLeaveCounters.php?user_id=${user.id}`;
|
||||
console.log(' Dashboard - Récupération des compteurs:', url);
|
||||
console.log(' Dashboard - User ID utilisé:', user.id);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
console.log(' Dashboard - Réponse brute compteurs:', text);
|
||||
console.log(' Dashboard - Longueur de la réponse:', text.length);
|
||||
console.log(' Dashboard - Premiers 500 caractères:', text.substring(0, 500));
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (parseError) {
|
||||
console.error(' Dashboard - Erreur parsing JSON:', parseError);
|
||||
console.error(' Dashboard - Texte qui a causé l\'erreur:', text);
|
||||
throw new Error('Réponse PHP invalide: ' + text.substring(0, 200));
|
||||
}
|
||||
|
||||
console.log(' Dashboard - Compteurs parsés:', data);
|
||||
|
||||
if (data.success) {
|
||||
console.log(' Dashboard - Compteurs récupérés:', data.counters);
|
||||
setLeaveCounters(data.counters);
|
||||
} else {
|
||||
console.error(' Dashboard - Erreur API compteurs:', data.message);
|
||||
console.error(' Dashboard - Données complètes:', data);
|
||||
throw new Error('API Error: ' + (data.message || 'Erreur inconnue'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('💥 Dashboard - Erreur lors de la récupération des compteurs:', error);
|
||||
|
||||
// Fallback avec des données par défaut
|
||||
console.log(' Dashboard - Utilisation des données par défaut');
|
||||
setLeaveCounters({
|
||||
availableCP: 25,
|
||||
availableRTT: 10,
|
||||
availableABS: 0,
|
||||
rttInProcess: 0,
|
||||
absenteism: 0
|
||||
});
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const fetchAllRequests = async () => {
|
||||
console.log(' Dashboard - Début fetchAllRequests pour user:', user?.id);
|
||||
|
||||
try {
|
||||
const url = `http://localhost/GTA/project/public/getRequests.php?user_id=${user.id}`;
|
||||
console.log(' Dashboard - URL appelée:', url);
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
console.log(' Dashboard - Réponse brute:', text);
|
||||
|
||||
const data = JSON.parse(text);
|
||||
console.log(' Dashboard - Données parsées:', data);
|
||||
|
||||
if (data.success) {
|
||||
console.log('Dashboard - Demandes récupérées:', data.requests?.length || 0);
|
||||
setAllRequests(data.requests || []);
|
||||
setRecentRequests(data.requests?.slice(0, 3) || []);
|
||||
} else {
|
||||
throw new Error(data.message || 'Erreur lors de la récupération des demandes');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(' Dashboard - Erreur lors de la récupération des demandes:', error);
|
||||
|
||||
// En cas d'erreur, on garde des tableaux vides
|
||||
setAllRequests([]);
|
||||
setRecentRequests([]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetCounters = async () => {
|
||||
if (!confirm(' ATTENTION !\n\nCette action va réinitialiser TOUS les compteurs de congés selon les règles de gestion :\n\n• Congés Payés : 25 jours (exercice 01/06 au 31/05)\n• RTT : 10 jours pour 2025 (exercice 01/01 au 31/12)\n• Congés Maladie : 0 jours\n\nCette action est IRRÉVERSIBLE !\n\nÊtes-vous sûr de vouloir continuer ?')) {
|
||||
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 !\n\n• ${data.details.employees_updated} employés mis à jour\n• Exercice CP : ${data.details.leave_year}\n• Année RTT : ${data.details.rtt_year}\n• Date : ${data.details.reset_date}`);
|
||||
fetchLeaveCounters();
|
||||
} else {
|
||||
alert(` Erreur lors de la réinitialisation :\n${data.message}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur:', error);
|
||||
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é':
|
||||
case 'Validée': return 'bg-green-100 text-green-800';
|
||||
case 'En attente': return 'bg-yellow-100 text-yellow-800';
|
||||
case 'Refusé': return 'bg-red-100 text-red-800';
|
||||
default: return 'bg-gray-100 text-gray-800';
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Sidebar isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
|
||||
<div className="lg:ml-60 flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||
<p className="text-gray-600">Chargement des données...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex">
|
||||
<Sidebar isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
|
||||
|
||||
<div className="flex-1 lg:ml-60">
|
||||
<div className="p-4 lg:p-8 w-full">
|
||||
{/* Mobile menu button */}
|
||||
<div className="lg:hidden 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>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h1 className="text-2xl lg:text-3xl font-bold text-gray-900 mb-2">
|
||||
Bonjour, {user?.name || user?.prenom || 'Utilisateur'} 👋
|
||||
</h1>
|
||||
<p className="text-sm lg:text-base text-gray-600">
|
||||
Voici un aperçu de vos congés et demandes récentes
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2 lg:gap-3">
|
||||
{(user?.role === 'Admin' || user?.role === 'RH') && (
|
||||
<button
|
||||
onClick={() => setShowAdminPanel(!showAdminPanel)}
|
||||
className="bg-gray-600 text-white px-3 lg:px-4 py-2 lg:py-3 rounded-lg font-medium hover:bg-gray-700 transition-colors flex items-center gap-2"
|
||||
title="Administration"
|
||||
>
|
||||
<Settings className="w-5 h-5" />
|
||||
<span className="hidden lg:inline">Admin</span>
|
||||
</button>
|
||||
)}
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{/* Admin Panel */}
|
||||
{showAdminPanel && (
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||
<Settings className="w-5 h-5" />
|
||||
Administration
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowAdminPanel(false)}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-4">
|
||||
<h3 className="font-medium text-yellow-800 mb-2">⚠️ Zone d'administration</h3>
|
||||
<p className="text-yellow-700 text-sm">
|
||||
Ces actions affectent tous les utilisateurs du système. Utilisez avec précaution.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<button
|
||||
onClick={handleResetCounters}
|
||||
className="flex items-center gap-3 p-4 border border-red-200 rounded-lg hover:bg-red-50 transition-colors text-left"
|
||||
>
|
||||
<div className="w-10 h-10 bg-red-100 rounded-lg flex items-center justify-center">
|
||||
<RefreshCw className="w-5 h-5 text-red-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">Réinitialiser les compteurs</h3>
|
||||
<p className="text-sm text-gray-600">Remet à zéro tous les compteurs selon les règles</p>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={openManualResetPage}
|
||||
className="flex items-center gap-3 p-4 border border-blue-200 rounded-lg hover:bg-blue-50 transition-colors text-left"
|
||||
>
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<Settings className="w-5 h-5 text-blue-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-900">Interface d'administration</h3>
|
||||
<p className="text-sm text-gray-600">Ouvre l'interface complète d'administration</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stats Cards */}
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 lg:gap-6 mb-8">
|
||||
<div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs lg:text-sm font-medium text-gray-600">CP restants</p>
|
||||
<p className="text-xl lg:text-2xl font-bold text-gray-900">{leaveCounters.availableCP}</p>
|
||||
<p className="text-xs text-gray-500">jours</p>
|
||||
</div>
|
||||
<div className="w-8 h-8 lg:w-12 lg:h-12 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||
<CalendarIcon className="w-4 h-4 lg:w-6 lg:h-6 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-xs lg:text-sm font-medium text-gray-600">RTT restants</p>
|
||||
<p className="text-xl lg:text-2xl font-bold text-gray-900">{leaveCounters.availableRTT}</p>
|
||||
<p className="text-xs text-gray-500">jours</p>
|
||||
</div>
|
||||
<div className="w-8 h-8 lg:w-12 lg:h-12 bg-green-100 rounded-lg flex items-center justify-center">
|
||||
<Clock className="w-4 h-4 lg:w-6 lg:h-6 text-green-600" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Requests Section */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* Recent Requests */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className="p-4 lg:p-6 border-b border-gray-100">
|
||||
<h2 className="text-lg lg:text-xl font-semibold text-gray-900">Demandes récentes</h2>
|
||||
</div>
|
||||
<div className="p-4 lg:p-6">
|
||||
{recentRequests.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<CalendarIcon className="w-6 h-6 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-600 mb-4">Aucune demande récente</p>
|
||||
<button
|
||||
onClick={() => setShowNewRequestModal(true)}
|
||||
className="text-blue-600 hover:text-blue-800 text-sm font-medium"
|
||||
>
|
||||
Faire votre première demande
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{recentRequests.map((request) => (
|
||||
<div key={request.id} className="flex items-center justify-between p-3 lg:p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center gap-2 lg:gap-4">
|
||||
<div className="min-w-0">
|
||||
<p className="font-medium text-gray-900 text-sm lg:text-base truncate">{request.type}</p>
|
||||
<p className="text-xs lg:text-sm text-gray-600">{request.dateDisplay}</p>
|
||||
</div>
|
||||
<span className={`px-2 lg:px-3 py-1 rounded-full text-xs font-medium self-start lg:self-auto ${getStatusColor(request.status)}`}>
|
||||
{request.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right ml-2">
|
||||
<p className="font-medium text-gray-900 text-sm lg:text-base">{request.days}j</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* All Requests Summary */}
|
||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100">
|
||||
<div className="p-4 lg:p-6 border-b border-gray-100">
|
||||
<h2 className="text-lg lg:text-xl font-semibold text-gray-900">Toutes les demandes</h2>
|
||||
</div>
|
||||
<div className="p-4 lg:p-6">
|
||||
{allRequests.length === 0 ? (
|
||||
<div className="text-center py-8">
|
||||
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-3">
|
||||
<FileText className="w-6 h-6 text-gray-400" />
|
||||
</div>
|
||||
<p className="text-gray-600">Aucune demande</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3 max-h-80 overflow-y-auto">
|
||||
{allRequests.map((request) => (
|
||||
<div key={request.id} className="flex items-center justify-between p-3 border border-gray-100 rounded-lg hover:bg-gray-50 transition-colors">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center gap-2 lg:gap-4">
|
||||
<div className="min-w-0">
|
||||
<p className="font-medium text-gray-900 text-sm truncate">{request.type}</p>
|
||||
<p className="text-xs text-gray-600">{request.dateDisplay}</p>
|
||||
<p className="text-xs text-gray-500">Soumis le {request.submittedDisplay}</p>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded-full text-xs font-medium self-start lg:self-auto ${getStatusColor(request.status)}`}>
|
||||
{request.status}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right ml-2">
|
||||
<p className="font-medium text-gray-900 text-sm">{request.days}j</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modal nouvelle demande */}
|
||||
{showNewRequestModal && (
|
||||
<NewLeaveRequestModal
|
||||
onClose={() => setShowNewRequestModal(false)}
|
||||
availableLeaveCounters={leaveCounters}
|
||||
userId={user?.id}
|
||||
onRequestSubmitted={() => {
|
||||
fetchLeaveCounters();
|
||||
fetchAllRequests();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
Reference in New Issue
Block a user