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';
🏫
= htmlspecialchars($mode) ?>
-
+
@@ -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 += `