Files
GTA/project/src/pages/Dashboard.jsx

427 lines
22 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { useAuth } from '../context/AuthContext';
import Sidebar from '../components/Sidebar';
import { Calendar as CalendarIcon, Clock, Plus, Settings, RefreshCw, Menu, FileText } from 'lucide-react';
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
import { useMsal } from "@azure/msal-react";
import { loginRequest } from "../authConfig";
const Dashboard = () => {
const { user } = useAuth();
const [graphToken, setGraphToken] = useState(null);
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([]);
const { instance, accounts } = useMsal();
// Récupération du bon ID utilisateur (CollaborateurAD)
const userId = user?.id || user?.CollaborateurADId || user?.ID;
useEffect(() => {
console.log("🔎 Utilisateur chargé dans Dashboard:", user);
}, [user]);
useEffect(() => {
if (accounts.length > 0) {
const request = {
...loginRequest,
account: accounts[0],
};
instance.acquireTokenSilent(request)
.then((response) => {
console.log("✅ Token Graph récupéré (silent):", response.accessToken);
setGraphToken(response.accessToken);
})
.catch(async (err) => {
console.warn("⚠️ Silent échoué, on tente un popup:", err);
try {
const tokenResponse = await instance.acquireTokenPopup(request);
console.log("✅ Token Graph récupéré (popup):", tokenResponse.accessToken);
setGraphToken(tokenResponse.accessToken);
} catch (popupErr) {
console.error("❌ Impossible d'obtenir un token:", popupErr);
}
});
}
}, [accounts, instance]);
// Vérification rapide du token
useEffect(() => {
if (graphToken) {
console.log("🔎 Token prêt ?",
graphToken.split(".").length === 3 ? "✅ JWT valide" : "❌ Token invalide: " + graphToken
);
}
}, [graphToken]);
// 🔄 Récupération compteurs et demandes
useEffect(() => {
if (userId) {
fetchLeaveCounters();
fetchAllRequests();
}
}, [userId]);
const fetchLeaveCounters = async () => {
try {
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${userId}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
const text = await response.text();
console.log("🔎 Réponse brute getLeaveCounters:", text);
let data;
try {
data = JSON.parse(text);
} catch (e) {
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
}
if (data.success) {
setLeaveCounters(data.counters);
} else {
throw new Error(data.message || "Erreur lors de la récupération des compteurs");
}
} catch (error) {
console.error("💥 Erreur compteurs:", error);
setLeaveCounters({
availableCP: 25,
availableRTT: 10,
availableABS: 0,
rttInProcess: 0,
absenteism: 0
});
} finally {
setIsLoading(false);
}
};
const fetchAllRequests = async () => {
try {
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${userId}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
const text = await response.text();
console.log("🔎 Réponse brute getRequests:", text);
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
}
if (data.success) {
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("💥 Erreur demandes:", error);
setAllRequests([]);
setRecentRequests([]);
}
};
const handleResetCounters = async () => {
if (!confirm("⚠️ Voulez-vous vraiment réinitialiser les compteurs ?")) return;
try {
const response = await fetch('http://localhost/GTA/project/public/php/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 (error) {
console.error("Erreur:", error);
alert("❌ Erreur serveur");
}
};
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}
accessToken={graphToken}
userId={userId}
userEmail={user.email}
userName={`${user.prenom} ${user.nom}`}
onRequestSubmitted={() => {
fetchLeaveCounters();
fetchAllRequests();
}}
/>
)}
</div>
</div>
</div>
);
};
export default Dashboard;