changement au niveau de requetes adaptés aux collaborateurs AD

This commit is contained in:
2025-08-27 09:40:17 +02:00
parent 9fb0c0a27f
commit ed4a7c02ca
29 changed files with 1741 additions and 548 deletions

View File

@@ -642,7 +642,7 @@ END:VEVENT`;
</button>
</div>
{/* Ton rendu de calendrier ici */}
</div>
{/* Modal nouvelle demande */}
@@ -654,9 +654,12 @@ END:VEVENT`;
}}
availableLeaveCounters={leaveCounters}
userId={user?.id}
onRequestSubmitted={() => {
resetSelection();
}}
userEmail={user.email}
userName={`${user.prenom} ${user.nom}`}
preselectedStartDate={selectedDate}
preselectedEndDate={selectedEndDate}
preselectedType={preselectedType}

View File

@@ -1,11 +1,14 @@
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 { 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,
@@ -20,54 +23,82 @@ const Dashboard = () => {
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(() => {
if (user?.id) {
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();
}
}, [user]);
}, [userId]);
const fetchLeaveCounters = async () => {
try {
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${user.id}`;
console.log(' Dashboard - Récupération des compteurs:', url);
console.log(' Dashboard - User ID utilisé:', user.id);
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}`);
}
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));
console.log("🔎 Réponse brute getLeaveCounters:", text);
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));
} catch (e) {
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
}
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'));
throw new Error(data.message || "Erreur lors de la récupération des compteurs");
}
} 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');
console.error("💥 Erreur compteurs:", error);
setLeaveCounters({
availableCP: 25,
availableRTT: 10,
@@ -75,78 +106,63 @@ const Dashboard = () => {
rttInProcess: 0,
absenteism: 0
});
} finally {
setIsLoading(false);
}
setIsLoading(false);
};
const fetchAllRequests = async () => {
console.log(' Dashboard - Début fetchAllRequests pour user:', user?.id);
try {
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${user.id}`;
console.log(' Dashboard - URL appelée:', url);
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}`);
}
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
const text = await response.text();
console.log(' Dashboard - Réponse brute:', text);
console.log("🔎 Réponse brute getRequests:", text);
const data = JSON.parse(text);
console.log(' Dashboard - Données parsées:', data);
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
}
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');
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
console.error("💥 Erreur demandes:", error);
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;
}
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',
},
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}`);
alert("✅ Réinitialisation réussie !");
fetchLeaveCounters();
} else {
alert(` Erreur lors de la réinitialisation :\n${data.message}`);
alert(` Erreur : ${data.message}`);
}
} catch (error) {
console.error('Erreur:', error);
alert(' Erreur de connexion au serveur');
console.error("Erreur:", error);
alert("❌ Erreur serveur");
}
};
const openManualResetPage = () => {
window.open('http://localhost/GTA/project/public/php/manualResetCounters.php', '_blank');
};
const getStatusColor = (status) => {
switch (status) {
case 'Approuvé':
@@ -392,7 +408,10 @@ const Dashboard = () => {
<NewLeaveRequestModal
onClose={() => setShowNewRequestModal(false)}
availableLeaveCounters={leaveCounters}
userId={user?.id}
accessToken={graphToken}
userId={userId}
userEmail={user.email}
userName={`${user.prenom} ${user.nom}`}
onRequestSubmitted={() => {
fetchLeaveCounters();
fetchAllRequests();

View File

@@ -0,0 +1,114 @@
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import Sidebar from '../components/Sidebar';
import { Calendar, Clock, CheckCircle, XCircle } from 'lucide-react';
const EmployeeDetails = () => {
const { id } = useParams();
const [employee, setEmployee] = useState(null);
const [requests, setRequests] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchEmployeeData();
}, [id]);
const fetchEmployeeData = async () => {
try {
setIsLoading(true);
// 1⃣ Données employé
const resEmployee = await fetch(`http://localhost/GTA/project/public/php/getEmploye.php?id=${id}`);
const dataEmployee = await resEmployee.json();
if (!dataEmployee.success) {
setEmployee(null);
return;
}
setEmployee(dataEmployee.employee);
// 2⃣ Historique des demandes
const resRequests = await fetch(`http://localhost/GTA/project/public/php/getEmployeRequest.php?id=${id}`);
const dataRequests = await resRequests.json();
setRequests(dataRequests.requests || []);
// 3⃣ Compteurs de congés et RTT
const resCounters = await fetch(`http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${id}`);
const dataCounters = await resCounters.json();
if (dataCounters.success) {
setEmployee(prev => ({
...prev,
conges_restants: dataCounters.counters.availableCP,
rtt_restants: dataCounters.counters.availableRTT
}));
}
} catch (err) {
console.error("Erreur récupération données collaborateur:", err);
} finally {
setIsLoading(false);
}
};
const getStatusIcon = (status) => {
switch (status) {
case 'Validée':
return <CheckCircle className="inline text-green-500 mr-1" />;
case 'Refusée':
case 'Annulée':
return <XCircle className="inline text-red-500 mr-1" />;
default:
return <Clock className="inline text-yellow-500 mr-1" />;
}
};
if (isLoading) return <p className="text-center p-6">Chargement...</p>;
if (!employee) return <p className="text-center p-6">Collaborateur introuvable</p>;
return (
<div className="min-h-screen bg-gray-50 flex">
<Sidebar />
<div className="flex-1 lg:ml-60 p-6">
<h1 className="text-2xl font-bold mb-2">{employee.Prenom} {employee.Nom}</h1>
<p className="text-gray-600 mb-6">{employee.Email}</p>
{/* Compteurs congés/RTT */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<div className="bg-white p-4 rounded-xl shadow">
<p className="text-sm text-gray-600">Congés restants</p>
<p className="text-xl font-bold">{employee.conges_restants || 0} jours</p>
</div>
<div className="bg-white p-4 rounded-xl shadow">
<p className="text-sm text-gray-600">RTT restants</p>
<p className="text-xl font-bold">{employee.rtt_restants || 0} jours</p>
</div>
</div>
{/* Historique des congés */}
<h2 className="text-lg font-semibold mb-4">Historique des congés</h2>
<div className="space-y-3">
{requests.length === 0 ? (
<p className="text-gray-500">Aucune demande</p>
) : (
requests.map((r) => (
<div key={r.Id} className="bg-white p-4 rounded-xl shadow border flex justify-between items-center">
<div>
<p className="font-medium">{r.type} - {r.days}j</p>
<p className="text-sm text-gray-600">{r.date_display}</p>
</div>
<div className="flex items-center">
{getStatusIcon(r.status)}
<span className="text-sm text-gray-700">{r.status}</span>
</div>
</div>
))
)}
</div>
</div>
</div>
);
};
export default EmployeeDetails;

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';
import { Building2, Mail, Lock, Eye, EyeOff } from 'lucide-react';
import { Building2, Mail, Lock, Eye, EyeOff, AlertTriangle } from 'lucide-react';
const Login = () => {
const [email, setEmail] = useState('');
@@ -9,25 +9,88 @@ const Login = () => {
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [authMethod, setAuthMethod] = useState(''); // Pour tracker la méthode d'auth utilisée
const navigate = useNavigate();
const { login, loginWithO365 } = useAuth();
const { login, loginWithO365, isAuthorized } = useAuth();
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
setError('');
setAuthMethod('local');
const success = await login(email, password);
if (success) {
navigate('/dashboard');
} else {
setError('Identifiants incorrects. Veuillez réessayer.');
try {
const success = await login(email, password);
if (success) {
navigate('/dashboard');
} else {
setError('Identifiants incorrects. Veuillez réessayer.');
}
} catch (error) {
setError(error.message || 'Erreur lors de la connexion');
}
setIsLoading(false);
};
const handleO365Login = async () => {
setIsLoading(true);
setError('');
setAuthMethod('o365');
try {
// Étape 1 : Login O365
const success = await loginWithO365();
if (!success) {
setError("Erreur lors de la connexion Office 365");
setIsLoading(false);
return;
}
// Étape 2 : Récupération du token dauthentification (si ton context le fournit)
const token = localStorage.getItem("o365_token");
// ⚠️ Ici jimagine que tu stockes ton token quelque part (dans ton AuthContext ou localStorage).
// Adapte selon ton implémentation de loginWithO365
// Étape 3 : Appel de ton API PHP
const response = await fetch("http://localhost/GTA/project/public/php/initial-sync.php", {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json",
},
});
const data = await response.json();
console.log("Résultat syncGroups :", data);
if (!data.success) {
setError("Erreur de synchronisation des groupes : " + data.message);
setIsLoading(false);
return;
}
// Étape 4 : Redirection vers le dashboard
navigate('/dashboard');
} catch (error) {
console.error('Erreur O365:', error);
if (error.message?.includes('non autorisé') || error.message?.includes('Accès refusé')) {
setError('Accès refusé : Vous devez être membre d\'un groupe autorisé dans votre organisation.');
} else if (error.message?.includes('AADSTS')) {
setError('Erreur d\'authentification Azure AD. Contactez votre administrateur.');
} else {
setError(error.message || "Erreur lors de la connexion Office 365");
}
}
setIsLoading(false);
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex flex-col lg:flex-row">
{/* Image côté gauche */}
@@ -50,7 +113,38 @@ const Login = () => {
<p className="text-sm lg:text-base text-gray-600">Gestion de congés</p>
</div>
{/* Form */}
{/* Connexion Office 365 prioritaire */}
<div className="mb-6">
<button
onClick={handleO365Login}
disabled={isLoading}
type="button"
className="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center space-x-2"
>
{isLoading && authMethod === 'o365' ? (
<span>Connexion Office 365...</span>
) : (
<>
<svg className="w-5 h-5" viewBox="0 0 21 21" fill="currentColor">
<path d="M10.5 0L0 7v7l10.5 7L21 14V7L10.5 0zM3.5 8.5L10.5 3l7 5.5v5L10.5 19l-7-5.5v-5z" />
</svg>
<span>Se connecter avec Office 365</span>
</>
)}
</button>
</div>
{/* Séparateur */}
<div className="relative mb-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">ou</span>
</div>
</div>
{/* Formulaire classique */}
<form onSubmit={handleSubmit} className="space-y-4 lg:space-y-6">
<div>
<label htmlFor="email" className="block text-sm lg:text-base font-medium text-gray-700 mb-2">
@@ -66,6 +160,7 @@ const Login = () => {
className="w-full pl-9 lg:pl-10 pr-4 py-2 lg:py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm lg:text-base"
placeholder="votre.email@entreprise.com"
required
disabled={isLoading}
/>
</div>
</div>
@@ -84,11 +179,13 @@ const Login = () => {
className="w-full pl-9 lg:pl-10 pr-10 lg:pr-12 py-2 lg:py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm lg:text-base"
placeholder="••••••••"
required
disabled={isLoading}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-900"
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-900 disabled:opacity-50"
disabled={isLoading}
title={showPassword ? "Masquer le mot de passe" : "Afficher le mot de passe"}
>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
@@ -96,37 +193,41 @@ const Login = () => {
</div>
</div>
<div className="mt-6 text-center">
<button
onClick={async () => {
const success = await loginWithO365();
if (success) {
navigate('/dashboard');
} else {
setError("Erreur lors de la connexion Office 365");
}
}}
type="button"
className="w-full bg-gray-700 text-white py-3 rounded-lg font-medium hover:bg-green-700 transition-colors"
>
Se connecter avec Office 365
</button>
</div>
{/* Affichage des erreurs */}
{error && (
<div className="p-2 lg:p-3 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-600 text-xs lg:text-sm">{error}</p>
<div className="p-3 bg-red-50 border border-red-200 rounded-lg">
<div className="flex items-start space-x-2">
<AlertTriangle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
<div className="flex-1">
<p className="text-red-700 text-sm font-medium">
{error.includes('Accès refusé') ? 'Accès refusé' : 'Erreur de connexion'}
</p>
<p className="text-red-600 text-xs mt-1">{error}</p>
{error.includes('groupe autorisé') && (
<p className="text-red-600 text-xs mt-2">
Contactez votre administrateur pour être ajouté aux groupes appropriés.
</p>
)}
</div>
</div>
</div>
)}
<button
type="submit"
disabled={isLoading}
className="w-full bg-blue-600 text-white py-2 lg:py-3 px-4 rounded-lg font-medium hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm lg:text-base"
className="w-full bg-gray-600 text-white py-2 lg:py-3 px-4 rounded-lg font-medium hover:bg-gray-700 focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-sm lg:text-base"
>
{isLoading ? 'Connexion...' : 'Se connecter'}
{isLoading && authMethod === 'local' ? 'Connexion...' : 'Connexion locale'}
</button>
</form>
{/* Info sur l'authentification */}
<div className="mt-6 text-center">
<p className="text-xs text-gray-500">
Utilisez votre compte Office 365 pour une connexion sécurisée
</p>
</div>
</div>
</div>
</div>
@@ -134,4 +235,4 @@ const Login = () => {
);
};
export default Login;
export default Login;

View File

@@ -3,6 +3,7 @@ import { useAuth } from '../context/AuthContext';
import Sidebar from '../components/Sidebar';
import { Plus, Search, Filter, Eye, Menu, X } from 'lucide-react';
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
import { useMsal } from "@azure/msal-react";
const Requests = () => {
const { user } = useAuth();
@@ -32,8 +33,30 @@ const Requests = () => {
const [showFilters, setShowFilters] = useState(false);
const [graphToken, setGraphToken] = useState(null);
const { instance, accounts } = useMsal();
const userId = user?.id || user?.CollaborateurADId || user?.ID;
useEffect(() => {
if (user?.id) {
if (accounts.length > 0) {
const request = {
scopes: ["User.Read", "Mail.Send"],
account: accounts[0],
};
instance.acquireTokenSilent(request)
.then((response) => {
setGraphToken(response.accessToken);
console.log("✅ Token Graph récupéré :", response.accessToken);
})
.catch((err) => {
console.error("❌ Erreur récupération token Graph:", err);
});
}
}, [accounts, instance]);
useEffect(() => {
if (userId) {
fetchLeaveCounters();
fetchAllRequests();
}
@@ -69,7 +92,7 @@ const Requests = () => {
const fetchLeaveCounters = async () => {
try {
const response = await fetch(`http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${user.id}`);
const response = await fetch(`http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${userId}`);
const text = await response.text();
let data;
try {
@@ -89,7 +112,7 @@ const Requests = () => {
const fetchAllRequests = async () => {
try {
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${user.id}`;
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${userId}`;
const response = await fetch(url);
const text = await response.text();
let data;
@@ -426,6 +449,8 @@ const Requests = () => {
onClose={() => setShowNewRequestModal(false)}
availableLeaveCounters={leaveCounters}
userId={user?.id}
userEmail={user.email}
userName={`${user.prenom} ${user.nom}`}
onRequestSubmitted={() => {
fetchLeaveCounters();
fetchAllRequests();