update calendar
Deploy HouseHub / deploy (push) Successful in 2s

This commit is contained in:
2026-07-22 18:36:08 +02:00
parent c48a6ba116
commit 49d4ec7c35
6 changed files with 136 additions and 80 deletions
+7 -6
View File
@@ -441,18 +441,18 @@ require __DIR__ . '/header.php';
<span style="font-size:1.1rem; line-height:1; margin-right:6px;">🏫</span> <span style="font-size:1.1rem; line-height:1; margin-right:6px;">🏫</span>
<span><?= htmlspecialchars($mode) ?></span> <span><?= htmlspecialchars($mode) ?></span>
</div> </div>
<?php else: <?php else: ?>
$hue = ($index * 137) % 360; ?>
<div class="pf-legend-item"> <div class="pf-legend-item">
<div class="pf-legend-color" style="background: hsl(<?= $hue ?>, 70%, 50%);"></div> <div class="pf-legend-color" style="background: var(--primary);"></div>
<span><?= htmlspecialchars($mode) ?></span> <span><?= htmlspecialchars($mode) ?></span>
</div> </div>
<?php endif; <?php endif;
endforeach; ?> endforeach; ?>
<!-- Enfants Malades (Dynamique avec couleur BDD) --> <!-- Enfants Malades (Dynamique avec couleur BDD) -->
<?php foreach ($kids as $kid): <?php foreach ($kids as $kid):
$color = !empty($kid['color']) ? $kid['color'] : 'var(--danger)'; // Fallback aligné exactement sur celui du JS (#e11d48)
$color = !empty($kid['color']) ? $kid['color'] : '#e11d48';
?> ?>
<div class="pf-legend-item"> <div class="pf-legend-item">
<div class="pf-legend-color" style="background: <?= htmlspecialchars($color) ?>; opacity: 0.8;"></div> <div class="pf-legend-color" style="background: <?= htmlspecialchars($color) ?>; opacity: 0.8;"></div>
@@ -477,5 +477,6 @@ window.FAMILY_CONFIG = {
} }
}; };
</script> </script>
<script src="/modules/family-calendar/family-calendar.js"></script>
<script src="/modules/family-calendar/family-calendar.js?v=<?= time() ?>"></script>
<?php require __DIR__ . '/footer.php'; ?> <?php require __DIR__ . '/footer.php'; ?>
+45 -38
View File
@@ -1,52 +1,59 @@
<?php <?php
// includes/db.php
if (file_exists(__DIR__ . '/config.php')) {
require_once __DIR__ . '/config.php';
}
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
session_start(); session_start();
} }
$host = getenv('DB_HOST') ?: 'househub-db'; $envHost = getenv('DB_HOST');
$user = getenv('DB_USER') ?: 'househub';
$pass = getenv('DB_PASS') ?: 'changeme';
$db = $_SESSION['family_db'] ?? null;
if (!$db) { // 1. Détection stricte de l'environnement
$current = basename($_SERVER['PHP_SELF'] ?? ''); if ($envHost === 'househub-db') {
if (!in_array($current, ['login.php', 'register.php'])) { // Docker
header('Location: /login.php'); $host = 'househub-db';
exit; $user = getenv('DB_USER') ?: 'househub';
} $pass = getenv('DB_PASS') ?: 'changeme';
return; } else {
// XAMPP
$host = '127.0.0.1'; // Plus stable que 'localhost' sous XAMPP
$user = 'root';
$pass = '';
} }
// 2. Multi-tenant strict : cibler la DB de la famille courante
$db = 'househub_meta'; // Fallback par défaut
if (!empty($_SESSION['user']['family_id'])) {
$db = 'househub_f' . (int)$_SESSION['user']['family_id'];
}
$charset = 'utf8mb4';
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$options = [
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,
];
try { try {
$pdo = new PDO( $pdo = new PDO($dsn, $user, $pass, $options);
"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");
try { // Forcer la collation
$foyer = $pdo->query("SELECT currency, zone_scolaire FROM pf_foyer_settings WHERE id = 1")->fetch(); $pdo->exec("SET collation_connection = utf8mb4_general_ci");
if (!defined('CURRENCY')) { $pdo->exec("SET collation_database = utf8mb4_general_ci");
define('CURRENCY', $foyer['currency'] ?? '€'); $pdo->exec("SET collation_server = utf8mb4_general_ci");
}
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');
}
// ------------------------------------------------
} catch (\PDOException $e) { } catch (\PDOException $e) {
if (!headers_sent()) { header('Content-Type: application/json'); http_response_code(500); } die("Erreur de connexion BDD (Host: $host, DB: $db) : " . $e->getMessage());
die(json_encode(['ok' => false, 'error' => 'Erreur BDD : ' . $e->getMessage()]));
} }
?> ?>
+20 -4
View File
@@ -1,9 +1,23 @@
<?php <?php
// includes/meta_db.php
// Connexion à la base meta (gestion des familles et utilisateurs) // Connexion à la base meta (gestion des familles et utilisateurs)
$meta_host = getenv('DB_HOST') ?: 'househub-db';
$meta_db = 'househub_meta'; $envHost = getenv('DB_HOST');
$meta_user = getenv('DB_USER') ?: 'househub';
$meta_pass = getenv('DB_PASS') ?: 'changeme'; // Détection stricte : Docker injecte toujours 'househub-db' via le docker-compose
if ($envHost === 'househub-db') {
// Environnement Docker (Ton collègue ou la Prod)
$meta_host = 'househub-db';
$meta_user = getenv('DB_USER') ?: 'househub';
$meta_pass = getenv('DB_PASS') ?: 'changeme';
} else {
// Environnement Local XAMPP (Toi)
$meta_host = '127.0.0.1'; // Plus stable que 'localhost' sous XAMPP
$meta_user = 'root';
$meta_pass = '';
}
$meta_db = 'househub_meta';
try { try {
$meta_pdo = new PDO( $meta_pdo = new PDO(
@@ -14,8 +28,10 @@ try {
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_EMULATE_PREPARES => false,
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci",
] ]
); );
} catch (\PDOException $e) { } catch (\PDOException $e) {
die(json_encode(['error' => 'Meta DB unavailable: ' . $e->getMessage()])); die(json_encode(['error' => 'Meta DB unavailable: ' . $e->getMessage()]));
} }
?>
+2 -2
View File
@@ -1,8 +1,8 @@
<?php <?php
// modules/budget/views/budget_prev.php // modules/budget/views/budget_prev.php
// 1. Chargement dynamique des ADULTES de la famille // 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 NOT IN ('enfant', 'nounou') AND is_active = 1 ORDER BY id ASC"); $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); $budgetParents = $stmtPeople->fetchAll(PDO::FETCH_ASSOC);
// Sécurité : au cas où aucun adulte n'est trouvé, on évite un crash // Sécurité : au cas où aucun adulte n'est trouvé, on évite un crash
+61 -29
View File
@@ -1289,46 +1289,78 @@ document.addEventListener("DOMContentLoaded", () => {
} }
generateMonthSummaryHTML(year, month) { 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(); const daysInMonth = new Date(year, month + 1, 0).getDate();
let html = "";
for (let d = 1; d <= daysInMonth; d++) { // 2. Boucler sur chaque enfant concerné pour un calcul individuel
const dateObj = new Date(year, month, d); nounouKids.forEach((kid) => {
const dayOfWeek = dateObj.getDay(); let workingDays = 0;
if (dayOfWeek === 0 || dayOfWeek === 6) continue; let off = 0;
let extra = 0;
let sick = 0;
const iso = this.getLocalIsoDate(dateObj); for (let d = 1; d <= daysInMonth; d++) {
const dayEvents = this.events.filter((e) => e.date === iso); const dateObj = new Date(year, month, d);
const dayOfWeek = dateObj.getDay();
dayEvents.forEach((e) => { // Exclure les week-ends des jours ouvrés (0 = Dimanche, 6 = Samedi)
const dur = parseFloat(e.duration) || 1; if (dayOfWeek === 0 || dayOfWeek === 6) continue;
if (e.type === "HELPER_OFF") stats.off += dur;
if (e.type === "HELPER_EXTRA") stats.extra += dur; const iso = this.getLocalIsoDate(dateObj);
if (e.type === "CHILD_SICK") stats.sick += dur;
}); // 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) => { dayEvents.forEach((e) => {
if (["HELPER_OFF", "HELPER_EXTRA", "CHILD_SICK"].includes(e.type)) { const dur = parseFloat(e.duration) || 1;
dayAbsence += 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 ` // Calcul final strict : Jours ouvrés - Maladie (de l'enfant) - Extra - Off
<div class="fc-month-summary-inline" style="display:flex; justify-content:space-around; gap:10px; margin-top:8px; font-size:0.75rem; background: var(--bg-panel); padding: 8px; border-radius: 8px; border: 1px solid var(--border-light);"> const presence = Math.max(0, workingDays - sick - extra - off);
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Off</span> <strong style="color:var(--text-main); font-size:0.95rem;">${parseFloat(stats.off.toFixed(1))} j</strong></div>
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Extra</span> <strong style="color:var(--text-main); font-size:0.95rem;">${parseFloat(stats.extra.toFixed(1))} j</strong></div> // Rendu HTML individuel
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Maladie</span> <strong style="color:var(--text-main); font-size:0.95rem;">${parseFloat(stats.sick.toFixed(1))} j</strong></div> html += `
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Présence</span> <strong style="color:var(--primary); font-size:0.95rem;">${parseFloat(stats.presence.toFixed(1))} j</strong></div> <div style="margin-top: 10px;">
<div style="font-size: 0.8rem; font-weight: bold; color: var(--text-muted); margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.05em;">
👧👦 Présence ${kid.name} (Nounou)
</div>
<div class="fc-month-summary-inline" style="display:flex; justify-content:space-around; gap:10px; font-size:0.75rem; background: var(--bg-panel); padding: 8px; border-radius: 8px; border: 1px solid var(--border-light);">
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Ouvrés</span> <strong style="color:var(--text-main); font-size:0.95rem;">${workingDays} j</strong></div>
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Off</span> <strong style="color:var(--text-main); font-size:0.95rem;">${parseFloat(off.toFixed(1))} j</strong></div>
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Extra</span> <strong style="color:var(--text-main); font-size:0.95rem;">${parseFloat(extra.toFixed(1))} j</strong></div>
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Maladie</span> <strong style="color:var(--danger); font-size:0.95rem;">${parseFloat(sick.toFixed(1))} j</strong></div>
<div class="fc-summ-pill" style="display:flex; flex-direction:column; align-items:center; flex:1;"><span>Présence</span> <strong style="color:var(--primary); font-size:0.95rem;">${parseFloat(presence.toFixed(1))} j</strong></div>
</div>
</div> </div>
`; `;
});
return html;
} }
scrollToCurrentMonth() { scrollToCurrentMonth() {
+1 -1
View File
@@ -785,7 +785,7 @@ function openPlanningModal(step) {
<div class="hol-time-slots-container"> <div class="hol-time-slots-container">
`; `;
for (let h = 8; h <= 22; h++) { for (let h = 6; h <= 23; h++) {
let hourStr = h.toString().padStart(2, "0") + ":00"; let hourStr = h.toString().padStart(2, "0") + ":00";
html += ` html += `
<div class="hol-time-slot" data-date="${dateStr}" data-time="${hourStr}" <div class="hol-time-slot" data-date="${dateStr}" data-time="${hourStr}"