diff --git a/gift-list.php b/gift-list.php index c724f9d..81b7bdf 100644 --- a/gift-list.php +++ b/gift-list.php @@ -8,66 +8,58 @@ require_once __DIR__ . '/includes/i18n.php'; if (session_status() === PHP_SESSION_NONE) { session_start(); } -// --- 1. CONFIGURATION --- +// --- 1. CONFIGURATION DE BASE --- $year = (int)date('Y'); $pageTitle = tr('gift_page_title'); $activePage = "gift-list"; $mainClass = "pf-gift-list"; $pageCss = "/modules/gift-list/gift-list.css"; -$children = ['Pol', 'Pep', 'Elna', 'Bru', 'Guim']; -$baseAdults = ['Laia', 'Laura', 'Avi Iaia']; -$extraAdults = ['Pauline', 'Papy JC', 'Mamy Caro']; +// --- 2. RÉCUPÉRATION DYNAMIQUE DES DONNÉES (Multi-tenant) --- +$stmt = $pdo->query("SELECT name FROM pf_people WHERE role = 'enfant' AND is_active = 1 ORDER BY name ASC"); +$children = $stmt->fetchAll(PDO::FETCH_COLUMN) ?: ['Aucun enfant configuré']; -$adultsByChildForAnniv = [ - 'Pol' => array_merge($baseAdults, $extraAdults), - 'Pep' => array_merge($baseAdults, $extraAdults), - 'Elna' => $baseAdults, - 'Bru' => $baseAdults, - 'Guim' => $baseAdults, -]; +$stmt = $pdo->query("SELECT name FROM pf_people WHERE role NOT IN ('enfant', 'nounou') AND is_active = 1 ORDER BY name ASC"); +$allAdultsList = $stmt->fetchAll(PDO::FETCH_COLUMN) ?: ['Aucun adulte configuré']; -$VIEWS = [ - 'nadal' => ['TIO', 'NOEL', 'ROIS'], - 'anniversary' => ['ANNIV', 'SANT'], -]; +$stmt = $pdo->query("SELECT code, name, month_date FROM pf_gift_occasions WHERE is_active = 1 ORDER BY month_date ASC, id ASC"); +$activeOccasions = $stmt->fetchAll(PDO::FETCH_ASSOC); -$currentView = strtolower($_GET['view'] ?? ($_SESSION['gift_view'] ?? 'nadal')); -if (!isset($VIEWS[$currentView])) $currentView = 'nadal'; -$_SESSION['gift_view'] = $currentView; -$allowedOccasions = $VIEWS[$currentView]; +if (empty($activeOccasions)) { + $activeOccasions = [['code' => 'DEMO', 'name' => 'Fête par défaut', 'month_date' => null]]; +} +$allowedOccasionCodes = array_column($activeOccasions, 'code'); -$allOccasionLabels = [ - 'TIO' => tr('gift_occ_tio'), 'NOEL' => tr('gift_occ_noel'), 'ROIS' => tr('gift_occ_rois'), - 'ANNIV' => tr('gift_occ_anniv'), 'SANT' => tr('gift_occ_sant') -]; $occasionIcons = [ 'TIO' => '/modules/gift-list/assets/img/tio.png', 'NOEL' => '/modules/gift-list/assets/img/santa.png', 'ROIS' => '/modules/gift-list/assets/img/reis.png', 'ANNIV' => '/modules/gift-list/assets/img/corona.png', 'SANT' => '/modules/gift-list/assets/img/sant.png' ]; -// --- 2. DONNÉES --- -$inMarks = implode(',', array_fill(0, count($allowedOccasions), '?')); -// Tri: Par Fête, puis Enfant, puis Adulte +// --- 3. CHARGEMENT DES CADEAUX --- +$inMarks = implode(',', array_fill(0, count($allowedOccasionCodes), '?')); $sql = "SELECT * FROM pf_gifts WHERE year = ? AND occasion IN ($inMarks) ORDER BY occasion ASC, child_name ASC, adult_name ASC"; $stmt = $pdo->prepare($sql); -$stmt->execute(array_merge([$year], $allowedOccasions)); +$stmt->execute(array_merge([$year], $allowedOccasionCodes)); $gifts = $stmt->fetchAll(PDO::FETCH_ASSOC); $data = []; -$adultsInView = []; +$adultsInView = []; foreach ($gifts as $g) { $data[$g['occasion']][$g['child_name']]['gifts'][] = $g; $data[$g['occasion']][$g['child_name']]['totals'][$g['adult_name']] = ($data[$g['occasion']][$g['child_name']]['totals'][$g['adult_name']] ?? 0) + $g['amount']; $adultsInView[$g['adult_name']] = true; + if (!empty($g['payer_name'])) { + $adultsInView[$g['payer_name']] = true; + } } -$allAdultsList = array_keys($adultsInView); sort($allAdultsList); -// --- 3. TRICOUNT --- -$people = array_values(array_unique(array_merge($baseAdults, array_column($gifts, 'adult_name'), array_column($gifts, 'payer_name')))); +// --- 4. TRICOUNT (Bilan et Remboursements) --- +$people = array_values(array_unique(array_merge($allAdultsList, array_keys($adultsInView)))); $people = array_filter($people); +sort($people); + $matrix = []; foreach ($people as $p1) { foreach ($people as $p2) $matrix[$p1][$p2] = 0.0; } @@ -94,18 +86,15 @@ for ($i = 0; $i < $countPeople; $i++) { require __DIR__ . '/header.php'; ?> -
+

🎁

-
- - -
+
- 🔍 + 🔍
@@ -139,16 +128,22 @@ require __DIR__ . '/header.php';
- +

- - + + + + 🎀 + +

[], 'totals' => []]; - $adultsForThisChild = ($currentView === 'anniversary' && in_array($child, ['Pol', 'Pep'])) ? array_merge($baseAdults, $extraAdults) : $baseAdults; ?>
@@ -156,7 +151,7 @@ require __DIR__ . '/header.php';
@@ -168,9 +163,9 @@ require __DIR__ . '/header.php';
-

+

- +

@@ -201,41 +196,42 @@ require __DIR__ . '/header.php';
-
-

⚖️

+
+

⚖️

-

+

  • à - +
-
- -
+
+ +
- + - + 0 ? 'gift-cell-danger' : 'gift-cell-muted'; ?> - + @@ -247,10 +243,10 @@ require __DIR__ . '/header.php';
-
-
-

Cadeau

- +
+
+

Cadeau

+
@@ -264,9 +260,9 @@ require __DIR__ . '/header.php';
-
+
-
+ + + \ No newline at end of file diff --git a/includes/lang/ca.php b/includes/lang/ca.php index 7850cb1..666c863 100644 --- a/includes/lang/ca.php +++ b/includes/lang/ca.php @@ -602,47 +602,57 @@ return [ 'bud_adv_confirm_delete' => 'Eliminar definitivament aquest avançament?', // ========================================== - // CADEAUX (GIFTS) + // MÒDUL: REGALS (gift-list) // ========================================== - 'gift_page_title' => 'HouseHub - Llista de regals', - 'gift_occ_tio' => 'Tió', - 'gift_occ_noel' => 'Nadal', - 'gift_occ_rois' => 'Reis', - 'gift_occ_anniv' => 'Aniversari', - 'gift_occ_sant' => 'Sant', - 'gift_main_title' => 'Llista de regals %s', - 'gift_aria_change_view' => 'Canvia la vista', - 'gift_view_nadal' => 'Nadal', - 'gift_view_anniv' => 'Aniversaris', - 'gift_view_by_party' => 'Vista per festa', - 'gift_no_gifts' => 'No hi ha cap regal registrat per a %s en aquesta vista.', - 'gift_add_gift' => 'Afegeix un regal', - 'gift_paid_by' => 'pagat per %s', - 'gift_summary_title' => 'Resum del pressupost', - 'gift_col_adult' => 'Adult', - 'gift_col_child' => 'Infant', - 'gift_col_party' => 'Festa', - 'gift_debtor' => 'Deutor', - 'gift_creditor' => 'Creditor', - 'gift_liquidations' => 'Tricount', - 'gift_no_debt' => 'Cap deute pendent.', - 'gift_owes' => 'ha de pagar', - 'gift_to' => 'a', - 'gift_detailed_list' => 'Llista detallada de regals', - 'gift_col_gift' => 'Regal', - 'gift_col_link' => 'Enllaç', - 'gift_modal_title_add' => 'Afegeix un regal per a %s', - 'gift_modal_title_edit' => 'Edita el regal', + + // 1. Títols i capçaleres + 'gift_page_title' => 'Llista de Regals', + 'gift_main_title' => 'Regals de %s', // %s serà substituït per l'any (ex: Regals de 2026) + 'settings' => 'Configuració', + + // 2. Filtres + 'gift_filter_all_children' => 'Tots els nens', + 'gift_filter_all_adults' => 'Tots els adults', + + // 3. Contingut i estats buits + 'gift_add_gift' => 'Afegir', + 'gift_empty_state_no_gifts' => 'Cap regal registrat per a aquesta ocasió.', + 'gift_empty_state_no_filter' => 'Cap regal coincideix amb els teus filtres.', + 'gift_paid_by' => 'Pagat per %s', // %s serà substituït pel nom del pagador + + // 4. Secció Tricount (Balanç i Reemborsaments) + 'gift_liquidations' => 'Balanç i Reemborsaments', + 'gift_no_debt' => 'Tot està al dia, cap deute!', + 'gift_owes' => 'deu', + 'gift_view_matrix' => 'Veure la matriu detallada', + 'gift_debtor' => 'Deutor', + 'gift_creditor' => 'Creditor', + + // 5. Modal: Afegir/Modificar Regal + 'gift_col_adult' => 'Per compte de', 'gift_modal_payer' => 'Pagat per', - 'gift_modal_gift_name' => 'Nom del regal', - 'gift_modal_ph_name' => 'p. ex., Lego Star Wars', - 'gift_modal_price' => 'Preu (€)', - 'gift_modal_link' => 'Enllaç (opcional)', - 'gift_confirm_delete' => 'Vols eliminar aquest regal?', - 'gift_filter_all_children' => 'Tots els nens', - 'gift_filter_all_adults' => 'Tots els adults', - 'gift_empty_state_no_gifts' => 'Cap regal de moment.', - 'gift_empty_state_no_filter' => 'Cap regal correspon al filtre.', + 'gift_modal_gift_name' => 'Descripció del regal', + 'gift_modal_price' => 'Preu estimat / pagat (€)', + 'gift_modal_link' => 'Enllaç del producte (URL)', + 'gift_modal_title_add' => 'Afegir per a %s', // JS: %s = nom del nen + 'gift_modal_title_edit' => 'Modificar el regal', // JS + 'gift_confirm_delete' => 'Vols eliminar realment aquest regal?', // JS + + // 6. Modal: Configuració de les Festes + 'gift_settings_title' => 'Configuració de les Festes', + 'gift_settings_add_title' => '+ Afegir una ocasió', + 'gift_settings_name_placeholder' => 'Nom (ex: Sant Valentí)', + 'gift_settings_date_placeholder' => 'MM-DD', + 'gift_settings_date_help' => 'La data (MM-DD) és opcional. Deixa en blanc per a una festa mòbil.', + 'gift_settings_empty' => 'Cap festa configurada.', // JS + 'gift_settings_active' => 'Actiu', // JS + + // 7. Botons genèrics + 'btn_cancel' => 'Cancel·lar', + 'btn_save' => 'Desar', + 'btn_add' => 'Afegir', + 'loading' => 'Carregant...', // + // ========================================== // GARAGE MANAGER // ========================================== diff --git a/includes/lang/en.php b/includes/lang/en.php index 5c5b817..2b71bad 100644 --- a/includes/lang/en.php +++ b/includes/lang/en.php @@ -592,48 +592,56 @@ return [ 'btn_save' => 'Save', // ========================================== - // GIFTS + // MODULE: GIFTS (gift-list) // ========================================== - 'gift_page_title' => 'HouseHub - Gift list', - 'gift_occ_tio' => 'Tió', - 'gift_occ_noel' => 'Christmas', - 'gift_occ_rois' => 'Three Kings', - 'gift_occ_anniv' => 'Birthday', - 'gift_occ_sant' => 'Name day', - 'gift_main_title' => 'Gift list %s', - 'gift_aria_change_view' => 'Change view', - 'gift_view_nadal' => 'Christmas', - 'gift_view_anniv' => 'Birthdays', - 'gift_view_by_party' => 'View by occasion', - 'gift_no_gifts' => 'No gifts recorded for %s in this view.', - 'gift_add_gift' => 'Add a gift', - 'gift_paid_by' => 'paid by %s', - 'gift_summary_title' => 'Budget summary', - 'gift_col_adult' => 'Adult', - 'gift_col_child' => 'Child', - 'gift_col_party' => 'Occasion', - 'gift_debtor' => 'Debtor', - 'gift_creditor' => 'Creditor', - 'gift_liquidations' => 'Settlement', - 'gift_no_debt' => 'No outstanding debts.', - 'gift_owes' => 'owes', - 'gift_to' => 'to', - 'gift_detailed_list' => 'Detailed gift list', - 'gift_col_gift' => 'Gift', - 'gift_col_link' => 'Link', - 'gift_modal_title_add' => 'Add a gift for %s', - 'gift_modal_title_edit' => 'Edit gift', + + // 1. Titles and headers + 'gift_page_title' => 'Gift List', + 'gift_main_title' => 'Gifts of %s', // %s will be replaced by the year (e.g., Gifts of 2026) + 'settings' => 'Settings', + + // 2. Filters + 'gift_filter_all_children' => 'All children', + 'gift_filter_all_adults' => 'All adults', + + // 3. Content and empty states + 'gift_add_gift' => 'Add', + 'gift_empty_state_no_gifts' => 'No gifts recorded for this occasion.', + 'gift_empty_state_no_filter' => 'No gifts match your filters.', + 'gift_paid_by' => 'Paid by %s', // %s will be replaced by the payer's name + + // 4. Tricount Section (Balance & Refunds) + 'gift_liquidations' => 'Balance & Refunds', + 'gift_no_debt' => 'Everything is settled, no debts!', + 'gift_owes' => 'owes', + 'gift_view_matrix' => 'View detailed matrix', + 'gift_debtor' => 'Debtor', + 'gift_creditor' => 'Creditor', + + // 5. Modal: Add/Edit Gift + 'gift_col_adult' => 'On behalf of', 'gift_modal_payer' => 'Paid by', - 'gift_modal_gift_name' => 'Gift name', - 'gift_modal_ph_name' => 'e.g. Lego Star Wars', - 'gift_modal_price' => 'Price (€)', - 'gift_modal_link' => 'Link (optional)', - 'gift_confirm_delete' => 'Are you sure you want to delete this gift?', - 'gift_filter_all_children' => 'All children', - 'gift_filter_all_adults' => 'All adults', - 'gift_empty_state_no_gifts' => 'No gifts yet.', - 'gift_empty_state_no_filter' => 'No gifts match the filter.', - 'gift_view_matrix' => 'View detailed matrix', + 'gift_modal_gift_name' => 'Gift description', + 'gift_modal_price' => 'Estimated / paid price (€)', + 'gift_modal_link' => 'Product link (URL)', + 'gift_modal_title_add' => 'Add for %s', // JS: %s = child's name + 'gift_modal_title_edit' => 'Edit gift', // JS + 'gift_confirm_delete' => 'Are you sure you want to delete this gift?', // JS + + // 6. Modal: Occasions Settings + 'gift_settings_title' => 'Occasions Settings', + 'gift_settings_add_title' => '+ Add an occasion', + 'gift_settings_name_placeholder' => 'Name (e.g. Valentine\'s Day)', + 'gift_settings_date_placeholder' => 'MM-DD', + 'gift_settings_date_help' => 'Date (MM-DD) is optional. Leave empty for a floating date.', + 'gift_settings_empty' => 'No occasion configured.', // JS + 'gift_settings_active' => 'Active', // JS + + // 7. Generic buttons + 'btn_cancel' => 'Cancel', + 'btn_save' => 'Save', + 'btn_add' => 'Add', + 'loading' => 'Loading...', // ========================================== // GARAGE MANAGER diff --git a/includes/lang/fr.php b/includes/lang/fr.php index 86b8ada..13b6641 100644 --- a/includes/lang/fr.php +++ b/includes/lang/fr.php @@ -599,48 +599,57 @@ return [ 'bud_adv_confirm_delete' => 'Supprimer définitivement cette avance ?', // ========================================== - // CADEAUX (GIFTS) + // MODULE : CADEAUX (gift-list) // ========================================== - 'gift_page_title' => 'HouseHub - Liste de cadeaux', - 'gift_occ_tio' => 'Tió', - 'gift_occ_noel' => 'Noël', - 'gift_occ_rois' => 'Rois mages', - 'gift_occ_anniv' => 'Anniversaire', - 'gift_occ_sant' => 'Saint', - 'gift_main_title' => 'Liste de cadeaux %s', - 'gift_aria_change_view' => 'Changer la vue', - 'gift_view_nadal' => 'Noël', - 'gift_view_anniv' => 'Anniversaires', - 'gift_view_by_party' => 'Vue par fête', - 'gift_no_gifts' => 'Aucun cadeau enregistré pour %s dans cette vue.', - 'gift_add_gift' => 'Ajouter un cadeau', - 'gift_paid_by' => 'payé par %s', - 'gift_summary_title' => 'Résumé du budget', - 'gift_col_adult' => 'Adulte', - 'gift_col_child' => 'Enfant', - 'gift_col_party' => 'Fête', - 'gift_debtor' => 'Débiteur', - 'gift_creditor' => 'Créancier', - 'gift_liquidations' => 'Tricount', - 'gift_no_debt' => 'Aucune dette en cours.', - 'gift_owes' => 'doit', - 'gift_to' => 'à', - 'gift_detailed_list' => 'Liste détaillée des cadeaux', - 'gift_col_gift' => 'Cadeau', - 'gift_col_link' => 'Lien', - 'gift_modal_title_add' => 'Ajouter un cadeau pour %s', - 'gift_modal_title_edit' => 'Modifier le cadeau', + + // 1. Titres et en-têtes + 'gift_page_title' => 'Liste des Cadeaux', + 'gift_main_title' => 'Cadeaux de %s', // %s sera remplacé par l'année (ex: Cadeaux de 2026) + 'settings' => 'Paramètres', + + // 2. Filtres + 'gift_filter_all_children' => 'Tous les enfants', + 'gift_filter_all_adults' => 'Tous les adultes', + + // 3. Contenu et états vides + 'gift_add_gift' => 'Ajouter', + 'gift_empty_state_no_gifts' => 'Aucun cadeau enregistré pour cette fête.', + 'gift_empty_state_no_filter' => 'Aucun cadeau ne correspond à vos filtres.', + 'gift_paid_by' => 'Payé par %s', // %s sera remplacé par le nom du payeur + + // 4. Section Tricount (Bilan & Remboursements) + 'gift_liquidations' => 'Bilan & Remboursements', + 'gift_no_debt' => 'Tout est à jour, aucune dette !', + 'gift_owes' => 'doit', + 'gift_view_matrix' => 'Voir la matrice détaillée', + 'gift_debtor' => 'Débiteur', + 'gift_creditor' => 'Créancier', + + // 5. Modale : Ajout/Modification de Cadeau + 'gift_col_adult' => 'Pour le compte de', 'gift_modal_payer' => 'Payé par', - 'gift_modal_gift_name' => 'Nom du cadeau', - 'gift_modal_ph_name' => 'ex: Lego Star Wars', - 'gift_modal_price' => 'Prix (€)', - 'gift_modal_link' => 'Lien (optionnel)', - 'gift_confirm_delete' => 'Voulez-vous vraiment supprimer ce cadeau ?', - 'gift_filter_all_children' => 'Tous les enfants', - 'gift_filter_all_adults' => 'Tous les adultes', - 'gift_empty_state_no_gifts' => 'Aucun cadeau pour le moment.', - 'gift_empty_state_no_filter' => 'Aucun cadeau ne correspond au filtre.', - 'gift_view_matrix' => 'Voir la matrice détaillée', + 'gift_modal_gift_name' => 'Description du cadeau', + 'gift_modal_price' => 'Prix estimé / payé (€)', + 'gift_modal_link' => 'Lien du produit (URL)', + 'gift_modal_title_add' => 'Ajouter pour %s', // JS : %s = nom de l'enfant + 'gift_modal_title_edit' => 'Modifier le cadeau', // JS + 'gift_confirm_delete' => 'Voulez-vous vraiment supprimer ce cadeau ?', // JS + + // 6. Modale : Paramètres des Fêtes (Nouveau !) + 'gift_settings_title' => 'Configuration des Fêtes', + 'gift_settings_add_title' => '+ Ajouter une occasion', + 'gift_settings_name_placeholder' => 'Nom (ex: Saint Valentin)', + 'gift_settings_date_placeholder' => 'MM-JJ', + 'gift_settings_date_help' => 'La date (MM-JJ) est optionnelle. Laissez vide pour une fête mobile.', + 'gift_settings_empty' => 'Aucune fête configurée.', // JS + 'gift_settings_active' => 'Actif', // JS + + // 7. Boutons génériques (si tu ne les as pas déjà en global) + 'btn_cancel' => 'Annuler', + 'btn_save' => 'Enregistrer', + 'btn_add' => 'Ajouter', + 'loading' => 'Chargement...', + // ========================================== // GARAGE MANAGER // ========================================== diff --git a/migrate.php b/migrate.php index d3ace5e..45446ba 100644 --- a/migrate.php +++ b/migrate.php @@ -1,261 +1,158 @@ 🚀 Début de la migration Globale ..."; +$db_host = getenv('DB_HOST') ?: '127.0.0.1'; +$db_user = getenv('DB_USER') ?: 'househub'; +$db_pass = getenv('DB_PASS') ?: 'househub_dev'; + +echo "

🚀 Début de la migration Multi-Tenant

"; -// ========================================== -// 0. MISE À JOUR DE LA META DB -// ========================================== -echo "

Mise à jour Meta DB (househub_meta)

    "; try { - $meta_pdo->exec("ALTER TABLE user_calendar_integrations ADD COLUMN calendar_prefs_json TEXT DEFAULT NULL"); - echo "
  • Colonne 'calendar_prefs_json' ajoutée à user_calendar_integrations.
  • "; -} catch (\PDOException $e) { - if ($e->getCode() == '42S21' || strpos($e->getMessage(), '1060') !== false) { - echo "
  • Colonne 'calendar_prefs_json' déjà présente.
  • "; - } else { throw $e; } -} -echo "
"; + $stmt = $meta_pdo->query("SELECT db_name, name FROM families WHERE is_active = 1"); + $families = $stmt->fetchAll(PDO::FETCH_ASSOC); -// ========================================== -// MIGRATIONS PAR FAMILLE -// ========================================== -$stmt = $meta_pdo->query("SELECT id, name, db_name FROM families WHERE db_name != ''"); -$families = $stmt->fetchAll(); + foreach ($families as $family) { + $dbName = $family['db_name']; + echo "

Famille : {$family['name']} ($dbName)

"; -$host = getenv('DB_HOST') ?: 'househub-db'; -$user = getenv('DB_USER') ?: 'househub'; -$pass = getenv('DB_PASS') ?: 'changeme'; - -foreach ($families as $f) { - $db_name = $f['db_name']; - $family_id = $f['id']; - echo "

Mise à jour de {$f['name']} ($db_name)

    "; - - $p1_name = null; - $p2_name = null; - - try { - $fam_pdo = new PDO("mysql:host=$host;dbname=$db_name;charset=utf8mb4", $user, $pass, [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION - ]); - - // 1. Table pf_foyer_settings - $fam_pdo->exec("CREATE TABLE IF NOT EXISTS pf_foyer_settings ( - id INT AUTO_INCREMENT PRIMARY KEY, - currency VARCHAR(10) NOT NULL DEFAULT '€', - zone_scolaire VARCHAR(5) NOT NULL DEFAULT 'C', - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); - $fam_pdo->exec("INSERT IGNORE INTO pf_foyer_settings (id, currency, zone_scolaire) VALUES (1, '€', 'C');"); - echo "
  • Table pf_foyer_settings vérifiée/créée.
  • "; - - // 2. Colonne external_href pour le Calendrier iOS try { - $fam_pdo->exec("ALTER TABLE pf_calendar_event_links ADD COLUMN external_href VARCHAR(2048) DEFAULT NULL"); - echo "
  • Colonne 'external_href' ajoutée à pf_calendar_event_links.
  • "; - } catch (\PDOException $e) { - if ($e->getCode() != '42S21' && strpos($e->getMessage(), '1060') === false) throw $e; - } + $pdo = new PDO( + "mysql:host=$db_host;dbname=$dbName;charset=utf8mb4", + $db_user, $db_pass, + [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] + ); - // 3. Colonne budget_item_id pour les règles d'import - try { - $fam_pdo->exec("ALTER TABLE pf_import_rules ADD COLUMN budget_item_id INT(11) DEFAULT NULL"); - echo "
  • Colonne 'budget_item_id' ajoutée à pf_import_rules.
  • "; - } catch (\PDOException $e) { - if ($e->getCode() != '42S21' && strpos($e->getMessage(), '1060') === false) throw $e; - } - - // 4. Uniformisation des VARCHAR de dates pour le budget (YYYY-MM-01 = 10 chars) - $fam_pdo->exec("ALTER TABLE pf_expenses MODIFY gestion_month VARCHAR(10) NOT NULL"); - $fam_pdo->exec("ALTER TABLE pf_alloc_values MODIFY month_date VARCHAR(10) NOT NULL"); - $fam_pdo->exec("ALTER TABLE pf_savings MODIFY month_date VARCHAR(10) NOT NULL"); - echo "
  • Formats de dates (VARCHAR 10) uniformisés pour le budget.
  • "; - - // 5. GESTION DE PF_PEOPLE (user_id, role, color) - echo "
  • Mise à jour pf_people : "; - try { $fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN user_id INT NULL DEFAULT NULL"); } catch (\Exception $e) {} - try { $fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN role VARCHAR(50) DEFAULT NULL"); } catch (\Exception $e) {} - try { $fam_pdo->exec("ALTER TABLE pf_people ADD COLUMN color VARCHAR(7) DEFAULT '#0891b2'"); } catch (\Exception $e) {} - - $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(); - - foreach ($users as $u) { - $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) { - $fam_pdo->prepare("UPDATE pf_people SET user_id = ? WHERE id = ?")->execute([$u['id'], $exists]); + // --------------------------------------------------------- + // 1. UPDATE DE pf_people (Juste la date de naissance et l'état actif) + // --------------------------------------------------------- + $colCheck = $pdo->prepare("SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'pf_people' AND COLUMN_NAME = 'birthdate'"); + $colCheck->execute([$dbName]); + if ($colCheck->rowCount() === 0) { + $pdo->exec("ALTER TABLE pf_people + ADD COLUMN birthdate DATE DEFAULT NULL, + ADD COLUMN is_active TINYINT(1) DEFAULT 1 + "); + echo "✅ pf_people mis à jour (birthdate ajouté).
    "; } else { - $fam_pdo->prepare("INSERT INTO pf_people (name, user_id) VALUES (?, ?)")->execute([$u['display_name'] ?: $u['username'], $u['id']]); + echo "➖ pf_people déjà à jour.
    "; } - } - $fam_pdo->exec("UPDATE pf_people SET role = 'parent' WHERE user_id IS NOT NULL AND role IS NULL;"); - $fam_pdo->exec("UPDATE pf_people SET role = 'nounou' WHERE LOWER(name) = 'carole';"); - $pIds = $fam_pdo->query("SELECT id FROM pf_people WHERE role = 'parent' ORDER BY id ASC")->fetchAll(PDO::FETCH_COLUMN); - if (isset($pIds[0])) $fam_pdo->exec("UPDATE pf_people SET color = '#0891b2' WHERE id = " . (int)$pIds[0] . " AND color IS NULL"); - if (isset($pIds[1])) $fam_pdo->exec("UPDATE pf_people SET color = '#f59e0b' WHERE id = " . (int)$pIds[1] . " AND color IS NULL"); - echo "OK
  • "; - - // ========================================== - // 6. REFONTE RELATIONNELLE : pf_alloc_values (Colonnes -> Lignes) - // ========================================== - echo "
  • Table pf_alloc_values (Normalisation) : "; - - // Vérifier si la table est déjà convertie - $checkNewFormat = $fam_pdo->query("SHOW COLUMNS FROM pf_alloc_values LIKE 'person_id'")->rowCount(); - - if ($checkNewFormat > 0) { - echo "Déjà convertie au format relationnel.
  • "; - } else { - // A. Récupérer l'ordre des parents réels pour faire le mapping d'index - $stmtParents = $fam_pdo->query("SELECT id, name FROM pf_people WHERE role = 'parent' ORDER BY id ASC"); - $orderedParents = $stmtParents->fetchAll(); + // --------------------------------------------------------- + // 2. CRÉATION DES NOUVELLES TABLES (IF NOT EXISTS) + // --------------------------------------------------------- - // B. Sauvegarder les anciennes données à migrer - $oldAllocations = $fam_pdo->query("SELECT * FROM pf_alloc_values")->fetchAll(PDO::FETCH_ASSOC); - - // C. Supprimer l'ancienne table - $fam_pdo->exec("DROP TABLE IF EXISTS pf_alloc_values"); - - // D. Créer la nouvelle table normalisée - $fam_pdo->exec("CREATE TABLE pf_alloc_values ( - id INT AUTO_INCREMENT PRIMARY KEY, - month_date VARCHAR(10) NOT NULL, - cat_id INT NOT NULL, - person_id INT NOT NULL, - amount DECIMAL(10,2) DEFAULT 0.00, - UNIQUE KEY uq_alloc_person (month_date, cat_id, person_id), - FOREIGN KEY (person_id) REFERENCES pf_people(id) ON DELETE CASCADE - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); - - // E. Migration des données - $stmtInsert = $fam_pdo->prepare("INSERT INTO pf_alloc_values (month_date, cat_id, person_id, amount) VALUES (?, ?, ?, ?)"); - $migratedRows = 0; + // Comptes bancaires + $pdo->exec("CREATE TABLE IF NOT EXISTS pf_bank_accounts ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(100) NOT NULL, + owner_person_id INT DEFAULT NULL, + account_type VARCHAR(50) DEFAULT 'savings', + is_default TINYINT(1) DEFAULT 0, + FOREIGN KEY (owner_person_id) REFERENCES pf_people(id) ON DELETE SET NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); - foreach ($oldAllocations as $oldRow) { - // Mapping des anciennes colonnes potentielles vers les index de parents - $possibleColumns = [ - 0 => ['amount_p1', 'amount_alex'], - 1 => ['amount_p2', 'amount_laia'], - 2 => ['amount_p3'], - 3 => ['amount_p4'] - ]; + // Catégories de budget + $pdo->exec("CREATE TABLE IF NOT EXISTS pf_budget_categories ( + id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(50) NOT NULL, + label VARCHAR(100) NOT NULL, + type VARCHAR(50) NOT NULL, + color VARCHAR(20) DEFAULT '#ccc', + icon VARCHAR(20) DEFAULT '💰', + UNIQUE KEY (code) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); - foreach ($possibleColumns as $parentIndex => $colNames) { - if (!isset($orderedParents[$parentIndex])) continue; - $targetParentId = $orderedParents[$parentIndex]['id']; + // Types de congés + $pdo->exec("CREATE TABLE IF NOT EXISTS pf_leave_types ( + id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(20) NOT NULL, + label VARCHAR(100) NOT NULL, + default_allowance DECIMAL(5,2) DEFAULT 0, + reset_month INT DEFAULT 1, + allow_carry_over TINYINT(1) DEFAULT 0, + UNIQUE KEY (code) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); - foreach ($colNames as $col) { - if (isset($oldRow[$col]) && (float)$oldRow[$col] > 0) { - $stmtInsert->execute([ - $oldRow['month_date'], - $oldRow['cat_id'], - $targetParentId, - (float)$oldRow[$col] - ]); - $migratedRows++; - break; // Passer à l'index parent suivant dès qu'on a trouvé une valeur - } - } - } + // Fêtes (Cadeaux) + $pdo->exec("CREATE TABLE IF NOT EXISTS pf_gift_occasions ( + id INT AUTO_INCREMENT PRIMARY KEY, + code VARCHAR(20) NOT NULL, + name VARCHAR(100) NOT NULL, + month_date VARCHAR(5) DEFAULT NULL, + is_active TINYINT(1) DEFAULT 1, + UNIQUE KEY (code) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); + + // Règles Cadeaux (Qui paie pour quel enfant à quelle occasion) + // L'ERREUR DE SYNTAXE ÉTAIT ICI : UNIQUE KEY adult_child_occ + $pdo->exec("CREATE TABLE IF NOT EXISTS pf_gift_rules ( + id INT AUTO_INCREMENT PRIMARY KEY, + adult_person_id INT NOT NULL, + child_person_id INT NOT NULL, + occasion_id INT NOT NULL, + FOREIGN KEY (adult_person_id) REFERENCES pf_people(id) ON DELETE CASCADE, + FOREIGN KEY (child_person_id) REFERENCES pf_people(id) ON DELETE CASCADE, + FOREIGN KEY (occasion_id) REFERENCES pf_gift_occasions(id) ON DELETE CASCADE, + UNIQUE KEY adult_child_occ (adult_person_id, child_person_id, occasion_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); + + echo "✅ Nouvelles tables de configuration créées ou vérifiées.
    "; + + // --------------------------------------------------------- + // 3. INJECTION DE DONNÉES PAR DÉFAUT (Pour ne rien casser) + // --------------------------------------------------------- + + if ($pdo->query("SELECT COUNT(*) FROM pf_bank_accounts")->fetchColumn() == 0) { + $pdo->exec("INSERT INTO pf_bank_accounts (name, account_type, is_default) VALUES + ('Compte Commun', 'checking', 1), + ('Livret A Alex', 'savings', 0), + ('Livret A Laia', 'savings', 0), + ('Livret A Pol', 'savings', 0), + ('Livret A Pep', 'savings', 0) + "); } - echo "Succès ! Nouvelle table créée, $migratedRows lignes migrées."; + + if ($pdo->query("SELECT COUNT(*) FROM pf_budget_categories")->fetchColumn() == 0) { + $pdo->exec("INSERT INTO pf_budget_categories (code, label, type, color, icon) VALUES + ('INCOME', 'Revenus', 'Income', '#22c55e', '💵'), + ('FMCG', 'Alimentation', 'Expense', '#3b82f6', '🛒'), + ('FUEL', 'Carburant', 'Expense', '#f59e0b', '⛽'), + ('SCHOOL', 'École / Garde', 'Expense', '#a855f7', '🎒'), + ('HEALTH', 'Santé', 'Expense', '#ef4444', '⚕️') + "); + } + + if ($pdo->query("SELECT COUNT(*) FROM pf_leave_types")->fetchColumn() == 0) { + $pdo->exec("INSERT INTO pf_leave_types (code, label, default_allowance, reset_month, allow_carry_over) VALUES + ('CA', 'Congés Annuels', 25, 6, 1), + ('JRA', 'Jours de Repos', 10, 1, 0), + ('JA', 'Jour Anniversaire', 1, 1, 0) + "); + } + + if ($pdo->query("SELECT COUNT(*) FROM pf_gift_occasions")->fetchColumn() == 0) { + $pdo->exec("INSERT INTO pf_gift_occasions (code, name, month_date) VALUES + ('NOEL', 'Noël', '12-25'), + ('ROIS', 'Les Rois', '01-06'), + ('ANNIV', 'Anniversaire', NULL) + "); + } + + echo "✅ Données de base injectées (si nécessaire).
    "; + + } catch (PDOException $e) { + echo "❌ Erreur sur la base $dbName : " . $e->getMessage() . "
    "; } - - // 7. GESTION DU BUDGET (pf_alloc_categories & pf_salary_config) - $fam_pdo->exec("UPDATE pf_alloc_categories SET name = 'Eco P1' WHERE name LIKE 'Eco Alex%'"); - $fam_pdo->exec("UPDATE pf_alloc_categories SET name = 'Eco P2' WHERE name LIKE 'Eco Laia%'"); - - $stmtParents = $fam_pdo->query("SELECT name FROM pf_people WHERE role = 'parent' ORDER BY id ASC"); - $parents = $stmtParents->fetchAll(); - if (count($parents) >= 2) { - $fam_pdo->prepare("UPDATE pf_salary_config SET person = ? WHERE person = 'Alex'")->execute([$parents[0]['name']]); - $fam_pdo->prepare("UPDATE pf_salary_config SET person = ? WHERE person = 'Laia'")->execute([$parents[1]['name']]); - } - - } catch (\PDOException $e) { - echo "
  • ❌ Erreur : " . $e->getMessage() . "
  • "; } - echo "
"; - // ========================================== - // 8. SEPARATION CIBLE BUDGET / DESTINATION VIREMENT - // ========================================== - echo "
  • Table pf_alloc_categories (Fix Collision) : "; - try { - $fam_pdo->exec("ALTER TABLE pf_alloc_categories ADD COLUMN transfer_dest VARCHAR(50) DEFAULT NULL AFTER target"); - echo "Succès ! Colonne 'transfer_dest' ajoutée pour préserver vos objectifs chiffrés.
  • "; - } catch (\PDOException $e) { - echo "La colonne transfer_dest existe déjà."; - } + echo "

    🎉 Migration terminée avec succès !

    "; - // ========================================== - // 9. TRANSFERT DES DONNÉES (Cible -> Destination) - // ========================================== - echo "
  • Table pf_alloc_categories (Récupération des données) : "; - try { - // Vérifier si la colonne target est encore au format texte (VARCHAR) - $stmtCol = $fam_pdo->query("SHOW COLUMNS FROM pf_alloc_categories LIKE 'target'"); - $colInfo = $stmtCol->fetch(PDO::FETCH_ASSOC); - - if ($colInfo && strpos(strtolower($colInfo['Type']), 'varchar') !== false) { - // 1. Déplacer les textes "vers..." - $updated1 = $fam_pdo->exec("UPDATE pf_alloc_categories SET transfer_dest = target, target = '0' WHERE target LIKE 'vers %'"); - - // 2. Gérer le mot 'SYSTEM' - $updated2 = $fam_pdo->exec("UPDATE pf_alloc_categories SET transfer_dest = 'SYSTEM', target = '0' WHERE target = 'SYSTEM'"); - - // 3. Sécurité absolue avant conversion - $fam_pdo->exec("UPDATE pf_alloc_categories SET target = '0' WHERE target NOT REGEXP '^[0-9]+(\\\\.[0-9]+)?$'"); - - echo "" . ($updated1 + $updated2) . " destinations récupérées et déplacées.
    "; - - // 4. Remettre la colonne en format numérique - $fam_pdo->exec("ALTER TABLE pf_alloc_categories MODIFY target DECIMAL(10,2) DEFAULT 0.00"); - echo "Colonne 'target' re-sécurisée en format monétaire DECIMAL(10,2).
  • "; - } else { - echo "La colonne 'target' est déjà au format DECIMAL, transfert ignoré."; - } - } catch (\PDOException $e) { - echo "Erreur : " . $e->getMessage() . ""; - } - - try { - // Récupération de toutes les bases de données de familles via la base meta - $stmtFamilies = $meta_pdo->query("SELECT id, db_name FROM families WHERE is_active = 1"); - while ($fam = $stmtFamilies->fetch(PDO::FETCH_ASSOC)) { - error_log("Migration de la base familiale : " . $fam['db_name']); - - $fam_dsn = "mysql:host=" . DB_HOST . ";dbname=" . $fam['db_name'] . ";charset=utf8mb4"; - $fam_pdo = new PDO($fam_dsn, DB_USER, DB_PASS, $options); - - // Injection incrémentale sécurisée - $fam_pdo->exec(" - CREATE TABLE IF NOT EXISTS `pf_advances` ( - `id` INT AUTO_INCREMENT PRIMARY KEY, - `advance_date` DATE NOT NULL, - `payer` VARCHAR(100) NOT NULL, - `description` VARCHAR(255) NOT NULL, - `amount` DECIMAL(10,2) DEFAULT 0.00, - `from_savings` TINYINT(1) DEFAULT 0, - `is_resolved` TINYINT(1) DEFAULT 0, - `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - "); - } - error_log("✅ Migration de la table pf_advances terminée sur tous les tenants."); } catch (Exception $e) { - die("❌ Erreur critique lors de la migration : " . $e->getMessage()); + die("❌ Erreur fatale Meta DB : " . $e->getMessage()); } -} - - - -echo "

    🎉 Migration terminée avec succès !

    "; ?> \ No newline at end of file diff --git a/modules/gift-list/api-settings.php b/modules/gift-list/api-settings.php new file mode 100644 index 0000000..f643c4a --- /dev/null +++ b/modules/gift-list/api-settings.php @@ -0,0 +1,51 @@ +query("SELECT * FROM pf_gift_occasions ORDER BY month_date ASC, id ASC"); + echo json_encode(['ok' => true, 'data' => $stmt->fetchAll(PDO::FETCH_ASSOC)]); + exit; + } + + // 2. Ajouter une nouvelle fête (Renvoie l'ID pour rafraîchir la modale) + if ($action === 'add_occasion') { + $name = trim($_POST['name'] ?? ''); + $month_date = trim($_POST['month_date'] ?? ''); + + if (empty($name)) throw new Exception("Name required"); + + $code = strtoupper(preg_replace('/[^A-Za-z0-9]/', '', $name)); + $code = substr($code, 0, 20); + + $stmt = $pdo->prepare("INSERT INTO pf_gift_occasions (code, name, month_date, is_active) VALUES (?, ?, ?, 1)"); + $stmt->execute([$code, $name, $month_date ?: null]); + + echo json_encode(['ok' => true]); + exit; + } + + // 3. Sauvegarde globale des cases à cocher + if ($action === 'save_toggles') { + $states = json_decode($_POST['states'] ?? '[]', true); + if (is_array($states)) { + $stmt = $pdo->prepare("UPDATE pf_gift_occasions SET is_active = ? WHERE id = ?"); + foreach ($states as $item) { + $stmt->execute([(int)$item['state'], (int)$item['id']]); + } + } + echo json_encode(['ok' => true]); + exit; + } + + throw new Exception("Action inconnue."); + +} catch (Exception $e) { + http_response_code(400); + echo json_encode(['ok' => false, 'error' => $e->getMessage()]); +} +?> \ No newline at end of file diff --git a/modules/gift-list/gift-list.css b/modules/gift-list/gift-list.css index 694d500..4fe8543 100644 --- a/modules/gift-list/gift-list.css +++ b/modules/gift-list/gift-list.css @@ -64,7 +64,7 @@ .pf-filter-select { padding: 6px 12px; - border-radius: 20px; /* Forme de pillule */ + border-radius: 20px; border: 1px solid var(--border-light); background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(8px); @@ -75,16 +75,15 @@ font-family: inherit; cursor: pointer; box-shadow: var(--shadow-sm); - width: auto; /* Empêche de prendre toute la largeur */ - appearance: none; /* Nettoie la flèche système... */ + width: auto; + appearance: none; -webkit-appearance: none; -moz-appearance: none; - /* ...pour mettre une flèche personnalisée plus discrète */ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2364748b'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; background-size: 12px; - padding-right: 28px; /* Place pour la flèche */ + padding-right: 28px; } .pf-filter-select:focus { @@ -121,10 +120,10 @@ padding-bottom: 12px; margin-bottom: 15px; border-bottom: 1px solid var(--border-light); - scrollbar-width: none; /* Firefox */ + scrollbar-width: none; } .pf-child-totals-bar::-webkit-scrollbar { - display: none; /* Chrome/Safari */ + display: none; } .pf-summary-pill { white-space: nowrap; @@ -182,12 +181,6 @@ color: var(--success); font-size: 1rem; } -.pf-gift-badges-col { - display: flex; - flex-direction: column; - gap: 4px; - align-items: flex-start; -} .pf-pill-adult { font-size: 0.7rem; font-weight: 700; @@ -196,14 +189,6 @@ padding: 2px 6px; border-radius: 4px; } -.pf-pill-occ { - font-size: 0.65rem; - font-weight: 700; - background: #fef3c7; - color: #b45309; - padding: 2px 6px; - border-radius: 4px; -} .pf-gift-payer { font-size: 0.7rem; color: #ef4444; @@ -242,9 +227,8 @@ border-right: 2px solid var(--border-light); } -/* Icônes des fêtes (Tió, Noël, etc.) plus petites et bien alignées */ .cl-occasion-icon { - width: 20px; /* Réduit pour être discret */ + width: 20px; height: 20px; object-fit: contain; vertical-align: middle; @@ -253,41 +237,14 @@ .cl-occasion-title { display: flex; align-items: center; - gap: 8px; /* Espace réduit entre l'icône et le texte */ + gap: 8px; } -@media (max-width: 768px) { - .pf-child-section { - padding: 15px; - } - .pf-filter-bar { - padding: 0 5px; - } - .pf-filter-label { - display: none; - } - .pf-gift-feed { - grid-template-columns: 1fr 1fr; - } - .pf-gift-title { - font-size: 0.85rem; - } - .pf-gift-price { - font-size: 0.9rem; - } -} -@media (max-width: 400px) { - .pf-gift-feed { - grid-template-columns: 1fr; - } -} /* === COMPOSANT MULTI-SELECT VANILLA === */ .pf-multi-select { position: relative; display: inline-block; } - -/* Le bouton déclencheur (réutilise le style des filtres) */ .pf-ms-trigger { padding: 6px 16px; border-radius: 20px; @@ -310,8 +267,6 @@ border-color: var(--primary); box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1); } - -/* Le menu déroulant */ .pf-ms-dropdown { display: none; position: absolute; @@ -333,8 +288,6 @@ display: flex; animation: popDown 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; } - -/* Les options (checkboxes) */ .pf-ms-option { display: flex; align-items: center; @@ -375,3 +328,380 @@ transform: translateY(0); } } + +@media (max-width: 768px) { + .pf-child-section { + padding: 15px; + } + .pf-filter-bar { + padding: 0 5px; + } + .pf-filter-label { + display: none; + } + .pf-gift-feed { + grid-template-columns: 1fr 1fr; + } + .pf-gift-title { + font-size: 0.85rem; + } + .pf-gift-price { + font-size: 0.9rem; + } +} +@media (max-width: 400px) { + .pf-gift-feed { + grid-template-columns: 1fr; + } +} + +/* Header & Settings Button */ +.gift-settings-btn { + font-size: 1.5rem; + background: none; + border: none; + cursor: pointer; +} + +/* Empty States */ +.gift-empty-state { + color: var(--text-muted); + font-size: 0.9rem; + font-style: italic; + margin: 0; +} + +/* Section Tricount (Bilan) */ +.gift-tricount-section { + border: 1px solid var(--border-light); + margin-top: 40px; +} +.gift-tricount-title { + margin-top: 0; + color: var(--text-main); + font-size: 1.3rem; +} +.gift-tricount-success { + color: var(--success); + font-weight: 700; + margin-bottom: 0; +} +.gift-tricount-debt-amount { + color: var(--danger); +} +.gift-matrix-details { + margin-top: 20px; + padding: 12px; + border-radius: 8px; +} +.gift-matrix-summary { + cursor: pointer; + font-weight: 600; + color: var(--text-muted); + outline: none; +} +.gift-matrix-wrapper { + overflow-x: auto; + margin-top: 15px; +} +.cl-debt-matrix th.sticky-col, +.cl-debt-matrix td.sticky-col { + position: sticky; + left: 0; + z-index: 2; + border-right: 2px solid #e2e8f0; +} +.cl-debt-matrix th.sticky-col.bg-header { + background: #f8fafc; +} +.cl-debt-matrix th.sticky-col.bg-body { + background: white; +} + +.gift-cell-danger { + color: var(--danger); + font-weight: 700; + background: #fef2f2; +} +.gift-cell-muted { + color: var(--text-muted); +} + +/* Modale Edition Cadeau */ +.gift-modal-custom-content { + max-width: 500px; + width: 95%; +} +.gift-modal-custom-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.gift-modal-custom-title { + margin: 0; + border: none; + padding: 0; +} +.gift-modal-close-btn { + background: none; + border: none; + font-size: 1.8rem; + cursor: pointer; + color: var(--text-muted); + line-height: 1; +} +.gift-form-group-spaced { + margin-bottom: 25px; +} +.gift-modal-custom-footer { + padding-top: 15px; + display: flex; + justify-content: flex-end; + gap: 10px; +} + +/* Modale Paramètres Occasions (Bottom Sheet style) */ +.gift-settings-backdrop { + display: none; + align-items: flex-end; /* Bottom sheet on mobile */ +} +.gift-settings-modal { + width: 100%; + border-radius: 12px 12px 0 0; + margin: 0; +} +.gift-settings-header { + display: flex; + justify-content: space-between; + align-items: center; +} +.gift-settings-header h3 { + margin: 0; +} +.gift-settings-body { + padding: 1rem; +} +.gift-occasions-list { + margin-bottom: 2rem; +} +.gift-occasions-grid { + display: grid; + gap: 0.5rem; +} +.gift-occasion-card { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.8rem 1rem; + border: 1px solid var(--border-light); + border-radius: 8px; +} +.gift-occasion-toggle { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; +} +.gift-date-badge { + font-size: 0.8em; + background: var(--bg-page); + padding: 2px 6px; + border-radius: 4px; + margin-left: 10px; +} +.gift-add-subtitle { + margin-top: 1rem; + margin-bottom: 0.5rem; +} +.gift-add-form { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} +.gift-add-input-name { + flex: 1; + min-width: 150px; +} +.gift-add-input-date { + width: 120px; +} +.gift-help-text { + color: var(--text-muted); + font-size: 0.85em; +} +.gift-loader { + text-align: center; + padding: 20px; +} +.gift-error-msg { + color: var(--danger); + padding: 10px; +} +/* ========================================================================== + BOUTONS & MODALE (Design System Todo) + ========================================================================== */ +.btn { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.4rem 0.85rem; + border-radius: 8px; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + border: 1px solid transparent; + transition: all 0.15s; + white-space: nowrap; +} +.btn-icon { + padding: 0.25rem; + min-width: 28px; + justify-content: center; + font-size: 1.2rem; +} +.btn-ghost { + background: transparent; + border: none; + color: var(--text-muted); +} +.btn-ghost:hover { + color: var(--text-main); + background: var(--bg-page); + border-radius: 6px; +} + +/* Correction de la surimpression de la modale Paramètres */ +.gift-settings-backdrop { + display: none; + position: fixed; + inset: 0; + z-index: 200; + background: rgba(15, 23, 42, 0.5); + backdrop-filter: blur(4px); + align-items: center; + justify-content: center; + padding: 1rem; +} + +.gift-settings-modal { + background: var(--bg-panel); + border: 1px solid var(--border-light); + border-radius: 14px; + width: 100%; + max-width: 500px; + max-height: 90vh; + display: flex; + flex-direction: column; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); + animation: modalIn 0.18s ease; +} + +@keyframes modalIn { + from { + opacity: 0; + transform: scale(0.96) translateY(-8px); + } + to { + opacity: 1; + transform: none; + } +} + +/* ========================================================================== + MODALE PARAMÈTRES (Design System Todo) + ========================================================================== */ +.gift-settings-backdrop { + display: none; + position: fixed; + inset: 0; + z-index: 200; + background: rgba(15, 23, 42, 0.5); + backdrop-filter: blur(4px); + align-items: center; + justify-content: center; + padding: 1rem; +} +.gift-settings-backdrop.show { + display: flex; +} + +.gift-settings-modal { + background: var(--bg-panel); + border: 1px solid var(--border-light); + border-radius: 14px; + width: 100%; + max-width: 500px; + max-height: 90vh; + display: flex; + flex-direction: column; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); + animation: giftModalIn 0.18s ease; +} + +@keyframes giftModalIn { + from { + opacity: 0; + transform: scale(0.96) translateY(-8px); + } + to { + opacity: 1; + transform: none; + } +} + +.gift-settings-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1rem 1.25rem; + border-bottom: 1px solid var(--border-light); +} +.gift-settings-header h3 { + font-size: 1rem; + font-weight: 700; + margin: 0; +} +.gift-settings-close { + background: none; + border: none; + cursor: pointer; + color: var(--text-muted); + font-size: 1.2rem; + border-radius: 6px; + padding: 0.2rem 0.4rem; +} +.gift-settings-close:hover { + background: var(--bg-page); +} + +.gift-settings-body { + padding: 1.25rem; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 0.9rem; +} + +.gift-add-subtitle { + font-size: 0.72rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); + margin: 0.5rem 0 0; +} +.gift-add-form { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} +/* Aligner le bouton sur celui de Todo */ +.gift-add-form .btn-primary { + background: var(--primary); + color: #fff; + border-color: var(--primary); +} +.gift-add-form .btn-primary:hover { + background: var(--primary-dark); +}
    \ \