diff --git a/README.md b/README.md
index c50968b..af3df43 100644
--- a/README.md
+++ b/README.md
@@ -151,12 +151,15 @@ L'architecture de HouseHub isole chaque foyer dans sa propre base de données (`
Afin de transformer complètement l'application héritée de Pacha Family en un produit générique et autonome pour chaque espace familial, les chantiers suivants doivent être menés.
🟢 Niveau 1 : Facile (Constantes et configurations globales)
+
[x] Configuration de base : Extraire la devise (CURRENCY) et la zone de vacances scolaires (ZONE_SCOLAIRE) du fichier config.php pour les stocker dans les paramètres d'espace en BDD.
+[x] Suppression des IDs statiques : Supprimer les IDs parents fixes (ID_ALEX = 2, ID_LAIA = 3) dans config.php au profit d'une lecture dynamique basée sur les utilisateurs réels de la famille connectée (liens via la table pf_people).
+[x] Charte graphique dynamique : Remplacer les couleurs codées en dur (bleu/orange) par des sélecteurs natifs dans l'espace "Paramètres", en générant les variables CSS et leurs nuances (via color-mix()) à la volée.
[ ] Filtres API Éducation Nationale : Rendre dynamique la zone de l'API (zones LIKE '%Zone C%' dans family-calendar.js) pour s'adapter à la région de chaque foyer.
[ ] Identifiant Planka : Variabiliser le project_id Planka par défaut écrit en dur dans planka/api.php.
-[ ] Suppression des IDs statiques : Supprimer les IDs parents fixes (ID_ALEX = 2, ID_LAIA = 3) dans config.php au profit d'une lecture dynamique basée sur les utilisateurs réels de la famille connectée.
🟡 Niveau 2 : Intermédiaire (Listes et dictionnaires en dur)
+
[ ] Gestion des membres (Cadeaux) : Dans gift-list.php, remplacer la liste d'enfants ($children) et d'adultes ($baseAdults, $extraAdults) codée en dur par une lecture dynamique des membres du foyer déclarés en BDD.
[ ] Occasions festives (Cadeaux) : Rendre configurables les occasions spéciales (TIO, NOEL, ROIS, ANNIV, SANT).
[ ] Configuration des catégories (Budget) : Dans budget/views/suivi.php, migrer le tableau $categoriesConfig (FMCG, Essence, École, etc.), leurs couleurs et leurs suggestions de magasins associés vers un stockage paramétrable.
@@ -164,7 +167,9 @@ Afin de transformer complètement l'application héritée de Pacha Family en un
[ ] Types de carburants (Garage) : Variabiliser la liste statique des carburants dans garage.php.
🔴 Niveau 3 : Complexe (Logique métier et Schéma SQL)
-[ ] Refonte relationnelle de la répartition (Budget) : Modifier la structure de la table pf_alloc_values pour supprimer les colonnes figées amount_alex et amount_laia et basculer sur un modèle relationnel (alloc_id, user_id, amount) capable de gérer n'importe quel nombre d'adultes (1, 2, 3 ou plus).
+
+[x] Refonte relationnelle du Budget (Prévisionnel) : Refonte totale de budget_prev.php. Modification de la table pf_alloc_values pour basculer sur des colonnes génériques (amount_p1, amount_p2) et suppression définitive de toute logique conditionnelle liée aux prénoms des concepteurs originaux.
+[ ] Refonte globale de la répartition (Budget) : Étendre le modèle relationnel (alloc_id, user_id, amount) au reste de l'application pour gérer n'importe quel nombre d'adultes (1, 2, 3 ou plus).
[ ] Destinations de transferts (Budget) : Rendre dynamiques les cibles de virements prévisionnels (vers L.Pol, vers L.Pep, etc.) en fonction des comptes épargne créés par la famille.
[ ] Onglets d'Épargne : Générer dynamiquement les sous-onglets de la vue epargne.php à partir des membres réels au lieu des blocs statiques Alex, Laia et Nens.
[ ] Types de Garde (Calendrier) : Supprimer les types d'événements et compteurs codés en dur (OFF_CAROLE, EXTRA_OFF_CAROLE, CENTRE, AVIS, PEP_SICK) pour permettre à chaque foyer de définir ses propres modalités de garde et compteurs de congés associés.
@@ -174,14 +179,15 @@ Afin de transformer complètement l'application héritée de Pacha Family en un
## 📌 Versioning
-| Version | Changements |
-| ------- | ------------------------------------------------------------------------------------------ |
-| v2.2.0 | Multi-tenant (p.1) : Paramètres dynamiques du foyer (€, Zone) via BDD + script migration |
-| v2.1.0 | Module Planka : Kanban natif connecté à Planka via API proxy PHP |
-| v2.0.0 | Module PrintVault : viewer 3D STL/3MF/GCode, stockage local, trajectoires GCode illimitées |
-| v1.5.0 | Modules Todo (rappels Discord), Mémo, Courses (liste partagée) |
-| v1.4.0 | Synchronisation CalDAV iOS |
-| v1.3.0 | Optimisation SQL — suppression des requêtes N+1, temps de réponse ÷3 |
-| v1.2.0 | Modernisation UI — Toasts, confirmations sur-mesure |
-| v1.1.0 | Refonte expérience mobile (Bottom Sheets) |
-| v1.0.0 | Version stable initiale — Budget, Calendrier, Voyages, Cadeaux, Garage |
+| Version | Changements |
+| ------- | ------------------------------------------------------------------------------------------------------------------------- |
+| v2.3.0 | Multi-tenant (p.2) : Refonte BDD Budget (p1, p2), suppression IDs statiques et thèmes de profils dynamiques (color-mix()) |
+| v2.2.0 | Multi-tenant (p.1) : Paramètres dynamiques du foyer (€, Zone) via BDD + script migration |
+| v2.1.0 | Module Planka : Kanban natif connecté à Planka via API proxy PHP |
+| v2.0.0 | Module PrintVault : viewer 3D STL/3MF/GCode, stockage local, trajectoires GCode illimitées |
+| v1.5.0 | Modules Todo (rappels Discord), Mémo, Courses (liste partagée) |
+| v1.4.0 | Synchronisation CalDAV iOS |
+| v1.3.0 | Optimisation SQL — suppression des requêtes N+1, temps de réponse ÷3 |
+| v1.2.0 | Modernisation UI — Toasts, confirmations sur-mesure |
+| v1.1.0 | Refonte expérience mobile (Bottom Sheets) |
+| v1.0.0 | Version stable initiale — Budget, Calendrier, Voyages, Cadeaux, Garage |
diff --git a/docker/schema_family.sql b/docker/schema_family.sql
index 4d7e727..34413ca 100644
--- a/docker/schema_family.sql
+++ b/docker/schema_family.sql
@@ -24,10 +24,13 @@ CREATE TABLE IF NOT EXISTS pf_foyer_settings (
INSERT IGNORE INTO pf_foyer_settings (id, currency, zone_scolaire) VALUES (1, '€', 'C');
-- ─── Personnes ────────────────────────────────────────────────────────────────
-CREATE TABLE IF NOT EXISTS pf_people (
- id INT AUTO_INCREMENT PRIMARY KEY,
- name VARCHAR(100) NOT NULL
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+CREATE TABLE `pf_people` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `name` varchar(50) NOT NULL,
+ `user_id` int(11) DEFAULT NULL,
+ `role` varchar(50) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ci;
-- ─── Calendrier familial ──────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS pf_events (
@@ -243,9 +246,6 @@ SET FOREIGN_KEY_CHECKS = 1;
INSERT IGNORE INTO pf_users (id, username, password_hash, display_name)
VALUES (1, 'admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Admin');
--- Personnes (IDs fixes correspondant aux constantes dans config.php)
-INSERT IGNORE INTO pf_people (id, name) VALUES (2, 'Alex');
-INSERT IGNORE INTO pf_people (id, name) VALUES (3, 'Laia');
-- ─── Garage Manager ───────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS pf_vehicles (
diff --git a/family-calendar.php b/family-calendar.php
index f837c58..a33beb4 100644
--- a/family-calendar.php
+++ b/family-calendar.php
@@ -8,6 +8,12 @@ require_login();
require __DIR__ . '/includes/db.php';
require_once __DIR__ . '/includes/i18n.php';
+$stmtPeople = $pdo->query("SELECT id, name, user_id, role FROM pf_people ORDER BY id ASC");
+$familyPeople = $stmtPeople->fetchAll(PDO::FETCH_ASSOC);
+$parents = array_values(array_filter($familyPeople, function($p) {
+ return strtolower($p['role'] ?? '') === 'parent';
+}));
+
$pageTitle = tr('fc_page_title');
$activePage = "family-calendar";
$mainClass = "pf-family-calendar";
@@ -70,8 +76,12 @@ require __DIR__ . '/header.php';
@@ -169,20 +179,24 @@ require __DIR__ . '/header.php';
= tr('leg_avis') ?> |
= tr('leg_pep_sick') ?> |
= tr('leg_presence') ?> |
-
-
+
+
| CP | JRA | JA |
CP | JRA | JA |
- | = tr('fc_col_av') ?> | = tr('fc_col_use') ?> |
- = tr('fc_col_av') ?> | = tr('fc_col_use') ?> |
- = tr('fc_col_av') ?> | = tr('fc_col_use') ?> |
- = tr('fc_col_av') ?> | = tr('fc_col_use') ?> |
- = tr('fc_col_av') ?> | = tr('fc_col_use') ?> |
- = tr('fc_col_av') ?> | = tr('fc_col_use') ?> |
+ $parent):
+ $parentClass = ($index % 2 === 0) ? 'col-alex' : 'col-laia';
+ ?>
+ = tr('fc_col_av') ?> |
+ = tr('fc_col_use') ?> |
+ = tr('fc_col_av') ?> |
+ = tr('fc_col_use') ?> |
+ = tr('fc_col_av') ?> |
+ = tr('fc_col_use') ?> |
+
diff --git a/global.css b/global.css
index 026cc30..196ce6b 100644
--- a/global.css
+++ b/global.css
@@ -307,7 +307,6 @@ body.pf-home .pf-mobile-close-btn {
.pf-muted-note {
color: var(--text-muted);
font-size: 0.85rem;
- margin-bottom: 20px;
}
.pf-muted-note.pf-muted-note--tight {
margin-bottom: 8px;
@@ -359,6 +358,58 @@ body.pf-home .pf-mobile-close-btn {
gap: 8px;
}
+/* --- SÉLECTEUR DE COULEUR THÉMATIQUE (SETTINGS) --- */
+.pf-color-picker-group {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-top: 6px;
+}
+
+.pf-input-color {
+ /* On réduit la taille pour le rendre plus subtil */
+ width: 36px;
+ height: 36px;
+ /* On enlève les marges/paddings par défaut */
+ padding: 0;
+ cursor: pointer;
+ /* On le rend complètement rond, c'est plus moderne */
+ border-radius: 50%;
+ border: 2px solid #e2e8f0;
+ /* On cache le fond par défaut du navigateur */
+ background: transparent;
+ box-shadow: var(--shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.05));
+ transition: all 0.2s ease;
+ /* Optionnel : masquer la bordure interne sur Chrome/Safari */
+ -webkit-appearance: none;
+}
+
+/* On s'assure que le carré de couleur interne (Webkit) remplit tout et est rond */
+.pf-input-color::-webkit-color-swatch-wrapper {
+ padding: 0;
+}
+.pf-input-color::-webkit-color-swatch {
+ border: none;
+ border-radius: 50%;
+}
+
+/* Même chose pour Firefox */
+.pf-input-color::-moz-color-swatch {
+ border: none;
+ border-radius: 50%;
+}
+
+.pf-input-color:hover {
+ border-color: #94a3b8;
+ transform: scale(1.05);
+}
+
+.pf-color-picker-group .pf-muted-note {
+ font-size: 0.85rem;
+ color: var(--text-muted, #64748b);
+ line-height: 1.4;
+}
+
/* Tuile module (checkbox) — état coché via :has(), compatible thème */
.pf-module-tile {
display: flex;
@@ -968,21 +1019,23 @@ p {
DARK MODE — [data-theme="dark"] sur
══════════════════════════════════════════════════════════════ */
[data-theme="dark"] {
- --text-main: #e6edf3;
- --text-muted: #8b949e;
- --bg-page: #0d1117;
- --bg-panel: #161b22;
+ --text-main: #e6edf3;
+ --text-muted: #8b949e;
+ --bg-page: #0d1117;
+ --bg-panel: #161b22;
--border-light: #30363d;
- --shadow: 0 4px 6px -1px rgba(0,0,0,.4), 0 2px 4px -2px rgba(0,0,0,.3);
- --shadow-lg: 0 10px 15px -3px rgba(0,0,0,.5), 0 4px 6px -4px rgba(0,0,0,.4);
- --bg-subtle: rgba(255,255,255,.06);
- --bg-soft: rgba(59,130,246,.15);
- --bg-danger-soft: rgba(239,68,68,.1);
+ --shadow:
+ 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
+ --shadow-lg:
+ 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
+ --bg-subtle: rgba(255, 255, 255, 0.06);
+ --bg-soft: rgba(59, 130, 246, 0.15);
+ --bg-danger-soft: rgba(239, 68, 68, 0.1);
--bg-overlay: rgba(0, 0, 0, 0.55);
--text-danger-soft: #f87171;
--border-strong: #484f58;
- --border-danger-soft: rgba(239,68,68,.3);
- --focus-ring: rgba(59,130,246,.25);
+ --border-danger-soft: rgba(239, 68, 68, 0.3);
+ --focus-ring: rgba(59, 130, 246, 0.25);
--alert-success-bg: rgba(34, 197, 94, 0.12);
--alert-success-border: rgba(34, 197, 94, 0.45);
--alert-success-text: #86efac;
@@ -993,67 +1046,207 @@ p {
--badge-admin-bg: rgba(59, 130, 246, 0.2);
--badge-admin-text: #93c5fd;
}
-[data-theme="dark"] body { background: var(--bg-page); color: var(--text-main); }
-[data-theme="dark"] .pf-header { background: var(--bg-panel); border-color: var(--border-light); }
-[data-theme="dark"] .pf-logo { color: var(--primary); }
-[data-theme="dark"] .pf-nav-link { color: var(--text-muted); }
-[data-theme="dark"] .pf-nav-link:hover { background: rgba(255,255,255,.06); color: var(--text-main); }
-[data-theme="dark"] .pf-nav-link--active { background: rgba(59,130,246,.15); color: var(--primary); }
-[data-theme="dark"] .pf-user-badge { color: var(--text-muted); }
-[data-theme="dark"] .pf-logout-btn { color: #f87171; }
-[data-theme="dark"] .pf-logout-btn:hover { background: rgba(239,68,68,.1); border-color: rgba(239,68,68,.3); }
-[data-theme="dark"] .pf-burger-btn { color: var(--text-main); }
-[data-theme="dark"] .pf-mobile-menu { background: var(--bg-panel); color: var(--text-main); }
-[data-theme="dark"] .pf-mobile-menu-header { background: var(--bg-page); border-color: var(--border-light); }
-[data-theme="dark"] .pf-mobile-nav-link { color: var(--text-main); border-color: var(--border-light); }
-[data-theme="dark"] .pf-mobile-nav-link:active { background: rgba(255,255,255,.06); }
-[data-theme="dark"] .pf-module-card { background: var(--bg-panel); border-color: var(--border-light); }
-[data-theme="dark"] .pf-module-card:hover { border-color: #484f58; box-shadow: var(--shadow); }
-[data-theme="dark"] .pf-card-title { color: var(--text-main); }
-[data-theme="dark"] .pf-card-desc { color: var(--text-muted); }
-[data-theme="dark"] h1, [data-theme="dark"] h2, [data-theme="dark"] h3 { color: var(--text-main); }
-[data-theme="dark"] p { color: var(--text-muted); }
-[data-theme="dark"] .pf-btn.btn-secondary { background: var(--bg-panel); color: var(--text-muted); border-color: var(--border-light); }
-[data-theme="dark"] .pf-btn.btn-secondary:hover { background: rgba(255,255,255,.05); color: var(--text-main); border-color: #484f58; }
-[data-theme="dark"] .pf-input { background: #1c2128; border-color: var(--border-light); color: var(--text-main); }
-[data-theme="dark"] .pf-input::placeholder { color: #484f58; }
-[data-theme="dark"] .pf-input:focus { background: #1c2128; border-color: var(--primary); }
-[data-theme="dark"] .pf-table { background: var(--bg-panel); }
-[data-theme="dark"] .pf-table th { background: #1c2128; color: var(--text-muted); border-color: var(--border-light); }
-[data-theme="dark"] .pf-table td { border-color: rgba(48,54,61,.5); color: var(--text-main); }
-[data-theme="dark"] .pf-table tbody tr:hover { background: rgba(255,255,255,.03); }
-[data-theme="dark"] .pf-modal-content { background: var(--bg-panel); border: 1px solid var(--border-light); }
-[data-theme="dark"] .pf-modal-title { border-color: var(--border-light); color: var(--text-main); }
-[data-theme="dark"] .modal-footer { border-color: var(--border-light); }
-[data-theme="dark"] .pf-footer { background: var(--bg-panel); border-color: var(--border-light); }
-[data-theme="dark"] .pf-toast { background: var(--bg-panel); color: var(--text-main); box-shadow: 0 10px 25px rgba(0,0,0,.4); }
-[data-theme="dark"] .pf-login-card { background: var(--bg-panel); border-color: var(--border-light); }
-[data-theme="dark"] .pf-login-error { background: rgba(239,68,68,.1); border-color: rgba(239,68,68,.3); }
-[data-theme="dark"] .btn-icon-action.edit:hover { background: rgba(59,130,246,.15); }
-[data-theme="dark"] .btn-icon-action.delete:hover { background: rgba(239,68,68,.1); }
+[data-theme="dark"] body {
+ background: var(--bg-page);
+ color: var(--text-main);
+}
+[data-theme="dark"] .pf-header {
+ background: var(--bg-panel);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-logo {
+ color: var(--primary);
+}
+[data-theme="dark"] .pf-nav-link {
+ color: var(--text-muted);
+}
+[data-theme="dark"] .pf-nav-link:hover {
+ background: rgba(255, 255, 255, 0.06);
+ color: var(--text-main);
+}
+[data-theme="dark"] .pf-nav-link--active {
+ background: rgba(59, 130, 246, 0.15);
+ color: var(--primary);
+}
+[data-theme="dark"] .pf-user-badge {
+ color: var(--text-muted);
+}
+[data-theme="dark"] .pf-logout-btn {
+ color: #f87171;
+}
+[data-theme="dark"] .pf-logout-btn:hover {
+ background: rgba(239, 68, 68, 0.1);
+ border-color: rgba(239, 68, 68, 0.3);
+}
+[data-theme="dark"] .pf-burger-btn {
+ color: var(--text-main);
+}
+[data-theme="dark"] .pf-mobile-menu {
+ background: var(--bg-panel);
+ color: var(--text-main);
+}
+[data-theme="dark"] .pf-mobile-menu-header {
+ background: var(--bg-page);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-mobile-nav-link {
+ color: var(--text-main);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-mobile-nav-link:active {
+ background: rgba(255, 255, 255, 0.06);
+}
+[data-theme="dark"] .pf-module-card {
+ background: var(--bg-panel);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-module-card:hover {
+ border-color: #484f58;
+ box-shadow: var(--shadow);
+}
+[data-theme="dark"] .pf-card-title {
+ color: var(--text-main);
+}
+[data-theme="dark"] .pf-card-desc {
+ color: var(--text-muted);
+}
+[data-theme="dark"] h1,
+[data-theme="dark"] h2,
+[data-theme="dark"] h3 {
+ color: var(--text-main);
+}
+[data-theme="dark"] p {
+ color: var(--text-muted);
+}
+[data-theme="dark"] .pf-btn.btn-secondary {
+ background: var(--bg-panel);
+ color: var(--text-muted);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-btn.btn-secondary:hover {
+ background: rgba(255, 255, 255, 0.05);
+ color: var(--text-main);
+ border-color: #484f58;
+}
+[data-theme="dark"] .pf-input {
+ background: #1c2128;
+ border-color: var(--border-light);
+ color: var(--text-main);
+}
+[data-theme="dark"] .pf-input::placeholder {
+ color: #484f58;
+}
+[data-theme="dark"] .pf-input:focus {
+ background: #1c2128;
+ border-color: var(--primary);
+}
+[data-theme="dark"] .pf-table {
+ background: var(--bg-panel);
+}
+[data-theme="dark"] .pf-table th {
+ background: #1c2128;
+ color: var(--text-muted);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-table td {
+ border-color: rgba(48, 54, 61, 0.5);
+ color: var(--text-main);
+}
+[data-theme="dark"] .pf-table tbody tr:hover {
+ background: rgba(255, 255, 255, 0.03);
+}
+[data-theme="dark"] .pf-modal-content {
+ background: var(--bg-panel);
+ border: 1px solid var(--border-light);
+}
+[data-theme="dark"] .pf-modal-title {
+ border-color: var(--border-light);
+ color: var(--text-main);
+}
+[data-theme="dark"] .modal-footer {
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-footer {
+ background: var(--bg-panel);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-toast {
+ background: var(--bg-panel);
+ color: var(--text-main);
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
+}
+[data-theme="dark"] .pf-login-card {
+ background: var(--bg-panel);
+ border-color: var(--border-light);
+}
+[data-theme="dark"] .pf-login-error {
+ background: rgba(239, 68, 68, 0.1);
+ border-color: rgba(239, 68, 68, 0.3);
+}
+[data-theme="dark"] .btn-icon-action.edit:hover {
+ background: rgba(59, 130, 246, 0.15);
+}
+[data-theme="dark"] .btn-icon-action.delete:hover {
+ background: rgba(239, 68, 68, 0.1);
+}
/* ─── Pill toggle thème ────────────────────────────────────── */
.theme-switch {
- display: inline-flex; align-items: center; gap: 5px;
- background: none; border: none; cursor: pointer;
- padding: 3px 5px; border-radius: 8px; transition: background .15s;
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 3px 5px;
+ border-radius: 8px;
+ transition: background 0.15s;
+}
+.theme-switch:hover {
+ background: #f1f5f9;
+}
+[data-theme="dark"] .theme-switch:hover {
+ background: rgba(255, 255, 255, 0.08);
+}
+.ts-sun,
+.ts-moon {
+ font-size: 0.85rem;
+ line-height: 1;
+ transition: opacity 0.2s;
+ user-select: none;
+}
+[data-theme="dark"] .ts-sun {
+ opacity: 0.3;
+}
+.ts-moon {
+ opacity: 0.3;
+}
+[data-theme="dark"] .ts-moon {
+ opacity: 1;
}
-.theme-switch:hover { background: #f1f5f9; }
-[data-theme="dark"] .theme-switch:hover { background: rgba(255,255,255,.08); }
-.ts-sun, .ts-moon { font-size: .85rem; line-height: 1; transition: opacity .2s; user-select: none; }
-[data-theme="dark"] .ts-sun { opacity: .3; }
- .ts-moon { opacity: .3; }
-[data-theme="dark"] .ts-moon { opacity: 1; }
.ts-track {
- position: relative; width: 36px; height: 20px;
- background: #cbd5e1; border-radius: 999px;
- flex-shrink: 0; transition: background .25s;
+ position: relative;
+ width: 36px;
+ height: 20px;
+ background: #cbd5e1;
+ border-radius: 999px;
+ flex-shrink: 0;
+ transition: background 0.25s;
+}
+[data-theme="dark"] .ts-track {
+ background: #2563eb;
}
-[data-theme="dark"] .ts-track { background: #2563eb; }
.ts-thumb {
- position: absolute; top: 3px; left: 3px;
- width: 14px; height: 14px; border-radius: 50%;
- background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3);
- transition: transform .25s cubic-bezier(.4,0,.2,1);
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ background: #fff;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+}
+[data-theme="dark"] .ts-thumb {
+ transform: translateX(16px);
}
-[data-theme="dark"] .ts-thumb { transform: translateX(16px); }
diff --git a/header.php b/header.php
index 421a7eb..4d2cd9b 100644
--- a/header.php
+++ b/header.php
@@ -130,10 +130,12 @@ $currentLang = $_SESSION['app_lang'] ?? 'fr';
*/
window.I18N = ;
- // ✅ Injection sécurisée (évite le crash si config.php n'est pas chargé)
+ // ✅ Injection sécurisée
window.CONFIG = {
- ID_ALEX: ,
- ID_LAIA: ,
+ // Liste complète des profils de la famille (Membres, Nounou, Enfants)
+ PEOPLE: = isset($familyPeople) ? json_encode($familyPeople) : '[]' ?>,
+ // ID du user global connecté (pour savoir "qui" clique)
+ CURRENT_USER_ID: = isset($_SESSION['user_id']) ? (int)$_SESSION['user_id'] : 'null' ?>,
CURRENCY: '',
ZONE_SCOLAIRE: ''
};
diff --git a/includes/auth.php b/includes/auth.php
index 337638e..e5ea1e5 100644
--- a/includes/auth.php
+++ b/includes/auth.php
@@ -54,3 +54,8 @@ function verify_csrf(?string $token): bool
}
return hash_equals($_SESSION['csrf_token'], $token);
}
+if (!isset($familyPeople) && !empty($family_id)) {
+ require_once __DIR__ . '/db.php';
+ $stmtPeople = $pdo->query("SELECT id, name, user_id FROM pf_people ORDER BY id ASC");
+ $familyPeople = $stmtPeople->fetchAll(PDO::FETCH_ASSOC);
+}
diff --git a/includes/config.php b/includes/config.php
deleted file mode 100644
index 2d1c73b..0000000
--- a/includes/config.php
+++ /dev/null
@@ -1,7 +0,0 @@
-🚀 Début de la migration...";
+echo "
🚀 Début de la migration Globale ...
";
-// 1. Récupérer toutes les bases de données des familles
-$stmt = $meta_pdo->query("SELECT name, db_name FROM families WHERE db_name != ''");
+$stmt = $meta_pdo->query("SELECT id, name, db_name FROM families WHERE db_name != ''");
$families = $stmt->fetchAll();
$host = getenv('DB_HOST') ?: 'househub-db';
$user = getenv('DB_USER') ?: 'househub';
$pass = getenv('DB_PASS') ?: 'changeme';
-// 2. Boucler sur chaque famille
foreach ($families as $f) {
$db_name = $f['db_name'];
- echo "Mise à jour de la famille
{$f['name']} (Base : $db_name) ... ";
+ $family_id = $f['id'];
+ echo "
Mise à jour de {$f['name']} ($db_name)
";
try {
$fam_pdo = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8mb4", $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
- // 3. Injecter la nouvelle table
- $sql = "
- CREATE TABLE IF NOT EXISTS pf_foyer_settings (
- id INT AUTO_INCREMENT PRIMARY KEY,
- currency VARCHAR(10) NOT NULL DEFAULT '€',
- zone_scolaire VARCHAR(5) NOT NULL DEFAULT 'C',
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+ // ==========================================
+ // 1. GESTION DE PF_PEOPLE (user_id, role, color)
+ // ==========================================
+ echo "- Table pf_people : ";
+
+ // user_id
+ try {
+ $fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN user_id INT NULL DEFAULT NULL");
+ echo "user_id OK - ";
+ } catch (\PDOException $e) {
+ if ($e->getCode() == '42S21' || strpos($e->getMessage(), '1060') !== false) {
+ echo "user_id déjà présent - ";
+ } else { throw $e; }
+ }
- INSERT IGNORE INTO pf_foyer_settings (id, currency, zone_scolaire) VALUES (1, '€', 'C');
- ";
+ // Auto-mapping
+ $stmtUsers = $meta_pdo->prepare("SELECT id, username FROM users WHERE family_id = ?");
+ $stmtUsers->execute([$family_id]);
+ $users = $stmtUsers->fetchAll();
+ $mappedCount = 0;
+ foreach ($users as $u) {
+ $stmtUpdate = $fam_pdo->prepare("UPDATE pf_people SET user_id = ? WHERE LOWER(name) = LOWER(?) AND user_id IS NULL");
+ $stmtUpdate->execute([$u['id'], $u['username']]);
+ $mappedCount += $stmtUpdate->rowCount();
+ }
+ echo "$mappedCount profils liés - ";
- $fam_pdo->exec($sql);
- echo "✅ OK
";
+ // role
+ try {
+ $fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN role VARCHAR(50) DEFAULT NULL");
+ echo "role OK - ";
+ } catch (\PDOException $e) {
+ if ($e->getCode() == '42S21' || strpos($e->getMessage(), '1060') !== false) {
+ echo "role déjà présent - ";
+ } else { throw $e; }
+ }
+
+ // 🟢 NOUVEAU : Colonne color
+ try {
+ $fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN color VARCHAR(7) DEFAULT '#0891b2'");
+ echo "colonne 'color' ajoutée - ";
+ } catch (\PDOException $e) {
+ if ($e->getCode() == '42S21' || strpos($e->getMessage(), '1060') !== false) {
+ echo "colonne 'color' déjà présente - ";
+ } else { throw $e; }
+ }
+
+ // Configuration des valeurs par défaut et des rôles
+ $fam_pdo->exec("
+ UPDATE pf_people SET role = 'parent' WHERE user_id IS NOT NULL;
+ UPDATE pf_people SET role = 'nounou' WHERE LOWER(name) = 'carole';
+ ");
+
+ // On donne des couleurs distinctes par défaut aux deux parents (P1 = Cyan, P2 = Orange)
+ $stmtP = $fam_pdo->query("SELECT id FROM pf_people WHERE role = 'parent' ORDER BY id ASC");
+ $pIds = $stmtP->fetchAll(PDO::FETCH_COLUMN);
+ if (isset($pIds[0])) {
+ $fam_pdo->exec("UPDATE pf_people SET color = '#0891b2' WHERE id = " . (int)$pIds[0]);
+ }
+ if (isset($pIds[1])) {
+ $fam_pdo->exec("UPDATE pf_people SET color = '#f59e0b' WHERE id = " . (int)$pIds[1]);
+ }
+
+ echo "Rôles et Couleurs initialisés. ";
+
+ // ==========================================
+ // 2. GESTION DU BUDGET (pf_alloc_values)
+ // ==========================================
+ echo "- Table pf_alloc_values : ";
+ // Vérification et renommage de amount_alex
+ $checkAlex = $fam_pdo->query("SHOW COLUMNS FROM pf_alloc_values LIKE 'amount_alex'")->rowCount();
+ if ($checkAlex > 0) {
+ // Utilisation de CHANGE pour compatibilité maximale avec les anciennes versions MySQL
+ $fam_pdo->exec("ALTER TABLE pf_alloc_values CHANGE amount_alex amount_p1 FLOAT DEFAULT 0");
+ echo "amount_alex -> amount_p1 - ";
+ } else {
+ echo "amount_p1 OK - ";
+ }
+
+ // Vérification et renommage de amount_laia
+ $checkLaia = $fam_pdo->query("SHOW COLUMNS FROM pf_alloc_values LIKE 'amount_laia'")->rowCount();
+ if ($checkLaia > 0) {
+ $fam_pdo->exec("ALTER TABLE pf_alloc_values CHANGE amount_laia amount_p2 FLOAT DEFAULT 0");
+ echo "amount_laia -> amount_p2
";
+ } else {
+ echo "amount_p2 OK";
+ }
+
+ // ==========================================
+ // 3. GESTION DU BUDGET (pf_alloc_categories)
+ // ==========================================
+ echo "- Table pf_alloc_categories : ";
+ $stmtCats1 = $fam_pdo->exec("UPDATE pf_alloc_categories SET name = 'Eco P1' WHERE name LIKE 'Eco Alex%'");
+ $stmtCats2 = $fam_pdo->exec("UPDATE pf_alloc_categories SET name = 'Eco P2' WHERE name LIKE 'Eco Laia%'");
+ echo "Catégories 'Eco' génériques mises à jour (" . ($stmtCats1 + $stmtCats2) . " lignes).
";
+
+ // ==========================================
+ // 4. GESTION DU BUDGET (pf_salary_config)
+ // ==========================================
+ echo "- Table pf_salary_config : ";
+ // On récupère les vrais prénoms pour les remplacer dans les configs de salaires
+ $stmtParents = $fam_pdo->query("SELECT name FROM pf_people WHERE role = 'parent' ORDER BY id ASC");
+ $parents = $stmtParents->fetchAll();
+
+ if (count($parents) >= 2) {
+ $p1_name = $parents[0]['name'];
+ $p2_name = $parents[1]['name'];
+
+ $stmtSal1 = $fam_pdo->prepare("UPDATE pf_salary_config SET person = ? WHERE person = 'Alex'");
+ $stmtSal1->execute([$p1_name]);
+
+ $stmtSal2 = $fam_pdo->prepare("UPDATE pf_salary_config SET person = ? WHERE person = 'Laia'");
+ $stmtSal2->execute([$p2_name]);
+
+ echo "Salaires mappés vers $p1_name et $p2_name.
";
+ } else {
+ echo "Pas assez de parents trouvés pour mapper les salaires.";
+ }
+
} catch (\PDOException $e) {
- echo "❌ Erreur : " . $e->getMessage() . "
";
+ echo "- ❌ Erreur : " . $e->getMessage() . "
";
}
+ echo "
";
}
-echo "
🎉 Migration terminée ! Tu peux supprimer ce fichier.
";
+echo "
🎉 Migration terminée avec succès !
";
?>
\ No newline at end of file
diff --git a/modules/budget/budget.css b/modules/budget/budget.css
index f732ae5..5d9325a 100644
--- a/modules/budget/budget.css
+++ b/modules/budget/budget.css
@@ -1,7 +1,6 @@
/* modules/budget/budget.css */
/* --- 1. VARIABLES & BASE --- */
-/* Le root garde les couleurs sémantiques dont le JS/PHP a besoin localement */
.budget-view {
font-family:
"Segoe UI",
@@ -128,7 +127,7 @@
/* --- 3. BOUTONS SPÉCIFIQUES & ACTIONS --- */
.pf-btn {
- border-radius: 50px; /* Surcharge le global pour le budget */
+ border-radius: 50px;
}
.btn-icon {
background: transparent;
@@ -263,7 +262,6 @@ td:hover .btn-cell-delete {
border-top: 1px solid #e2e8f0;
}
-/* Inputs modifiés */
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
@@ -346,20 +344,94 @@ input[type="number"] {
gap: 30px;
}
.prev-section-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 1px solid #e2e8f0;
+ display: flex !important;
+ flex-direction: row !important;
+ justify-content: space-between !important;
+ align-items: center !important;
+ margin-bottom: 20px;
+ padding: 15px;
+ background: #f8fafc;
+ border-radius: 12px;
+ border: 1px solid #e2e8f0;
+ width: 100% !important;
+ box-sizing: border-box;
}
.prev-section-header h2 {
margin: 0;
- font-size: 1.5rem;
+ font-size: 1.3rem;
font-weight: 800;
color: #0f172a;
letter-spacing: -0.025em;
}
+.prev-header-left {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+}
+.prev-header-actions {
+ display: flex;
+ gap: 10px;
+}
+
+/* Configuration Chromatique Parent 1 */
+.txt-p1,
+.txt-alex {
+ color: color-mix(in srgb, var(--p1-main, #0891b2) 85%, #000000) !important;
+ font-weight: bold;
+ text-align: center;
+}
+.col-p1,
+.col-alex {
+ color: color-mix(in srgb, var(--p1-main, #0891b2) 85%, #000000) !important;
+ text-align: center;
+}
+th.col-p1,
+td.col-p1 {
+ background-color: color-mix(
+ in srgb,
+ var(--p1-main, #0891b2) 8%,
+ #ffffff
+ ) !important;
+ text-align: center;
+}
+input.txt-p1 {
+ color: color-mix(in srgb, var(--p1-main, #0891b2) 85%, #000000) !important;
+ text-align: center;
+ font-weight: 600;
+}
+input.txt-p1:focus {
+ border-color: var(--p1-main, #0891b2) !important;
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--p1-main, #0891b2) 10%, #ffffff) !important;
+}
+
+/* Configuration Chromatique Parent 2 */
+.txt-p2 {
+ color: color-mix(in srgb, var(--p2-main, #f59e0b) 85%, #000000) !important;
+ font-weight: bold;
+ text-align: center;
+}
+.col-p2 {
+ color: color-mix(in srgb, var(--p2-main, #f59e0b) 85%, #000000) !important;
+ text-align: center;
+}
+th.col-p2,
+td.col-p2 {
+ background-color: color-mix(
+ in srgb,
+ var(--p2-main, #f59e0b) 8%,
+ #ffffff
+ ) !important;
+ text-align: center;
+}
+input.txt-p2 {
+ color: color-mix(in srgb, var(--p2-main, #f59e0b) 85%, #000000) !important;
+ text-align: center;
+ font-weight: 600;
+}
+input.txt-p2:focus {
+ border-color: var(--p2-main, #f59e0b) !important;
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--p2-main, #f59e0b) 10%, #ffffff) !important;
+}
.prev-input {
width: 100%;
@@ -385,19 +457,6 @@ input[type="number"] {
font-weight: 700;
}
-.prev-section-header,
-.view-header,
-.budget-view > div[style*="justify-content: space-between"] {
- display: flex !important;
- flex-direction: row !important;
- justify-content: space-between !important;
- align-items: center !important;
- float: none !important;
- position: relative !important;
- width: 100% !important;
- gap: 15px !important;
-}
-
.prev-salary-table {
width: 100%;
max-width: 900px;
@@ -419,6 +478,15 @@ input[type="number"] {
text-align: center;
border-bottom: 1px solid #e2e8f0;
}
+.prev-salary-table th small {
+ font-weight: normal;
+ text-transform: none;
+}
+.prev-salary-table th.th-family-savings,
+.prev-salary-table td.td-family-savings {
+ background: #f0f9ff !important;
+ color: #0284c7 !important;
+}
.prev-salary-table th:first-child {
text-align: left;
}
@@ -426,6 +494,22 @@ input[type="number"] {
padding: 8px 12px;
border-bottom: 1px solid var(--border-light);
text-align: center;
+ vertical-align: middle;
+}
+
+.prev-salary-table tbody tr:nth-child(1) td:first-child {
+ text-align: left !important;
+ font-weight: bold !important;
+ color: var(--text-main) !important;
+ border-left: 4px solid var(--p1-main, #0891b2) !important;
+}
+
+/* Parent 2 (Ligne 2, ex: Laia) */
+.prev-salary-table tbody tr:nth-child(2) td:first-child {
+ text-align: left !important;
+ font-weight: bold !important;
+ color: var(--text-main) !important;
+ border-left: 4px solid var(--p2-main, #f59e0b) !important;
}
.prev-timeline-wrapper {
@@ -455,7 +539,7 @@ input[type="number"] {
border-right: 1px solid var(--border-light);
white-space: nowrap;
text-align: center;
- font-size: 0.9rem;
+ vertical-align: middle;
}
.col-sticky {
@@ -490,6 +574,15 @@ input[type="number"] {
border-left: 2px solid white;
padding: 8px !important;
}
+.th-month span {
+ text-transform: capitalize;
+}
+.th-month .cycle-start-label {
+ font-size: 0.75rem;
+ font-weight: normal;
+ color: #64748b;
+ margin-top: 2px;
+}
.th-month.current {
background: var(--primary) !important;
}
@@ -511,16 +604,42 @@ input[type="number"] {
font-weight: 700;
border-bottom: 2px solid #bfdbfe;
}
-.txt-alex {
- color: #0891b2;
+.row-restant td.td-separator {
+ border-left: 2px solid #cbd5e1;
+ background: #e2e8f0;
}
-.txt-laia {
- color: #f59e0b;
+.row-indicative {
+ background: #f8fafc;
+ color: #94a3b8;
+}
+.row-indicative .col-sticky {
+ opacity: 0.8;
+}
+.cat-name-label {
+ font-weight: 600;
+ color: var(--text-main);
+}
+.row-indicative .cat-name-label {
+ color: #64748b;
+}
+.row-indicative .cat-name-label span {
+ font-size: 0.7rem;
+ border: 1px solid #cbd5e1;
+ border-radius: 4px;
+ padding: 0 4px;
+ margin-left: 5px;
+}
+.cat-target-label {
+ font-size: 0.75rem;
+ color: var(--text-muted);
+ font-style: italic;
+ text-align: right;
}
.txt-global {
color: var(--text-muted);
font-weight: 700;
background: #f8fafc;
+ border-left: 2px solid #e2e8f0;
}
.val-ok {
color: var(--success);
@@ -594,6 +713,9 @@ tr:hover .row-actions {
letter-spacing: 0.05em;
text-align: center;
}
+.recap-header span {
+ text-transform: capitalize;
+}
.recap-table {
width: 100%;
border-collapse: collapse;
@@ -603,6 +725,7 @@ tr:hover .row-actions {
padding: 10px 15px;
border-bottom: 1px solid var(--border-light);
text-align: center;
+ vertical-align: middle;
font-size: 0.95rem;
}
.recap-table th {
@@ -618,30 +741,68 @@ tr:hover .row-actions {
font-weight: 600;
color: #1e293b;
}
-.col-alex {
- background-color: #ecfeff;
- color: #0891b2;
- border-left: 1px solid #cffafe;
+
+.recap-th-content {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
}
-th.col-alex {
- background-color: #cffafe;
- border-bottom-color: #a5f3fc;
+.recap-th-content span {
+ text-transform: uppercase;
}
-.col-laia {
- background-color: #fffbeb;
- color: #d97706;
- border-left: 1px solid #fef3c7;
+.validation-badge {
+ background: #10b981;
+ color: white;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 0.7rem;
+ display: flex;
+ align-items: center;
+ gap: 4px;
}
-th.col-laia {
- background-color: #fef3c7;
- border-bottom-color: #fde68a;
+
+/* --- Style de base du bouton de validation (Générique) --- */
+.recap-table .pf-btn.btn-small {
+ background: #ffffff !important;
+ font-size: 0.7rem !important;
+ padding: 2px 8px !important;
+ height: auto !important;
+ width: auto !important;
+ font-weight: 600;
+ border-radius: 9px !important;
+ box-shadow: none !important;
+ transform: none !important;
+ transition: all 0.2s ease;
}
-.col-global {
- background-color: #f8fafc;
- color: #475569;
- font-weight: 700;
- border-left: 2px solid #e2e8f0;
+
+/* Couleurs dynamiques adaptées au Parent 1 (ex: Féfé) */
+.col-p1 .pf-btn.btn-small {
+ color: var(--p1-main, #0891b2) !important;
+ border: 1px solid var(--p1-main, #0891b2) !important;
}
+.col-p1 .pf-btn.btn-small:hover {
+ background: color-mix(
+ in srgb,
+ var(--p1-main, #0891b2) 12%,
+ #ffffff
+ ) !important;
+}
+
+/* Couleurs dynamiques adaptées au Parent 2 (ex: Laia) */
+.col-p2 .pf-btn.btn-small {
+ color: var(--p2-main, #f59e0b) !important;
+ border: 1px solid var(--p2-main, #f59e0b) !important;
+}
+.col-p2 .pf-btn.btn-small:hover {
+ background: color-mix(
+ in srgb,
+ var(--p2-main, #f59e0b) 12%,
+ #ffffff
+ ) !important;
+}
+
+/* Grand Totaux Structuraux */
.row-grand-total td {
background-color: #1e293b;
color: white;
@@ -649,14 +810,22 @@ th.col-laia {
border: none;
padding: 12px 15px;
}
-.row-grand-total .col-alex {
- background: #164e63;
- color: #67e8f9;
+.row-grand-total .col-p1 {
+ background: color-mix(
+ in srgb,
+ var(--p1-main, #0891b2) 65%,
+ #000000
+ ) !important;
+ color: color-mix(in srgb, var(--p1-main, #0891b2) 20%, #ffffff) !important;
border: none;
}
-.row-grand-total .col-laia {
- background: #78350f;
- color: #fcd34d;
+.row-grand-total .col-p2 {
+ background: color-mix(
+ in srgb,
+ var(--p2-main, #f59e0b) 65%,
+ #000000
+ ) !important;
+ color: color-mix(in srgb, var(--p2-main, #f59e0b) 20%, #ffffff) !important;
border: none;
}
.row-grand-total .col-global {
@@ -665,7 +834,7 @@ th.col-laia {
border: none;
}
-/* --- 9. STYLES SPÉCIFIQUES AU SUIVI MENSUEL --- */
+/* --- 9. STYLES DU SUIVI MENSUEL --- */
.cat-card {
background: var(--bg-panel);
border-radius: var(--radius);
@@ -675,20 +844,17 @@ th.col-laia {
overflow: hidden;
position: relative;
}
-
.cat-card-header {
display: flex;
justify-content: space-between;
align-items: center;
- padding: 15px; /* Padding interne à l'encart coloré */
+ padding: 15px;
width: 100%;
}
-
.cat-card-title-group {
display: flex;
flex-direction: column;
}
-
.cat-card-subtitle {
font-size: 0.85rem;
color: var(--text-muted);
@@ -768,7 +934,52 @@ input[type="month"].pf-input {
border-radius: 6px;
}
-/* --- 10. EPARGNE --- */
+/* --- 10. SECTION NOTES BUDGETAIRES --- */
+.budget-notes-section {
+ margin: 20px 0;
+ background: white;
+ padding: 20px;
+ border-radius: 12px;
+ box-shadow: var(--shadow-sm);
+ border: 1px solid #e2e8f0;
+}
+.notes-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+}
+.notes-header h3 {
+ margin: 0;
+ font-size: 1.1rem;
+ color: #1e293b;
+}
+.notes-header h3 span {
+ text-transform: capitalize;
+}
+.note-save-indicator {
+ font-size: 0.85rem;
+ color: #10b981;
+ font-weight: bold;
+ opacity: 0;
+ transition: opacity 0.3s;
+}
+#monthNoteArea {
+ width: 100%;
+ resize: vertical;
+ border-color: #cbd5e1;
+ background: #f8fafc;
+ margin-bottom: 10px;
+}
+.notes-footer {
+ text-align: right;
+}
+.notes-footer .pf-btn {
+ width: auto;
+ display: inline-flex;
+}
+
+/* --- 11. EPARGNE --- */
.epargne-inline-input {
width: 100%;
min-width: 60px;
@@ -793,7 +1004,7 @@ input[type="month"].pf-input {
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
}
-/* --- 11. CALCULATRICE FLOTTANTE --- */
+/* --- 12. CALCULATRICE FLOTTANTE --- */
.pf-fab-sum {
position: fixed;
right: 30px;
@@ -883,7 +1094,7 @@ body.sum-mode-active .sum-target {
transition: background-color 0.2s;
}
-/* --- 12. RESPONSIVE MOBILE BUDGET --- */
+/* --- 13. RESPONSIVE MOBILE BUDGET --- */
@media (max-width: 768px) {
.budget-view {
padding: 10px !important;
@@ -917,11 +1128,6 @@ body.sum-mode-active .sum-target {
.budget-view .categories-grid > .cat-card {
padding: 0 !important;
}
- .budget-view
- > div[style*="justify-content: space-between"]:not(.view-header) {
- flex-direction: column !important;
- align-items: stretch !important;
- }
.budget-view
> div[style*="justify-content: space-between"]:not(.view-header) {
flex-direction: column !important;
@@ -1021,7 +1227,6 @@ body.sum-mode-active .sum-target {
justify-content: flex-start;
width: auto !important;
}
-
.col-sticky,
.sticky-col {
min-width: 120px !important;
@@ -1059,8 +1264,7 @@ body.sum-mode-active .sum-target {
right: auto;
top: auto;
}
-
.cat-card-header {
- padding: 12px 10px; /* Plus serré sur mobile */
+ padding: 12px 10px;
}
}
diff --git a/modules/budget/views/budget_prev.php b/modules/budget/views/budget_prev.php
index bc6d5e8..0dce674 100644
--- a/modules/budget/views/budget_prev.php
+++ b/modules/budget/views/budget_prev.php
@@ -1,9 +1,28 @@
query("SELECT id, name, user_id, role, color FROM pf_people WHERE role = 'parent' ORDER BY id ASC");
+$budgetParents = $stmtPeople->fetchAll(PDO::FETCH_ASSOC);
+
+$p1_name = $budgetParents[0]['name'] ?? 'Parent 1';
+$p2_name = $budgetParents[1]['name'] ?? 'Parent 2';
$currentYear = date('Y');
-// 1. Récupération Config Salaires
+// Cartographie dynamique
+$parentMapping = [];
+foreach ($budgetParents as $index => $parent) {
+ $num = $index + 1;
+ $parentMapping[] = [
+ 'id' => (int)$parent['id'],
+ 'name' => $parent['name'],
+ 'db_field' => 'amount_p' . $num,
+ 'css' => 'p' . $num,
+ 'color' => $parent['color'] ?? (($num === 1) ? '#0891b2' : '#f59e0b')
+ ];
+}
+
+// 2. Récupération Config Salaires
$salaryConfig = [];
$stmt = $pdo->prepare("SELECT * FROM pf_salary_config WHERE year = ?");
$stmt->execute([$currentYear]);
@@ -11,16 +30,17 @@ while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$salaryConfig[$row['person']] = $row;
}
-foreach (['Alex', 'Laia'] as $p) {
- if (!isset($salaryConfig[$p])) {
- $salaryConfig[$p] = ['salary'=>0, 'mensualite'=>0, 'frais_func'=>0, 'eco_perso'=>0, 'eco_family'=>0];
+// Sécurité profils de salaire vides
+foreach ($parentMapping as $map) {
+ if (!isset($salaryConfig[$map['name']])) {
+ $salaryConfig[$map['name']] = ['salary'=>0, 'mensualite'=>0, 'frais_func'=>0, 'eco_perso'=>0, 'eco_family'=>0];
}
}
-// 2. Récupération Catégories
+// 3. Récupération Catégories
$cats = $pdo->query("SELECT * FROM pf_alloc_categories ORDER BY sort_order ASC, id ASC")->fetchAll(PDO::FETCH_ASSOC);
-// 3. Gestion du mois FOCUS et Navigation
+// 4. Gestion du mois FOCUS et Navigation
$focusDate = isset($_GET['focus_date']) ? $_GET['focus_date'] : date('Y-m-01');
$focusTs = strtotime($focusDate);
@@ -32,7 +52,7 @@ for ($i = 0; $i < 6; $i++) {
$prevMonthLink = date('Y-m-01', strtotime("-1 month", $focusTs));
$nextMonthLink = date('Y-m-01', strtotime("+1 month", $focusTs));
-// NOUVEAU : Récupération des Cycles configurés dans pf_notes
+// Récupération des Cycles configurés dans pf_notes
$cycleConfigs = [];
$stmtNotes = $pdo->query("SELECT reference_id, content FROM pf_notes WHERE note_type = 'month_config'");
while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
@@ -43,7 +63,7 @@ while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
}
}
-// 4. Récupération Valeurs Répartition
+// 5. Récupération Valeurs Répartition
$inQuery = implode(',', array_fill(0, count($months), '?'));
$stmt = $pdo->prepare("SELECT * FROM pf_alloc_values WHERE month_date IN ($inQuery)");
$stmt->execute($months);
@@ -53,7 +73,7 @@ while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$allocs[$row['month_date']][$row['cat_id']] = $row;
}
-// 5. Récupération de l'ID de la catégorie système
+// 6. Récupération de l'ID de la catégorie système
$sysCatId = null;
foreach ($cats as $key => $c) {
if ($c['name'] === 'SYSTEM_VALIDATION') {
@@ -63,25 +83,26 @@ foreach ($cats as $key => $c) {
}
}
-// 6. Lecture des statuts de validation
+// Statuts de validation
$focusDate = $months[0];
-$isValidatedAlex = false;
-$isValidatedLaia = false;
-
+$isValidated = [];
if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
$row = $allocs[$focusDate][$sysCatId];
- $isValidatedAlex = ($row['amount_alex'] == 1);
- $isValidatedLaia = ($row['amount_laia'] == 1);
+ foreach ($parentMapping as $map) {
+ $isValidated[$map['css']] = ($row[$map['db_field']] == 1);
+ }
+} else {
+ foreach ($parentMapping as $map) {
+ $isValidated[$map['css']] = false;
+ }
}
$stmtNote = $pdo->prepare("SELECT content FROM pf_notes WHERE note_type = 'budget_prev' AND reference_id = ?");
$stmtNote->execute([$focusDate]);
$currentNote = $stmtNote->fetchColumn();
-// 7. Récupération des vacances actives
$activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN ('draft', 'planned', 'booked') ORDER BY start_date ASC")->fetchAll(PDO::FETCH_ASSOC);
-// Helper function pour les mois
function getTranslatedMonthName($dateString) {
$m = date('m', strtotime($dateString));
$y = date('Y', strtotime($dateString));
@@ -89,7 +110,7 @@ function getTranslatedMonthName($dateString) {
}
?>
-