Merge pull request 'Merge branch 'main' of http://nas.percolouco.com:3500/perco/HouseHub into parent_id' (#4) from parent_id into main
Deploy HouseHub / deploy (push) Successful in 2s
Deploy HouseHub / deploy (push) Successful in 2s
Reviewed-on: #4
This commit was merged in pull request #4.
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
househub_db_data
|
househub_db_data
|
||||||
.env
|
.env
|
||||||
data/.hh_app_secret
|
data/.hh_app_secret
|
||||||
|
extract_source.php
|
||||||
@@ -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.
|
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)
|
🟢 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] 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.
|
[ ] 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.
|
[ ] 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)
|
🟡 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.
|
[ ] 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).
|
[ ] 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.
|
[ ] 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.
|
[ ] Types de carburants (Garage) : Variabiliser la liste statique des carburants dans garage.php.
|
||||||
|
|
||||||
🔴 Niveau 3 : Complexe (Logique métier et Schéma SQL)
|
🔴 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.
|
[ ] 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.
|
[ ] 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.
|
[ ] 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.
|
||||||
@@ -175,7 +180,8 @@ Afin de transformer complètement l'application héritée de Pacha Family en un
|
|||||||
## 📌 Versioning
|
## 📌 Versioning
|
||||||
|
|
||||||
| Version | Changements |
|
| 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.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.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 |
|
| v2.0.0 | Module PrintVault : viewer 3D STL/3MF/GCode, stockage local, trajectoires GCode illimitées |
|
||||||
|
|||||||
@@ -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');
|
INSERT IGNORE INTO pf_foyer_settings (id, currency, zone_scolaire) VALUES (1, '€', 'C');
|
||||||
|
|
||||||
-- ─── Personnes ────────────────────────────────────────────────────────────────
|
-- ─── Personnes ────────────────────────────────────────────────────────────────
|
||||||
CREATE TABLE IF NOT EXISTS pf_people (
|
CREATE TABLE `pf_people` (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
name VARCHAR(100) NOT NULL
|
`name` varchar(50) NOT NULL,
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
`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 ──────────────────────────────────────────────────────
|
-- ─── Calendrier familial ──────────────────────────────────────────────────────
|
||||||
CREATE TABLE IF NOT EXISTS pf_events (
|
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)
|
INSERT IGNORE INTO pf_users (id, username, password_hash, display_name)
|
||||||
VALUES (1, 'admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Admin');
|
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 ───────────────────────────────────────────────────────────
|
-- ─── Garage Manager ───────────────────────────────────────────────────────────
|
||||||
CREATE TABLE IF NOT EXISTS pf_vehicles (
|
CREATE TABLE IF NOT EXISTS pf_vehicles (
|
||||||
|
|||||||
+24
-10
@@ -8,6 +8,12 @@ require_login();
|
|||||||
require __DIR__ . '/includes/db.php';
|
require __DIR__ . '/includes/db.php';
|
||||||
require_once __DIR__ . '/includes/i18n.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');
|
$pageTitle = tr('fc_page_title');
|
||||||
$activePage = "family-calendar";
|
$activePage = "family-calendar";
|
||||||
$mainClass = "pf-family-calendar";
|
$mainClass = "pf-family-calendar";
|
||||||
@@ -70,8 +76,12 @@ require __DIR__ . '/header.php';
|
|||||||
<div class="pf-form-group">
|
<div class="pf-form-group">
|
||||||
<label class="pf-label"><?= tr('fc_label_person') ?></label>
|
<label class="pf-label"><?= tr('fc_label_person') ?></label>
|
||||||
<select id="snapPerson" class="pf-input" required>
|
<select id="snapPerson" class="pf-input" required>
|
||||||
<option value="2">Alex</option>
|
<option value="" disabled selected>-- <?= tr('fc_choose_person') ?> --</option>
|
||||||
<option value="3">Laia</option>
|
<?php foreach ($familyPeople as $person): ?>
|
||||||
|
<option value="<?= htmlspecialchars($person['id']) ?>">
|
||||||
|
<?= htmlspecialchars($person['name']) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="pf-form-group">
|
<div class="pf-form-group">
|
||||||
@@ -169,20 +179,24 @@ require __DIR__ . '/header.php';
|
|||||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_avis') ?></span></th>
|
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_avis') ?></span></th>
|
||||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_pep_sick') ?></span></th>
|
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_pep_sick') ?></span></th>
|
||||||
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_presence') ?></span></th>
|
<th rowspan="3" class="col-total rotated-text"><span><?= tr('leg_presence') ?></span></th>
|
||||||
<th colspan="6" class="col-alex header-group">ALEX</th>
|
<th colspan="6" class="col-alex header-group"><?= htmlspecialchars(strtoupper($parents[0]['name'] ?? 'PARENT 1')) ?></th>
|
||||||
<th colspan="6" class="col-laia header-group">LAIA</th>
|
<th colspan="6" class="col-laia header-group"><?= htmlspecialchars(strtoupper($parents[1]['name'] ?? 'PARENT 2')) ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="2" class="col-alex-sub">CP</th><th colspan="2" class="col-alex-sub">JRA</th><th colspan="2" class="col-alex-sub">JA</th>
|
<th colspan="2" class="col-alex-sub">CP</th><th colspan="2" class="col-alex-sub">JRA</th><th colspan="2" class="col-alex-sub">JA</th>
|
||||||
<th colspan="2" class="col-laia-sub">CP</th><th colspan="2" class="col-laia-sub">JRA</th><th colspan="2" class="col-laia-sub">JA</th>
|
<th colspan="2" class="col-laia-sub">CP</th><th colspan="2" class="col-laia-sub">JRA</th><th colspan="2" class="col-laia-sub">JA</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-alex-sub col-alex-av"><?= tr('fc_col_av') ?></th><th class="col-alex-sub col-alex-use"><?= tr('fc_col_use') ?></th>
|
<?php foreach ($parents as $index => $parent):
|
||||||
<th class="col-alex-sub col-alex-av"><?= tr('fc_col_av') ?></th><th class="col-alex-sub col-alex-use"><?= tr('fc_col_use') ?></th>
|
$parentClass = ($index % 2 === 0) ? 'col-alex' : 'col-laia';
|
||||||
<th class="col-alex-sub col-alex-av"><?= tr('fc_col_av') ?></th><th class="col-alex-sub col-alex-use"><?= tr('fc_col_use') ?></th>
|
?>
|
||||||
<th class="col-laia-sub col-laia-av"><?= tr('fc_col_av') ?></th><th class="col-laia-sub col-laia-use"><?= tr('fc_col_use') ?></th>
|
<th class="<?= $parentClass ?>-sub <?= $parentClass ?>-av"><?= tr('fc_col_av') ?></th>
|
||||||
<th class="col-laia-sub col-laia-av"><?= tr('fc_col_av') ?></th><th class="col-laia-sub col-laia-use"><?= tr('fc_col_use') ?></th>
|
<th class="<?= $parentClass ?>-sub <?= $parentClass ?>-use"><?= tr('fc_col_use') ?></th>
|
||||||
<th class="col-laia-sub col-laia-av"><?= tr('fc_col_av') ?></th><th class="col-laia-sub col-laia-use"><?= tr('fc_col_use') ?></th>
|
<th class="<?= $parentClass ?>-sub <?= $parentClass ?>-av"><?= tr('fc_col_av') ?></th>
|
||||||
|
<th class="<?= $parentClass ?>-sub <?= $parentClass ?>-use"><?= tr('fc_col_use') ?></th>
|
||||||
|
<th class="<?= $parentClass ?>-sub <?= $parentClass ?>-av"><?= tr('fc_col_av') ?></th>
|
||||||
|
<th class="<?= $parentClass ?>-sub <?= $parentClass ?>-use"><?= tr('fc_col_use') ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="planningBody"></tbody>
|
<tbody id="planningBody"></tbody>
|
||||||
|
|||||||
+257
-64
@@ -307,7 +307,6 @@ body.pf-home .pf-mobile-close-btn {
|
|||||||
.pf-muted-note {
|
.pf-muted-note {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
.pf-muted-note.pf-muted-note--tight {
|
.pf-muted-note.pf-muted-note--tight {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
@@ -359,6 +358,58 @@ body.pf-home .pf-mobile-close-btn {
|
|||||||
gap: 8px;
|
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 */
|
/* Tuile module (checkbox) — état coché via :has(), compatible thème */
|
||||||
.pf-module-tile {
|
.pf-module-tile {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -973,16 +1024,18 @@ p {
|
|||||||
--bg-page: #0d1117;
|
--bg-page: #0d1117;
|
||||||
--bg-panel: #161b22;
|
--bg-panel: #161b22;
|
||||||
--border-light: #30363d;
|
--border-light: #30363d;
|
||||||
--shadow: 0 4px 6px -1px rgba(0,0,0,.4), 0 2px 4px -2px rgba(0,0,0,.3);
|
--shadow:
|
||||||
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,.5), 0 4px 6px -4px rgba(0,0,0,.4);
|
0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -2px rgba(0, 0, 0, 0.3);
|
||||||
--bg-subtle: rgba(255,255,255,.06);
|
--shadow-lg:
|
||||||
--bg-soft: rgba(59,130,246,.15);
|
0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -4px rgba(0, 0, 0, 0.4);
|
||||||
--bg-danger-soft: rgba(239,68,68,.1);
|
--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);
|
--bg-overlay: rgba(0, 0, 0, 0.55);
|
||||||
--text-danger-soft: #f87171;
|
--text-danger-soft: #f87171;
|
||||||
--border-strong: #484f58;
|
--border-strong: #484f58;
|
||||||
--border-danger-soft: rgba(239,68,68,.3);
|
--border-danger-soft: rgba(239, 68, 68, 0.3);
|
||||||
--focus-ring: rgba(59,130,246,.25);
|
--focus-ring: rgba(59, 130, 246, 0.25);
|
||||||
--alert-success-bg: rgba(34, 197, 94, 0.12);
|
--alert-success-bg: rgba(34, 197, 94, 0.12);
|
||||||
--alert-success-border: rgba(34, 197, 94, 0.45);
|
--alert-success-border: rgba(34, 197, 94, 0.45);
|
||||||
--alert-success-text: #86efac;
|
--alert-success-text: #86efac;
|
||||||
@@ -993,67 +1046,207 @@ p {
|
|||||||
--badge-admin-bg: rgba(59, 130, 246, 0.2);
|
--badge-admin-bg: rgba(59, 130, 246, 0.2);
|
||||||
--badge-admin-text: #93c5fd;
|
--badge-admin-text: #93c5fd;
|
||||||
}
|
}
|
||||||
[data-theme="dark"] body { background: var(--bg-page); color: var(--text-main); }
|
[data-theme="dark"] body {
|
||||||
[data-theme="dark"] .pf-header { background: var(--bg-panel); border-color: var(--border-light); }
|
background: var(--bg-page);
|
||||||
[data-theme="dark"] .pf-logo { color: var(--primary); }
|
color: var(--text-main);
|
||||||
[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-header {
|
||||||
[data-theme="dark"] .pf-nav-link--active { background: rgba(59,130,246,.15); color: var(--primary); }
|
background: var(--bg-panel);
|
||||||
[data-theme="dark"] .pf-user-badge { color: var(--text-muted); }
|
border-color: var(--border-light);
|
||||||
[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-logo {
|
||||||
[data-theme="dark"] .pf-burger-btn { color: var(--text-main); }
|
color: var(--primary);
|
||||||
[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-nav-link {
|
||||||
[data-theme="dark"] .pf-mobile-nav-link { color: var(--text-main); border-color: var(--border-light); }
|
color: var(--text-muted);
|
||||||
[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-nav-link:hover {
|
||||||
[data-theme="dark"] .pf-module-card:hover { border-color: #484f58; box-shadow: var(--shadow); }
|
background: rgba(255, 255, 255, 0.06);
|
||||||
[data-theme="dark"] .pf-card-title { color: var(--text-main); }
|
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"] .pf-nav-link--active {
|
||||||
[data-theme="dark"] p { color: var(--text-muted); }
|
background: rgba(59, 130, 246, 0.15);
|
||||||
[data-theme="dark"] .pf-btn.btn-secondary { background: var(--bg-panel); color: var(--text-muted); border-color: var(--border-light); }
|
color: var(--primary);
|
||||||
[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-user-badge {
|
||||||
[data-theme="dark"] .pf-input::placeholder { color: #484f58; }
|
color: var(--text-muted);
|
||||||
[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-logout-btn {
|
||||||
[data-theme="dark"] .pf-table th { background: #1c2128; color: var(--text-muted); border-color: var(--border-light); }
|
color: #f87171;
|
||||||
[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-logout-btn:hover {
|
||||||
[data-theme="dark"] .pf-modal-content { background: var(--bg-panel); border: 1px solid var(--border-light); }
|
background: rgba(239, 68, 68, 0.1);
|
||||||
[data-theme="dark"] .pf-modal-title { border-color: var(--border-light); color: var(--text-main); }
|
border-color: rgba(239, 68, 68, 0.3);
|
||||||
[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-burger-btn {
|
||||||
[data-theme="dark"] .pf-toast { background: var(--bg-panel); color: var(--text-main); box-shadow: 0 10px 25px rgba(0,0,0,.4); }
|
color: var(--text-main);
|
||||||
[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"] .pf-mobile-menu {
|
||||||
[data-theme="dark"] .btn-icon-action.edit:hover { background: rgba(59,130,246,.15); }
|
background: var(--bg-panel);
|
||||||
[data-theme="dark"] .btn-icon-action.delete:hover { background: rgba(239,68,68,.1); }
|
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 ────────────────────────────────────── */
|
/* ─── Pill toggle thème ────────────────────────────────────── */
|
||||||
.theme-switch {
|
.theme-switch {
|
||||||
display: inline-flex; align-items: center; gap: 5px;
|
display: inline-flex;
|
||||||
background: none; border: none; cursor: pointer;
|
align-items: center;
|
||||||
padding: 3px 5px; border-radius: 8px; transition: background .15s;
|
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 {
|
.ts-track {
|
||||||
position: relative; width: 36px; height: 20px;
|
position: relative;
|
||||||
background: #cbd5e1; border-radius: 999px;
|
width: 36px;
|
||||||
flex-shrink: 0; transition: background .25s;
|
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 {
|
.ts-thumb {
|
||||||
position: absolute; top: 3px; left: 3px;
|
position: absolute;
|
||||||
width: 14px; height: 14px; border-radius: 50%;
|
top: 3px;
|
||||||
background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3);
|
left: 3px;
|
||||||
transition: transform .25s cubic-bezier(.4,0,.2,1);
|
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); }
|
|
||||||
|
|||||||
+5
-3
@@ -130,10 +130,12 @@ $currentLang = $_SESSION['app_lang'] ?? 'fr';
|
|||||||
*/
|
*/
|
||||||
window.I18N = <?php echo json_encode($current_translations_array ?? [], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE); ?>;
|
window.I18N = <?php echo json_encode($current_translations_array ?? [], JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_UNICODE); ?>;
|
||||||
|
|
||||||
// ✅ Injection sécurisée (évite le crash si config.php n'est pas chargé)
|
// ✅ Injection sécurisée
|
||||||
window.CONFIG = {
|
window.CONFIG = {
|
||||||
ID_ALEX: <?php echo defined('ID_ALEX') ? ID_ALEX : 2; ?>,
|
// Liste complète des profils de la famille (Membres, Nounou, Enfants)
|
||||||
ID_LAIA: <?php echo defined('ID_LAIA') ? ID_LAIA : 3; ?>,
|
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: '<?php echo defined('CURRENCY') ? CURRENCY : "€"; ?>',
|
CURRENCY: '<?php echo defined('CURRENCY') ? CURRENCY : "€"; ?>',
|
||||||
ZONE_SCOLAIRE: '<?php echo defined('ZONE_SCOLAIRE') ? ZONE_SCOLAIRE : "C"; ?>'
|
ZONE_SCOLAIRE: '<?php echo defined('ZONE_SCOLAIRE') ? ZONE_SCOLAIRE : "C"; ?>'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,3 +54,8 @@ function verify_csrf(?string $token): bool
|
|||||||
}
|
}
|
||||||
return hash_equals($_SESSION['csrf_token'], $token);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<?php
|
|
||||||
// includes/config.php
|
|
||||||
// HouseHub Config - Version dynamique
|
|
||||||
if (!defined('ID_ALEX')) {
|
|
||||||
define('ID_ALEX', 2);
|
|
||||||
define('ID_LAIA', 3);
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
if (file_exists(__DIR__ . '/config.php')) {
|
|
||||||
require_once __DIR__ . '/config.php';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session_status() === PHP_SESSION_NONE) {
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
session_start();
|
session_start();
|
||||||
}
|
}
|
||||||
|
|||||||
+141
-20
@@ -1,46 +1,167 @@
|
|||||||
<?php
|
<?php
|
||||||
// Script de migration pour mettre à jour TOUTES les bases de données familiales
|
// Script de migration : Refonte globale Multi-Tenant (pf_people & Budget)
|
||||||
require_once __DIR__ . '/includes/meta_db.php';
|
require_once __DIR__ . '/includes/meta_db.php';
|
||||||
|
|
||||||
echo "<h1>🚀 Début de la migration...</h1>";
|
echo "<h1>🚀 Début de la migration Globale ...</h1>";
|
||||||
|
|
||||||
// 1. Récupérer toutes les bases de données des familles
|
$stmt = $meta_pdo->query("SELECT id, name, db_name FROM families WHERE db_name != ''");
|
||||||
$stmt = $meta_pdo->query("SELECT name, db_name FROM families WHERE db_name != ''");
|
|
||||||
$families = $stmt->fetchAll();
|
$families = $stmt->fetchAll();
|
||||||
|
|
||||||
$host = getenv('DB_HOST') ?: 'househub-db';
|
$host = getenv('DB_HOST') ?: 'househub-db';
|
||||||
$user = getenv('DB_USER') ?: 'househub';
|
$user = getenv('DB_USER') ?: 'househub';
|
||||||
$pass = getenv('DB_PASS') ?: 'changeme';
|
$pass = getenv('DB_PASS') ?: 'changeme';
|
||||||
|
|
||||||
// 2. Boucler sur chaque famille
|
|
||||||
foreach ($families as $f) {
|
foreach ($families as $f) {
|
||||||
$db_name = $f['db_name'];
|
$db_name = $f['db_name'];
|
||||||
echo "Mise à jour de la famille <strong>{$f['name']}</strong> (Base : $db_name) ... ";
|
$family_id = $f['id'];
|
||||||
|
echo "<h3>Mise à jour de <strong>{$f['name']}</strong> ($db_name)</h3><ul>";
|
||||||
|
|
||||||
|
// 🟢 SÉCURITÉ : On vide les variables à chaque famille pour éviter les fuites (le bug f2 vers f1)
|
||||||
|
$p1_name = null;
|
||||||
|
$p2_name = null;
|
||||||
|
$parents = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$fam_pdo = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8mb4", $user, $pass, [
|
$fam_pdo = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8mb4", $user, $pass, [
|
||||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 3. Injecter la nouvelle table
|
// ==========================================
|
||||||
$sql = "
|
// 1. GESTION DE PF_PEOPLE (user_id, role, color)
|
||||||
CREATE TABLE IF NOT EXISTS pf_foyer_settings (
|
// ==========================================
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
echo "<li><strong>Table pf_people :</strong> ";
|
||||||
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;
|
|
||||||
|
|
||||||
INSERT IGNORE INTO pf_foyer_settings (id, currency, zone_scolaire) VALUES (1, '€', 'C');
|
// user_id
|
||||||
";
|
try {
|
||||||
|
$fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN user_id INT NULL DEFAULT NULL");
|
||||||
|
echo "<span style='color:green'>user_id OK</span> - ";
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
if ($e->getCode() == '42S21' || strpos($e->getMessage(), '1060') !== false) {
|
||||||
|
echo "<span style='color:gray'>user_id déjà présent</span> - ";
|
||||||
|
} else { throw $e; }
|
||||||
|
}
|
||||||
|
|
||||||
$fam_pdo->exec($sql);
|
// 🟢 CORRECTION DE L'AUTO-MAPPING : Si pf_people est vide, on la remplit !
|
||||||
echo "<span style='color:green'>✅ OK</span><br>";
|
$stmtUsers = $meta_pdo->prepare("SELECT id, username, display_name FROM users WHERE family_id = ? ORDER BY id ASC");
|
||||||
|
$stmtUsers->execute([$family_id]);
|
||||||
|
$users = $stmtUsers->fetchAll();
|
||||||
|
|
||||||
|
$mappedCount = 0;
|
||||||
|
foreach ($users as $u) {
|
||||||
|
// On vérifie si l'utilisateur existe déjà dans pf_people (via user_id ou son nom)
|
||||||
|
$stmtCheck = $fam_pdo->prepare("SELECT id FROM pf_people WHERE user_id = ? OR LOWER(name) = LOWER(?)");
|
||||||
|
$stmtCheck->execute([$u['id'], $u['username']]);
|
||||||
|
$exists = $stmtCheck->fetchColumn();
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
// S'il existe, on s'assure que son user_id est bien renseigné
|
||||||
|
$stmtUpdate = $fam_pdo->prepare("UPDATE pf_people SET user_id = ? WHERE id = ?");
|
||||||
|
$stmtUpdate->execute([$u['id'], $exists]);
|
||||||
|
$mappedCount += $stmtUpdate->rowCount();
|
||||||
|
} else {
|
||||||
|
// S'il n'existe pas du tout (base f1 vide), on le crée !
|
||||||
|
$stmtInsert = $fam_pdo->prepare("INSERT INTO pf_people (name, user_id) VALUES (?, ?)");
|
||||||
|
$stmtInsert->execute([$u['display_name'] ?: $u['username'], $u['id']]);
|
||||||
|
$mappedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo "<span style='color:blue'>$mappedCount profils liés ou créés</span> - ";
|
||||||
|
|
||||||
|
// role
|
||||||
|
try {
|
||||||
|
$fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN role VARCHAR(50) DEFAULT NULL");
|
||||||
|
echo "<span style='color:green'>role OK</span> - ";
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
if ($e->getCode() == '42S21' || strpos($e->getMessage(), '1060') !== false) {
|
||||||
|
echo "<span style='color:gray'>role déjà présent</span> - ";
|
||||||
|
} else { throw $e; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// color
|
||||||
|
try {
|
||||||
|
$fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN color VARCHAR(7) DEFAULT '#0891b2'");
|
||||||
|
echo "<span style='color:green'>colonne 'color' ajoutée</span> - ";
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
if ($e->getCode() == '42S21' || strpos($e->getMessage(), '1060') !== false) {
|
||||||
|
echo "<span style='color:gray'>colonne 'color' déjà présente</span> - ";
|
||||||
|
} 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 "<span style='color:blue'>Rôles et Couleurs initialisés.</span></li>";
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 2. GESTION DU BUDGET (pf_alloc_values)
|
||||||
|
// ==========================================
|
||||||
|
echo "<li><strong>Table pf_alloc_values :</strong> ";
|
||||||
|
|
||||||
|
$checkAlex = $fam_pdo->query("SHOW COLUMNS FROM pf_alloc_values LIKE 'amount_alex'")->rowCount();
|
||||||
|
if ($checkAlex > 0) {
|
||||||
|
$fam_pdo->exec("ALTER TABLE pf_alloc_values CHANGE amount_alex amount_p1 FLOAT DEFAULT 0");
|
||||||
|
echo "<span style='color:green'>amount_alex -> amount_p1</span> - ";
|
||||||
|
} else {
|
||||||
|
echo "<span style='color:gray'>amount_p1 OK</span> - ";
|
||||||
|
}
|
||||||
|
|
||||||
|
$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 "<span style='color:green'>amount_laia -> amount_p2</span></li>";
|
||||||
|
} else {
|
||||||
|
echo "<span style='color:gray'>amount_p2 OK</span></li>";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 3. GESTION DU BUDGET (pf_alloc_categories)
|
||||||
|
// ==========================================
|
||||||
|
echo "<li><strong>Table pf_alloc_categories :</strong> ";
|
||||||
|
$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 "<span style='color:green'>Catégories 'Eco' génériques mises à jour (" . ($stmtCats1 + $stmtCats2) . " lignes).</span></li>";
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 4. GESTION DU BUDGET (pf_salary_config)
|
||||||
|
// ==========================================
|
||||||
|
echo "<li><strong>Table pf_salary_config :</strong> ";
|
||||||
|
// On récupère les vrais prénoms fraîchement créés/mappés
|
||||||
|
$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 "<span style='color:green'>Salaires mappés vers $p1_name et $p2_name.</span></li>";
|
||||||
|
} else {
|
||||||
|
echo "<span style='color:orange'>Pas assez de parents trouvés pour mapper les salaires.</span></li>";
|
||||||
|
}
|
||||||
|
|
||||||
} catch (\PDOException $e) {
|
} catch (\PDOException $e) {
|
||||||
echo "<span style='color:red'>❌ Erreur : " . $e->getMessage() . "</span><br>";
|
echo "<li style='color:red'>❌ Erreur : " . $e->getMessage() . "</li>";
|
||||||
}
|
}
|
||||||
|
echo "</ul>";
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "<h2>🎉 Migration terminée ! Tu peux supprimer ce fichier.</h2>";
|
echo "<h2>🎉 Migration terminée avec succès !</h2>";
|
||||||
?>
|
?>
|
||||||
+273
-69
@@ -1,7 +1,6 @@
|
|||||||
/* modules/budget/budget.css */
|
/* modules/budget/budget.css */
|
||||||
|
|
||||||
/* --- 1. VARIABLES & BASE --- */
|
/* --- 1. VARIABLES & BASE --- */
|
||||||
/* Le root garde les couleurs sémantiques dont le JS/PHP a besoin localement */
|
|
||||||
.budget-view {
|
.budget-view {
|
||||||
font-family:
|
font-family:
|
||||||
"Segoe UI",
|
"Segoe UI",
|
||||||
@@ -128,7 +127,7 @@
|
|||||||
|
|
||||||
/* --- 3. BOUTONS SPÉCIFIQUES & ACTIONS --- */
|
/* --- 3. BOUTONS SPÉCIFIQUES & ACTIONS --- */
|
||||||
.pf-btn {
|
.pf-btn {
|
||||||
border-radius: 50px; /* Surcharge le global pour le budget */
|
border-radius: 50px;
|
||||||
}
|
}
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -263,7 +262,6 @@ td:hover .btn-cell-delete {
|
|||||||
border-top: 1px solid #e2e8f0;
|
border-top: 1px solid #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Inputs modifiés */
|
|
||||||
.form-row {
|
.form-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
@@ -346,20 +344,94 @@ input[type="number"] {
|
|||||||
gap: 30px;
|
gap: 30px;
|
||||||
}
|
}
|
||||||
.prev-section-header {
|
.prev-section-header {
|
||||||
display: flex;
|
display: flex !important;
|
||||||
justify-content: space-between;
|
flex-direction: row !important;
|
||||||
align-items: center;
|
justify-content: space-between !important;
|
||||||
margin-bottom: 15px;
|
align-items: center !important;
|
||||||
padding-bottom: 10px;
|
margin-bottom: 20px;
|
||||||
border-bottom: 1px solid #e2e8f0;
|
padding: 15px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
width: 100% !important;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
.prev-section-header h2 {
|
.prev-section-header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.5rem;
|
font-size: 1.3rem;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #0f172a;
|
color: #0f172a;
|
||||||
letter-spacing: -0.025em;
|
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 {
|
.prev-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -385,19 +457,6 @@ input[type="number"] {
|
|||||||
font-weight: 700;
|
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 {
|
.prev-salary-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
@@ -419,6 +478,15 @@ input[type="number"] {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 1px solid #e2e8f0;
|
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 {
|
.prev-salary-table th:first-child {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
@@ -426,6 +494,22 @@ input[type="number"] {
|
|||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-bottom: 1px solid var(--border-light);
|
border-bottom: 1px solid var(--border-light);
|
||||||
text-align: center;
|
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 {
|
.prev-timeline-wrapper {
|
||||||
@@ -455,7 +539,7 @@ input[type="number"] {
|
|||||||
border-right: 1px solid var(--border-light);
|
border-right: 1px solid var(--border-light);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 0.9rem;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-sticky {
|
.col-sticky {
|
||||||
@@ -490,6 +574,15 @@ input[type="number"] {
|
|||||||
border-left: 2px solid white;
|
border-left: 2px solid white;
|
||||||
padding: 8px !important;
|
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 {
|
.th-month.current {
|
||||||
background: var(--primary) !important;
|
background: var(--primary) !important;
|
||||||
}
|
}
|
||||||
@@ -511,16 +604,42 @@ input[type="number"] {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
border-bottom: 2px solid #bfdbfe;
|
border-bottom: 2px solid #bfdbfe;
|
||||||
}
|
}
|
||||||
.txt-alex {
|
.row-restant td.td-separator {
|
||||||
color: #0891b2;
|
border-left: 2px solid #cbd5e1;
|
||||||
|
background: #e2e8f0;
|
||||||
}
|
}
|
||||||
.txt-laia {
|
.row-indicative {
|
||||||
color: #f59e0b;
|
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 {
|
.txt-global {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
|
border-left: 2px solid #e2e8f0;
|
||||||
}
|
}
|
||||||
.val-ok {
|
.val-ok {
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
@@ -594,6 +713,9 @@ tr:hover .row-actions {
|
|||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.recap-header span {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
.recap-table {
|
.recap-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
@@ -603,6 +725,7 @@ tr:hover .row-actions {
|
|||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
border-bottom: 1px solid var(--border-light);
|
border-bottom: 1px solid var(--border-light);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
}
|
}
|
||||||
.recap-table th {
|
.recap-table th {
|
||||||
@@ -618,30 +741,68 @@ tr:hover .row-actions {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1e293b;
|
color: #1e293b;
|
||||||
}
|
}
|
||||||
.col-alex {
|
|
||||||
background-color: #ecfeff;
|
.recap-th-content {
|
||||||
color: #0891b2;
|
display: flex;
|
||||||
border-left: 1px solid #cffafe;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
}
|
}
|
||||||
th.col-alex {
|
.recap-th-content span {
|
||||||
background-color: #cffafe;
|
text-transform: uppercase;
|
||||||
border-bottom-color: #a5f3fc;
|
|
||||||
}
|
}
|
||||||
.col-laia {
|
.validation-badge {
|
||||||
background-color: #fffbeb;
|
background: #10b981;
|
||||||
color: #d97706;
|
color: white;
|
||||||
border-left: 1px solid #fef3c7;
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
th.col-laia {
|
|
||||||
background-color: #fef3c7;
|
/* --- Style de base du bouton de validation (Générique) --- */
|
||||||
border-bottom-color: #fde68a;
|
.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;
|
/* Couleurs dynamiques adaptées au Parent 1 (ex: Féfé) */
|
||||||
color: #475569;
|
.col-p1 .pf-btn.btn-small {
|
||||||
font-weight: 700;
|
color: var(--p1-main, #0891b2) !important;
|
||||||
border-left: 2px solid #e2e8f0;
|
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 {
|
.row-grand-total td {
|
||||||
background-color: #1e293b;
|
background-color: #1e293b;
|
||||||
color: white;
|
color: white;
|
||||||
@@ -649,14 +810,22 @@ th.col-laia {
|
|||||||
border: none;
|
border: none;
|
||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
}
|
}
|
||||||
.row-grand-total .col-alex {
|
.row-grand-total .col-p1 {
|
||||||
background: #164e63;
|
background: color-mix(
|
||||||
color: #67e8f9;
|
in srgb,
|
||||||
|
var(--p1-main, #0891b2) 65%,
|
||||||
|
#000000
|
||||||
|
) !important;
|
||||||
|
color: color-mix(in srgb, var(--p1-main, #0891b2) 20%, #ffffff) !important;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.row-grand-total .col-laia {
|
.row-grand-total .col-p2 {
|
||||||
background: #78350f;
|
background: color-mix(
|
||||||
color: #fcd34d;
|
in srgb,
|
||||||
|
var(--p2-main, #f59e0b) 65%,
|
||||||
|
#000000
|
||||||
|
) !important;
|
||||||
|
color: color-mix(in srgb, var(--p2-main, #f59e0b) 20%, #ffffff) !important;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
.row-grand-total .col-global {
|
.row-grand-total .col-global {
|
||||||
@@ -665,7 +834,7 @@ th.col-laia {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- 9. STYLES SPÉCIFIQUES AU SUIVI MENSUEL --- */
|
/* --- 9. STYLES DU SUIVI MENSUEL --- */
|
||||||
.cat-card {
|
.cat-card {
|
||||||
background: var(--bg-panel);
|
background: var(--bg-panel);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
@@ -675,20 +844,17 @@ th.col-laia {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cat-card-header {
|
.cat-card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 15px; /* Padding interne à l'encart coloré */
|
padding: 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cat-card-title-group {
|
.cat-card-title-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cat-card-subtitle {
|
.cat-card-subtitle {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
@@ -768,7 +934,52 @@ input[type="month"].pf-input {
|
|||||||
border-radius: 6px;
|
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 {
|
.epargne-inline-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
@@ -793,7 +1004,7 @@ input[type="month"].pf-input {
|
|||||||
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
|
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- 11. CALCULATRICE FLOTTANTE --- */
|
/* --- 12. CALCULATRICE FLOTTANTE --- */
|
||||||
.pf-fab-sum {
|
.pf-fab-sum {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
@@ -883,7 +1094,7 @@ body.sum-mode-active .sum-target {
|
|||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- 12. RESPONSIVE MOBILE BUDGET --- */
|
/* --- 13. RESPONSIVE MOBILE BUDGET --- */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.budget-view {
|
.budget-view {
|
||||||
padding: 10px !important;
|
padding: 10px !important;
|
||||||
@@ -917,11 +1128,6 @@ body.sum-mode-active .sum-target {
|
|||||||
.budget-view .categories-grid > .cat-card {
|
.budget-view .categories-grid > .cat-card {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
.budget-view
|
|
||||||
> div[style*="justify-content: space-between"]:not(.view-header) {
|
|
||||||
flex-direction: column !important;
|
|
||||||
align-items: stretch !important;
|
|
||||||
}
|
|
||||||
.budget-view
|
.budget-view
|
||||||
> div[style*="justify-content: space-between"]:not(.view-header) {
|
> div[style*="justify-content: space-between"]:not(.view-header) {
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
@@ -1021,7 +1227,6 @@ body.sum-mode-active .sum-target {
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-sticky,
|
.col-sticky,
|
||||||
.sticky-col {
|
.sticky-col {
|
||||||
min-width: 120px !important;
|
min-width: 120px !important;
|
||||||
@@ -1059,8 +1264,7 @@ body.sum-mode-active .sum-target {
|
|||||||
right: auto;
|
right: auto;
|
||||||
top: auto;
|
top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cat-card-header {
|
.cat-card-header {
|
||||||
padding: 12px 10px; /* Plus serré sur mobile */
|
padding: 12px 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
// modules/budget/views/budget_prev.php
|
// modules/budget/views/budget_prev.php
|
||||||
|
|
||||||
|
// 1. Chargement dynamique des parents
|
||||||
|
$stmtPeople = $pdo->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');
|
$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 = [];
|
$salaryConfig = [];
|
||||||
$stmt = $pdo->prepare("SELECT * FROM pf_salary_config WHERE year = ?");
|
$stmt = $pdo->prepare("SELECT * FROM pf_salary_config WHERE year = ?");
|
||||||
$stmt->execute([$currentYear]);
|
$stmt->execute([$currentYear]);
|
||||||
@@ -11,16 +30,17 @@ while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|||||||
$salaryConfig[$row['person']] = $row;
|
$salaryConfig[$row['person']] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (['Alex', 'Laia'] as $p) {
|
// Sécurité profils de salaire vides
|
||||||
if (!isset($salaryConfig[$p])) {
|
foreach ($parentMapping as $map) {
|
||||||
$salaryConfig[$p] = ['salary'=>0, 'mensualite'=>0, 'frais_func'=>0, 'eco_perso'=>0, 'eco_family'=>0];
|
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);
|
$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');
|
$focusDate = isset($_GET['focus_date']) ? $_GET['focus_date'] : date('Y-m-01');
|
||||||
$focusTs = strtotime($focusDate);
|
$focusTs = strtotime($focusDate);
|
||||||
|
|
||||||
@@ -32,7 +52,7 @@ for ($i = 0; $i < 6; $i++) {
|
|||||||
$prevMonthLink = date('Y-m-01', strtotime("-1 month", $focusTs));
|
$prevMonthLink = date('Y-m-01', strtotime("-1 month", $focusTs));
|
||||||
$nextMonthLink = 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 = [];
|
$cycleConfigs = [];
|
||||||
$stmtNotes = $pdo->query("SELECT reference_id, content FROM pf_notes WHERE note_type = 'month_config'");
|
$stmtNotes = $pdo->query("SELECT reference_id, content FROM pf_notes WHERE note_type = 'month_config'");
|
||||||
while ($row = $stmtNotes->fetch(PDO::FETCH_ASSOC)) {
|
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), '?'));
|
$inQuery = implode(',', array_fill(0, count($months), '?'));
|
||||||
$stmt = $pdo->prepare("SELECT * FROM pf_alloc_values WHERE month_date IN ($inQuery)");
|
$stmt = $pdo->prepare("SELECT * FROM pf_alloc_values WHERE month_date IN ($inQuery)");
|
||||||
$stmt->execute($months);
|
$stmt->execute($months);
|
||||||
@@ -53,7 +73,7 @@ while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
|||||||
$allocs[$row['month_date']][$row['cat_id']] = $row;
|
$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;
|
$sysCatId = null;
|
||||||
foreach ($cats as $key => $c) {
|
foreach ($cats as $key => $c) {
|
||||||
if ($c['name'] === 'SYSTEM_VALIDATION') {
|
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];
|
$focusDate = $months[0];
|
||||||
$isValidatedAlex = false;
|
$isValidated = [];
|
||||||
$isValidatedLaia = false;
|
|
||||||
|
|
||||||
if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
|
if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
|
||||||
$row = $allocs[$focusDate][$sysCatId];
|
$row = $allocs[$focusDate][$sysCatId];
|
||||||
$isValidatedAlex = ($row['amount_alex'] == 1);
|
foreach ($parentMapping as $map) {
|
||||||
$isValidatedLaia = ($row['amount_laia'] == 1);
|
$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 = $pdo->prepare("SELECT content FROM pf_notes WHERE note_type = 'budget_prev' AND reference_id = ?");
|
||||||
$stmtNote->execute([$focusDate]);
|
$stmtNote->execute([$focusDate]);
|
||||||
$currentNote = $stmtNote->fetchColumn();
|
$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);
|
$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) {
|
function getTranslatedMonthName($dateString) {
|
||||||
$m = date('m', strtotime($dateString));
|
$m = date('m', strtotime($dateString));
|
||||||
$y = date('Y', strtotime($dateString));
|
$y = date('Y', strtotime($dateString));
|
||||||
@@ -89,7 +110,7 @@ function getTranslatedMonthName($dateString) {
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="prev-container">
|
<div class="prev-container" style="--p1-main: <?= $parentMapping[0]['color'] ?>; --p2-main: <?= $parentMapping[1]['color'] ?>;">
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="prev-section-header">
|
<div class="prev-section-header">
|
||||||
@@ -101,35 +122,31 @@ function getTranslatedMonthName($dateString) {
|
|||||||
<tr>
|
<tr>
|
||||||
<th><?= tr('bud_prev_person') ?></th>
|
<th><?= tr('bud_prev_person') ?></th>
|
||||||
<th><?= tr('bud_prev_salary') ?></th>
|
<th><?= tr('bud_prev_salary') ?></th>
|
||||||
<th><?= tr('bud_prev_monthly_payment') ?><br><small style="font-weight:normal; text-transform:none;">(<?= tr('bud_prev_joint_account') ?>)</small></th>
|
<th><?= tr('bud_prev_monthly_payment') ?><br><small>(<?= tr('bud_prev_joint_account') ?>)</small></th>
|
||||||
<th><?= tr('bud_prev_func_expenses') ?></th>
|
<th><?= tr('bud_prev_func_expenses') ?></th>
|
||||||
<th><?= tr('bud_prev_perso_savings') ?></th>
|
<th><?= tr('bud_prev_perso_savings') ?></th>
|
||||||
<th style="background:#f0f9ff; color:#0284c7;"><?= tr('bud_prev_family_savings') ?><br><small style="font-weight:normal; text-transform:none;">(<?= tr('bud_prev_available') ?>)</small></th>
|
<th class="th-family-savings"><?= tr('bud_prev_family_savings') ?><br><small>(<?= tr('bud_prev_available') ?>)</small></th>
|
||||||
<th><?= tr('bud_prev_perso_remaining') ?></th>
|
<th><?= tr('bud_prev_perso_remaining') ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php
|
<?php
|
||||||
// Définition des noms à partir de la config
|
foreach ($parentMapping as $map):
|
||||||
$family_members = [ID_ALEX => 'Alex', ID_LAIA => 'Laia'];
|
$d = $salaryConfig[$map['name']];
|
||||||
|
|
||||||
foreach ($family_members as $id => $p):
|
|
||||||
$d = $salaryConfig[$p];
|
|
||||||
$restant = $d['salary'] - ($d['mensualite'] + $d['frais_func'] + $d['eco_perso'] + $d['eco_family']);
|
$restant = $d['salary'] - ($d['mensualite'] + $d['frais_func'] + $d['eco_perso'] + $d['eco_family']);
|
||||||
$borderColor = ($id === ID_ALEX) ? '#0891b2' : '#f59e0b';
|
|
||||||
?>
|
?>
|
||||||
<tr data-person="<?= $p ?>">
|
<tr data-person="<?= htmlspecialchars($map['name']) ?>">
|
||||||
<td style="text-align:left; font-weight:bold; color:var(--text-main); border-left:4px solid <?= $borderColor ?>;">
|
<td class="col-p<?= $map['css'] ?>">
|
||||||
<?= $p ?>
|
<?= htmlspecialchars($map['name']) ?>
|
||||||
</td>
|
</td>
|
||||||
<td><input type="number" class="prev-input" data-field="salary" value="<?= round($d['salary']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
<td><input type="number" class="prev-input" data-field="salary" value="<?= round($d['salary']) ?>" onchange="updateSalary('<?= htmlspecialchars($map['name']) ?>', this)"></td>
|
||||||
<td><input type="number" class="prev-input" data-field="mensualite" value="<?= round($d['mensualite']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
<td><input type="number" class="prev-input" data-field="mensualite" value="<?= round($d['mensualite']) ?>" onchange="updateSalary('<?= htmlspecialchars($map['name']) ?>', this)"></td>
|
||||||
<td><input type="number" class="prev-input" data-field="frais_func" value="<?= round($d['frais_func']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
<td><input type="number" class="prev-input" data-field="frais_func" value="<?= round($d['frais_func']) ?>" onchange="updateSalary('<?= htmlspecialchars($map['name']) ?>', this)"></td>
|
||||||
<td><input type="number" class="prev-input" data-field="eco_perso" value="<?= round($d['eco_perso']) ?>" onchange="updateSalary('<?= $p ?>', this)"></td>
|
<td><input type="number" class="prev-input" data-field="eco_perso" value="<?= round($d['eco_perso']) ?>" onchange="updateSalary('<?= htmlspecialchars($map['name']) ?>', this)"></td>
|
||||||
<td style="background:#f0f9ff;">
|
<td class="td-family-savings">
|
||||||
<input type="number" class="prev-input bold-blue" id="eco_family_<?= $p ?>" data-field="eco_family" value="<?= round($d['eco_family']) ?>" onchange="updateSalary('<?= $p ?>', this)">
|
<input type="number" class="prev-input bold-blue" id="eco_family_<?= $map['css'] ?>" data-field="eco_family" value="<?= round($d['eco_family']) ?>" onchange="updateSalary('<?= htmlspecialchars($map['name']) ?>', this)">
|
||||||
</td>
|
</td>
|
||||||
<td id="restant_<?= $p ?>" style="font-weight:bold; color:var(--text-muted); text-align:center;" class="sum-target">
|
<td id="restant_<?= $map['css'] ?>" class="sum-target font-bold">
|
||||||
<?= number_format($restant, 0, ',', ' ') ?> €
|
<?= number_format($restant, 0, ',', ' ') ?> €
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -139,16 +156,16 @@ function getTranslatedMonthName($dateString) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="prev-section-header" style="background: #f8fafc; padding: 15px; border-radius: 12px; border: 1px solid #e2e8f0; margin-bottom: 20px;">
|
<div class="prev-section-header">
|
||||||
<div style="display:flex; gap:10px; align-items:center;">
|
<div class="prev-header-left">
|
||||||
<h2 style="margin:0; font-size:1.3rem;"><?= tr('bud_prev_budget_alloc') ?></h2>
|
<h2><?= tr('bud_prev_budget_alloc') ?></h2>
|
||||||
<div class="nav-group" style="display:flex; gap:5px;">
|
<div class="nav-group">
|
||||||
<a href="?tab=budget_prev&focus_date=<?= $prevMonthLink ?>" class="btn-nav">◀</a>
|
<a href="?tab=budget_prev&focus_date=<?= $prevMonthLink ?>" class="btn-nav">◀</a>
|
||||||
<a href="?tab=budget_prev&focus_date=<?= date('Y-m-01') ?>" class="btn-nav"><?= tr('bud_prev_today') ?></a>
|
<a href="?tab=budget_prev&focus_date=<?= date('Y-m-01') ?>" class="btn-nav"><?= tr('bud_prev_today') ?></a>
|
||||||
<a href="?tab=budget_prev&focus_date=<?= $nextMonthLink ?>" class="btn-nav">▶</a>
|
<a href="?tab=budget_prev&focus_date=<?= $nextMonthLink ?>" class="btn-nav">▶</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex; gap:10px;">
|
<div class="prev-header-actions">
|
||||||
<button type="button" class="pf-btn btn-secondary" onclick="duplicateMonth()">
|
<button type="button" class="pf-btn btn-secondary" onclick="duplicateMonth()">
|
||||||
🔁 <?= tr('bud_sav_add_one_month') ?>
|
🔁 <?= tr('bud_sav_add_one_month') ?>
|
||||||
</button>
|
</button>
|
||||||
@@ -166,13 +183,14 @@ function getTranslatedMonthName($dateString) {
|
|||||||
<?php foreach ($months as $month):
|
<?php foreach ($months as $month):
|
||||||
$isCurrent = ($month == date('Y-m-01'));
|
$isCurrent = ($month == date('Y-m-01'));
|
||||||
$cls = $isCurrent ? 'current' : '';
|
$cls = $isCurrent ? 'current' : '';
|
||||||
|
$colspan = count($parentMapping) + 1;
|
||||||
?>
|
?>
|
||||||
<th colspan="3" class="th-month <?= $cls ?>">
|
<th colspan="<?= $colspan ?>" class="th-month <?= $cls ?>">
|
||||||
<span style="text-transform:capitalize;"><?= getTranslatedMonthName($month) ?></span>
|
<span><?= getTranslatedMonthName($month) ?></span>
|
||||||
<?php
|
<?php
|
||||||
if (isset($cycleConfigs[$month]) && !empty($cycleConfigs[$month]['start_date'])) {
|
if (isset($cycleConfigs[$month]) && !empty($cycleConfigs[$month]['start_date'])) {
|
||||||
$cStart = date('d/m', strtotime($cycleConfigs[$month]['start_date']));
|
$cStart = date('d/m', strtotime($cycleConfigs[$month]['start_date']));
|
||||||
echo "<div style='font-size:0.75rem; font-weight:normal; color:#64748b; margin-top:2px;'>" . sprintf(tr('bud_sav_from_date'), $cStart) . "</div>";
|
echo "<div class='cycle-start-label'>" . sprintf(tr('bud_sav_from_date'), $cStart) . "</div>";
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</th>
|
</th>
|
||||||
@@ -182,88 +200,76 @@ function getTranslatedMonthName($dateString) {
|
|||||||
<th class="col-sticky header-cell"></th>
|
<th class="col-sticky header-cell"></th>
|
||||||
<?php foreach ($months as $month): ?>
|
<?php foreach ($months as $month): ?>
|
||||||
<th class="th-sub">Global</th>
|
<th class="th-sub">Global</th>
|
||||||
<th class="th-sub txt-alex">Alex</th>
|
<?php foreach ($parentMapping as $map): ?>
|
||||||
<th class="th-sub txt-laia">Laia</th>
|
<th class="th-sub txt-<?= $map['css'] ?>"><?= htmlspecialchars($map['name']) ?></th>
|
||||||
|
<?php endforeach; ?>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="row-total">
|
<tr class="row-total">
|
||||||
<td class="col-sticky" style="text-align:right !important;">Total</td>
|
<td class="col-sticky">Total</td>
|
||||||
<?php foreach ($months as $m): ?>
|
<?php foreach ($months as $m): ?>
|
||||||
<td class="txt-global sum-target" style="border-left:2px solid #e2e8f0;" id="total_global_<?= $m ?>">0</td>
|
<td class="txt-global sum-target" id="total_global_<?= $m ?>">0</td>
|
||||||
<td class="txt-alex sum-target" id="total_alex_<?= $m ?>">0</td>
|
<?php foreach ($parentMapping as $map): ?>
|
||||||
<td class="txt-laia sum-target" id="total_laia_<?= $m ?>">0</td>
|
<td class="txt-<?= $map['css'] ?> sum-target" id="total_<?= $map['css'] ?>_<?= $m ?>">0</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="row-restant">
|
<tr class="row-restant">
|
||||||
<td class="col-sticky" style="text-align:right !important;"><?= tr('bud_prev_remaining') ?> (Eco Family)</td>
|
<td class="col-sticky"><?= tr('bud_prev_remaining') ?> (Eco Family)</td>
|
||||||
<?php foreach ($months as $m): ?>
|
<?php foreach ($months as $m): ?>
|
||||||
<td style="border-left:2px solid #cbd5e1; background:#e2e8f0;">-</td>
|
<td class="td-separator">-</td>
|
||||||
<td class="val-ok sum-target" id="restant_alloc_alex_<?= $m ?>">0</td>
|
<?php foreach ($parentMapping as $map): ?>
|
||||||
<td class="val-ok sum-target" id="restant_alloc_laia_<?= $m ?>">0</td>
|
<td class="val-ok sum-target" id="restant_alloc_<?= $map['css'] ?>_<?= $m ?>">0</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<?php foreach ($cats as $cat):
|
<?php foreach ($cats as $cat):
|
||||||
$isIndicative = ($cat['name'] === 'Eco Alex' || $cat['name'] === 'Eco Laia');
|
$isIndicative = (strpos($cat['name'], 'Eco P') === 0);
|
||||||
$rowClass = $isIndicative ? 'row-indicative' : '';
|
$rowClass = $isIndicative ? 'row-indicative' : '';
|
||||||
$inputClass = $isIndicative ? 'ignore-calc' : '';
|
$inputClass = $isIndicative ? 'ignore-calc' : '';
|
||||||
$rowStyle = $isIndicative ? 'background:#f8fafc; color:#94a3b8;' : '';
|
|
||||||
|
$catDisplayName = $cat['name'];
|
||||||
|
if ($catDisplayName === 'Eco P1') $catDisplayName = 'Eco ' . $p1_name;
|
||||||
|
if ($catDisplayName === 'Eco P2') $catDisplayName = 'Eco ' . $p2_name;
|
||||||
?>
|
?>
|
||||||
<tr class="<?= $rowClass ?>" style="<?= $rowStyle ?>">
|
<tr class="<?= $rowClass ?>">
|
||||||
<td class="col-sticky" style="position:relative; <?= $isIndicative ? 'opacity:0.8;' : '' ?>">
|
<td class="col-sticky">
|
||||||
<div style="font-weight:600; color:<?= $isIndicative ? '#64748b' : 'var(--text-main)' ?>;">
|
<div class="cat-name-label">
|
||||||
<?= htmlspecialchars($cat['name']) ?>
|
<?= htmlspecialchars($catDisplayName) ?>
|
||||||
<?php if(!empty($cat['holiday_id'])) echo " 🌴"; ?>
|
<?php if(!empty($cat['holiday_id'])) echo " 🌴"; ?>
|
||||||
<?php if($isIndicative): ?><span style="font-size:0.7rem; border:1px solid #cbd5e1; border-radius:4px; padding:0 4px; margin-left:5px;">Info</span><?php endif; ?>
|
<?php if($isIndicative): ?><span>Info</span><?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<div style="font-size:0.75rem; color:var(--text-muted); font-style:italic; text-align:right;">
|
<div class="cat-target-label">
|
||||||
<?= htmlspecialchars($cat['target']) ?>
|
<?= htmlspecialchars($cat['target']) ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row-actions">
|
<div class="row-actions">
|
||||||
<button type="button"
|
<button type="button" class="btn-icon-action edit" title="<?= tr('edit') ?>" data-id="<?= $cat['id'] ?>" data-name="<?= htmlspecialchars($cat['name']) ?>" data-target="<?= htmlspecialchars($cat['target']) ?>" data-holiday="<?= $cat['holiday_id'] ?? '' ?>" onclick="openEditModal(this)">✎</button>
|
||||||
class="btn-icon-action edit"
|
<button type="button" onclick="deleteCategory(<?= $cat['id'] ?>)" class="btn-icon-action delete" title="<?= tr('delete') ?>">🗑️</button>
|
||||||
title="<?= tr('edit') ?>"
|
|
||||||
data-id="<?= $cat['id'] ?>"
|
|
||||||
data-name="<?= htmlspecialchars($cat['name']) ?>"
|
|
||||||
data-target="<?= htmlspecialchars($cat['target']) ?>"
|
|
||||||
data-holiday="<?= $cat['holiday_id'] ?? '' ?>"
|
|
||||||
onclick="openEditModal(this)">
|
|
||||||
✎
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="button"
|
|
||||||
onclick="deleteCategory(<?= $cat['id'] ?>)"
|
|
||||||
class="btn-icon-action delete" title="<?= tr('delete') ?>">🗑️
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<?php foreach ($months as $m):
|
<?php foreach ($months as $m):
|
||||||
$val = $allocs[$m][$cat['id']] ?? ['amount_alex'=>0, 'amount_laia'=>0];
|
$val = $allocs[$m][$cat['id']] ?? [];
|
||||||
?>
|
?>
|
||||||
<td class="txt-global sum-target" style="border-left:2px solid #e2e8f0; <?= $isIndicative?'color:#94a3b8; font-weight:normal;':'' ?>" id="g_<?= $m ?>_<?= $cat['id'] ?>">0</td>
|
<td class="txt-global sum-target" id="g_<?= $m ?>_<?= $cat['id'] ?>">0</td>
|
||||||
|
|
||||||
|
<?php foreach ($parentMapping as $map):
|
||||||
|
$dbField = $map['db_field'];
|
||||||
|
$cellValue = isset($val[$dbField]) ? $val[$dbField] : 0;
|
||||||
|
?>
|
||||||
<td>
|
<td>
|
||||||
<input type="number" step="1" class="prev-input txt-alex inp-alex-<?= $m ?> <?= $inputClass ?>"
|
<input type="number" step="1" class="prev-input txt-<?= $map['css'] ?> inp-<?= $map['css'] ?>-<?= $m ?> <?= $inputClass ?>"
|
||||||
style="<?= $isIndicative ? 'color:#64748b;' : '' ?>"
|
value="<?= $cellValue == 0 ? '' : round($cellValue) ?>"
|
||||||
value="<?= $val['amount_alex'] == 0 ? '' : round($val['amount_alex']) ?>"
|
|
||||||
placeholder="-"
|
placeholder="-"
|
||||||
data-target="<?= htmlspecialchars($cat['target']) ?>"
|
data-target="<?= htmlspecialchars($cat['target']) ?>"
|
||||||
onchange="updateAlloc('<?= $m ?>', <?= $cat['id'] ?>, 'amount_alex', this)">
|
onchange="updateAlloc('<?= $m ?>', <?= $cat['id'] ?>, '<?= $dbField ?>', this)">
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<input type="number" step="1" class="prev-input txt-laia inp-laia-<?= $m ?> <?= $inputClass ?>"
|
|
||||||
style="<?= $isIndicative ? 'color:#64748b;' : '' ?>"
|
|
||||||
value="<?= $val['amount_laia'] == 0 ? '' : round($val['amount_laia']) ?>"
|
|
||||||
placeholder="-"
|
|
||||||
data-target="<?= htmlspecialchars($cat['target']) ?>"
|
|
||||||
onchange="updateAlloc('<?= $m ?>', <?= $cat['id'] ?>, 'amount_laia', this)">
|
|
||||||
</td>
|
</td>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -271,120 +277,62 @@ function getTranslatedMonthName($dateString) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin: 20px 0; background: white; padding: 20px; border-radius: 12px; box-shadow: var(--shadow-sm); border: 1px solid #e2e8f0;">
|
<div class="budget-notes-section">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
|
<div class="notes-header">
|
||||||
<h3 style="margin: 0; font-size: 1.1rem; color: #1e293b;">
|
<h3>📝 <?= tr('bud_prev_notes_for') ?> <span><?= getTranslatedMonthName($focusDate) ?></span></h3>
|
||||||
📝 <?= tr('bud_prev_notes_for') ?> <span style="text-transform:capitalize;"><?= getTranslatedMonthName($focusDate) ?></span>
|
<span id="note-save-indicator" class="note-save-indicator">✓ <?= tr('bud_prev_saved') ?></span>
|
||||||
</h3>
|
|
||||||
<span id="note-save-indicator" style="font-size:0.85rem; color:#10b981; font-weight:bold; opacity:0; transition:opacity 0.3s;">
|
|
||||||
✓ <?= tr('bud_prev_saved') ?>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea id="monthNoteArea" class="pf-input" rows="3" placeholder="<?= tr('bud_prev_notes_ph') ?>"><?= htmlspecialchars((string)$currentNote) ?></textarea>
|
||||||
id="monthNoteArea"
|
|
||||||
class="pf-input"
|
|
||||||
rows="3"
|
|
||||||
placeholder="<?= tr('bud_prev_notes_ph') ?>"
|
|
||||||
style="width: 100%; resize: vertical; border-color: #cbd5e1; background: #f8fafc; margin-bottom: 10px;"
|
|
||||||
><?= htmlspecialchars((string)$currentNote) ?></textarea>
|
|
||||||
|
|
||||||
<div style="text-align: right;">
|
<div class="notes-footer">
|
||||||
<button type="button" class="pf-btn" style="width: auto; display: inline-flex;"
|
<button type="button" class="pf-btn" onclick="saveGenericNote('budget_prev', '<?= $focusDate ?>', document.getElementById('monthNoteArea').value)"><?= tr('bud_prev_save_note') ?></button>
|
||||||
onclick="saveGenericNote('budget_prev', '<?= $focusDate ?>', document.getElementById('monthNoteArea').value)">
|
|
||||||
<?= tr('bud_prev_save_note') ?>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
$focusMonth = $months[0];
|
$focusMonth = $months[0];
|
||||||
|
|
||||||
$targetsOrder = ['vers commune', 'vers L.Pol', 'vers L.Pep', 'vers L.Perso'];
|
$targetsOrder = ['vers commune', 'vers L.Pol', 'vers L.Pep', 'vers L.Perso'];
|
||||||
|
|
||||||
$allTargets = $targetsOrder;
|
$allTargets = $targetsOrder;
|
||||||
foreach($cats as $c) {
|
foreach($cats as $c) {
|
||||||
$t = trim($c['target']);
|
$t = trim($c['target']);
|
||||||
if(!empty($t) && !in_array($t, $allTargets)) {
|
if(!empty($t) && !in_array($t, $allTargets)) { $allTargets[] = $t; }
|
||||||
$allTargets[] = $t;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$allTargets = array_unique($allTargets);
|
$allTargets = array_unique($allTargets);
|
||||||
|
|
||||||
$summaryData = [];
|
|
||||||
foreach($allTargets as $t) $summaryData[$t] = ['Alex' => 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;
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="recap-wrapper">
|
<div class="recap-wrapper">
|
||||||
<div class="recap-card">
|
<div class="recap-card">
|
||||||
<div class="recap-header">
|
<div class="recap-header">
|
||||||
<?= tr('bud_prev_transfers_to_make') ?> - <span style="text-transform:capitalize;"><?= getTranslatedMonthName($focusMonth) ?></span>
|
<?= tr('bud_prev_transfers_to_make') ?> - <span><?= getTranslatedMonthName($focusMonth) ?></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="recap-table">
|
<table class="recap-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="text-align:left;"><?= tr('bud_prev_destination') ?></th>
|
<th><?= tr('bud_prev_destination') ?></th>
|
||||||
|
<?php foreach ($parentMapping as $map): ?>
|
||||||
<th class="col-alex">
|
<th class="col-<?= $map['css'] ?>">
|
||||||
<div style="display:flex; flex-direction:column; align-items:center; gap:5px;">
|
<div class="recap-th-content">
|
||||||
<span>ALEX</span>
|
<span><?= htmlspecialchars($map['name']) ?></span>
|
||||||
<?php if($isValidatedAlex): ?>
|
<?php if($isValidated[$map['css']]): ?>
|
||||||
<div style="background:#10b981; color:white; padding:4px 8px; border-radius:4px; font-size:0.7rem; display:flex; align-items:center; gap:4px;">
|
<div class="validation-badge">✓ <?= tr('bud_prev_done') ?></div>
|
||||||
✓ <?= tr('bud_prev_done') ?>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<button onclick="validateTransfers('Alex', '<?= $focusMonth ?>')" class="pf-btn btn-small" style="background:white; color:#0891b2; border:1px solid #0891b2; font-size:0.7rem; padding:2px 8px; height:auto;">
|
<button onclick="validateTransfers('<?= $map['css'] ?>', '<?= $focusMonth ?>')" class="pf-btn btn-small"><?= tr('bud_prev_validate') ?></button>
|
||||||
<?= tr('bud_prev_validate') ?>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
|
<?php endforeach; ?>
|
||||||
<th class="col-laia">
|
|
||||||
<div style="display:flex; flex-direction:column; align-items:center; gap:5px;">
|
|
||||||
<span>LAIA</span>
|
|
||||||
<?php if($isValidatedLaia): ?>
|
|
||||||
<div style="background:#10b981; color:white; padding:4px 8px; border-radius:4px; font-size:0.7rem; display:flex; align-items:center; gap:4px;">
|
|
||||||
✓ <?= tr('bud_prev_done') ?>
|
|
||||||
</div>
|
|
||||||
<?php else: ?>
|
|
||||||
<button onclick="validateTransfers('Laia', '<?= $focusMonth ?>')" class="pf-btn btn-small" style="background:white; color:#d97706; border:1px solid #d97706; font-size:0.7rem; padding:2px 8px; height:auto;">
|
|
||||||
<?= tr('bud_prev_validate') ?>
|
|
||||||
</button>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
|
|
||||||
<th class="col-global">Global</th>
|
<th class="col-global">Global</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php foreach($allTargets as $target):
|
<?php foreach($allTargets as $target): $tId = md5($target); ?>
|
||||||
$tId = md5($target);
|
|
||||||
?>
|
|
||||||
<tr id="row_summary_<?= $tId ?>">
|
<tr id="row_summary_<?= $tId ?>">
|
||||||
<td><?= htmlspecialchars($target) ?></td>
|
<td><?= htmlspecialchars($target) ?></td>
|
||||||
<td class="col-alex sum-target" id="sum_alex_<?= $tId ?>">0 €</td>
|
<?php foreach ($parentMapping as $map): ?>
|
||||||
<td class="col-laia sum-target" id="sum_laia_<?= $tId ?>">0 €</td>
|
<td class="col-<?= $map['css'] ?> sum-target" id="sum_<?= $map['css'] ?>_<?= $tId ?>">0 €</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
<td class="col-global sum-target" id="sum_global_<?= $tId ?>">0 €</td>
|
<td class="col-global sum-target" id="sum_global_<?= $tId ?>">0 €</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -392,8 +340,9 @@ function getTranslatedMonthName($dateString) {
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr class="row-grand-total">
|
<tr class="row-grand-total">
|
||||||
<td><?= tr('bud_prev_grand_total') ?></td>
|
<td><?= tr('bud_prev_grand_total') ?></td>
|
||||||
<td class="col-alex sum-target" id="grand_total_alex">0 €</td>
|
<?php foreach ($parentMapping as $map): ?>
|
||||||
<td class="col-laia sum-target" id="grand_total_laia">0 €</td>
|
<td class="col-<?= $map['css'] ?> sum-target" id="grand_total_<?= $map['css'] ?>">0 €</td>
|
||||||
|
<?php endforeach; ?>
|
||||||
<td class="col-global sum-target" id="grand_total_global">0 €</td>
|
<td class="col-global sum-target" id="grand_total_global">0 €</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
@@ -403,18 +352,18 @@ function getTranslatedMonthName($dateString) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="addCatModal" class="pf-modal">
|
<div id="addCatModal" class="pf-modal">
|
||||||
<div class="pf-modal-content" style="background:white; width:95%; max-width:400px; border-radius:20px; box-shadow:0 20px 25px -5px rgba(0,0,0,0.1); padding:30px;">
|
<div class="pf-modal-content">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
<div class="prev-header-left">
|
||||||
<h3 class="pf-modal-title" style="margin:0; border:none; font-size:1.2rem;"><?= tr('bud_prev_new_line_title') ?></h3>
|
<h3 class="pf-modal-title"><?= tr('bud_prev_new_line_title') ?></h3>
|
||||||
<button onclick="document.getElementById('addCatModal').style.display='none'; document.body.classList.remove('no-scroll');" style="border:none; background:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted);">×</button>
|
<button onclick="document.getElementById('addCatModal').style.display='none'; document.body.classList.remove('no-scroll');">×</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="/modules/budget/includes/api/save-budget.php" method="POST">
|
<form action="/modules/budget/includes/api/save-budget.php" method="POST">
|
||||||
<input type="hidden" name="action" value="add_category">
|
<input type="hidden" name="action" value="add_category">
|
||||||
<div class="form-group" style="margin-bottom:15px;">
|
<div class="form-group">
|
||||||
<label class="pf-label"><?= tr('bud_prev_label_name') ?></label>
|
<label class="pf-label"><?= tr('bud_prev_label_name') ?></label>
|
||||||
<input type="text" name="name" class="pf-input" required>
|
<input type="text" name="name" class="pf-input" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom:15px;">
|
<div class="form-group">
|
||||||
<label class="pf-label"><?= tr('bud_prev_label_target') ?></label>
|
<label class="pf-label"><?= tr('bud_prev_label_target') ?></label>
|
||||||
<select name="target" class="pf-input" required>
|
<select name="target" class="pf-input" required>
|
||||||
<option value="" disabled selected>-- <?= tr('bud_prev_choose') ?> --</option>
|
<option value="" disabled selected>-- <?= tr('bud_prev_choose') ?> --</option>
|
||||||
@@ -424,9 +373,9 @@ function getTranslatedMonthName($dateString) {
|
|||||||
<option value="vers commune">vers commune</option>
|
<option value="vers commune">vers commune</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom:20px;">
|
<div class="form-group">
|
||||||
<label class="pf-label" style="color:#8b5cf6;">🌴 <?= tr('bud_prev_link_holiday') ?></label>
|
<label class="pf-label">🌴 <?= tr('bud_prev_link_holiday') ?></label>
|
||||||
<select name="holiday_id" class="pf-input" style="border-color:#8b5cf6; background:#f5f3ff;">
|
<select name="holiday_id" class="pf-input">
|
||||||
<option value="">-- <?= tr('bud_prev_no_link') ?> --</option>
|
<option value="">-- <?= tr('bud_prev_no_link') ?> --</option>
|
||||||
<?php foreach ($activeHolidays as $hol): ?>
|
<?php foreach ($activeHolidays as $hol): ?>
|
||||||
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
|
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
|
||||||
@@ -442,20 +391,19 @@ function getTranslatedMonthName($dateString) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="editCatModal" class="pf-modal">
|
<div id="editCatModal" class="pf-modal">
|
||||||
<div class="pf-modal-content" style="max-width:400px;">
|
<div class="pf-modal-content">
|
||||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
|
<div class="prev-header-left">
|
||||||
<h3 class="pf-modal-title" style="margin:0; border:none; font-size:1.2rem;"><?= tr('bud_prev_edit_line_title') ?></h3>
|
<h3 class="pf-modal-title"><?= tr('bud_prev_edit_line_title') ?></h3>
|
||||||
<button onclick="document.getElementById('editCatModal').style.display='none'; document.body.classList.remove('no-scroll');" style="border:none; background:none; font-size:1.5rem; cursor:pointer; color:var(--text-muted);">×</button>
|
<button onclick="document.getElementById('editCatModal').style.display='none'; document.body.classList.remove('no-scroll');">×</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="/modules/budget/includes/api/save-budget.php" method="POST">
|
<form action="/modules/budget/includes/api/save-budget.php" method="POST">
|
||||||
<input type="hidden" name="action" value="update_category">
|
<input type="hidden" name="action" value="update_category">
|
||||||
<input type="hidden" name="cat_id" id="edit_cat_id">
|
<input type="hidden" name="cat_id" id="edit_cat_id">
|
||||||
|
<div class="form-group">
|
||||||
<div class="form-group" style="margin-bottom:15px;">
|
|
||||||
<label class="pf-label"><?= tr('bud_label_name') ?></label>
|
<label class="pf-label"><?= tr('bud_label_name') ?></label>
|
||||||
<input type="text" name="name" id="edit_cat_name" class="pf-input" required>
|
<input type="text" name="name" id="edit_cat_name" class="pf-input" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom:15px;">
|
<div class="form-group">
|
||||||
<label class="pf-label"><?= tr('bud_prev_label_target') ?></label>
|
<label class="pf-label"><?= tr('bud_prev_label_target') ?></label>
|
||||||
<select name="target" id="edit_cat_target" class="pf-input" required>
|
<select name="target" id="edit_cat_target" class="pf-input" required>
|
||||||
<option value="vers L.Pol">vers L.Pol</option>
|
<option value="vers L.Pol">vers L.Pol</option>
|
||||||
@@ -464,9 +412,9 @@ function getTranslatedMonthName($dateString) {
|
|||||||
<option value="vers commune">vers commune</option>
|
<option value="vers commune">vers commune</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" style="margin-bottom:20px;">
|
<div class="form-group">
|
||||||
<label class="pf-label" style="color:#8b5cf6;">🌴 <?= tr('bud_prev_link_holiday') ?></label>
|
<label class="pf-label">🌴 <?= tr('bud_prev_link_holiday') ?></label>
|
||||||
<select name="holiday_id" id="edit_cat_holiday" class="pf-input" style="border-color:#8b5cf6; background:#f5f3ff;">
|
<select name="holiday_id" id="edit_cat_holiday" class="pf-input">
|
||||||
<option value="">-- <?= tr('bud_prev_no_link') ?> --</option>
|
<option value="">-- <?= tr('bud_prev_no_link') ?> --</option>
|
||||||
<?php foreach ($activeHolidays as $hol): ?>
|
<?php foreach ($activeHolidays as $hol): ?>
|
||||||
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
|
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
|
||||||
@@ -481,20 +429,17 @@ function getTranslatedMonthName($dateString) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button id="fabSumMode" class="pf-fab-sum" onclick="toggleSumMode()">
|
||||||
<button id="fabSumMode" class="pf-fab-sum" onclick="toggleSumMode()" title="<?= tr('bud_sav_sum_mode_title') ?>">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path><line x1="8" y1="12" x2="16" y2="12"></line><line x1="12" y1="8" x2="12" y2="16"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path><line x1="8" y1="12" x2="16" y2="12"></line><line x1="12" y1="8" x2="12" y2="16"></line></svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div id="sumResultBar" class="pf-sum-bar">
|
<div id="sumResultBar" class="pf-sum-bar">
|
||||||
<span class="pf-sum-label"><?= tr('bud_sav_selection') ?></span>
|
<span class="pf-sum-label"><?= tr('bud_sav_selection') ?></span>
|
||||||
<span id="sumResultValue" class="pf-sum-value">0 €</span>
|
<span id="sumResultValue" class="pf-sum-value">0 €</span>
|
||||||
<button onclick="toggleSumMode()" class="pf-sum-close" title="<?= tr('btn_close') ?>">×</button>
|
<button onclick="toggleSumMode()" class="pf-sum-close">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// --- 1. SÉCURISATION TRADUCTIONS ET LANGUE ---
|
|
||||||
window.appLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
|
window.appLang = document.documentElement.lang === "ca" ? "ca-ES" : "fr-FR";
|
||||||
window.I18N = {
|
window.I18N = {
|
||||||
...(window.I18N || {}),
|
...(window.I18N || {}),
|
||||||
@@ -505,21 +450,17 @@ window.I18N = {
|
|||||||
'bud_err_tech': <?= json_encode(tr('bud_err_tech')) ?>
|
'bud_err_tech': <?= json_encode(tr('bud_err_tech')) ?>
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.CONFIG = window.CONFIG || {};
|
||||||
|
window.CONFIG.parentMapping = <?= json_encode($parentMapping) ?>;
|
||||||
|
|
||||||
const currentYear = <?= $currentYear ?? 'new Date().getFullYear()' ?>;
|
const currentYear = <?= $currentYear ?>;
|
||||||
const months = <?= json_encode($months ?? []) ?>;
|
const months = <?= json_encode($months) ?>;
|
||||||
|
|
||||||
function openEditModal(btn) {
|
function openEditModal(btn) {
|
||||||
const id = btn.getAttribute('data-id');
|
document.getElementById('edit_cat_id').value = btn.getAttribute('data-id');
|
||||||
const name = btn.getAttribute('data-name');
|
document.getElementById('edit_cat_name').value = btn.getAttribute('data-name');
|
||||||
const target = btn.getAttribute('data-target');
|
document.getElementById('edit_cat_target').value = btn.getAttribute('data-target');
|
||||||
const holiday = btn.getAttribute('data-holiday');
|
document.getElementById('edit_cat_holiday').value = btn.getAttribute('data-holiday');
|
||||||
|
|
||||||
document.getElementById('edit_cat_id').value = id;
|
|
||||||
document.getElementById('edit_cat_name').value = name;
|
|
||||||
document.getElementById('edit_cat_target').value = target;
|
|
||||||
document.getElementById('edit_cat_holiday').value = holiday;
|
|
||||||
|
|
||||||
document.getElementById('editCatModal').style.display = 'flex';
|
document.getElementById('editCatModal').style.display = 'flex';
|
||||||
document.body.classList.add('no-scroll');
|
document.body.classList.add('no-scroll');
|
||||||
}
|
}
|
||||||
@@ -533,7 +474,11 @@ function updateSalary(person, input) {
|
|||||||
const ecoF = parseFloat(row.querySelector('[data-field="eco_family"]').value) || 0;
|
const ecoF = parseFloat(row.querySelector('[data-field="eco_family"]').value) || 0;
|
||||||
|
|
||||||
const restant = salary - (mens + frais + ecoP + ecoF);
|
const restant = salary - (mens + frais + ecoP + ecoF);
|
||||||
document.getElementById('restant_' + person).innerText = Math.round(restant).toLocaleString(window.appLang) + ' €';
|
|
||||||
|
const parentMap = window.CONFIG.parentMapping.find(m => m.name === person);
|
||||||
|
if(parentMap) {
|
||||||
|
document.getElementById('restant_' + parentMap.css).innerText = Math.round(restant).toLocaleString(window.appLang) + ' €';
|
||||||
|
}
|
||||||
|
|
||||||
saveData('update_salary_config', { year: currentYear, person: person, field: input.dataset.field, value: input.value });
|
saveData('update_salary_config', { year: currentYear, person: person, field: input.dataset.field, value: input.value });
|
||||||
recalcAllAllocations();
|
recalcAllAllocations();
|
||||||
@@ -547,6 +492,7 @@ function updateAlloc(month, catId, personField, input) {
|
|||||||
function duplicateMonth() {
|
function duplicateMonth() {
|
||||||
const targetDateStr = months[0];
|
const targetDateStr = months[0];
|
||||||
const sourceDateStr = months[1];
|
const sourceDateStr = months[1];
|
||||||
|
const parentMap = window.CONFIG.parentMapping;
|
||||||
|
|
||||||
if (!sourceDateStr) {
|
if (!sourceDateStr) {
|
||||||
alert(window.I18N['bud_prev_err_no_history']);
|
alert(window.I18N['bud_prev_err_no_history']);
|
||||||
@@ -560,48 +506,48 @@ function duplicateMonth() {
|
|||||||
|
|
||||||
const sourceName = formatMonth(sourceDateStr);
|
const sourceName = formatMonth(sourceDateStr);
|
||||||
const targetName = formatMonth(targetDateStr);
|
const targetName = formatMonth(targetDateStr);
|
||||||
|
|
||||||
const message = window.I18N['bud_prev_confirm_copy'].replace('%s', sourceName).replace('%t', targetName);
|
const message = window.I18N['bud_prev_confirm_copy'].replace('%s', sourceName).replace('%t', targetName);
|
||||||
|
|
||||||
if(!confirm(message)) return;
|
if(!confirm(message)) return;
|
||||||
|
|
||||||
document.querySelectorAll('.inp-alex-' + sourceDateStr).forEach(sourceInput => {
|
const firstCss = parentMap[0].css;
|
||||||
|
document.querySelectorAll('.inp-' + firstCss + '-' + sourceDateStr).forEach(sourceInput => {
|
||||||
const catIdMatch = sourceInput.getAttribute('onchange').match(/, (\d+),/);
|
const catIdMatch = sourceInput.getAttribute('onchange').match(/, (\d+),/);
|
||||||
if(!catIdMatch) return;
|
if(!catIdMatch) return;
|
||||||
const catId = catIdMatch[1];
|
const catId = catIdMatch[1];
|
||||||
const row = sourceInput.closest('tr');
|
const row = sourceInput.closest('tr');
|
||||||
|
|
||||||
const valAlex = sourceInput.value;
|
parentMap.forEach(map => {
|
||||||
const targetAlex = row.querySelector('.inp-alex-' + targetDateStr);
|
const sInp = row.querySelector('.inp-' + map.css + '-' + sourceDateStr);
|
||||||
if(targetAlex) {
|
const tInp = row.querySelector('.inp-' + map.css + '-' + targetDateStr);
|
||||||
targetAlex.value = valAlex;
|
if(sInp && tInp) {
|
||||||
updateAlloc(targetDateStr, catId, 'amount_alex', targetAlex);
|
tInp.value = sInp.value;
|
||||||
}
|
updateAlloc(targetDateStr, catId, map.db_field, tInp);
|
||||||
|
|
||||||
const valLaia = row.querySelector('.inp-laia-' + sourceDateStr).value;
|
|
||||||
const targetLaia = row.querySelector('.inp-laia-' + targetDateStr);
|
|
||||||
if(targetLaia) {
|
|
||||||
targetLaia.value = valLaia;
|
|
||||||
updateAlloc(targetDateStr, catId, 'amount_laia', targetLaia);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function recalcAllAllocations() {
|
function recalcAllAllocations() {
|
||||||
const budgetAlex = parseFloat(document.getElementById('eco_family_Alex').value) || 0;
|
const parentMap = window.CONFIG.parentMapping;
|
||||||
const budgetLaia = parseFloat(document.getElementById('eco_family_Laia').value) || 0;
|
|
||||||
|
|
||||||
months.forEach(m => {
|
months.forEach(m => {
|
||||||
let sumAlex = 0;
|
let sums = {};
|
||||||
let sumLaia = 0;
|
parentMap.forEach(map => sums[map.css] = 0);
|
||||||
|
|
||||||
document.querySelectorAll('.inp-alex-' + m).forEach(inp => {
|
|
||||||
const val = parseFloat(inp.value) || 0;
|
|
||||||
if (!inp.classList.contains('ignore-calc')) sumAlex += val;
|
|
||||||
|
|
||||||
|
const firstCss = parentMap[0].css;
|
||||||
|
document.querySelectorAll('.inp-' + firstCss + '-' + m).forEach(inp => {
|
||||||
const row = inp.closest('tr');
|
const row = inp.closest('tr');
|
||||||
const laiaVal = parseFloat(row.querySelector('.inp-laia-' + m).value) || 0;
|
let globalSum = 0;
|
||||||
const globalSum = val + laiaVal;
|
|
||||||
|
parentMap.forEach(map => {
|
||||||
|
const pInp = row.querySelector('.inp-' + map.css + '-' + m);
|
||||||
|
const val = pInp ? (parseFloat(pInp.value) || 0) : 0;
|
||||||
|
if (pInp && !pInp.classList.contains('ignore-calc')) {
|
||||||
|
sums[map.css] += val;
|
||||||
|
}
|
||||||
|
globalSum += val;
|
||||||
|
});
|
||||||
|
|
||||||
const onchangeStr = inp.getAttribute('onchange');
|
const onchangeStr = inp.getAttribute('onchange');
|
||||||
const matches = onchangeStr.match(/, (\d+),/);
|
const matches = onchangeStr.match(/, (\d+),/);
|
||||||
@@ -611,73 +557,76 @@ function recalcAllAllocations() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.inp-laia-' + m).forEach(inp => {
|
let totalGlobal = 0;
|
||||||
const val = parseFloat(inp.value) || 0;
|
parentMap.forEach(map => {
|
||||||
if (!inp.classList.contains('ignore-calc')) sumLaia += val;
|
const sumEl = document.getElementById('total_' + map.css + '_' + m);
|
||||||
|
if(sumEl) sumEl.innerText = Math.round(sums[map.css]) + ' €';
|
||||||
|
|
||||||
|
totalGlobal += sums[map.css];
|
||||||
|
|
||||||
|
const budget = parseFloat(document.getElementById('eco_family_' + map.css).value) || 0;
|
||||||
|
const rest = budget - sums[map.css];
|
||||||
|
|
||||||
|
const elRest = document.getElementById('restant_alloc_' + map.css + '_' + m);
|
||||||
|
if (elRest) {
|
||||||
|
elRest.innerText = Math.round(rest) + ' €';
|
||||||
|
elRest.className = 'val-' + (rest >= 0 ? 'ok' : 'ko') + ' sum-target';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('total_alex_' + m).innerText = Math.round(sumAlex) + ' €';
|
const globEl = document.getElementById('total_global_' + m);
|
||||||
document.getElementById('total_laia_' + m).innerText = Math.round(sumLaia) + ' €';
|
if(globEl) globEl.innerText = Math.round(totalGlobal) + ' €';
|
||||||
document.getElementById('total_global_' + m).innerText = Math.round(sumAlex + sumLaia) + ' €';
|
|
||||||
|
|
||||||
const restAlex = budgetAlex - sumAlex;
|
|
||||||
const restLaia = budgetLaia - sumLaia;
|
|
||||||
|
|
||||||
const elRestAlex = document.getElementById('restant_alloc_alex_' + m);
|
|
||||||
const elRestLaia = document.getElementById('restant_alloc_laia_' + m);
|
|
||||||
|
|
||||||
elRestAlex.innerText = Math.round(restAlex) + ' €';
|
|
||||||
elRestAlex.className = 'val-' + (restAlex >= 0 ? 'ok' : 'ko') + ' sum-target';
|
|
||||||
|
|
||||||
elRestLaia.innerText = Math.round(restLaia) + ' €';
|
|
||||||
elRestLaia.className = 'val-' + (restLaia >= 0 ? 'ok' : 'ko') + ' sum-target';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateSummaryTable();
|
updateSummaryTable();
|
||||||
if(isSumModeActive) updateSumResult();
|
if(isSumModeActive) updateSumResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSummaryTable() {
|
function updateSummaryTable() {
|
||||||
const focusMonth = months[0];
|
const focusMonth = months[0];
|
||||||
let grandTotalAlex = 0;
|
const parentMap = window.CONFIG.parentMapping;
|
||||||
let grandTotalLaia = 0;
|
let grandTotals = {};
|
||||||
const dataByTarget = {};
|
let dataByTarget = {};
|
||||||
|
|
||||||
document.querySelectorAll('.inp-alex-' + focusMonth).forEach(inp => {
|
parentMap.forEach(map => {
|
||||||
|
grandTotals[map.css] = 0;
|
||||||
|
dataByTarget[map.css] = {};
|
||||||
|
|
||||||
|
document.querySelectorAll('.inp-' + map.css + '-' + focusMonth).forEach(inp => {
|
||||||
const target = inp.getAttribute('data-target');
|
const target = inp.getAttribute('data-target');
|
||||||
if(target) {
|
if(target) {
|
||||||
if(!dataByTarget[target]) dataByTarget[target] = { alex: 0, laia: 0 };
|
if(!dataByTarget[map.css][target]) dataByTarget[map.css][target] = 0;
|
||||||
dataByTarget[target].alex += (parseFloat(inp.value) || 0);
|
dataByTarget[map.css][target] += (parseFloat(inp.value) || 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelectorAll('.inp-laia-' + focusMonth).forEach(inp => {
|
|
||||||
const target = inp.getAttribute('data-target');
|
|
||||||
if(target) {
|
|
||||||
if(!dataByTarget[target]) dataByTarget[target] = { alex: 0, laia: 0 };
|
|
||||||
dataByTarget[target].laia += (parseFloat(inp.value) || 0);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const tbody = document.querySelector('.recap-table tbody');
|
const tbody = document.querySelector('.recap-table tbody');
|
||||||
if(tbody) {
|
if(tbody) {
|
||||||
Array.from(tbody.rows).forEach(row => {
|
Array.from(tbody.rows).forEach(row => {
|
||||||
const targetName = row.cells[0].innerText.trim();
|
const targetName = row.cells[0].innerText.trim();
|
||||||
const alexSum = dataByTarget[targetName] ? dataByTarget[targetName].alex : 0;
|
let globalSum = 0;
|
||||||
const laiaSum = dataByTarget[targetName] ? dataByTarget[targetName].laia : 0;
|
|
||||||
const globalSum = alexSum + laiaSum;
|
|
||||||
|
|
||||||
row.cells[1].innerText = Math.round(alexSum).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
parentMap.forEach((map, idx) => {
|
||||||
row.cells[2].innerText = Math.round(laiaSum).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
const pSum = dataByTarget[map.css][targetName] || 0;
|
||||||
row.cells[3].innerText = Math.round(globalSum).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
row.cells[idx + 1].innerText = Math.round(pSum).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
||||||
|
grandTotals[map.css] += pSum;
|
||||||
|
globalSum += pSum;
|
||||||
|
});
|
||||||
|
|
||||||
grandTotalAlex += alexSum;
|
row.cells[parentMap.length + 1].innerText = Math.round(globalSum).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
||||||
grandTotalLaia += laiaSum;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('grand_total_alex').innerText = Math.round(grandTotalAlex).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
let totalGrandGlobal = 0;
|
||||||
document.getElementById('grand_total_laia').innerText = Math.round(grandTotalLaia).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
parentMap.forEach(map => {
|
||||||
document.getElementById('grand_total_global').innerText = Math.round(grandTotalAlex + grandTotalLaia).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
const grandEl = document.getElementById('grand_total_' + map.css);
|
||||||
|
if(grandEl) grandEl.innerText = Math.round(grandTotals[map.css]).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
||||||
|
totalGrandGlobal += grandTotals[map.css];
|
||||||
|
});
|
||||||
|
|
||||||
|
const globGrandEl = document.getElementById('grand_total_global');
|
||||||
|
if(globGrandEl) globGrandEl.innerText = Math.round(totalGrandGlobal).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveData(action, data) {
|
function saveData(action, data) {
|
||||||
@@ -687,19 +636,19 @@ function saveData(action, data) {
|
|||||||
fetch('modules/budget/includes/api/save-budget.php', { method: 'POST', body: formData });
|
fetch('modules/budget/includes/api/save-budget.php', { method: 'POST', body: formData });
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateTransfers(person, month) {
|
function validateTransfers(personCss, month) {
|
||||||
const msg = window.I18N['bud_prev_confirm_transfers'].replace('%p', person).replace('%m', month);
|
const msg = window.I18N['bud_prev_confirm_transfers'].replace('%p', personCss).replace('%m', month);
|
||||||
if (!confirm(msg)) return;
|
if (!confirm(msg)) return;
|
||||||
|
|
||||||
|
const parentMap = window.CONFIG.parentMapping.find(m => m.css === personCss);
|
||||||
|
const dbPersonName = parentMap ? parentMap.name : personCss;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('action', 'validate_transfers');
|
formData.append('action', 'validate_transfers');
|
||||||
formData.append('person', person);
|
formData.append('person', dbPersonName);
|
||||||
formData.append('month_date', month);
|
formData.append('month_date', month);
|
||||||
|
|
||||||
fetch('modules/budget/includes/api/save-budget.php', {
|
fetch('modules/budget/includes/api/save-budget.php', { method: 'POST', body: formData })
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if(data.success) window.location.reload();
|
if(data.success) window.location.reload();
|
||||||
@@ -715,19 +664,11 @@ function saveGenericNote(noteType, refId, content) {
|
|||||||
formData.append('reference_id', refId);
|
formData.append('reference_id', refId);
|
||||||
formData.append('content', content);
|
formData.append('content', content);
|
||||||
|
|
||||||
fetch('modules/budget/includes/api/save-budget.php', {
|
fetch('modules/budget/includes/api/save-budget.php', { method: 'POST', body: formData })
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(async r => {
|
.then(async r => {
|
||||||
const text = await r.text();
|
const text = await r.text();
|
||||||
if (!r.ok) throw new Error(`Erreur HTTP ${r.status} : ${text}`);
|
if (!r.ok) throw new Error(`Erreur HTTP ${r.status}`);
|
||||||
if (!text) throw new Error("Le serveur a renvoyé une réponse vide.");
|
|
||||||
try {
|
|
||||||
return JSON.parse(text);
|
return JSON.parse(text);
|
||||||
} catch(e) {
|
|
||||||
throw new Error("Réponse inattendue (non JSON) : " + text);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if(data.success) {
|
if(data.success) {
|
||||||
@@ -737,13 +678,10 @@ function saveGenericNote(noteType, refId, content) {
|
|||||||
setTimeout(() => indicator.style.opacity = '0', 2000);
|
setTimeout(() => indicator.style.opacity = '0', 2000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert("Erreur lors de la sauvegarde : " + data.error);
|
alert("Erreur: " + data.error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => alert(e.message));
|
||||||
console.error('Erreur technique:', e);
|
|
||||||
alert(e.message);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let isSumModeActive = false;
|
let isSumModeActive = false;
|
||||||
@@ -751,10 +689,8 @@ let selectedElementsForSum = new Set();
|
|||||||
|
|
||||||
function toggleSumMode() {
|
function toggleSumMode() {
|
||||||
isSumModeActive = !isSumModeActive;
|
isSumModeActive = !isSumModeActive;
|
||||||
|
|
||||||
const fab = document.getElementById('fabSumMode');
|
const fab = document.getElementById('fabSumMode');
|
||||||
const resultBar = document.getElementById('sumResultBar');
|
const resultBar = document.getElementById('sumResultBar');
|
||||||
|
|
||||||
if (isSumModeActive) {
|
if (isSumModeActive) {
|
||||||
fab.classList.add('active');
|
fab.classList.add('active');
|
||||||
document.body.classList.add('sum-mode-active');
|
document.body.classList.add('sum-mode-active');
|
||||||
@@ -764,7 +700,6 @@ function toggleSumMode() {
|
|||||||
fab.classList.remove('active');
|
fab.classList.remove('active');
|
||||||
document.body.classList.remove('sum-mode-active');
|
document.body.classList.remove('sum-mode-active');
|
||||||
resultBar.classList.remove('visible');
|
resultBar.classList.remove('visible');
|
||||||
|
|
||||||
selectedElementsForSum.forEach(el => el.classList.remove('sum-selected'));
|
selectedElementsForSum.forEach(el => el.classList.remove('sum-selected'));
|
||||||
selectedElementsForSum.clear();
|
selectedElementsForSum.clear();
|
||||||
}
|
}
|
||||||
@@ -772,53 +707,34 @@ function toggleSumMode() {
|
|||||||
|
|
||||||
function extractNumberFromText(text) {
|
function extractNumberFromText(text) {
|
||||||
if (!text) return 0;
|
if (!text) return 0;
|
||||||
const cleanText = text.replace(',', '.').replace(/[^\d.-]/g, '');
|
return parseFloat(text.replace(',', '.').replace(/[^\d.-]/g, '')) || 0;
|
||||||
return parseFloat(cleanText) || 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSumResult() {
|
function updateSumResult() {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
selectedElementsForSum.forEach(el => {
|
selectedElementsForSum.forEach(el => {
|
||||||
let val = 0;
|
total += (el.tagName === 'INPUT') ? (parseFloat(el.value) || 0) : extractNumberFromText(el.innerText);
|
||||||
if (el.tagName === 'INPUT') {
|
|
||||||
val = parseFloat(el.value) || 0;
|
|
||||||
} else {
|
|
||||||
val = extractNumberFromText(el.innerText);
|
|
||||||
}
|
|
||||||
total += val;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('sumResultValue').innerText = Math.round(total).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
document.getElementById('sumResultValue').innerText = Math.round(total).toLocaleString(window.appLang) + ' ' + window.CONFIG.CURRENCY;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteCategory(id) {
|
async function deleteCategory(id) {
|
||||||
if (!confirm(tr('bud_prev_confirm_del_line'))) return;
|
if (!confirm(window.I18N['bud_prev_confirm_del_line'])) return;
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('action', 'delete_category');
|
formData.append('action', 'delete_category');
|
||||||
formData.append('id', id);
|
formData.append('id', id);
|
||||||
formData.append('ajax', '1'); //
|
formData.append('ajax', '1');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pachaFetch('modules/budget/includes/api/save-budget.php', {
|
const result = await pachaFetch('modules/budget/includes/api/save-budget.php', { method: 'POST', body: formData });
|
||||||
method: 'POST',
|
if (result.success) window.location.reload();
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
} catch(e) { console.error(e); }
|
} catch(e) { console.error(e); }
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('click', function(e) {
|
document.addEventListener('click', function(e) {
|
||||||
if (!isSumModeActive) return;
|
if (!isSumModeActive) return;
|
||||||
|
|
||||||
const targetElement = e.target.closest('input[type="number"], .sum-target');
|
const targetElement = e.target.closest('input[type="number"], .sum-target');
|
||||||
|
|
||||||
if (targetElement) {
|
if (targetElement) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (selectedElementsForSum.has(targetElement)) {
|
if (selectedElementsForSum.has(targetElement)) {
|
||||||
selectedElementsForSum.delete(targetElement);
|
selectedElementsForSum.delete(targetElement);
|
||||||
targetElement.classList.remove('sum-selected');
|
targetElement.classList.remove('sum-selected');
|
||||||
@@ -826,36 +742,24 @@ document.addEventListener('click', function(e) {
|
|||||||
selectedElementsForSum.add(targetElement);
|
selectedElementsForSum.add(targetElement);
|
||||||
targetElement.classList.add('sum-selected');
|
targetElement.classList.add('sum-selected');
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSumResult();
|
updateSumResult();
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', recalcAllAllocations);
|
document.addEventListener('DOMContentLoaded', recalcAllAllocations);
|
||||||
|
|
||||||
// --- INTERCEPTION ASYNCHRONE DES FORMULAIRES DE MODALES ---
|
|
||||||
document.querySelectorAll('#addCatModal form, #editCatModal form').forEach(form => {
|
document.querySelectorAll('#addCatModal form, #editCatModal form').forEach(form => {
|
||||||
form.addEventListener('submit', async (e) => {
|
form.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
const originalText = submitBtn.innerText;
|
const originalText = submitBtn.innerText;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
submitBtn.innerText = '⏳ ...';
|
submitBtn.innerText = '⏳ ...';
|
||||||
|
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
formData.append('ajax', '1');
|
formData.append('ajax', '1');
|
||||||
|
|
||||||
const actionUrl = form.getAttribute('action');
|
const actionUrl = form.getAttribute('action');
|
||||||
|
const result = await pachaFetch(actionUrl, { method: 'POST', body: formData });
|
||||||
// On utilise pachaFetch au lieu de fetch
|
|
||||||
const result = await pachaFetch(actionUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
form.closest('.pf-modal').style.display = 'none';
|
form.closest('.pf-modal').style.display = 'none';
|
||||||
document.body.classList.remove('no-scroll');
|
document.body.classList.remove('no-scroll');
|
||||||
@@ -864,7 +768,6 @@ document.querySelectorAll('#addCatModal form, #editCatModal form').forEach(form
|
|||||||
alert((window.I18N['bud_err_tech'] || 'Erreur') + " : " + (result.error || "Inconnue"));
|
alert((window.I18N['bud_err_tech'] || 'Erreur') + " : " + (result.error || "Inconnue"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Erreur Fetch Modale:", error);
|
|
||||||
alert("Une erreur technique est survenue.");
|
alert("Une erreur technique est survenue.");
|
||||||
} finally {
|
} finally {
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
|||||||
@@ -5,32 +5,42 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const CONGE_TYPES = ["OFF_CAROLE", "EXTRA_OFF_CAROLE"];
|
const CONGE_TYPES = ["OFF_CAROLE", "EXTRA_OFF_CAROLE"];
|
||||||
const GUARDE_TYPES = ["CENTRE", "AVIS"];
|
const GUARDE_TYPES = ["CENTRE", "AVIS"];
|
||||||
const PEP_TYPES = ["PEP_SICK"];
|
const PEP_TYPES = ["PEP_SICK"];
|
||||||
const FAMILY = {
|
|
||||||
ALEX: { id: 2, prefix: "alex" },
|
// 🟢 Liste globale dynamique des personnes (fini les IDs en dur !)
|
||||||
LAIA: { id: 3, prefix: "laia" },
|
const peopleList = window.CONFIG.PEOPLE || [];
|
||||||
};
|
|
||||||
|
// On filtre pour ne garder que les parents pour la logique principale des congés
|
||||||
|
const parents = peopleList.filter(
|
||||||
|
(p) => p.role && p.role.toLowerCase() === "parent",
|
||||||
|
);
|
||||||
|
|
||||||
const LEAVES_CONFIG = {
|
const LEAVES_CONFIG = {
|
||||||
CP: {
|
CP: {
|
||||||
startMonth: 8, // Le cycle commence en août (après la tolérance de juillet)
|
startMonth: 8,
|
||||||
defaultBalance: 25,
|
defaultBalance: 25,
|
||||||
},
|
},
|
||||||
JRA: {
|
JRA: {
|
||||||
// Tu pourras ajouter les années suivantes ici
|
|
||||||
yearlyTotals: {
|
yearlyTotals: {
|
||||||
2024: 10, // ex: 0.83 * 12 arrondi
|
2024: 10,
|
||||||
2025: 10,
|
2025: 10,
|
||||||
2026: 11, // ex: 0.9 * 12 arrondi
|
2026: 11,
|
||||||
},
|
},
|
||||||
defaultBalance: 10,
|
defaultBalance: 10,
|
||||||
toleranceMonths: 2, // Janvier et Février
|
toleranceMonths: 2,
|
||||||
maxReport: 2,
|
maxReport: 2,
|
||||||
},
|
},
|
||||||
JA: {
|
JA: {}, // Ce tableau est maintenant vide par défaut, on le remplit dynamiquement juste en dessous
|
||||||
[FAMILY.ALEX.id]: { startMonth: 4, startDay: 29, defaultBalance: 4 },
|
|
||||||
[FAMILY.LAIA.id]: { startMonth: 10, startDay: 1, defaultBalance: 4 }, // Date de Laia à adapter
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 🟢 INITIALISATION DYNAMIQUE DES JOURS D'ANNIVERSAIRE (JA)
|
||||||
|
parents.forEach((parent) => {
|
||||||
|
LEAVES_CONFIG.JA[parent.id] = {
|
||||||
|
startMonth: 4, // Mois par défaut (à faire évoluer en BDD au Niveau 3)
|
||||||
|
startDay: 29, // Jour par défaut
|
||||||
|
defaultBalance: 4,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
class FamilyCalendar {
|
class FamilyCalendar {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.planningBody = document.getElementById("planningBody");
|
this.planningBody = document.getElementById("planningBody");
|
||||||
@@ -455,12 +465,17 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const d = new Date(l.leave_date + "T00:00:00");
|
const d = new Date(l.leave_date + "T00:00:00");
|
||||||
if (d >= w.dayDates.mon && d <= w.dayDates.fri) {
|
if (d >= w.dayDates.mon && d <= w.dayDates.fri) {
|
||||||
const dur = parseFloat(l.duration) || 1;
|
const dur = parseFloat(l.duration) || 1;
|
||||||
const prefix =
|
|
||||||
l.person_id === FAMILY.ALEX.id
|
// 🟢 Correspondance dynamique pour le stockage des totaux de la semaine
|
||||||
? FAMILY.ALEX.prefix
|
let prefix = null;
|
||||||
: l.person_id === FAMILY.LAIA.id
|
if (parents[0] && parseInt(l.person_id) === parseInt(parents[0].id))
|
||||||
? FAMILY.LAIA.prefix
|
prefix = "alex";
|
||||||
: null;
|
else if (
|
||||||
|
parents[1] &&
|
||||||
|
parseInt(l.person_id) === parseInt(parents[1].id)
|
||||||
|
)
|
||||||
|
prefix = "laia";
|
||||||
|
|
||||||
if (prefix) w.totals[`${prefix}${l.leave_type}`] += dur;
|
if (prefix) w.totals[`${prefix}${l.leave_type}`] += dur;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -479,17 +494,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
calculateMonthlyBalances() {
|
calculateMonthlyBalances() {
|
||||||
const balances = {
|
const balances = {};
|
||||||
[FAMILY.ALEX.id]: { CP: {}, JRA: {}, JA: {} },
|
// 🟢 On initialise le tableau des soldes pour chaque parent trouvé
|
||||||
[FAMILY.LAIA.id]: { CP: {}, JRA: {}, JA: {} },
|
parents.forEach((p) => {
|
||||||
};
|
balances[p.id] = { CP: {}, JRA: {}, JA: {} };
|
||||||
|
});
|
||||||
|
|
||||||
// Liste des mois actuellement affichés dans le planning
|
|
||||||
const ymSet = new Set();
|
const ymSet = new Set();
|
||||||
this.weeks.forEach((w) => ymSet.add(w.monthKey));
|
this.weeks.forEach((w) => ymSet.add(w.monthKey));
|
||||||
const ymList = Array.from(ymSet).sort();
|
const ymList = Array.from(ymSet).sort();
|
||||||
|
|
||||||
// Pré-calcul de l'utilisation par mois
|
|
||||||
const usageByMonth = {};
|
const usageByMonth = {};
|
||||||
this.leaves.forEach((l) => {
|
this.leaves.forEach((l) => {
|
||||||
const pid = l.person_id;
|
const pid = l.person_id;
|
||||||
@@ -502,7 +516,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
(usageByMonth[pid][type][ym] || 0) + parseFloat(l.duration);
|
(usageByMonth[pid][type][ym] || 0) + parseFloat(l.duration);
|
||||||
});
|
});
|
||||||
|
|
||||||
[FAMILY.ALEX.id, FAMILY.LAIA.id].forEach((pid) => {
|
parents.forEach((parent) => {
|
||||||
|
const pid = parent.id;
|
||||||
["CP", "JRA", "JA"].forEach((type) => {
|
["CP", "JRA", "JA"].forEach((type) => {
|
||||||
ymList.forEach((ym) => {
|
ymList.forEach((ym) => {
|
||||||
const [currYear, currMonth] = ym.split("-").map(Number);
|
const [currYear, currMonth] = ym.split("-").map(Number);
|
||||||
@@ -510,8 +525,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
let cycleStartStr = "";
|
let cycleStartStr = "";
|
||||||
let initialBalance = 0;
|
let initialBalance = 0;
|
||||||
|
|
||||||
// --- 1. RECHERCHE D'UN CORRECTIF MANUEL (SNAPSHOT) ---
|
|
||||||
// On cherche le snapshot le plus récent qui est inférieur ou égal au mois en cours de calcul
|
|
||||||
const latestSnapshot = (this.leaveSnapshots || [])
|
const latestSnapshot = (this.leaveSnapshots || [])
|
||||||
.filter(
|
.filter(
|
||||||
(s) =>
|
(s) =>
|
||||||
@@ -519,18 +532,14 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
s.leave_type == type &&
|
s.leave_type == type &&
|
||||||
s.snapshot_date.substring(0, 7) <= ym,
|
s.snapshot_date.substring(0, 7) <= ym,
|
||||||
)
|
)
|
||||||
// On trie du plus récent au plus ancien pour prendre le premier
|
|
||||||
.sort((a, b) =>
|
.sort((a, b) =>
|
||||||
b.snapshot_date.localeCompare(a.snapshot_date),
|
b.snapshot_date.localeCompare(a.snapshot_date),
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
if (latestSnapshot) {
|
if (latestSnapshot) {
|
||||||
// Si on trouve un correctif, il devient notre nouveau "point zéro"
|
|
||||||
cycleStartStr = latestSnapshot.snapshot_date.substring(0, 7);
|
cycleStartStr = latestSnapshot.snapshot_date.substring(0, 7);
|
||||||
initialBalance = parseFloat(latestSnapshot.remaining_balance); // Utilisation de VOTRE nom de colonne
|
initialBalance = parseFloat(latestSnapshot.remaining_balance);
|
||||||
}
|
} else {
|
||||||
// --- 2. SINON, CALCUL CLASSIQUE PAR DÉFAUT ---
|
|
||||||
else {
|
|
||||||
if (type === "CP") {
|
if (type === "CP") {
|
||||||
const refYear =
|
const refYear =
|
||||||
currMonth >= LEAVES_CONFIG.CP.startMonth
|
currMonth >= LEAVES_CONFIG.CP.startMonth
|
||||||
@@ -552,7 +561,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
LEAVES_CONFIG.JRA.yearlyTotals[currYear] ||
|
LEAVES_CONFIG.JRA.yearlyTotals[currYear] ||
|
||||||
LEAVES_CONFIG.JRA.defaultBalance;
|
LEAVES_CONFIG.JRA.defaultBalance;
|
||||||
|
|
||||||
// Logique de report du reliquat de l'année N-1
|
|
||||||
if (currMonth <= LEAVES_CONFIG.JRA.toleranceMonths) {
|
if (currMonth <= LEAVES_CONFIG.JRA.toleranceMonths) {
|
||||||
const prevYear = currYear - 1;
|
const prevYear = currYear - 1;
|
||||||
const prevInitial =
|
const prevInitial =
|
||||||
@@ -569,14 +577,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
0,
|
0,
|
||||||
prevInitial - usedPrevYear,
|
prevInitial - usedPrevYear,
|
||||||
);
|
);
|
||||||
// Ajout du report plafonné à 2 jours
|
|
||||||
initialBalance += Math.min(
|
initialBalance += Math.min(
|
||||||
remainingPrevYear,
|
remainingPrevYear,
|
||||||
LEAVES_CONFIG.JRA.maxReport,
|
LEAVES_CONFIG.JRA.maxReport,
|
||||||
);
|
);
|
||||||
console.log(
|
|
||||||
`[JRA] Personne ${pid}, Année ${currYear}: Report de ${Math.min(remainingPrevYear, LEAVES_CONFIG.JRA.maxReport)}j inclus.`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (type === "JA") {
|
} else if (type === "JA") {
|
||||||
const configJA = LEAVES_CONFIG.JA[pid];
|
const configJA = LEAVES_CONFIG.JA[pid];
|
||||||
@@ -598,11 +602,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- 3. DÉDUCTION DES CONGÉS PRIS DEPUIS LE POINT ZÉRO ---
|
|
||||||
let usedBeforeCurrentMonth = 0;
|
let usedBeforeCurrentMonth = 0;
|
||||||
|
|
||||||
Object.keys(usageByMonth[pid]?.[type] || {}).forEach((usedYm) => {
|
Object.keys(usageByMonth[pid]?.[type] || {}).forEach((usedYm) => {
|
||||||
// On ne déduit que ce qui a été posé entre le début du cycle (ou la date du snapshot) et le mois en cours
|
|
||||||
if (usedYm >= cycleStartStr && usedYm < ym) {
|
if (usedYm >= cycleStartStr && usedYm < ym) {
|
||||||
usedBeforeCurrentMonth += usageByMonth[pid][type][usedYm];
|
usedBeforeCurrentMonth += usageByMonth[pid][type][usedYm];
|
||||||
}
|
}
|
||||||
@@ -688,10 +689,28 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
|
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
|
||||||
if (dayLeaves.length) {
|
if (dayLeaves.length) {
|
||||||
let html = `<div style="position:absolute; bottom:0; left:0; width:100%; font-size:9px; line-height:1; display:flex; justify-content:center; gap:2px; pointer-events:none;">`;
|
let html = `<div style="position:absolute; bottom:0; left:0; width:100%; font-size:9px; line-height:1; display:flex; justify-content:center; gap:2px; pointer-events:none;">`;
|
||||||
if (dayLeaves.some((l) => l.person_id === window.CONFIG.ID_ALEX))
|
|
||||||
html += `<span style="color:#0f766e; font-weight:800;">A</span>`;
|
const colors = [
|
||||||
if (dayLeaves.some((l) => l.person_id === window.CONFIG.ID_LAIA))
|
"#0f766e",
|
||||||
html += `<span style="color:#b45309; font-weight:800;">L</span>`;
|
"#b45309",
|
||||||
|
"#047857",
|
||||||
|
"#4338ca",
|
||||||
|
"#b91c1c",
|
||||||
|
];
|
||||||
|
if (window.CONFIG.PEOPLE && window.CONFIG.PEOPLE.length > 0) {
|
||||||
|
window.CONFIG.PEOPLE.forEach((person, index) => {
|
||||||
|
if (
|
||||||
|
dayLeaves.some(
|
||||||
|
(l) => parseInt(l.person_id) === parseInt(person.id),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const color = colors[index % colors.length];
|
||||||
|
const initial = person.name.charAt(0).toUpperCase();
|
||||||
|
html += `<span style="color:${color}; font-weight:800; margin: 0 1px;">${initial}</span>`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
td.innerHTML += html;
|
td.innerHTML += html;
|
||||||
}
|
}
|
||||||
@@ -731,8 +750,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
tr.appendChild(tdUse);
|
tr.appendChild(tdUse);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
renderPersonCols(FAMILY.ALEX.id, `col-${FAMILY.ALEX.prefix}`);
|
parents.forEach((parent, index) => {
|
||||||
renderPersonCols(FAMILY.LAIA.id, `col-${FAMILY.LAIA.prefix}`);
|
const cssPrefix = index % 2 === 0 ? "col-alex" : "col-laia";
|
||||||
|
renderPersonCols(parent.id, cssPrefix);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.planningBody.appendChild(tr);
|
this.planningBody.appendChild(tr);
|
||||||
});
|
});
|
||||||
@@ -845,9 +866,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
container.style.display = "flex";
|
container.style.display = "flex";
|
||||||
let html = "";
|
let html = "";
|
||||||
|
|
||||||
[FAMILY.ALEX, FAMILY.LAIA].forEach((person) => {
|
parents.forEach((person) => {
|
||||||
html += `<div class="fc-minimal-balance-card">
|
html += `<div class="fc-minimal-balance-card">
|
||||||
<strong class="${person.prefix}">${person.prefix.toUpperCase()}</strong>
|
<strong class="col-alex">${person.name.toUpperCase()}</strong>
|
||||||
<div class="fc-minimal-chips">`;
|
<div class="fc-minimal-chips">`;
|
||||||
|
|
||||||
["CP", "JRA", "JA"].forEach((type) => {
|
["CP", "JRA", "JA"].forEach((type) => {
|
||||||
@@ -989,10 +1010,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
|
const dayLeaves = this.leaves.filter((l) => l.leave_date === iso);
|
||||||
if (dayLeaves.length) {
|
if (dayLeaves.length) {
|
||||||
content += `<div style="position:absolute; bottom:2px; left:2px; font-size:10px; font-weight:bold;">`;
|
content += `<div style="position:absolute; bottom:2px; left:2px; font-size:10px; font-weight:bold;">`;
|
||||||
if (dayLeaves.some((l) => l.person_id === 2))
|
|
||||||
content += `<span style="color:#0f766e">A</span> `;
|
parents.forEach((parent, index) => {
|
||||||
if (dayLeaves.some((l) => l.person_id === 3))
|
if (
|
||||||
content += `<span style="color:#b45309">L</span>`;
|
dayLeaves.some(
|
||||||
|
(l) => parseInt(l.person_id) === parseInt(parent.id),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
const color = index % 2 === 0 ? "#0f766e" : "#b45309";
|
||||||
|
const initial = parent.name.charAt(0).toUpperCase();
|
||||||
|
content += `<span style="color:${color}">${initial}</span> `;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
content += `</div>`;
|
content += `</div>`;
|
||||||
}
|
}
|
||||||
content += `</div>`;
|
content += `</div>`;
|
||||||
@@ -1557,7 +1587,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
if (dates.includes(e.date)) activeEvents.add(e.type);
|
if (dates.includes(e.date)) activeEvents.add(e.type);
|
||||||
});
|
});
|
||||||
|
|
||||||
const activeLeaves = { 2: new Set(), 3: new Set() };
|
const activeLeaves = {};
|
||||||
|
parents.forEach((p) => (activeLeaves[p.id] = new Set()));
|
||||||
|
|
||||||
this.leaves.forEach((l) => {
|
this.leaves.forEach((l) => {
|
||||||
if (dates.includes(l.leave_date)) {
|
if (dates.includes(l.leave_date)) {
|
||||||
if (activeLeaves[l.person_id])
|
if (activeLeaves[l.person_id])
|
||||||
@@ -1623,32 +1655,33 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
<button class="fc-menu-btn ${getBtnClass("PEP_SICK")}" data-action="add" data-type="PEP_SICK" style="width:100%">${getBtnIcon("PEP_SICK")}${tr("leg_pep_sick")} 🤒</button>
|
<button class="fc-menu-btn ${getBtnClass("PEP_SICK")}" data-action="add" data-type="PEP_SICK" style="width:100%">${getBtnIcon("PEP_SICK")}${tr("leg_pep_sick")} 🤒</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// 5. Section Enfants
|
// 5. Section Parents (Congés)
|
||||||
html += `<div class="fc-menu-section">
|
html += `<div class="fc-menu-section">
|
||||||
${buildHeader(tr("fc_menu_kids_leaves"), null, null)}
|
${buildHeader(tr("fc_menu_kids_leaves"), null, null)}
|
||||||
<div class="fc-menu-leaves-table">
|
<div class="fc-menu-leaves-table">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>`;
|
||||||
<th>
|
|
||||||
|
// En-têtes dynamiques (Noms + Bouton Poubelle)
|
||||||
|
parents.forEach((parent) => {
|
||||||
|
html += `<th>
|
||||||
<div class="fc-th-inline">
|
<div class="fc-th-inline">
|
||||||
Alex <button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="2" title="${tr("fc_clear")} Alex">${trashSvg}</button>
|
${parent.name} <button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="${parent.id}" title="${tr("fc_clear")} ${parent.name}">${trashSvg}</button>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>`;
|
||||||
<th>
|
});
|
||||||
<div class="fc-th-inline">
|
html += ` </tr>
|
||||||
Laia <button class="fc-menu-clear-icon fc-menu-btn-th" data-action="clear-leaves-person" data-pid="3" title="${tr("fc_clear")} Laia">${trashSvg}</button>
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>`;
|
<tbody>`;
|
||||||
|
|
||||||
|
// Lignes de congés (CP, JRA, JA)
|
||||||
["CP", "JRA", "JA"].forEach((t) => {
|
["CP", "JRA", "JA"].forEach((t) => {
|
||||||
html += `<tr>
|
html += `<tr>`;
|
||||||
<td><button class="fc-menu-btn ${getBtnClass(t, 2)}" data-action="add-leave" data-pid="2" data-type="${t}">${getBtnIcon(t, 2)}${t}</button></td>
|
parents.forEach((parent) => {
|
||||||
<td><button class="fc-menu-btn ${getBtnClass(t, 3)}" data-action="add-leave" data-pid="3" data-type="${t}">${getBtnIcon(t, 3)}${t}</button></td>
|
html += `<td><button class="fc-menu-btn ${getBtnClass(t, parent.id)}" data-action="add-leave" data-pid="${parent.id}" data-type="${t}">${getBtnIcon(t, parent.id)}${t}</button></td>`;
|
||||||
</tr>`;
|
});
|
||||||
|
html += `</tr>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
html += ` </tbody>
|
html += ` </tbody>
|
||||||
@@ -1743,23 +1776,19 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (action === "clear-leaves") {
|
} else if (action === "clear-leaves") {
|
||||||
|
if (window.CONFIG.PEOPLE && window.CONFIG.PEOPLE.length > 0) {
|
||||||
|
for (const person of window.CONFIG.PEOPLE) {
|
||||||
await this.postApi(
|
await this.postApi(
|
||||||
"/modules/family-calendar/includes/api/manage-leaf.php",
|
"/modules/family-calendar/includes/api/manage-leaf.php",
|
||||||
{
|
{
|
||||||
action: "bulk_delete_day_person",
|
action: "bulk_delete_day_person",
|
||||||
dates,
|
dates,
|
||||||
person_id: window.CONFIG.ID_ALEX,
|
person_id: person.id,
|
||||||
},
|
|
||||||
);
|
|
||||||
await this.postApi(
|
|
||||||
"/modules/family-calendar/includes/api/manage-leaf.php",
|
|
||||||
{
|
|
||||||
action: "bulk_delete_day_person",
|
|
||||||
dates,
|
|
||||||
person_id: window.CONFIG.ID_LAIA,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.refreshAllData();
|
await this.refreshAllData();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -51,3 +51,4 @@
|
|||||||
[2026-05-06T20:38:24+02:00] RAW INPUT: [{"date":"2026-05-08","type":"AVIS","duration":1,"person":"Carole"}]
|
[2026-05-06T20:38:24+02:00] RAW INPUT: [{"date":"2026-05-08","type":"AVIS","duration":1,"person":"Carole"}]
|
||||||
[2026-05-06T20:43:43+02:00] RAW INPUT: [{"date":"2026-05-06","type":"AVIS","duration":1,"person":"Carole"}]
|
[2026-05-06T20:43:43+02:00] RAW INPUT: [{"date":"2026-05-06","type":"AVIS","duration":1,"person":"Carole"}]
|
||||||
[2026-05-06T20:44:36+02:00] RAW INPUT: [{"date":"2026-05-06","type":"PEP_SICK","duration":1,"person":"Carole"}]
|
[2026-05-06T20:44:36+02:00] RAW INPUT: [{"date":"2026-05-06","type":"PEP_SICK","duration":1,"person":"Carole"}]
|
||||||
|
[2026-05-20T16:40:17+02:00] RAW INPUT: [{"date":"2026-05-19","type":"OFF_CAROLE","duration":1,"person":"Carole"}]
|
||||||
|
|||||||
+7
-7
@@ -13,10 +13,13 @@ CREATE TABLE IF NOT EXISTS pf_users (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
-- ─── Personnes ────────────────────────────────────────────────────────────────
|
-- ─── Personnes ────────────────────────────────────────────────────────────────
|
||||||
CREATE TABLE IF NOT EXISTS pf_people (
|
CREATE TABLE `pf_people` (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
name VARCHAR(100) NOT NULL
|
`name` varchar(50) NOT NULL,
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
`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 ──────────────────────────────────────────────────────
|
-- ─── Calendrier familial ──────────────────────────────────────────────────────
|
||||||
CREATE TABLE IF NOT EXISTS pf_events (
|
CREATE TABLE IF NOT EXISTS pf_events (
|
||||||
@@ -232,9 +235,6 @@ SET FOREIGN_KEY_CHECKS = 1;
|
|||||||
INSERT IGNORE INTO pf_users (id, username, password_hash, display_name)
|
INSERT IGNORE INTO pf_users (id, username, password_hash, display_name)
|
||||||
VALUES (1, 'admin', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'Admin');
|
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');
|
|
||||||
|
|
||||||
-- ─── Calendar iOS ─────────────────────────────────────────────────────────────
|
-- ─── Calendar iOS ─────────────────────────────────────────────────────────────
|
||||||
CREATE TABLE IF NOT EXISTS pf_calendar_events (
|
CREATE TABLE IF NOT EXISTS pf_calendar_events (
|
||||||
|
|||||||
+42
-15
@@ -69,13 +69,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🟢 ENRICHISSEMENT : Prise en compte de la couleur du profil
|
||||||
if ($action === 'update_profile') {
|
if ($action === 'update_profile') {
|
||||||
$display_name = trim($_POST['display_name'] ?? '');
|
$display_name = trim($_POST['display_name'] ?? '');
|
||||||
|
$color = trim($_POST['color'] ?? '#0891b2');
|
||||||
|
|
||||||
if (!$display_name) {
|
if (!$display_name) {
|
||||||
$error = "Le prénom ne peut pas être vide.";
|
$error = "Le prénom ne peut pas être vide.";
|
||||||
} else {
|
} else {
|
||||||
|
// MÀJ dans la base Meta (users)
|
||||||
$meta_pdo->prepare("UPDATE users SET display_name = ? WHERE id = ?")
|
$meta_pdo->prepare("UPDATE users SET display_name = ? WHERE id = ?")
|
||||||
->execute([$display_name, $user_id]);
|
->execute([$display_name, $user_id]);
|
||||||
|
|
||||||
|
// MÀJ dans la base locale Foyer (pf_people) via le user_id mappé
|
||||||
|
require_once __DIR__ . '/includes/db.php';
|
||||||
|
if (isset($pdo)) {
|
||||||
|
$stmtPeopleColor = $pdo->prepare("UPDATE pf_people SET color = ? WHERE user_id = ?");
|
||||||
|
$stmtPeopleColor->execute([$color, $user_id]);
|
||||||
|
}
|
||||||
|
|
||||||
$_SESSION['user']['display_name'] = $display_name;
|
$_SESSION['user']['display_name'] = $display_name;
|
||||||
$success = "Profil mis à jour.";
|
$success = "Profil mis à jour.";
|
||||||
}
|
}
|
||||||
@@ -129,7 +141,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$error = "Format non supporté (jpg, png, webp).";
|
$error = "Format non supporté (jpg, png, webp).";
|
||||||
} else {
|
} else {
|
||||||
$dest = $upload_dir . 'home_bg_' . $family_id . '.' . $ext;
|
$dest = $upload_dir . 'home_bg_' . $family_id . '.' . $ext;
|
||||||
// Supprimer anciens fichiers de ce family
|
|
||||||
foreach (glob($upload_dir . 'home_bg_' . $family_id . '.*') as $old) @unlink($old);
|
foreach (glob($upload_dir . 'home_bg_' . $family_id . '.*') as $old) @unlink($old);
|
||||||
if (move_uploaded_file($file['tmp_name'], $dest)) {
|
if (move_uploaded_file($file['tmp_name'], $dest)) {
|
||||||
$success = "Image d'accueil mise à jour.";
|
$success = "Image d'accueil mise à jour.";
|
||||||
@@ -225,9 +236,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── Chargement données ───────────────────────────────────────────────────────
|
// ─── Chargement données ───────────────────────────────────────────────────────
|
||||||
$user = $meta_pdo->prepare("SELECT * FROM users WHERE id = ?");
|
|
||||||
$user->execute([$user_id]);
|
// 1. On charge la base locale AVANT pour que son $user n'écrase pas le nôtre
|
||||||
$user = $user->fetch();
|
if ($family_id) {
|
||||||
|
require_once __DIR__ . '/includes/db.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Maintenant on peut charger ton profil en toute sécurité
|
||||||
|
$stmtUser = $meta_pdo->prepare("SELECT * FROM users WHERE id = ?");
|
||||||
|
$stmtUser->execute([$user_id]);
|
||||||
|
$user = $stmtUser->fetch();
|
||||||
|
|
||||||
|
// 3. Récupération de la couleur actuelle depuis pf_people
|
||||||
|
$user_color = '#0891b2'; // Fallback par défaut
|
||||||
|
if ($family_id && isset($pdo)) {
|
||||||
|
$stmtColorFetch = $pdo->prepare("SELECT color FROM pf_people WHERE user_id = ?");
|
||||||
|
$stmtColorFetch->execute([$user_id]);
|
||||||
|
$fetched_color = $stmtColorFetch->fetchColumn();
|
||||||
|
if ($fetched_color) {
|
||||||
|
$user_color = $fetched_color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$family = null;
|
$family = null;
|
||||||
$members = [];
|
$members = [];
|
||||||
@@ -276,7 +305,6 @@ require __DIR__ . '/header.php';
|
|||||||
<div class="pf-alert pf-alert--error">✗ <?= htmlspecialchars($error) ?></div>
|
<div class="pf-alert pf-alert--error">✗ <?= htmlspecialchars($error) ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- ── Langue ─────────────────────────────────────────────────────────── -->
|
|
||||||
<section class="pf-panel-card">
|
<section class="pf-panel-card">
|
||||||
<h2 class="pf-card-h2">🌐 Langue / Language</h2>
|
<h2 class="pf-card-h2">🌐 Langue / Language</h2>
|
||||||
<form method="post" class="pf-lang-form">
|
<form method="post" class="pf-lang-form">
|
||||||
@@ -303,7 +331,6 @@ require __DIR__ . '/header.php';
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ── Modules ────────────────────────────────────────────────────────── -->
|
|
||||||
<?php if ($family_id): ?>
|
<?php if ($family_id): ?>
|
||||||
<section class="pf-panel-card">
|
<section class="pf-panel-card">
|
||||||
<h2 class="pf-card-h2 pf-card-h2--tight">🧩 Modules actifs</h2>
|
<h2 class="pf-card-h2 pf-card-h2--tight">🧩 Modules actifs</h2>
|
||||||
@@ -413,7 +440,6 @@ require __DIR__ . '/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ── Fond page d'accueil ───────────────────────────────────────────── -->
|
|
||||||
<?php if ($family_id):
|
<?php if ($family_id):
|
||||||
$bg_file = null;
|
$bg_file = null;
|
||||||
foreach (glob('/uploads/home_bg_' . $family_id . '.*') as $f) { $bg_file = $f; break; }
|
foreach (glob('/uploads/home_bg_' . $family_id . '.*') as $f) { $bg_file = $f; break; }
|
||||||
@@ -452,15 +478,12 @@ require __DIR__ . '/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
<!-- ── Famille ──────────────────────────────────────────────────────────── -->
|
|
||||||
<?php
|
<?php
|
||||||
$enabledMods = $_SESSION['enabled_modules'] ?? [];
|
$enabledMods = $_SESSION['enabled_modules'] ?? [];
|
||||||
|
|
||||||
// On cible précisément qui a besoin de quoi
|
|
||||||
$showCurrency = count(array_intersect(['budget', 'holidays', 'gifts', 'garage'], $enabledMods)) > 0;
|
$showCurrency = count(array_intersect(['budget', 'holidays', 'gifts', 'garage'], $enabledMods)) > 0;
|
||||||
$showZone = in_array('calendar', $enabledMods);
|
$showZone = in_array('calendar', $enabledMods);
|
||||||
|
|
||||||
// Si au moins un des champs doit être affiché, on rend la section visible
|
|
||||||
if ($family_id && ($showCurrency || $showZone)):
|
if ($family_id && ($showCurrency || $showZone)):
|
||||||
$currentCurrency = defined('CURRENCY') ? CURRENCY : '€';
|
$currentCurrency = defined('CURRENCY') ? CURRENCY : '€';
|
||||||
$currentZone = defined('ZONE_SCOLAIRE') ? ZONE_SCOLAIRE : 'C';
|
$currentZone = defined('ZONE_SCOLAIRE') ? ZONE_SCOLAIRE : 'C';
|
||||||
@@ -502,7 +525,6 @@ require __DIR__ . '/header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
||||||
<!-- ── Profil ──────────────────────────────────────────────────────────── -->
|
|
||||||
<section class="pf-panel-card">
|
<section class="pf-panel-card">
|
||||||
<h2 class="pf-card-h2">👤 Mon profil</h2>
|
<h2 class="pf-card-h2">👤 Mon profil</h2>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@@ -516,11 +538,19 @@ require __DIR__ . '/header.php';
|
|||||||
<label class="pf-label">Prénom / Pseudo affiché</label>
|
<label class="pf-label">Prénom / Pseudo affiché</label>
|
||||||
<input type="text" name="display_name" class="pf-input" value="<?= htmlspecialchars($user['display_name']) ?>" required>
|
<input type="text" name="display_name" class="pf-input" value="<?= htmlspecialchars($user['display_name']) ?>" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pf-form-group">
|
||||||
|
<label class="pf-label" for="profile_color">Couleur thématique de mon profil</label>
|
||||||
|
<div class="pf-color-picker-group">
|
||||||
|
<input type="color" name="color" id="profile_color" class="pf-input-color" value="<?= htmlspecialchars($user_color) ?>">
|
||||||
|
<span class="pf-muted-note">Cette couleur sera utilisée pour vos grilles de budget, calendriers et indicateurs personnels.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="pf-btn">Enregistrer</button>
|
<button type="submit" class="pf-btn">Enregistrer</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- ── Mot de passe ────────────────────────────────────────────────────── -->
|
|
||||||
<section class="pf-panel-card">
|
<section class="pf-panel-card">
|
||||||
<h2 class="pf-card-h2">🔒 Changer le mot de passe</h2>
|
<h2 class="pf-card-h2">🔒 Changer le mot de passe</h2>
|
||||||
<form method="post">
|
<form method="post">
|
||||||
@@ -543,7 +573,6 @@ require __DIR__ . '/header.php';
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<?php if ($family): ?>
|
<?php if ($family): ?>
|
||||||
<!-- ── Espace familial ─────────────────────────────────────────────────── -->
|
|
||||||
<section class="pf-panel-card">
|
<section class="pf-panel-card">
|
||||||
<h2 class="pf-card-h2">🏠 Mon espace familial</h2>
|
<h2 class="pf-card-h2">🏠 Mon espace familial</h2>
|
||||||
|
|
||||||
@@ -559,7 +588,6 @@ require __DIR__ . '/header.php';
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Code d'invitation -->
|
|
||||||
<div class="pf-dashed-panel">
|
<div class="pf-dashed-panel">
|
||||||
<p class="pf-muted-note pf-muted-note--tight">Code d'invitation — partagez-le pour inviter quelqu'un</p>
|
<p class="pf-muted-note pf-muted-note--tight">Code d'invitation — partagez-le pour inviter quelqu'un</p>
|
||||||
<div class="pf-invite-row">
|
<div class="pf-invite-row">
|
||||||
@@ -582,7 +610,6 @@ require __DIR__ . '/header.php';
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Membres -->
|
|
||||||
<h3 class="pf-members-heading">Membres (<?= count($members) ?>)</h3>
|
<h3 class="pf-members-heading">Membres (<?= count($members) ?>)</h3>
|
||||||
<div class="pf-stack-sm">
|
<div class="pf-stack-sm">
|
||||||
<?php foreach ($members as $m): ?>
|
<?php foreach ($members as $m): ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user