ouragan
Deploy HouseHub / deploy (push) Successful in 2s

This commit is contained in:
2026-06-17 12:27:29 +02:00
parent 16b08dd03b
commit d6e95c1b2a
18 changed files with 2299 additions and 2175 deletions
@@ -52,3 +52,10 @@
[2026-05-06T20:43:43+02:00] RAW INPUT: [{"date":"2026-05-06","type":"AVIS","duration":1,"person":"Carole"}]
[2026-05-06T20:44:36+02:00] RAW INPUT: [{"date":"2026-05-06","type":"PEP_SICK","duration":1,"person":"Carole"}]
[2026-05-20T16:40:17+02:00] RAW INPUT: [{"date":"2026-05-19","type":"OFF_CAROLE","duration":1,"person":"Carole"}]
[2026-06-16T14:20:35+02:00] RAW INPUT: [{"date":"2026-07-29","type":"CARE_MODE","duration":1,"person":"Centre"}]
[2026-06-16T14:20:43+02:00] RAW INPUT: [{"date":"2026-06-16","type":"CARE_MODE","duration":1,"person":"Centre"}]
[2026-06-16T14:22:51+02:00] RAW INPUT: [{"date":"2026-06-16","type":"CHILD_SICK","duration":1,"person":"5"}]
[2026-06-16T14:23:10+02:00] RAW INPUT: [{"date":"2026-06-16","type":"CHILD_SICK","duration":1,"person":"5"}]
[2026-06-16T14:23:33+02:00] RAW INPUT: [{"date":"2026-06-18","type":"CARE_MODE","duration":1,"person":"Nounou"}]
[2026-06-16T14:23:54+02:00] RAW INPUT: [{"date":"2026-06-11","type":"HELPER_OFF","duration":1,"person":"1"}]
[2026-06-16T14:23:57+02:00] RAW INPUT: [{"date":"2026-06-25","type":"CHILD_SICK","duration":1,"person":"4"}]
@@ -8,77 +8,98 @@ $action = $input['action'] ?? '';
if (!$action) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Action manquante.']);
echo json_encode(['success' => false, 'status' => 'error', 'message' => 'Action manquante.']);
exit;
}
try {
// --- SUPPRESSION UNITAIRE ---
if ($action === 'delete') {
$eventId = (int)($input['event_id'] ?? 0);
if ($eventId <= 0) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'ID manquant.']);
echo json_encode(['success' => false, 'status' => 'error', 'message' => 'ID manquant.']);
exit;
}
$stmt = $pdo->prepare("DELETE FROM pf_events WHERE id = ?");
$stmt->execute([$eventId]);
echo json_encode(['status' => 'success']);
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
// --- MISE À JOUR UNITAIRE ---
if ($action === 'update') {
$eventId = (int)($input['event_id'] ?? 0);
$newType = $input['new_type'] ?? '';
if ($eventId <= 0 || !$newType) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Données manquantes.']);
echo json_encode(['success' => false, 'status' => 'error', 'message' => 'Données manquantes.']);
exit;
}
$stmt = $pdo->prepare("UPDATE pf_events SET event_type = ? WHERE id = ?");
$stmt->execute([$newType, $eventId]);
echo json_encode(['status' => 'success']);
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
if ($action === 'bulk_delete_day_types_person') {
$dates = $input['dates'] ?? [];
$types = $input['types'] ?? [];
$person_id = $input['person_id'] ?? null;
$dates = array_filter($dates, function($d) { return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); });
if (empty($dates) || empty($types)) {
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
$datePlaceholders = implode(',', array_fill(0, count($dates), '?'));
$typePlaceholders = implode(',', array_fill(0, count($types), '?'));
$sql = "DELETE FROM pf_events WHERE event_date IN ($datePlaceholders) AND event_type IN ($typePlaceholders)";
$params = array_merge($dates, $types);
// 🔥 LE CORRECTIF : On cherche le 0 en base de données
if ($person_id === null || $person_id === '' || (int)$person_id === 0) {
$sql .= " AND person_id = 0";
} else {
$sql .= " AND person_id = ?";
$params[] = (int)$person_id;
}
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
// --- SUPPRESSION DE MASSE (Par date et type) ---
// Utilisé quand on ajoute un événement pour nettoyer les doublons potentiels (ex: Off vs Extra)
if ($action === 'bulk_delete_day_types') {
$dates = $input['dates'] ?? [];
$types = $input['types'] ?? [];
$dates = array_filter($dates, function($d) {
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d);
});
$dates = array_filter($dates, function($d) { return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); });
if (empty($dates) || empty($types)) {
echo json_encode(['status' => 'success']);
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
// Création des placeholders IN (?,?,?)
$datePlaceholders = implode(',', array_fill(0, count($dates), '?'));
$typePlaceholders = implode(',', array_fill(0, count($types), '?'));
$sql = "DELETE FROM pf_events WHERE event_date IN ($datePlaceholders) AND event_type IN ($typePlaceholders)";
$stmt = $pdo->prepare($sql);
// Fusion des tableaux pour l'exécution
$stmt->execute(array_merge($dates, $types));
echo json_encode(['status' => 'success']);
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
// --- SUPPRESSION TOTALE SUR DES DATES ---
if ($action === 'bulk_delete_all') {
$dates = $input['dates'] ?? [];
$dates = array_filter($dates, function($d) {
return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d);
});
$dates = array_filter($dates, function($d) { return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); });
if (empty($dates) || empty($types)) {
echo json_encode(['status' => 'success']);
if (empty($dates)) {
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
@@ -87,15 +108,15 @@ try {
$stmt = $pdo->prepare($sql);
$stmt->execute($dates);
echo json_encode(['status' => 'success']);
echo json_encode(['success' => true, 'status' => 'success']);
exit;
}
// Action inconnue
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Action non reconnue : ' . $action]);
echo json_encode(['success' => false, 'status' => 'error', 'message' => 'Action non reconnue : ' . $action]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => $e->getMessage()]);
}
echo json_encode(['success' => false, 'status' => 'error', 'message' => $e->getMessage()]);
}
?>
@@ -1,33 +1,14 @@
<?php
// includes/api/save-events.php
// modules/family-calendar/includes/api/save-events.php
header('Content-Type: application/json');
require __DIR__ . '/../../../../includes/db.php';
$rawInput = file_get_contents('php://input');
$eventsToSave = json_decode($rawInput, true);
// Optionnel : tu peux commenter/supprimer ces logs en production pour économiser du disque
file_put_contents(
__DIR__ . '/events-debug.log',
"[" . date('c') . "] RAW INPUT: " . $rawInput . PHP_EOL,
FILE_APPEND
);
if (empty($eventsToSave) || !is_array($eventsToSave)) {
http_response_code(400);
echo json_encode(['status' => 'error', 'message' => 'Aucune donnée d\'événement reçue.']);
exit;
}
$inserted = [];
require_once __DIR__ . '/../../../../includes/db.php';
try {
// 1. OPTIMISATION : On récupère toutes les personnes d'un coup (Mapping)
$stmtPeople = $pdo->query("SELECT id, name FROM pf_people");
$peopleMap = [];
while ($row = $stmtPeople->fetch(PDO::FETCH_ASSOC)) {
// On crée un tableau associatif : ['Carole' => 1, 'Alex' => 2, etc.]
$peopleMap[$row['name']] = $row['id'];
$rawInput = file_get_contents('php://input');
$eventsToSave = json_decode($rawInput, true);
if (empty($eventsToSave) || !is_array($eventsToSave)) {
throw new Exception("Aucune donnée d'événement reçue.");
}
$pdo->beginTransaction();
@@ -36,26 +17,32 @@ try {
VALUES (:event_date, :event_type, :person_id, :duration)";
$stmt = $pdo->prepare($sql);
foreach ($eventsToSave as $event) {
$person_id = null;
$inserted = [];
// 2. On vérifie simplement dans notre tableau (plus de requête SQL ici !)
if (!empty($event['person']) && isset($peopleMap[$event['person']])) {
$person_id = $peopleMap[$event['person']];
foreach ($eventsToSave as $event) {
// 🔥 LE CORRECTIF : On initialise à 0 (car NOT NULL en base)
$person_id = 0;
if (!empty($event['person_id']) && is_numeric($event['person_id'])) {
$person_id = (int)$event['person_id'];
} elseif (!empty($event['person']) && is_numeric($event['person'])) {
$person_id = (int)$event['person'];
}
$duration = isset($event['duration']) ? (float)$event['duration'] : 1.0;
$stmt->execute([
':event_date' => $event['date'],
':event_type' => $event['type'],
':person_id' => $person_id,
':duration' => $event['duration'] ?? 1.0,
':duration' => $duration,
]);
$inserted[] = [
'id' => $pdo->lastInsertId(),
'date' => $event['date'],
'type' => $event['type'],
'duration' => $event['duration'] ?? 1.0,
'duration' => $duration,
'person_id' => $person_id,
];
}
@@ -63,12 +50,20 @@ try {
$pdo->commit();
echo json_encode([
'success' => true,
'status' => 'success',
'inserted' => $inserted,
]);
} catch (Exception $e) {
$pdo->rollBack();
if (isset($pdo) && $pdo->inTransaction()) {
$pdo->rollBack();
}
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Erreur lors de la sauvegarde : ' . $e->getMessage()]);
}
echo json_encode([
'success' => false,
'status' => 'error',
'message' => 'Erreur SQL : ' . $e->getMessage()
]);
}
?>
@@ -11,17 +11,18 @@ try {
if ($action === 'get_all') {
// A. Options globales du foyer
$stmtFoyer = $pdo->query("SELECT zone_scolaire, care_modes FROM pf_foyer_settings LIMIT 1");
$foyer = $stmtFoyer->fetch(PDO::FETCH_ASSOC) ?: ['zone_scolaire' => 'C', 'care_modes' => '["Nounou","Centre"]'];
// On retire le fallback "Nounou" codé en dur, on part sur vide si rien n'est configuré
$foyer = $stmtFoyer->fetch(PDO::FETCH_ASSOC) ?: ['zone_scolaire' => 'C', 'care_modes' => '[]'];
// B. Liste des membres de la famille
$stmtPeople = $pdo->query("SELECT id, name, role FROM pf_people WHERE is_active = 1 ORDER BY role DESC, name ASC");
// B. Liste des membres de la famille (Triés par rôle pour grouper Parents, Enfants, Helpers)
// 🟢 CORRECTION : Ajout de la colonne `care_modes` et `color` pour le Javascript !
$stmtPeople = $pdo->query("SELECT id, name, role, care_modes, color FROM pf_people WHERE is_active = 1 ORDER BY role ASC, name ASC");
$people = $stmtPeople->fetchAll(PDO::FETCH_ASSOC);
// C. Matrice des congés par personne
$stmtLeaves = $pdo->query("SELECT person_id, leave_type, anniversary_date FROM pf_person_leave_meta");
$stmtLeaves = $pdo->query("SELECT person_id, leave_type, anniversary_date, method, allowance FROM pf_person_leave_meta");
$leavesRaw = $stmtLeaves->fetchAll(PDO::FETCH_ASSOC);
// On organise les congés par ID de personne pour faciliter le traitement côté JS
$leavesMap = [];
foreach ($leavesRaw as $leave) {
$leavesMap[$leave['person_id']][] = [
@@ -32,6 +33,21 @@ try {
];
}
// D. Paramètres dynamiques du calendrier (pf_settings)
$stmtSettings = $pdo->query("SELECT setting_key, setting_value FROM pf_settings WHERE module = 'calendar'");
$calendarSettingsRaw = $stmtSettings->fetchAll(PDO::FETCH_KEY_PAIR); // Crée un tableau [key => value]
// Paramètres par défaut si la table est vide
$calendarSettings = [
'calendar_default_view' => $calendarSettingsRaw['calendar_default_view'] ?? 'month',
'calendar_first_day' => $calendarSettingsRaw['calendar_first_day'] ?? '1',
'calendar_working_hours' => $calendarSettingsRaw['calendar_working_hours'] ?? '08:00-19:00'
];
// E. NOUVEAU : Récupération du catalogue des types de congés de la famille
$stmtLeaveTypes = $pdo->query("SELECT code, label, default_allowance, reset_month, allow_carry_over FROM pf_leave_types ORDER BY label ASC");
$leaveTypes = $stmtLeaveTypes->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'data' => [
@@ -40,7 +56,9 @@ try {
'care_modes' => json_decode($foyer['care_modes'] ?? '[]', true)
],
'people' => $people,
'leaves' => $leavesMap
'leaves' => $leavesMap,
'calendar_settings' => $calendarSettings,
'leave_types' => $leaveTypes // On envoie le catalogue au JS !
]
]);
exit;
@@ -48,7 +66,7 @@ try {
// ─── 2. SAUVEGARDE DU FOYER (ONGLET 1) ───
if ($action === 'save_foyer') {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Error("Méthode non autorisée");
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Méthode non autorisée");
$zone = trim($_POST['zone_scolaire'] ?? 'C');
$modesRaw = $_POST['care_modes'] ?? '[]';
@@ -64,7 +82,7 @@ try {
// ─── 3. SAUVEGARDE DES CONGÉS D'UN MEMBRE (ONGLET 2) ───
if ($action === 'save_member_leaves') {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Error("Méthode non autorisée");
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Méthode non autorisée");
$personId = (int)($_POST['person_id'] ?? 0);
$leavesData = json_decode($_POST['leaves'] ?? '[]', true);
@@ -73,19 +91,22 @@ try {
$pdo->beginTransaction();
// On nettoie les anciennes configurations de congés de cette personne
$stmtDelete = $pdo->prepare("DELETE FROM pf_person_leave_meta WHERE person_id = ?");
$stmtDelete->execute([$personId]);
// On réinsère la nouvelle matrice propre
if (!empty($leavesData)) {
$stmtInsert = $pdo->prepare("INSERT INTO pf_person_leave_meta (person_id, leave_type, method, allowance, anniversary_date) VALUES (?, ?, ?, ?, ?)");
foreach ($leavesData as $leave) {
$type = strtoupper(trim($leave['type']));
$method = in_array($leave['method'], ['FIXED', 'ACCUMULATED']) ? $leave['method'] : 'FIXED';
$method = in_array($leave['method'] ?? '', ['FIXED', 'ACCUMULATED']) ? $leave['method'] : 'FIXED';
$allowance = (float)($leave['allowance'] ?? 0);
$date = trim($leave['date']);
// Si le JS n'envoie que "MM-DD" (Renouvellement perpétuel), on ajoute l'année bissextile 2000 pour satisfaire le format DATE de MySQL
if (preg_match('/^\d{2}-\d{2}$/', $date)) {
$date = "2000-" . $date;
}
if (!empty($type) && !empty($date)) {
$stmtInsert->execute([$personId, $type, $method, $allowance, $date]);
}
@@ -97,6 +118,52 @@ try {
exit;
}
// ─── 4. SAUVEGARDE DES PARAMÈTRES D'AFFICHAGE DU CALENDRIER (ONGLET 3) ───
if ($action === 'save_calendar_settings') {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Méthode non autorisée");
$allowed_keys = ['calendar_default_view', 'calendar_first_day', 'calendar_working_hours'];
$stmt = $pdo->prepare("
INSERT INTO pf_settings (setting_key, setting_value, module)
VALUES (:key, :val, 'calendar')
ON DUPLICATE KEY UPDATE setting_value = :val2
");
$pdo->beginTransaction();
foreach ($allowed_keys as $key) {
if (isset($_POST[$key])) {
$value = trim($_POST[$key]);
$stmt->execute([
'key' => $key,
'val' => $value,
'val2' => $value
]);
}
}
$pdo->commit();
echo json_encode(['success' => true]);
exit;
}
// ─── 5. SAUVEGARDE DES MODES DE GARDE D'UN ENFANT ───
if ($action === 'save_child_care_modes') {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new Exception("Méthode non autorisée");
$person_id = (int)($_POST['person_id'] ?? 0);
$care_modes = $_POST['care_modes'] ?? '[]';
if ($person_id > 0) {
$stmt = $pdo->prepare("UPDATE pf_people SET care_modes = ? WHERE id = ?");
$stmt->execute([$care_modes, $person_id]);
echo json_encode(['success' => true]);
} else {
echo json_encode(['success' => false, 'error' => 'ID de l\'enfant manquant.']);
}
exit;
}
throw new Exception("Action inconnue");
} catch (\Throwable $e) {