+6
-5
@@ -441,10 +441,9 @@ 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;
|
||||||
@@ -452,7 +451,8 @@ require __DIR__ . '/header.php';
|
|||||||
|
|
||||||
<!-- 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'; ?>
|
||||||
+39
-32
@@ -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');
|
||||||
|
|
||||||
|
// 1. Détection stricte de l'environnement
|
||||||
|
if ($envHost === 'househub-db') {
|
||||||
|
// Docker
|
||||||
|
$host = 'househub-db';
|
||||||
$user = getenv('DB_USER') ?: 'househub';
|
$user = getenv('DB_USER') ?: 'househub';
|
||||||
$pass = getenv('DB_PASS') ?: 'changeme';
|
$pass = getenv('DB_PASS') ?: 'changeme';
|
||||||
$db = $_SESSION['family_db'] ?? null;
|
} else {
|
||||||
|
// XAMPP
|
||||||
if (!$db) {
|
$host = '127.0.0.1'; // Plus stable que 'localhost' sous XAMPP
|
||||||
$current = basename($_SERVER['PHP_SELF'] ?? '');
|
$user = 'root';
|
||||||
if (!in_array($current, ['login.php', 'register.php'])) {
|
$pass = '';
|
||||||
header('Location: /login.php');
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// 2. Multi-tenant strict : cibler la DB de la famille courante
|
||||||
$pdo = new PDO(
|
$db = 'househub_meta'; // Fallback par défaut
|
||||||
"mysql:host=$host;dbname=$db;charset=utf8mb4",
|
if (!empty($_SESSION['user']['family_id'])) {
|
||||||
$user, $pass,
|
$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_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",
|
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_general_ci",
|
||||||
PDO::ATTR_TIMEOUT => 30,
|
PDO::ATTR_TIMEOUT => 30,
|
||||||
]
|
];
|
||||||
);
|
|
||||||
$pdo->exec("SET collation_connection = utf8mb4_general_ci");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$foyer = $pdo->query("SELECT currency, zone_scolaire FROM pf_foyer_settings WHERE id = 1")->fetch();
|
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||||
if (!defined('CURRENCY')) {
|
|
||||||
define('CURRENCY', $foyer['currency'] ?? '€');
|
// Forcer la collation
|
||||||
}
|
$pdo->exec("SET collation_connection = utf8mb4_general_ci");
|
||||||
if (!defined('ZONE_SCOLAIRE')) {
|
$pdo->exec("SET collation_database = utf8mb4_general_ci");
|
||||||
define('ZONE_SCOLAIRE', $foyer['zone_scolaire'] ?? 'C');
|
$pdo->exec("SET collation_server = utf8mb4_general_ci");
|
||||||
}
|
|
||||||
} 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()]));
|
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
+18
-2
@@ -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');
|
||||||
|
|
||||||
|
// 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_user = getenv('DB_USER') ?: 'househub';
|
||||||
$meta_pass = getenv('DB_PASS') ?: 'changeme';
|
$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()]));
|
||||||
}
|
}
|
||||||
|
?>
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = "";
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
for (let d = 1; d <= daysInMonth; d++) {
|
for (let d = 1; d <= daysInMonth; d++) {
|
||||||
const dateObj = new Date(year, month, d);
|
const dateObj = new Date(year, month, d);
|
||||||
const dayOfWeek = dateObj.getDay();
|
const dayOfWeek = dateObj.getDay();
|
||||||
|
|
||||||
|
// Exclure les week-ends des jours ouvrés (0 = Dimanche, 6 = Samedi)
|
||||||
if (dayOfWeek === 0 || dayOfWeek === 6) continue;
|
if (dayOfWeek === 0 || dayOfWeek === 6) continue;
|
||||||
|
|
||||||
const iso = this.getLocalIsoDate(dateObj);
|
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);
|
const dayEvents = this.events.filter((e) => e.date === iso);
|
||||||
|
|
||||||
dayEvents.forEach((e) => {
|
dayEvents.forEach((e) => {
|
||||||
const dur = parseFloat(e.duration) || 1;
|
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;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
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 (
|
if (
|
||||||
!this.publicHolidayDates.has(iso) &&
|
e.type === "CHILD_SICK" &&
|
||||||
!dayEvents.some((e) => e.type === "VACANCES_SCOLAIRES")
|
Number(e.person_id) === Number(kid.id)
|
||||||
) {
|
) {
|
||||||
let dayAbsence = 0;
|
sick += dur;
|
||||||
dayEvents.forEach((e) => {
|
|
||||||
if (["HELPER_OFF", "HELPER_EXTRA", "CHILD_SICK"].includes(e.type)) {
|
|
||||||
dayAbsence += parseFloat(e.duration) || 1;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
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() {
|
||||||
|
|||||||
@@ -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}"
|
||||||
|
|||||||
Reference in New Issue
Block a user