modification concernant l'affichage de type de congés dans la pagedemande+Affichage du service du collabrateur et son rôle+ version mobile de la page demande
This commit is contained in:
@@ -60,7 +60,7 @@ $stmt = $conn->prepare($sql);
|
|||||||
$stmt->bind_param("i", $managerId);
|
$stmt->bind_param("i", $managerId);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
// ✅ Manquant dans ton code
|
// Manquant dans ton code
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
$requests = [];
|
$requests = [];
|
||||||
|
|||||||
@@ -1,277 +1,147 @@
|
|||||||
<?php
|
<?php
|
||||||
// Récupération des compteurs de congés avec gestion des exercices
|
|
||||||
// Exercice CP: 01/06 au 31/05 | Exercice RTT: 01/01 au 31/12
|
|
||||||
|
|
||||||
header("Access-Control-Allow-Origin: *");
|
header("Access-Control-Allow-Origin: *");
|
||||||
header("Access-Control-Allow-Methods: GET, OPTIONS");
|
header("Access-Control-Allow-Methods: GET, OPTIONS");
|
||||||
header("Access-Control-Allow-Headers: Content-Type");
|
header("Access-Control-Allow-Headers: Content-Type");
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { http_response_code(200); exit(); }
|
||||||
// Gère la requête OPTIONS (pré-vol CORS)
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
|
||||||
http_response_code(200);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
|
ini_set('display_errors',1); ini_set('display_startup_errors',1); error_reporting(E_ALL);
|
||||||
|
|
||||||
// Log des erreurs pour debug
|
$host="192.168.0.4"; $dbname="DemandeConge"; $username="wpuser"; $password="-2b/)ru5/Bi8P[7_";
|
||||||
ini_set('display_errors', 1);
|
$conn = new mysqli($host,$username,$password,$dbname);
|
||||||
ini_set('display_startup_errors', 1);
|
if ($conn->connect_error) { echo json_encode(["success"=>false,"message"=>"Erreur DB: ".$conn->connect_error]); exit(); }
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
$host = "192.168.0.4";
|
$userId = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
|
||||||
$dbname = "DemandeConge";
|
if (!$userId) { echo json_encode(["success"=>false,"message"=>"user_id manquant"]); $conn->close(); exit(); }
|
||||||
$username = "wpuser";
|
|
||||||
$password = "-2b/)ru5/Bi8P[7_";
|
|
||||||
|
|
||||||
// IMPORTANT: Changer ces paramètres pour votre configuration locale
|
function getLeaveYear($date=null){ $d=$date?new DateTime($date):new DateTime(); $y=(int)$d->format('Y'); return ((int)$d->format('m')<6)?$y-1:$y;}
|
||||||
// $host = "localhost";
|
function getRTTYear($date=null){ $d=$date?new DateTime($date):new DateTime(); return (int)$d->format('Y');}
|
||||||
// $username = "root";
|
function getWorkingDays($start,$end){ $c=new DateTime($start); $e=new DateTime($end); $days=0; while($c<=$e){ $n=(int)$c->format('N'); if($n<6) $days++; $c->modify('+1 day'); } return $days;}
|
||||||
// $password = "";
|
|
||||||
|
|
||||||
// Crée une nouvelle connexion à la base de données
|
// Récupérer les typeIds utiles
|
||||||
$conn = new mysqli($host, $username, $password, $dbname);
|
function getTypeId($conn,$name){ $s=$conn->prepare("SELECT Id FROM TypeConge WHERE Nom=?"); $s->bind_param("s",$name); $s->execute(); $res=$s->get_result(); $id=null; if($r=$res->fetch_assoc()) $id=(int)$r['Id']; $s->close(); return $id; }
|
||||||
|
$cpTypeId = getTypeId($conn,'Congé payé');
|
||||||
|
$rttTypeId = getTypeId($conn,'RTT');
|
||||||
|
$absTypeId = getTypeId($conn,'Congé maladie');
|
||||||
|
|
||||||
// Vérifie la connexion
|
$leaveYear = getLeaveYear();
|
||||||
if ($conn->connect_error) {
|
$rttYear = getRTTYear();
|
||||||
error_log("Erreur connexion DB getLeaveCounters: " . $conn->connect_error);
|
$currentDate = date('Y-m-d');
|
||||||
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données : " . $conn->connect_error]);
|
|
||||||
exit();
|
// --- Soldes initiaux (CompteurConges) restent inchangés ---
|
||||||
|
$cpSolde = 0; $rttSolde = 0; $absSolde = 0;
|
||||||
|
if ($cpTypeId !== null) {
|
||||||
|
$q="SELECT Solde FROM CompteurConges WHERE EmployeeId=? AND TypeCongeId=? AND Annee=?";
|
||||||
|
$s=$conn->prepare($q); $s->bind_param("iii",$userId,$cpTypeId,$leaveYear); $s->execute(); $res=$s->get_result(); if($r=$res->fetch_assoc()) $cpSolde=$r['Solde']; $s->close();
|
||||||
|
}
|
||||||
|
if ($rttTypeId !== null) {
|
||||||
|
$q="SELECT Solde FROM CompteurConges WHERE EmployeeId=? AND TypeCongeId=? AND Annee=?";
|
||||||
|
$s=$conn->prepare($q); $s->bind_param("iii",$userId,$rttTypeId,$rttYear); $s->execute(); $res=$s->get_result(); if($r=$res->fetch_assoc()) $rttSolde=$r['Solde']; $s->close();
|
||||||
|
}
|
||||||
|
if ($absTypeId !== null) {
|
||||||
|
$q="SELECT Solde FROM CompteurConges WHERE EmployeeId=? AND TypeCongeId=? AND Annee=?";
|
||||||
|
$s=$conn->prepare($q); $s->bind_param("iii",$userId,$absTypeId,$rttYear); $s->execute(); $res=$s->get_result(); if($r=$res->fetch_assoc()) $absSolde=$r['Solde']; $s->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupère l'ID utilisateur depuis les paramètres de requête GET
|
// --- Calcul CP in process : priorité DemandeCongeType, fallback = working days on DemandeConge ---
|
||||||
$userId = $_GET['user_id'] ?? null;
|
|
||||||
|
|
||||||
error_log("=== DEBUT getLeaveCounters.php ===");
|
|
||||||
error_log("getLeaveCounters - user_id reçu: " . ($userId ?? 'NULL'));
|
|
||||||
error_log("getLeaveCounters - Toutes les variables GET: " . print_r($_GET, true));
|
|
||||||
|
|
||||||
if ($userId === null) {
|
|
||||||
error_log("getLeaveCounters - user_id manquant");
|
|
||||||
echo json_encode(["success" => false, "message" => "ID utilisateur manquant."]);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
// Fonction pour déterminer l'exercice des congés payés (01/06 au 31/05)
|
|
||||||
function getLeaveYear($date = null) {
|
|
||||||
if ($date === null) {
|
|
||||||
$date = new DateTime();
|
|
||||||
} else {
|
|
||||||
$date = new DateTime($date);
|
|
||||||
}
|
|
||||||
|
|
||||||
$currentYear = (int)$date->format('Y');
|
|
||||||
$currentMonth = (int)$date->format('m');
|
|
||||||
|
|
||||||
// Si on est avant le 1er juin, l'exercice a commencé l'année précédente
|
|
||||||
if ($currentMonth < 6) {
|
|
||||||
return $currentYear - 1;
|
|
||||||
}
|
|
||||||
// Si on est le 1er juin ou après, l'exercice a commencé cette année
|
|
||||||
return $currentYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonction pour déterminer l'année RTT (01/01 au 31/12)
|
|
||||||
function getRTTYear($date = null) {
|
|
||||||
if ($date === null) {
|
|
||||||
$date = new DateTime();
|
|
||||||
} else {
|
|
||||||
$date = new DateTime($date);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (int)$date->format('Y');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Récupère l'ID utilisateur depuis les paramètres de requête GET
|
|
||||||
$userId = $_GET['user_id'] ?? null;
|
|
||||||
|
|
||||||
if ($userId === null) {
|
|
||||||
echo json_encode(["success" => false, "message" => "ID utilisateur manquant."]);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calcul des exercices selon les règles de gestion
|
|
||||||
$leaveYear = getLeaveYear(); // Exercice CP (01/06 au 31/05)
|
|
||||||
$rttYear = getRTTYear(); // Exercice RTT (01/01 au 31/12)
|
|
||||||
$currentDate = date('Y-m-d'); // Date actuelle pour les filtres de demandes
|
|
||||||
|
|
||||||
// Variables pour les soldes disponibles
|
|
||||||
$cpSolde = 0;
|
|
||||||
$rttSolde = 0;
|
|
||||||
$absSolde = 0;
|
|
||||||
|
|
||||||
// Variables pour les demandes en cours/validées
|
|
||||||
$cpInProcess = 0;
|
$cpInProcess = 0;
|
||||||
$rttInProcess = 0;
|
if ($cpTypeId !== null) {
|
||||||
$absenteism = 0;
|
$sql = "
|
||||||
|
SELECT dc.Id, dc.DateDebut, dc.DateFin, dct.NombreJours
|
||||||
// --- FONCTION UTILITAIRE POUR CALCULER LES JOURS OUVRÉS (hors week-ends) ---
|
FROM DemandeConge dc
|
||||||
function getWorkingDays($startDate, $endDate) {
|
LEFT JOIN DemandeCongeType dct
|
||||||
$workingDays = 0;
|
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
||||||
$current = new DateTime($startDate);
|
WHERE dc.EmployeeId = ?
|
||||||
$end = new DateTime($endDate);
|
AND dc.Statut IN ('En attente','Validée')
|
||||||
|
AND dc.DateFin >= ?
|
||||||
while ($current <= $end) {
|
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
||||||
$dayOfWeek = (int)$current->format('N'); // 1 (pour Lundi) à 7 (pour Dimanche)
|
";
|
||||||
if ($dayOfWeek < 6) { // Si ce n'est ni Samedi (6) ni Dimanche (7)
|
$s = $conn->prepare($sql);
|
||||||
$workingDays++;
|
$s->bind_param("iiss", $cpTypeId, $userId, $currentDate, $cpTypeId);
|
||||||
|
$s->execute();
|
||||||
|
$res = $s->get_result();
|
||||||
|
while ($r = $res->fetch_assoc()) {
|
||||||
|
if ($r['NombreJours'] !== null) {
|
||||||
|
$cpInProcess += (float)$r['NombreJours'];
|
||||||
|
} else {
|
||||||
|
$cpInProcess += getWorkingDays($r['DateDebut'], $r['DateFin']);
|
||||||
}
|
}
|
||||||
$current->modify('+1 day');
|
|
||||||
}
|
}
|
||||||
return $workingDays;
|
$s->close();
|
||||||
}
|
}
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
|
// --- Calcul RTT in process (même logique) ---
|
||||||
// --- Récupération du Solde de Congé Payé (CP) ---
|
$rttInProcess = 0;
|
||||||
$queryCPSolde = "SELECT cc.Solde FROM CompteurConges cc
|
if ($rttTypeId !== null) {
|
||||||
JOIN TypeConge tc ON cc.TypeCongeId = tc.Id
|
$sql = "
|
||||||
WHERE cc.EmployeeId = ? AND tc.Nom = 'Congé payé' AND cc.Annee = ?";
|
SELECT dc.Id, dc.DateDebut, dc.DateFin, dct.NombreJours
|
||||||
$stmtCPSolde = $conn->prepare($queryCPSolde);
|
FROM DemandeConge dc
|
||||||
if ($stmtCPSolde === false) {
|
LEFT JOIN DemandeCongeType dct
|
||||||
error_log("Erreur de préparation de la requête CP Solde : " . $conn->error);
|
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
||||||
} else {
|
WHERE dc.EmployeeId = ?
|
||||||
$stmtCPSolde->bind_param("ii", $userId, $leaveYear);
|
AND dc.Statut IN ('En attente','Validée')
|
||||||
$stmtCPSolde->execute();
|
AND dc.DateFin >= ?
|
||||||
$resultCPSolde = $stmtCPSolde->get_result();
|
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
||||||
if ($rowCPSolde = $resultCPSolde->fetch_assoc()) {
|
";
|
||||||
$cpSolde = $rowCPSolde['Solde'];
|
$s = $conn->prepare($sql);
|
||||||
|
$s->bind_param("iiss", $rttTypeId, $userId, $currentDate, $rttTypeId);
|
||||||
|
$s->execute();
|
||||||
|
$res = $s->get_result();
|
||||||
|
while ($r = $res->fetch_assoc()) {
|
||||||
|
if ($r['NombreJours'] !== null) {
|
||||||
|
$rttInProcess += (float)$r['NombreJours'];
|
||||||
|
} else {
|
||||||
|
$rttInProcess += getWorkingDays($r['DateDebut'], $r['DateFin']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$stmtCPSolde->close();
|
$s->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Récupération du Solde de RTT ---
|
// --- Calcul absenteisme (validation) : priorité DemandeCongeType, fallback = DATEDIFF+1 ---
|
||||||
$queryRTTSolde = "SELECT cc.Solde FROM CompteurConges cc
|
$absenteism = 0;
|
||||||
JOIN TypeConge tc ON tc.Id = cc.TypeCongeId
|
if ($absTypeId !== null) {
|
||||||
WHERE cc.EmployeeId = ? AND tc.Nom = 'RTT' AND cc.Annee = ?";
|
$sql = "
|
||||||
$stmtRTTSolde = $conn->prepare($queryRTTSolde);
|
SELECT dc.DateDebut, dc.DateFin, dct.NombreJours
|
||||||
if ($stmtRTTSolde === false) {
|
FROM DemandeConge dc
|
||||||
error_log("Erreur de préparation de la requête RTT Solde : " . $conn->error);
|
LEFT JOIN DemandeCongeType dct
|
||||||
} else {
|
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
||||||
$stmtRTTSolde->bind_param("ii", $userId, $rttYear);
|
WHERE dc.EmployeeId = ?
|
||||||
$stmtRTTSolde->execute();
|
AND dc.Statut = 'Validée'
|
||||||
$resultRTTSolde = $stmtRTTSolde->get_result();
|
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
||||||
if ($rowRTTSolde = $resultRTTSolde->fetch_assoc()) {
|
";
|
||||||
$rttSolde = $rowRTTSolde['Solde'];
|
$s = $conn->prepare($sql);
|
||||||
|
$s->bind_param("iii", $absTypeId, $userId, $absTypeId);
|
||||||
|
$s->execute();
|
||||||
|
$res = $s->get_result();
|
||||||
|
while ($r = $res->fetch_assoc()) {
|
||||||
|
if ($r['NombreJours'] !== null) {
|
||||||
|
$absenteism += (float)$r['NombreJours'];
|
||||||
|
} else {
|
||||||
|
// fallback : DATEDIFF + 1
|
||||||
|
$d1 = new DateTime($r['DateDebut']); $d2 = new DateTime($r['DateFin']);
|
||||||
|
$absenteism += ($d2->diff($d1)->days + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$stmtRTTSolde->close();
|
$s->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Récupération du Solde de Congé Maladie (ABS) ---
|
$availableCPCalculated = max(0, $cpSolde - $cpInProcess);
|
||||||
$queryABSSolde = "SELECT cc.Solde FROM CompteurConges cc
|
$availableRTTCalculated = max(0, $rttSolde - $rttInProcess);
|
||||||
JOIN TypeConge tc ON tc.Id = cc.TypeCongeId
|
|
||||||
WHERE cc.EmployeeId = ? AND tc.Nom = 'Congé maladie' AND cc.Annee = ?";
|
|
||||||
$stmtABSSolde = $conn->prepare($queryABSSolde);
|
|
||||||
if ($stmtABSSolde === false) {
|
|
||||||
error_log("Erreur de préparation de la requête ABS Solde : " . $conn->error);
|
|
||||||
} else {
|
|
||||||
$stmtABSSolde->bind_param("ii", $userId, $rttYear);
|
|
||||||
$stmtABSSolde->execute();
|
|
||||||
$resultABSSolde = $stmtABSSolde->get_result();
|
|
||||||
if ($rowABSSolde = $resultABSSolde->fetch_assoc()) {
|
|
||||||
$absSolde = $rowABSSolde['Solde'];
|
|
||||||
}
|
|
||||||
$stmtABSSolde->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- Calcul des Congés Payés (CP) en cours (demandes 'En attente' ou 'Validée' dont la fin est >= date actuelle) ---
|
|
||||||
// Cette requête sélectionne les dates pour le calcul en PHP
|
|
||||||
$queryCPInProcessDates = "SELECT dc.DateDebut, dc.DateFin FROM DemandeConge dc
|
|
||||||
JOIN TypeConge tc ON dc.TypeCongeId = tc.Id
|
|
||||||
WHERE dc.EmployeeId = ?
|
|
||||||
AND tc.Nom = 'Congé payé'
|
|
||||||
AND dc.Statut IN ('En attente', 'Validée')
|
|
||||||
AND dc.DateFin >= ?";
|
|
||||||
$stmtCPInProcessDates = $conn->prepare($queryCPInProcessDates);
|
|
||||||
if ($stmtCPInProcessDates === false) {
|
|
||||||
error_log("Erreur de préparation de la requête CP en cours dates : " . $conn->error);
|
|
||||||
} else {
|
|
||||||
$stmtCPInProcessDates->bind_param("is", $userId, $currentDate);
|
|
||||||
$stmtCPInProcessDates->execute();
|
|
||||||
$resultCPInProcessDates = $stmtCPInProcessDates->get_result();
|
|
||||||
while ($row = $resultCPInProcessDates->fetch_assoc()) {
|
|
||||||
$cpInProcess += getWorkingDays($row['DateDebut'], $row['DateFin']);
|
|
||||||
}
|
|
||||||
$stmtCPInProcessDates->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Calcul des RTT en cours (mêmes critères que CP, mais pour RTT) ---
|
|
||||||
$queryRTTInProcessDates = "SELECT dc.DateDebut, dc.DateFin FROM DemandeConge dc
|
|
||||||
JOIN TypeConge tc ON dc.TypeCongeId = tc.Id
|
|
||||||
WHERE dc.EmployeeId = ?
|
|
||||||
AND tc.Nom = 'RTT'
|
|
||||||
AND dc.Statut IN ('En attente', 'Validée')
|
|
||||||
AND dc.DateFin >= ?";
|
|
||||||
$stmtRTTInProcessDates = $conn->prepare($queryRTTInProcessDates);
|
|
||||||
if ($stmtRTTInProcessDates === false) {
|
|
||||||
error_log("Erreur de préparation de la requête RTT en cours dates : " . $conn->error);
|
|
||||||
} else {
|
|
||||||
$stmtRTTInProcessDates->bind_param("is", $userId, $currentDate);
|
|
||||||
$stmtRTTInProcessDates->execute();
|
|
||||||
$resultRTTInProcessDates = $stmtRTTInProcessDates->get_result();
|
|
||||||
while ($row = $resultRTTInProcessDates->fetch_assoc()) {
|
|
||||||
$rttInProcess += getWorkingDays($row['DateDebut'], $row['DateFin']);
|
|
||||||
}
|
|
||||||
$stmtRTTInProcessDates->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --- Calcul des jours d'absence (ABS) (somme des jours DATEDIFF à partir de DemandeConge) ---
|
|
||||||
// Note: Ici, on ne modifie pas le calcul, car l'absentéisme maladie est souvent compté sur tous les jours, y compris week-ends, pour le suivi global.
|
|
||||||
// Si vous devez exclure les week-ends pour les ABS, appliquez getWorkingDays ici aussi.
|
|
||||||
$queryABSInProcess = "SELECT SUM(DATEDIFF(dc.DateFin, dc.DateDebut) + 1) AS total_abs FROM DemandeConge dc
|
|
||||||
JOIN TypeConge tc ON dc.TypeCongeId = tc.Id
|
|
||||||
WHERE dc.EmployeeId = ?
|
|
||||||
AND tc.Nom = 'Congé maladie'
|
|
||||||
AND dc.Statut = 'Validée'";
|
|
||||||
$stmtABSInProcess = $conn->prepare($queryABSInProcess);
|
|
||||||
if ($stmtABSInProcess === false) {
|
|
||||||
error_log("Erreur de préparation de la requête ABS en cours : " . $conn->error);
|
|
||||||
} else {
|
|
||||||
$stmtABSInProcess->bind_param("i", $userId);
|
|
||||||
$stmtABSInProcess->execute();
|
|
||||||
$resultABSInProcess = $stmtABSInProcess->get_result();
|
|
||||||
if ($rowABSInProcess = $resultABSInProcess->fetch_assoc()) {
|
|
||||||
$absenteism = $rowABSInProcess['total_abs'] ?? 0;
|
|
||||||
}
|
|
||||||
$stmtABSInProcess->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Calcul des soldes disponibles réels (déduction "douce" pour l'affichage/validation frontend) ---
|
|
||||||
$availableCPCalculated = $cpSolde - $cpInProcess;
|
|
||||||
if ($availableCPCalculated < 0) {
|
|
||||||
$availableCPCalculated = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$availableRTTCalculated = $rttSolde - $rttInProcess;
|
|
||||||
if ($availableRTTCalculated < 0) {
|
|
||||||
$availableRTTCalculated = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Renvoie les compteurs sous format JSON
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => true,
|
"success" => true,
|
||||||
"message" => "Compteurs récupérés avec succès.",
|
"message" => "Compteurs récupérés avec succès.",
|
||||||
"counters" => [
|
"counters" => [
|
||||||
"availableCP" => (int)$availableCPCalculated, // CP: Solde brut - jours ouvrés en cours/validés futurs
|
"availableCP" => (int)$availableCPCalculated,
|
||||||
"availableRTT" => (int)$availableRTTCalculated, // RTT: Solde brut - jours ouvrés en cours/validés futurs
|
"availableRTT" => (int)$availableRTTCalculated,
|
||||||
"availableABS" => (int)$absSolde, // ABS: Solde brut (sans déduction des jours en cours)
|
"availableABS" => (int)$absSolde,
|
||||||
"rttInProcess" => (int)$rttInProcess, // RTT: Jours ouvrés en attente/validés futurs (pour information)
|
"rttInProcess" => (int)$rttInProcess,
|
||||||
"absenteism" => (int)$absenteism // ABS: Jours d'absence maladie validés/pris (pour information)
|
"absenteism" => (int)$absenteism
|
||||||
],
|
],
|
||||||
"debug_values" => [
|
"debug" => [
|
||||||
"initial_cp_solde" => (int)$cpSolde,
|
"cpSolde"=>$cpSolde,"cpInProcess"=>$cpInProcess,
|
||||||
"cp_en_cours" => (int)$cpInProcess,
|
"rttSolde"=>$rttSolde,"rttInProcess"=>$rttInProcess,
|
||||||
"calculated_available_cp" => (int)$availableCPCalculated,
|
"absSolde"=>$absSolde,"absenteism"=>$absenteism
|
||||||
"initial_rtt_solde" => (int)$rttSolde,
|
]
|
||||||
"rtt_en_cours" => (int)$rttInProcess,
|
|
||||||
"calculated_available_rtt" => (int)$availableRTTCalculated,
|
|
||||||
"leave_year" => $leaveYear,
|
|
||||||
"rtt_year" => $rttYear,
|
|
||||||
"current_date_php" => $currentDate,
|
|
||||||
"user_id_php" => (int)$userId
|
|
||||||
]
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$conn->close();
|
$conn->close();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
// Récupération des demandes de congés avec gestion des exercices
|
// En-têtes CORS et JSON
|
||||||
header("Access-Control-Allow-Origin: *");
|
header("Access-Control-Allow-Origin: *");
|
||||||
header("Access-Control-Allow-Methods: GET, OPTIONS");
|
header("Access-Control-Allow-Methods: GET, OPTIONS");
|
||||||
header("Access-Control-Allow-Headers: Content-Type");
|
header("Access-Control-Allow-Headers: Content-Type");
|
||||||
@@ -9,51 +9,33 @@ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json; charset=utf-8");
|
||||||
|
|
||||||
|
// Affichage des erreurs PHP (utile en dev)
|
||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1);
|
||||||
ini_set('display_startup_errors', 1);
|
ini_set('display_startup_errors', 1);
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
|
|
||||||
|
// Connexion BDD
|
||||||
$host = "192.168.0.4";
|
$host = "192.168.0.4";
|
||||||
$dbname = "DemandeConge";
|
$dbname = "DemandeConge";
|
||||||
$username = "wpuser";
|
$username = "wpuser";
|
||||||
$password = "-2b/)ru5/Bi8P[7_";
|
$password = "-2b/)ru5/Bi8P[7_";
|
||||||
|
|
||||||
$conn = new mysqli($host, $username, $password, $dbname);
|
$conn = new mysqli($host, $username, $password, $dbname);
|
||||||
|
|
||||||
if ($conn->connect_error) {
|
if ($conn->connect_error) {
|
||||||
error_log("Erreur connexion DB getRequests: " . $conn->connect_error);
|
echo json_encode(["success" => false, "message" => "Erreur connexion DB: " . $conn->connect_error]);
|
||||||
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données : " . $conn->connect_error]);
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Récup paramètre
|
||||||
$userId = $_GET['user_id'] ?? null;
|
$userId = $_GET['user_id'] ?? null;
|
||||||
|
if (!$userId) {
|
||||||
error_log("=== DEBUT getRequests.php ===");
|
echo json_encode(["success" => false, "message" => "ID utilisateur manquant"]);
|
||||||
error_log("getRequests - user_id reçu: " . ($userId ?? 'NULL'));
|
|
||||||
error_log("getRequests - Toutes les variables GET: " . print_r($_GET, true));
|
|
||||||
|
|
||||||
if ($userId === null) {
|
|
||||||
error_log("getRequests - user_id manquant");
|
|
||||||
echo json_encode(["success" => false, "message" => "ID utilisateur manquant."]);
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
$checkUserQuery = "SELECT ID, Nom, Prenom FROM Users WHERE ID = ?";
|
// Fonction jours ouvrés
|
||||||
$checkUserStmt = $conn->prepare($checkUserQuery);
|
|
||||||
if ($checkUserStmt) {
|
|
||||||
$checkUserStmt->bind_param("i", $userId);
|
|
||||||
$checkUserStmt->execute();
|
|
||||||
$userResult = $checkUserStmt->get_result();
|
|
||||||
if ($userRow = $userResult->fetch_assoc()) {
|
|
||||||
error_log("getRequests - Utilisateur trouvé: " . $userRow['Prenom'] . " " . $userRow['Nom']);
|
|
||||||
} else {
|
|
||||||
error_log("getRequests - ATTENTION: Utilisateur ID $userId non trouvé dans la table Users");
|
|
||||||
}
|
|
||||||
$checkUserStmt->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getWorkingDays($startDate, $endDate) {
|
function getWorkingDays($startDate, $endDate) {
|
||||||
$workingDays = 0;
|
$workingDays = 0;
|
||||||
$current = new DateTime($startDate);
|
$current = new DateTime($startDate);
|
||||||
@@ -69,6 +51,7 @@ function getWorkingDays($startDate, $endDate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Requête multi-types
|
||||||
$query = "
|
$query = "
|
||||||
SELECT
|
SELECT
|
||||||
dc.Id,
|
dc.Id,
|
||||||
@@ -78,17 +61,20 @@ try {
|
|||||||
dc.DateDemande,
|
dc.DateDemande,
|
||||||
dc.Commentaire,
|
dc.Commentaire,
|
||||||
dc.Validateur,
|
dc.Validateur,
|
||||||
dc.DocumentJoint, -- 👈 CHAMP AJOUTÉ ICI
|
dc.DocumentJoint,
|
||||||
tc.Nom as TypeConge
|
GROUP_CONCAT(tc.Nom ORDER BY tc.Nom SEPARATOR ', ') AS TypeConges
|
||||||
FROM DemandeConge dc
|
FROM DemandeConge dc
|
||||||
JOIN TypeConge tc ON dc.TypeCongeId = tc.Id
|
JOIN TypeConge tc ON FIND_IN_SET(tc.Id, dc.TypeCongeId)
|
||||||
WHERE dc.EmployeeId = ?
|
WHERE dc.EmployeeId = ?
|
||||||
|
GROUP BY
|
||||||
|
dc.Id, dc.DateDebut, dc.DateFin, dc.Statut, dc.DateDemande,
|
||||||
|
dc.Commentaire, dc.Validateur, dc.DocumentJoint
|
||||||
ORDER BY dc.DateDemande DESC
|
ORDER BY dc.DateDemande DESC
|
||||||
";
|
";
|
||||||
|
|
||||||
$stmt = $conn->prepare($query);
|
$stmt = $conn->prepare($query);
|
||||||
if ($stmt === false) {
|
if (!$stmt) {
|
||||||
throw new Exception("Erreur de préparation de la requête : " . $conn->error);
|
throw new Exception("Erreur préparation SQL : " . $conn->error);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->bind_param("i", $userId);
|
$stmt->bind_param("i", $userId);
|
||||||
@@ -96,43 +82,27 @@ try {
|
|||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
$requests = [];
|
$requests = [];
|
||||||
|
|
||||||
while ($row = $result->fetch_assoc()) {
|
while ($row = $result->fetch_assoc()) {
|
||||||
$workingDays = getWorkingDays($row['DateDebut'], $row['DateFin']);
|
$workingDays = getWorkingDays($row['DateDebut'], $row['DateFin']);
|
||||||
|
|
||||||
$displayType = $row['TypeConge'];
|
// Format dates
|
||||||
switch ($row['TypeConge']) {
|
|
||||||
case 'Congé payé':
|
|
||||||
$displayType = 'Congés payés';
|
|
||||||
break;
|
|
||||||
case 'RTT':
|
|
||||||
$displayType = 'RTT';
|
|
||||||
break;
|
|
||||||
case 'Congé maladie':
|
|
||||||
$displayType = 'Congé maladie';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$startDate = new DateTime($row['DateDebut']);
|
$startDate = new DateTime($row['DateDebut']);
|
||||||
$endDate = new DateTime($row['DateFin']);
|
$endDate = new DateTime($row['DateFin']);
|
||||||
$submittedDate = new DateTime($row['DateDemande']);
|
$submittedDate = new DateTime($row['DateDemande']);
|
||||||
|
|
||||||
if ($row['DateDebut'] === $row['DateFin']) {
|
$dateDisplay = ($row['DateDebut'] === $row['DateFin'])
|
||||||
$dateDisplay = $startDate->format('d/m/Y');
|
? $startDate->format('d/m/Y')
|
||||||
} else {
|
: $startDate->format('d/m/Y') . ' - ' . $endDate->format('d/m/Y');
|
||||||
$dateDisplay = $startDate->format('d/m/Y') . ' - ' . $endDate->format('d/m/Y');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 👇 GÉNÉRATION DU LIEN VERS LE FICHIER
|
// Lien fichier si congé maladie
|
||||||
$fileUrl = null;
|
$fileUrl = null;
|
||||||
if ($row['TypeConge'] === 'Congé maladie' && !empty($row['DocumentJoint'])) {
|
if (strpos($row['TypeConges'], 'Congé maladie') !== false && !empty($row['DocumentJoint'])) {
|
||||||
$fileName = basename($row['DocumentJoint']);
|
$fileUrl = 'http://localhost/GTA/project/uploads/' . basename($row['DocumentJoint']);
|
||||||
$fileUrl = 'http://localhost/GTA/project/uploads/'. $fileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$requests[] = [
|
$requests[] = [
|
||||||
'id' => (int)$row['Id'],
|
'id' => (int)$row['Id'],
|
||||||
'type' => $displayType,
|
'type' => $row['TypeConges'], // ex: "Congé payé, RTT"
|
||||||
'startDate' => $row['DateDebut'],
|
'startDate' => $row['DateDebut'],
|
||||||
'endDate' => $row['DateFin'],
|
'endDate' => $row['DateFin'],
|
||||||
'dateDisplay' => $dateDisplay,
|
'dateDisplay' => $dateDisplay,
|
||||||
@@ -146,22 +116,18 @@ try {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->close();
|
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => true,
|
"success" => true,
|
||||||
"message" => "Demandes récupérées avec succès.",
|
"message" => "Demandes récupérées avec succès",
|
||||||
"requests" => $requests,
|
"requests" => $requests,
|
||||||
"total" => count($requests)
|
"total" => count($requests)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Erreur récupération demandes : " . $e->getMessage());
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => false,
|
"success" => false,
|
||||||
"message" => "Erreur lors de la récupération des demandes : " . $e->getMessage()
|
"message" => "Erreur: " . $e->getMessage()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$conn->close();
|
$conn->close();
|
||||||
?>
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ header("Access-Control-Allow-Origin: *");
|
|||||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||||
header("Access-Control-Allow-Headers: Content-Type");
|
header("Access-Control-Allow-Headers: Content-Type");
|
||||||
|
|
||||||
// Gère la requête OPTIONS (pré-vol CORS)
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
exit();
|
exit();
|
||||||
@@ -16,42 +15,44 @@ $dbname = "DemandeConge";
|
|||||||
$username = "wpuser";
|
$username = "wpuser";
|
||||||
$password = "-2b/)ru5/Bi8P[7_";
|
$password = "-2b/)ru5/Bi8P[7_";
|
||||||
|
|
||||||
// Crée une nouvelle connexion à la base de données
|
|
||||||
$conn = new mysqli($host, $username, $password, $dbname);
|
$conn = new mysqli($host, $username, $password, $dbname);
|
||||||
|
|
||||||
// Vérifie la connexion
|
|
||||||
if ($conn->connect_error) {
|
if ($conn->connect_error) {
|
||||||
// En cas d'erreur de connexion, renvoie un JSON d'échec
|
|
||||||
die(json_encode(["success" => false, "message" => "Erreur de connexion à la base de données : " . $conn->connect_error]));
|
die(json_encode(["success" => false, "message" => "Erreur de connexion à la base de données : " . $conn->connect_error]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupère les données JSON envoyées via la requête POST
|
|
||||||
$data = json_decode(file_get_contents('php://input'), true);
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
$email = $data['email'] ?? '';
|
$email = $data['email'] ?? '';
|
||||||
$mot_de_passe = $data['mot_de_passe'] ?? '';
|
$mot_de_passe = $data['mot_de_passe'] ?? '';
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
SELECT
|
||||||
|
u.ID,
|
||||||
|
u.Prenom,
|
||||||
|
u.Nom,
|
||||||
|
u.Email,
|
||||||
|
u.Role,
|
||||||
|
u.ServiceId,
|
||||||
|
s.Nom AS ServiceNom
|
||||||
|
FROM Users u
|
||||||
|
LEFT JOIN Services s ON u.ServiceId = s.Id
|
||||||
|
WHERE u.Email = ? AND u.MDP = ?
|
||||||
|
";
|
||||||
|
|
||||||
$query = "SELECT ID, Prenom, Nom, Email, Role FROM Users WHERE Email = ? AND MDP = ?";
|
|
||||||
$stmt = $conn->prepare($query);
|
$stmt = $conn->prepare($query);
|
||||||
|
|
||||||
// Vérifie si la préparation de la requête a réussi
|
|
||||||
if ($stmt === false) {
|
if ($stmt === false) {
|
||||||
die(json_encode(["success" => false, "message" => "Erreur de préparation de la requête : " . $conn->error]));
|
die(json_encode(["success" => false, "message" => "Erreur de préparation de la requête : " . $conn->error]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lie les paramètres (ss = string, string pour email et mot_de_passe)
|
|
||||||
$stmt->bind_param("ss", $email, $mot_de_passe);
|
$stmt->bind_param("ss", $email, $mot_de_passe);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
// Récupère le résultat de la requête
|
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
// Vérifie si un utilisateur correspondant a été trouvé
|
|
||||||
if ($result->num_rows === 1) {
|
if ($result->num_rows === 1) {
|
||||||
// Récupère la ligne de l'utilisateur sous forme de tableau associatif
|
|
||||||
$user = $result->fetch_assoc();
|
$user = $result->fetch_assoc();
|
||||||
|
|
||||||
// Renvoie une réponse JSON de succès avec les données de l'utilisateur
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => true,
|
"success" => true,
|
||||||
"message" => "Connexion réussie.",
|
"message" => "Connexion réussie.",
|
||||||
@@ -60,15 +61,14 @@ if ($result->num_rows === 1) {
|
|||||||
"prenom" => $user['Prenom'],
|
"prenom" => $user['Prenom'],
|
||||||
"nom" => $user['Nom'],
|
"nom" => $user['Nom'],
|
||||||
"email" => $user['Email'],
|
"email" => $user['Email'],
|
||||||
"role" => $user['Role']
|
"role" => $user['Role'],
|
||||||
|
"service" => $user['ServiceNom'] ?? 'Non défini'
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
// Renvoie une réponse JSON d'échec si les identifiants sont incorrects
|
|
||||||
echo json_encode(["success" => false, "message" => "Identifiants incorrects."]);
|
echo json_encode(["success" => false, "message" => "Identifiants incorrects."]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ferme la connexion à la base de données
|
|
||||||
$stmt->close();
|
$stmt->close();
|
||||||
$conn->close();
|
$conn->close();
|
||||||
?>
|
?>
|
||||||
@@ -1,181 +1,100 @@
|
|||||||
<?php
|
<?php
|
||||||
// Active l'affichage des erreurs pour le dev
|
// (headers, connexion, lecture FormData ou JSON — pareil que précédemment)
|
||||||
ini_set('display_errors', 1);
|
ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL);
|
||||||
ini_set('display_startup_errors', 1);
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
|
|
||||||
header("Access-Control-Allow-Origin: *");
|
header("Access-Control-Allow-Origin: *");
|
||||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
||||||
header("Access-Control-Allow-Headers: Content-Type");
|
header("Access-Control-Allow-Headers: Content-Type");
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit(); }
|
||||||
// Gère le pré-vol CORS
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
|
||||||
http_response_code(200);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
// --- Paramètres de connexion ---
|
$host="192.168.0.4"; $dbname="DemandeConge"; $username="wpuser"; $password="-2b/)ru5/Bi8P[7_";
|
||||||
$host = "192.168.0.4";
|
$conn = new mysqli($host,$username,$password,$dbname);
|
||||||
$dbname = "DemandeConge";
|
if ($conn->connect_error) { echo json_encode(["success"=>false,"message"=>"Erreur DB: ".$conn->connect_error]); exit(); }
|
||||||
$username = "wpuser";
|
|
||||||
$password = "-2b/)ru5/Bi8P[7_";
|
|
||||||
|
|
||||||
// Connexion
|
// Lecture JSON (support FormData via $_POST['data'])
|
||||||
$conn = new mysqli($host, $username, $password, $dbname);
|
if (isset($_POST['data'])) {
|
||||||
if ($conn->connect_error) {
|
$data = json_decode($_POST['data'], true);
|
||||||
error_log("Erreur connexion DB submitLeaveRequest: " . $conn->connect_error);
|
} else {
|
||||||
echo json_encode([
|
$input = file_get_contents('php://input');
|
||||||
"success" => false,
|
$data = json_decode($input, true);
|
||||||
"message" => "Erreur de connexion DB : " . $conn->connect_error
|
}
|
||||||
]);
|
if ($data === null) {
|
||||||
exit();
|
echo json_encode(["success"=>false,"message"=>"JSON invalide"]); $conn->close(); exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lecture du JSON envoyé
|
// Vérifs minimales
|
||||||
$input = file_get_contents('php://input');
|
if (!isset($data['EmployeeId'],$data['DateDebut'],$data['DateFin'],$data['Repartition'],$data['NombreJours'])) {
|
||||||
error_log("submitLeaveRequest - Input reçu: " . $input);
|
echo json_encode(["success"=>false,"message"=>"Données manquantes"]); $conn->close(); exit();
|
||||||
|
|
||||||
$data = json_decode($input, true);
|
|
||||||
|
|
||||||
if (!isset(
|
|
||||||
$data['EmployeeId'],
|
|
||||||
$data['TypeConge'],
|
|
||||||
$data['DateDebut'],
|
|
||||||
$data['DateFin'],
|
|
||||||
$data['NumDays']
|
|
||||||
)) {
|
|
||||||
error_log("submitLeaveRequest - Données manquantes: " . print_r($data, true));
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => "Données manquantes pour la demande de congé."
|
|
||||||
]);
|
|
||||||
exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Récupération des champs
|
$employeeId = (int)$data['EmployeeId'];
|
||||||
$employeeId = (int) $data['EmployeeId'];
|
$dateDebut = $data['DateDebut'];
|
||||||
$typeCongeNom= $data['TypeConge'];
|
$dateFin = $data['DateFin'];
|
||||||
$dateDebut = $data['DateDebut'];
|
$commentaire= $data['Commentaire'] ?? '';
|
||||||
$dateFin = $data['DateFin'];
|
$numDays = (float)$data['NombreJours'];
|
||||||
$commentaire = $data['Commentaire'] ?? '';
|
|
||||||
$numDays = (int) $data['NumDays'];
|
|
||||||
|
|
||||||
error_log("submitLeaveRequest - Données parsées: EmployeeId=$employeeId, Type=$typeCongeNom, Début=$dateDebut, Fin=$dateFin");
|
|
||||||
|
|
||||||
$statut = 'En attente';
|
$statut = 'En attente';
|
||||||
$validateur = null;
|
$currentDate= date('Y-m-d H:i:s');
|
||||||
$currentDate= date('Y-m-d H:i:s'); // date complète pour DateDemande
|
|
||||||
|
|
||||||
// Mapping frontend → DB
|
// 1) Construire la liste d'IDs pour TypeCongeId (CSV) (compatibilité)
|
||||||
switch ($typeCongeNom) {
|
$typeIds = [];
|
||||||
case 'CP': $dbTypeCongeName = 'Congé payé'; break;
|
foreach ($data['Repartition'] as $rep) {
|
||||||
case 'RTT': $dbTypeCongeName = 'RTT'; break;
|
$code = $rep['TypeConge']; // CP, RTT, ABS ou texte libre
|
||||||
case 'ABS': $dbTypeCongeName = 'Congé maladie'; break;
|
switch ($code) {
|
||||||
default:
|
case 'CP': $name = 'Congé payé'; break;
|
||||||
error_log("submitLeaveRequest - Type de congé inconnu: $typeCongeNom");
|
case 'RTT': $name = 'RTT'; break;
|
||||||
echo json_encode([
|
case 'ABS': $name = 'Congé maladie'; break;
|
||||||
"success" => false,
|
default: $name = $code; break;
|
||||||
"message" => "Type de congé inconnu."
|
}
|
||||||
]);
|
$s = $conn->prepare("SELECT Id FROM TypeConge WHERE Nom = ?");
|
||||||
$conn->close();
|
$s->bind_param("s", $name);
|
||||||
exit();
|
$s->execute();
|
||||||
|
$res = $s->get_result();
|
||||||
|
if ($r = $res->fetch_assoc()) $typeIds[] = $r['Id'];
|
||||||
|
$s->close();
|
||||||
|
}
|
||||||
|
if (empty($typeIds)) { echo json_encode(["success"=>false,"message"=>"Aucun type valide"]); $conn->close(); exit(); }
|
||||||
|
$typeCongeIdCsv = implode(',', $typeIds);
|
||||||
|
|
||||||
|
// 2) Insertion unique dans DemandeConge
|
||||||
|
$insert = $conn->prepare("INSERT INTO DemandeConge (EmployeeId, DateDebut, DateFin, TypeCongeId, Statut, DateDemande, Commentaire, Validateur, NombreJours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
$validParam = '';
|
||||||
|
$insert->bind_param("isssssssd", $employeeId, $dateDebut, $dateFin, $typeCongeIdCsv, $statut, $currentDate, $commentaire, $validParam, $numDays);
|
||||||
|
if (!$insert->execute()) {
|
||||||
|
echo json_encode(["success"=>false,"message"=>"Erreur insert DemandeConge: ".$insert->error]);
|
||||||
|
$insert->close(); $conn->close(); exit();
|
||||||
|
}
|
||||||
|
$demandeId = $conn->insert_id;
|
||||||
|
$insert->close();
|
||||||
|
|
||||||
|
// 3) INSÉRER la répartition réelle dans DemandeCongeType (une ligne par type)
|
||||||
|
$insertType = $conn->prepare("INSERT INTO DemandeCongeType (DemandeCongeId, TypeCongeId, NombreJours) VALUES (?, ?, ?)");
|
||||||
|
if (!$insertType) {
|
||||||
|
echo json_encode(["success"=>false,"message"=>"Erreur préparation DemandeCongeType: ".$conn->error]); $conn->close(); exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
error_log("submitLeaveRequest - Type DB mappé: $dbTypeCongeName");
|
foreach ($data['Repartition'] as $rep) {
|
||||||
|
$code = $rep['TypeConge'];
|
||||||
|
$jours = (float)$rep['NombreJours'];
|
||||||
|
|
||||||
// Récupération de l'ID du type de congé
|
switch ($code) {
|
||||||
$stmt = $conn->prepare("SELECT Id FROM TypeConge WHERE Nom = ?");
|
case 'CP': $name = 'Congé payé'; break;
|
||||||
if (!$stmt) {
|
case 'RTT': $name = 'RTT'; break;
|
||||||
error_log("submitLeaveRequest - Erreur préparation requête TypeConge: " . $conn->error);
|
case 'ABS': $name = 'Congé maladie'; break;
|
||||||
echo json_encode([
|
default: $name = $code; break;
|
||||||
"success" => false,
|
}
|
||||||
"message" => "Erreur préparation requête TypeConge"
|
$s = $conn->prepare("SELECT Id FROM TypeConge WHERE Nom = ?");
|
||||||
]);
|
$s->bind_param("s", $name);
|
||||||
$conn->close();
|
$s->execute();
|
||||||
exit();
|
$res = $s->get_result();
|
||||||
|
if ($r = $res->fetch_assoc()) {
|
||||||
|
$typeId = (int)$r['Id'];
|
||||||
|
$insertType->bind_param("iid", $demandeId, $typeId, $jours); // i,i,d
|
||||||
|
$insertType->execute();
|
||||||
|
}
|
||||||
|
$s->close();
|
||||||
}
|
}
|
||||||
|
$insertType->close();
|
||||||
|
|
||||||
$stmt->bind_param("s", $dbTypeCongeName);
|
echo json_encode(["success"=>true,"message"=>"Demande soumise", "request_id"=>$demandeId]);
|
||||||
$stmt->execute();
|
|
||||||
$res = $stmt->get_result();
|
|
||||||
if ($row = $res->fetch_assoc()) {
|
|
||||||
$typeCongeId = (int) $row['Id'];
|
|
||||||
error_log("submitLeaveRequest - TypeCongeId trouvé: $typeCongeId");
|
|
||||||
} else {
|
|
||||||
error_log("submitLeaveRequest - Type de congé non trouvé en DB: $dbTypeCongeName");
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => "Type de congé non trouvé en DB : $dbTypeCongeName"
|
|
||||||
]);
|
|
||||||
$stmt->close();
|
|
||||||
$conn->close();
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
$stmt->close();
|
|
||||||
|
|
||||||
// Requête d'insertion dans DemandeConge
|
|
||||||
$query = "
|
|
||||||
INSERT INTO DemandeConge
|
|
||||||
(EmployeeId, DateDebut, DateFin, TypeCongeId, Statut, DateDemande, Commentaire, Validateur, NombreJours)
|
|
||||||
VALUES
|
|
||||||
(?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
";
|
|
||||||
|
|
||||||
error_log("submitLeaveRequest - Requête d'insertion: $query");
|
|
||||||
|
|
||||||
// Préparation de la requête
|
|
||||||
$stmt = $conn->prepare($query);
|
|
||||||
if (!$stmt) {
|
|
||||||
error_log("Erreur prepare insert : " . $conn->error);
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => "Erreur interne lors de la préparation de la requête."
|
|
||||||
]);
|
|
||||||
$conn->close();
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pour la colonne Validateur, on passe '' si null
|
|
||||||
$validParam = $validateur ?? '';
|
|
||||||
|
|
||||||
error_log("submitLeaveRequest - Paramètres bind: $employeeId, $dateDebut, $dateFin, $typeCongeId, $statut, $currentDate, $commentaire, $validParam, $numDays");
|
|
||||||
|
|
||||||
// Bind des paramètres (types : i=integer, s=string, d=decimal)
|
|
||||||
$stmt->bind_param(
|
|
||||||
"ississssi",
|
|
||||||
$employeeId, // i
|
|
||||||
$dateDebut, // s
|
|
||||||
$dateFin, // s
|
|
||||||
$typeCongeId, // i
|
|
||||||
$statut, // s
|
|
||||||
$currentDate, // s - DateDemande
|
|
||||||
$commentaire, // s
|
|
||||||
$validParam, // s
|
|
||||||
$numDays // i - NombreJours
|
|
||||||
);
|
|
||||||
|
|
||||||
// Exécution
|
|
||||||
if ($stmt->execute()) {
|
|
||||||
$insertId = $conn->insert_id;
|
|
||||||
error_log("submitLeaveRequest - Insertion réussie, ID: $insertId");
|
|
||||||
echo json_encode([
|
|
||||||
"success" => true,
|
|
||||||
"message" => "Demande de congé soumise avec succès.",
|
|
||||||
"request_id" => $insertId
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
error_log("Erreur execute insert : " . $stmt->error);
|
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => "Erreur lors de l'enregistrement : " . $stmt->error
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmt->close();
|
|
||||||
$conn->close();
|
$conn->close();
|
||||||
|
|
||||||
error_log("submitLeaveRequest - Script terminé");
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { LogOut, Calendar, Home, FileText, Building2, Menu, X, Users } from 'lucide-react';
|
import { LogOut, Calendar, Home, FileText, Building2, X, Users } from 'lucide-react';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
|
|
||||||
const Sidebar = ({ isOpen, onToggle }) => {
|
const Sidebar = ({ isOpen, onToggle }) => {
|
||||||
@@ -9,9 +9,19 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
|
|
||||||
const isActive = (path) => location.pathname === path;
|
const isActive = (path) => location.pathname === path;
|
||||||
|
|
||||||
|
const getRoleBadgeClass = (role) => {
|
||||||
|
switch (role) {
|
||||||
|
case 'Admin':
|
||||||
|
return 'bg-red-100 text-red-800';
|
||||||
|
case 'Manager':
|
||||||
|
return 'bg-green-100 text-green-800';
|
||||||
|
default:
|
||||||
|
return 'bg-blue-100 text-blue-800';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Mobile overlay */}
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
|
className="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden"
|
||||||
@@ -19,22 +29,18 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Sidebar */}
|
|
||||||
<div className={`
|
<div className={`
|
||||||
fixed inset-y-0 left-0 z-50 w-60 bg-white border-r border-gray-200 min-h-screen flex flex-col transform transition-transform duration-300 ease-in-out
|
fixed inset-y-0 left-0 z-50 w-60 bg-white border-r border-gray-200 min-h-screen flex flex-col transform transition-transform duration-300 ease-in-out
|
||||||
${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
${isOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
||||||
`}>
|
`}>
|
||||||
{/* Mobile close button */}
|
{/* Bouton fermer (mobile) */}
|
||||||
<div className="lg:hidden flex justify-end p-4">
|
<div className="lg:hidden flex justify-end p-4">
|
||||||
<button
|
<button onClick={onToggle} className="p-2 rounded-lg hover:bg-gray-100">
|
||||||
onClick={onToggle}
|
|
||||||
className="p-2 rounded-lg hover:bg-gray-100"
|
|
||||||
>
|
|
||||||
<X className="w-6 h-6" />
|
<X className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Logo Section */}
|
{/* Logo */}
|
||||||
<div className="p-6 border-b border-gray-100">
|
<div className="p-6 border-b border-gray-100">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
|
<div className="w-10 h-10 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||||
@@ -47,7 +53,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* User Info */}
|
{/* Infos utilisateur */}
|
||||||
<div className="p-4 lg:p-6 border-b border-gray-100">
|
<div className="p-4 lg:p-6 border-b border-gray-100">
|
||||||
<div className="flex flex-col items-center text-center">
|
<div className="flex flex-col items-center text-center">
|
||||||
<img
|
<img
|
||||||
@@ -56,73 +62,69 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
className="w-12 h-12 lg:w-16 lg:h-16 rounded-full object-cover mb-3"
|
className="w-12 h-12 lg:w-16 lg:h-16 rounded-full object-cover mb-3"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold text-gray-900 text-sm lg:text-base">{user?.name || "Utilisateur"}</p>
|
<p className="font-semibold text-gray-900 text-sm lg:text-base">
|
||||||
<p className="text-xs lg:text-sm text-gray-500">{user?.department || "Service"}</p>
|
{user?.name || "Utilisateur"}
|
||||||
<span className="inline-block mt-2 px-3 py-1 text-xs font-medium bg-blue-100 text-blue-800 rounded-full">
|
</p>
|
||||||
Employé
|
<p className="text-xs lg:text-sm text-gray-500">
|
||||||
</span>
|
{user?.service || "Service non défini"}
|
||||||
|
</p>
|
||||||
|
{user?.role && (
|
||||||
|
<span className={`inline-block mt-2 px-3 py-1 text-xs font-medium rounded-full ${getRoleBadgeClass(user.role)}`}>
|
||||||
|
{user.role}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<nav className="flex-1 p-4">
|
<nav className="flex-1 p-4 space-y-2">
|
||||||
<div className="space-y-2">
|
<Link
|
||||||
|
to="/dashboard"
|
||||||
|
onClick={() => window.innerWidth < 1024 && onToggle()}
|
||||||
|
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/dashboard") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Home className="w-5 h-5" />
|
||||||
|
<span className="font-medium">Dashboard</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to="/demandes"
|
||||||
|
onClick={() => window.innerWidth < 1024 && onToggle()}
|
||||||
|
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/demandes") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<FileText className="w-5 h-5" />
|
||||||
|
<span className="font-medium">Demandes</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
to="/calendrier"
|
||||||
|
onClick={() => window.innerWidth < 1024 && onToggle()}
|
||||||
|
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/calendrier") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Calendar className="w-5 h-5" />
|
||||||
|
<span className="font-medium">Calendrier</span>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{(user?.role === 'Manager' || user?.role === 'Admin' || user?.role === 'Employe') && (
|
||||||
<Link
|
<Link
|
||||||
to="/dashboard"
|
to="/manager"
|
||||||
onClick={() => window.innerWidth < 1024 && onToggle()}
|
onClick={() => window.innerWidth < 1024 && onToggle()}
|
||||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/dashboard")
|
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/manager") ? "bg-blue-50 text-blue-700 border-r-2 border-blue-700" : "text-gray-700 hover:bg-gray-50"
|
||||||
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
|
|
||||||
: "text-gray-700 hover:bg-gray-50"
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Home className="w-5 h-5" />
|
<Users className="w-5 h-5" />
|
||||||
<span className="font-medium">Dashboard</span>
|
<span className="font-medium">
|
||||||
|
{user?.role === 'Employe' ? 'Mon équipe' : 'Équipe'}
|
||||||
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
<Link
|
|
||||||
to="/demandes"
|
|
||||||
onClick={() => window.innerWidth < 1024 && onToggle()}
|
|
||||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/demandes")
|
|
||||||
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
|
|
||||||
: "text-gray-700 hover:bg-gray-50"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<FileText className="w-5 h-5" />
|
|
||||||
<span className="font-medium">Demandes</span>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
to="/calendrier"
|
|
||||||
onClick={() => window.innerWidth < 1024 && onToggle()}
|
|
||||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/calendrier")
|
|
||||||
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
|
|
||||||
: "text-gray-700 hover:bg-gray-50"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Calendar className="w-5 h-5" />
|
|
||||||
<span className="font-medium">Calendrier</span>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{(user?.role === 'Manager' || user?.role === 'Admin' || user?.role === 'Employe') && (
|
|
||||||
<Link
|
|
||||||
to="/manager"
|
|
||||||
onClick={() => window.innerWidth < 1024 && onToggle()}
|
|
||||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-colors ${isActive("/manager")
|
|
||||||
? "bg-blue-50 text-blue-700 border-r-2 border-blue-700"
|
|
||||||
: "text-gray-700 hover:bg-gray-50"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Users className="w-5 h-5" />
|
|
||||||
<span className="font-medium">
|
|
||||||
{user?.role === 'Employe' ? 'Mon équipe' : 'Équipe'}
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Logout Button */}
|
{/* Bouton déconnexion */}
|
||||||
<div className="p-4 border-t border-gray-100">
|
<div className="p-4 border-t border-gray-100">
|
||||||
<button
|
<button
|
||||||
onClick={logout}
|
onClick={logout}
|
||||||
|
|||||||
@@ -15,13 +15,12 @@ export const AuthProvider = ({ children }) => {
|
|||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Vérifier si l'utilisateur est déjà connecté
|
|
||||||
const savedUser = localStorage.getItem('user');
|
const savedUser = localStorage.getItem('user');
|
||||||
if (savedUser) {
|
if (savedUser) {
|
||||||
try {
|
try {
|
||||||
setUser(JSON.parse(savedUser));
|
setUser(JSON.parse(savedUser));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur lors du parsing de l\'utilisateur sauvegardé:', error);
|
console.error("Erreur parsing utilisateur sauvegardé:", error);
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,7 +29,6 @@ export const AuthProvider = ({ children }) => {
|
|||||||
|
|
||||||
const login = async (email, password) => {
|
const login = async (email, password) => {
|
||||||
try {
|
try {
|
||||||
// Tester plusieurs URLs possibles selon la configuration locale
|
|
||||||
const possibleUrls = [
|
const possibleUrls = [
|
||||||
'http://localhost/GTA/project/public/login.php',
|
'http://localhost/GTA/project/public/login.php',
|
||||||
'http://localhost:80/GTA/project/public/login.php',
|
'http://localhost:80/GTA/project/public/login.php',
|
||||||
@@ -39,29 +37,16 @@ export const AuthProvider = ({ children }) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
let response = null;
|
let response = null;
|
||||||
let lastError = null;
|
|
||||||
|
|
||||||
for (const url of possibleUrls) {
|
for (const url of possibleUrls) {
|
||||||
try {
|
try {
|
||||||
console.log(' Test URL:', url);
|
console.log("Test URL:", url);
|
||||||
response = await fetch(url, {
|
response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
body: JSON.stringify({ email, mot_de_passe: password }),
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: email,
|
|
||||||
mot_de_passe: password
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
if (response.ok) break;
|
||||||
if (response.ok) {
|
} catch {
|
||||||
console.log(' URL qui fonctionne:', url);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
lastError = error;
|
|
||||||
console.log(' URL échouée:', url, error.message);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,35 +56,34 @@ export const AuthProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
console.log(' Réponse brute:', text);
|
|
||||||
|
|
||||||
// Vérifier si la réponse est du JSON valide
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(text);
|
data = JSON.parse(text);
|
||||||
} catch (parseError) {
|
} catch {
|
||||||
console.error(' Réponse non-JSON:', text.substring(0, 200));
|
console.error("Réponse non-JSON:", text.substring(0, 200));
|
||||||
throw new Error('Le serveur PHP ne répond pas correctement. Vérifiez que PHP est démarré.');
|
throw new Error("Le serveur PHP ne répond pas correctement.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
const userData = {
|
const userData = {
|
||||||
id: data.user.id,
|
id: data.user.id,
|
||||||
name: data.user.prenom + ' ' + data.user.nom,
|
name: `${data.user.prenom} ${data.user.nom}`,
|
||||||
prenom: data.user.prenom,
|
prenom: data.user.prenom,
|
||||||
nom: data.user.nom,
|
nom: data.user.nom,
|
||||||
email: data.user.email,
|
email: data.user.email,
|
||||||
role: data.user.role || 'Employe'
|
role: data.user.role || 'Employe',
|
||||||
|
service: data.user.service || 'Non défini'
|
||||||
};
|
};
|
||||||
|
|
||||||
setUser(userData);
|
setUser(userData);
|
||||||
localStorage.setItem('user', JSON.stringify(userData));
|
localStorage.setItem('user', JSON.stringify(userData));
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
console.error(' Échec connexion:', data.message);
|
console.error("Échec connexion:", data.message);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur de connexion:', error);
|
console.error("Erreur de connexion:", error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -109,12 +93,7 @@ export const AuthProvider = ({ children }) => {
|
|||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
};
|
};
|
||||||
|
|
||||||
const value = {
|
const value = { user, login, logout, isLoading };
|
||||||
user,
|
|
||||||
login,
|
|
||||||
logout,
|
|
||||||
isLoading
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={value}>
|
<AuthContext.Provider value={value}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import Sidebar from '../components/Sidebar';
|
import Sidebar from '../components/Sidebar';
|
||||||
import { Calendar as CalendarIcon, Clock, Plus, Settings, RefreshCw, Search, Filter, Eye, Edit, Trash2, Menu, X } from 'lucide-react';
|
import { Plus, Search, Filter, Eye, Menu, X } from 'lucide-react';
|
||||||
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
|
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
|
||||||
|
|
||||||
const Requests = () => {
|
const Requests = () => {
|
||||||
@@ -17,12 +17,11 @@ const Requests = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
const [showNewRequestModal, setShowNewRequestModal] = useState(false);
|
||||||
const [showAdminPanel, setShowAdminPanel] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
const [allRequests, setAllRequests] = useState([]);
|
const [allRequests, setAllRequests] = useState([]);
|
||||||
const [filteredRequests, setFilteredRequests] = useState([]);
|
const [filteredRequests, setFilteredRequests] = useState([]);
|
||||||
const [selectedRequest, setSelectedRequest] = useState(null); // 👈 Nouveau
|
const [selectedRequest, setSelectedRequest] = useState(null);
|
||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [statusFilter, setStatusFilter] = useState('all');
|
const [statusFilter, setStatusFilter] = useState('all');
|
||||||
@@ -31,6 +30,8 @@ const Requests = () => {
|
|||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [requestsPerPage] = useState(10);
|
const [requestsPerPage] = useState(10);
|
||||||
|
|
||||||
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.id) {
|
if (user?.id) {
|
||||||
fetchLeaveCounters();
|
fetchLeaveCounters();
|
||||||
@@ -70,14 +71,12 @@ const Requests = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`http://localhost/GTA/project/public/getLeaveCounters.php?user_id=${user.id}`);
|
const response = await fetch(`http://localhost/GTA/project/public/getLeaveCounters.php?user_id=${user.id}`);
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(text);
|
data = JSON.parse(text);
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error('Le serveur PHP ne répond pas correctement');
|
throw new Error('Le serveur PHP ne répond pas correctement');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setLeaveCounters(data.counters);
|
setLeaveCounters(data.counters);
|
||||||
} else {
|
} else {
|
||||||
@@ -93,8 +92,12 @@ const Requests = () => {
|
|||||||
const url = `http://localhost/GTA/project/public/getRequests.php?user_id=${user.id}`;
|
const url = `http://localhost/GTA/project/public/getRequests.php?user_id=${user.id}`;
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
const data = JSON.parse(text);
|
let data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
throw new Error('Le serveur PHP ne répond pas correctement');
|
||||||
|
}
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
setAllRequests(data.requests || []);
|
setAllRequests(data.requests || []);
|
||||||
} else {
|
} else {
|
||||||
@@ -107,33 +110,6 @@ const Requests = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetCounters = async () => {
|
|
||||||
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' },
|
|
||||||
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 {
|
|
||||||
alert('Erreur de connexion au serveur');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const openManualResetPage = () => {
|
|
||||||
window.open('http://localhost/GTA/project/public/manualResetCounters.php', '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusColor = (status) => {
|
const getStatusColor = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'Approuvé':
|
case 'Approuvé':
|
||||||
@@ -157,7 +133,6 @@ const Requests = () => {
|
|||||||
const indexOfFirstRequest = indexOfLastRequest - requestsPerPage;
|
const indexOfFirstRequest = indexOfLastRequest - requestsPerPage;
|
||||||
const currentRequests = filteredRequests.slice(indexOfFirstRequest, indexOfLastRequest);
|
const currentRequests = filteredRequests.slice(indexOfFirstRequest, indexOfLastRequest);
|
||||||
const totalPages = Math.ceil(filteredRequests.length / requestsPerPage);
|
const totalPages = Math.ceil(filteredRequests.length / requestsPerPage);
|
||||||
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -178,33 +153,37 @@ const Requests = () => {
|
|||||||
<Sidebar isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
|
<Sidebar isOpen={sidebarOpen} onToggle={() => setSidebarOpen(!sidebarOpen)} />
|
||||||
|
|
||||||
<div className="flex-1 lg:ml-60 p-4 lg:p-8">
|
<div className="flex-1 lg:ml-60 p-4 lg:p-8">
|
||||||
{/* Bouton mobile */}
|
{/* Mobile top bar */}
|
||||||
<div className="lg:hidden mb-4">
|
<div className="lg:hidden flex justify-between items-center mb-4">
|
||||||
<button onClick={() => setSidebarOpen(true)} className="p-2 rounded-lg bg-white shadow-sm border border-gray-200">
|
<button onClick={() => setSidebarOpen(true)} className="p-2 rounded-lg bg-white shadow-sm border border-gray-200">
|
||||||
<Menu className="w-6 h-6" />
|
<Menu className="w-6 h-6" />
|
||||||
</button>
|
</button>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<button onClick={() => setShowFilters(!showFilters)} className="p-2 rounded-lg bg-white shadow-sm border border-gray-200 flex items-center gap-2">
|
||||||
|
<Filter className="w-4 h-4" /> <span className="text-sm">Filtres</span>
|
||||||
|
</button>
|
||||||
|
<button onClick={() => setShowNewRequestModal(true)} className="p-2 rounded-lg bg-blue-600 text-white">
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex justify-between items-center mb-8">
|
<div className="flex justify-between items-center mb-4 lg:mb-8">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl lg:text-3xl font-bold text-gray-900 mb-2">
|
<h1 className="text-2xl lg:text-3xl font-bold text-gray-900 mb-1">Mes Demandes de Congés</h1>
|
||||||
Mes Demandes de Congés
|
<p className="text-sm text-gray-600">Gérez toutes vos demandes de congés</p>
|
||||||
</h1>
|
</div>
|
||||||
<p className="text-sm lg:text-base text-gray-600">Gérez toutes vos demandes de congés</p>
|
|
||||||
|
<div className="hidden lg:flex items-center gap-3">
|
||||||
|
<button onClick={() => setShowNewRequestModal(true)} className="bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center gap-2">
|
||||||
|
<Plus className="w-4 h-4" /> Nouvelle demande
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
|
|
||||||
{/* Filtres */}
|
{/* Filters panel (mobile toggle + desktop always visible) */}
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-4 lg:p-6 mb-6">
|
<div className={`${showFilters ? 'block' : 'hidden'} lg:block bg-white rounded-xl shadow-sm border border-gray-100 p-4 lg:p-6 mb-6`}>
|
||||||
<div className="flex flex-col lg:flex-row gap-4">
|
<div className="flex flex-col lg:flex-row gap-4">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
|
||||||
@@ -217,30 +196,20 @@ const Requests = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<select value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)} className="px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||||
<select
|
<option value="all">Tous les statuts</option>
|
||||||
value={statusFilter}
|
<option value="En attente">En attente</option>
|
||||||
onChange={(e) => setStatusFilter(e.target.value)}
|
<option value="Validée">Validée</option>
|
||||||
className="px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
<option value="Refusée">Refusée</option>
|
||||||
>
|
</select>
|
||||||
<option value="all">Tous les statuts</option>
|
|
||||||
<option value="En attente">En attente</option>
|
|
||||||
<option value="Validée">Validée</option>
|
|
||||||
<option value="Refusée">Refusée</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<select
|
<select value={typeFilter} onChange={(e) => setTypeFilter(e.target.value)} className="px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||||
value={typeFilter}
|
<option value="all">Tous les types</option>
|
||||||
onChange={(e) => setTypeFilter(e.target.value)}
|
<option value="Congés payés">Congés payés</option>
|
||||||
className="px-3 py-2 border border-gray-300 rounded-lg text-sm"
|
<option value="RTT">RTT</option>
|
||||||
>
|
<option value="Congé maladie">Congé maladie</option>
|
||||||
<option value="all">Tous les types</option>
|
<option value="Autres">Autres types</option>
|
||||||
<option value="Congés payés">Congés payés</option>
|
</select>
|
||||||
<option value="RTT">RTT</option>
|
|
||||||
<option value="Congé maladie">Congé maladie</option>
|
|
||||||
<option value="Autres">Autres types</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 text-sm text-gray-600">
|
<div className="mt-4 text-sm text-gray-600">
|
||||||
@@ -249,17 +218,15 @@ const Requests = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tableau + Détails */}
|
{/* Main content: left = list/table, right = details (desktop) */}
|
||||||
<div className="flex flex-col lg:flex-row gap-6">
|
<div className="flex flex-col lg:flex-row gap-6">
|
||||||
{/* Tableau des demandes */}
|
{/* Left: table (desktop) + cards (mobile) */}
|
||||||
<div className="flex-1 bg-white rounded-xl shadow-sm border border-gray-100">
|
<div className="flex-1 bg-white rounded-xl shadow-sm border border-gray-100">
|
||||||
<div className="p-6 border-b border-gray-100">
|
<div className="p-6 border-b border-gray-100 flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between">
|
<h2 className="text-xl font-semibold text-gray-900">Toutes mes demandes</h2>
|
||||||
<h2 className="text-xl font-semibold text-gray-900">Toutes mes demandes</h2>
|
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
<Filter className="w-4 h-4" />
|
||||||
<Filter className="w-4 h-4" />
|
<span>Page {currentPage} / {totalPages || 1}</span>
|
||||||
Page {currentPage} sur {totalPages || 1}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -267,102 +234,193 @@ const Requests = () => {
|
|||||||
{currentRequests.length === 0 ? (
|
{currentRequests.length === 0 ? (
|
||||||
<div className="text-center py-8 text-gray-600">Aucune demande à afficher.</div>
|
<div className="text-center py-8 text-gray-600">Aucune demande à afficher.</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<>
|
||||||
<table className="w-full">
|
{/* Desktop table */}
|
||||||
<thead>
|
<div className="hidden lg:block overflow-x-auto">
|
||||||
<tr className="border-b border-gray-200">
|
<table className="w-full">
|
||||||
<th className="text-left py-3 px-4 text-gray-700">Type</th>
|
<thead>
|
||||||
<th className="text-left py-3 px-4 text-gray-700">Dates</th>
|
<tr className="border-b border-gray-200">
|
||||||
<th className="text-left py-3 px-4 text-gray-700">Jours</th>
|
<th className="text-left py-3 px-4 text-gray-700">Type</th>
|
||||||
<th className="text-left py-3 px-4 text-gray-700">Statut</th>
|
<th className="text-left py-3 px-4 text-gray-700">Dates</th>
|
||||||
<th className="text-left py-3 px-4 text-gray-700">Soumis</th>
|
<th className="text-left py-3 px-4 text-gray-700">Jours</th>
|
||||||
<th className="text-left py-3 px-4 text-gray-700">Actions</th>
|
<th className="text-left py-3 px-4 text-gray-700">Statut</th>
|
||||||
</tr>
|
<th className="text-left py-3 px-4 text-gray-700">Soumis</th>
|
||||||
</thead>
|
<th className="text-left py-3 px-4 text-gray-700">Actions</th>
|
||||||
<tbody>
|
</tr>
|
||||||
{currentRequests.map(request => (
|
</thead>
|
||||||
<tr key={request.id} className="border-b hover:bg-gray-50">
|
<tbody>
|
||||||
<td className="py-4 px-4">{request.type}</td>
|
{currentRequests.map(request => (
|
||||||
<td className="py-4 px-4">{request.dateDisplay}</td>
|
<tr key={request.id} className="border-b hover:bg-gray-50">
|
||||||
<td className="py-4 px-4">{request.days}</td>
|
<td className="py-4 px-4">
|
||||||
<td className="py-4 px-4">
|
{request.type.split(',').map((t, i) => (
|
||||||
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(request.status)}`}>
|
<span key={i} className="inline-block bg-gray-100 text-gray-800 text-[11px] px-2 py-0.5 rounded-full mr-1">
|
||||||
|
{t.trim()}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4">{request.dateDisplay}</td>
|
||||||
|
<td className="py-4 px-4">{request.days}</td>
|
||||||
|
<td className="py-4 px-4">
|
||||||
|
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(request.status)}`}>
|
||||||
|
{request.status}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4">{request.submittedDisplay}</td>
|
||||||
|
<td className="py-4 px-4">
|
||||||
|
<button onClick={() => handleViewRequest(request)} className="text-blue-600 hover:underline text-sm flex items-center gap-1">
|
||||||
|
<Eye className="w-4 h-4" /> Voir
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile cards */}
|
||||||
|
<div className="lg:hidden space-y-4">
|
||||||
|
{currentRequests.map(request => (
|
||||||
|
<div key={request.id} className="bg-white rounded-lg shadow-sm p-4 border border-gray-200">
|
||||||
|
<div className="flex justify-between items-start">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex flex-wrap gap-1 mb-2">
|
||||||
|
{request.type.split(',').map((t, i) => (
|
||||||
|
<span key={i} className="bg-gray-100 text-gray-800 text-[11px] px-2 py-0.5 rounded-full">
|
||||||
|
{t.trim()}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-600">{request.dateDisplay}</p>
|
||||||
|
<p className="text-sm font-medium mt-1">{request.days} jour{request.days > 1 ? 's' : ''}</p>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3">
|
||||||
|
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(request.status)}`}>
|
||||||
{request.status}
|
{request.status}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</div>
|
||||||
<td className="py-4 px-4">{request.submittedDisplay}</td>
|
</div>
|
||||||
<td className="py-4 px-4">
|
|
||||||
<button onClick={() => handleViewRequest(request)} className="text-blue-600 hover:underline text-sm flex items-center gap-1">
|
<div className="mt-3 flex justify-between items-center text-sm">
|
||||||
<Eye className="w-4 h-4" /> Voir
|
<span className="text-gray-500">{request.submittedDisplay}</span>
|
||||||
</button>
|
<button onClick={() => handleViewRequest(request)} className="text-blue-600 flex items-center gap-1">
|
||||||
</td>
|
<Eye className="w-4 h-4" /> Voir
|
||||||
</tr>
|
</button>
|
||||||
))}
|
</div>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Détails */}
|
{/* Right: details (desktop) */}
|
||||||
{selectedRequest && (
|
<div className="hidden lg:block w-full lg:max-w-sm">
|
||||||
<div className="w-full lg:max-w-sm bg-white rounded-xl shadow-md border border-gray-100 p-6 h-fit sticky top-20 self-start">
|
{selectedRequest ? (
|
||||||
<div className="flex justify-between items-start mb-6">
|
<div className="bg-white rounded-xl shadow-md border border-gray-100 p-6 sticky top-20">
|
||||||
<h3 className="text-lg font-semibold">Détails de la demande</h3>
|
<div className="flex justify-between items-start mb-6">
|
||||||
<button onClick={handleCloseDetails} className="text-gray-500 hover:text-gray-800 p-2">
|
<h3 className="text-lg font-semibold">Détails de la demande</h3>
|
||||||
<X className="w-4 h-4" />
|
<button onClick={handleCloseDetails} className="text-gray-500 hover:text-gray-800 p-2">
|
||||||
</button>
|
<X className="w-4 h-4" />
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4 text-sm text-gray-700">
|
<div className="space-y-4 text-sm text-gray-700">
|
||||||
<div>
|
|
||||||
<p className="text-gray-500">Type</p>
|
|
||||||
<p className="text-base font-medium text-gray-900">{selectedRequest.type}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-gray-500">Dates</p>
|
|
||||||
<p className="text-base font-medium text-gray-900">{selectedRequest.dateDisplay}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-gray-500">Nombre de jours</p>
|
|
||||||
<p className="text-base font-medium text-gray-900">{selectedRequest.days}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="text-gray-500">Statut</p>
|
|
||||||
<span className={`px-3 py-1 rounded-full text-sm font-medium ${getStatusColor(selectedRequest.status)}`}>
|
|
||||||
{selectedRequest.status}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{selectedRequest.reason && (
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">Motif</p>
|
<p className="text-gray-500">Type</p>
|
||||||
<p className="italic">{selectedRequest.reason}</p>
|
<p className="text-base font-medium text-gray-900">{selectedRequest.type}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedRequest.fileUrl && (
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p className="text-gray-500">Arrêt maladie</p>
|
<p className="text-gray-500">Dates</p>
|
||||||
<a
|
<p className="text-base font-medium text-gray-900">{selectedRequest.dateDisplay}</p>
|
||||||
href={selectedRequest.fileUrl}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-blue-600 hover:underline flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
Voir le fichier
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div>
|
||||||
|
<p className="text-gray-500">Nombre de jours</p>
|
||||||
|
<p className="text-base font-medium text-gray-900">{selectedRequest.days}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Statut</p>
|
||||||
|
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(selectedRequest.status)}`}>
|
||||||
|
{selectedRequest.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{selectedRequest.reason && (
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Motif</p>
|
||||||
|
<p className="italic">{selectedRequest.reason}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedRequest.fileUrl && (
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Arrêt maladie</p>
|
||||||
|
<a href={selectedRequest.fileUrl} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline flex items-center gap-2">
|
||||||
|
<Eye className="w-4 h-4" /> Voir le fichier
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<div className="bg-white rounded-xl shadow-sm border border-gray-100 p-6 text-sm text-gray-500">
|
||||||
|
Sélectionnez une demande pour voir les détails
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Mobile details modal */}
|
||||||
|
{selectedRequest && (
|
||||||
|
<div className="fixed inset-0 z-50 lg:hidden">
|
||||||
|
<div className="absolute inset-0 bg-black bg-opacity-40" onClick={handleCloseDetails}></div>
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center p-4">
|
||||||
|
<div className="bg-white w-full max-w-md h-full overflow-y-auto rounded-lg p-5">
|
||||||
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<h3 className="text-lg font-semibold">Détails</h3>
|
||||||
|
<button onClick={handleCloseDetails} className="text-gray-500">
|
||||||
|
<X className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4 text-sm text-gray-700">
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Type</p>
|
||||||
|
<p className="text-base font-medium text-gray-900">{selectedRequest.type}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Dates</p>
|
||||||
|
<p className="text-base font-medium text-gray-900">{selectedRequest.dateDisplay}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Nombre de jours</p>
|
||||||
|
<p className="text-base font-medium text-gray-900">{selectedRequest.days}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Statut</p>
|
||||||
|
<span className={`px-2 py-0.5 rounded-full text-[10px] font-medium ${getStatusColor(selectedRequest.status)}`}>
|
||||||
|
{selectedRequest.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{selectedRequest.reason && (
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Motif</p>
|
||||||
|
<p className="italic">{selectedRequest.reason}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{selectedRequest.fileUrl && (
|
||||||
|
<div>
|
||||||
|
<p className="text-gray-500">Arrêt maladie</p>
|
||||||
|
<a href={selectedRequest.fileUrl} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline flex items-center gap-2">
|
||||||
|
<Eye className="w-4 h-4" /> Voir le fichier
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* New request modal */}
|
||||||
{showNewRequestModal && (
|
{showNewRequestModal && (
|
||||||
<NewLeaveRequestModal
|
<NewLeaveRequestModal
|
||||||
onClose={() => setShowNewRequestModal(false)}
|
onClose={() => setShowNewRequestModal(false)}
|
||||||
|
|||||||
Reference in New Issue
Block a user