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 (
@@ -206,12 +178,9 @@ const Requests = () => { setSidebarOpen(!sidebarOpen)} />
- {/* Mobile menu button */} + {/* Bouton mobile */}
-
@@ -222,154 +191,37 @@ const Requests = () => {

Mes Demandes de Congés

-

- Gérez toutes vos demandes de congés -

-
-
- - +

Gérez toutes vos demandes de congés

+
- {/* Admin Panel */} - {showAdminPanel && ( -
-
-

- - Administration -

- -
- -
-

⚠️ Zone d'administration

-

- Ces actions affectent tous les utilisateurs du système. Utilisez avec précaution. -

-
- -
- - - -
-
- )} - - {/* Stats Cards */} -
-
-
-
-

CP restants

-

{leaveCounters.availableCP}

-

jours

-
-
- -
-
-
- -
-
-
-

RTT restants

-

{leaveCounters.availableRTT}

-

jours

-
-
- -
-
-
- -
-
-
-

RTT en cours

-

{leaveCounters.rttInProcess}

-

en cours

-
-
- -
-
-
- -
-
-
-

Absences

-

{leaveCounters.absenteism}

-

jours

-
-
- -
-
-
-
- - {/* Filtres et Recherche */} + {/* Filtres */}
- {/* Barre de recherche */} -
-
- - setSearchTerm(e.target.value)} - className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm" - /> -
+
+ + setSearchTerm(e.target.value)} + className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 text-sm" + />
- {/* Filtres */}
setTypeFilter(e.target.value)} - className="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm" + className="px-3 py-2 border border-gray-300 rounded-lg text-sm" > - {/* Nouvelle option */} +
- {/* Statistiques des résultats */}
{filteredRequests.length} demande{filteredRequests.length > 1 ? 's' : ''} trouvée{filteredRequests.length > 1 ? 's' : ''} {allRequests.length !== filteredRequests.length && ` sur ${allRequests.length} au total`}
- {/* Liste des Demandes */} -
-
-
-

- Toutes mes demandes -

-
- - Page {currentPage} sur {totalPages || 1} + {/* Tableau + Détails */} +
+ {/* Tableau des demandes */} +
+
+
+

Toutes mes demandes

+
+ + Page {currentPage} sur {totalPages || 1} +
+ +
+ {currentRequests.length === 0 ? ( +
Aucune demande à afficher.
+ ) : ( +
+ + + + + + + + + + + + + {currentRequests.map(request => ( + + + + + + + + + ))} + +
TypeDatesJoursStatutSoumisActions
{request.type}{request.dateDisplay}{request.days} + + {request.status} + + {request.submittedDisplay} + +
+
+ )} +
-
- {currentRequests.length === 0 ? ( -
-
- -
-

- {filteredRequests.length === 0 && allRequests.length > 0 - ? 'Aucune demande ne correspond à vos critères' - : 'Aucune demande trouvée' - } -

-
- ) : ( - <> - {/* Version Desktop */} -
-
- - - - - - - - - - - - - {currentRequests.map((request) => ( - - - - - - - - - ))} - -
TypeDatesDuréeStatutSoumis leActions
- {request.type} - {request.dateDisplay}{request.days} jour{request.days > 1 ? 's' : ''} - - {request.status} - - {request.submittedDisplay} -
- - {request.status === 'En attente' && ( - <> - - - - )} -
-
-
+ +
+
+

Type

+

{selectedRequest.type}

- - {/* Version Mobile */} -
- {currentRequests.map((request) => ( -
-
-
-

{request.type}

-

{request.dateDisplay}

-
- - {request.status} - -
- -
- {request.days} jour{request.days > 1 ? 's' : ''} - Soumis le {request.submittedDisplay} -
- - {request.reason && request.reason !== 'Aucun commentaire' && ( -

"{request.reason}"

- )} - -
- - {request.status === 'En attente' && ( - <> - - - - )} -
-
- ))} +
+

Dates

+

{selectedRequest.dateDisplay}

- - {/* Pagination */} - {totalPages > 1 && ( -
-
- Affichage de {indexOfFirstRequest + 1} à {Math.min(indexOfLastRequest, filteredRequests.length)} sur {filteredRequests.length} demandes -
- -
- - - {[...Array(totalPages)].map((_, index) => { - const pageNumber = index + 1; - if ( - pageNumber === 1 || - pageNumber === totalPages || - (pageNumber >= currentPage - 1 && pageNumber <= currentPage + 1) - ) { - return ( - - ); - } else if ( - pageNumber === currentPage - 2 || - pageNumber === currentPage + 2 - ) { - return ...; - } - return null; - })} - - -
+
+

Nombre de jours

+

{selectedRequest.days}

+
+
+

Statut

+ + {selectedRequest.status} + +
+ {selectedRequest.reason && ( +
+

Motif

+

{selectedRequest.reason}

)} - - )} -
+ + {selectedRequest.fileUrl && ( + +
+

Arrêt maladie

+ + + Voir le fichier + +
+ )} + +
+
+ )}
- {/* Modal nouvelle demande */} + {/* Modal */} {showNewRequestModal && ( setShowNewRequestModal(false)} @@ -627,4 +379,4 @@ const Requests = () => { ); }; -export default Requests; \ No newline at end of file +export default Requests; diff --git a/project/uploads/doc_6895edf8d6538.jpg b/project/uploads/doc_6895edf8d6538.jpg new file mode 100644 index 0000000..70ff4fd Binary files /dev/null and b/project/uploads/doc_6895edf8d6538.jpg differ