From f8e2ad67ee6837e67ff6a3a120b26423399e22c1 Mon Sep 17 00:00:00 2001 From: "fefe.clochette" Date: Wed, 20 May 2026 17:56:25 +0200 Subject: [PATCH] refacto parent Calendrier budget et couleur profil. --- README.md | 32 +- docker/schema_family.sql | 14 +- family-calendar.php | 34 +- global.css | 329 +++++++-- header.php | 8 +- includes/auth.php | 5 + includes/config.php | 7 - includes/db.php | 4 - migrate.php | 145 +++- modules/budget/budget.css | 342 ++++++++-- modules/budget/views/budget_prev.php | 627 ++++++++---------- modules/family-calendar/family-calendar.js | 205 +++--- .../includes/api/events-debug.log | 1 + schema.sql | 14 +- settings.php | 61 +- 15 files changed, 1153 insertions(+), 675 deletions(-) delete mode 100644 includes/config.php 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'; - ALEX - LAIA + + CPJRAJA CPJRAJA - - - - - - + $parent): + $parentClass = ($index % 2 === 0) ? 'col-alex' : 'col-laia'; + ?> + + + + + + + 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: , + // ID du user global connecté (pour savoir "qui" clique) + CURRENT_USER_ID: , 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)

"; } -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) { } ?> -
+
@@ -101,35 +122,31 @@ function getTranslatedMonthName($dateString) { -
() +
() -
() +
() 'Alex', ID_LAIA => 'Laia']; - - foreach ($family_members as $id => $p): - $d = $salaryConfig[$p]; + foreach ($parentMapping as $map): + $d = $salaryConfig[$map['name']]; $restant = $d['salary'] - ($d['mensualite'] + $d['frais_func'] + $d['eco_perso'] + $d['eco_family']); - $borderColor = ($id === ID_ALEX) ? '#0891b2' : '#f59e0b'; ?> - - - + + + - - - - - - + + + + + + - + € @@ -139,24 +156,24 @@ function getTranslatedMonthName($dateString) {
-
-
-

- -
- - -
-
@@ -166,13 +183,14 @@ function getTranslatedMonthName($dateString) { - @@ -182,87 +200,75 @@ function getTranslatedMonthName($dateString) { - - + + + - + - - - + + + + - + - - - + + + + - - + 0, 'amount_laia'=>0]; + $val = $allocs[$m][$cat['id']] ?? []; ?> - + + - - + @@ -271,120 +277,62 @@ function getTranslatedMonthName($dateString) { -
-
-

- 📝 -

- - ✓ - +
+
+

📝

+
- + -
- +
0, 'Laia' => 0]; - - foreach ($cats as $cat) { - $t = trim($cat['target']); - if (empty($t)) continue; - - $val = $allocs[$focusMonth][$cat['id']] ?? ['amount_alex'=>0, 'amount_laia'=>0]; - - if (!isset($summaryData[$t])) { - $summaryData[$t] = ['Alex' => 0, 'Laia' => 0]; - } - - $summaryData[$t]['Alex'] += $val['amount_alex']; - $summaryData[$t]['Laia'] += $val['amount_laia']; - } - - $grandTotalAlex = 0; - $grandTotalLaia = 0; - $grandTotalGlobal = 0; ?>
- - + -
- + + " . sprintf(tr('bud_sav_from_date'), $cStart) . ""; + echo "
" . sprintf(tr('bud_sav_from_date'), $cStart) . "
"; } ?>
GlobalAlexLaia
TotalTotal 00000
(Eco Family) (Eco Family) -00-0
-
- + $isIndicative = (strpos($cat['name'], 'Eco P') === 0); + $rowClass = $isIndicative ? 'row-indicative' : ''; + $inputClass = $isIndicative ? 'ignore-calc' : ''; + + $catDisplayName = $cat['name']; + if ($catDisplayName === 'Eco P1') $catDisplayName = 'Eco ' . $p1_name; + if ($catDisplayName === 'Eco P2') $catDisplayName = 'Eco ' . $p2_name; + ?> +
+
+ - Info + Info
-
+
- - - + +
00 - - - + onchange="updateAlloc('', , '', this)">
- - - + + - - - + - + - - + + + @@ -392,8 +340,9 @@ function getTranslatedMonthName($dateString) { - - + + + @@ -403,18 +352,18 @@ function getTranslatedMonthName($dateString) {
-
-
-

- +
+
+

+
-
+
-
+
-
- - @@ -442,20 +391,19 @@ function getTranslatedMonthName($dateString) {
-
-
-

- +
+
+

+
- -
+
-
+
-
- - @@ -481,20 +429,17 @@ function getTranslatedMonthName($dateString) {
- -
0 € - +
- - + \ No newline at end of file
-
- ALEX - -
- ✓ -
+
+
+ + +
- +
-
- LAIA - -
- ✓ -
- - - -
-
Global
0 €0 €0 € 0 €
0 €0 €0 € 0 €