diff --git a/project/public/php/check-user-groups.php b/project/public/php/check-user-groups.php index 9c18c98..eaa44e4 100644 --- a/project/public/php/check-user-groups.php +++ b/project/public/php/check-user-groups.php @@ -130,7 +130,7 @@ echo json_encode([ "authorized" => true, "role" => $role, "groups" => [$role], - "localUserId" => (int)$newUserId, // 🔹 ajout important + "localUserId" => (int)$newUserId, "user" => [ "id" => $newUserId, "entraUserId" => $entraUserId, diff --git a/project/public/php/getEmploye.php b/project/public/php/getEmploye.php index 82ee97d..844feae 100644 --- a/project/public/php/getEmploye.php +++ b/project/public/php/getEmploye.php @@ -28,11 +28,12 @@ if ($id <= 0) { } try { - $stmt = $conn->prepare(" - SELECT id, Nom, Prenom, Email, Matricule, Telephone, Adresse - FROM CollaborateurAD - WHERE id = ? AND Actif = 1 - "); + $stmt = $conn->prepare(" + SELECT id, Nom, Prenom, Email + FROM CollaborateurAD + WHERE id = ? +"); + $stmt->bind_param("i", $id); $stmt->execute(); $result = $stmt->get_result(); diff --git a/project/public/php/initial-sync.php b/project/public/php/initial-sync.php index f603981..f3e4834 100644 --- a/project/public/php/initial-sync.php +++ b/project/public/php/initial-sync.php @@ -13,7 +13,6 @@ if ($conn->connect_error) { die(json_encode(["success" => false, "message" => "Erreur DB: " . $conn->connect_error])); } -// --- Authentification (client credentials) --- $tenantId = "9840a2a0-6ae1-4688-b03d-d2ec291be0f9"; $clientId = "4bb4cc24-bac3-427c-b02c-5d14fc67b561"; $clientSecret = "ViC8Q~n4F5YweE18wjS0kfhp3kHh6LB2gZ76_b4R"; @@ -42,8 +41,9 @@ if (!$accessToken) { } // --- ID du groupe cible (Ensup-Groupe) --- -$groupId = "c1ea877c-6bca-4f47-bfad-f223640813a0"; // 🔹 Mets l'Object ID de ton groupe ici +$groupId = "c1ea877c-6bca-4f47-bfad-f223640813a0"; +// --- Récupérer infos du groupe --- $urlGroup = "https://graph.microsoft.com/v1.0/groups/$groupId?\$select=id,displayName,description,mail,createdDateTime"; $ch = curl_init($urlGroup); curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken"]); @@ -57,31 +57,6 @@ if (!isset($group["id"])) { } $displayName = $group["displayName"] ?? ""; -$description = $group["description"] ?? ""; -$mail = $group["mail"] ?? ""; -$createdAt = null; -if (!empty($group["createdDateTime"])) { - $dt = new DateTime($group["createdDateTime"]); - $createdAt = $dt->format("Y-m-d H:i:s"); // format MySQL -} - - - - -// --- Insérer / mettre à jour le groupe dans EntraGroups --- -$stmt = $conn->prepare("INSERT INTO EntraGroups (Id, DisplayName, Description, Mail, CreatedAt, UpdatedAt, SyncDate, IsActive) - VALUES (?, ?, ?, ?, ?, NOW(), NOW(), 1) - ON DUPLICATE KEY UPDATE - DisplayName=?, Description=?, Mail=?, UpdatedAt=NOW(), SyncDate=NOW(), IsActive=1"); -if ($stmt) { - $stmt->bind_param("ssssssss", - $groupId, $displayName, $description, $mail, $createdAt, - $displayName, $description, $mail - ); - $stmt->execute(); -} - - // --- Récupérer les membres du groupe --- $urlMembers = "https://graph.microsoft.com/v1.0/groups/$groupId/members?\$select=id,givenName,surname,mail,department,jobTitle"; @@ -100,23 +75,25 @@ foreach ($members as $m) { $nom = $m["surname"] ?? ""; $email = $m["mail"] ?? ""; $service = $m["department"] ?? ""; - $role = "Collaborateur"; // par défaut if (!$email) continue; + // Insertion ou mise à jour de l’utilisateur $stmt = $conn->prepare("INSERT INTO CollaborateurAD (entraUserId, prenom, nom, email, service, role) VALUES (?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE prenom=?, nom=?, email=?, service=?, role=?"); + ON DUPLICATE KEY UPDATE prenom=?, nom=?, email=?, service=?"); if ($stmt) { - $stmt->bind_param("sssssssssss", + $role = "Collaborateur"; // attribué uniquement si nouvel utilisateur + $stmt->bind_param("ssssssssss", $entraUserId, $prenom, $nom, $email, $service, $role, - $prenom, $nom, $email, $service, $role + $prenom, $nom, $email, $service ); $stmt->execute(); $usersInserted++; } } +// --- Réponse finale --- echo json_encode([ "success" => true, "message" => "Synchronisation terminée", diff --git a/project/public/php/validateRequest.php b/project/public/php/validateRequest.php index 7fe78d1..74124ba 100644 --- a/project/public/php/validateRequest.php +++ b/project/public/php/validateRequest.php @@ -44,149 +44,100 @@ $comment = $data['comment'] ?? ''; try { $conn->begin_transaction(); - // Vérifier si validateur est Users ou CollaborateurAD - $isUserValidator = false; - $stmt = $conn->prepare("SELECT ID FROM Users WHERE ID = ?"); + // Vérifier que le validateur existe dans CollaborateurAD + $stmt = $conn->prepare("SELECT Id, prenom, nom FROM CollaborateurAD WHERE Id = ?"); $stmt->bind_param("i", $validatorId); $stmt->execute(); - $res = $stmt->get_result(); - if ($res->fetch_assoc()) { - $isUserValidator = true; - } else { - $stmt = $conn->prepare("SELECT Id FROM CollaborateurAD WHERE Id = ?"); - $stmt->bind_param("i", $validatorId); - $stmt->execute(); - $res = $stmt->get_result(); - if (!$res->fetch_assoc()) { - throw new Exception("Validateur introuvable dans Users ou CollaborateurAD"); - } - } + $validator = $stmt->get_result()->fetch_assoc(); $stmt->close(); - // Récupération demande + if (!$validator) { + throw new Exception("Validateur introuvable dans CollaborateurAD"); + } + + // Récupération de la demande $queryCheck = " - SELECT dc.Id, dc.EmployeeId, dc.CollaborateurADId, dc.TypeCongeId, dc.DateDebut, dc.DateFin, dc.NombreJours, - u.Nom as UserNom, u.Prenom as UserPrenom, - ca.nom as CADNom, ca.prenom as CADPrenom, + SELECT dc.Id, dc.CollaborateurADId, dc.TypeCongeId, dc.DateDebut, dc.DateFin, dc.NombreJours, + ca.prenom as CADPrenom, ca.nom as CADNom, tc.Nom as TypeNom FROM DemandeConge dc JOIN TypeConge tc ON dc.TypeCongeId = tc.Id - LEFT JOIN Users u ON dc.EmployeeId = u.ID LEFT JOIN CollaborateurAD ca ON dc.CollaborateurADId = ca.Id WHERE dc.Id = ? AND dc.Statut = 'En attente' "; $stmtCheck = $conn->prepare($queryCheck); $stmtCheck->bind_param("i", $requestId); $stmtCheck->execute(); - $resultCheck = $stmtCheck->get_result(); - - if (!($requestRow = $resultCheck->fetch_assoc())) { - throw new Exception("Demande non trouvée ou déjà traitée"); - } + $requestRow = $stmtCheck->get_result()->fetch_assoc(); $stmtCheck->close(); - $employeeId = $requestRow['EmployeeId']; + if (!$requestRow) { + throw new Exception("Demande non trouvée ou déjà traitée"); + } + $collaborateurId = $requestRow['CollaborateurADId']; $typeCongeId = $requestRow['TypeCongeId']; $nombreJours = $requestRow['NombreJours']; - $employeeName = $employeeId - ? $requestRow['UserPrenom']." ".$requestRow['UserNom'] - : $requestRow['CADPrenom']." ".$requestRow['CADNom']; + $employeeName = $requestRow['CADPrenom']." ".$requestRow['CADNom']; $typeNom = $requestRow['TypeNom']; $newStatus = ($action === 'approve') ? 'Validée' : 'Refusée'; // 🔹 Mise à jour DemandeConge - if ($isUserValidator) { - $queryUpdate = " - UPDATE DemandeConge - SET Statut = ?, - ValidateurId = ?, - ValidateurADId = NULL, - DateValidation = NOW(), - CommentaireValidation = ? - WHERE Id = ? - "; - } else { - $queryUpdate = " - UPDATE DemandeConge - SET Statut = ?, - ValidateurId = NULL, - ValidateurADId = ?, - DateValidation = NOW(), - CommentaireValidation = ? - WHERE Id = ? - "; - } + $queryUpdate = " + UPDATE DemandeConge + SET Statut = ?, + ValidateurId = ?, + ValidateurADId = ?, + DateValidation = NOW(), + CommentaireValidation = ? + WHERE Id = ? + "; $stmtUpdate = $conn->prepare($queryUpdate); - $stmtUpdate->bind_param("sisi", $newStatus, $validatorId, $comment, $requestId); + $stmtUpdate->bind_param("siisi", $newStatus, $validatorId, $validatorId, $comment, $requestId); $stmtUpdate->execute(); $stmtUpdate->close(); - // 🔹 Déduction solde (seulement Users, pas AD, hors maladie) - if ($action === 'approve' && $typeNom !== 'Congé maladie' && $employeeId) { - $currentDate = new DateTime(); - $year = ($typeNom === 'Congé payé' && (int)$currentDate->format('m') < 6) - ? $currentDate->format('Y') - 1 - : $currentDate->format('Y'); - + // 🔹 Déduction solde (pas maladie) + if ($action === 'approve' && $typeNom !== 'Congé maladie' && $collaborateurId) { + $year = date("Y"); $queryDeduct = " UPDATE CompteurConges SET Solde = GREATEST(0, Solde - ?) - WHERE EmployeeId = ? AND TypeCongeId = ? AND Annee = ? + WHERE CollaborateurADId = ? AND TypeCongeId = ? AND Annee = ? "; $stmtDeduct = $conn->prepare($queryDeduct); - $stmtDeduct->bind_param("diii", $nombreJours, $employeeId, $typeCongeId, $year); + $stmtDeduct->bind_param("diii", $nombreJours, $collaborateurId, $typeCongeId, $year); $stmtDeduct->execute(); $stmtDeduct->close(); } - // 🔹 Notification (User ou CollaborateurAD) + // 🔹 Notification $notificationTitle = ($action === 'approve') ? 'Demande approuvée' : 'Demande refusée'; $notificationMessage = "Votre demande de $typeNom a été " . (($action === 'approve') ? "approuvée" : "refusée"); if ($comment) $notificationMessage .= " (Commentaire: $comment)"; $notifType = ($action === 'approve') ? 'Success' : 'Error'; - if ($employeeId) { - $queryNotif = " - INSERT INTO Notifications (UserId, CollaborateurADId, Titre, Message, Type, DemandeCongeId) - VALUES (?, NULL, ?, ?, ?, ?) - "; - $stmtNotif = $conn->prepare($queryNotif); - $stmtNotif->bind_param("isssi", $employeeId, $notificationTitle, $notificationMessage, $notifType, $requestId); - $stmtNotif->execute(); - $stmtNotif->close(); - } elseif ($collaborateurId) { - $queryNotif = " - INSERT INTO Notifications (UserId, CollaborateurADId, Titre, Message, Type, DemandeCongeId) - VALUES (NULL, ?, ?, ?, ?, ?) - "; - $stmtNotif = $conn->prepare($queryNotif); - $stmtNotif->bind_param("isssi", $collaborateurId, $notificationTitle, $notificationMessage, $notifType, $requestId); - $stmtNotif->execute(); - $stmtNotif->close(); - } + $queryNotif = " + INSERT INTO Notifications (CollaborateurADId, Titre, Message, Type, DemandeCongeId) + VALUES (?, ?, ?, ?, ?) + "; + $stmtNotif = $conn->prepare($queryNotif); + $stmtNotif->bind_param("isssi", $collaborateurId, $notificationTitle, $notificationMessage, $notifType, $requestId); + $stmtNotif->execute(); + $stmtNotif->close(); - // 🔹 Historique (User ou CollaborateurAD) + // 🔹 Historique $actionText = ($action === 'approve') ? 'Validation congé' : 'Refus congé'; $actionDetails = "$actionText $employeeName ($typeNom)"; if ($comment) $actionDetails .= " - $comment"; - if ($isUserValidator) { - $queryHistory = " - INSERT INTO HistoriqueActions (UserId, CollaborateurADId, Action, Details, DemandeCongeId) - VALUES (?, NULL, ?, ?, ?) - "; - $stmtHistory = $conn->prepare($queryHistory); - $stmtHistory->bind_param("issi", $validatorId, $actionText, $actionDetails, $requestId); - } else { - $queryHistory = " - INSERT INTO HistoriqueActions (UserId, CollaborateurADId, Action, Details, DemandeCongeId) - VALUES (NULL, ?, ?, ?, ?) - "; - $stmtHistory = $conn->prepare($queryHistory); - $stmtHistory->bind_param("issi", $validatorId, $actionText, $actionDetails, $requestId); - } + $queryHistory = " + INSERT INTO HistoriqueActions (CollaborateurADId, Action, Details, DemandeCongeId) + VALUES (?, ?, ?, ?) + "; + $stmtHistory = $conn->prepare($queryHistory); + $stmtHistory->bind_param("issi", $validatorId, $actionText, $actionDetails, $requestId); $stmtHistory->execute(); $stmtHistory->close(); @@ -204,4 +155,3 @@ try { } $conn->close(); -?> diff --git a/project/src/App.jsx b/project/src/App.jsx index ddec53c..9ac4a96 100644 --- a/project/src/App.jsx +++ b/project/src/App.jsx @@ -7,35 +7,73 @@ import Requests from './pages/Requests'; import Calendar from './pages/Calendar'; import Manager from './pages/Manager'; import ProtectedRoute from './components/ProtectedRoute'; -import EmployeeDetails from './pages/EmployeeDetails'; +import EmployeeDetails from './pages/EmployeeDetails'; +import Collaborateur from './pages/Collaborateur'; function App() { return ( + {/* Route publique */} } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - } /> + + {/* Routes protégées */} + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + + } + /> + + {/* Redirection par défaut */} } /> @@ -43,4 +81,4 @@ function App() { ); } -export default App; \ No newline at end of file +export default App; diff --git a/project/src/components/Sidebar.jsx b/project/src/components/Sidebar.jsx index 9919ce7..9c9c6ac 100644 --- a/project/src/components/Sidebar.jsx +++ b/project/src/components/Sidebar.jsx @@ -15,8 +15,10 @@ const Sidebar = ({ isOpen, onToggle }) => { return 'bg-red-100 text-red-800'; case 'Validateur': return 'bg-green-100 text-green-800'; + case 'Collaborateur': + return 'bg-cyan-600 text-white'; default: - return 'bg-blue-100 text-blue-800'; + return 'bg-gray-100 text-gray-800'; } }; @@ -29,10 +31,12 @@ const Sidebar = ({ isOpen, onToggle }) => { /> )} -
+
{/* Bouton fermer (mobile) */}
-
diff --git a/project/src/pages/Collaborateur.jsx b/project/src/pages/Collaborateur.jsx new file mode 100644 index 0000000..f0b58f3 --- /dev/null +++ b/project/src/pages/Collaborateur.jsx @@ -0,0 +1,524 @@ +import React, { useState, useEffect } from 'react'; +import { useAuth } from '../context/AuthContext'; +import Sidebar from '../components/Sidebar'; +import { Users, CheckCircle, XCircle, Clock, Calendar, FileText, Menu, Eye, MessageSquare } from 'lucide-react'; + +const Collaborateur = () => { + const { user } = useAuth(); + const [sidebarOpen, setSidebarOpen] = useState(false); + const isEmployee = user?.role === 'Collaborateur'; + const [teamMembers, setTeamMembers] = useState([]); + const [pendingRequests, setPendingRequests] = useState([]); + const [allRequests, setAllRequests] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedRequest, setSelectedRequest] = useState(null); + const [showValidationModal, setShowValidationModal] = useState(false); + const [validationComment, setValidationComment] = useState(''); + const [validationAction, setValidationAction] = useState(''); + + useEffect(() => { + if (user?.id) { + fetchTeamData(); + } + }, [user]); + + const fetchTeamData = async () => { + try { + setIsLoading(true); + + // Récupérer les membres de l'équipe + await fetchTeamMembers(); + + // Récupérer les demandes en attente + await fetchPendingRequests(); + + // Récupérer toutes les demandes de l'équipe + await fetchAllTeamRequests(); + + } catch (error) { + console.error('Erreur lors de la récupération des données équipe:', error); + } finally { + setIsLoading(false); + } + }; + + const fetchTeamMembers = async () => { + try { + const response = await fetch(`http://localhost/GTA/project/public/php/getTeamMembers.php?manager_id=${user.id}`); + const text = await response.text(); + console.log('Réponse équipe:', text); + + const data = JSON.parse(text); + if (data.success) { + setTeamMembers(data.team_members || []); + } + } catch (error) { + console.error('Erreur récupération équipe:', error); + setTeamMembers([]); + } + }; + + const fetchPendingRequests = async () => { + try { + const response = await fetch(`http://localhost/GTA/project/public/php/getPendingRequests.php?manager_id=${user.id}`); + const text = await response.text(); + console.log('Réponse demandes en attente:', text); + + const data = JSON.parse(text); + if (data.success) { + setPendingRequests(data.requests || []); + } + } catch (error) { + console.error('Erreur récupération demandes en attente:', error); + setPendingRequests([]); + } + }; + + const fetchAllTeamRequests = async () => { + try { + const response = await fetch(`http://localhost/GTA/project/public/php/getAllTeamRequests.php?SuperieurId=${user.id}`); + const text = await response.text(); + console.log('Réponse toutes demandes équipe:', text); + + const data = JSON.parse(text); + if (data.success) { + setAllRequests(data.requests || []); + } + } catch (error) { + + console.error('Erreur récupération toutes demandes:', error); + console.log('Réponse brute:', text); + setAllRequests([]); + } + }; + + const handleValidateRequest = async (requestId, action, comment = '') => { + try { + const response = await fetch('http://localhost/GTA/project/public/php/validateRequest.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + request_id: requestId, + action: action, // 'approve' ou 'reject' + comment: comment, + validator_id: user.id + }), + }); + + const text = await response.text(); + console.log('Réponse validation:', text); + + const data = JSON.parse(text); + + if (data.success) { + // Rafraîchir les données + await fetchTeamData(); + setShowValidationModal(false); + setSelectedRequest(null); + setValidationComment(''); + + alert(`Demande ${action === 'approve' ? 'approuvée' : 'refusée'} avec succès !`); + } else { + alert(`Erreur: ${data.message}`); + } + } catch (error) { + console.error('Erreur validation:', error); + alert('Erreur lors de la validation'); + } + }; + + const openValidationModal = (request, action) => { + setSelectedRequest(request); + setValidationAction(action); + setValidationComment(''); + setShowValidationModal(true); + }; + + const getStatusColor = (status) => { + switch (status) { + case 'En attente': return 'bg-yellow-100 text-yellow-800'; + case 'Validée': + case 'Approuvé': return 'bg-green-100 text-green-800'; + case 'Refusée': return 'bg-red-100 text-red-800'; + default: return 'bg-gray-100 text-gray-800'; + } + }; + + const getTypeColor = (type) => { + switch (type) { + case 'Congés payés': + case 'Congé payé': return 'bg-blue-100 text-blue-800'; + case 'RTT': return 'bg-green-100 text-green-800'; + case 'Congé maladie': return 'bg-red-100 text-red-800'; + default: return 'bg-gray-100 text-gray-800'; + } + }; + + if (isLoading) { + return ( +
+ setSidebarOpen(!sidebarOpen)} /> +
+
+
+

Chargement des données équipe...

+
+
+
+ ); + } + + return ( +
+ setSidebarOpen(!sidebarOpen)} /> + +
+
+ {/* Mobile menu button */} +
+ +
+ + {/* Header */} +
+

+ {isEmployee ? 'Mon équipe 👥' : 'Gestion d\'équipe 👥'} +

+

+ {isEmployee ? 'Consultez les congés de votre équipe' : 'Gérez les demandes de congés de votre équipe'} +

+
+ + {/* Stats Cards */} +
+
+
+
+

Équipe

+

{teamMembers.length}

+

membres

+
+
+ +
+
+
+ +
+
+
+

En attente

+

{pendingRequests.length}

+

demandes

+
+
+ +
+
+
+ +
+
+
+

Approuvées

+

+ {allRequests.filter(r => r.status === 'Validée' || r.status === 'Approuvé').length} +

+

demandes

+
+
+ +
+
+
+ +
+
+
+

Refusées

+

+ {allRequests.filter(r => r.status === 'Refusée').length} +

+

demandes

+
+
+ +
+
+
+
+ + {/* Main Content */} +
+ {/* Demandes en attente */} + {!isEmployee && ( +
+
+

+ + Demandes en attente ({pendingRequests.length}) +

+
+
+ {pendingRequests.length === 0 ? ( +
+ +

Aucune demande en attente

+
+ ) : ( +
+ {pendingRequests.map((request) => ( +
+
+
+
+

{request.employee_name}

+ + {request.type} + +
+

{request.date_display}

+

Soumis le {request.submitted_display}

+
+
+

{request.days}j

+
+
+ + {request.reason && ( +
+ Motif: {request.reason} +
+ )} + +
+ + +
+
+ ))} +
+ )} +
+
+ )} + + {/* Équipe */} +
+
+

+ + Mon équipe ({teamMembers.length}) +

+
+
+ {teamMembers.length === 0 ? ( +
+ +

Aucun membre d'équipe

+
+ ) : ( +
+ {teamMembers.map((member) => ( +
+
+
+ + {member.prenom?.charAt(0)}{member.nom?.charAt(0)} + +
+
+

{member.prenom} {member.nom}

+

{member.email}

+
+
+ {!isEmployee && ( +
+

+ {allRequests.filter(r => r.employee_id === member.id && r.status === 'En attente').length} en attente +

+

+ {allRequests.filter(r => r.employee_id === member.id).length} total +

+
+ )} +
+ ))} +
+ )} +
+
+
+ + {/* Historique des demandes */} + {!isEmployee && ( +
+
+

+ + Historique des demandes ({allRequests.length}) +

+
+
+ {allRequests.length === 0 ? ( +
+ +

Aucune demande

+
+ ) : ( +
+ {allRequests.map((request) => ( +
+
+

{request.employee_name}

+ + {request.type} + + + {request.status} + +
+

{request.date_display}

+

Soumis le {request.submitted_display}

+ + {request.reason && ( +

Motif : {request.reason}

+ )} + + {request.file && ( +
+

Document joint

+ + + Voir le fichier + +
+ )} + +
+

{request.days}j

+
+
+ ))} + +
+ )} +
+
+ )} +
+
+ + {/* Modal de validation */} + + {showValidationModal && selectedRequest && ( +
+
+ {/* Header */} +
+

+ {validationAction === 'approve' ? 'Approuver' : 'Refuser'} la demande +

+
+ + {/* Corps du contenu */} +
+
+

{selectedRequest.employee_name}

+

+ {selectedRequest.type} - {selectedRequest.date_display} +

+

{selectedRequest.days} jour(s)

+ + {selectedRequest.reason && ( +

+ Motif: {selectedRequest.reason} +

+ )} + + {selectedRequest.file && ( +
+

Document joint

+ + + Voir le fichier + +
+ )} + + +
+ + {/* Champ commentaire */} +
+ +