This commit is contained in:
2026-03-04 20:24:54 +01:00
parent 730c2a363e
commit 7060a43859
7 changed files with 200 additions and 148 deletions
+13 -16
View File
@@ -4,14 +4,11 @@ require __DIR__ . '/includes/db.php';
require_login('/login.php'); require_login('/login.php');
// Gestion de l'onglet actif (par défaut 'recap') // Gestion de l'onglet actif (par défaut 'recap')
$tab = $_GET['tab'] ?? 'recap'; $tab = $_GET['tab'] ?? 'suivi';
$pageTitle = "PachaFamily - Budget"; $pageTitle = "PachaFamily - Budget";
$activePage = "budget"; $activePage = "budget";
// Note: budget.css est conservé pour le style global,
// mais budget.js n'est plus requis si tu l'as supprimé comme prévu.
$pageCss = "/modules/budget/budget.css"; $pageCss = "/modules/budget/budget.css";
// $pageJs = "/modules/budget/budget.js"; // Ligne commentée ou supprimée
require __DIR__ . '/header.php'; require __DIR__ . '/header.php';
?> ?>
@@ -21,25 +18,25 @@ require __DIR__ . '/header.php';
<h1 style="margin-bottom: 20px;">Gestion du Budget</h1> <h1 style="margin-bottom: 20px;">Gestion du Budget</h1>
<nav class="budget-tabs-container"> <nav class="budget-tabs-container">
<a href="?tab=recap" class="tab-item <?= $tab == 'recap' ? 'active' : '' ?>">
<span class="tab-icon">📊</span>
<span>Récapitulatif</span>
</a>
<a href="?tab=budget_prev" class="tab-item <?= $tab == 'budget_prev' ? 'active' : '' ?>">
<span class="tab-icon">🎯</span>
<span>Budget 2026</span>
</a>
<a href="?tab=suivi" class="tab-item <?= $tab == 'suivi' ? 'active' : '' ?>"> <a href="?tab=suivi" class="tab-item <?= $tab == 'suivi' ? 'active' : '' ?>">
<span class="tab-icon">🗓️</span> <span class="tab-icon">🗓️</span>
<span>Suivi Mensuel</span> <span>Suivi Mensuel</span>
</a> </a>
<a href="?tab=budget_prev" class="tab-item <?= $tab == 'budget_prev' ? 'active' : '' ?>">
<span class="tab-icon">🎯</span>
<span>Budget 2026</span>
</a>
<a href="?tab=epargne" class="tab-item <?= $tab == 'epargne' ? 'active' : '' ?>"> <a href="?tab=epargne" class="tab-item <?= $tab == 'epargne' ? 'active' : '' ?>">
<span class="tab-icon">🐷</span> <span class="tab-icon">🐷</span>
<span>Épargne</span> <span>Épargne</span>
</a> </a>
<a href="?tab=recap" class="tab-item <?= $tab == 'recap' ? 'active' : '' ?>">
<span class="tab-icon">📊</span>
<span>Récapitulatif</span>
</a>
</nav> </nav>
</div> </div>
+26 -26
View File
@@ -1,6 +1,6 @@
<?php <?php
require __DIR__ . '/../../../../includes/auth.php'; require __DIR__ . '/../../../../includes/auth.php';
require __DIR__ . '/../../../../includes/db.php'; // On suppose que $pdo est défini ici require __DIR__ . '/../../../../includes/db.php';
require_login(); require_login();
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
@@ -8,41 +8,42 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// --- ACTION : SAUVEGARDER (AJOUT OU MODIF) --- // --- ACTION : SAUVEGARDER (AJOUT OU MODIF) ---
if ($action === 'save') { if ($action === 'save') {
$id = $_POST['id'] ?? ''; $id = $_POST['id'] ?? '';
$name = $_POST['name']; $name = $_POST['name'];
$amount = $_POST['amount']; $amount = $_POST['amount'];
$category = $_POST['category']; $category = $_POST['category'];
$type = $_POST['type']; $type = $_POST['type'];
$payment_day = $_POST['payment_day']; $payment_day = $_POST['payment_day'];
$is_estimate = $_POST['is_estimate']; $is_estimate = $_POST['is_estimate'];
$reg_month = $_POST['reg_month']; $reg_month = $_POST['reg_month'];
// NOUVEAU : Récupération des mots-clés $keywords = $_POST['mapping_keywords'] ?? '';
$keywords = $_POST['mapping_keywords'] ?? '';
// NOUVEAU : Récupération de l'ID des vacances
$holiday_id = !empty($_POST['holiday_id']) ? (int)$_POST['holiday_id'] : null;
if ($id) { if ($id) {
// UPDATE // UPDATE
$stmt = $pdo->prepare("UPDATE pf_budget_items SET name=?, amount=?, category=?, type=?, payment_day=?, is_estimate=?, reg_month=?, mapping_keywords=? WHERE id=?"); $stmt = $pdo->prepare("UPDATE pf_budget_items SET name=?, amount=?, category=?, type=?, payment_day=?, is_estimate=?, reg_month=?, mapping_keywords=?, holiday_id=? WHERE id=?");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $id]); $stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $holiday_id, $id]);
} else { } else {
// INSERT // INSERT
$stmt = $pdo->prepare("INSERT INTO pf_budget_items (name, amount, category, type, payment_day, is_estimate, reg_month, mapping_keywords) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); $stmt = $pdo->prepare("INSERT INTO pf_budget_items (name, amount, category, type, payment_day, is_estimate, reg_month, mapping_keywords, holiday_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords]); $stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $holiday_id]);
}
header('Location: /budget.php?tab=recap');
exit;
} }
header('Location: /budget.php?tab=recap'); // Ou ta redirection habituelle
exit;
}
// --- ACTION : COCHER/DÉCOCHER RAPIDE (VIA JS FETCH) --- // --- ACTION : COCHER/DÉCOCHER RAPIDE (VIA JS FETCH) ---
if ($action === 'toggle-check') { if ($action === 'toggle-check') {
$id = $_POST['id']; $id = $_POST['id'];
$status = $_POST['status']; // 1 ou 0 $status = $_POST['status'];
$sql = "UPDATE pf_budget_items SET is_checked = ? WHERE id = ?"; $sql = "UPDATE pf_budget_items SET is_checked = ? WHERE id = ?";
$stmt = $pdo->prepare($sql); $stmt = $pdo->prepare($sql);
$stmt->execute([$status, $id]); $stmt->execute([$status, $id]);
// On renvoie du JSON car c'est un appel JS (pas de redirection)
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
exit; exit;
@@ -53,7 +54,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$id = $_POST['id']; $id = $_POST['id'];
$pdo->prepare("DELETE FROM pf_budget_items WHERE id = ?")->execute([$id]); $pdo->prepare("DELETE FROM pf_budget_items WHERE id = ?")->execute([$id]);
// On peut répondre en JSON pour le JS ou rediriger
if (isset($_POST['ajax'])) { if (isset($_POST['ajax'])) {
header('Content-Type: application/json'); header('Content-Type: application/json');
echo json_encode(['success' => true]); echo json_encode(['success' => true]);
+40 -68
View File
@@ -5,8 +5,6 @@ require __DIR__ . '/../../../../includes/auth.php';
require __DIR__ . '/../../../../includes/db.php'; require __DIR__ . '/../../../../includes/db.php';
require_login(); require_login();
// Note : On ne force pas le header JSON partout car add/delete/update utilisent des redirections
// header('Content-Type: application/json');
$action = $_POST['action'] ?? ''; $action = $_POST['action'] ?? '';
@@ -30,7 +28,7 @@ if ($action === 'update_salary_config') {
// 2. MISE A JOUR TABLEAU REPARTITION (AJAX) // 2. MISE A JOUR TABLEAU REPARTITION (AJAX)
if ($action === 'update_allocation') { if ($action === 'update_allocation') {
header('Content-Type: application/json'); // On précise JSON ici header('Content-Type: application/json');
$date = $_POST['month_date']; $date = $_POST['month_date'];
$catId = $_POST['cat_id']; $catId = $_POST['cat_id'];
$person = $_POST['person']; // 'amount_alex' ou 'amount_laia' $person = $_POST['person']; // 'amount_alex' ou 'amount_laia'
@@ -46,25 +44,26 @@ if ($action === 'update_allocation') {
if ($action === 'add_category') { if ($action === 'add_category') {
$name = trim($_POST['name']); $name = trim($_POST['name']);
$target = trim($_POST['target']); $target = trim($_POST['target']);
$holiday_id = !empty($_POST['holiday_id']) ? (int)$_POST['holiday_id'] : null;
if (!empty($name)) { if (!empty($name)) {
$stmt = $pdo->prepare("INSERT INTO pf_alloc_categories (name, target) VALUES (?, ?)"); $stmt = $pdo->prepare("INSERT INTO pf_alloc_categories (name, target, holiday_id) VALUES (?, ?, ?)");
$stmt->execute([$name, $target]); $stmt->execute([$name, $target, $holiday_id]);
} }
// Redirection vers la page précédente
header("Location: " . $_SERVER['HTTP_REFERER']); header("Location: " . $_SERVER['HTTP_REFERER']);
exit; exit;
} }
// 4. MODIFICATION D'UNE CATEGORIE (Nom / Cible) - NOUVEAU BLOC // 4. MODIFICATION D'UNE CATEGORIE
if ($action === 'update_category') { if ($action === 'update_category') {
$id = (int)$_POST['cat_id']; $id = (int)$_POST['cat_id'];
$name = trim($_POST['name']); $name = trim($_POST['name']);
$target = trim($_POST['target']); $target = trim($_POST['target']);
$holiday_id = !empty($_POST['holiday_id']) ? (int)$_POST['holiday_id'] : null;
if ($id > 0 && !empty($name)) { if ($id > 0 && !empty($name)) {
$stmt = $pdo->prepare("UPDATE pf_alloc_categories SET name = ?, target = ? WHERE id = ?"); $stmt = $pdo->prepare("UPDATE pf_alloc_categories SET name = ?, target = ?, holiday_id = ? WHERE id = ?");
$stmt->execute([$name, $target, $id]); $stmt->execute([$name, $target, $holiday_id, $id]);
} }
header("Location: " . $_SERVER['HTTP_REFERER']); header("Location: " . $_SERVER['HTTP_REFERER']);
exit; exit;
@@ -72,11 +71,11 @@ if ($action === 'update_category') {
// 5. SUPPRESSION CATEGORIE // 5. SUPPRESSION CATEGORIE
if ($action === 'delete_category') { if ($action === 'delete_category') {
$id = (int)$_GET['id'] ?? (int)$_POST['id']; // Peut venir du GET (lien) ou POST $id = (int)$_GET['id'] ?? (int)$_POST['id'];
if ($id > 0) { if ($id > 0) {
$pdo->prepare("DELETE FROM pf_alloc_categories WHERE id = ?")->execute([$id]); $pdo->prepare("DELETE FROM pf_alloc_categories WHERE id = ?")->execute([$id]);
$pdo->prepare("DELETE FROM pf_alloc_values WHERE cat_id = ?")->execute([$id]); // Nettoyage valeurs $pdo->prepare("DELETE FROM pf_alloc_values WHERE cat_id = ?")->execute([$id]);
} }
// Redirection vers la page précédente // Redirection vers la page précédente
header("Location: " . $_SERVER['HTTP_REFERER']); header("Location: " . $_SERVER['HTTP_REFERER']);
@@ -86,17 +85,14 @@ if ($action === 'delete_category') {
// 6. VALIDATION DES VIREMENTS (Complex Business Logic) // 6. VALIDATION DES VIREMENTS (Complex Business Logic)
if ($action === 'validate_transfers') { if ($action === 'validate_transfers') {
header('Content-Type: application/json'); header('Content-Type: application/json');
$person = $_POST['person'];
$person = $_POST['person']; // Alex ou Laia
$monthDate = $_POST['month_date']; $monthDate = $_POST['month_date'];
try { try {
$pdo->beginTransaction(); $pdo->beginTransaction();
// 1. Récupérer tous les virements prévus pour ce mois/personne dans le BUDGET
// On joint avec les catégories pour avoir le nom et la cible
$stmt = $pdo->prepare(" $stmt = $pdo->prepare("
SELECT v.*, c.name as cat_name, c.target SELECT v.*, c.name as cat_name, c.target, c.holiday_id
FROM pf_alloc_values v FROM pf_alloc_values v
JOIN pf_alloc_categories c ON v.cat_id = c.id JOIN pf_alloc_categories c ON v.cat_id = c.id
WHERE v.month_date = ? WHERE v.month_date = ?
@@ -104,28 +100,21 @@ if ($action === 'validate_transfers') {
$stmt->execute([$monthDate]); $stmt->execute([$monthDate]);
$budgetLines = $stmt->fetchAll(PDO::FETCH_ASSOC); $budgetLines = $stmt->fetchAll(PDO::FETCH_ASSOC);
// On prépare les totaux à transférer par Cible
// Structure : ['Alex' => ['total'=>100, 'cats'=>['Noel'=>50, 'Eco'=>50]], 'Pol' => ...]
$transfersToDo = []; $transfersToDo = [];
foreach ($budgetLines as $line) { foreach ($budgetLines as $line) {
$amount = ($person === 'Alex') ? $line['amount_alex'] : $line['amount_laia']; $amount = ($person === 'Alex') ? $line['amount_alex'] : $line['amount_laia'];
if ($amount <= 0) continue; // Rien à virer if ($amount <= 0) continue;
$target = trim($line['target']); $target = trim($line['target']);
$catName = trim($line['cat_name']); $catName = trim($line['cat_name']);
$holidayId = $line['holiday_id'];
// MAPPING DES PROPRIÉTAIRES CIBLES
$targetOwner = null; $targetOwner = null;
if ($target === 'vers L.Perso') { if ($target === 'vers L.Perso') { $targetOwner = $person; }
$targetOwner = $person; // Alex -> Alex, Laia -> Laia elseif ($target === 'vers L.Pol') { $targetOwner = 'Pol'; }
} elseif ($target === 'vers L.Pol') { elseif ($target === 'vers L.Pep') { $targetOwner = 'Pep'; }
$targetOwner = 'Pol'; elseif ($target === 'vers commune') { continue; }
} elseif ($target === 'vers L.Pep') {
$targetOwner = 'Pep';
} elseif ($target === 'vers commune') {
continue; // On ignore (Business Rule)
}
if ($targetOwner) { if ($targetOwner) {
if (!isset($transfersToDo[$targetOwner])) { if (!isset($transfersToDo[$targetOwner])) {
@@ -134,69 +123,55 @@ if ($action === 'validate_transfers') {
$transfersToDo[$targetOwner]['total_add'] += $amount; $transfersToDo[$targetOwner]['total_add'] += $amount;
if (!isset($transfersToDo[$targetOwner]['cats'][$catName])) { if (!isset($transfersToDo[$targetOwner]['cats'][$catName])) {
$transfersToDo[$targetOwner]['cats'][$catName] = 0; $transfersToDo[$targetOwner]['cats'][$catName] = ['amount' => 0, 'holiday_id' => $holidayId];
} }
$transfersToDo[$targetOwner]['cats'][$catName] += $amount; $transfersToDo[$targetOwner]['cats'][$catName]['amount'] += $amount;
} }
} }
// 2. Traiter chaque Propriétaire Cible (Alex, Laia, Pol, Pep)
foreach ($transfersToDo as $owner => $data) { foreach ($transfersToDo as $owner => $data) {
// A. VERIFIER EXISTENCE (Inchangé)
// A. VÉRIFIER SI LE MOIS EXISTE EN EPARGNE
$stmtCheck = $pdo->prepare("SELECT COUNT(*) FROM pf_savings WHERE owner = ? AND month_date = ? AND category = 'TOTAL_BANQUE'"); $stmtCheck = $pdo->prepare("SELECT COUNT(*) FROM pf_savings WHERE owner = ? AND month_date = ? AND category = 'TOTAL_BANQUE'");
$stmtCheck->execute([$owner, $monthDate]); $stmtCheck->execute([$owner, $monthDate]);
$exists = $stmtCheck->fetchColumn() > 0; $exists = $stmtCheck->fetchColumn() > 0;
if (!$exists) { if (!$exists) {
// SCENARIO : LE MOIS N'EXISTE PAS -> DUPLICATION DEPUIS M-1
$prevDate = date('Y-m-d', strtotime($monthDate . ' -1 month')); $prevDate = date('Y-m-d', strtotime($monthDate . ' -1 month'));
$stmtPrev = $pdo->prepare("SELECT category, amount, holiday_id FROM pf_savings WHERE owner = ? AND month_date = ?");
// Récup M-1
$stmtPrev = $pdo->prepare("SELECT category, amount FROM pf_savings WHERE owner = ? AND month_date = ?");
$stmtPrev->execute([$owner, $prevDate]); $stmtPrev->execute([$owner, $prevDate]);
$prevLines = $stmtPrev->fetchAll(PDO::FETCH_KEY_PAIR); // [Cat => Montant] $prevLines = $stmtPrev->fetchAll(PDO::FETCH_ASSOC);
if (empty($prevLines)) { if (empty($prevLines)) { $prevLines = [['category' => 'TOTAL_BANQUE', 'amount' => 0, 'holiday_id' => null]]; }
// Si M-1 n'existe pas non plus, on initialise à 0 (ou on lève une erreur selon préférence)
$prevLines = ['TOTAL_BANQUE' => 0];
}
// Insertion M (Copie de M-1) $stmtInsert = $pdo->prepare("INSERT INTO pf_savings (month_date, owner, category, amount, holiday_id) VALUES (?, ?, ?, ?, ?)");
$stmtInsert = $pdo->prepare("INSERT INTO pf_savings (month_date, owner, category, amount) VALUES (?, ?, ?, ?)"); foreach ($prevLines as $row) {
foreach ($prevLines as $cat => $amt) { $stmtInsert->execute([$monthDate, $owner, $row['category'], $row['amount'], $row['holiday_id']]);
$stmtInsert->execute([$monthDate, $owner, $cat, $amt]);
} }
} }
// B. MISE À JOUR DU TOTAL_BANQUE // B. UPDATE TOTAL (Inchangé)
// On ajoute le montant du virement au montant existant (qu'il vienne d'être créé ou non)
$stmtUpdTotal = $pdo->prepare("UPDATE pf_savings SET amount = amount + ? WHERE owner = ? AND month_date = ? AND category = 'TOTAL_BANQUE'"); $stmtUpdTotal = $pdo->prepare("UPDATE pf_savings SET amount = amount + ? WHERE owner = ? AND month_date = ? AND category = 'TOTAL_BANQUE'");
$stmtUpdTotal->execute([$data['total_add'], $owner, $monthDate]); $stmtUpdTotal->execute([$data['total_add'], $owner, $monthDate]);
// C. MISE À JOUR / CRÉATION DES CATÉGORIES // C. UPDATE CATÉGORIES (Modifié pour gérer le holiday_id)
foreach ($data['cats'] as $catName => $catAmount) { foreach ($data['cats'] as $catName => $catInfo) {
$catAmount = $catInfo['amount'];
// --- REGLE METIER : IGNORER LA CREATION DE LIGNE POUR LES ECO --- $catHolidayId = $catInfo['holiday_id']; // NOUVEAU
// Ces montants ont déjà été ajoutés au TOTAL_BANQUE (étape B juste au-dessus),
// mais on ne veut pas voir apparaître une ligne "Eco Alex" dans le détail. if ($catName === 'Eco Alex' || $catName === 'Eco Laia') { continue; }
if ($catName === 'Eco Alex' || $catName === 'Eco Laia') {
continue;
}
// On vérifie si la catégorie existe déjà
$stmtCheckCat = $pdo->prepare("SELECT id FROM pf_savings WHERE owner = ? AND month_date = ? AND category = ?"); $stmtCheckCat = $pdo->prepare("SELECT id FROM pf_savings WHERE owner = ? AND month_date = ? AND category = ?");
$stmtCheckCat->execute([$owner, $monthDate, $catName]); $stmtCheckCat->execute([$owner, $monthDate, $catName]);
$catId = $stmtCheckCat->fetchColumn(); $catId = $stmtCheckCat->fetchColumn();
if ($catId) { if ($catId) {
// Update : Sommer // Update : On actualise aussi le holiday_id au cas où il aurait changé
$stmtUpdateCat = $pdo->prepare("UPDATE pf_savings SET amount = amount + ? WHERE id = ?"); $stmtUpdateCat = $pdo->prepare("UPDATE pf_savings SET amount = amount + ?, holiday_id = ? WHERE id = ?");
$stmtUpdateCat->execute([$catAmount, $catId]); $stmtUpdateCat->execute([$catAmount, $catHolidayId, $catId]);
} else { } else {
// Insert : Créer // Insert
$stmtInsertCat = $pdo->prepare("INSERT INTO pf_savings (month_date, owner, category, amount) VALUES (?, ?, ?, ?)"); $stmtInsertCat = $pdo->prepare("INSERT INTO pf_savings (month_date, owner, category, amount, holiday_id) VALUES (?, ?, ?, ?, ?)");
$stmtInsertCat->execute([$monthDate, $owner, $catName, $catAmount]); $stmtInsertCat->execute([$monthDate, $owner, $catName, $catAmount, $catHolidayId]);
} }
} }
} }
@@ -209,9 +184,6 @@ if ($action === 'validate_transfers') {
$sysCatId = $stmtSys->fetchColumn(); $sysCatId = $stmtSys->fetchColumn();
if ($sysCatId) { if ($sysCatId) {
// b. Mettre à jour la valeur (1 = Validé)
// On utilise une astuce SQL : on met à jour uniquement la colonne de la personne concernée
// Si la ligne n'existe pas, on l'insère avec 1 pour la personne et 0 pour l'autre.
if ($person === 'Alex') { if ($person === 'Alex') {
$sql = "INSERT INTO pf_alloc_values (month_date, cat_id, amount_alex, amount_laia) $sql = "INSERT INTO pf_alloc_values (month_date, cat_id, amount_alex, amount_laia)
+29 -10
View File
@@ -68,6 +68,9 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
$isValidatedAlex = ($row['amount_alex'] == 1); $isValidatedAlex = ($row['amount_alex'] == 1);
$isValidatedLaia = ($row['amount_laia'] == 1); $isValidatedLaia = ($row['amount_laia'] == 1);
} }
// 7. Récupération des vacances actives pour les lier au budget
$activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN ('draft', 'planned', 'booked') ORDER BY start_date ASC")->fetchAll(PDO::FETCH_ASSOC);
?> ?>
<div class="prev-container"> <div class="prev-container">
@@ -191,6 +194,7 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
<td class="col-sticky" style="position:relative; <?= $isIndicative ? 'opacity:0.8;' : '' ?>"> <td class="col-sticky" style="position:relative; <?= $isIndicative ? 'opacity:0.8;' : '' ?>">
<div style="font-weight:600; color:<?= $isIndicative ? '#64748b' : 'var(--text-main)' ?>;"> <div style="font-weight:600; color:<?= $isIndicative ? '#64748b' : 'var(--text-main)' ?>;">
<?= htmlspecialchars($cat['name']) ?> <?= htmlspecialchars($cat['name']) ?>
<?php if(!empty($cat['holiday_id'])) echo " 🌴"; ?>
<?php if($isIndicative): ?><span style="font-size:0.7rem; border:1px solid #cbd5e1; border-radius:4px; padding:0 4px; margin-left:5px;">Info</span><?php endif; ?> <?php if($isIndicative): ?><span style="font-size:0.7rem; border:1px solid #cbd5e1; border-radius:4px; padding:0 4px; margin-left:5px;">Info</span><?php endif; ?>
</div> </div>
<div style="font-size:0.75rem; color:var(--text-muted); font-style:italic; text-align:right;"> <div style="font-size:0.75rem; color:var(--text-muted); font-style:italic; text-align:right;">
@@ -204,6 +208,7 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
data-id="<?= $cat['id'] ?>" data-id="<?= $cat['id'] ?>"
data-name="<?= htmlspecialchars($cat['name']) ?>" data-name="<?= htmlspecialchars($cat['name']) ?>"
data-target="<?= htmlspecialchars($cat['target']) ?>" data-target="<?= htmlspecialchars($cat['target']) ?>"
data-holiday="<?= $cat['holiday_id'] ?? '' ?>"
onclick="openEditModal(this)"> onclick="openEditModal(this)">
</button> </button>
@@ -369,7 +374,7 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
<label class="pf-label">Nom (ex: Vacances)</label> <label class="pf-label">Nom (ex: Vacances)</label>
<input type="text" name="name" class="pf-input" required> <input type="text" name="name" class="pf-input" required>
</div> </div>
<div class="form-group" style="margin-bottom:20px;"> <div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Cible (Destination)</label> <label class="pf-label">Cible (Destination)</label>
<select name="target" class="pf-input" required> <select name="target" class="pf-input" required>
<option value="" disabled selected>-- Choisir --</option> <option value="" disabled selected>-- Choisir --</option>
@@ -379,6 +384,15 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
<option value="vers commune">vers commune</option> <option value="vers commune">vers commune</option>
</select> </select>
</div> </div>
<div class="form-group" style="margin-bottom:20px;">
<label class="pf-label" style="color:#8b5cf6;">🌴 Lier à un voyage (Optionnel)</label>
<select name="holiday_id" class="pf-input" style="border-color:#8b5cf6; background:#f5f3ff;">
<option value="">-- Ne pas associer --</option>
<?php foreach ($activeHolidays as $hol): ?>
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" onclick="document.getElementById('addCatModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button> <button type="button" onclick="document.getElementById('addCatModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button>
<button type="submit" class="pf-btn">Ajouter</button> <button type="submit" class="pf-btn">Ajouter</button>
@@ -386,6 +400,7 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
</form> </form>
</div> </div>
</div> </div>
<div id="editCatModal" class="pf-modal"> <div id="editCatModal" class="pf-modal">
<div class="pf-modal-content" style="max-width:400px;"> <div class="pf-modal-content" style="max-width:400px;">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;"> <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:20px;">
@@ -396,11 +411,11 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
<input type="hidden" name="action" value="update_category"> <input type="hidden" name="action" value="update_category">
<input type="hidden" name="cat_id" id="edit_cat_id"> <input type="hidden" name="cat_id" id="edit_cat_id">
<div class="form-group"> <div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Nom</label> <label class="pf-label">Nom</label>
<input type="text" name="name" id="edit_cat_name" class="pf-input" required> <input type="text" name="name" id="edit_cat_name" class="pf-input" required>
</div> </div>
<div class="form-group"> <div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Cible (Destination)</label> <label class="pf-label">Cible (Destination)</label>
<select name="target" id="edit_cat_target" class="pf-input" required> <select name="target" id="edit_cat_target" class="pf-input" required>
<option value="vers L.Pol">vers L.Pol</option> <option value="vers L.Pol">vers L.Pol</option>
@@ -409,6 +424,15 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
<option value="vers commune">vers commune</option> <option value="vers commune">vers commune</option>
</select> </select>
</div> </div>
<div class="form-group" style="margin-bottom:20px;">
<label class="pf-label" style="color:#8b5cf6;">🌴 Lier à un voyage (Optionnel)</label>
<select name="holiday_id" id="edit_cat_holiday" class="pf-input" style="border-color:#8b5cf6; background:#f5f3ff;">
<option value="">-- Ne pas associer --</option>
<?php foreach ($activeHolidays as $hol): ?>
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" onclick="document.getElementById('editCatModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button> <button type="button" onclick="document.getElementById('editCatModal').style.display='none'" class="pf-btn btn-secondary">Annuler</button>
<button type="submit" class="pf-btn">Enregistrer</button> <button type="submit" class="pf-btn">Enregistrer</button>
@@ -420,21 +444,16 @@ if ($sysCatId && isset($allocs[$focusDate][$sysCatId])) {
<script> <script>
// Fonction pour ouvrir la modale et pré-remplir les valeurs // Fonction pour ouvrir la modale et pré-remplir les valeurs
function openEditModal(btn) { function openEditModal(btn) {
// 1. Récupération des données depuis le bouton
// getAttribute est plus sûr pour éviter les bugs si des données sont manquantes
const id = btn.getAttribute('data-id'); const id = btn.getAttribute('data-id');
const name = btn.getAttribute('data-name'); const name = btn.getAttribute('data-name');
const target = btn.getAttribute('data-target'); const target = btn.getAttribute('data-target');
const holiday = btn.getAttribute('data-holiday'); // NOUVEAU
// 2. Remplissage du formulaire
document.getElementById('edit_cat_id').value = id; document.getElementById('edit_cat_id').value = id;
document.getElementById('edit_cat_name').value = name; document.getElementById('edit_cat_name').value = name;
// Pour le SELECT, définir la .value sélectionne automatiquement la bonne option
// Si la valeur actuelle en base n'existe pas dans la liste, ça sélectionnera la 1ère par défaut
document.getElementById('edit_cat_target').value = target; document.getElementById('edit_cat_target').value = target;
document.getElementById('edit_cat_holiday').value = holiday; // NOUVEAU
// 3. Affichage
document.getElementById('editCatModal').style.display = 'flex'; document.getElementById('editCatModal').style.display = 'flex';
} }
</script> </script>
+59 -18
View File
@@ -35,11 +35,11 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_snapshot') {
header("Location: ?tab=suivi"); exit; header("Location: ?tab=suivi"); exit;
} }
// D. SAUVEGARDE IMPORT CSV (AVEC BUDGET_ITEM_ID) // D. SAUVEGARDE IMPORT CSV (AVEC BUDGET_ITEM_ID ET HOLIDAY_ID)
if (isset($_POST['action']) && $_POST['action'] === 'save_import') { if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
$count = 0; $count = 0;
// 1. Catégories temporaires // 1. Catégories temporaires (Inchangé)
$tempCatMapping = []; $tempCatMapping = [];
if (!empty($_POST['new_temp_cats'])) { if (!empty($_POST['new_temp_cats'])) {
$stmtTemp = $pdo->prepare("INSERT INTO pf_monthly_categories (month_year, name, type, budget) VALUES (?, ?, ?, 0)"); $stmtTemp = $pdo->prepare("INSERT INTO pf_monthly_categories (month_year, name, type, budget) VALUES (?, ?, ?, 0)");
@@ -49,8 +49,8 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
} }
} }
// 2. Insertion avec budget_item_id // 2. Insertion avec budget_item_id ET holiday_id
$stmtExp = $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref, budget_item_id) VALUES (?, ?, ?, ?, ?, ?)"); $stmtExp = $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref, budget_item_id, holiday_id) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmtRule = $pdo->prepare("INSERT INTO pf_import_rules (keyword, category) VALUES (?, ?) ON DUPLICATE KEY UPDATE category = VALUES(category)"); $stmtRule = $pdo->prepare("INSERT INTO pf_import_rules (keyword, category) VALUES (?, ?) ON DUPLICATE KEY UPDATE category = VALUES(category)");
if (isset($_POST['lines']) && is_array($_POST['lines'])) { if (isset($_POST['lines']) && is_array($_POST['lines'])) {
@@ -59,6 +59,7 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
$cat = $line['cat']; $cat = $line['cat'];
$is_credit = isset($line['is_credit']) ? (int)$line['is_credit'] : 0; $is_credit = isset($line['is_credit']) ? (int)$line['is_credit'] : 0;
$budgetItemId = !empty($line['budget_item_id']) ? (int)$line['budget_item_id'] : null; $budgetItemId = !empty($line['budget_item_id']) ? (int)$line['budget_item_id'] : null;
$holidayId = !empty($line['holiday_id']) ? (int)$line['holiday_id'] : null; // NOUVEAU
if ($is_credit && empty($cat)) continue; if ($is_credit && empty($cat)) continue;
if (!$is_credit && empty($cat)) continue; if (!$is_credit && empty($cat)) continue;
@@ -70,7 +71,7 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_import') {
$finalAmount = $is_credit ? -abs($line['amount']) : abs($line['amount']); $finalAmount = $is_credit ? -abs($line['amount']) : abs($line['amount']);
try { try {
$stmtExp->execute([$line['date'], $cat, $line['label'], $finalAmount, $line['ref'], $budgetItemId]); $stmtExp->execute([$line['date'], $cat, $line['label'], $finalAmount, $line['ref'], $budgetItemId, $holidayId]);
$stmtRule->execute([$line['label'], $cat]); $stmtRule->execute([$line['label'], $cat]);
$count++; $count++;
} catch (Exception $e) { continue; } } catch (Exception $e) { continue; }
@@ -89,6 +90,7 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_expense_manual') {
$label = trim($_POST['label']); $label = trim($_POST['label']);
$budgetItemId = null; $budgetItemId = null;
$holidayId = !empty($_POST['holiday_id']) ? (int)$_POST['holiday_id'] : null; // NOUVEAU
if ($cat === 'School' && !empty($_POST['label_select'])) { if ($cat === 'School' && !empty($_POST['label_select'])) {
$label = trim($_POST['label_select']); $label = trim($_POST['label_select']);
@@ -98,19 +100,17 @@ if (isset($_POST['action']) && $_POST['action'] === 'save_expense_manual') {
} }
if ($label && $amount > 0) { if ($label && $amount > 0) {
// Gestion du signe (Revenu = Négatif en BDD, Dépense = Positif) $is_credit = isset($_POST['is_credit']) ? (int)$_POST['is_credit'] : 0;
// Note: L'utilisateur saisit toujours du positif dans le formulaire $finalAmount = $is_credit ? -abs($amount) : abs($amount);
$finalAmount = ($cat === 'Income') ? -abs($amount) : abs($amount);
if ($id) { if ($id) {
// UPDATE // UPDATE
$pdo->prepare("UPDATE pf_expenses SET date_exp=?, category=?, label=?, amount=?, budget_item_id=? WHERE id=?") $pdo->prepare("UPDATE pf_expenses SET date_exp=?, category=?, label=?, amount=?, budget_item_id=?, holiday_id=? WHERE id=?")
->execute([$date, $cat, $label, $finalAmount, $budgetItemId, $id]); ->execute([$date, $cat, $label, $finalAmount, $budgetItemId, $holidayId, $id]);
} else { } else {
// INSERT // INSERT
$uniqueRef = "MANUAL_" . uniqid(); $uniqueRef = "MANUAL_" . uniqid();
$pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref, budget_item_id) VALUES (?, ?, ?, ?, ?, ?)") $pdo->prepare("INSERT INTO pf_expenses (date_exp, category, label, amount, import_ref, budget_item_id, holiday_id) VALUES (?, ?, ?, ?, ?, ?, ?)")
->execute([$date, $cat, $label, $finalAmount, $uniqueRef, $budgetItemId]); ->execute([$date, $cat, $label, $finalAmount, $uniqueRef, $budgetItemId, $holidayId]);
} }
header("Location: ?tab=suivi"); exit; header("Location: ?tab=suivi"); exit;
} }
@@ -122,6 +122,9 @@ if (isset($_GET['delete_expense'])) {
header("Location: ?tab=suivi"); exit; header("Location: ?tab=suivi"); exit;
} }
// G. RECUPERATION VACANCES
$activeHolidays = $pdo->query("SELECT id, title FROM pf_holidays WHERE status IN ('draft', 'planned', 'booked') ORDER BY start_date ASC")->fetchAll(PDO::FETCH_ASSOC);
// ============================================================================ // ============================================================================
// 2. CALCUL DES BUDGETS & CHARGES FIXES // 2. CALCUL DES BUDGETS & CHARGES FIXES
// ============================================================================ // ============================================================================
@@ -318,7 +321,13 @@ function getDisplayLogic($spent, $bg, $type) {
} else { } else {
$pct = ($bg > 0) ? min(100, ($spent / $bg) * 100) : ($spent > 0 ? 100 : 0); $pct = ($bg > 0) ? min(100, ($spent / $bg) * 100) : ($spent > 0 ? 100 : 0);
$isOver = ($spent > $bg && $bg > 0); $isOver = ($spent > $bg && $bg > 0);
$text = number_format(ceil($spent), 0, ',', ' ') . ' / ' . number_format(ceil($bg), 0, ',', ' ') . ' €';
// MODIFICATION ICI : On cache le "/ 0 €" si le budget est à 0
if ($bg > 0) {
$text = number_format(ceil($spent), 0, ',', ' ') . ' / ' . number_format(ceil($bg), 0, ',', ' ') . ' €';
} else {
$text = number_format(ceil($spent), 0, ',', ' ') . ' €';
}
} }
return ['pct' => $pct, 'isOver' => $isOver, 'text' => $text]; return ['pct' => $pct, 'isOver' => $isOver, 'text' => $text];
} }
@@ -476,6 +485,12 @@ function getDisplayLogic($spent, $bg, $type) {
<option value="<?= $inc['id'] ?>"><?= htmlspecialchars($inc['name']) ?> (<?= number_format($inc['amount'],0) ?>€)</option> <option value="<?= $inc['id'] ?>"><?= htmlspecialchars($inc['name']) ?> (<?= number_format($inc['amount'],0) ?>€)</option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
<select name="lines[<?= $idx ?>][holiday_id]" class="pf-input" style="flex:1; border-color:#8b5cf6; background:#f5f3ff;" <?= $dis ?>>
<option value="">-- Voyage (Optionnel) --</option>
<?php foreach ($activeHolidays as $hol): ?>
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
<?php endforeach; ?>
</select>
</div> </div>
</td> </td>
</tr> </tr>
@@ -565,6 +580,7 @@ function getDisplayLogic($spent, $bg, $type) {
<form method="POST"> <form method="POST">
<input type="hidden" name="action" value="save_expense_manual"> <input type="hidden" name="action" value="save_expense_manual">
<input type="hidden" name="expense_id" id="modalExpenseId"> <input type="hidden" name="expense_id" id="modalExpenseId">
<div class="form-group" style="margin-bottom:15px;"> <div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Catégorie</label> <label class="pf-label">Catégorie</label>
<select name="category" id="modalCatSelect" class="pf-input" onchange="handleModalCatChange(this)"> <select name="category" id="modalCatSelect" class="pf-input" onchange="handleModalCatChange(this)">
@@ -574,6 +590,14 @@ function getDisplayLogic($spent, $bg, $type) {
</select> </select>
</div> </div>
<div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Type de mouvement</label>
<select name="is_credit" id="modalIsCredit" class="pf-input" style="font-weight:bold;">
<option value="0">Dépense ( - sur le compte )</option>
<option value="1">Revenu / Remboursement ( + sur le compte )</option>
</select>
</div>
<div class="form-group" style="margin-bottom:15px;"> <div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Date</label> <label class="pf-label">Date</label>
<input type="date" name="date" id="modalDate" class="pf-input" value="<?= date('Y-m-d') ?>" required> <input type="date" name="date" id="modalDate" class="pf-input" value="<?= date('Y-m-d') ?>" required>
@@ -613,6 +637,16 @@ function getDisplayLogic($spent, $bg, $type) {
</select> </select>
</div> </div>
<div class="form-group" style="margin-bottom:15px;">
<label class="pf-label" style="color:#8b5cf6;">🌴 Associer à un voyage (Optionnel)</label>
<select name="holiday_id" id="modalHolidayId" class="pf-input" style="border-color:#8b5cf6; background:#f5f3ff;">
<option value="">-- Ne pas associer --</option>
<?php foreach ($activeHolidays as $hol): ?>
<option value="<?= $hol['id'] ?>"><?= htmlspecialchars($hol['title']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group" style="margin-bottom:15px;"> <div class="form-group" style="margin-bottom:15px;">
<label class="pf-label">Montant (€)</label> <label class="pf-label">Montant (€)</label>
<input type="number" step="0.01" name="amount" id="modalAmount" class="pf-input" placeholder="0.00" required> <input type="number" step="0.01" name="amount" id="modalAmount" class="pf-input" placeholder="0.00" required>
@@ -741,15 +775,19 @@ function openAddModal(catKey, catLabel) {
openSuiviModal('manualExpenseModal'); openSuiviModal('manualExpenseModal');
document.getElementById('modalTitle').innerText = "Ajouter : " + catLabel; document.getElementById('modalTitle').innerText = "Ajouter : " + catLabel;
document.getElementById('modalExpenseId').value = ""; // Reset ID (ajout) document.getElementById('modalExpenseId').value = "";
document.getElementById('modalDate').value = new Date().toISOString().split('T')[0]; document.getElementById('modalDate').value = new Date().toISOString().split('T')[0];
document.getElementById('modalLabelInput').value = ""; document.getElementById('modalLabelInput').value = "";
document.getElementById('modalAmount').value = ""; document.getElementById('modalAmount').value = "";
document.getElementById('modalHolidayId').value = "";
const catSelect = document.getElementById('modalCatSelect'); const catSelect = document.getElementById('modalCatSelect');
catSelect.value = catKey; catSelect.value = catKey;
handleModalCatChange(catSelect); handleModalCatChange(catSelect);
// Auto-sélection : Si on clique sur le + de "Revenus", on pré-sélectionne "Revenu"
document.getElementById('modalIsCredit').value = (catKey === 'Income') ? "1" : "0";
setTimeout(() => document.getElementById('modalLabelInput').focus(), 100); setTimeout(() => document.getElementById('modalLabelInput').focus(), 100);
} }
@@ -762,14 +800,17 @@ function openEditModal(expenseData) {
document.getElementById('modalDate').value = expenseData.date_exp; document.getElementById('modalDate').value = expenseData.date_exp;
document.getElementById('modalLabelInput').value = expenseData.label; document.getElementById('modalLabelInput').value = expenseData.label;
// Le montant en BDD est signé (- pour revenus), on remet en absolu pour l'input // NOUVEAU : On lit la vraie valeur en BDD pour définir le type (+ ou -)
document.getElementById('modalAmount').value = Math.abs(parseFloat(expenseData.amount)); const rawAmount = parseFloat(expenseData.amount);
document.getElementById('modalIsCredit').value = rawAmount < 0 ? "1" : "0";
document.getElementById('modalAmount').value = Math.abs(rawAmount);
document.getElementById('modalHolidayId').value = expenseData.holiday_id || "";
const catSelect = document.getElementById('modalCatSelect'); const catSelect = document.getElementById('modalCatSelect');
catSelect.value = expenseData.category; catSelect.value = expenseData.category;
handleModalCatChange(catSelect); handleModalCatChange(catSelect);
// Pré-remplir les selects spécifiques
if (expenseData.category === 'Frais') { if (expenseData.category === 'Frais') {
document.getElementById('fraisSelect').value = expenseData.budget_item_id; document.getElementById('fraisSelect').value = expenseData.budget_item_id;
} else if (expenseData.category === 'Income') { } else if (expenseData.category === 'Income') {
+33 -10
View File
@@ -1,14 +1,18 @@
<?php <?php
// modules/holidays/index.php // modules/holidays/index.php
// 1. Récupération des voyages + Calcul du coût total // 1. Récupération des voyages + Calculs (Coût Total ET Montant déjà financé)
$sql = " $sql = "
SELECT h.*, SELECT h.*,
( (
COALESCE(h.budget_food, 0) + COALESCE(h.budget_food, 0) +
COALESCE(h.budget_extra, 0) + COALESCE(h.budget_extra, 0) +
COALESCE((SELECT SUM(amount) FROM pf_holidays_items WHERE holiday_id = h.id), 0) COALESCE((SELECT SUM(amount) FROM pf_holidays_items WHERE holiday_id = h.id), 0)
) as total_cost ) as total_cost,
(
COALESCE((SELECT SUM(ABS(amount)) FROM pf_expenses WHERE holiday_id = h.id), 0) +
COALESCE((SELECT SUM(amount) FROM pf_savings WHERE holiday_id = h.id), 0)
) as total_funded
FROM pf_holidays h FROM pf_holidays h
ORDER BY FIELD(status, 'booked', 'planned', 'draft', 'passed', 'archived'), ORDER BY FIELD(status, 'booked', 'planned', 'draft', 'passed', 'archived'),
start_date ASC start_date ASC
@@ -154,18 +158,13 @@ $history = array_filter($holidays, fn($h) => in_array($h['status'], ['passed', '
<?php <?php
function renderHolidayCard($h, $pdo) { function renderHolidayCard($h, $pdo) {
// Récupérer le détail des items pour l'affichage JS futur
$stmt = $pdo->prepare("SELECT * FROM pf_holidays_items WHERE holiday_id = ?"); $stmt = $pdo->prepare("SELECT * FROM pf_holidays_items WHERE holiday_id = ?");
$stmt->execute([$h['id']]); $stmt->execute([$h['id']]);
$items = $stmt->fetchAll(PDO::FETCH_ASSOC); $items = $stmt->fetchAll(PDO::FETCH_ASSOC);
// JSON pour le JS
$json = htmlspecialchars(json_encode(['main' => $h, 'items' => $items]), ENT_QUOTES, 'UTF-8'); $json = htmlspecialchars(json_encode(['main' => $h, 'items' => $items]), ENT_QUOTES, 'UTF-8');
// Affichage des dates
$dateDisplay = htmlspecialchars($h['period_hint'] ?? ''); $dateDisplay = htmlspecialchars($h['period_hint'] ?? '');
// MODIFICATION ICI : Format d/m/Y (jj/mm/aaaa)
if (empty($dateDisplay) && $h['start_date']) { if (empty($dateDisplay) && $h['start_date']) {
$dateDisplay = date('d/m/Y', strtotime($h['start_date'])); $dateDisplay = date('d/m/Y', strtotime($h['start_date']));
if ($h['end_date']) $dateDisplay .= ' → ' . date('d/m/Y', strtotime($h['end_date'])); if ($h['end_date']) $dateDisplay .= ' → ' . date('d/m/Y', strtotime($h['end_date']));
@@ -178,6 +177,17 @@ function renderHolidayCard($h, $pdo) {
default => 'bg-yellow-50 text-yellow-800' default => 'bg-yellow-50 text-yellow-800'
}; };
// --- NOUVEAU : Calcul de la progression ---
$cost = (float)$h['total_cost'];
$funded = (float)$h['total_funded'];
$leftToPay = max(0, $cost - $funded);
// Pourcentage pour la barre de progression (max 100%)
$percent = $cost > 0 ? min(100, round(($funded / $cost) * 100)) : 0;
// Couleur de la barre : Rouge si < 50%, Jaune si < 100%, Vert si tout est payé
$barColor = $percent === 100 ? '#10b981' : ($percent > 50 ? '#f59e0b' : '#ef4444');
echo " echo "
<div class='hol-idea-card' onclick='editHoliday($json)'> <div class='hol-idea-card' onclick='editHoliday($json)'>
<div class='hol-idea-card__head'> <div class='hol-idea-card__head'>
@@ -190,9 +200,22 @@ function renderHolidayCard($h, $pdo) {
<span>🗓️ ".($dateDisplay ?: 'Dates à définir')."</span> <span>🗓️ ".($dateDisplay ?: 'Dates à définir')."</span>
</div> </div>
<div style='margin-top:auto; padding-top:10px; border-top:1px solid #f1f5f9; display:flex; justify-content:space-between; align-items:center;'> <div style='margin-top:auto; padding-top:10px; border-top:1px solid #f1f5f9;'>
<span style='font-size:0.85rem; color:#64748b;'>Budget Total</span>
<span style='font-size:1.1rem; font-weight:bold; color:#1e293b;'>".number_format($h['total_cost'], 0, ',', ' ')." €</span> <div style='display:flex; justify-content:space-between; align-items:center; margin-bottom:5px;'>
<span style='font-size:0.85rem; color:#64748b;'>Budget Total</span>
<span style='font-size:1rem; font-weight:bold; color:#1e293b;'>".number_format($cost, 0, ',', ' ')." €</span>
</div>
<div style='width:100%; height:6px; background:#e2e8f0; border-radius:3px; margin-bottom:8px; overflow:hidden;'>
<div style='width:{$percent}%; height:100%; background:{$barColor}; transition:width 0.3s ease;'></div>
</div>
<div style='display:flex; justify-content:space-between; align-items:center; font-size:0.8rem;'>
<span style='color:#10b981; font-weight:600;'>✓ Financé : ".number_format($funded, 0, ',', ' ')." €</span>
<span style='color:#ef4444; font-weight:600;'>Reste : ".number_format($leftToPay, 0, ',', ' ')." €</span>
</div>
</div> </div>
</div> </div>
"; ";