V1_Sans_Congé_Anticipéfemini collaboratrice

This commit is contained in:
2025-08-29 15:30:31 +02:00
parent 34a369dccd
commit 0eb4dbb99b
41 changed files with 30347 additions and 2733 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,19 @@
// webhook-config.js
export const WEBHOOKS= {
COLLABORATEURS_URL: process.env.COLLABORATEURS_URL || 'http://localhost:3000',
RH_URL: process.env.RH_URL || 'http://localhost:3001',
SECRET_KEY: process.env.WEBHOOK_SECRET || 'secret-key-securise'
};
// Types d'événements
export const EVENTS= {
DEMANDE_VALIDATED: 'demande.validated',
DEMANDE_CREATED: 'demande.created',
DEMANDE_UPDATED: 'demande.updated',
DEMANDE_DELETED: 'demande.deleted',
COMPTEUR_UPDATED: 'compteur.updated'
};

View File

@@ -0,0 +1,115 @@
// webhook-utils.js (VERSION ES MODULES)
// Pour projets avec "type": "module" dans package.json
import axios from 'axios';
import crypto from 'crypto';
class WebhookManager {
constructor(secretKey) {
this.secretKey = secretKey;
}
/**
* Génère une signature HMAC SHA-256 pour sécuriser le webhook
* @param {Object} payload - Les données à signer
* @returns {string} La signature hexadécimale
*/
generateSignature(payload) {
return crypto
.createHmac('sha256', this.secretKey)
.update(JSON.stringify(payload))
.digest('hex');
}
/**
* Vérifie la signature d'un webhook reçu
* @param {Object} payload - Les données reçues
* @param {string} receivedSignature - La signature reçue dans le header
* @returns {boolean} True si la signature est valide
*/
verifySignature(payload, receivedSignature) {
if (!receivedSignature) {
console.error('❌ Aucune signature fournie');
return false;
}
try {
const expectedSignature = this.generateSignature(payload);
return crypto.timingSafeEqual(
Buffer.from(expectedSignature),
Buffer.from(receivedSignature)
);
} catch (error) {
console.error('❌ Erreur vérification signature:', error);
return false;
}
}
/**
* Envoie un webhook à une URL cible avec retry automatique
* @param {string} targetUrl - URL du serveur cible
* @param {string} eventType - Type d'événement (ex: 'demande.validated')
* @param {Object} data - Données de l'événement
* @param {number} retries - Nombre de tentatives (défaut: 3)
* @returns {Promise<Object>} La réponse du serveur
*/
async sendWebhook(targetUrl, eventType, data, retries = 3) {
const payload = {
event: eventType,
data: data,
timestamp: new Date().toISOString()
};
const signature = this.generateSignature(payload);
for (let attempt = 1; attempt <= retries; attempt++) {
try {
console.log(`📤 Envoi webhook: ${eventType} vers ${targetUrl} (tentative ${attempt}/${retries})`);
const response = await axios.post(
`${targetUrl}/api/webhook/receive`,
payload,
{
headers: {
'Content-Type': 'application/json',
'X-Webhook-Signature': signature
},
timeout: 5000 // 5 secondes de timeout
}
);
console.log(`✅ Webhook envoyé avec succès: ${eventType}`);
return response.data;
} catch (error) {
console.error(`❌ Erreur envoi webhook (tentative ${attempt}/${retries}):`, error.message);
if (attempt === retries) {
console.error(`❌ Échec définitif du webhook après ${retries} tentatives`);
throw error;
}
// Attendre avant de réessayer (backoff exponentiel)
const waitTime = 1000 * attempt;
console.log(`⏳ Nouvelle tentative dans ${waitTime}ms...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}
}
}
/**
* Envoie un webhook sans attendre la réponse (fire and forget)
* Utile pour ne pas bloquer l'exécution
* @param {string} targetUrl - URL du serveur cible
* @param {string} eventType - Type d'événement
* @param {Object} data - Données de l'événement
*/
sendWebhookAsync(targetUrl, eventType, data) {
this.sendWebhook(targetUrl, eventType, data)
.catch(error => {
console.error('❌ Webhook async échoué (non bloquant):', error.message);
});
}
}
export default WebhookManager;

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

@@ -2,146 +2,162 @@
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { http_response_code(200); exit(); }
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
header("Content-Type: application/json");
ini_set('display_errors',1); ini_set('display_startup_errors',1); error_reporting(E_ALL);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$host="192.168.0.4"; $dbname="DemandeConge"; $username="wpuser"; $password="-2b/)ru5/Bi8P[7_";
$conn = new mysqli($host,$username,$password,$dbname);
if ($conn->connect_error) { echo json_encode(["success"=>false,"message"=>"Erreur DB: ".$conn->connect_error]); exit(); }
$host = "192.168.0.4";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$dbname = "DemandeConge";
$userId = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
if (!$userId) { echo json_encode(["success"=>false,"message"=>"user_id manquant"]); $conn->close(); exit(); }
function getLeaveYear($date=null){ $d=$date?new DateTime($date):new DateTime(); $y=(int)$d->format('Y'); return ((int)$d->format('m')<6)?$y-1:$y;}
function getRTTYear($date=null){ $d=$date?new DateTime($date):new DateTime(); return (int)$d->format('Y');}
function getWorkingDays($start,$end){ $c=new DateTime($start); $e=new DateTime($end); $days=0; while($c<=$e){ $n=(int)$c->format('N'); if($n<6) $days++; $c->modify('+1 day'); } return $days;}
// Récupérer les typeIds utiles
function getTypeId($conn,$name){ $s=$conn->prepare("SELECT Id FROM TypeConge WHERE Nom=?"); $s->bind_param("s",$name); $s->execute(); $res=$s->get_result(); $id=null; if($r=$res->fetch_assoc()) $id=(int)$r['Id']; $s->close(); return $id; }
$cpTypeId = getTypeId($conn,'Congé payé');
$rttTypeId = getTypeId($conn,'RTT');
$absTypeId = getTypeId($conn,'Congé maladie');
$leaveYear = getLeaveYear();
$rttYear = getRTTYear();
$currentDate = date('Y-m-d');
// --- Soldes initiaux (CompteurConges pour CollaborateurAD) ---
$cpSolde = 0; $rttSolde = 0; $absSolde = 0;
if ($cpTypeId !== null) {
$q="SELECT Solde FROM CompteurConges WHERE CollaborateurADId=? AND TypeCongeId=? AND Annee=?";
$s=$conn->prepare($q); $s->bind_param("iii",$userId,$cpTypeId,$leaveYear); $s->execute(); $res=$s->get_result(); if($r=$res->fetch_assoc()) $cpSolde=$r['Solde']; $s->close();
}
if ($rttTypeId !== null) {
$q="SELECT Solde FROM CompteurConges WHERE CollaborateurADId=? AND TypeCongeId=? AND Annee=?";
$s=$conn->prepare($q); $s->bind_param("iii",$userId,$rttTypeId,$rttYear); $s->execute(); $res=$s->get_result(); if($r=$res->fetch_assoc()) $rttSolde=$r['Solde']; $s->close();
}
if ($absTypeId !== null) {
$q="SELECT Solde FROM CompteurConges WHERE 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();
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
error_log("Erreur DB: " . $conn->connect_error);
echo json_encode(['success' => false, 'message' => 'Erreur de connexion à la base de données']);
exit;
}
// --- Calcul CP en cours ---
$cpInProcess = 0;
if ($cpTypeId !== null) {
$sql = "
SELECT dc.Id, dc.DateDebut, dc.DateFin, dct.NombreJours
FROM DemandeConge dc
LEFT JOIN DemandeCongeType dct
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
WHERE dc.CollaborateurADId = ?
AND dc.Statut IN ('En attente','Validée')
AND dc.DateFin >= ?
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
";
$s = $conn->prepare($sql);
$s->bind_param("iisi", $cpTypeId, $userId, $currentDate, $cpTypeId);
$s->execute();
$res = $s->get_result();
while ($r = $res->fetch_assoc()) {
if ($r['NombreJours'] !== null) {
$cpInProcess += (float)$r['NombreJours'];
} else {
$cpInProcess += getWorkingDays($r['DateDebut'], $r['DateFin']);
}
$today = new DateTime();
$yearCurrent = (int)$today->format('Y');
$yearNMinus1 = $yearCurrent - 1;
function getTypeId($conn, $nom) {
$stmt = $conn->prepare("SELECT Id FROM TypeConge WHERE Nom=?");
$stmt->bind_param("s", $nom);
$stmt->execute();
$result = $stmt->get_result();
$id = null;
if ($row = $result->fetch_assoc()) {
$id = (int)$row['Id'];
}
$s->close();
$stmt->close();
error_log("TypeConge '$nom' => Id $id");
return $id;
}
// --- Calcul RTT en cours ---
$rttInProcess = 0;
if ($rttTypeId !== null) {
$sql = "
SELECT dc.Id, dc.DateDebut, dc.DateFin, dct.NombreJours
FROM DemandeConge dc
LEFT JOIN DemandeCongeType dct
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
WHERE dc.CollaborateurADId = ?
AND dc.Statut IN ('En attente','Validée')
AND dc.DateFin >= ?
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
";
$s = $conn->prepare($sql);
$s->bind_param("iisi", $rttTypeId, $userId, $currentDate, $rttTypeId);
$s->execute();
$res = $s->get_result();
while ($r = $res->fetch_assoc()) {
if ($r['NombreJours'] !== null) {
$rttInProcess += (float)$r['NombreJours'];
} else {
$rttInProcess += getWorkingDays($r['DateDebut'], $r['DateFin']);
$cpTypeId = getTypeId($conn, 'Congé payé');
$rttTypeId = getTypeId($conn, 'RTT');
$soldeReportInitial_CP = 0.0;
$soldeReportInitial_RTT = 0.0;
$collaborateursResult = $conn->query("SELECT id FROM CollaborateurAD");
if (!$collaborateursResult) {
error_log("Erreur récupération collaborateurs : ".$conn->error);
echo json_encode(['success' => false, 'message' => 'Erreur récupération collaborateurs']);
exit;
}
while ($collab = $collaborateursResult->fetch_assoc()) {
$collabId = (int)$collab['id'];
if ($cpTypeId !== null) {
$existsStmt = $conn->prepare("SELECT Id FROM CompteurConges WHERE CollaborateurADId=? AND TypeCongeId=? AND Annee=?");
$existsStmt->bind_param("iii", $collabId, $cpTypeId, $yearNMinus1);
$existsStmt->execute();
$existsStmt->store_result();
if ($existsStmt->num_rows === 0) {
$insertStmt = $conn->prepare("INSERT INTO CompteurConges (CollaborateurADId, TypeCongeId, Annee, Solde, Total, SoldeReporte) VALUES (?, ?, ?, ?, ?, ?)");
$insertStmt->bind_param("iiiddd", $collabId, $cpTypeId, $yearNMinus1, $soldeReportInitial_CP, $soldeReportInitial_CP, $soldeReportInitial_CP);
if (!$insertStmt->execute()) {
error_log("Erreur insertion CP N-1 collaborateur $collabId : ".$insertStmt->error);
}
$insertStmt->close();
}
$existsStmt->close();
}
$s->close();
}
// --- Calcul absenteisme validé ---
$absenteism = 0;
if ($absTypeId !== null) {
$sql = "
SELECT dc.DateDebut, dc.DateFin, dct.NombreJours
FROM DemandeConge dc
LEFT JOIN DemandeCongeType dct
ON dct.DemandeCongeId = dc.Id AND dct.TypeCongeId = ?
WHERE dc.CollaborateurADId = ?
AND dc.Statut = 'Validée'
AND (dct.NombreJours IS NOT NULL OR FIND_IN_SET(?, dc.TypeCongeId))
";
$s = $conn->prepare($sql);
$s->bind_param("iii", $absTypeId, $userId, $absTypeId);
$s->execute();
$res = $s->get_result();
while ($r = $res->fetch_assoc()) {
if ($r['NombreJours'] !== null) {
$absenteism += (float)$r['NombreJours'];
} else {
$d1 = new DateTime($r['DateDebut']); $d2 = new DateTime($r['DateFin']);
$absenteism += ($d2->diff($d1)->days + 1);
if ($rttTypeId !== null) {
$existsStmt = $conn->prepare("SELECT Id FROM CompteurConges WHERE CollaborateurADId=? AND TypeCongeId=? AND Annee=?");
$existsStmt->bind_param("iii", $collabId, $rttTypeId, $yearNMinus1);
$existsStmt->execute();
$existsStmt->store_result();
if ($existsStmt->num_rows === 0) {
$insertStmt = $conn->prepare("INSERT INTO CompteurConges (CollaborateurADId, TypeCongeId, Annee, Solde, Total, SoldeReporte) VALUES (?, ?, ?, ?, ?, ?)");
$insertStmt->bind_param("iiiddd", $collabId, $rttTypeId, $yearNMinus1, $soldeReportInitial_RTT, $soldeReportInitial_RTT, $soldeReportInitial_RTT);
if (!$insertStmt->execute()) {
error_log("Erreur insertion RTT N-1 collaborateur $collabId : ".$insertStmt->error);
}
$insertStmt->close();
}
$existsStmt->close();
}
$s->close();
}
$availableCPCalculated = max(0, $cpSolde - $cpInProcess);
$availableRTTCalculated = max(0, $rttSolde - $rttInProcess);
$cpStart = new DateTime("$yearCurrent-06-01");
$cpEnd = new DateTime(($yearCurrent + 1) . "-05-31");
$rttStart = new DateTime("$yearCurrent-01-01");
$rttEnd = new DateTime("$yearCurrent-12-31");
echo json_encode([
"success" => true,
"message" => "Compteurs récupérés avec succès.",
"counters" => [
"availableCP" => (int)$availableCPCalculated,
"availableRTT" => (int)$availableRTTCalculated,
"availableABS" => (int)$absSolde,
"rttInProcess" => (int)$rttInProcess,
"absenteism" => (int)$absenteism
],
"debug" => [
"cpSolde"=>$cpSolde,"cpInProcess"=>$cpInProcess,
"rttSolde"=>$rttSolde,"rttInProcess"=>$rttInProcess,
"absSolde"=>$absSolde,"absenteism"=>$absenteism
]
]);
$cpAnnualDays = 25;
$rttAnnualDays = 10;
$cpPeriodDays = $cpEnd->diff($cpStart)->days + 1;
$rttPeriodDays = $rttEnd->diff($rttStart)->days + 1;
$cpDailyIncrement = $cpAnnualDays / $cpPeriodDays;
$rttDailyIncrement = $rttAnnualDays / $rttPeriodDays;
error_log("Incrément CP jour : $cpDailyIncrement");
error_log("Incrément RTT jour : $rttDailyIncrement");
if ($today >= $cpStart && $today <= $cpEnd && $cpTypeId !== null) {
$exerciseYear = (int)$cpStart->format('Y');
$stmt = $conn->prepare("UPDATE CompteurConges SET Solde = Solde + ? WHERE TypeCongeId = ? AND Annee = ?");
$stmt->bind_param("dii", $cpDailyIncrement, $cpTypeId, $exerciseYear);
if (!$stmt->execute()) {
error_log("Erreur incrément CP N : ".$stmt->error);
}
$stmt->close();
}
if ($today >= $rttStart && $today <= $rttEnd && $rttTypeId !== null) {
$exerciseYear = $yearCurrent;
$stmt = $conn->prepare("UPDATE CompteurConges SET Solde = Solde + ? WHERE TypeCongeId = ? AND Annee = ?");
$stmt->bind_param("dii", $rttDailyIncrement, $rttTypeId, $exerciseYear);
if (!$stmt->execute()) {
error_log("Erreur incrément RTT N : ".$stmt->error);
}
$stmt->close();
}
// Récupérer les compteurs actuels de l'utilisateur demandé en GET
$userId = isset($_GET['user_id']) ? (int)$_GET['user_id'] : 0;
$data = [];
if ($userId > 0) {
$stmt = $conn->prepare(
"SELECT tc.Nom, cc.Annee, cc.Solde, cc.Total, cc.SoldeReporte
FROM CompteurConges cc
JOIN TypeConge tc ON cc.TypeCongeId = tc.Id
WHERE cc.CollaborateurADId = ?"
);
$stmt->bind_param("i", $userId);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
$data[$row['Nom']] = [
'Annee' => $row['Annee'],
'Solde' => (float)$row['Solde'],
'Total' => (float)$row['Total'],
'SoldeReporte' => (float)$row['SoldeReporte'],
];
}
$stmt->close();
}
$conn->close();
?>
echo json_encode([
'success' => true,
'message' => 'Compteurs mis à jour',
'counters' => $data,
]);
exit;

View File

@@ -0,0 +1,75 @@
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$host = "192.168.0.4";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$dbname = "DemandeConge";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
http_response_code(500);
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données"]);
exit;
}
$user_id = isset($_GET['user_id']) ? intval($_GET['user_id']) : 0;
if ($user_id <= 0) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Paramètre user_id manquant ou invalide"]);
exit;
}
// Récupérer les notifications non lues ou récentes (ex: dernières 30 j)
$query = "
SELECT Id, Titre, Message, Type, DemandeCongeId, DateCreation, lu
FROM Notifications
WHERE CollaborateurADId = ?
ORDER BY DateCreation DESC
LIMIT 50
";
$stmt = $conn->prepare($query);
if (!$stmt) {
http_response_code(500);
echo json_encode(["success" => false, "message" => "Erreur préparation requête"]);
exit;
}
$stmt->bind_param('i', $user_id); // ✅ correction ici
$stmt->execute();
$result = $stmt->get_result();
$notifications = [];
while ($row = $result->fetch_assoc()) {
$notifications[] = [
"Id" => intval($row['Id']),
"Titre" => $row['Titre'],
"Message" => $row['Message'],
"Type" => $row['Type'],
"DemandeCongeId" => intval($row['DemandeCongeId']),
"DateCreation" => $row['DateCreation'],
"lu" => intval($row['lu']) === 1,
];
}
$stmt->close();
$conn->close();
echo json_encode([
"success" => true,
"notifications" => $notifications
]);

View File

@@ -190,15 +190,9 @@ try {
$stmtEmployees->close();
} elseif ($role === 'directeur de campus') {
$filters['societes'] = [];
$filters['services'] = [];
$result = $conn->query("SELECT DISTINCT Nom as societe_nom FROM Societe ORDER BY societe_nom");
while($row = $result->fetch_assoc()) $filters['societes'][] = $row['societe_nom'];
$result = $conn->query("SELECT DISTINCT Nom as service_nom FROM Services ORDER BY service_nom");
while($row = $result->fetch_assoc()) $filters['services'][] = $row['service_nom'];
// Pour le directeur, les filtres se basent sur les congés de son campus
$filters['societes'] = array_values(array_unique(array_column($leaves, 'societe_nom')));
$filters['services'] = array_values(array_unique(array_column($leaves, 'service_nom')));
} elseif (in_array($role, ['president', 'rh'])) {
// 🔹 Récupérer tous les campus, sociétés, services de manière unique

View File

@@ -75,18 +75,17 @@ foreach ($members as $m) {
$nom = $m["surname"] ?? "";
$email = $m["mail"] ?? "";
$service = $m["department"] ?? "";
$description = $m["jobTitle"] ?? null;
if (!$email) continue;
// Insertion ou mise à jour de lutilisateur
$stmt = $conn->prepare("INSERT INTO CollaborateurAD (entraUserId, prenom, nom, email, service, role)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE prenom=?, nom=?, email=?, service=?");
$stmt = $conn->prepare("INSERT INTO CollaborateurAD (entraUserId, prenom, nom, email, service, description, role)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE prenom=?, nom=?, email=?, service=?, description=?");
if ($stmt) {
$role = "Collaborateur"; // attribué uniquement si nouvel utilisateur
$stmt->bind_param("ssssssssss",
$entraUserId, $prenom, $nom, $email, $service, $role,
$prenom, $nom, $email, $service
$role = "Collaborateur";
$stmt->bind_param("ssssssssssss",
$entraUserId, $prenom, $nom, $email, $service, $description, $role,
$prenom, $nom, $email, $service, $description
);
$stmt->execute();
$usersInserted++;

View File

@@ -0,0 +1,62 @@
<?php
// Autoriser CORS
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
exit(0);
}
header("Content-Type: application/json");
// Affichage erreurs PHP (utile pour debug)
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Connexion base de données
$host = "192.168.0.4";
$username = "wpuser";
$password = "-2b/)ru5/Bi8P[7_";
$dbname = "DemandeConge";
$conn = new mysqli($host, $username, $password, $dbname);
if ($conn->connect_error) {
http_response_code(500);
echo json_encode(["success" => false, "message" => "Erreur de connexion à la base de données"]);
exit;
}
// Récupération données JSON POST
$postData = json_decode(file_get_contents("php://input"), true);
if (!isset($postData['notificationId'])) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "Paramètre notificationId manquant"]);
exit;
}
$notificationId = intval($postData['notificationId']);
if ($notificationId <= 0) {
http_response_code(400);
echo json_encode(["success" => false, "message" => "ID notification invalide"]);
exit;
}
// Mettre à jour notification lu = 1
$query = "UPDATE Notifications SET lu = 1 WHERE Id = ?";
$stmt = $conn->prepare($query);
if (!$stmt) {
http_response_code(500);
echo json_encode(["success" => false, "message" => "Erreur préparation requête"]);
exit;
}
$stmt->bind_param("i", $notificationId);
if ($stmt->execute()) {
echo json_encode(["success" => true, "message" => "Notification marquée comme lue"]);
} else {
http_response_code(500);
echo json_encode(["success" => false, "message" => "Erreur lors de la mise à jour"]);
}
$stmt->close();
$conn->close();