diff --git a/project/public/getRequests.php b/project/public/getRequests.php index 5b00459..5f26ada 100644 --- a/project/public/getRequests.php +++ b/project/public/getRequests.php @@ -4,7 +4,6 @@ header("Access-Control-Allow-Origin: *"); header("Access-Control-Allow-Methods: GET, OPTIONS"); header("Access-Control-Allow-Headers: Content-Type"); -// Gère la requête OPTIONS (pré-vol CORS) if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { http_response_code(200); exit(); @@ -12,7 +11,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { header("Content-Type: application/json"); -// Log des erreurs pour debug ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); @@ -22,17 +20,14 @@ $dbname = "DemandeConge"; $username = "wpuser"; $password = "-2b/)ru5/Bi8P[7_"; -// Crée une nouvelle connexion à la base de données $conn = new mysqli($host, $username, $password, $dbname); -// Vérifie la connexion if ($conn->connect_error) { error_log("Erreur connexion DB getRequests: " . $conn->connect_error); echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données : " . $conn->connect_error]); exit(); } -// Récupère l'ID utilisateur depuis les paramètres de requête GET $userId = $_GET['user_id'] ?? null; error_log("=== DEBUT getRequests.php ==="); @@ -45,9 +40,6 @@ if ($userId === null) { exit(); } -error_log("getRequests - Récupération pour user_id: $userId (type: " . gettype($userId) . ")"); - -// Vérifier si l'utilisateur existe $checkUserQuery = "SELECT ID, Nom, Prenom FROM Users WHERE ID = ?"; $checkUserStmt = $conn->prepare($checkUserQuery); if ($checkUserStmt) { @@ -62,15 +54,13 @@ if ($checkUserStmt) { $checkUserStmt->close(); } -// Fonction pour calculer les jours ouvrés (hors week-ends) function getWorkingDays($startDate, $endDate) { $workingDays = 0; $current = new DateTime($startDate); $end = new DateTime($endDate); - while ($current <= $end) { - $dayOfWeek = (int)$current->format('N'); // 1 (Lundi) à 7 (Dimanche) - if ($dayOfWeek < 6) { // Si ce n'est ni Samedi (6) ni Dimanche (7) + $dayOfWeek = (int)$current->format('N'); + if ($dayOfWeek < 6) { $workingDays++; } $current->modify('+1 day'); @@ -79,7 +69,6 @@ function getWorkingDays($startDate, $endDate) { } try { - // Requête pour récupérer les demandes de l'utilisateur avec les informations du type de congé $query = " SELECT dc.Id, @@ -89,47 +78,28 @@ try { dc.DateDemande, dc.Commentaire, dc.Validateur, + dc.DocumentJoint, -- 👈 CHAMP AJOUTÉ ICI tc.Nom as TypeConge FROM DemandeConge dc JOIN TypeConge tc ON dc.TypeCongeId = tc.Id WHERE dc.EmployeeId = ? ORDER BY dc.DateDemande DESC "; - - error_log("getRequests - Requête SQL: $query"); - + $stmt = $conn->prepare($query); if ($stmt === false) { throw new Exception("Erreur de préparation de la requête : " . $conn->error); } - + $stmt->bind_param("i", $userId); $stmt->execute(); $result = $stmt->get_result(); - - error_log("getRequests - Nombre de résultats trouvés: " . $result->num_rows); - - // Debug: Afficher toutes les demandes de la table pour cet utilisateur - $debugQuery = "SELECT COUNT(*) as total FROM DemandeConge WHERE EmployeeId = ?"; - $debugStmt = $conn->prepare($debugQuery); - if ($debugStmt) { - $debugStmt->bind_param("i", $userId); - $debugStmt->execute(); - $debugResult = $debugStmt->get_result(); - $debugRow = $debugResult->fetch_assoc(); - error_log("getRequests - Total demandes en DB pour user $userId: " . $debugRow['total']); - $debugStmt->close(); - } - + $requests = []; - + while ($row = $result->fetch_assoc()) { - error_log("getRequests - Traitement demande ID: " . $row['Id']); - - // Calcul des jours ouvrés $workingDays = getWorkingDays($row['DateDebut'], $row['DateFin']); - - // Mapping des types de congés pour l'affichage + $displayType = $row['TypeConge']; switch ($row['TypeConge']) { case 'Congé payé': @@ -142,19 +112,24 @@ try { $displayType = 'Congé maladie'; break; } - - // Formatage des dates pour l'affichage + $startDate = new DateTime($row['DateDebut']); $endDate = new DateTime($row['DateFin']); $submittedDate = new DateTime($row['DateDemande']); - - // Format d'affichage des dates + if ($row['DateDebut'] === $row['DateFin']) { $dateDisplay = $startDate->format('d/m/Y'); } else { $dateDisplay = $startDate->format('d/m/Y') . ' - ' . $endDate->format('d/m/Y'); } - + + // 👇 GÉNÉRATION DU LIEN VERS LE FICHIER + $fileUrl = null; + if ($row['TypeConge'] === 'Congé maladie' && !empty($row['DocumentJoint'])) { + $fileName = basename($row['DocumentJoint']); + $fileUrl = 'http://localhost/GTA/project/uploads/'. $fileName; + } + $requests[] = [ 'id' => (int)$row['Id'], 'type' => $displayType, @@ -166,23 +141,20 @@ try { 'reason' => $row['Commentaire'] ?: 'Aucun commentaire', 'submittedAt' => $row['DateDemande'], 'submittedDisplay' => $submittedDate->format('d/m/Y'), - 'validator' => $row['Validateur'] ?: null + 'validator' => $row['Validateur'] ?: null, + 'fileUrl' => $fileUrl ]; } - + $stmt->close(); - - error_log("getRequests - Demandes formatées: " . count($requests)); - error_log("getRequests - Détail des demandes: " . print_r($requests, true)); - error_log("=== FIN getRequests.php ==="); - + echo json_encode([ "success" => true, "message" => "Demandes récupérées avec succès.", "requests" => $requests, "total" => count($requests) ]); - + } catch (Exception $e) { error_log("Erreur récupération demandes : " . $e->getMessage()); echo json_encode([ @@ -192,4 +164,4 @@ try { } $conn->close(); -?> \ No newline at end of file +?> diff --git a/project/src/components/NewLeaveRequestModal.jsx b/project/src/components/NewLeaveRequestModal.jsx index ceeb631..7dbbf26 100644 --- a/project/src/components/NewLeaveRequestModal.jsx +++ b/project/src/components/NewLeaveRequestModal.jsx @@ -255,52 +255,64 @@ const NewLeaveRequestModal = ({ const handleSubmit = async (e) => { e.preventDefault(); - if (!validateForm()) return; setIsSubmitting(true); setError(''); try { - // Créer une demande pour chaque type de congé - const requests = formData.types.map(type => { - const days = formData.types.length > 1 ? (typeDistribution[type] || 0) : calculatedDays; - // Utiliser le type sélectionné dans la liste déroulante si "Autre" est coché - const finalType = type === 'Autres' ? otherLeaveType : type; - return { - EmployeeId: userId, - TypeConge: type, - DateDebut: formData.startDate, - DateFin: formData.endDate, - Commentaire: formData.reason + (formData.types.length > 1 ? ` (${days} jours ${getTypeLabel(type)})` : ''), - NumDays: days - }; - }); - - // Soumettre toutes les demandes - const responses = await Promise.all( - requests.map(requestData => - fetch('http://localhost/GTA/project/public/submitLeaveRequest.php', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(requestData), - }) - ) + const finalTypes = formData.types.map(type => + type === 'Autres' ? otherLeaveType : type ); - const results = await Promise.all(responses.map(r => r.json())); + const repartition = finalTypes.map(type => ({ + TypeConge: type, + NombreJours: formData.types.length > 1 + ? (typeDistribution[type] || 0) + : calculatedDays + })); - const allSuccessful = results.every(result => result.success); + const requestData = { + EmployeeId: userId, + DateDebut: formData.startDate, + DateFin: formData.endDate, + Commentaire: formData.reason, + NombreJours: calculatedDays, + Repartition: repartition + }; + console.log("Payload envoyé au backend :", JSON.stringify(requestData, null, 2)); - if (allSuccessful) { + const formDataToSend = new FormData(); + formDataToSend.append('data', JSON.stringify(requestData)); + + // Ajouter les fichiers + formData.medicalDocuments.forEach((file, index) => { + formDataToSend.append(`medicalDocuments[]`, file); + }); + + const response = await fetch('http://localhost/GTA/project/public/submitLeaveRequest.php', { + method: 'POST', + body: formDataToSend + }); + + + const text = await response.text(); + let result; + try { + result = JSON.parse(text); + } catch (err) { + console.error("Réponse non JSON:", text); + setError("Erreur serveur : réponse invalide."); + return; + } + + if (result.success) { onRequestSubmitted?.(); onClose(); } else { - const failedResults = results.filter(r => !r.success); - setError(`Erreur lors de la soumission : ${failedResults.map(r => r.message).join(', ')}`); + setError(result.message || 'Erreur lors de la soumission'); } + } catch (error) { console.error('Erreur:', error); setError('Erreur de connexion au serveur'); @@ -309,6 +321,7 @@ const NewLeaveRequestModal = ({ } }; + const getTypeLabel = (type) => { switch (type) { case 'CP': return 'Congés payés'; diff --git a/project/src/pages/Login.jsx b/project/src/pages/Login.jsx index 2dcde3c..50d8f70 100644 --- a/project/src/pages/Login.jsx +++ b/project/src/pages/Login.jsx @@ -89,14 +89,11 @@ const Login = () => { diff --git a/project/src/pages/Requests.jsx b/project/src/pages/Requests.jsx index edcdee7..9ae2423 100644 --- a/project/src/pages/Requests.jsx +++ b/project/src/pages/Requests.jsx @@ -1,11 +1,12 @@ 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, Search, Filter, Eye, Edit, Trash2, Menu } from 'lucide-react'; +import { Calendar as CalendarIcon, Clock, Plus, Settings, RefreshCw, Search, Filter, Eye, Edit, Trash2, Menu, X } from 'lucide-react'; import NewLeaveRequestModal from '../components/NewLeaveRequestModal'; const Requests = () => { const { user } = useAuth(); + const [sidebarOpen, setSidebarOpen] = useState(false); const [leaveCounters, setLeaveCounters] = useState({ availableCP: 0, @@ -14,14 +15,19 @@ const Requests = () => { rttInProcess: 0, absenteism: 0 }); + 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 [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [typeFilter, setTypeFilter] = useState('all'); + const [currentPage, setCurrentPage] = useState(1); const [requestsPerPage] = useState(10); @@ -32,11 +38,9 @@ const Requests = () => { } }, [user]); - // Filtrage des demandes useEffect(() => { let filtered = allRequests; - // Filtre par terme de recherche if (searchTerm) { filtered = filtered.filter(request => request.type.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -45,12 +49,10 @@ const Requests = () => { ); } - // Filtre par statut if (statusFilter !== 'all') { filtered = filtered.filter(request => request.status === statusFilter); } - // Filtre par type if (typeFilter !== 'all') { if (typeFilter === 'Autres') { const otherTypes = ['Récup', 'Congés sans solde', 'Congés pour évènement familial', 'Congé maternité', 'Congé paternité', 'Congé parental', 'Congé parental à temps partiel']; @@ -61,20 +63,18 @@ const Requests = () => { } setFilteredRequests(filtered); - setCurrentPage(1); // Reset à la première page lors du filtrage + setCurrentPage(1); }, [allRequests, searchTerm, statusFilter, typeFilter]); const fetchLeaveCounters = async () => { try { const response = await fetch(`http://localhost/GTA/project/public/getLeaveCounters.php?user_id=${user.id}`); const text = await response.text(); - console.log(' Requests - Réponse brute compteurs:', text); let data; try { data = JSON.parse(text); - } catch (parseError) { - console.error(' Requests - Réponse non-JSON:', text.substring(0, 200)); + } catch { throw new Error('Le serveur PHP ne répond pas correctement'); } @@ -84,62 +84,49 @@ const Requests = () => { throw new Error(data.message || 'Erreur lors de la récupération des compteurs'); } } catch (error) { - console.error('Erreur lors de la récupération des compteurs:', error); + console.error('Erreur compteurs:', error); } }; const fetchAllRequests = async () => { - console.log('Requests - Début fetchAllRequests pour user:', user?.id); - try { const url = `http://localhost/GTA/project/public/getRequests.php?user_id=${user.id}`; - console.log(' Requests - URL appelée:', url); - const response = await fetch(url); const text = await response.text(); - console.log(' Requests - Réponse brute:', text); - const data = JSON.parse(text); - console.log(' Requests - Données parsées:', data); if (data.success) { - console.log(' Requests - Demandes récupérées:', data.requests?.length); setAllRequests(data.requests || []); } else { throw new Error(data.message || 'Erreur lors de la récupération des demandes'); } } catch (error) { - console.error(' Requests - Erreur:', error); + console.error('Erreur requêtes:', error); } finally { setIsLoading(false); } }; 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('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', - }, + 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'); + } catch { + alert('Erreur de connexion au serveur'); } }; @@ -152,41 +139,26 @@ const Requests = () => { 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'; + case 'Refusé': + case 'Refusée': return 'bg-red-100 text-red-800'; default: return 'bg-gray-100 text-gray-800'; } }; - // Pagination + const handleViewRequest = (request) => { + setSelectedRequest(request); + }; + + const handleCloseDetails = () => { + setSelectedRequest(null); + }; + const indexOfLastRequest = currentPage * requestsPerPage; const indexOfFirstRequest = indexOfLastRequest - requestsPerPage; const currentRequests = filteredRequests.slice(indexOfFirstRequest, indexOfLastRequest); const totalPages = Math.ceil(filteredRequests.length / requestsPerPage); - const paginate = (pageNumber) => setCurrentPage(pageNumber); - const handleViewRequest = (request) => { - alert(`Détails de la demande:\n\nType: ${request.type}\nDates: ${request.dateDisplay}\nJours: ${request.days}\nStatut: ${request.status}\nMotif: ${request.reason}`); - }; - - const handleEditRequest = (request) => { - if (request.status !== 'En attente') { - alert('Seules les demandes en attente peuvent être modifiées.'); - return; - } - alert('Fonctionnalité de modification en cours de développement.'); - }; - - const handleDeleteRequest = (request) => { - if (request.status !== 'En attente') { - alert('Seules les demandes en attente peuvent être supprimées.'); - return; - } - if (confirm(`Êtes-vous sûr de vouloir supprimer cette demande de ${request.type} ?`)) { - alert('Fonctionnalité de suppression en cours de développement.'); - } - }; - if (isLoading) { return (
- Gérez toutes vos demandes de congés -
-Gérez toutes vos demandes de congés
- Ces actions affectent tous les utilisateurs du système. Utilisez avec précaution. -
-Remet à zéro tous les compteurs selon les règles
-Ouvre l'interface complète d'administration
-CP restants
-{leaveCounters.availableCP}
-jours
-RTT restants
-{leaveCounters.availableRTT}
-jours
-RTT en cours
-{leaveCounters.rttInProcess}
-en cours
-Absences
-{leaveCounters.absenteism}
-jours
-| Type | +Dates | +Jours | +Statut | +Soumis | +Actions | +
|---|---|---|---|---|---|
| {request.type} | +{request.dateDisplay} | +{request.days} | ++ + {request.status} + + | +{request.submittedDisplay} | +
+ |
+
- {filteredRequests.length === 0 && allRequests.length > 0 - ? 'Aucune demande ne correspond à vos critères' - : 'Aucune demande trouvée' - } -
-| Type | -Dates | -Durée | -Statut | -Soumis le | -Actions | -
|---|---|---|---|---|---|
| - {request.type} - | -{request.dateDisplay} | -{request.days} jour{request.days > 1 ? 's' : ''} | -- - {request.status} - - | -{request.submittedDisplay} | -
-
-
- |
-
Type
+{selectedRequest.type}
{request.dateDisplay}
-"{request.reason}"
- )} - -Dates
+{selectedRequest.dateDisplay}
Nombre de jours
+{selectedRequest.days}
+Statut
+ + {selectedRequest.status} + +Motif
+{selectedRequest.reason}
Arrêt maladie
+ +