match recap

This commit is contained in:
2026-02-15 12:21:18 +01:00
parent 9a3d646ca5
commit 92424bdb51
2 changed files with 93 additions and 50 deletions
+21 -22
View File
@@ -8,31 +8,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// --- ACTION : SAUVEGARDER (AJOUT OU MODIF) ---
if ($action === 'save') {
$id = !empty($_POST['id']) ? $_POST['id'] : null;
$name = $_POST['name'];
$amount = str_replace(',', '.', $_POST['amount']);
$type = $_POST['type'];
$day = !empty($_POST['payment_day']) ? $_POST['payment_day'] : null;
$month = $_POST['reg_month'] ?: null;
$cat = $_POST['category'];
$is_estimate = isset($_POST['is_estimate']) ? (int)$_POST['is_estimate'] : 0;
$id = $_POST['id'] ?? '';
$name = $_POST['name'];
$amount = $_POST['amount'];
$category = $_POST['category'];
$type = $_POST['type'];
$payment_day = $_POST['payment_day'];
$is_estimate = $_POST['is_estimate'];
$reg_month = $_POST['reg_month'];
if ($id) {
// Update : on ajoute is_estimate
$sql = "UPDATE pf_budget_items SET name=?, amount=?, type=?, payment_day=?, reg_month=?, category=?, is_estimate=? WHERE id=?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $amount, $type, $day, $month, $cat, $is_estimate, $id]);
} else {
// Insert : on ajoute is_estimate
$sql = "INSERT INTO pf_budget_items (name, amount, type, payment_day, reg_month, category, is_estimate) VALUES (?, ?, ?, ?, ?, ?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute([$name, $amount, $type, $day, $month, $cat, $is_estimate]);
}
// NOUVEAU : Récupération des mots-clés
$keywords = $_POST['mapping_keywords'] ?? '';
// Redirection après sauvegarde classique
header('Location: /budget.php?tab=recap');
exit;
if ($id) {
// UPDATE
$stmt = $pdo->prepare("UPDATE pf_budget_items SET name=?, amount=?, category=?, type=?, payment_day=?, is_estimate=?, reg_month=?, mapping_keywords=? WHERE id=?");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords, $id]);
} else {
// INSERT
$stmt = $pdo->prepare("INSERT INTO pf_budget_items (name, amount, category, type, payment_day, is_estimate, reg_month, mapping_keywords) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $amount, $category, $type, $payment_day, $is_estimate, $reg_month, $keywords]);
}
header('Location: /budget.php?tab=recap'); // Ou ta redirection habituelle
exit;
}
// --- ACTION : COCHER/DÉCOCHER RAPIDE (VIA JS FETCH) ---
if ($action === 'toggle-check') {
+70 -26
View File
@@ -1,12 +1,20 @@
<?php
// modules/budget/views/recap.php
// 1. Récupération des données
// 1. Récupération des Items du Budget
$stmt = $pdo->query("SELECT * FROM pf_budget_items ORDER BY category DESC, sort_order ASC, name ASC");
$items = $stmt->fetchAll();
$totalDepensesMensuelles = 0;
$totalRevenusMensuels = 0;
// 2. Récupération des Dépenses Réelles du mois (Pour le matching auto)
$currentMonth = date('m');
$currentYear = date('Y');
$stmtExp = $pdo->prepare("SELECT label FROM pf_expenses WHERE MONTH(date_exp) = ? AND YEAR(date_exp) = ?");
$stmtExp->execute([$currentMonth, $currentYear]);
// On stocke tous les libellés réels dans un tableau simple
$realExpensesLabels = $stmtExp->fetchAll(PDO::FETCH_COLUMN);
$totalDepenses = 0;
$totalRevenus = 0;
?>
<div class="budget-view">
@@ -30,15 +38,35 @@ $totalRevenusMensuels = 0;
</thead>
<tbody>
<?php foreach ($items as $item):
// Calcul des totaux (Mensuel uniquement)
if ($item['type'] === 'Mensuel') {
if ($item['category'] === 'expense') {
$totalDepensesMensuelles += $item['amount'];
} else {
$totalRevenusMensuels += $item['amount'];
// --- CALCUL DES TOTAUX ---
$amountToAdd = ($item['type'] === 'Annuel') ? $item['amount'] / 12 : $item['amount'];
if ($item['category'] === 'expense') $totalDepenses += $amountToAdd;
else $totalRevenus += $amountToAdd;
// --- LOGIQUE DE DETECTION AUTOMATIQUE ---
$isAutoChecked = false;
// Si des mots-clés sont définis pour cet item
if (!empty($item['mapping_keywords'])) {
// On explose la chaine "Netflix, Spotify" en tableau
$keywords = array_map('trim', explode(',', $item['mapping_keywords']));
foreach ($keywords as $kw) {
if (empty($kw)) continue;
// On cherche ce mot clé dans TOUTES les dépenses réelles du mois
foreach ($realExpensesLabels as $realLabel) {
// stripos pour insensible à la casse
if (stripos($realLabel, $kw) !== false) {
$isAutoChecked = true;
break 2; // On sort des deux boucles si trouvé
}
}
}
}
// L'état final est : Soit coché manuellement (BDD), soit détecté auto
$isPaid = $item['is_checked'] || $isAutoChecked;
// Styles
$rowClass = ($item['category'] === 'income') ? 'row-income' : 'row-expense';
if ($item['is_estimate']) $rowClass .= ' row-estimate';
?>
@@ -46,9 +74,15 @@ $totalRevenusMensuels = 0;
<td style="padding:15px;">
<strong><?= htmlspecialchars($item['name']) ?></strong>
<?= $item['is_estimate'] ? ' <small style="color:#64748b;">(Est.)</small>' : '' ?>
<?php if(!empty($item['mapping_keywords'])): ?>
<span title="Mapping auto activé: <?= htmlspecialchars($item['mapping_keywords']) ?>" style="font-size:0.7rem; cursor:help;">🔗</span>
<?php endif; ?>
</td>
<td class="cell-amount" style="font-weight:600; padding:15px; color:<?= $item['category']==='income'?'#10b981':'#1e293b' ?>;">
<?= number_format($item['amount'], 2, ',', ' ') ?> €
<?php if($item['type'] === 'Annuel'): ?>
<div style="font-size:0.75rem; color:#94a3b8; font-weight:normal;">Soit <?= number_format($amountToAdd, 2, ',', ' ') ?>/mois</div>
<?php endif; ?>
</td>
<td style="padding:15px;">
<span class="badge-type <?= strtolower($item['type']) ?>" style="background:#e2e8f0; padding:4px 8px; border-radius:12px; font-size:0.8rem; font-weight:600; color:#475569;">
@@ -58,12 +92,17 @@ $totalRevenusMensuels = 0;
<td style="padding:15px; color:#64748b;"><?= $item['payment_day'] ? $item['payment_day'] : '-' ?></td>
<td style="padding:15px;">
<div style="display:flex; align-items:center; gap:8px;">
<input type="checkbox"
<?= $item['is_checked'] ? 'checked' : '' ?>
onclick="toggleItemCheck(<?= $item['id'] ?>, this.checked)"
title="Marquer comme payé"
style="width:18px; height:18px; cursor:pointer;">
<?= $item['is_checked'] ? ' <span style="color:#10b981; font-weight:500; font-size:0.9rem;">Payé</span>' : ' <span style="color:#f59e0b; font-weight:500; font-size:0.9rem;">Attente</span>' ?>
<?php if ($isAutoChecked): ?>
<input type="checkbox" checked disabled style="width:18px; height:18px; accent-color:#3b82f6; cursor:not-allowed;">
<span style="color:#3b82f6; font-weight:600; font-size:0.9rem;">Auto ✅</span>
<?php else: ?>
<input type="checkbox"
<?= $item['is_checked'] ? 'checked' : '' ?>
onclick="toggleItemCheck(<?= $item['id'] ?>, this.checked)"
title="Marquer comme payé"
style="width:18px; height:18px; cursor:pointer;">
<?= $item['is_checked'] ? ' <span style="color:#10b981; font-weight:500; font-size:0.9rem;">Payé</span>' : ' <span style="color:#f59e0b; font-weight:500; font-size:0.9rem;">Attente</span>' ?>
<?php endif; ?>
</div>
</td>
<td style="padding:15px; color:#64748b; font-size:0.9rem;">
@@ -80,16 +119,16 @@ $totalRevenusMensuels = 0;
</tbody>
<tfoot style="background:#f8fafc;">
<tr>
<td colspan="1" style="padding:15px;"><strong>Total Revenus Mensuels</strong></td>
<td colspan="6" style="padding:15px; color:#10b981;"><strong>+ <?= number_format($totalRevenusMensuels, 2, ',', ' ') ?> €</strong></td>
<td colspan="1" style="padding:15px;"><strong>Total Revenus (Lissés)</strong></td>
<td colspan="6" style="padding:15px; color:#10b981;"><strong>+ <?= number_format($totalRevenus, 2, ',', ' ') ?> €</strong></td>
</tr>
<tr>
<td colspan="1" style="padding:15px;"><strong>Total Dépenses Mensuelles</strong></td>
<td colspan="6" style="padding:15px; color:#ef4444;"><strong>- <?= number_format($totalDepensesMensuelles, 2, ',', ' ') ?> €</strong></td>
<td colspan="1" style="padding:15px;"><strong>Total Dépenses (Lissées)</strong></td>
<td colspan="6" style="padding:15px; color:#ef4444;"><strong>- <?= number_format($totalDepenses, 2, ',', ' ') ?> €</strong></td>
</tr>
<tr style="border-top: 2px solid #e2e8f0; background: white;">
<td colspan="1" style="padding:15px; font-size:1.1rem;"><strong>Équilibre du compte</strong></td>
<?php $balance = $totalRevenusMensuels - $totalDepensesMensuelles; ?>
<td colspan="1" style="padding:15px; font-size:1.1rem;"><strong>Équilibre théorique</strong></td>
<?php $balance = $totalRevenus - $totalDepenses; ?>
<td colspan="6" style="padding:15px; font-size: 1.3em;" class="<?= $balance >= 0 ? 'text-success' : 'text-danger' ?>">
<strong style="color:<?= $balance >= 0 ? '#10b981' : '#ef4444' ?>;"><?= number_format($balance, 2, ',', ' ') ?> € / mois</strong>
</td>
@@ -99,7 +138,7 @@ $totalRevenusMensuels = 0;
</div>
<div class="budget-note" style="margin-top:15px; font-size:0.85rem; color:#64748b;">
<p>* Note : Les frais de type "Annuel" sont affichés pour information mais ne sont pas inclus dans le calcul de l'équilibre mensuel.</p>
<p>* Les lignes marquées <strong>Auto ✅</strong> ont été détectées automatiquement dans les dépenses réelles du mois.</p>
</div>
</div>
@@ -120,6 +159,12 @@ $totalRevenusMensuels = 0;
<input type="text" name="name" id="item_name" required class="pf-input" placeholder="Ex: Loyer, Salaire..." style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px;">
</div>
<div class="form-group" style="margin-bottom:15px;">
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Mots-clés (Détection Auto)</label>
<input type="text" name="mapping_keywords" id="item_keywords" class="pf-input" placeholder="Ex: NETFLIX, PRIME VIDEO (séparés par virgule)" style="width:100%; padding:10px; border:1px solid #cbd5e1; border-radius:8px; background:#f0f9ff; border-color:#bae6fd;">
<small style="color:#64748b; font-size:0.75rem;">Si une dépense du mois contient ce mot, la ligne passera en "Payé".</small>
</div>
<div style="display:flex; gap:15px; margin-bottom:15px;">
<div style="flex:1;">
<label class="pf-label" style="display:block; margin-bottom:5px; font-weight:600; color:#475569; font-size:0.9rem;">Montant (€)</label>
@@ -181,7 +226,6 @@ $totalRevenusMensuels = 0;
// SCRIPTS ISOLÉS POUR L'ONGLET RECAPITULATIF
// ==========================================
// Fermer au clic extérieur
window.onclick = function(event) {
const modal = document.getElementById('budgetRecapModal');
if (event.target == modal) {
@@ -203,12 +247,14 @@ function closeRecapModal() {
}
function editRecapItem(item) {
// Si item est une string JSON (passée depuis PHP), on la parse
const data = typeof item === "string" ? JSON.parse(item) : item;
document.getElementById("recapModalTitle").innerText = "Modifier : " + data.name;
document.getElementById("item_id").value = data.id;
document.getElementById("item_name").value = data.name;
// Remplissage du nouveau champ Keywords
document.getElementById("item_keywords").value = data.mapping_keywords || '';
document.getElementById("item_amount").value = data.amount;
document.getElementById("item_category").value = data.category;
document.getElementById("item_type").value = data.type;
@@ -249,13 +295,11 @@ function toggleItemCheck(id, isChecked) {
})
.then((response) => {
if (!response.ok) throw new Error("Erreur réseau");
// Optionnel : on pourrait forcer un rechargement pour mettre l'icône à jour dynamiquement
window.location.reload();
})
.catch((error) => {
alert("Erreur lors de la mise à jour");
console.error(error);
// Si erreur, on décoche/recoche pour refléter la réalité
window.location.reload();
});
}