diff --git a/family-calendar.php b/family-calendar.php index ce996ef..46b445b 100644 --- a/family-calendar.php +++ b/family-calendar.php @@ -441,18 +441,18 @@ require __DIR__ . '/header.php'; 🏫 - +
-
+
-
@@ -477,5 +477,6 @@ window.FAMILY_CONFIG = { } }; - + + \ No newline at end of file diff --git a/includes/db.php b/includes/db.php index 7a00b41..ac4441f 100644 --- a/includes/db.php +++ b/includes/db.php @@ -1,52 +1,59 @@ PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false, + PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci", + PDO::ATTR_TIMEOUT => 30, +]; + try { - $pdo = new PDO( - "mysql:host=$host;dbname=$db;charset=utf8mb4", - $user, $pass, - [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, - PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci", - PDO::ATTR_TIMEOUT => 30, - ] - ); - $pdo->exec("SET collation_connection = utf8mb4_general_ci"); + $pdo = new PDO($dsn, $user, $pass, $options); - try { - $foyer = $pdo->query("SELECT currency, zone_scolaire FROM pf_foyer_settings WHERE id = 1")->fetch(); - if (!defined('CURRENCY')) { - define('CURRENCY', $foyer['currency'] ?? '€'); - } - if (!defined('ZONE_SCOLAIRE')) { - define('ZONE_SCOLAIRE', $foyer['zone_scolaire'] ?? 'C'); - } - } catch (\PDOException $e) { - if (!defined('CURRENCY')) define('CURRENCY', '€'); - if (!defined('ZONE_SCOLAIRE')) define('ZONE_SCOLAIRE', 'C'); - } - // ------------------------------------------------ + // Forcer la collation + $pdo->exec("SET collation_connection = utf8mb4_general_ci"); + $pdo->exec("SET collation_database = utf8mb4_general_ci"); + $pdo->exec("SET collation_server = utf8mb4_general_ci"); } catch (\PDOException $e) { - if (!headers_sent()) { header('Content-Type: application/json'); http_response_code(500); } - die(json_encode(['ok' => false, 'error' => 'Erreur BDD : ' . $e->getMessage()])); + die("Erreur de connexion BDD (Host: $host, DB: $db) : " . $e->getMessage()); } ?> \ No newline at end of file diff --git a/includes/meta_db.php b/includes/meta_db.php index 88e7f39..1c33389 100644 --- a/includes/meta_db.php +++ b/includes/meta_db.php @@ -1,9 +1,23 @@ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, + PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci", ] ); } catch (\PDOException $e) { die(json_encode(['error' => 'Meta DB unavailable: ' . $e->getMessage()])); } +?> \ No newline at end of file diff --git a/modules/budget/views/budget_prev.php b/modules/budget/views/budget_prev.php index 1d51310..4fd7c37 100644 --- a/modules/budget/views/budget_prev.php +++ b/modules/budget/views/budget_prev.php @@ -1,8 +1,8 @@ query("SELECT id, name, user_id, role, color FROM pf_people WHERE role NOT IN ('enfant', 'nounou') AND is_active = 1 ORDER BY id ASC"); +// 1. Chargement dynamique des ADULTES de la famille (Exclusion stricte des rôles secondaires) +$stmtPeople = $pdo->query("SELECT id, name, user_id, role, color FROM pf_people WHERE role IN ('parent') AND is_active = 1 ORDER BY id ASC"); $budgetParents = $stmtPeople->fetchAll(PDO::FETCH_ASSOC); // Sécurité : au cas où aucun adulte n'est trouvé, on évite un crash diff --git a/modules/family-calendar/family-calendar.js b/modules/family-calendar/family-calendar.js index 6916b0b..b0e29e9 100644 --- a/modules/family-calendar/family-calendar.js +++ b/modules/family-calendar/family-calendar.js @@ -1289,46 +1289,78 @@ document.addEventListener("DOMContentLoaded", () => { } generateMonthSummaryHTML(year, month) { - const stats = { off: 0, extra: 0, sick: 0, presence: 0 }; + // 1. Identifier UNIQUEMENT les enfants qui ont le mode de garde "Nounou" + const nounouKids = (window.FAMILY_CONFIG.kids || []).filter( + (k) => k.modes && k.modes.some((m) => m.toLowerCase() === "nounou"), + ); + + // S'il n'y a pas d'enfant avec ce mode, on ne retourne rien pour ce bloc + if (nounouKids.length === 0) return ""; + const daysInMonth = new Date(year, month + 1, 0).getDate(); + let html = ""; - for (let d = 1; d <= daysInMonth; d++) { - const dateObj = new Date(year, month, d); - const dayOfWeek = dateObj.getDay(); - if (dayOfWeek === 0 || dayOfWeek === 6) continue; + // 2. Boucler sur chaque enfant concerné pour un calcul individuel + nounouKids.forEach((kid) => { + let workingDays = 0; + let off = 0; + let extra = 0; + let sick = 0; - const iso = this.getLocalIsoDate(dateObj); - const dayEvents = this.events.filter((e) => e.date === iso); + for (let d = 1; d <= daysInMonth; d++) { + const dateObj = new Date(year, month, d); + const dayOfWeek = dateObj.getDay(); - dayEvents.forEach((e) => { - const dur = parseFloat(e.duration) || 1; - if (e.type === "HELPER_OFF") stats.off += dur; - if (e.type === "HELPER_EXTRA") stats.extra += dur; - if (e.type === "CHILD_SICK") stats.sick += dur; - }); + // Exclure les week-ends des jours ouvrés (0 = Dimanche, 6 = Samedi) + if (dayOfWeek === 0 || dayOfWeek === 6) continue; + + const iso = this.getLocalIsoDate(dateObj); + + // Exclure les jours fériés des jours ouvrés + if (this.publicHolidayDates.has(iso)) continue; + + // C'est un jour ouvré valide + workingDays += 1; + + const dayEvents = this.events.filter((e) => e.date === iso); - if ( - !this.publicHolidayDates.has(iso) && - !dayEvents.some((e) => e.type === "VACANCES_SCOLAIRES") - ) { - let dayAbsence = 0; dayEvents.forEach((e) => { - if (["HELPER_OFF", "HELPER_EXTRA", "CHILD_SICK"].includes(e.type)) { - dayAbsence += parseFloat(e.duration) || 1; + const dur = parseFloat(e.duration) || 1; + + if (e.type === "HELPER_OFF") off += dur; + if (e.type === "HELPER_EXTRA") extra += dur; + + // On vérifie que la maladie concerne BIEN cet enfant précis ! + if ( + e.type === "CHILD_SICK" && + Number(e.person_id) === Number(kid.id) + ) { + sick += dur; } }); - stats.presence += Math.max(0, 1 - dayAbsence); } - } - return ` -
-
Off ${parseFloat(stats.off.toFixed(1))} j
-
Extra ${parseFloat(stats.extra.toFixed(1))} j
-
Maladie ${parseFloat(stats.sick.toFixed(1))} j
-
Présence ${parseFloat(stats.presence.toFixed(1))} j
+ // Calcul final strict : Jours ouvrés - Maladie (de l'enfant) - Extra - Off + const presence = Math.max(0, workingDays - sick - extra - off); + + // Rendu HTML individuel + html += ` +
+
+ 👧👦 Présence ${kid.name} (Nounou) +
+
+
Ouvrés ${workingDays} j
+
Off ${parseFloat(off.toFixed(1))} j
+
Extra ${parseFloat(extra.toFixed(1))} j
+
Maladie ${parseFloat(sick.toFixed(1))} j
+
Présence ${parseFloat(presence.toFixed(1))} j
+
- `; + `; + }); + + return html; } scrollToCurrentMonth() { diff --git a/modules/holidays/holidays.js b/modules/holidays/holidays.js index db2e687..b784a0a 100644 --- a/modules/holidays/holidays.js +++ b/modules/holidays/holidays.js @@ -785,7 +785,7 @@ function openPlanningModal(step) {
`; - for (let h = 8; h <= 22; h++) { + for (let h = 6; h <= 23; h++) { let hourStr = h.toString().padStart(2, "0") + ":00"; html += `