-
Cadeau
-
×
+
+
+
+
+
+
+
+
= tr('gift_settings_add_title') ?? '+ Ajouter' ?>
+
+
= tr('gift_settings_date_help') ?? 'Optionnel' ?>
+
+
+
+
+
+
+
+
+
\ 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);
+}