+6
-5
@@ -441,10 +441,9 @@ require __DIR__ . '/header.php';
|
||||
<span style="font-size:1.1rem; line-height:1; margin-right:6px;">🏫</span>
|
||||
<span><?= htmlspecialchars($mode) ?></span>
|
||||
</div>
|
||||
<?php else:
|
||||
$hue = ($index * 137) % 360; ?>
|
||||
<?php else: ?>
|
||||
<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>
|
||||
</div>
|
||||
<?php endif;
|
||||
@@ -452,7 +451,8 @@ require __DIR__ . '/header.php';
|
||||
|
||||
<!-- Enfants Malades (Dynamique avec couleur BDD) -->
|
||||
<?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-color" style="background: <?= htmlspecialchars($color) ?>; opacity: 0.8;"></div>
|
||||
@@ -477,5 +477,6 @@ window.FAMILY_CONFIG = {
|
||||
}
|
||||
};
|
||||
</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'; ?>
|
||||
+45
-38
@@ -1,52 +1,59 @@
|
||||
<?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) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$host = getenv('DB_HOST') ?: 'househub-db';
|
||||
$user = getenv('DB_USER') ?: 'househub';
|
||||
$pass = getenv('DB_PASS') ?: 'changeme';
|
||||
$db = $_SESSION['family_db'] ?? null;
|
||||
$envHost = getenv('DB_HOST');
|
||||
|
||||
if (!$db) {
|
||||
$current = basename($_SERVER['PHP_SELF'] ?? '');
|
||||
if (!in_array($current, ['login.php', 'register.php'])) {
|
||||
header('Location: /login.php');
|
||||
exit;
|
||||
}
|
||||
return;
|
||||
// 1. Détection stricte de l'environnement
|
||||
if ($envHost === 'househub-db') {
|
||||
// Docker
|
||||
$host = 'househub-db';
|
||||
$user = getenv('DB_USER') ?: 'househub';
|
||||
$pass = getenv('DB_PASS') ?: 'changeme';
|
||||
} 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 {
|
||||
$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());
|
||||
}
|
||||
?>
|
||||
+20
-4
@@ -1,9 +1,23 @@
|
||||
<?php
|
||||
// includes/meta_db.php
|
||||
// Connexion à la base meta (gestion des familles et utilisateurs)
|
||||
$meta_host = getenv('DB_HOST') ?: 'househub-db';
|
||||
$meta_db = 'househub_meta';
|
||||
$meta_user = getenv('DB_USER') ?: 'househub';
|
||||
$meta_pass = getenv('DB_PASS') ?: 'changeme';
|
||||
|
||||
$envHost = getenv('DB_HOST');
|
||||
|
||||
// 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 {
|
||||
$meta_pdo = new PDO(
|
||||
@@ -14,8 +28,10 @@ try {
|
||||
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",
|
||||
]
|
||||
);
|
||||
} catch (\PDOException $e) {
|
||||
die(json_encode(['error' => 'Meta DB unavailable: ' . $e->getMessage()]));
|
||||
}
|
||||
?>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
// modules/budget/views/budget_prev.php
|
||||
|
||||
// 1. Chargement dynamique des ADULTES de la famille
|
||||
$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");
|
||||
// 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
|
||||
|
||||
@@ -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 `
|
||||
<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);">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
// 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 += `
|
||||
<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>
|
||||
`;
|
||||
`;
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
scrollToCurrentMonth() {
|
||||
|
||||
@@ -785,7 +785,7 @@ function openPlanningModal(step) {
|
||||
<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";
|
||||
html += `
|
||||
<div class="hol-time-slot" data-date="${dateStr}" data-time="${hourStr}"
|
||||
|
||||
Reference in New Issue
Block a user