1 parent
693b277002
commit
a7a8244a33
12 files changed
+268
-114
No files matched your search
+22
-3
@@ -16,8 +16,23 @@ $hasBudgetItems = ((int)$pdo->query("SELECT COUNT(*) FROM pf_budget_items")->fet
|
||||
// Le minimum vital pour que ça tourne
|
||||
$isBudgetSetupOk = ($hasCategories && $hasSalaries && $hasAccounts);
|
||||
|
||||
// Gestion de l'onglet actif (par défaut 'suivi')
|
||||
// ============================================================================
|
||||
// GESTION DES VUES (MANDATORY VS OPTIONAL)
|
||||
// ============================================================================
|
||||
$enabledViews = $_SESSION['enabled_views'] ?? ['budget_prev', 'budget_epargne', 'budget_provisions'];
|
||||
|
||||
// Vues obligatoires pour le cœur du budget
|
||||
$allowedTabs = ['suivi', 'recap'];
|
||||
|
||||
if (in_array('budget_prev', $enabledViews)) $allowedTabs[] = 'budget_prev';
|
||||
if (in_array('budget_epargne', $enabledViews)) $allowedTabs[] = 'epargne';
|
||||
if (in_array('budget_provisions', $enabledViews)) $allowedTabs[] = 'provisions';
|
||||
|
||||
// Gestion de l'onglet actif avec fallback de sécurité
|
||||
$tab = $_GET['tab'] ?? 'suivi';
|
||||
if (!in_array($tab, $allowedTabs)) {
|
||||
$tab = 'suivi';
|
||||
}
|
||||
|
||||
$pageTitle = tr('budget_page_title');
|
||||
$activePage = "budget";
|
||||
@@ -30,7 +45,6 @@ require __DIR__ . '/header.php';
|
||||
<div class="pf-hero budget-hero">
|
||||
<div class="budget-title-group">
|
||||
<h1><?= tr('budget_main_header') ?></h1>
|
||||
<!-- Bouton avec séparation claire entre l'engrenage animable et l'alerte fixe -->
|
||||
<button class="btn-header-settings" onclick="openBudgetSettings()" title="<?= tr('settings') ?>">
|
||||
<span class="gear-icon-anim">⚙️</span>
|
||||
<?php if (!$isBudgetSetupOk || !$hasBudgetItems): ?>
|
||||
@@ -45,26 +59,31 @@ require __DIR__ . '/header.php';
|
||||
<span><?= tr('budget_tab_tracking') ?></span>
|
||||
</a>
|
||||
|
||||
<?php if (in_array('budget_prev', $allowedTabs)): ?>
|
||||
<a href="?tab=budget_prev" class="tab-item <?= $tab == 'budget_prev' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🎯</span>
|
||||
<span><?= tr('budget_tab_prev') ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('epargne', $allowedTabs)): ?>
|
||||
<a href="?tab=epargne" class="tab-item <?= $tab == 'epargne' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🐷</span>
|
||||
<span><?= tr('budget_tab_savings') ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="?tab=recap" class="tab-item <?= $tab == 'recap' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">📊</span>
|
||||
<span><?= tr('budget_tab_recap') ?></span>
|
||||
</a>
|
||||
|
||||
<?php if (in_array('provisions', $allowedTabs)): ?>
|
||||
<a href="?tab=provisions" class="tab-item <?= $tab == 'provisions' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🧮</span>
|
||||
<span><?= tr('budget_tab_provisions') ?></span>
|
||||
</a>
|
||||
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -208,3 +208,15 @@
|
||||
[data-theme="dark"] .month-actions .btn-icon-small:hover {
|
||||
background: rgba(255, 255, 255, 0.06) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .pf-module-card-wrapper:has(input.js-mod-checkbox:checked) {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
[data-theme="dark"] .pf-sub-module-pill {
|
||||
background: var(--bg-page);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
[data-theme="dark"] .pf-sub-module-pill:has(input:checked) {
|
||||
background: var(--bg-panel);
|
||||
border-color: var(--primary);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS families (
|
||||
invite_code VARCHAR(32) NOT NULL UNIQUE,
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
enabled_modules JSON DEFAULT NULL,
|
||||
enabled_views JSON DEFAULT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
|
||||
@@ -5,8 +5,26 @@ require __DIR__ . '/includes/db.php';
|
||||
require_once __DIR__ . '/includes/i18n.php';
|
||||
require_login('/login.php');
|
||||
|
||||
// Gestion de l'onglet actif
|
||||
$tab = $_GET['tab'] ?? 'meals';
|
||||
// 1. Détection des vues actives
|
||||
$enabledViews = $_SESSION['enabled_views'] ?? ['food_meals', 'food_liste'];
|
||||
$allowedTabs = [];
|
||||
|
||||
if (in_array('food_meals', $enabledViews)) $allowedTabs[] = 'meals';
|
||||
if (in_array('food_liste', $enabledViews)) $allowedTabs[] = 'liste';
|
||||
|
||||
// 2. Fallback intelligent de l'onglet actif
|
||||
if (empty($allowedTabs)) {
|
||||
$tab = 'empty'; // L'utilisateur a désactivé les deux vues
|
||||
} else {
|
||||
// Par défaut, on prend le premier onglet disponible (Meals, ou Liste si Meals est désactivé)
|
||||
$defaultTab = $allowedTabs[0];
|
||||
$tab = $_GET['tab'] ?? $defaultTab;
|
||||
|
||||
// Sécurité URL : Si on force un onglet non autorisé
|
||||
if (!in_array($tab, $allowedTabs)) {
|
||||
$tab = $defaultTab;
|
||||
}
|
||||
}
|
||||
|
||||
$pageTitle = tr('mod_food_name');
|
||||
$activePage = "food";
|
||||
@@ -21,30 +39,44 @@ require __DIR__ . '/header.php';
|
||||
<h1>🍳 <?= tr('mod_food_name') ?></h1>
|
||||
</div>
|
||||
|
||||
<?php if ($tab !== 'empty'): ?>
|
||||
<nav class="budget-tabs-container">
|
||||
<?php if (in_array('meals', $allowedTabs)): ?>
|
||||
<a href="?tab=meals" class="tab-item <?= $tab == 'meals' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🍽️</span>
|
||||
<span><?= tr('food_tab_meals') ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('liste', $allowedTabs)): ?>
|
||||
<a href="?tab=liste" class="tab-item <?= $tab == 'liste' ? 'active' : '' ?>">
|
||||
<span class="tab-icon">🛒</span>
|
||||
<span><?= tr('food_tab_list') ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<section class="pf-section">
|
||||
<?php
|
||||
$allowedTabs = ['meals', 'liste'];
|
||||
if (in_array($tab, $allowedTabs)) {
|
||||
if ($tab === 'empty') {
|
||||
// État Vide Intégré (Zéro vue activée)
|
||||
echo "
|
||||
<div class='pf-empty-state' style='text-align:center; padding: 60px 20px; max-width: 600px; margin: 0 auto; background: var(--bg-panel); border-radius: 16px; border: 1px dashed var(--border-strong); box-shadow: var(--shadow-sm);'>
|
||||
<div style='font-size: 4rem; margin-bottom: 20px; opacity: 0.5;'>👀</div>
|
||||
<h3 style='color: var(--text-main); margin-bottom: 10px; font-size: 1.3rem;'>" . tr('food_err_no_views') . "</h3>
|
||||
<p style='color: var(--text-muted); margin-bottom: 30px; font-size: 0.95rem; line-height: 1.5;'>" . tr('food_err_no_views_desc') . "</p>
|
||||
<a href='/settings.php' class='pf-btn'>⚙️ " . tr('settings') . "</a>
|
||||
</div>";
|
||||
} else {
|
||||
// Routage classique
|
||||
$viewPath = __DIR__ . "/modules/food/views/{$tab}.php";
|
||||
if (file_exists($viewPath)) {
|
||||
require $viewPath;
|
||||
} else {
|
||||
echo "<p>Vue introuvable : " . htmlspecialchars($tab) . "</p>";
|
||||
}
|
||||
} else {
|
||||
echo "<p>" . tr('error_occured') . "</p>";
|
||||
}
|
||||
?>
|
||||
</section>
|
||||
|
||||
+112
-13
@@ -410,25 +410,124 @@ body.pf-home .pf-mobile-close-btn {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Tuile module (checkbox) — état coché via :has(), compatible thème */
|
||||
.pf-module-tile {
|
||||
/* --- MODULES & SOUS-VUES UNIFIÉS (Settings) --- */
|
||||
.pf-module-card-wrapper {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 12px;
|
||||
padding: 12px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
border: 2px solid var(--border-light);
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
background: var(--bg-page);
|
||||
transition:
|
||||
border-color 0.15s ease,
|
||||
background 0.15s ease;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.pf-module-tile:has(input:checked) {
|
||||
|
||||
/* Le fond bleu léger s'applique à TOUTE la carte si le module est actif */
|
||||
.pf-module-card-wrapper:has(input.js-mod-checkbox:checked) {
|
||||
border-color: var(--primary);
|
||||
background: var(--bg-soft);
|
||||
}
|
||||
|
||||
/* Ligne supérieure : Titre + Pills (Alignement horizontal + Wrap auto) */
|
||||
.pf-module-top-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pf-module-tile-compact {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pf-tile-icon {
|
||||
font-size: 1.3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pf-tile-label {
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
/* --- Sous-Vues (Pills) --- */
|
||||
.pf-module-inline-pills {
|
||||
display: none;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.pf-module-card-wrapper:has(input.js-mod-checkbox:checked)
|
||||
.pf-module-inline-pills {
|
||||
display: flex;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.pf-sub-module-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 4px 12px;
|
||||
border-radius: 50px;
|
||||
background: var(--bg-page);
|
||||
border: 1px solid var(--border-strong);
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.pf-sub-module-pill input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pf-sub-module-pill .pf-pill-text {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.pf-sub-module-pill:hover {
|
||||
border-color: var(--text-main);
|
||||
}
|
||||
|
||||
/* État actif de la Pill */
|
||||
.pf-sub-module-pill:has(input:checked) {
|
||||
background: var(--bg-panel);
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 1px 2px rgba(59, 130, 246, 0.15);
|
||||
}
|
||||
|
||||
.pf-sub-module-pill:has(input:checked) .pf-pill-text {
|
||||
color: var(--primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Note informative en bas de carte */
|
||||
.pf-module-info-footer {
|
||||
display: none;
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
margin-top: 4px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px dashed var(--border-strong);
|
||||
}
|
||||
|
||||
.pf-module-card-wrapper:has(input.js-mod-checkbox:checked)
|
||||
.pf-module-info-footer {
|
||||
display: block;
|
||||
animation: fadeIn 0.2s ease-out;
|
||||
}
|
||||
|
||||
.pf-checkbox-lg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
||||
+2
-1
@@ -35,7 +35,7 @@ function require_login(?string $loginPage = '/login.php'): void
|
||||
|
||||
$stmt = $meta_pdo->prepare("
|
||||
SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, u.is_admin, u.is_active, u.lang, u.remember_token,
|
||||
f.db_name, f.is_active as family_active, f.enabled_modules
|
||||
f.db_name, f.is_active as family_active, f.enabled_modules, f.enabled_views
|
||||
FROM users u
|
||||
LEFT JOIN families f ON f.id = u.family_id
|
||||
WHERE u.id = ?
|
||||
@@ -57,6 +57,7 @@ function require_login(?string $loginPage = '/login.php'): void
|
||||
$_SESSION['family_db'] = $user['db_name'];
|
||||
$_SESSION['app_lang'] = $user['lang'] ?? 'fr';
|
||||
$_SESSION['enabled_modules'] = json_decode($user['enabled_modules'] ?? '["calendar","budget","holidays","gifts","calendar_ios"]', true);
|
||||
$_SESSION['enabled_views'] = json_decode($user['enabled_views'] ?? '["food_meals","food_liste"]', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ return [
|
||||
'mod_printvault_desc'=> 'Gestioneu els vostres fitxers d\'impressió 3D — STL, 3MF, GCode.',
|
||||
'mod_planka_name' => 'Planka',
|
||||
'mod_planka_desc' => 'Gestioneu els vostres projectes en mode Kanban amb Planka.',
|
||||
'budget_mandatory_note' => 'Les pestanyes de Seguiment i Resum són obligatòries i sempre actives.',
|
||||
|
||||
'cta_open' => 'Obrir',
|
||||
'cta_explore' => 'Explorar',
|
||||
@@ -1031,4 +1032,6 @@ return [
|
||||
'menu_food' => 'Menjar',
|
||||
'mod_food_desc' => 'Gestioneu els vostres menús setmanals i les llistes de la compra compartides.',
|
||||
'cta_food' => 'Cuinar',
|
||||
'food_err_no_views' => "Cap vista activada per a aquest mòdul. Modifiqueu la vostra configuració d'espai.",
|
||||
'food_err_no_views_desc' => 'Heu desactivat totes les funcions d\'aquest mòdul. Aneu a la configuració per reactivar-les.',
|
||||
];
|
||||
@@ -105,6 +105,7 @@ return [
|
||||
'mod_printvault_desc'=> 'Manage your 3D print files — STL, 3MF, GCode with interactive viewer.',
|
||||
'mod_planka_name' => 'Planka',
|
||||
'mod_planka_desc' => 'Manage your projects in Kanban mode with Planka.',
|
||||
'budget_mandatory_note' => 'Les onglets Suivi et Récapitulatif sont indispensables et toujours actifs.',
|
||||
|
||||
'cta_open' => 'Open',
|
||||
'cta_explore' => 'Explore',
|
||||
@@ -542,6 +543,7 @@ return [
|
||||
'bud_err_tech' => 'A technical error occurred.',
|
||||
'bud_err_server' => 'Server error: ',
|
||||
'bud_err_delete' => 'Error while deleting.',
|
||||
'budget_mandatory_note' => 'Tracking and Summary tabs are mandatory and always active.',
|
||||
|
||||
// Categories (Budget)
|
||||
'cat_income' => 'Income',
|
||||
@@ -1034,4 +1036,7 @@ return [
|
||||
'menu_food' => 'Food',
|
||||
'mod_food_desc' => 'Manage your weekly menus and shared shopping lists.',
|
||||
'cta_food' => 'Cook up',
|
||||
'food_err_no_views' => "No view is enabled for this module. Please check your space settings.",
|
||||
'food_err_no_views_desc' => 'You have disabled all features for this module. Please visit your settings to re-enable them.',
|
||||
|
||||
];
|
||||
@@ -105,6 +105,7 @@ return [
|
||||
'mod_printvault_desc'=> 'Gérez vos fichiers d\'impression 3D — STL, 3MF, GCode avec viewer interactif.',
|
||||
'mod_planka_name' => 'Planka',
|
||||
'mod_planka_desc' => 'Gérez vos projets en mode Kanban avec Planka.',
|
||||
'budget_mandatory_note' => 'Les onglets Suivi et Récapitulatif sont indispensables et toujours actifs.',
|
||||
|
||||
'cta_open' => 'Ouvrir',
|
||||
'cta_explore' => 'Explorer',
|
||||
@@ -1030,4 +1031,7 @@ return [
|
||||
'menu_food' => 'Food',
|
||||
'mod_food_desc' => 'Gérez vos menus de la semaine et vos listes de courses partagées.',
|
||||
'cta_food' => 'Mijoter',
|
||||
'food_err_no_views' => "Aucune vue n'est activée pour ce module. Modifiez vos paramètres d'espace.",
|
||||
'food_err_no_views_desc' => 'Vous avez désactivé toutes les fonctionnalités de ce module. Rendez-vous dans vos paramètres pour les réactiver.',
|
||||
|
||||
];
|
||||
@@ -25,8 +25,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$error = tr('error_missing_fields');
|
||||
} else {
|
||||
$stmt = $meta_pdo->prepare("
|
||||
SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, u.is_admin, u.is_active, u.lang,
|
||||
f.db_name, f.is_active as family_active, f.enabled_modules
|
||||
SELECT u.id, u.username, u.password_hash, u.display_name, u.family_id, u.is_admin, u.is_active, u.lang, u.remember_token,
|
||||
f.db_name, f.is_active as family_active, f.enabled_modules, f.enabled_views
|
||||
FROM users u
|
||||
LEFT JOIN families f ON f.id = u.family_id
|
||||
WHERE u.username = ?
|
||||
@@ -51,6 +51,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$_SESSION['family_db'] = $user['db_name'];
|
||||
$_SESSION['app_lang'] = $user['lang'] ?? 'fr';
|
||||
$_SESSION['enabled_modules'] = json_decode($user['enabled_modules'] ?? '["calendar","budget","holidays","gifts","calendar_ios"]', true);
|
||||
$_SESSION['enabled_views'] = json_decode($user['enabled_views'] ?? '["food_meals","food_liste"]', true);
|
||||
|
||||
// --- NOUVEAU : Logique "Se souvenir de moi" ---
|
||||
if (isset($_POST['remember_me']) && $_POST['remember_me'] === '1') {
|
||||
|
||||
+12
-62
@@ -4,78 +4,28 @@ require __DIR__ . '/includes/auth.php';
|
||||
require __DIR__ . '/includes/meta_db.php';
|
||||
require_login();
|
||||
|
||||
// Sécurité : Accès limité aux administrateurs
|
||||
if (empty($_SESSION['user']['is_admin'])) {
|
||||
die("Accès refusé. Droits administrateur requis.");
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $meta_pdo->query("SELECT db_name FROM families");
|
||||
$families = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
echo "<h1>Migration de la base Meta (househub_meta)</h1>";
|
||||
echo "Vérification de la structure de la table 'families'...<br>";
|
||||
|
||||
foreach ($families as $fam) {
|
||||
$dbName = $fam['db_name'];
|
||||
echo "Migration de la base : $dbName ... <br>";
|
||||
// Vérification de l'existence de la colonne pour rendre le script idempotent
|
||||
$checkMetaCol = $meta_pdo->query("SHOW COLUMNS FROM families LIKE 'enabled_views'");
|
||||
|
||||
// --- DÉBUT DU BLOC MODIFIÉ ---
|
||||
$envHost = getenv('DB_HOST');
|
||||
if ($envHost === 'househub-db') {
|
||||
$host = 'househub-db';
|
||||
$user = getenv('DB_USER') ?: 'househub';
|
||||
$pass = getenv('DB_PASS') ?: 'changeme';
|
||||
if ($checkMetaCol->rowCount() == 0) {
|
||||
// Ajout de la colonne JSON
|
||||
$meta_pdo->exec("ALTER TABLE families ADD COLUMN enabled_views JSON DEFAULT NULL AFTER enabled_modules");
|
||||
echo "✅ <strong>Succès :</strong> Colonne 'enabled_views' ajoutée à househub_meta.families.<br><br>";
|
||||
} else {
|
||||
$host = '127.0.0.1';
|
||||
$user = 'root';
|
||||
$pass = '';
|
||||
echo "ℹ️ <strong>Info :</strong> La colonne 'enabled_views' est déjà présente. Aucune modification requise.<br><br>";
|
||||
}
|
||||
|
||||
$familyPdo = new PDO(
|
||||
"mysql:host=$host;dbname=$dbName;charset=utf8mb4",
|
||||
$user,
|
||||
$pass,
|
||||
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
|
||||
);
|
||||
// --- FIN DU BLOC MODIFIÉ ---
|
||||
|
||||
$sql = "
|
||||
CREATE TABLE IF NOT EXISTS pf_meals_plan (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
plan_date DATE NOT NULL,
|
||||
service ENUM('lunch', 'dinner') NOT NULL,
|
||||
person_id INT NOT NULL,
|
||||
meal_name VARCHAR(500) DEFAULT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_meal_plan (plan_date, service, person_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
";
|
||||
|
||||
$familyPdo->exec($sql);
|
||||
echo "✅ Table pf_meals_plan créée pour $dbName.<br>";
|
||||
}
|
||||
echo "<br>🎉 Migration terminée avec succès.";
|
||||
|
||||
// À ajouter dans migrate.php
|
||||
echo "Migration : Ajout des colonnes dynamiques au Budget...\n";
|
||||
$stmtFam = $meta_pdo->query("SELECT id, db_name FROM families");
|
||||
$families = $stmtFam->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
foreach ($families as $fam) {
|
||||
$dbName = $fam['db_name'];
|
||||
try {
|
||||
$famPdo = new PDO("mysql:host=$meta_host;dbname=$dbName;charset=utf8mb4", $meta_user, $meta_pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
|
||||
|
||||
// Vérification si la colonne n'existe pas déjà
|
||||
$checkCol = $famPdo->query("SHOW COLUMNS FROM pf_budget_items LIKE 'is_dynamic'");
|
||||
if ($checkCol->rowCount() == 0) {
|
||||
$famPdo->exec("ALTER TABLE pf_budget_items
|
||||
ADD COLUMN is_dynamic TINYINT(1) DEFAULT 0 AFTER is_estimate,
|
||||
ADD COLUMN dynamic_code VARCHAR(50) DEFAULT NULL AFTER is_dynamic");
|
||||
echo "Famille {$fam['id']} ($dbName) : Colonnes is_dynamic et dynamic_code ajoutées.\n";
|
||||
}
|
||||
} catch (\PDOException $e) {
|
||||
echo "Erreur sur la famille {$fam['id']} ($dbName) : " . $e->getMessage() . "\n";
|
||||
}
|
||||
}
|
||||
echo "🎉 Migration Meta terminée avec succès.";
|
||||
|
||||
} catch (Exception $e) {
|
||||
die("❌ Erreur lors de la migration : " . $e->getMessage());
|
||||
die("❌ <strong>Erreur lors de la migration :</strong> " . $e->getMessage());
|
||||
}
|
||||
+54
-27
@@ -41,14 +41,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = $_POST['action'] ?? '';
|
||||
|
||||
if ($action === 'set_modules' && $family_id) {
|
||||
$all = ['calendar', 'budget', 'holidays', 'gifts', 'garage', 'memo', 'todo', 'food', 'calendar_ios', 'printvault', 'planka'];
|
||||
$all = ['calendar', 'budget', 'holidays', 'gifts', 'garage', 'memo', 'todo', 'liste', 'calendar_ios', 'printvault', 'planka', 'food'];
|
||||
$allViews = ['food_meals', 'food_liste', 'budget_prev', 'budget_epargne', 'budget_provisions'];
|
||||
|
||||
$enabled = array_values(array_filter($all, fn($m) => isset($_POST['mod_' . $m])));
|
||||
$enabledViews = array_values(array_filter($allViews, fn($v) => isset($_POST['view_' . $v])));
|
||||
|
||||
if (empty($enabled)) {
|
||||
$error = tr('set_err_min_module');
|
||||
} else {
|
||||
$meta_pdo->prepare("UPDATE families SET enabled_modules = ? WHERE id = ?")
|
||||
->execute([json_encode($enabled), $family_id]);
|
||||
$meta_pdo->prepare("UPDATE families SET enabled_modules = ?, enabled_views = ? WHERE id = ?")
|
||||
->execute([json_encode($enabled), json_encode($enabledViews), $family_id]);
|
||||
$_SESSION['enabled_modules'] = $enabled;
|
||||
$_SESSION['enabled_views'] = $enabledViews;
|
||||
$success = tr('set_success_modules');
|
||||
}
|
||||
}
|
||||
@@ -396,15 +401,34 @@ require __DIR__ . '/header.php';
|
||||
<p class="pf-muted-note"><?= tr('set_desc_modules') ?></p>
|
||||
<?php
|
||||
$enabledMods = $_SESSION['enabled_modules'] ?? ['calendar','budget','holidays','gifts','calendar_ios'];
|
||||
$enabledViews = $_SESSION['enabled_views'] ?? ['food_meals', 'food_liste', 'budget_prev', 'budget_epargne', 'budget_provisions'];
|
||||
|
||||
$allModules = [
|
||||
'calendar' => ['icon' => '📅', 'label' => tr('menu_calendar')],
|
||||
'budget' => ['icon' => '💰', 'label' => tr('menu_budget')],
|
||||
'budget' => [
|
||||
'icon' => '💰',
|
||||
'label' => tr('menu_budget'),
|
||||
'mandatory_note' => tr('budget_mandatory_note'),
|
||||
'views' => [
|
||||
'budget_prev' => tr('budget_tab_prev'),
|
||||
'budget_epargne' => tr('budget_tab_savings'),
|
||||
'budget_provisions' => tr('budget_tab_provisions')
|
||||
]
|
||||
],
|
||||
'holidays' => ['icon' => '🏖️', 'label' => tr('menu_holidays')],
|
||||
'gifts' => ['icon' => '🎁', 'label' => tr('menu_gifts')],
|
||||
'garage' => ['icon' => '🚗', 'label' => tr('menu_garage')],
|
||||
'memo' => ['icon' => '📝', 'label' => tr('menu_memo')],
|
||||
'todo' => ['icon' => '✅', 'label' => tr('menu_todo')],
|
||||
'food' => ['icon' => '🍳', 'label' => tr('menu_food')],
|
||||
'liste' => ['icon' => '🛒', 'label' => tr('menu_liste')],
|
||||
'food' => [
|
||||
'icon' => '🍳',
|
||||
'label' => tr('mod_food_name'),
|
||||
'views' => [
|
||||
'food_meals' => tr('food_tab_meals'),
|
||||
'food_liste' => tr('food_tab_list')
|
||||
]
|
||||
],
|
||||
'calendar_ios' => ['icon' => '📱', 'label' => tr('menu_calendar_ios')],
|
||||
'printvault' => ['icon' => '🖨️', 'label' => tr('menu_printvault')],
|
||||
'planka' => ['icon' => '📋', 'label' => tr('menu_planka')],
|
||||
@@ -415,11 +439,34 @@ require __DIR__ . '/header.php';
|
||||
<input type="hidden" name="action" value="set_modules">
|
||||
<div class="pf-stack-md">
|
||||
<?php foreach ($allModules as $key => $mod): $active = in_array($key, $enabledMods); ?>
|
||||
<label class="pf-module-tile">
|
||||
<input type="checkbox" name="mod_<?= $key ?>" <?= $active ? 'checked' : '' ?> class="pf-checkbox-lg">
|
||||
<div class="pf-module-card-wrapper">
|
||||
|
||||
<div class="pf-module-top-row">
|
||||
<label class="pf-module-tile-compact">
|
||||
<input type="checkbox" name="mod_<?= $key ?>" <?= $active ? 'checked' : '' ?> class="pf-checkbox-lg js-mod-checkbox">
|
||||
<span class="pf-tile-icon"><?= $mod['icon'] ?></span>
|
||||
<span class="pf-tile-label"><?= $mod['label'] ?></span>
|
||||
</label>
|
||||
|
||||
<?php if (!empty($mod['views'])): ?>
|
||||
<div class="pf-module-inline-pills">
|
||||
<?php foreach ($mod['views'] as $vKey => $vLabel): ?>
|
||||
<label class="pf-sub-module-pill">
|
||||
<input type="checkbox" name="view_<?= $vKey ?>" <?= in_array($vKey, $enabledViews) ? 'checked' : '' ?>>
|
||||
<span class="pf-pill-text"><?= htmlspecialchars($vLabel) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($mod['mandatory_note'])): ?>
|
||||
<div class="pf-module-info-footer">
|
||||
ℹ️ <?= $mod['mandatory_note'] ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<button type="submit" class="pf-btn"><?= tr('btn_save') ?></button>
|
||||
@@ -427,26 +474,6 @@ require __DIR__ . '/header.php';
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$enabledModsForFood = $_SESSION['enabled_modules'] ?? [];
|
||||
if ($family_id && in_array('food', $enabledModsForFood, true)):
|
||||
?>
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight">📝 <?= htmlspecialchars(tr('liste_settings_title')) ?></h2>
|
||||
<p class="pf-muted-note"><?= htmlspecialchars(tr('liste_settings_intro')) ?></p>
|
||||
<form method="post" class="pf-stack-md pf-mt-md">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="grocery_history_max">
|
||||
<div class="pf-form-group">
|
||||
<label class="pf-label" for="history_max"><?= htmlspecialchars(tr('liste_settings_history_max')) ?></label>
|
||||
<input type="number" name="history_max" id="history_max" class="pf-input pf-input-sm" min="1" max="50" step="1" value="<?= (int) $groceryHistoryMaxSetting ?>" required>
|
||||
<p class="pf-muted-note pf-mt-xs"><?= htmlspecialchars(tr('liste_settings_history_hint')) ?></p>
|
||||
</div>
|
||||
<button type="submit" class="pf-btn"><?= htmlspecialchars(tr('btn_save')) ?></button>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="pf-panel-card">
|
||||
<h2 class="pf-card-h2 pf-card-h2--tight"><?= tr('set_title_ios') ?></h2>
|
||||
<p class="pf-muted-note"><?= tr('set_desc_ios') ?></p>
|
||||
|
||||
Reference in new issue
Block a user