changement au niveau de requetes adaptés aux collaborateurs AD
This commit is contained in:
32
Dockerfile.frontend
Normal file
32
Dockerfile.frontend
Normal 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
12
docker-compose.yml
Normal 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"
|
||||||
14
project/public/php/Dockerfile.backend
Normal file
14
project/public/php/Dockerfile.backend
Normal 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
|
||||||
147
project/public/php/check-user-groups.php
Normal file
147
project/public/php/check-user-groups.php
Normal 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 l’utilisateur 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
20
project/public/php/db.php
Normal 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 l’encodage en UTF-8 (pour accents, etc.)
|
||||||
|
$conn->set_charset("utf8mb4");
|
||||||
@@ -27,7 +27,6 @@ if ($conn->connect_error) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Récupération ID manager
|
// Récupération ID manager
|
||||||
$managerId = $_GET['SuperieurId'] ?? null;
|
$managerId = $_GET['SuperieurId'] ?? null;
|
||||||
if (!$managerId) {
|
if (!$managerId) {
|
||||||
@@ -36,31 +35,29 @@ if (!$managerId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sql = "
|
$sql = "
|
||||||
SELECT
|
SELECT
|
||||||
dc.Id,
|
dc.Id,
|
||||||
dc.DateDebut,
|
dc.DateDebut,
|
||||||
dc.DateFin,
|
dc.DateFin,
|
||||||
dc.Statut,
|
dc.Statut,
|
||||||
dc.DateDemande,
|
dc.DateDemande,
|
||||||
dc.Commentaire,
|
dc.Commentaire,
|
||||||
dc.DocumentJoint,
|
dc.DocumentJoint,
|
||||||
dc.EmployeeId,
|
dc.CollaborateurADId AS employee_id,
|
||||||
CONCAT(u.Prenom, ' ', u.Nom) as employee_name,
|
CONCAT(ca.Prenom, ' ', ca.Nom) as employee_name,
|
||||||
u.Email as employee_email,
|
ca.Email as employee_email,
|
||||||
tc.Nom as type
|
tc.Nom as type
|
||||||
FROM DemandeConge dc
|
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 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 = ?
|
WHERE hv.SuperieurId = ?
|
||||||
ORDER BY dc.DateDemande DESC
|
ORDER BY dc.DateDemande DESC
|
||||||
";
|
";
|
||||||
|
|
||||||
$stmt = $conn->prepare($sql);
|
$stmt = $conn->prepare($sql);
|
||||||
$stmt->bind_param("i", $managerId);
|
$stmt->bind_param("i", $managerId);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
|
|
||||||
// Manquant dans ton code
|
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
$requests = [];
|
$requests = [];
|
||||||
@@ -78,7 +75,7 @@ while ($row = $result->fetch_assoc()) {
|
|||||||
|
|
||||||
$requests[] = [
|
$requests[] = [
|
||||||
"id" => (int)$row['Id'],
|
"id" => (int)$row['Id'],
|
||||||
"employee_id" => (int)$row['EmployeeId'],
|
"employee_id" => (int)$row['employee_id'],
|
||||||
"employee_name" => $row['employee_name'],
|
"employee_name" => $row['employee_name'],
|
||||||
"employee_email" => $row['employee_email'],
|
"employee_email" => $row['employee_email'],
|
||||||
"type" => $row['type'],
|
"type" => $row['type'],
|
||||||
|
|||||||
51
project/public/php/getEmploye.php
Normal file
51
project/public/php/getEmploye.php
Normal 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();
|
||||||
|
?>
|
||||||
66
project/public/php/getEmployeRequest.php
Normal file
66
project/public/php/getEmployeRequest.php
Normal 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();
|
||||||
|
?>
|
||||||
@@ -27,22 +27,22 @@ $leaveYear = getLeaveYear();
|
|||||||
$rttYear = getRTTYear();
|
$rttYear = getRTTYear();
|
||||||
$currentDate = date('Y-m-d');
|
$currentDate = date('Y-m-d');
|
||||||
|
|
||||||
// --- Soldes initiaux (CompteurConges) restent inchangés ---
|
// --- Soldes initiaux (CompteurConges pour CollaborateurAD) ---
|
||||||
$cpSolde = 0; $rttSolde = 0; $absSolde = 0;
|
$cpSolde = 0; $rttSolde = 0; $absSolde = 0;
|
||||||
if ($cpTypeId !== null) {
|
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();
|
$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) {
|
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();
|
$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) {
|
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();
|
$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;
|
$cpInProcess = 0;
|
||||||
if ($cpTypeId !== null) {
|
if ($cpTypeId !== null) {
|
||||||
$sql = "
|
$sql = "
|
||||||
@@ -50,13 +50,13 @@ if ($cpTypeId !== null) {
|
|||||||
FROM DemandeConge dc
|
FROM DemandeConge dc
|
||||||
LEFT JOIN DemandeCongeType dct
|
LEFT JOIN DemandeCongeType dct
|
||||||
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
||||||
WHERE dc.EmployeeId = ?
|
WHERE dc.CollaborateurADId = ?
|
||||||
AND dc.Statut IN ('En attente','Validée')
|
AND dc.Statut IN ('En attente','Validée')
|
||||||
AND dc.DateFin >= ?
|
AND dc.DateFin >= ?
|
||||||
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
||||||
";
|
";
|
||||||
$s = $conn->prepare($sql);
|
$s = $conn->prepare($sql);
|
||||||
$s->bind_param("iiss", $cpTypeId, $userId, $currentDate, $cpTypeId);
|
$s->bind_param("iisi", $cpTypeId, $userId, $currentDate, $cpTypeId);
|
||||||
$s->execute();
|
$s->execute();
|
||||||
$res = $s->get_result();
|
$res = $s->get_result();
|
||||||
while ($r = $res->fetch_assoc()) {
|
while ($r = $res->fetch_assoc()) {
|
||||||
@@ -69,7 +69,7 @@ if ($cpTypeId !== null) {
|
|||||||
$s->close();
|
$s->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Calcul RTT in process (même logique) ---
|
// --- Calcul RTT en cours ---
|
||||||
$rttInProcess = 0;
|
$rttInProcess = 0;
|
||||||
if ($rttTypeId !== null) {
|
if ($rttTypeId !== null) {
|
||||||
$sql = "
|
$sql = "
|
||||||
@@ -77,13 +77,13 @@ if ($rttTypeId !== null) {
|
|||||||
FROM DemandeConge dc
|
FROM DemandeConge dc
|
||||||
LEFT JOIN DemandeCongeType dct
|
LEFT JOIN DemandeCongeType dct
|
||||||
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
||||||
WHERE dc.EmployeeId = ?
|
WHERE dc.CollaborateurADId = ?
|
||||||
AND dc.Statut IN ('En attente','Validée')
|
AND dc.Statut IN ('En attente','Validée')
|
||||||
AND dc.DateFin >= ?
|
AND dc.DateFin >= ?
|
||||||
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
||||||
";
|
";
|
||||||
$s = $conn->prepare($sql);
|
$s = $conn->prepare($sql);
|
||||||
$s->bind_param("iiss", $rttTypeId, $userId, $currentDate, $rttTypeId);
|
$s->bind_param("iisi", $rttTypeId, $userId, $currentDate, $rttTypeId);
|
||||||
$s->execute();
|
$s->execute();
|
||||||
$res = $s->get_result();
|
$res = $s->get_result();
|
||||||
while ($r = $res->fetch_assoc()) {
|
while ($r = $res->fetch_assoc()) {
|
||||||
@@ -96,7 +96,7 @@ if ($rttTypeId !== null) {
|
|||||||
$s->close();
|
$s->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Calcul absenteisme (validation) : priorité DemandeCongeType, fallback = DATEDIFF+1 ---
|
// --- Calcul absenteisme validé ---
|
||||||
$absenteism = 0;
|
$absenteism = 0;
|
||||||
if ($absTypeId !== null) {
|
if ($absTypeId !== null) {
|
||||||
$sql = "
|
$sql = "
|
||||||
@@ -104,7 +104,7 @@ if ($absTypeId !== null) {
|
|||||||
FROM DemandeConge dc
|
FROM DemandeConge dc
|
||||||
LEFT JOIN DemandeCongeType dct
|
LEFT JOIN DemandeCongeType dct
|
||||||
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
|
||||||
WHERE dc.EmployeeId = ?
|
WHERE dc.CollaborateurADId = ?
|
||||||
AND dc.Statut = 'Validée'
|
AND dc.Statut = 'Validée'
|
||||||
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
|
||||||
";
|
";
|
||||||
@@ -116,7 +116,6 @@ if ($absTypeId !== null) {
|
|||||||
if ($r['NombreJours'] !== null) {
|
if ($r['NombreJours'] !== null) {
|
||||||
$absenteism += (float)$r['NombreJours'];
|
$absenteism += (float)$r['NombreJours'];
|
||||||
} else {
|
} else {
|
||||||
// fallback : DATEDIFF + 1
|
|
||||||
$d1 = new DateTime($r['DateDebut']); $d2 = new DateTime($r['DateFin']);
|
$d1 = new DateTime($r['DateDebut']); $d2 = new DateTime($r['DateFin']);
|
||||||
$absenteism += ($d2->diff($d1)->days + 1);
|
$absenteism += ($d2->diff($d1)->days + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ function getWorkingDays($startDate, $endDate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Récupérer le service du manager
|
// Récupérer le service du manager (table CollaborateurAD)
|
||||||
$queryManagerService = "SELECT ServiceId FROM Users WHERE ID = ?";
|
$queryManagerService = "SELECT ServiceId FROM CollaborateurAD WHERE id = ?";
|
||||||
$stmtManager = $conn->prepare($queryManagerService);
|
$stmtManager = $conn->prepare($queryManagerService);
|
||||||
$stmtManager->bind_param("i", $managerId);
|
$stmtManager->bind_param("i", $managerId);
|
||||||
$stmtManager->execute();
|
$stmtManager->execute();
|
||||||
@@ -75,19 +75,19 @@ try {
|
|||||||
dc.Statut,
|
dc.Statut,
|
||||||
dc.DateDemande,
|
dc.DateDemande,
|
||||||
dc.Commentaire,
|
dc.Commentaire,
|
||||||
dc.EmployeeId,
|
dc.CollaborateurADId,
|
||||||
CONCAT(u.Prenom, ' ', u.Nom) as employee_name,
|
CONCAT(ca.prenom, ' ', ca.nom) as employee_name,
|
||||||
u.Email as employee_email,
|
ca.email as employee_email,
|
||||||
GROUP_CONCAT(tc.Nom ORDER BY tc.Nom SEPARATOR ', ') as types
|
GROUP_CONCAT(tc.Nom ORDER BY tc.Nom SEPARATOR ', ') as types
|
||||||
FROM DemandeConge dc
|
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)
|
JOIN TypeConge tc ON FIND_IN_SET(tc.Id, dc.TypeCongeId)
|
||||||
WHERE u.ServiceId = ?
|
WHERE ca.ServiceId = ?
|
||||||
AND dc.Statut = 'En attente'
|
AND dc.Statut = 'En attente'
|
||||||
AND u.ID != ?
|
AND ca.id != ?
|
||||||
GROUP BY
|
GROUP BY
|
||||||
dc.Id, dc.DateDebut, dc.DateFin, dc.Statut, dc.DateDemande,
|
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
|
ORDER BY dc.DateDemande ASC
|
||||||
";
|
";
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ try {
|
|||||||
|
|
||||||
$requests[] = [
|
$requests[] = [
|
||||||
'id' => (int)$row['Id'],
|
'id' => (int)$row['Id'],
|
||||||
'employee_id' => (int)$row['EmployeeId'],
|
'employee_id' => (int)$row['CollaborateurADId'],
|
||||||
'employee_name' => $row['employee_name'],
|
'employee_name' => $row['employee_name'],
|
||||||
'employee_email' => $row['employee_email'],
|
'employee_email' => $row['employee_email'],
|
||||||
'type' => $row['types'], // ex: "Congé payé, RTT"
|
'type' => $row['types'], // ex: "Congé payé, RTT"
|
||||||
|
|||||||
@@ -53,31 +53,31 @@ function getWorkingDays($startDate, $endDate) {
|
|||||||
try {
|
try {
|
||||||
// Requête multi-types
|
// Requête multi-types
|
||||||
$query = "
|
$query = "
|
||||||
SELECT
|
SELECT
|
||||||
dc.Id,
|
dc.Id,
|
||||||
dc.DateDebut,
|
dc.DateDebut,
|
||||||
dc.DateFin,
|
dc.DateFin,
|
||||||
dc.Statut,
|
dc.Statut,
|
||||||
dc.DateDemande,
|
dc.DateDemande,
|
||||||
dc.Commentaire,
|
dc.Commentaire,
|
||||||
dc.Validateur,
|
dc.Validateur,
|
||||||
dc.DocumentJoint,
|
dc.DocumentJoint,
|
||||||
GROUP_CONCAT(tc.Nom ORDER BY tc.Nom SEPARATOR ', ') AS TypeConges
|
GROUP_CONCAT(tc.Nom ORDER BY tc.Nom SEPARATOR ', ') AS TypeConges
|
||||||
FROM DemandeConge dc
|
FROM DemandeConge dc
|
||||||
JOIN TypeConge tc ON FIND_IN_SET(tc.Id, dc.TypeCongeId)
|
JOIN TypeConge tc ON FIND_IN_SET(tc.Id, dc.TypeCongeId)
|
||||||
WHERE dc.EmployeeId = ?
|
WHERE (dc.EmployeeId = ? OR dc.CollaborateurADId = ?)
|
||||||
GROUP BY
|
GROUP BY
|
||||||
dc.Id, dc.DateDebut, dc.DateFin, dc.Statut, dc.DateDemande,
|
dc.Id, dc.DateDebut, dc.DateFin, dc.Statut, dc.DateDemande,
|
||||||
dc.Commentaire, dc.Validateur, dc.DocumentJoint
|
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) {
|
if (!$stmt) {
|
||||||
throw new Exception("Erreur préparation SQL : " . $conn->error);
|
throw new Exception("Erreur préparation SQL : " . $conn->error);
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->bind_param("i", $userId);
|
$stmt->bind_param("ii", $userId, $userId);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$result = $stmt->get_result();
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?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-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");
|
||||||
@@ -11,7 +11,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
|||||||
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
// Log des erreurs pour debug
|
// Debug erreurs
|
||||||
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);
|
||||||
@@ -24,7 +24,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 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"]);
|
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données"]);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
@@ -36,11 +36,11 @@ if ($managerId === null) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
error_log("getTeamMembers - Manager ID: $managerId");
|
error_log("getTeamMembersAD - Manager ID: $managerId");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// D'abord, récupérer le service du manager
|
// 🔹 1. Récupérer le ServiceId du manager
|
||||||
$queryManagerService = "SELECT ServiceId FROM Users WHERE ID = ?";
|
$queryManagerService = "SELECT ServiceId FROM CollaborateurAD WHERE id = ?";
|
||||||
$stmtManager = $conn->prepare($queryManagerService);
|
$stmtManager = $conn->prepare($queryManagerService);
|
||||||
$stmtManager->bind_param("i", $managerId);
|
$stmtManager->bind_param("i", $managerId);
|
||||||
$stmtManager->execute();
|
$stmtManager->execute();
|
||||||
@@ -48,22 +48,22 @@ try {
|
|||||||
|
|
||||||
if ($managerRow = $resultManager->fetch_assoc()) {
|
if ($managerRow = $resultManager->fetch_assoc()) {
|
||||||
$serviceId = $managerRow['ServiceId'];
|
$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 = "
|
$queryTeam = "
|
||||||
SELECT
|
SELECT
|
||||||
u.ID as id,
|
c.id,
|
||||||
u.Nom as nom,
|
c.nom,
|
||||||
u.Prenom as prenom,
|
c.prenom,
|
||||||
u.Email as email,
|
c.email,
|
||||||
u.Role as role,
|
c.role,
|
||||||
u.DateEmbauche as date_embauche,
|
|
||||||
s.Nom as service_name
|
s.Nom as service_name
|
||||||
FROM Users u
|
FROM CollaborateurAD c
|
||||||
JOIN Services s ON u.ServiceId = s.Id
|
JOIN Services s ON c.ServiceId = s.Id
|
||||||
WHERE u.ServiceId = ? AND u.ID != ? AND u.Actif = 1
|
WHERE c.ServiceId = ? AND c.id != ?
|
||||||
ORDER BY u.Prenom, u.Nom
|
ORDER BY c.prenom, c.nom
|
||||||
";
|
";
|
||||||
|
|
||||||
$stmtTeam = $conn->prepare($queryTeam);
|
$stmtTeam = $conn->prepare($queryTeam);
|
||||||
@@ -79,12 +79,12 @@ try {
|
|||||||
'prenom' => $row['prenom'],
|
'prenom' => $row['prenom'],
|
||||||
'email' => $row['email'],
|
'email' => $row['email'],
|
||||||
'role' => $row['role'],
|
'role' => $row['role'],
|
||||||
'date_embauche' => $row['date_embauche'],
|
|
||||||
'service_name' => $row['service_name']
|
'service_name' => $row['service_name']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
error_log("getTeamMembers - Membres trouvés: " . count($teamMembers));
|
error_log("getTeamMembersAD - Membres trouvés: " . count($teamMembers));
|
||||||
|
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => true,
|
"success" => true,
|
||||||
@@ -95,7 +95,7 @@ try {
|
|||||||
|
|
||||||
$stmtTeam->close();
|
$stmtTeam->close();
|
||||||
} else {
|
} else {
|
||||||
error_log("getTeamMembers - Manager non trouvé: $managerId");
|
error_log("getTeamMembersAD - Manager non trouvé: $managerId");
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => false,
|
"success" => false,
|
||||||
"message" => "Manager non trouvé"
|
"message" => "Manager non trouvé"
|
||||||
@@ -105,7 +105,7 @@ try {
|
|||||||
$stmtManager->close();
|
$stmtManager->close();
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
error_log("Erreur getTeamMembers: " . $e->getMessage());
|
error_log("Erreur getTeamMembersAD: " . $e->getMessage());
|
||||||
echo json_encode([
|
echo json_encode([
|
||||||
"success" => false,
|
"success" => false,
|
||||||
"message" => "Erreur lors de la récupération de l'équipe: " . $e->getMessage()
|
"message" => "Erreur lors de la récupération de l'équipe: " . $e->getMessage()
|
||||||
@@ -113,4 +113,4 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$conn->close();
|
$conn->close();
|
||||||
?>
|
?>
|
||||||
|
|||||||
128
project/public/php/initial-sync.php
Normal file
128
project/public/php/initial-sync.php
Normal 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();
|
||||||
|
?>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
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, Authorization");
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
@@ -16,59 +16,137 @@ $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) {
|
||||||
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);
|
$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'] ?? '';
|
||||||
|
$entraUserId = $data['entraUserId'] ?? '';
|
||||||
|
$userPrincipalName = $data['userPrincipalName'] ?? '';
|
||||||
|
|
||||||
$query = "
|
$headers = getallheaders();
|
||||||
SELECT
|
$accessToken = isset($headers['Authorization']) ? str_replace('Bearer ', '', $headers['Authorization']) : '';
|
||||||
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 = ?
|
|
||||||
";
|
|
||||||
|
|
||||||
$stmt = $conn->prepare($query);
|
// ======================================================
|
||||||
|
// 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 ($stmt === false) {
|
if ($result->num_rows === 0) {
|
||||||
die(json_encode(["success" => false, "message" => "Erreur de préparation de la requête : " . $conn->error]));
|
echo json_encode(["success" => false, "message" => "Utilisateur non autorisé (pas dans l'annuaire)"]);
|
||||||
}
|
exit();
|
||||||
|
}
|
||||||
$stmt->bind_param("ss", $email, $mot_de_passe);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
$result = $stmt->get_result();
|
|
||||||
|
|
||||||
if ($result->num_rows === 1) {
|
|
||||||
$user = $result->fetch_assoc();
|
$user = $result->fetch_assoc();
|
||||||
|
|
||||||
echo json_encode([
|
// Récupérer groupes de l’utilisateur via Graph
|
||||||
"success" => true,
|
$ch = curl_init("https://graph.microsoft.com/v1.0/users/$userPrincipalName/memberOf?\$select=id");
|
||||||
"message" => "Connexion réussie.",
|
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $accessToken"]);
|
||||||
"user" => [
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
"id" => $user['ID'],
|
$response = curl_exec($ch);
|
||||||
"prenom" => $user['Prenom'],
|
curl_close($ch);
|
||||||
"nom" => $user['Nom'],
|
|
||||||
"email" => $user['Email'],
|
$dataGraph = json_decode($response, true);
|
||||||
"role" => $user['Role'],
|
$userGroups = [];
|
||||||
"service" => $user['ServiceNom'] ?? 'Non défini'
|
if (isset($dataGraph['value'])) {
|
||||||
]
|
foreach ($dataGraph['value'] as $g) {
|
||||||
]);
|
if (isset($g['id'])) {
|
||||||
} else {
|
$userGroups[] = $g['id'];
|
||||||
echo json_encode(["success" => false, "message" => "Identifiants incorrects."]);
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
$stmt->close();
|
// ======================================================
|
||||||
|
// 2️⃣ Mode local (login/password → Users)
|
||||||
|
// ======================================================
|
||||||
|
if ($email && $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 = ?
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $conn->prepare($query);
|
||||||
|
|
||||||
|
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();
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
|
||||||
|
if ($result->num_rows === 1) {
|
||||||
|
$user = $result->fetch_assoc();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"message" => "Connexion réussie (mode local)",
|
||||||
|
"user" => [
|
||||||
|
"id" => $user['ID'],
|
||||||
|
"prenom" => $user['Prenom'],
|
||||||
|
"nom" => $user['Nom'],
|
||||||
|
"email" => $user['Email'],
|
||||||
|
"role" => $user['Role'],
|
||||||
|
"service" => $user['ServiceNom'] ?? 'Non défini'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
echo json_encode(["success" => false, "message" => "Identifiants incorrects (mode local)"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt->close();
|
||||||
|
$conn->close();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================
|
||||||
|
// 3️⃣ Aucun mode ne correspond
|
||||||
|
// ======================================================
|
||||||
|
echo json_encode(["success" => false, "message" => "Aucune méthode de connexion fournie"]);
|
||||||
$conn->close();
|
$conn->close();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -1,100 +1,293 @@
|
|||||||
<?php
|
<?php
|
||||||
// (headers, connexion, lecture FormData ou JSON — pareil que précédemment)
|
ob_clean();
|
||||||
ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL);
|
header("Content-Type: application/json; charset=UTF-8");
|
||||||
header("Access-Control-Allow-Origin: *");
|
header("Access-Control-Allow-Origin: http://localhost:5173");
|
||||||
header("Access-Control-Allow-Methods: POST, OPTIONS");
|
header("Access-Control-Allow-Methods: GET, 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); exit(); }
|
|
||||||
header("Content-Type: application/json");
|
|
||||||
|
|
||||||
$host="192.168.0.4"; $dbname="DemandeConge"; $username="wpuser"; $password="-2b/)ru5/Bi8P[7_";
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
$conn = new mysqli($host,$username,$password,$dbname);
|
http_response_code(200);
|
||||||
if ($conn->connect_error) { echo json_encode(["success"=>false,"message"=>"Erreur DB: ".$conn->connect_error]); exit(); }
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
// Lecture JSON (support FormData via $_POST['data'])
|
// Debug
|
||||||
if (isset($_POST['data'])) {
|
ini_set('display_errors', 1);
|
||||||
$data = json_decode($_POST['data'], true);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dateDebut = $data['DateDebut'];
|
||||||
|
$dateFin = $data['DateFin'];
|
||||||
|
$commentaire = $data['Commentaire'] ?? '';
|
||||||
|
$numDays = (float)$data['NombreJours'];
|
||||||
|
$userEmail = $data['Email'];
|
||||||
|
$userName = $data['Nom'];
|
||||||
|
$statut = 'En attente';
|
||||||
|
$currentDate = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// 🔎 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 {
|
} else {
|
||||||
$input = file_get_contents('php://input');
|
$stmt = $pdo->prepare("SELECT ID FROM Users WHERE Email = :email LIMIT 1");
|
||||||
$data = json_decode($input, true);
|
$stmt->execute([':email'=>$userEmail]);
|
||||||
}
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
if ($data === null) {
|
|
||||||
echo json_encode(["success"=>false,"message"=>"JSON invalide"]); $conn->close(); exit();
|
if (!$user) {
|
||||||
|
echo json_encode(["success"=>false,"message"=>"Aucun collaborateur trouvé pour $userEmail"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$employeeId = (int)$user['ID'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vérifs minimales
|
// 🔎 Résoudre les IDs des types de congés
|
||||||
if (!isset($data['EmployeeId'],$data['DateDebut'],$data['DateFin'],$data['Repartition'],$data['NombreJours'])) {
|
|
||||||
echo json_encode(["success"=>false,"message"=>"Données manquantes"]); $conn->close(); exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
$employeeId = (int)$data['EmployeeId'];
|
|
||||||
$dateDebut = $data['DateDebut'];
|
|
||||||
$dateFin = $data['DateFin'];
|
|
||||||
$commentaire= $data['Commentaire'] ?? '';
|
|
||||||
$numDays = (float)$data['NombreJours'];
|
|
||||||
$statut = 'En attente';
|
|
||||||
$currentDate= date('Y-m-d H:i:s');
|
|
||||||
|
|
||||||
// 1) Construire la liste d'IDs pour TypeCongeId (CSV) (compatibilité)
|
|
||||||
$typeIds = [];
|
$typeIds = [];
|
||||||
foreach ($data['Repartition'] as $rep) {
|
foreach ($data['Repartition'] as $rep) {
|
||||||
$code = $rep['TypeConge']; // CP, RTT, ABS ou texte libre
|
$code = $rep['TypeConge'];
|
||||||
switch ($code) {
|
switch ($code) {
|
||||||
case 'CP': $name = 'Congé payé'; break;
|
case 'CP': $name = 'Congé payé'; break;
|
||||||
case 'RTT': $name = 'RTT'; break;
|
case 'RTT': $name = 'RTT'; break;
|
||||||
case 'ABS': $name = 'Congé maladie'; break;
|
case 'ABS': $name = 'Congé maladie'; break;
|
||||||
default: $name = $code; break;
|
default: $name = $code; break;
|
||||||
|
}
|
||||||
|
$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'];
|
||||||
}
|
}
|
||||||
$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();
|
|
||||||
}
|
}
|
||||||
if (empty($typeIds)) { echo json_encode(["success"=>false,"message"=>"Aucun type valide"]); $conn->close(); exit(); }
|
if (empty($typeIds)) {
|
||||||
|
echo json_encode(["success"=>false,"message"=>"Aucun type de congé valide"]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
$typeCongeIdCsv = implode(',', $typeIds);
|
$typeCongeIdCsv = implode(',', $typeIds);
|
||||||
|
|
||||||
// 2) Insertion unique dans DemandeConge
|
// ✅ Insertion DemandeConge
|
||||||
$insert = $conn->prepare("INSERT INTO DemandeConge (EmployeeId, DateDebut, DateFin, TypeCongeId, Statut, DateDemande, Commentaire, Validateur, NombreJours) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
$sql = "INSERT INTO DemandeConge
|
||||||
$validParam = '';
|
(EmployeeId, CollaborateurADId, DateDebut, DateFin, TypeCongeId, Statut, DateDemande, Commentaire, Validateur, NombreJours)
|
||||||
$insert->bind_param("isssssssd", $employeeId, $dateDebut, $dateFin, $typeCongeIdCsv, $statut, $currentDate, $commentaire, $validParam, $numDays);
|
VALUES (:eid, :cid, :dd, :df, :tc, :st, :cd, :com, :val, :nj)";
|
||||||
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)
|
$stmt = $pdo->prepare($sql);
|
||||||
$insertType = $conn->prepare("INSERT INTO DemandeCongeType (DemandeCongeId, TypeCongeId, NombreJours) VALUES (?, ?, ?)");
|
$stmt->execute([
|
||||||
if (!$insertType) {
|
':eid'=> $isAD ? 0 : $employeeId,
|
||||||
echo json_encode(["success"=>false,"message"=>"Erreur préparation DemandeCongeType: ".$conn->error]); $conn->close(); exit();
|
':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) {
|
foreach ($data['Repartition'] as $rep) {
|
||||||
$code = $rep['TypeConge'];
|
|
||||||
$jours = (float)$rep['NombreJours'];
|
$jours = (float)$rep['NombreJours'];
|
||||||
|
$code = $rep['TypeConge'];
|
||||||
switch ($code) {
|
switch ($code) {
|
||||||
case 'CP': $name = 'Congé payé'; break;
|
case 'CP': $name = 'Congé payé'; break;
|
||||||
case 'RTT': $name = 'RTT'; break;
|
case 'RTT': $name = 'RTT'; break;
|
||||||
case 'ABS': $name = 'Congé maladie'; break;
|
case 'ABS': $name = 'Congé maladie'; break;
|
||||||
default: $name = $code; break;
|
default: $name = $code; break;
|
||||||
}
|
}
|
||||||
$s = $conn->prepare("SELECT Id FROM TypeConge WHERE Nom = ?");
|
$s = $pdo->prepare("SELECT Id FROM TypeConge WHERE Nom = :nom LIMIT 1");
|
||||||
$s->bind_param("s", $name);
|
$s->execute([':nom'=>$name]);
|
||||||
$s->execute();
|
if ($r = $s->fetch(PDO::FETCH_ASSOC)) {
|
||||||
$res = $s->get_result();
|
$stmt->execute([
|
||||||
if ($r = $res->fetch_assoc()) {
|
':did'=>$demandeId,
|
||||||
$typeId = (int)$r['Id'];
|
':tid'=>$r['Id'],
|
||||||
$insertType->bind_param("iid", $demandeId, $typeId, $jours); // i,i,d
|
':nj'=>$jours
|
||||||
$insertType->execute();
|
]);
|
||||||
}
|
}
|
||||||
$s->close();
|
|
||||||
}
|
}
|
||||||
$insertType->close();
|
|
||||||
|
|
||||||
echo json_encode(["success"=>true,"message"=>"Demande soumise", "request_id"=>$demandeId]);
|
// ✅ Récupérer les validateurs selon hiérarchie
|
||||||
$conn->close();
|
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
|
||||||
|
]);
|
||||||
|
|||||||
0
project/public/php/sync-groups.php
Normal file
0
project/public/php/sync-groups.php
Normal 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 !";
|
|
||||||
|
|
||||||
?>
|
|
||||||
@@ -11,187 +11,197 @@ if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
|
|||||||
|
|
||||||
header("Content-Type: application/json");
|
header("Content-Type: application/json");
|
||||||
|
|
||||||
// Log des erreurs pour debug
|
|
||||||
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 DB
|
||||||
$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 validateRequest: " . $conn->connect_error);
|
echo json_encode(["success" => false, "message" => "Erreur DB: " . $conn->connect_error]);
|
||||||
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données"]);
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lecture du JSON envoyé
|
// Lecture du JSON envoyé
|
||||||
$input = file_get_contents('php://input');
|
$input = file_get_contents('php://input');
|
||||||
error_log("validateRequest - Input reçu: " . $input);
|
|
||||||
|
|
||||||
$data = json_decode($input, true);
|
$data = json_decode($input, true);
|
||||||
|
|
||||||
if (!isset($data['request_id'], $data['action'], $data['validator_id'])) {
|
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"]);
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => "Données manquantes pour la validation"
|
|
||||||
]);
|
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
$requestId = (int)$data['request_id'];
|
$requestId = (int)$data['request_id'];
|
||||||
$action = $data['action']; // 'approve' ou 'reject'
|
$action = $data['action']; // "approve" | "reject"
|
||||||
$validatorId = (int)$data['validator_id'];
|
$validatorId = (int)$data['validator_id'];
|
||||||
$comment = $data['comment'] ?? '';
|
$comment = $data['comment'] ?? '';
|
||||||
|
|
||||||
error_log("validateRequest - Request ID: $requestId, Action: $action, Validator: $validatorId");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$conn->begin_transaction();
|
$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 = "
|
$queryCheck = "
|
||||||
SELECT dc.Id, dc.EmployeeId, dc.TypeCongeId, dc.DateDebut, dc.DateFin, dc.NombreJours,
|
SELECT dc.Id, dc.EmployeeId, dc.CollaborateurADId, dc.TypeCongeId, dc.DateDebut, dc.DateFin, dc.NombreJours,
|
||||||
u.Nom, u.Prenom, tc.Nom as TypeNom
|
u.Nom as UserNom, u.Prenom as UserPrenom,
|
||||||
|
ca.nom as CADNom, ca.prenom as CADPrenom,
|
||||||
|
tc.Nom as TypeNom
|
||||||
FROM DemandeConge dc
|
FROM DemandeConge dc
|
||||||
JOIN Users u ON dc.EmployeeId = u.ID
|
|
||||||
JOIN TypeConge tc ON dc.TypeCongeId = tc.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'
|
WHERE dc.Id = ? AND dc.Statut = 'En attente'
|
||||||
";
|
";
|
||||||
|
|
||||||
$stmtCheck = $conn->prepare($queryCheck);
|
$stmtCheck = $conn->prepare($queryCheck);
|
||||||
$stmtCheck->bind_param("i", $requestId);
|
$stmtCheck->bind_param("i", $requestId);
|
||||||
$stmtCheck->execute();
|
$stmtCheck->execute();
|
||||||
$resultCheck = $stmtCheck->get_result();
|
$resultCheck = $stmtCheck->get_result();
|
||||||
|
|
||||||
if ($requestRow = $resultCheck->fetch_assoc()) {
|
if (!($requestRow = $resultCheck->fetch_assoc())) {
|
||||||
$employeeId = $requestRow['EmployeeId'];
|
throw new Exception("Demande non trouvée ou déjà traitée");
|
||||||
$typeCongeId = $requestRow['TypeCongeId'];
|
}
|
||||||
$nombreJours = $requestRow['NombreJours'];
|
$stmtCheck->close();
|
||||||
$employeeName = $requestRow['Prenom'] . ' ' . $requestRow['Nom'];
|
|
||||||
$typeNom = $requestRow['TypeNom'];
|
$employeeId = $requestRow['EmployeeId'];
|
||||||
|
$collaborateurId = $requestRow['CollaborateurADId'];
|
||||||
error_log("validateRequest - Demande trouvée: $employeeName, Type: $typeNom, Jours: $nombreJours");
|
$typeCongeId = $requestRow['TypeCongeId'];
|
||||||
|
$nombreJours = $requestRow['NombreJours'];
|
||||||
// Déterminer le nouveau statut
|
$employeeName = $employeeId
|
||||||
$newStatus = ($action === 'approve') ? 'Validée' : 'Refusée';
|
? $requestRow['UserPrenom']." ".$requestRow['UserNom']
|
||||||
|
: $requestRow['CADPrenom']." ".$requestRow['CADNom'];
|
||||||
// Mettre à jour la demande
|
$typeNom = $requestRow['TypeNom'];
|
||||||
|
|
||||||
|
$newStatus = ($action === 'approve') ? 'Validée' : 'Refusée';
|
||||||
|
|
||||||
|
// 🔹 Mise à jour DemandeConge
|
||||||
|
if ($isUserValidator) {
|
||||||
$queryUpdate = "
|
$queryUpdate = "
|
||||||
UPDATE DemandeConge
|
UPDATE DemandeConge
|
||||||
SET Statut = ?,
|
SET Statut = ?,
|
||||||
ValidateurId = ?,
|
ValidateurId = ?,
|
||||||
|
ValidateurADId = NULL,
|
||||||
DateValidation = NOW(),
|
DateValidation = NOW(),
|
||||||
CommentaireValidation = ?
|
CommentaireValidation = ?
|
||||||
WHERE Id = ?
|
WHERE Id = ?
|
||||||
";
|
";
|
||||||
|
|
||||||
$stmtUpdate = $conn->prepare($queryUpdate);
|
|
||||||
$stmtUpdate->bind_param("sisi", $newStatus, $validatorId, $comment, $requestId);
|
|
||||||
|
|
||||||
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é
|
|
||||||
$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');
|
|
||||||
}
|
|
||||||
|
|
||||||
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->close();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Créer une notification pour l'employé
|
|
||||||
$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 (?, ?, ?, ?, ?)
|
|
||||||
";
|
|
||||||
|
|
||||||
$notifType = ($action === 'approve') ? 'Success' : 'Error';
|
|
||||||
$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";
|
|
||||||
}
|
|
||||||
|
|
||||||
$queryHistory = "
|
|
||||||
INSERT INTO HistoriqueActions (UserId, Action, Details, DemandeCongeId)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
";
|
|
||||||
|
|
||||||
$stmtHistory = $conn->prepare($queryHistory);
|
|
||||||
$stmtHistory->bind_param("issi", $validatorId, $actionText, $actionDetails, $requestId);
|
|
||||||
$stmtHistory->execute();
|
|
||||||
$stmtHistory->close();
|
|
||||||
|
|
||||||
$conn->commit();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
"success" => true,
|
|
||||||
"message" => "Demande " . (($action === 'approve') ? 'approuvée' : 'refusée') . " avec succès",
|
|
||||||
"new_status" => $newStatus
|
|
||||||
]);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new Exception("Erreur lors de la mise à jour: " . $stmtUpdate->error);
|
|
||||||
}
|
|
||||||
|
|
||||||
$stmtUpdate->close();
|
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("Demande non trouvée ou déjà traitée");
|
$queryUpdate = "
|
||||||
|
UPDATE DemandeConge
|
||||||
|
SET Statut = ?,
|
||||||
|
ValidateurId = NULL,
|
||||||
|
ValidateurADId = ?,
|
||||||
|
DateValidation = NOW(),
|
||||||
|
CommentaireValidation = ?
|
||||||
|
WHERE Id = ?
|
||||||
|
";
|
||||||
}
|
}
|
||||||
|
$stmtUpdate = $conn->prepare($queryUpdate);
|
||||||
$stmtCheck->close();
|
$stmtUpdate->bind_param("sisi", $newStatus, $validatorId, $comment, $requestId);
|
||||||
|
$stmtUpdate->execute();
|
||||||
|
$stmtUpdate->close();
|
||||||
|
|
||||||
|
// 🔹 Déduction solde (seulement Users, pas AD, hors maladie)
|
||||||
|
if ($action === 'approve' && $typeNom !== 'Congé maladie' && $employeeId) {
|
||||||
|
$currentDate = new DateTime();
|
||||||
|
$year = ($typeNom === 'Congé payé' && (int)$currentDate->format('m') < 6)
|
||||||
|
? $currentDate->format('Y') - 1
|
||||||
|
: $currentDate->format('Y');
|
||||||
|
|
||||||
|
$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);
|
||||||
|
$stmtDeduct->execute();
|
||||||
|
$stmtDeduct->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 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)";
|
||||||
|
$notifType = ($action === 'approve') ? 'Success' : 'Error';
|
||||||
|
|
||||||
|
if ($employeeId) {
|
||||||
|
$queryNotif = "
|
||||||
|
INSERT INTO Notifications (UserId, CollaborateurADId, Titre, Message, Type, DemandeCongeId)
|
||||||
|
VALUES (?, NULL, ?, ?, ?, ?)
|
||||||
|
";
|
||||||
|
$stmtNotif = $conn->prepare($queryNotif);
|
||||||
|
$stmtNotif->bind_param("isssi", $employeeId, $notificationTitle, $notificationMessage, $notifType, $requestId);
|
||||||
|
$stmtNotif->execute();
|
||||||
|
$stmtNotif->close();
|
||||||
|
} elseif ($collaborateurId) {
|
||||||
|
$queryNotif = "
|
||||||
|
INSERT INTO Notifications (UserId, CollaborateurADId, Titre, Message, Type, DemandeCongeId)
|
||||||
|
VALUES (NULL, ?, ?, ?, ?, ?)
|
||||||
|
";
|
||||||
|
$stmtNotif = $conn->prepare($queryNotif);
|
||||||
|
$stmtNotif->bind_param("isssi", $collaborateurId, $notificationTitle, $notificationMessage, $notifType, $requestId);
|
||||||
|
$stmtNotif->execute();
|
||||||
|
$stmtNotif->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔹 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();
|
||||||
|
|
||||||
|
$conn->commit();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"success" => true,
|
||||||
|
"message" => "Demande " . (($action === 'approve') ? 'approuvée' : 'refusée'),
|
||||||
|
"new_status" => $newStatus
|
||||||
|
]);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$conn->rollback();
|
$conn->rollback();
|
||||||
error_log("Erreur validateRequest: " . $e->getMessage());
|
echo json_encode(["success" => false, "message" => $e->getMessage()]);
|
||||||
echo json_encode([
|
|
||||||
"success" => false,
|
|
||||||
"message" => "Erreur lors de la validation: " . $e->getMessage()
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$conn->close();
|
$conn->close();
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Requests from './pages/Requests';
|
|||||||
import Calendar from './pages/Calendar';
|
import Calendar from './pages/Calendar';
|
||||||
import Manager from './pages/Manager';
|
import Manager from './pages/Manager';
|
||||||
import ProtectedRoute from './components/ProtectedRoute';
|
import ProtectedRoute from './components/ProtectedRoute';
|
||||||
|
import EmployeeDetails from './pages/EmployeeDetails';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -34,6 +35,7 @@ function App() {
|
|||||||
<Manager />
|
<Manager />
|
||||||
</ProtectedRoute>
|
</ProtectedRoute>
|
||||||
} />
|
} />
|
||||||
|
<Route path="/employee/:id" element={<ProtectedRoute><EmployeeDetails /></ProtectedRoute>} />
|
||||||
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
<Route path="/" element={<Navigate to="/dashboard" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Router>
|
</Router>
|
||||||
|
|||||||
@@ -4,9 +4,19 @@ export const msalConfig = {
|
|||||||
clientId: "4bb4cc24-bac3-427c-b02c-5d14fc67b561", // Application (client) ID dans Azure
|
clientId: "4bb4cc24-bac3-427c-b02c-5d14fc67b561", // Application (client) ID dans Azure
|
||||||
authority: "https://login.microsoftonline.com/9840a2a0-6ae1-4688-b03d-d2ec291be0f9", // Directory (tenant) ID
|
authority: "https://login.microsoftonline.com/9840a2a0-6ae1-4688-b03d-d2ec291be0f9", // Directory (tenant) ID
|
||||||
redirectUri: "http://localhost:5173"
|
redirectUri: "http://localhost:5173"
|
||||||
|
},
|
||||||
|
cache: {
|
||||||
|
cacheLocation: "sessionStorage",
|
||||||
|
storeAuthStateInCookie: false,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginRequest = {
|
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.
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { X, Calendar, Clock, AlertCircle, RotateCcw } from 'lucide-react';
|
import { X, Calendar, Clock, AlertCircle, RotateCcw } from 'lucide-react';
|
||||||
|
import { useMsal } from "@azure/msal-react";
|
||||||
|
import { loginRequest } from "../AuthConfig";
|
||||||
|
|
||||||
|
|
||||||
const NewLeaveRequestModal = ({
|
const NewLeaveRequestModal = ({
|
||||||
onClose,
|
onClose,
|
||||||
availableLeaveCounters,
|
availableLeaveCounters,
|
||||||
userId,
|
userId,
|
||||||
|
userEmail,
|
||||||
|
userName,
|
||||||
|
accessToken,
|
||||||
onRequestSubmitted,
|
onRequestSubmitted,
|
||||||
preselectedStartDate = null,
|
preselectedStartDate = null,
|
||||||
preselectedEndDate = null,
|
preselectedEndDate = null,
|
||||||
preselectedType = null
|
preselectedType = null,
|
||||||
|
|
||||||
}) => {
|
}) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
types: preselectedType ? [preselectedType] : [],
|
types: preselectedType ? [preselectedType] : [],
|
||||||
@@ -18,6 +25,7 @@ const NewLeaveRequestModal = ({
|
|||||||
medicalDocuments: []
|
medicalDocuments: []
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const [typeDistribution, setTypeDistribution] = useState({});
|
const [typeDistribution, setTypeDistribution] = useState({});
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
@@ -27,6 +35,25 @@ const NewLeaveRequestModal = ({
|
|||||||
const [isOtherChecked, setIsOtherChecked] = useState(false);
|
const [isOtherChecked, setIsOtherChecked] = useState(false);
|
||||||
const [otherLeaveType, setOtherLeaveType] = useState('');
|
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
|
// Vérifier si des valeurs sont pré-sélectionnées
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (preselectedStartDate || preselectedEndDate || preselectedType) {
|
if (preselectedStartDate || preselectedEndDate || preselectedType) {
|
||||||
@@ -273,55 +300,50 @@ const NewLeaveRequestModal = ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const requestData = {
|
const requestData = {
|
||||||
EmployeeId: userId,
|
|
||||||
DateDebut: formData.startDate,
|
DateDebut: formData.startDate,
|
||||||
DateFin: formData.endDate,
|
DateFin: formData.endDate,
|
||||||
Commentaire: formData.reason,
|
Commentaire: formData.reason,
|
||||||
NombreJours: calculatedDays,
|
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();
|
console.log("📤 Payload envoyé au backend :", JSON.stringify(requestData, null, 2));
|
||||||
formDataToSend.append('data', JSON.stringify(requestData));
|
|
||||||
|
|
||||||
// Ajouter les fichiers
|
|
||||||
formData.medicalDocuments.forEach((file, index) => {
|
|
||||||
formDataToSend.append(`medicalDocuments[]`, file);
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await fetch('http://localhost/GTA/project/public/php/submitLeaveRequest.php', {
|
const response = await fetch('http://localhost/GTA/project/public/php/submitLeaveRequest.php', {
|
||||||
method: 'POST',
|
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();
|
if (!result.success) {
|
||||||
let result;
|
setError(result.message || "Erreur lors de l'enregistrement");
|
||||||
try {
|
|
||||||
result = JSON.parse(text);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Réponse non JSON:", text);
|
|
||||||
setError("Erreur serveur : réponse invalide.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.success) {
|
console.log("✅ Demande enregistrée (et mails envoyés côté PHP)");
|
||||||
onRequestSubmitted?.();
|
|
||||||
onClose();
|
// Fermer modal et rafraîchir
|
||||||
} else {
|
onRequestSubmitted?.();
|
||||||
setError(result.message || 'Erreur lors de la soumission');
|
onClose();
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur:', error);
|
console.error('❌ Erreur handleSubmit:', error);
|
||||||
setError('Erreur de connexion au serveur');
|
setError("Erreur de connexion au serveur");
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getTypeLabel = (type) => {
|
const getTypeLabel = (type) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'CP': return 'Congés payés';
|
case 'CP': return 'Congés payés';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
switch (role) {
|
switch (role) {
|
||||||
case 'Admin':
|
case 'Admin':
|
||||||
return 'bg-red-100 text-red-800';
|
return 'bg-red-100 text-red-800';
|
||||||
case 'Manager':
|
case 'Validateur':
|
||||||
return 'bg-green-100 text-green-800';
|
return 'bg-green-100 text-green-800';
|
||||||
default:
|
default:
|
||||||
return 'bg-blue-100 text-blue-800';
|
return 'bg-blue-100 text-blue-800';
|
||||||
@@ -109,7 +109,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
<span className="font-medium">Calendrier</span>
|
<span className="font-medium">Calendrier</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{(user?.role === 'Manager' || user?.role === 'Admin' || user?.role === 'Employe') && (
|
{(user?.role === 'Validateur' || user?.role === 'Admin' || user?.role === 'Collaborateur') && (
|
||||||
<Link
|
<Link
|
||||||
to="/manager"
|
to="/manager"
|
||||||
onClick={() => window.innerWidth < 1024 && onToggle()}
|
onClick={() => window.innerWidth < 1024 && onToggle()}
|
||||||
@@ -118,7 +118,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
>
|
>
|
||||||
<Users className="w-5 h-5" />
|
<Users className="w-5 h-5" />
|
||||||
<span className="font-medium">
|
<span className="font-medium">
|
||||||
{user?.role === 'Employe' ? 'Mon équipe' : 'Équipe'}
|
{user?.role === 'Collaborateur' ? 'Mon équipe' : 'Équipe'}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { createContext, useContext, useState, useEffect } from 'react';
|
import React, { createContext, useContext, useState, useEffect } from 'react';
|
||||||
import * as msal from '@azure/msal-browser';
|
import * as msal from '@azure/msal-browser';
|
||||||
|
import { msalConfig, loginRequest } from '../AuthConfig';
|
||||||
|
|
||||||
const AuthContext = createContext();
|
const AuthContext = createContext();
|
||||||
|
|
||||||
@@ -11,70 +12,196 @@ export const useAuth = () => {
|
|||||||
return context;
|
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);
|
const msalInstance = new msal.PublicClientApplication(msalConfig);
|
||||||
|
|
||||||
export const AuthProvider = ({ children }) => {
|
export const AuthProvider = ({ children }) => {
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
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(() => {
|
useEffect(() => {
|
||||||
const initializeMsal = async () => {
|
const initializeMsal = async () => {
|
||||||
try {
|
try {
|
||||||
await msalInstance.initialize();
|
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) {
|
} catch (error) {
|
||||||
console.error("Erreur d'initialisation MSAL:", error);
|
console.error("Erreur d'initialisation MSAL:", error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
initializeMsal();
|
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);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const login = async (email, password) => {
|
// Gérer l'authentification réussie
|
||||||
|
const handleSuccessfulAuth = async (authResponse) => {
|
||||||
try {
|
try {
|
||||||
const possibleUrls = [
|
const account = authResponse.account;
|
||||||
'http://localhost/GTA/project/public/php/login.php',
|
const accessToken = authResponse.accessToken;
|
||||||
'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;
|
// Récupérer profil Microsoft Graph
|
||||||
for (const url of possibleUrls) {
|
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 l’utilisateur dans la DB
|
||||||
|
const syncResult = await syncUserToDatabase(entraUser, accessToken);
|
||||||
|
|
||||||
|
// 🚀 NEW : si admin → lancer full-sync.php
|
||||||
|
if (syncResult?.role === "Admin") {
|
||||||
try {
|
try {
|
||||||
console.log("Test URL:", url);
|
const syncResp = await fetch(getApiUrl('full-sync.php'), {
|
||||||
response = await fetch(url, {
|
method: "POST",
|
||||||
method: 'POST',
|
headers: { "Authorization": `Bearer ${accessToken}` }
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ email, mot_de_passe: password }),
|
|
||||||
});
|
});
|
||||||
if (response.ok) break;
|
const syncData = await syncResp.json();
|
||||||
} catch {
|
console.log("Résultat Full Sync:", syncData);
|
||||||
continue;
|
} catch (err) {
|
||||||
|
console.error("Erreur synchronisation groupes:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response || !response.ok) {
|
// 🔹 Vérifier autorisation via groupes DB
|
||||||
throw new Error('Aucune URL de connexion accessible');
|
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 response = await fetch(getApiUrl('login.php'), {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ email, mot_de_passe: password }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Erreur de connexion');
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
@@ -98,49 +225,79 @@ export const AuthProvider = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setUser(userData);
|
setUser(userData);
|
||||||
localStorage.setItem('user', JSON.stringify(userData));
|
setIsAuthorized(true);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Connexion Office 365
|
||||||
const loginWithO365 = async () => {
|
const loginWithO365 = async () => {
|
||||||
try {
|
try {
|
||||||
const loginResponse = await msalInstance.loginPopup({
|
const authResponse = await msalInstance.loginPopup(loginRequest);
|
||||||
scopes: ["user.read"]
|
await handleSuccessfulAuth(authResponse);
|
||||||
});
|
return true;
|
||||||
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));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur login Office 365:', 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
|
||||||
setUser(null);
|
const logout = async () => {
|
||||||
localStorage.removeItem('user');
|
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);
|
||||||
|
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 (
|
return (
|
||||||
<AuthContext.Provider value={value}>
|
<AuthContext.Provider value={value}>
|
||||||
@@ -149,4 +306,4 @@ export const AuthProvider = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AuthContext;
|
export default AuthContext;
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
import { StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import App from './App.jsx';
|
import App from './App.jsx';
|
||||||
import './index.css';
|
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(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<MsalProvider instance={msalInstance}>
|
||||||
|
<App />
|
||||||
|
</MsalProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
@@ -642,7 +642,7 @@ END:VEVENT`;
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Ton rendu de calendrier ici */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal nouvelle demande */}
|
{/* Modal nouvelle demande */}
|
||||||
@@ -654,9 +654,12 @@ END:VEVENT`;
|
|||||||
}}
|
}}
|
||||||
availableLeaveCounters={leaveCounters}
|
availableLeaveCounters={leaveCounters}
|
||||||
userId={user?.id}
|
userId={user?.id}
|
||||||
|
|
||||||
onRequestSubmitted={() => {
|
onRequestSubmitted={() => {
|
||||||
resetSelection();
|
resetSelection();
|
||||||
}}
|
}}
|
||||||
|
userEmail={user.email}
|
||||||
|
userName={`${user.prenom} ${user.nom}`}
|
||||||
preselectedStartDate={selectedDate}
|
preselectedStartDate={selectedDate}
|
||||||
preselectedEndDate={selectedEndDate}
|
preselectedEndDate={selectedEndDate}
|
||||||
preselectedType={preselectedType}
|
preselectedType={preselectedType}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
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, 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 NewLeaveRequestModal from '../components/NewLeaveRequestModal';
|
||||||
|
import { useMsal } from "@azure/msal-react";
|
||||||
|
import { loginRequest } from "../authConfig";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const [graphToken, setGraphToken] = useState(null);
|
||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
const [leaveCounters, setLeaveCounters] = useState({
|
const [leaveCounters, setLeaveCounters] = useState({
|
||||||
availableCP: 0,
|
availableCP: 0,
|
||||||
@@ -20,54 +23,82 @@ const Dashboard = () => {
|
|||||||
const [recentRequests, setRecentRequests] = useState([]);
|
const [recentRequests, setRecentRequests] = useState([]);
|
||||||
const [allRequests, setAllRequests] = 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(() => {
|
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();
|
fetchLeaveCounters();
|
||||||
fetchAllRequests();
|
fetchAllRequests();
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [userId]);
|
||||||
|
|
||||||
const fetchLeaveCounters = async () => {
|
const fetchLeaveCounters = async () => {
|
||||||
try {
|
try {
|
||||||
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${user.id}`;
|
const url = `http://localhost/GTA/project/public/php/getLeaveCounters.php?user_id=${userId}`;
|
||||||
console.log(' Dashboard - Récupération des compteurs:', url);
|
|
||||||
console.log(' Dashboard - User ID utilisé:', user.id);
|
|
||||||
|
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) throw new Error(`Erreur HTTP: ${response.status}`);
|
||||||
throw new Error(`Erreur HTTP: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
console.log(' Dashboard - Réponse brute compteurs:', text);
|
console.log("🔎 Réponse brute getLeaveCounters:", text);
|
||||||
console.log(' Dashboard - Longueur de la réponse:', text.length);
|
|
||||||
console.log(' Dashboard - Premiers 500 caractères:', text.substring(0, 500));
|
|
||||||
|
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(text);
|
data = JSON.parse(text);
|
||||||
} catch (parseError) {
|
} catch (e) {
|
||||||
console.error(' Dashboard - Erreur parsing JSON:', parseError);
|
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
|
||||||
console.error(' Dashboard - Texte qui a causé l\'erreur:', text);
|
|
||||||
throw new Error('Réponse PHP invalide: ' + text.substring(0, 200));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(' Dashboard - Compteurs parsés:', data);
|
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
console.log(' Dashboard - Compteurs récupérés:', data.counters);
|
|
||||||
setLeaveCounters(data.counters);
|
setLeaveCounters(data.counters);
|
||||||
} else {
|
} else {
|
||||||
console.error(' Dashboard - Erreur API compteurs:', data.message);
|
throw new Error(data.message || "Erreur lors de la récupération des compteurs");
|
||||||
console.error(' Dashboard - Données complètes:', data);
|
|
||||||
throw new Error('API Error: ' + (data.message || 'Erreur inconnue'));
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('💥 Dashboard - Erreur lors de la récupération des compteurs:', error);
|
console.error("💥 Erreur compteurs:", error);
|
||||||
|
|
||||||
// Fallback avec des données par défaut
|
|
||||||
console.log(' Dashboard - Utilisation des données par défaut');
|
|
||||||
setLeaveCounters({
|
setLeaveCounters({
|
||||||
availableCP: 25,
|
availableCP: 25,
|
||||||
availableRTT: 10,
|
availableRTT: 10,
|
||||||
@@ -75,78 +106,63 @@ const Dashboard = () => {
|
|||||||
rttInProcess: 0,
|
rttInProcess: 0,
|
||||||
absenteism: 0
|
absenteism: 0
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchAllRequests = async () => {
|
const fetchAllRequests = async () => {
|
||||||
console.log(' Dashboard - Début fetchAllRequests pour user:', user?.id);
|
|
||||||
|
|
||||||
try {
|
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}`;
|
||||||
console.log(' Dashboard - URL appelée:', url);
|
|
||||||
|
|
||||||
const response = await fetch(url);
|
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();
|
const text = await response.text();
|
||||||
console.log(' Dashboard - Réponse brute:', text);
|
console.log("🔎 Réponse brute getRequests:", text);
|
||||||
|
|
||||||
const data = JSON.parse(text);
|
let data;
|
||||||
console.log(' Dashboard - Données parsées:', data);
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
throw new Error("Le serveur PHP ne renvoie pas un JSON valide");
|
||||||
|
}
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
console.log('Dashboard - Demandes récupérées:', data.requests?.length || 0);
|
|
||||||
setAllRequests(data.requests || []);
|
setAllRequests(data.requests || []);
|
||||||
setRecentRequests(data.requests?.slice(0, 3) || []);
|
setRecentRequests(data.requests?.slice(0, 3) || []);
|
||||||
} else {
|
} 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) {
|
} catch (error) {
|
||||||
console.error(' Dashboard - Erreur lors de la récupération des demandes:', error);
|
console.error("💥 Erreur demandes:", error);
|
||||||
|
|
||||||
// En cas d'erreur, on garde des tableaux vides
|
|
||||||
setAllRequests([]);
|
setAllRequests([]);
|
||||||
setRecentRequests([]);
|
setRecentRequests([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetCounters = async () => {
|
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 ?')) {
|
if (!confirm("⚠️ Voulez-vous vraiment réinitialiser les compteurs ?")) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('http://localhost/GTA/project/public/php/resetLeaveCounters.php', {
|
const response = await fetch('http://localhost/GTA/project/public/php/resetLeaveCounters.php', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: { 'Content-Type': 'application/json' },
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ manual_reset: true }),
|
body: JSON.stringify({ manual_reset: true }),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.success) {
|
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();
|
fetchLeaveCounters();
|
||||||
} else {
|
} else {
|
||||||
alert(` Erreur lors de la réinitialisation :\n${data.message}`);
|
alert(`❌ Erreur : ${data.message}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur:', error);
|
console.error("Erreur:", error);
|
||||||
alert(' Erreur de connexion au serveur');
|
alert("❌ Erreur serveur");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openManualResetPage = () => {
|
|
||||||
window.open('http://localhost/GTA/project/public/php/manualResetCounters.php', '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusColor = (status) => {
|
const getStatusColor = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'Approuvé':
|
case 'Approuvé':
|
||||||
@@ -392,7 +408,10 @@ const Dashboard = () => {
|
|||||||
<NewLeaveRequestModal
|
<NewLeaveRequestModal
|
||||||
onClose={() => setShowNewRequestModal(false)}
|
onClose={() => setShowNewRequestModal(false)}
|
||||||
availableLeaveCounters={leaveCounters}
|
availableLeaveCounters={leaveCounters}
|
||||||
userId={user?.id}
|
accessToken={graphToken}
|
||||||
|
userId={userId}
|
||||||
|
userEmail={user.email}
|
||||||
|
userName={`${user.prenom} ${user.nom}`}
|
||||||
onRequestSubmitted={() => {
|
onRequestSubmitted={() => {
|
||||||
fetchLeaveCounters();
|
fetchLeaveCounters();
|
||||||
fetchAllRequests();
|
fetchAllRequests();
|
||||||
|
|||||||
114
project/src/pages/EmployeeDetails.jsx
Normal file
114
project/src/pages/EmployeeDetails.jsx
Normal 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;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useAuth } from '../context/AuthContext';
|
import { useAuth } from '../context/AuthContext';
|
||||||
import { useNavigate } from 'react-router-dom';
|
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 Login = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
@@ -9,25 +9,88 @@ const Login = () => {
|
|||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
const [authMethod, setAuthMethod] = useState(''); // Pour tracker la méthode d'auth utilisée
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { login, loginWithO365 } = useAuth();
|
const { login, loginWithO365, isAuthorized } = useAuth();
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError('');
|
setError('');
|
||||||
|
setAuthMethod('local');
|
||||||
|
|
||||||
const success = await login(email, password);
|
try {
|
||||||
if (success) {
|
const success = await login(email, password);
|
||||||
navigate('/dashboard');
|
if (success) {
|
||||||
} else {
|
navigate('/dashboard');
|
||||||
setError('Identifiants incorrects. Veuillez réessayer.');
|
} else {
|
||||||
|
setError('Identifiants incorrects. Veuillez réessayer.');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(error.message || 'Erreur lors de la connexion');
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsLoading(false);
|
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 d’authentification (si ton context le fournit)
|
||||||
|
const token = localStorage.getItem("o365_token");
|
||||||
|
// ⚠️ Ici j’imagine 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 (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex flex-col lg:flex-row">
|
<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 */}
|
{/* 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>
|
<p className="text-sm lg:text-base text-gray-600">Gestion de congés</p>
|
||||||
</div>
|
</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">
|
<form onSubmit={handleSubmit} className="space-y-4 lg:space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor="email" className="block text-sm lg:text-base font-medium text-gray-700 mb-2">
|
<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"
|
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"
|
placeholder="votre.email@entreprise.com"
|
||||||
required
|
required
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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"
|
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="••••••••"
|
placeholder="••••••••"
|
||||||
required
|
required
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
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"}
|
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" />}
|
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||||
@@ -96,37 +193,41 @@ const Login = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
{/* Affichage des erreurs */}
|
||||||
<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>
|
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-2 lg:p-3 bg-red-50 border border-red-200 rounded-lg">
|
<div className="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="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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
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>
|
</button>
|
||||||
</form>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,4 +235,4 @@ const Login = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Login;
|
export default Login;
|
||||||
@@ -3,6 +3,7 @@ import { useAuth } from '../context/AuthContext';
|
|||||||
import Sidebar from '../components/Sidebar';
|
import Sidebar from '../components/Sidebar';
|
||||||
import { Plus, Search, Filter, Eye, 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';
|
||||||
|
import { useMsal } from "@azure/msal-react";
|
||||||
|
|
||||||
const Requests = () => {
|
const Requests = () => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
@@ -32,8 +33,30 @@ const Requests = () => {
|
|||||||
|
|
||||||
const [showFilters, setShowFilters] = useState(false);
|
const [showFilters, setShowFilters] = useState(false);
|
||||||
|
|
||||||
|
const [graphToken, setGraphToken] = useState(null);
|
||||||
|
const { instance, accounts } = useMsal();
|
||||||
|
const userId = user?.id || user?.CollaborateurADId || user?.ID;
|
||||||
|
|
||||||
useEffect(() => {
|
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();
|
fetchLeaveCounters();
|
||||||
fetchAllRequests();
|
fetchAllRequests();
|
||||||
}
|
}
|
||||||
@@ -69,7 +92,7 @@ const Requests = () => {
|
|||||||
|
|
||||||
const fetchLeaveCounters = async () => {
|
const fetchLeaveCounters = async () => {
|
||||||
try {
|
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();
|
const text = await response.text();
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
@@ -89,7 +112,7 @@ const Requests = () => {
|
|||||||
|
|
||||||
const fetchAllRequests = async () => {
|
const fetchAllRequests = async () => {
|
||||||
try {
|
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 response = await fetch(url);
|
||||||
const text = await response.text();
|
const text = await response.text();
|
||||||
let data;
|
let data;
|
||||||
@@ -426,6 +449,8 @@ const Requests = () => {
|
|||||||
onClose={() => setShowNewRequestModal(false)}
|
onClose={() => setShowNewRequestModal(false)}
|
||||||
availableLeaveCounters={leaveCounters}
|
availableLeaveCounters={leaveCounters}
|
||||||
userId={user?.id}
|
userId={user?.id}
|
||||||
|
userEmail={user.email}
|
||||||
|
userName={`${user.prenom} ${user.nom}`}
|
||||||
onRequestSubmitted={() => {
|
onRequestSubmitted={() => {
|
||||||
fetchLeaveCounters();
|
fetchLeaveCounters();
|
||||||
fetchAllRequests();
|
fetchAllRequests();
|
||||||
|
|||||||
Reference in New Issue
Block a user