changement au niveau de requetes adaptés aux collaborateurs AD

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

32
Dockerfile.frontend Normal file
View File

@@ -0,0 +1,32 @@
# Étape 1 : Construction de l'application
FROM node:18-alpine AS builder
# Définir le répertoire de travail
WORKDIR /app
# Copier le package.json et package-lock.json depuis le dossier 'project'
# Le contexte de construction est './project' donc Docker peut les trouver
COPY package.json ./
COPY package-lock.json ./
# Installer les dépendances
RUN npm install
# Copier le reste des fichiers du dossier 'project'
# Cela inclut le dossier 'src' et tout le reste
COPY . .
# Lancer la compilation de l'application pour la production
RUN npm run build
# Étape 2 : Servir l'application avec Nginx
FROM nginx:alpine
# Copier les fichiers du build de l'étape précédente
COPY --from=builder /app/build /usr/share/nginx/html
# Exposer le port 80
EXPOSE 80
# Commande pour démarrer Nginx
CMD ["nginx", "-g", "daemon off;"]

12
docker-compose.yml Normal file
View File

@@ -0,0 +1,12 @@
services:
frontend:
image: ouijdaneim/gta-frontend:latest
ports:
- "3000:80"
depends_on:
- backend
backend:
image: ouijdaneim/gta-backend:latest
ports:
- "8000:80"

View File

@@ -0,0 +1,14 @@
# Utilise une image PHP avec Apache et la version 8.1
FROM php:8.1-apache
# Installe l'extension mysqli pour te connecter à la base de données MySQL
RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli
# Active le module de réécriture d'URL d'Apache (souvent utile)
RUN a2enmod rewrite
# Copie tous les fichiers du back-end dans le dossier de travail d'Apache
COPY . /var/www/html/
# Expose le port 80 (par défaut pour un serveur web)
EXPOSE 80

View File

@@ -0,0 +1,147 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
// Connexion DB
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die(json_encode(["authorized" => false, "message" => "Erreur DB: " . $conn->connect_error]));
}
// --- ID du groupe cible (Ensup-Groupe) ---
$groupId = "c1ea877c-6bca-4f47-bfad-f223640813a0";
// Récupération des données POST
$data = json_decode(file_get_contents("php://input"), true);
$userPrincipalName = $data["userPrincipalName"] ?? "";
// Récupération du token dans les headers
$headers = getallheaders();
$accessToken = isset($headers['Authorization'])
? str_replace("Bearer ", "", $headers['Authorization'])
: "";
if (!$userPrincipalName || !$accessToken) {
echo json_encode(["authorized" => false, "message" => "Email ou token manquant"]);
exit;
}
/**
* Fonction générique pour appeler Graph API
*/
function callGraph($url, $accessToken, $method = "GET", $body = null) {
$ch = curl_init($url);
$headers = ["Authorization: Bearer $accessToken"];
if ($method === "POST") {
$headers[] = "Content-Type: application/json";
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
return null;
}
return json_decode($response, true);
}
/**
* Vérifier si utilisateur appartient à un groupe
*/
function isUserInGroup($userId, $groupId, $accessToken) {
$url = "https://graph.microsoft.com/v1.0/users/$userId/checkMemberGroups";
$data = json_encode(["groupIds" => [$groupId]]);
$result = callGraph($url, $accessToken, "POST", $data);
return $result && isset($result["value"]) && in_array($groupId, $result["value"]);
}
// 🔹 1. Vérifier si utilisateur existe déjà en DB
$stmt = $conn->prepare("SELECT id, entraUserId, prenom, nom, email, service, role FROM CollaborateurAD WHERE email = ? LIMIT 1");
$stmt->bind_param("s", $userPrincipalName);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
$stmt->close();
if ($user) {
echo json_encode([
"authorized" => true,
"role" => $user["role"],
"groups" => [$user["role"]],
"localUserId" => (int)$user["id"], // 🔹 ajout important
"user" => $user
]);
$conn->close();
exit;
}
// 🔹 2. Sinon → chercher lutilisateur dans Microsoft Graph
$userGraph = callGraph("https://graph.microsoft.com/v1.0/users/$userPrincipalName?\$select=id,displayName,givenName,surname,mail,department,jobTitle", $accessToken);
if (!$userGraph) {
echo json_encode([
"authorized" => false,
"message" => "Utilisateur introuvable dans Entra ou token invalide"
]);
$conn->close();
exit;
}
// 🔹 3. Vérifier appartenance au groupe Ensup-Groupe
$isInTargetGroup = isUserInGroup($userGraph["id"], $groupId, $accessToken);
if (!$isInTargetGroup) {
echo json_encode([
"authorized" => false,
"message" => "Utilisateur non autorisé : il n'appartient pas au groupe requis"
]);
$conn->close();
exit;
}
// 🔹 4. Insérer dans la base si nouveau
$entraUserId = $userGraph["id"];
$prenom = $userGraph["givenName"] ?? "";
$nom = $userGraph["surname"] ?? "";
$email = $userGraph["mail"] ?? $userPrincipalName;
$service = $userGraph["department"] ?? "";
$role = "Collaborateur"; // rôle par défaut
$stmt = $conn->prepare("INSERT INTO CollaborateurAD (entraUserId, prenom, nom, email, service, role)
VALUES (?, ?, ?, ?, ?, ?)");
$stmt->bind_param("ssssss", $entraUserId, $prenom, $nom, $email, $service, $role);
$stmt->execute();
$newUserId = $stmt->insert_id;
$stmt->close();
// 🔹 5. Réponse finale
echo json_encode([
"authorized" => true,
"role" => $role,
"groups" => [$role],
"localUserId" => (int)$newUserId, // 🔹 ajout important
"user" => [
"id" => $newUserId,
"entraUserId" => $entraUserId,
"prenom" => $prenom,
"nom" => $nom,
"email" => $email,
"service" => $service,
"role" => $role
]
]);
$conn->close();
?>

20
project/public/php/db.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
// Informations de connexion
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
// Connexion MySQLi
$conn = new mysqli($host, $username, $password, $dbname);
// Vérification de la connexion
if ($conn->connect_error) {
die(json_encode([
"success" => false,
"message" => "Erreur DB: " . $conn->connect_error
]));
}
// Important : définir lencodage en UTF-8 (pour accents, etc.)
$conn->set_charset("utf8mb4");

View File

@@ -27,7 +27,6 @@ if ($conn->connect_error) {
exit();
}
// Récupération ID manager
$managerId = $_GET['SuperieurId'] ?? null;
if (!$managerId) {
@@ -44,14 +43,14 @@ $sql = "
dc.DateDemande,
dc.Commentaire,
dc.DocumentJoint,
dc.EmployeeId,
CONCAT(u.Prenom, ' ', u.Nom) as employee_name,
u.Email as employee_email,
dc.CollaborateurADId AS employee_id,
CONCAT(ca.Prenom, ' ', ca.Nom) as employee_name,
ca.Email as employee_email,
tc.Nom as type
FROM DemandeConge dc
JOIN Users u ON dc.EmployeeId = u.ID
JOIN CollaborateurAD ca ON dc.CollaborateurADId = ca.id
JOIN TypeConge tc ON dc.TypeCongeId = tc.Id
JOIN HierarchieValidation hv ON hv.EmployeId = u.ID
JOIN HierarchieValidationAD hv ON hv.CollaborateurId = ca.id
WHERE hv.SuperieurId = ?
ORDER BY dc.DateDemande DESC
";
@@ -59,8 +58,6 @@ $sql = "
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $managerId);
$stmt->execute();
// Manquant dans ton code
$result = $stmt->get_result();
$requests = [];
@@ -78,7 +75,7 @@ while ($row = $result->fetch_assoc()) {
$requests[] = [
"id" => (int)$row['Id'],
"employee_id" => (int)$row['EmployeeId'],
"employee_id" => (int)$row['employee_id'],
"employee_name" => $row['employee_name'],
"employee_email" => $row['employee_email'],
"type" => $row['type'],

View File

@@ -0,0 +1,51 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
exit();
}
header("Content-Type: application/json");
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die(json_encode(["success" => false, "message" => "Erreur DB : " . $conn->connect_error]));
}
// Récupérer l'ID
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
echo json_encode(["success" => false, "message" => "ID collaborateur invalide"]);
exit;
}
try {
$stmt = $conn->prepare("
SELECT id, Nom, Prenom, Email, Matricule, Telephone, Adresse
FROM CollaborateurAD
WHERE id = ? AND Actif = 1
");
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
$employee = $result->fetch_assoc();
if ($employee) {
echo json_encode(["success" => true, "employee" => $employee]);
} else {
echo json_encode(["success" => false, "message" => "Collaborateur non trouvé"]);
}
} catch (Exception $e) {
echo json_encode(["success" => false, "message" => "Erreur DB: " . $e->getMessage()]);
}
$conn->close();
?>

View File

@@ -0,0 +1,66 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
exit();
}
header("Content-Type: application/json");
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die(json_encode(["success" => false, "message" => "Erreur DB : " . $conn->connect_error]));
}
// Récupérer l'ID
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
echo json_encode(["success" => false, "message" => "ID employé invalide"]);
exit;
}
try {
$sql = "SELECT Id, TypeCongeId, NombreJours, DateDebut, DateFin, Statut
FROM DemandeConge
WHERE EmployeeId = ?
ORDER BY DateDemande DESC";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $id);
$stmt->execute();
$result = $stmt->get_result();
// Mapping des types de congés
$typeNames = [
1 => "Congé payé",
2 => "RTT",
3 => "Maladie"
];
$requests = [];
while ($row = $result->fetch_assoc()) {
$row['type'] = $typeNames[$row['TypeCongeId']] ?? "Autre";
$row['days'] = (float)$row['NombreJours'];
// Formater jours : 2j ou 1.5j
$row['days_display'] = ((int)$row['days'] == $row['days'] ? (int)$row['days'] : $row['days']) . "j";
$row['date_display'] = date("d/m/Y", strtotime($row['DateDebut']))
. " - "
. date("d/m/Y", strtotime($row['DateFin']));
$requests[] = $row;
}
echo json_encode(["success" => true, "requests" => $requests]);
} catch (Exception $e) {
echo json_encode(["success" => false, "message" => "Erreur DB: " . $e->getMessage()]);
}
$conn->close();
?>

View File

@@ -27,22 +27,22 @@ $leaveYear = getLeaveYear();
$rttYear = getRTTYear();
$currentDate = date('Y-m-d');
// --- Soldes initiaux (CompteurConges) restent inchangés ---
// --- Soldes initiaux (CompteurConges pour CollaborateurAD) ---
$cpSolde = 0; $rttSolde = 0; $absSolde = 0;
if ($cpTypeId !== null) {
$q="SELECT Solde FROM CompteurConges WHERE EmployeeId=? AND TypeCongeId=? AND Annee=?";
$q="SELECT Solde FROM CompteurConges WHERE CollaborateurADId=? 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=?";
$q="SELECT Solde FROM CompteurConges WHERE CollaborateurADId=? 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=?";
$q="SELECT Solde FROM CompteurConges WHERE CollaborateurADId=? 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();
}
// --- Calcul CP in process : priorité DemandeCongeType, fallback = working days on DemandeConge ---
// --- Calcul CP en cours ---
$cpInProcess = 0;
if ($cpTypeId !== null) {
$sql = "
@@ -50,13 +50,13 @@ if ($cpTypeId !== null) {
FROM DemandeConge dc
LEFT JOIN DemandeCongeType dct
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
WHERE dc.EmployeeId = ?
WHERE dc.CollaborateurADId = ?
AND dc.Statut IN ('En attente','Validée')
AND dc.DateFin >= ?
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
";
$s = $conn->prepare($sql);
$s->bind_param("iiss", $cpTypeId, $userId, $currentDate, $cpTypeId);
$s->bind_param("iisi", $cpTypeId, $userId, $currentDate, $cpTypeId);
$s->execute();
$res = $s->get_result();
while ($r = $res->fetch_assoc()) {
@@ -69,7 +69,7 @@ if ($cpTypeId !== null) {
$s->close();
}
// --- Calcul RTT in process (même logique) ---
// --- Calcul RTT en cours ---
$rttInProcess = 0;
if ($rttTypeId !== null) {
$sql = "
@@ -77,13 +77,13 @@ if ($rttTypeId !== null) {
FROM DemandeConge dc
LEFT JOIN DemandeCongeType dct
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
WHERE dc.EmployeeId = ?
WHERE dc.CollaborateurADId = ?
AND dc.Statut IN ('En attente','Validée')
AND dc.DateFin >= ?
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
";
$s = $conn->prepare($sql);
$s->bind_param("iiss", $rttTypeId, $userId, $currentDate, $rttTypeId);
$s->bind_param("iisi", $rttTypeId, $userId, $currentDate, $rttTypeId);
$s->execute();
$res = $s->get_result();
while ($r = $res->fetch_assoc()) {
@@ -96,7 +96,7 @@ if ($rttTypeId !== null) {
$s->close();
}
// --- Calcul absenteisme (validation) : priorité DemandeCongeType, fallback = DATEDIFF+1 ---
// --- Calcul absenteisme validé ---
$absenteism = 0;
if ($absTypeId !== null) {
$sql = "
@@ -104,7 +104,7 @@ if ($absTypeId !== null) {
FROM DemandeConge dc
LEFT JOIN DemandeCongeType dct
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
WHERE dc.EmployeeId = ?
WHERE dc.CollaborateurADId = ?
AND dc.Statut = 'Validée'
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
";
@@ -116,7 +116,6 @@ if ($absTypeId !== null) {
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);
}

View File

@@ -55,8 +55,8 @@ function getWorkingDays($startDate, $endDate) {
}
try {
// Récupérer le service du manager
$queryManagerService = "SELECT ServiceId FROM Users WHERE ID = ?";
// Récupérer le service du manager (table CollaborateurAD)
$queryManagerService = "SELECT ServiceId FROM CollaborateurAD WHERE id = ?";
$stmtManager = $conn->prepare($queryManagerService);
$stmtManager->bind_param("i", $managerId);
$stmtManager->execute();
@@ -75,19 +75,19 @@ try {
dc.Statut,
dc.DateDemande,
dc.Commentaire,
dc.EmployeeId,
CONCAT(u.Prenom, ' ', u.Nom) as employee_name,
u.Email as employee_email,
dc.CollaborateurADId,
CONCAT(ca.prenom, ' ', ca.nom) as employee_name,
ca.email as employee_email,
GROUP_CONCAT(tc.Nom ORDER BY tc.Nom SEPARATOR ', ') as types
FROM DemandeConge dc
JOIN Users u ON dc.EmployeeId = u.ID
JOIN CollaborateurAD ca ON dc.CollaborateurADId = ca.id
JOIN TypeConge tc ON FIND_IN_SET(tc.Id, dc.TypeCongeId)
WHERE u.ServiceId = ?
WHERE ca.ServiceId = ?
AND dc.Statut = 'En attente'
AND u.ID != ?
AND ca.id != ?
GROUP BY
dc.Id, dc.DateDebut, dc.DateFin, dc.Statut, dc.DateDemande,
dc.Commentaire, dc.EmployeeId, u.Prenom, u.Nom, u.Email
dc.Commentaire, dc.CollaborateurADId, ca.prenom, ca.nom, ca.email
ORDER BY dc.DateDemande ASC
";
@@ -112,7 +112,7 @@ try {
$requests[] = [
'id' => (int)$row['Id'],
'employee_id' => (int)$row['EmployeeId'],
'employee_id' => (int)$row['CollaborateurADId'],
'employee_name' => $row['employee_name'],
'employee_email' => $row['employee_email'],
'type' => $row['types'], // ex: "Congé payé, RTT"

View File

@@ -65,19 +65,19 @@ try {
GROUP_CONCAT(tc.Nom ORDER BY tc.Nom SEPARATOR ', ') AS TypeConges
FROM DemandeConge dc
JOIN TypeConge tc ON FIND_IN_SET(tc.Id, dc.TypeCongeId)
WHERE dc.EmployeeId = ?
WHERE (dc.EmployeeId = ? OR dc.CollaborateurADId = ?)
GROUP BY
dc.Id, dc.DateDebut, dc.DateFin, dc.Statut, dc.DateDemande,
dc.Commentaire, dc.Validateur, dc.DocumentJoint
ORDER BY dc.DateDemande DESC
";
";
$stmt = $conn->prepare($query);
if (!$stmt) {
throw new Exception("Erreur préparation SQL : " . $conn->error);
}
$stmt->bind_param("i", $userId);
$stmt->bind_param("ii", $userId, $userId);
$stmt->execute();
$result = $stmt->get_result();

View File

@@ -1,5 +1,5 @@
<?php
// Récupération des membres de l'équipe pour un manager
// Récupération des membres de l'équipe pour un manager AD
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
@@ -11,7 +11,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
header("Content-Type: application/json");
// Log des erreurs pour debug
// Debug erreurs
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
@@ -24,7 +24,7 @@ $password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
error_log("Erreur connexion DB getTeamMembers: " . $conn->connect_error);
error_log("Erreur connexion DB getTeamMembersAD: " . $conn->connect_error);
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données"]);
exit();
}
@@ -36,11 +36,11 @@ if ($managerId === null) {
exit();
}
error_log("getTeamMembers - Manager ID: $managerId");
error_log("getTeamMembersAD - Manager ID: $managerId");
try {
// D'abord, récupérer le service du manager
$queryManagerService = "SELECT ServiceId FROM Users WHERE ID = ?";
// 🔹 1. Récupérer le ServiceId du manager
$queryManagerService = "SELECT ServiceId FROM CollaborateurAD WHERE id = ?";
$stmtManager = $conn->prepare($queryManagerService);
$stmtManager->bind_param("i", $managerId);
$stmtManager->execute();
@@ -48,22 +48,22 @@ try {
if ($managerRow = $resultManager->fetch_assoc()) {
$serviceId = $managerRow['ServiceId'];
error_log("getTeamMembers - Service ID du manager: $serviceId");
error_log("getTeamMembersAD - ServiceId du manager: $serviceId");
// Récupérer tous les membres du même service (sauf le manager lui-même)
// 🔹 2. Récupérer tous les collaborateurs du même service (sauf le manager)
$queryTeam = "
SELECT
u.ID as id,
u.Nom as nom,
u.Prenom as prenom,
u.Email as email,
u.Role as role,
u.DateEmbauche as date_embauche,
c.id,
c.nom,
c.prenom,
c.email,
c.role,
s.Nom as service_name
FROM Users u
JOIN Services s ON u.ServiceId = s.Id
WHERE u.ServiceId = ? AND u.ID != ? AND u.Actif = 1
ORDER BY u.Prenom, u.Nom
FROM CollaborateurAD c
JOIN Services s ON c.ServiceId = s.Id
WHERE c.ServiceId = ? AND c.id != ?
ORDER BY c.prenom, c.nom
";
$stmtTeam = $conn->prepare($queryTeam);
@@ -79,12 +79,12 @@ try {
'prenom' => $row['prenom'],
'email' => $row['email'],
'role' => $row['role'],
'date_embauche' => $row['date_embauche'],
'service_name' => $row['service_name']
];
}
error_log("getTeamMembers - Membres trouvés: " . count($teamMembers));
error_log("getTeamMembersAD - Membres trouvés: " . count($teamMembers));
echo json_encode([
"success" => true,
@@ -95,7 +95,7 @@ try {
$stmtTeam->close();
} else {
error_log("getTeamMembers - Manager non trouvé: $managerId");
error_log("getTeamMembersAD - Manager non trouvé: $managerId");
echo json_encode([
"success" => false,
"message" => "Manager non trouvé"
@@ -105,7 +105,7 @@ try {
$stmtManager->close();
} catch (Exception $e) {
error_log("Erreur getTeamMembers: " . $e->getMessage());
error_log("Erreur getTeamMembersAD: " . $e->getMessage());
echo json_encode([
"success" => false,
"message" => "Erreur lors de la récupération de l'équipe: " . $e->getMessage()

View File

@@ -0,0 +1,128 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
// --- Connexion DB ---
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die(json_encode(["success" => false, "message" => "Erreur DB: " . $conn->connect_error]));
}
// --- Authentification (client credentials) ---
$tenantId = "9840a2a0-6ae1-4688-b03d-d2ec291be0f9";
$clientId = "4bb4cc24-bac3-427c-b02c-5d14fc67b561";
$clientSecret = "ViC8Q~n4F5YweE18wjS0kfhp3kHh6LB2gZ76_b4R";
$scope = "https://graph.microsoft.com/.default";
$url = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token";
$data = [
"grant_type" => "client_credentials",
"client_id" => $clientId,
"client_secret" => $clientSecret,
"scope" => $scope
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);
$tokenData = json_decode($result, true);
$accessToken = $tokenData["access_token"] ?? "";
if (!$accessToken) {
die(json_encode(["success" => false, "message" => "Impossible d'obtenir un token Microsoft", "details" => $tokenData]));
}
// --- ID du groupe cible (Ensup-Groupe) ---
$groupId = "c1ea877c-6bca-4f47-bfad-f223640813a0"; // 🔹 Mets l'Object ID de ton groupe ici
$urlGroup = "https://graph.microsoft.com/v1.0/groups/$groupId?\$select=id,displayName,description,mail,createdDateTime";
$ch = curl_init($urlGroup);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$respGroup = curl_exec($ch);
curl_close($ch);
$group = json_decode($respGroup, true);
if (!isset($group["id"])) {
die(json_encode(["success" => false, "message" => "Impossible de récupérer le groupe Ensup-Groupe"]));
}
$displayName = $group["displayName"] ?? "";
$description = $group["description"] ?? "";
$mail = $group["mail"] ?? "";
$createdAt = null;
if (!empty($group["createdDateTime"])) {
$dt = new DateTime($group["createdDateTime"]);
$createdAt = $dt->format("Y-m-d H:i:s"); // format MySQL
}
// --- Insérer / mettre à jour le groupe dans EntraGroups ---
$stmt = $conn->prepare("INSERT INTO EntraGroups (Id, DisplayName, Description, Mail, CreatedAt, UpdatedAt, SyncDate, IsActive)
VALUES (?, ?, ?, ?, ?, NOW(), NOW(), 1)
ON DUPLICATE KEY UPDATE
DisplayName=?, Description=?, Mail=?, UpdatedAt=NOW(), SyncDate=NOW(), IsActive=1");
if ($stmt) {
$stmt->bind_param("ssssssss",
$groupId, $displayName, $description, $mail, $createdAt,
$displayName, $description, $mail
);
$stmt->execute();
}
// --- Récupérer les membres du groupe ---
$urlMembers = "https://graph.microsoft.com/v1.0/groups/$groupId/members?\$select=id,givenName,surname,mail,department,jobTitle";
$ch = curl_init($urlMembers);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$respMembers = curl_exec($ch);
curl_close($ch);
$members = json_decode($respMembers, true)["value"] ?? [];
$usersInserted = 0;
foreach ($members as $m) {
$entraUserId = $m["id"];
$prenom = $m["givenName"] ?? "";
$nom = $m["surname"] ?? "";
$email = $m["mail"] ?? "";
$service = $m["department"] ?? "";
$role = "Collaborateur"; // par défaut
if (!$email) continue;
$stmt = $conn->prepare("INSERT INTO CollaborateurAD (entraUserId, prenom, nom, email, service, role)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE prenom=?, nom=?, email=?, service=?, role=?");
if ($stmt) {
$stmt->bind_param("sssssssssss",
$entraUserId, $prenom, $nom, $email, $service, $role,
$prenom, $nom, $email, $service, $role
);
$stmt->execute();
$usersInserted++;
}
}
echo json_encode([
"success" => true,
"message" => "Synchronisation terminée",
"groupe_sync" => $displayName,
"users_sync" => $usersInserted
]);
$conn->close();
?>

View File

@@ -1,7 +1,7 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
http_response_code(200);
@@ -16,16 +16,87 @@ $username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die(json_encode(["success" => false, "message" => "Erreur de connexion à la base de données : " . $conn->connect_error]));
die(json_encode(["success" => false, "message" => "Erreur DB : " . $conn->connect_error]));
}
$data = json_decode(file_get_contents('php://input'), true);
$email = $data['email'] ?? '';
$mot_de_passe = $data['mot_de_passe'] ?? '';
$entraUserId = $data['entraUserId'] ?? '';
$userPrincipalName = $data['userPrincipalName'] ?? '';
$query = "
$headers = getallheaders();
$accessToken = isset($headers['Authorization']) ? str_replace('Bearer ', '', $headers['Authorization']) : '';
// ======================================================
// 1⃣ Mode Azure AD (avec token + Entra)
// ======================================================
if ($accessToken && $entraUserId) {
// Vérifier si utilisateur existe déjà dans CollaborateurAD
$stmt = $conn->prepare("SELECT * FROM CollaborateurAD WHERE entraUserId=? OR email=? LIMIT 1");
$stmt->bind_param("ss", $entraUserId, $email);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
echo json_encode(["success" => false, "message" => "Utilisateur non autorisé (pas dans l'annuaire)"]);
exit();
}
$user = $result->fetch_assoc();
// Récupérer groupes de lutilisateur via Graph
$ch = curl_init("https://graph.microsoft.com/v1.0/users/$userPrincipalName/memberOf?\$select=id");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$dataGraph = json_decode($response, true);
$userGroups = [];
if (isset($dataGraph['value'])) {
foreach ($dataGraph['value'] as $g) {
if (isset($g['id'])) {
$userGroups[] = $g['id'];
}
}
}
// Vérifier si au moins un groupe est autorisé
$res = $conn->query("SELECT Id FROM EntraGroups WHERE IsActive=1");
$allowedGroups = [];
while ($row = $res->fetch_assoc()) {
$allowedGroups[] = $row['Id'];
}
$authorized = count(array_intersect($userGroups, $allowedGroups)) > 0;
if ($authorized) {
echo json_encode([
"success" => true,
"message" => "Connexion réussie via Azure AD",
"user" => [
"id" => $user['id'],
"prenom" => $user['prenom'],
"nom" => $user['nom'],
"email" => $user['email'],
"role" => $user['role'],
"service" => $user['service']
]
]);
} else {
echo json_encode(["success" => false, "message" => "Utilisateur non autorisé - pas dans un groupe actif"]);
}
$conn->close();
exit();
}
// ======================================================
// 2⃣ Mode local (login/password → Users)
// ======================================================
if ($email && $mot_de_passe) {
$query = "
SELECT
u.ID,
u.Prenom,
@@ -37,25 +108,24 @@ $query = "
FROM Users u
LEFT JOIN Services s ON u.ServiceId = s.Id
WHERE u.Email = ? AND u.MDP = ?
";
";
$stmt = $conn->prepare($query);
$stmt = $conn->prepare($query);
if ($stmt === false) {
die(json_encode(["success" => false, "message" => "Erreur de préparation de la requête : " . $conn->error]));
}
if ($stmt === false) {
die(json_encode(["success" => false, "message" => "Erreur de préparation : " . $conn->error]));
}
$stmt->bind_param("ss", $email, $mot_de_passe);
$stmt->execute();
$stmt->bind_param("ss", $email, $mot_de_passe);
$stmt->execute();
$result = $stmt->get_result();
$result = $stmt->get_result();
if ($result->num_rows === 1) {
if ($result->num_rows === 1) {
$user = $result->fetch_assoc();
echo json_encode([
"success" => true,
"message" => "Connexion réussie.",
"message" => "Connexion réussie (mode local)",
"user" => [
"id" => $user['ID'],
"prenom" => $user['Prenom'],
@@ -65,10 +135,18 @@ if ($result->num_rows === 1) {
"service" => $user['ServiceNom'] ?? 'Non défini'
]
]);
} else {
echo json_encode(["success" => false, "message" => "Identifiants incorrects."]);
} else {
echo json_encode(["success" => false, "message" => "Identifiants incorrects (mode local)"]);
}
$stmt->close();
$conn->close();
exit();
}
$stmt->close();
// ======================================================
// 3⃣ Aucun mode ne correspond
// ======================================================
echo json_encode(["success" => false, "message" => "Aucune méthode de connexion fournie"]);
$conn->close();
?>

View File

@@ -1,100 +1,293 @@
<?php
// (headers, connexion, lecture FormData ou JSON — pareil que précédemment)
ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL);
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit(); }
header("Content-Type: application/json");
ob_clean();
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Origin: http://localhost:5173");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
$host="192.168.0.4"; $dbname="DemandeConge"; $username="wpuser"; $password="-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host,$username,$password,$dbname);
if ($conn->connect_error) { echo json_encode(["success"=>false,"message"=>"Erreur DB: ".$conn->connect_error]); exit(); }
// Lecture JSON (support FormData via $_POST['data'])
if (isset($_POST['data'])) {
$data = json_decode($_POST['data'], true);
} else {
$input = file_get_contents('php://input');
$data = json_decode($input, true);
}
if ($data === null) {
echo json_encode(["success"=>false,"message"=>"JSON invalide"]); $conn->close(); exit();
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
// Vérifs minimales
if (!isset($data['EmployeeId'],$data['DateDebut'],$data['DateFin'],$data['Repartition'],$data['NombreJours'])) {
echo json_encode(["success"=>false,"message"=>"Données manquantes"]); $conn->close(); exit();
// Debug
ini_set('display_errors', 1);
error_reporting(E_ALL);
// Connexion DB
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
try {
$pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8", $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
echo json_encode(["success"=>false,"message"=>"Erreur DB: ".$e->getMessage()]);
exit;
}
// Lecture JSON brut
$input = file_get_contents('php://input');
$data = json_decode($input, true);
// 🔎 Debug pour vérifier ce qui arrive
error_log("📥 Payload reçu : " . print_r($data, true));
if (!$data) {
echo json_encode(["success"=>false,"message"=>"JSON invalide","raw"=>$input]);
exit;
}
// Vérification des champs obligatoires
$required = ['DateDebut','DateFin','Repartition','NombreJours','Email','Nom'];
foreach ($required as $f) {
if (!array_key_exists($f, $data)) {
echo json_encode([
"success"=>false,
"message"=>"Donnée manquante : $f",
"debug"=>$data
]);
exit;
}
}
$employeeId = (int)$data['EmployeeId'];
$dateDebut = $data['DateDebut'];
$dateFin = $data['DateFin'];
$commentaire= $data['Commentaire'] ?? '';
$commentaire = $data['Commentaire'] ?? '';
$numDays = (float)$data['NombreJours'];
$userEmail = $data['Email'];
$userName = $data['Nom'];
$statut = 'En attente';
$currentDate= date('Y-m-d H:i:s');
$currentDate = date('Y-m-d H:i:s');
// 1) Construire la liste d'IDs pour TypeCongeId (CSV) (compatibilité)
// 🔎 Identifier si c'est un CollaborateurAD ou un User
$stmt = $pdo->prepare("SELECT id FROM CollaborateurAD WHERE email = :email LIMIT 1");
$stmt->execute([':email'=>$userEmail]);
$collabAD = $stmt->fetch(PDO::FETCH_ASSOC);
$isAD = false;
$employeeId = null;
$collaborateurId = null;
if ($collabAD) {
$isAD = true;
$collaborateurId = (int)$collabAD['id'];
} else {
$stmt = $pdo->prepare("SELECT ID FROM Users WHERE Email = :email LIMIT 1");
$stmt->execute([':email'=>$userEmail]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$user) {
echo json_encode(["success"=>false,"message"=>"Aucun collaborateur trouvé pour $userEmail"]);
exit;
}
$employeeId = (int)$user['ID'];
}
// 🔎 Résoudre les IDs des types de congés
$typeIds = [];
foreach ($data['Repartition'] as $rep) {
$code = $rep['TypeConge']; // CP, RTT, ABS ou texte libre
$code = $rep['TypeConge'];
switch ($code) {
case 'CP': $name = 'Congé payé'; break;
case 'RTT': $name = 'RTT'; break;
case 'ABS': $name = 'Congé maladie'; break;
default: $name = $code; break;
}
$s = $conn->prepare("SELECT Id FROM TypeConge WHERE Nom = ?");
$s->bind_param("s", $name);
$s->execute();
$res = $s->get_result();
if ($r = $res->fetch_assoc()) $typeIds[] = $r['Id'];
$s->close();
$s = $pdo->prepare("SELECT Id FROM TypeConge WHERE Nom = :nom LIMIT 1");
$s->execute([':nom'=>$name]);
if ($r = $s->fetch(PDO::FETCH_ASSOC)) {
$typeIds[] = $r['Id'];
}
}
if (empty($typeIds)) {
echo json_encode(["success"=>false,"message"=>"Aucun type de congé valide"]);
exit;
}
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();
// Insertion DemandeConge
$sql = "INSERT INTO DemandeConge
(EmployeeId, CollaborateurADId, DateDebut, DateFin, TypeCongeId, Statut, DateDemande, Commentaire, Validateur, NombreJours)
VALUES (:eid, :cid, :dd, :df, :tc, :st, :cd, :com, :val, :nj)";
// 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();
}
$stmt = $pdo->prepare($sql);
$stmt->execute([
':eid'=> $isAD ? 0 : $employeeId,
':cid'=> $isAD ? $collaborateurId : null,
':dd'=>$dateDebut,
':df'=>$dateFin,
':tc'=>$typeCongeIdCsv,
':st'=>$statut,
':cd'=>$currentDate,
':com'=>$commentaire,
':val'=>'',
':nj'=>$numDays
]);
$demandeId = $pdo->lastInsertId();
// ✅ Insertion DemandeCongeType
$sql = "INSERT INTO DemandeCongeType (DemandeCongeId, TypeCongeId, NombreJours) VALUES (:did, :tid, :nj)";
$stmt = $pdo->prepare($sql);
foreach ($data['Repartition'] as $rep) {
$code = $rep['TypeConge'];
$jours = (float)$rep['NombreJours'];
$code = $rep['TypeConge'];
switch ($code) {
case 'CP': $name = 'Congé payé'; break;
case 'RTT': $name = 'RTT'; break;
case 'ABS': $name = 'Congé maladie'; break;
default: $name = $code; break;
}
$s = $conn->prepare("SELECT Id FROM TypeConge WHERE Nom = ?");
$s->bind_param("s", $name);
$s->execute();
$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 = $pdo->prepare("SELECT Id FROM TypeConge WHERE Nom = :nom LIMIT 1");
$s->execute([':nom'=>$name]);
if ($r = $s->fetch(PDO::FETCH_ASSOC)) {
$stmt->execute([
':did'=>$demandeId,
':tid'=>$r['Id'],
':nj'=>$jours
]);
}
$s->close();
}
$insertType->close();
echo json_encode(["success"=>true,"message"=>"Demande soumise", "request_id"=>$demandeId]);
$conn->close();
?>
// ✅ Récupérer les validateurs selon hiérarchie
if ($isAD) {
$stmt = $pdo->prepare("
SELECT c.email
FROM HierarchieValidationAD hv
JOIN CollaborateurAD c ON hv.SuperieurId = c.id
WHERE hv.CollaborateurId = :id
");
$stmt->execute([':id'=>$collaborateurId]);
} else {
$stmt = $pdo->prepare("
SELECT u.Email
FROM HierarchieValidation hv
JOIN Users u ON hv.SuperieurId = u.ID
WHERE hv.EmployeId = :id
");
$stmt->execute([':id'=>$employeeId]);
}
$managers = $stmt->fetchAll(PDO::FETCH_COLUMN);
# =============================================================
# 📧 AUTH Microsoft Graph (client_credentials)
# =============================================================
$tenantId = "9840a2a0-6ae1-4688-b03d-d2ec291be0f9";
$clientId = "4bb4cc24-bac3-427c-b02c-5d14fc67b561";
$clientSecret = "gvf8Q~545Bafn8yYsgjW~QG_P1lpzaRe6gJNgb2t";
$url = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token";
$data = [
"client_id" => $clientId,
"scope" => "https://graph.microsoft.com/.default",
"client_secret" => $clientSecret,
"grant_type" => "client_credentials"
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/x-www-form-urlencoded"
]);
$response = curl_exec($ch);
curl_close($ch);
$tokenData = json_decode($response, true);
if (!isset($tokenData['access_token'])) {
echo json_encode(["success" => false, "message" => "Impossible de générer un token Graph", "debug"=>$tokenData]);
exit;
}
$accessToken = $tokenData['access_token'];
# =============================================================
# 📧 Fonction envoi mail
# =============================================================
function sendMailGraph($accessToken, $fromEmail, $toEmail, $subject, $bodyHtml) {
$url = "https://graph.microsoft.com/v1.0/users/$fromEmail/sendMail";
$mailData = [
"message" => [
"subject" => $subject,
"body" => [
"contentType" => "HTML",
"content" => $bodyHtml
],
"toRecipients" => [
["emailAddress" => ["address" => $toEmail]]
]
],
"saveToSentItems" => "false"
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $accessToken",
"Content-Type: application/json"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($mailData));
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
return true;
} else {
error_log("❌ Erreur envoi mail: $response");
return false;
}
}
# =============================================================
# 📧 Envoi automatique des emails
# =============================================================
$fromEmail = "noreply@ensup.eu";
# Mail au collaborateur
sendMailGraph(
$accessToken,
$fromEmail,
$userEmail,
"Confirmation de votre demande de congés",
"
Bonjour {$userName},<br/><br/>
Votre demande du <b>{$dateDebut}</b> au <b>{$dateFin}</b>
({$numDays} jour(s)) a bien été enregistrée.<br/>
Elle est en attente de validation par votre manager.<br/><br/>
Merci.
"
);
# Mail aux managers
foreach ($managers as $managerEmail) {
sendMailGraph(
$accessToken,
$fromEmail,
$managerEmail,
"Nouvelle demande de congé - {$userName}",
"
Bonjour,<br/><br/>
{$userName} a soumis une demande de congé :<br/>
- Du <b>{$dateDebut}</b> au <b>{$dateFin}</b> ({$numDays} jour(s))<br/>
- Commentaire : " . (!empty($commentaire) ? $commentaire : "Aucun") . "<br/><br/>
Merci de valider cette demande.
"
);
}
# ✅ Réponse finale
echo json_encode([
"success"=>true,
"message"=>"Demande soumise",
"request_id"=>$demandeId,
"managers"=>$managers
]);

View File

View File

@@ -1,14 +0,0 @@
<?php
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
die("❌ Connexion échouée : " . $conn->connect_error);
}
echo "✅ Connexion réussie à la base de données !";
?>

View File

@@ -11,157 +11,182 @@ 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);
// Connexion DB
$host = "192.168.0.4";
$dbname = "DemandeConge";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
error_log("Erreur connexion DB validateRequest: " . $conn->connect_error);
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données"]);
echo json_encode(["success" => false, "message" => "Erreur DB: " . $conn->connect_error]);
exit();
}
// Lecture du JSON envoyé
$input = file_get_contents('php://input');
error_log("validateRequest - Input reçu: " . $input);
$data = json_decode($input, true);
if (!isset($data['request_id'], $data['action'], $data['validator_id'])) {
error_log("validateRequest - Données manquantes: " . print_r($data, true));
echo json_encode([
"success" => false,
"message" => "Données manquantes pour la validation"
]);
echo json_encode(["success" => false, "message" => "Données manquantes"]);
exit();
}
$requestId = (int)$data['request_id'];
$action = $data['action']; // 'approve' ou 'reject'
$action = $data['action']; // "approve" | "reject"
$validatorId = (int)$data['validator_id'];
$comment = $data['comment'] ?? '';
error_log("validateRequest - Request ID: $requestId, Action: $action, Validator: $validatorId");
try {
$conn->begin_transaction();
// Vérifier que la demande existe et est en attente
// Vérifier si validateur est Users ou CollaborateurAD
$isUserValidator = false;
$stmt = $conn->prepare("SELECT ID FROM Users WHERE ID = ?");
$stmt->bind_param("i", $validatorId);
$stmt->execute();
$res = $stmt->get_result();
if ($res->fetch_assoc()) {
$isUserValidator = true;
} else {
$stmt = $conn->prepare("SELECT Id FROM CollaborateurAD WHERE Id = ?");
$stmt->bind_param("i", $validatorId);
$stmt->execute();
$res = $stmt->get_result();
if (!$res->fetch_assoc()) {
throw new Exception("Validateur introuvable dans Users ou CollaborateurAD");
}
}
$stmt->close();
// Récupération demande
$queryCheck = "
SELECT dc.Id, dc.EmployeeId, dc.TypeCongeId, dc.DateDebut, dc.DateFin, dc.NombreJours,
u.Nom, u.Prenom, tc.Nom as TypeNom
SELECT dc.Id, dc.EmployeeId, dc.CollaborateurADId, dc.TypeCongeId, dc.DateDebut, dc.DateFin, dc.NombreJours,
u.Nom as UserNom, u.Prenom as UserPrenom,
ca.nom as CADNom, ca.prenom as CADPrenom,
tc.Nom as TypeNom
FROM DemandeConge dc
JOIN Users u ON dc.EmployeeId = u.ID
JOIN TypeConge tc ON dc.TypeCongeId = tc.Id
LEFT JOIN Users u ON dc.EmployeeId = u.ID
LEFT JOIN CollaborateurAD ca ON dc.CollaborateurADId = ca.Id
WHERE dc.Id = ? AND dc.Statut = 'En attente'
";
$stmtCheck = $conn->prepare($queryCheck);
$stmtCheck->bind_param("i", $requestId);
$stmtCheck->execute();
$resultCheck = $stmtCheck->get_result();
if ($requestRow = $resultCheck->fetch_assoc()) {
if (!($requestRow = $resultCheck->fetch_assoc())) {
throw new Exception("Demande non trouvée ou déjà traitée");
}
$stmtCheck->close();
$employeeId = $requestRow['EmployeeId'];
$collaborateurId = $requestRow['CollaborateurADId'];
$typeCongeId = $requestRow['TypeCongeId'];
$nombreJours = $requestRow['NombreJours'];
$employeeName = $requestRow['Prenom'] . ' ' . $requestRow['Nom'];
$employeeName = $employeeId
? $requestRow['UserPrenom']." ".$requestRow['UserNom']
: $requestRow['CADPrenom']." ".$requestRow['CADNom'];
$typeNom = $requestRow['TypeNom'];
error_log("validateRequest - Demande trouvée: $employeeName, Type: $typeNom, Jours: $nombreJours");
// Déterminer le nouveau statut
$newStatus = ($action === 'approve') ? 'Validée' : 'Refusée';
// Mettre à jour la demande
// 🔹 Mise à jour DemandeConge
if ($isUserValidator) {
$queryUpdate = "
UPDATE DemandeConge
SET Statut = ?,
ValidateurId = ?,
ValidateurADId = NULL,
DateValidation = NOW(),
CommentaireValidation = ?
WHERE Id = ?
";
} else {
$queryUpdate = "
UPDATE DemandeConge
SET Statut = ?,
ValidateurId = NULL,
ValidateurADId = ?,
DateValidation = NOW(),
CommentaireValidation = ?
WHERE Id = ?
";
}
$stmtUpdate = $conn->prepare($queryUpdate);
$stmtUpdate->bind_param("sisi", $newStatus, $validatorId, $comment, $requestId);
$stmtUpdate->execute();
$stmtUpdate->close();
if ($stmtUpdate->execute()) {
error_log("validateRequest - Demande mise à jour avec succès");
// Si approuvée, déduire du solde (sauf pour congé maladie)
if ($action === 'approve' && $typeNom !== 'Congé maladie') {
// Déterminer l'année selon le type de congé
// 🔹 Déduction solde (seulement Users, pas AD, hors maladie)
if ($action === 'approve' && $typeNom !== 'Congé maladie' && $employeeId) {
$currentDate = new DateTime();
if ($typeNom === 'Congé payé') {
// Exercice CP: 01/06 au 31/05
$year = ($currentDate->format('m') < 6) ? $currentDate->format('Y') - 1 : $currentDate->format('Y');
} else {
// RTT: année civile
$year = $currentDate->format('Y');
}
$year = ($typeNom === 'Congé payé' && (int)$currentDate->format('m') < 6)
? $currentDate->format('Y') - 1
: $currentDate->format('Y');
error_log("validateRequest - Déduction solde: Type=$typeNom, Année=$year, Jours=$nombreJours");
// Déduire du solde
$queryDeduct = "
UPDATE CompteurConges
SET Solde = GREATEST(0, Solde - ?)
WHERE EmployeeId = ? AND TypeCongeId = ? AND Annee = ?
";
$stmtDeduct = $conn->prepare($queryDeduct);
$stmtDeduct->bind_param("diii", $nombreJours, $employeeId, $typeCongeId, $year);
if ($stmtDeduct->execute()) {
error_log("validateRequest - Solde déduit avec succès");
} else {
error_log("validateRequest - Erreur déduction solde: " . $stmtDeduct->error);
}
$stmtDeduct->execute();
$stmtDeduct->close();
}
// Créer une notification pour l'employé
// 🔹 Notification (User ou CollaborateurAD)
$notificationTitle = ($action === 'approve') ? 'Demande approuvée' : 'Demande refusée';
$notificationMessage = "Votre demande de $typeNom a été " . (($action === 'approve') ? 'approuvée' : 'refusée');
if ($comment) {
$notificationMessage .= ". Commentaire: $comment";
}
$queryNotif = "
INSERT INTO Notifications (UserId, Titre, Message, Type, DemandeCongeId)
VALUES (?, ?, ?, ?, ?)
";
$notificationMessage = "Votre demande de $typeNom a été " . (($action === 'approve') ? "approuvée" : "refusée");
if ($comment) $notificationMessage .= " (Commentaire: $comment)";
$notifType = ($action === 'approve') ? 'Success' : 'Error';
if ($employeeId) {
$queryNotif = "
INSERT INTO Notifications (UserId, CollaborateurADId, Titre, Message, Type, DemandeCongeId)
VALUES (?, NULL, ?, ?, ?, ?)
";
$stmtNotif = $conn->prepare($queryNotif);
$stmtNotif->bind_param("isssi", $employeeId, $notificationTitle, $notificationMessage, $notifType, $requestId);
$stmtNotif->execute();
$stmtNotif->close();
// Log dans l'historique
$actionText = ($action === 'approve') ? 'Validation congé' : 'Refus congé';
$actionDetails = "$actionText $employeeName ($typeNom)";
if ($comment) {
$actionDetails .= " - $comment";
} elseif ($collaborateurId) {
$queryNotif = "
INSERT INTO Notifications (UserId, CollaborateurADId, Titre, Message, Type, DemandeCongeId)
VALUES (NULL, ?, ?, ?, ?, ?)
";
$stmtNotif = $conn->prepare($queryNotif);
$stmtNotif->bind_param("isssi", $collaborateurId, $notificationTitle, $notificationMessage, $notifType, $requestId);
$stmtNotif->execute();
$stmtNotif->close();
}
$queryHistory = "
INSERT INTO HistoriqueActions (UserId, Action, Details, DemandeCongeId)
VALUES (?, ?, ?, ?)
";
// 🔹 Historique (User ou CollaborateurAD)
$actionText = ($action === 'approve') ? 'Validation congé' : 'Refus congé';
$actionDetails = "$actionText $employeeName ($typeNom)";
if ($comment) $actionDetails .= " - $comment";
if ($isUserValidator) {
$queryHistory = "
INSERT INTO HistoriqueActions (UserId, CollaborateurADId, Action, Details, DemandeCongeId)
VALUES (?, NULL, ?, ?, ?)
";
$stmtHistory = $conn->prepare($queryHistory);
$stmtHistory->bind_param("issi", $validatorId, $actionText, $actionDetails, $requestId);
} else {
$queryHistory = "
INSERT INTO HistoriqueActions (UserId, CollaborateurADId, Action, Details, DemandeCongeId)
VALUES (NULL, ?, ?, ?, ?)
";
$stmtHistory = $conn->prepare($queryHistory);
$stmtHistory->bind_param("issi", $validatorId, $actionText, $actionDetails, $requestId);
}
$stmtHistory->execute();
$stmtHistory->close();
@@ -169,28 +194,13 @@ try {
echo json_encode([
"success" => true,
"message" => "Demande " . (($action === 'approve') ? 'approuvée' : 'refusée') . " avec succès",
"message" => "Demande " . (($action === 'approve') ? 'approuvée' : 'refusée'),
"new_status" => $newStatus
]);
} else {
throw new Exception("Erreur lors de la mise à jour: " . $stmtUpdate->error);
}
$stmtUpdate->close();
} else {
throw new Exception("Demande non trouvée ou déjà traitée");
}
$stmtCheck->close();
} catch (Exception $e) {
$conn->rollback();
error_log("Erreur validateRequest: " . $e->getMessage());
echo json_encode([
"success" => false,
"message" => "Erreur lors de la validation: " . $e->getMessage()
]);
echo json_encode(["success" => false, "message" => $e->getMessage()]);
}
$conn->close();

View File

@@ -7,6 +7,7 @@ import Requests from './pages/Requests';
import Calendar from './pages/Calendar';
import Manager from './pages/Manager';
import ProtectedRoute from './components/ProtectedRoute';
import EmployeeDetails from './pages/EmployeeDetails';
function App() {
return (
@@ -34,6 +35,7 @@ function App() {
<Manager />
</ProtectedRoute>
} />
<Route path="/employee/:id" element={<ProtectedRoute><EmployeeDetails /></ProtectedRoute>} />
<Route path="/" element={<Navigate to="/dashboard" replace />} />
</Routes>
</Router>

View File

@@ -4,9 +4,19 @@ export const msalConfig = {
clientId: "4bb4cc24-bac3-427c-b02c-5d14fc67b561", // Application (client) ID dans Azure
authority: "https://login.microsoftonline.com/9840a2a0-6ae1-4688-b03d-d2ec291be0f9", // Directory (tenant) ID
redirectUri: "http://localhost:5173"
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
}
};
export const loginRequest = {
scopes: ["User.Read"] // Permet de lire le profil utilisateur
scopes: [
"User.Read",
"User.Read.All", // Pour lire les profils des autres utilisateurs
"Group.Read.All", // Pour lire les groupes
"GroupMember.Read.All", // Pour lire les membres des groupes
"Mail.Send" //Envoyer les emails.
]
};

View File

@@ -1,14 +1,21 @@
import React, { useState, useEffect } from 'react';
import { X, Calendar, Clock, AlertCircle, RotateCcw } from 'lucide-react';
import { useMsal } from "@azure/msal-react";
import { loginRequest } from "../AuthConfig";
const NewLeaveRequestModal = ({
onClose,
availableLeaveCounters,
userId,
userEmail,
userName,
accessToken,
onRequestSubmitted,
preselectedStartDate = null,
preselectedEndDate = null,
preselectedType = null
preselectedType = null,
}) => {
const [formData, setFormData] = useState({
types: preselectedType ? [preselectedType] : [],
@@ -18,6 +25,7 @@ const NewLeaveRequestModal = ({
medicalDocuments: []
});
const [typeDistribution, setTypeDistribution] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
@@ -27,6 +35,25 @@ const NewLeaveRequestModal = ({
const [isOtherChecked, setIsOtherChecked] = useState(false);
const [otherLeaveType, setOtherLeaveType] = useState('');
const { instance, accounts } = useMsal();
// --- Helper pour garantir un token Graph valide
const ensureGraphToken = async () => {
if (!accounts[0]) throw new Error("Aucun utilisateur connecté");
const request = { ...loginRequest, account: accounts[0] };
try {
const response = await instance.acquireTokenSilent(request);
return response.accessToken;
} catch (err) {
console.warn("⚠️ Silent token failed, trying popup:", err);
const response = await instance.acquireTokenPopup(request);
return response.accessToken;
}
};
// Vérifier si des valeurs sont pré-sélectionnées
useEffect(() => {
if (preselectedStartDate || preselectedEndDate || preselectedType) {
@@ -273,55 +300,50 @@ const NewLeaveRequestModal = ({
}));
const requestData = {
EmployeeId: userId,
DateDebut: formData.startDate,
DateFin: formData.endDate,
Commentaire: formData.reason,
NombreJours: calculatedDays,
Repartition: repartition
Repartition: repartition,
Email: userEmail,
Nom: userName
};
console.log("Payload envoyé au backend :", JSON.stringify(requestData, null, 2));
const formDataToSend = new FormData();
formDataToSend.append('data', JSON.stringify(requestData));
// Ajouter les fichiers
formData.medicalDocuments.forEach((file, index) => {
formDataToSend.append(`medicalDocuments[]`, file);
});
console.log("📤 Payload envoyé au backend :", JSON.stringify(requestData, null, 2));
const response = await fetch('http://localhost/GTA/project/public/php/submitLeaveRequest.php', {
method: 'POST',
body: formDataToSend
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
});
const result = await response.json();
console.log("📥 Réponse backend :", result);
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.");
if (!result.success) {
setError(result.message || "Erreur lors de l'enregistrement");
return;
}
if (result.success) {
console.log("✅ Demande enregistrée (et mails envoyés côté PHP)");
// Fermer modal et rafraîchir
onRequestSubmitted?.();
onClose();
} else {
setError(result.message || 'Erreur lors de la soumission');
}
} catch (error) {
console.error('Erreur:', error);
setError('Erreur de connexion au serveur');
console.error('Erreur handleSubmit:', error);
setError("Erreur de connexion au serveur");
} finally {
setIsSubmitting(false);
}
};
const getTypeLabel = (type) => {
switch (type) {
case 'CP': return 'Congés payés';

View File

@@ -13,7 +13,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
switch (role) {
case 'Admin':
return 'bg-red-100 text-red-800';
case 'Manager':
case 'Validateur':
return 'bg-green-100 text-green-800';
default:
return 'bg-blue-100 text-blue-800';
@@ -109,7 +109,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
<span className="font-medium">Calendrier</span>
</Link>
{(user?.role === 'Manager' || user?.role === 'Admin' || user?.role === 'Employe') && (
{(user?.role === 'Validateur' || user?.role === 'Admin' || user?.role === 'Collaborateur') && (
<Link
to="/manager"
onClick={() => window.innerWidth < 1024 && onToggle()}
@@ -118,7 +118,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
>
<Users className="w-5 h-5" />
<span className="font-medium">
{user?.role === 'Employe' ? 'Mon équipe' : 'Équipe'}
{user?.role === 'Collaborateur' ? 'Mon équipe' : 'Équipe'}
</span>
</Link>
)}

View File

@@ -1,5 +1,6 @@
import React, { createContext, useContext, useState, useEffect } from 'react';
import * as msal from '@azure/msal-browser';
import { msalConfig, loginRequest } from '../AuthConfig';
const AuthContext = createContext();
@@ -11,70 +12,196 @@ export const useAuth = () => {
return context;
};
const msalConfig = {
auth: {
clientId: '4bb4cc24-bac3-427c-b02c-5d14fc67b561',
authority: 'https://login.microsoftonline.com/9840a2a0-6ae1-4688-b03d-d2ec291be0f9',
redirectUri: window.location.origin,
},
};
const msalInstance = new msal.PublicClientApplication(msalConfig);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [userGroups, setUserGroups] = useState([]);
const [isAuthorized, setIsAuthorized] = useState(false);
// Initialise MSAL au montage
// Fonction pour obtenir l'URL de l'API backend
const getApiUrl = (endpoint) => {
const possibleUrls = [
'http://localhost/GTA/project/public/php/',
'http://localhost:80/GTA/project/public/php/',
'http://localhost/GTA/public/php/',
'http://localhost/public/php/'
];
return possibleUrls[0] + endpoint; // Utilisez votre URL préférée
};
// Vérifier les groupes utilisateur via l'API backend
const checkUserAuthorization = async (userPrincipalName, accessToken) => {
try {
const response = await fetch(getApiUrl('check-user-groups.php'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({ userPrincipalName })
});
if (response.ok) {
const data = await response.json();
setUserGroups(data.groups || []);
setIsAuthorized(data.authorized || false);
return data;
}
return { authorized: false, groups: [] };
} catch (error) {
console.error('Erreur vérification groupes:', error);
return { authorized: false, groups: [] };
}
};
// Synchroniser l'utilisateur avec la base locale
const syncUserToDatabase = async (entraUser, accessToken) => {
try {
const response = await fetch(getApiUrl('check-user-groups.php'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({
entraUserId: entraUser.id,
userPrincipalName: entraUser.userPrincipalName,
email: entraUser.mail || entraUser.userPrincipalName,
displayName: entraUser.displayName,
givenName: entraUser.givenName,
surname: entraUser.surname,
jobTitle: entraUser.jobTitle,
department: entraUser.department,
officeLocation: entraUser.officeLocation
})
});
if (response.ok) {
return await response.json();
}
} catch (error) {
console.error('Erreur synchronisation utilisateur:', error);
}
return null;
};
// Initialisation MSAL
useEffect(() => {
const initializeMsal = async () => {
try {
await msalInstance.initialize();
// Vérifier si il y a un utilisateur connecté
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
// Essayer de récupérer un token silencieusement
try {
const response = await msalInstance.acquireTokenSilent({
...loginRequest,
account: accounts[0]
});
await handleSuccessfulAuth(response);
} catch (error) {
console.log('Token silent acquisition failed:', error);
}
}
} catch (error) {
console.error("Erreur d'initialisation MSAL:", error);
} finally {
setIsLoading(false);
}
};
initializeMsal();
const savedUser = localStorage.getItem('user');
if (savedUser) {
try {
setUser(JSON.parse(savedUser));
} catch (error) {
console.error("Erreur parsing utilisateur sauvegardé:", error);
localStorage.removeItem('user');
}
}
setIsLoading(false);
}, []);
// Gérer l'authentification réussie
const handleSuccessfulAuth = async (authResponse) => {
try {
const account = authResponse.account;
const accessToken = authResponse.accessToken;
// Récupérer profil Microsoft Graph
const graphResponse = await fetch('https://graph.microsoft.com/v1.0/me', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
let entraUser = {
id: account.homeAccountId,
displayName: account.name,
userPrincipalName: account.username,
mail: account.username
};
if (graphResponse.ok) {
const graphData = await graphResponse.json();
entraUser = { ...entraUser, ...graphData };
}
// 🔹 Synchroniser lutilisateur dans la DB
const syncResult = await syncUserToDatabase(entraUser, accessToken);
// 🚀 NEW : si admin → lancer full-sync.php
if (syncResult?.role === "Admin") {
try {
const syncResp = await fetch(getApiUrl('full-sync.php'), {
method: "POST",
headers: { "Authorization": `Bearer ${accessToken}` }
});
const syncData = await syncResp.json();
console.log("Résultat Full Sync:", syncData);
} catch (err) {
console.error("Erreur synchronisation groupes:", err);
}
}
// 🔹 Vérifier autorisation via groupes DB
const authResult = await checkUserAuthorization(entraUser.userPrincipalName, accessToken);
if (authResult.authorized) {
const userData = {
id: syncResult?.localUserId || entraUser.id,
entraUserId: entraUser.id,
name: entraUser.displayName,
prenom: entraUser.givenName || entraUser.displayName?.split(' ')[0] || '',
nom: entraUser.surname || entraUser.displayName?.split(' ')[1] || '',
email: entraUser.mail || entraUser.userPrincipalName,
userPrincipalName: entraUser.userPrincipalName,
role: syncResult?.role || 'Employe',
service: syncResult?.service || 'Non défini',
jobTitle: entraUser.jobTitle,
department: entraUser.department,
officeLocation: entraUser.officeLocation,
groups: authResult.groups
};
setUser(userData);
setIsAuthorized(true);
return true;
} else {
throw new Error('Utilisateur non autorisé - pas membre des groupes requis');
}
} catch (error) {
console.error('Erreur lors de la gestion de l\'authentification:', error);
throw error;
}
};
// Connexion classique (email/mot de passe)
const login = async (email, password) => {
try {
const possibleUrls = [
'http://localhost/GTA/project/public/php/login.php',
'http://localhost:80/GTA/project/public/php/login.php',
'http://localhost/GTA/public/php/login.php',
'http://localhost/public/php/login.php'
];
let response = null;
for (const url of possibleUrls) {
try {
console.log("Test URL:", url);
response = await fetch(url, {
const response = await fetch(getApiUrl('login.php'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, mot_de_passe: password }),
});
if (response.ok) break;
} catch {
continue;
}
}
if (!response || !response.ok) {
throw new Error('Aucune URL de connexion accessible');
if (!response.ok) {
throw new Error('Erreur de connexion');
}
const text = await response.text();
@@ -98,49 +225,79 @@ export const AuthProvider = ({ children }) => {
};
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
setIsAuthorized(true);
return true;
} else {
console.error("Échec connexion:", data.message);
return false;
}
return false;
} catch (error) {
console.error("Erreur de connexion:", error);
return false;
}
};
// Connexion Office 365
const loginWithO365 = async () => {
try {
const loginResponse = await msalInstance.loginPopup({
scopes: ["user.read"]
});
const account = loginResponse.account;
if (account) {
const userData = {
id: account.homeAccountId,
name: account.name,
email: account.username,
role: 'Employe',
service: 'Non défini',
};
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
const authResponse = await msalInstance.loginPopup(loginRequest);
await handleSuccessfulAuth(authResponse);
return true;
}
return false;
} catch (error) {
console.error('Erreur login Office 365:', error);
return false;
if (error.message?.includes('non autorisé')) {
throw new Error('Accès refusé: Vous n\'êtes pas membre d\'un groupe autorisé.');
}
throw error;
}
};
const logout = () => {
// Déconnexion
const logout = async () => {
try {
const accounts = msalInstance.getAllAccounts();
if (accounts.length > 0) {
await msalInstance.logoutPopup({
account: accounts[0]
});
}
} catch (error) {
console.error('Erreur lors de la déconnexion:', error);
} finally {
setUser(null);
localStorage.removeItem('user');
setUserGroups([]);
setIsAuthorized(false);
}
};
const value = { user, login, loginWithO365, logout, isLoading };
// Obtenir un token pour l'API
const getAccessToken = async () => {
try {
const accounts = msalInstance.getAllAccounts();
if (accounts.length === 0) {
throw new Error('Aucun compte connecté');
}
const response = await msalInstance.acquireTokenSilent({
...loginRequest,
account: accounts[0]
});
return response.accessToken;
} catch (error) {
console.error('Erreur obtention token:', error);
return null;
}
};
const value = {
user,
userGroups,
isAuthorized,
login,
loginWithO365,
logout,
isLoading,
getAccessToken
};
return (
<AuthContext.Provider value={value}>

View File

@@ -1,10 +1,17 @@
import { StrictMode } from 'react';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './index.css';
import { MsalProvider } from "@azure/msal-react";
import { PublicClientApplication } from "@azure/msal-browser";
import { msalConfig } from "./AuthConfig";
const msalInstance = new PublicClientApplication(msalConfig);
createRoot(document.getElementById('root')).render(
<StrictMode>
<MsalProvider instance={msalInstance}>
<App />
</MsalProvider>
</StrictMode>
);

View File

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

View File

@@ -1,11 +1,14 @@
import React, { useState, useEffect } from 'react';
import { useAuth } from '../context/AuthContext';
import Sidebar from '../components/Sidebar';
import { Calendar as CalendarIcon, Clock, Users, TrendingUp, Plus, Settings, RefreshCw, Menu, FileText } from 'lucide-react';
import { Calendar as CalendarIcon, Clock, Plus, Settings, RefreshCw, Menu, FileText } from 'lucide-react';
import NewLeaveRequestModal from '../components/NewLeaveRequestModal';
import { useMsal } from "@azure/msal-react";
import { loginRequest } from "../authConfig";
const Dashboard = () => {
const { user } = useAuth();
const [graphToken, setGraphToken] = useState(null);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [leaveCounters, setLeaveCounters] = useState({
availableCP: 0,
@@ -20,54 +23,82 @@ const Dashboard = () => {
const [recentRequests, setRecentRequests] = useState([]);
const [allRequests, setAllRequests] = useState([]);
const { instance, accounts } = useMsal();
// Récupération du bon ID utilisateur (CollaborateurAD)
const userId = user?.id || user?.CollaborateurADId || user?.ID;
useEffect(() => {
if (user?.id) {
console.log("🔎 Utilisateur chargé dans Dashboard:", user);
}, [user]);
useEffect(() => {
if (accounts.length > 0) {
const request = {
...loginRequest,
account: accounts[0],
};
instance.acquireTokenSilent(request)
.then((response) => {
console.log("✅ Token Graph récupéré (silent):", response.accessToken);
setGraphToken(response.accessToken);
})
.catch(async (err) => {
console.warn("⚠️ Silent échoué, on tente un popup:", err);
try {
const tokenResponse = await instance.acquireTokenPopup(request);
console.log("✅ Token Graph récupéré (popup):", tokenResponse.accessToken);
setGraphToken(tokenResponse.accessToken);
} catch (popupErr) {
console.error("❌ Impossible d'obtenir un token:", popupErr);
}
});
}
}, [accounts, instance]);
// Vérification rapide du token
useEffect(() => {
if (graphToken) {
console.log("🔎 Token prêt ?",
graphToken.split(".").length === 3 ? "✅ JWT valide" : "❌ Token invalide: " + graphToken
);
}
}, [graphToken]);
// 🔄 Récupération compteurs et demandes
useEffect(() => {
if (userId) {
fetchLeaveCounters();
fetchAllRequests();
}
}, [user]);
}, [userId]);
const fetchLeaveCounters = async () => {
try {
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${user.id}`;
console.log(' Dashboard - Récupération des compteurs:', url);
console.log(' Dashboard - User ID utilisé:', user.id);
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${userId}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
const text = await response.text();
console.log(' Dashboard - Réponse brute compteurs:', text);
console.log(' Dashboard - Longueur de la réponse:', text.length);
console.log(' Dashboard - Premiers 500 caractères:', text.substring(0, 500));
console.log("🔎 Réponse brute getLeaveCounters:", text);
let data;
try {
data = JSON.parse(text);
} catch (parseError) {
console.error(' Dashboard - Erreur parsing JSON:', parseError);
console.error(' Dashboard - Texte qui a causé l\'erreur:', text);
throw new Error('Réponse PHP invalide: ' + text.substring(0, 200));
} catch (e) {
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
}
console.log(' Dashboard - Compteurs parsés:', data);
if (data.success) {
console.log(' Dashboard - Compteurs récupérés:', data.counters);
setLeaveCounters(data.counters);
} else {
console.error(' Dashboard - Erreur API compteurs:', data.message);
console.error(' Dashboard - Données complètes:', data);
throw new Error('API Error: ' + (data.message || 'Erreur inconnue'));
throw new Error(data.message || "Erreur lors de la récupération des compteurs");
}
} catch (error) {
console.error('💥 Dashboard - Erreur lors de la récupération des compteurs:', error);
// Fallback avec des données par défaut
console.log(' Dashboard - Utilisation des données par défaut');
console.error("💥 Erreur compteurs:", error);
setLeaveCounters({
availableCP: 25,
availableRTT: 10,
@@ -75,78 +106,63 @@ const Dashboard = () => {
rttInProcess: 0,
absenteism: 0
});
}
} finally {
setIsLoading(false);
}
};
const fetchAllRequests = async () => {
console.log(' Dashboard - Début fetchAllRequests pour user:', user?.id);
try {
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${user.id}`;
console.log(' Dashboard - URL appelée:', url);
const url = `http://localhost/GTA/project/public/php/getRequests.php?user_id=${userId}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erreur HTTP: ${response.status}`);
}
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
const text = await response.text();
console.log(' Dashboard - Réponse brute:', text);
console.log("🔎 Réponse brute getRequests:", text);
const data = JSON.parse(text);
console.log(' Dashboard - Données parsées:', data);
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
}
if (data.success) {
console.log('Dashboard - Demandes récupérées:', data.requests?.length || 0);
setAllRequests(data.requests || []);
setRecentRequests(data.requests?.slice(0, 3) || []);
} else {
throw new Error(data.message || 'Erreur lors de la récupération des demandes');
throw new Error(data.message || "Erreur lors de la récupération des demandes");
}
} catch (error) {
console.error(' Dashboard - Erreur lors de la récupération des demandes:', error);
// En cas d'erreur, on garde des tableaux vides
console.error("💥 Erreur demandes:", error);
setAllRequests([]);
setRecentRequests([]);
}
};
const handleResetCounters = async () => {
if (!confirm(' ATTENTION !\n\nCette action va réinitialiser TOUS les compteurs de congés selon les règles de gestion :\n\n• Congés Payés : 25 jours (exercice 01/06 au 31/05)\n• RTT : 10 jours pour 2025 (exercice 01/01 au 31/12)\n• Congés Maladie : 0 jours\n\nCette action est IRRÉVERSIBLE !\n\nÊtes-vous sûr de vouloir continuer ?')) {
return;
}
if (!confirm("⚠️ Voulez-vous vraiment réinitialiser les compteurs ?")) return;
try {
const response = await fetch('http://localhost/GTA/project/public/php/resetLeaveCounters.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ manual_reset: true }),
});
const data = await response.json();
if (data.success) {
alert(` Réinitialisation réussie !\n\n• ${data.details.employees_updated} employés mis à jour\n• Exercice CP : ${data.details.leave_year}\n• Année RTT : ${data.details.rtt_year}\n• Date : ${data.details.reset_date}`);
alert("✅ Réinitialisation réussie !");
fetchLeaveCounters();
} else {
alert(` Erreur lors de la réinitialisation :\n${data.message}`);
alert(` Erreur : ${data.message}`);
}
} catch (error) {
console.error('Erreur:', error);
alert(' Erreur de connexion au serveur');
console.error("Erreur:", error);
alert("❌ Erreur serveur");
}
};
const openManualResetPage = () => {
window.open('http://localhost/GTA/project/public/php/manualResetCounters.php', '_blank');
};
const getStatusColor = (status) => {
switch (status) {
case 'Approuvé':
@@ -392,7 +408,10 @@ const Dashboard = () => {
<NewLeaveRequestModal
onClose={() => setShowNewRequestModal(false)}
availableLeaveCounters={leaveCounters}
userId={user?.id}
accessToken={graphToken}
userId={userId}
userEmail={user.email}
userName={`${user.prenom} ${user.nom}`}
onRequestSubmitted={() => {
fetchLeaveCounters();
fetchAllRequests();

View File

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

View File

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

View File

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