Files
depenses_trello/templates/index.html
2026-03-01 17:45:05 +01:00

508 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>💸 Dépenses Trello</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<style>
:root { --bg-dark: #0d0d1a; --bg-card: #16162a; --bg-input: #1a1a35; --primary: #e94560; --secondary: #7b2cbf; --accent: #00d9ff; --text: #fff; --text-dim: #888; --success: #00ff88; --danger: #ff4757; --warning: #ffc800; }
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Outfit', sans-serif; background: var(--bg-dark); color: var(--text); padding: 15px; min-height: 100vh; }
h1 { text-align: center; font-size: 1.6em; background: linear-gradient(135deg, var(--primary), var(--secondary)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.nav { display: flex; gap: 10px; margin: 15px 0; }
.nav a { flex: 1; padding: 12px; background: var(--bg-card); border-radius: 10px; text-align: center; color: var(--text-dim); text-decoration: none; }
.nav a.active { background: linear-gradient(135deg, var(--primary), var(--secondary)); color: #fff; }
.card { background: var(--bg-card); border-radius: 15px; padding: 15px; margin-bottom: 15px; }
h2 { font-size: 1em; margin-bottom: 12px; color: var(--accent); }
.form-group { margin-bottom: 10px; }
.form-group label { display: block; color: var(--text-dim); font-size: 0.85em; margin-bottom: 4px; }
.form-group input, .form-group select { width: 100%; padding: 12px; background: var(--bg-input); border: 2px solid transparent; border-radius: 10px; color: var(--text); font-size: 1em; }
.btn { width: 100%; padding: 14px; border: none; border-radius: 10px; font-weight: 600; cursor: pointer; }
.btn-sm { width: auto; padding: 8px 12px; font-size: 0.85em; }
.btn-primary { background: linear-gradient(135deg, var(--accent), #0099cc); color: var(--bg-dark); }
.btn-success { background: linear-gradient(135deg, var(--success), #00cc66); color: var(--bg-dark); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-warning { background: var(--warning); color: var(--bg-dark); }
.btn-export { background: linear-gradient(135deg, #9944cc, #6622aa); color: #fff; }
.prenom-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0; }
.prenom-tag { display: flex; align-items: center; gap: 5px; background: linear-gradient(135deg, var(--primary), var(--secondary)); padding: 8px 12px; border-radius: 20px; font-size: 0.85em; }
.expense-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; background: var(--bg-input); border-radius: 10px; margin-bottom: 8px; }
.expense-prenom { background: linear-gradient(135deg, var(--primary), var(--secondary)); padding: 4px 10px; border-radius: 15px; font-size: 0.75em; }
.expense-cat { background: var(--secondary); padding: 4px 8px; border-radius: 8px; font-size: 0.7em; }
.expense-montant { font-weight: bold; color: var(--accent); }
.status { padding: 3px 8px; border-radius: 10px; font-size: 0.75em; }
.status.pending { background: rgba(255,200,0,0.2); color: #ffc800; }
.status.sent { background: rgba(0,255,136,0.2); color: var(--success); }
.total { text-align: right; font-size: 1.2em; margin: 10px 0; padding: 12px; background: rgba(0,217,255,0.1); border-radius: 10px; }
.actions { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.actions-row { display: flex; gap: 10px; }
.actions-row .btn { margin-top: 0; }
.file-input { display: none; }
.filter-bar { display: flex; gap: 10px; margin-bottom: 10px; overflow-x: auto; }
.filter-chip { padding: 8px 16px; background: var(--bg-input); border-radius: 20px; font-size: 0.85em; white-space: nowrap; cursor: pointer; }
.filter-chip.active { background: var(--primary); color: #fff; }
.budget-alert { background: rgba(255,71,87,0.2); border: 1px solid var(--danger); padding: 10px; border-radius: 10px; margin-bottom: 10px; display: none; }
.recurring-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; background: var(--bg-input); border-radius: 8px; margin-bottom: 8px; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 1000; }
.modal-content { background: var(--bg-card); padding: 20px; border-radius: 15px; max-width: 400px; margin: 50px auto; }
.modal h2 { margin-bottom: 15px; }
.modal-buttons { display: flex; gap: 10px; margin-top: 15px; }
.chart-container { height: 200px; margin: 15px 0; }
</style>
</head>
<body>
<h1>💸 Dépenses Trello</h1>
<div class="nav">
<a href="?page=saisie" id="nav-saisie">✏️ Saisie</a>
<a href="/dashboard" id="nav-dashboard">📊 Dashboard</a>
<a href="?page=config" id="nav-config">⚙️ Config</a>
</div>
<div id="saisie" style="display:none">
<div class="card">
<h2> Nouvelle dépense</h2>
<form method="POST" action="/api/add" id="form-add">
<div class="form-group"><label>👤 Prénom</label><select name="prenom" id="prenom-select" required></select></div>
<div class="form-group"><label>📂 Catégorie</label><select name="category" id="category-select"></select></div>
<div class="form-group"><label>📅 Date</label><input type="text" name="date" id="depense-date" placeholder="JJ/MM/AAAA" required></div>
<div class="form-group"><label>📝 Libellé</label><input type="text" name="libelle" id="libelle-input" placeholder="Courses, Essence..." required></div>
<div class="form-group"><label>💰 Montant (€)</label><input type="number" name="montant" placeholder="0.00" step="0.01" required></div>
<button type="submit" class="btn btn-primary">💾 Enregistrer</button>
<button type="button" class="btn btn-danger" onclick="resetForm()" style="margin-top:8px">🧹 Effacer</button>
</form>
</div>
<div class="card">
<div id="budget-alert" class="budget-alert">⚠️ Attention: Vous avez depasse le budget mensuel!</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
<h2>📋 Dépenses (<span id="count">0</span>)</h2>
<button class="btn btn-sm" style="background:var(--secondary)" onclick="loadDepenses()">🔄</button>
</div>
<div class="filter-bar" id="filter-bar"></div>
<div id="expenses"></div>
<div class="total">Total: <span id="total">0.00€</span></div>
<div class="actions">
<div class="actions-row">
<button class="btn btn-primary" onclick="generate()">👁️ Aperçu</button>
<button class="btn btn-success" onclick="sendAll()">🟦 Tout envoyer</button>
</div>
<div class="actions-row">
<button class="btn btn-export" onclick="exportCSV()">📥 CSV</button>
<button class="btn btn-export" onclick="exportPDF()">📥 PDF</button>
<button class="btn btn-export" onclick="document.getElementById('file-import').click()">📤</button>
<input type="file" id="file-import" class="file-input" accept=".csv" onchange="importCSV(this)">
</div>
</div>
</div>
</div>
<div id="config" style="display:none">
<div class="card">
<h2>💰 Budget Mensuel</h2>
<form method="POST" action="/api/budget" id="form-budget">
<div class="form-group"><label>Budget (€)</label><input type="number" name="budget" id="budget-input" placeholder="0.00" step="0.01"></div>
<button type="submit" class="btn btn-primary">💾 Sauvegarder</button>
</form>
</div>
<div class="card">
<h2>🔄 Dépenses Récurrentes</h2>
<div id="recurring-list"></div>
<form method="POST" action="/api/recurring/add" id="form-recurring" style="margin-top:10px">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="form-group"><label>Libellé</label><input type="text" name="libelle" placeholder="Loyer, EDF..." required></div>
<div class="form-group"><label>Montant (€)</label><input type="number" name="montant" placeholder="0.00" step="0.01" required></div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
<div class="form-group"><label>Catégorie</label><select name="category" id="recurring-cat"></select></div>
<div class="form-group"><label>Jour du mois</label><input type="number" name="jour" value="1" min="1" max="31"></div>
</div>
<button type="submit" class="btn btn-primary"> Ajouter</button>
</form>
</div>
<div class="card">
<h2>👥 Prénoms</h2>
<div id="prenom-tags"></div>
<form method="POST" action="/api/prenom/add" id="form-prenom">
<div style="display:flex;gap:10px;margin-top:10px">
<input type="text" name="prenom" placeholder="Nouveau prénom..." style="flex:1">
<button type="submit" class="btn btn-primary" style="width:auto"></button>
</div>
</form>
</div>
<div class="card">
<h2>📂 Catégories</h2>
<div id="category-tags"></div>
<form method="POST" action="/api/category/add" id="form-category">
<div style="display:flex;gap:10px;margin-top:10px">
<input type="text" name="category" placeholder="Nouvelle catégorie..." style="flex:1">
<button type="submit" class="btn btn-primary" style="width:auto"></button>
</div>
</form>
</div>
<div class="card">
<h2>📄 Format du texte</h2>
<form method="POST" action="/api/format" id="form-format">
<div class="form-group"><input type="text" name="format" id="format-input"></div>
<button type="submit" class="btn btn-primary">💾 Sauvegarder</button>
</form>
</div>
<div class="card">
<h2>🔗 Trello</h2>
<form method="POST" action="/api/trello/save" id="form-trello">
<div class="form-group"><label>🔑 API Key</label><input type="text" name="api_key" id="trello-api_key"></div>
<div class="form-group"><label>🔐 Token</label><input type="password" name="token" id="trello-token"></div>
<div class="form-group"><label>📁 List ID</label><input type="text" name="list_id" id="trello-list_id"></div>
<button type="submit" class="btn btn-primary">💾 Sauvegarder</button>
</form>
</div>
</div>
<!-- Edit Modal -->
<div id="edit-modal" class="modal">
<div class="modal-content">
<h2>✏️ Modifier la dépense</h2>
<form id="form-edit">
<input type="hidden" id="edit-id">
<div class="form-group"><label>👤 Prénom</label><select name="prenom" id="edit-prenom"></select></div>
<div class="form-group"><label>📂 Catégorie</label><select name="category" id="edit-category"></select></div>
<div class="form-group"><label>📅 Date</label><input type="text" name="date" id="edit-date" placeholder="JJ/MM/AAAA"></div>
<div class="form-group"><label>📝 Libellé</label><input type="text" name="libelle" id="edit-libelle"></div>
<div class="form-group"><label>💰 Montant (€)</label><input type="number" name="montant" id="edit-montant" step="0.01"></div>
<div class="modal-buttons">
<button type="submit" class="btn btn-primary">💾 Sauvegarder</button>
<button type="button" class="btn btn-danger" onclick="closeModal()">Annuler</button>
</div>
</form>
</div>
</div>
<script>
var config = { prenoms: [], categories: [], format: '', trello: {} };
var depenses = [];
var budget = 0;
var filterCategory = '';
function formatDate(d) { if(!d) return ""; return d.split("-").reverse().join("/"); }
function parseDate(d) { if(!d) return ""; var p = d.split("/"); if(p.length===3) return p[2]+"-"+p[1]+"-"+p[0]; return d; }
async function load() {
var r = await fetch('/api/config');
config = await r.json();
r = await fetch('/api/depenses');
depenses = await r.json();
r = await fetch('/api/budget');
budget = await r.json();
render();
checkPage();
initAutoCategory();
renderFilters();
loadRecurring();
checkBudget();
}
function checkBudget() {
var now = new Date();
var currentMonth = now.getFullYear() + "-" + String(now.getMonth()+1).padStart(2,'0');
var monthTotal = depenses.filter(function(d) { return d.date && d.date.startsWith(currentMonth); })
.reduce(function(s, d) { return s + (parseFloat(d.montant) || 0); }, 0);
var alert = document.getElementById('budget-alert');
if (budget.budget > 0 && monthTotal > budget.budget) {
alert.textContent = "⚠️ Attention: Vous avez depasse le budget mensuel! (" + monthTotal.toFixed(2) + "€ / " + budget.budget + "€)";
alert.style.display = 'block';
} else {
alert.style.display = 'none';
}
}
async function loadRecurring() {
var r = await fetch('/api/recurring');
var rec = await r.json();
var html = '';
for (var i = 0; i < rec.length; i++) {
html += '<div class="recurring-item">';
html += '<span>' + rec[i].libelle + '</span>';
html += '<span>' + rec[i].montant + '€ (' + rec[i].category + ') - jour ' + rec[i].jour + '</span>';
html += '<a href="/api/recurring/del/' + rec[i].id + '" style="color:var(--danger)">🗑️</a>';
html += '</div>';
}
document.getElementById('recurring-list').innerHTML = html || '<p style="color:var(--text-dim)">Aucune depense recurrente</p>';
}
function renderFilters() {
var cats = config.categories || ['Courses', 'Essence', 'Loisirs', 'Maison', 'Santé', 'Transport', 'Autre'];
var html = '<div class="filter-chip' + (filterCategory === '' ? ' active' : '') + '" onclick="setFilter(\'\')">Tous</div>';
for (var i = 0; i < cats.length; i++) {
html += '<div class="filter-chip' + (filterCategory === cats[i] ? ' active' : '') + '" onclick="setFilter(\'' + cats[i] + '\')">' + cats[i] + '</div>';
}
document.getElementById('filter-bar').innerHTML = html;
}
function setFilter(cat) {
filterCategory = cat;
renderFilters();
render();
}
function initAutoCategory() {
document.getElementById('libelle-input').addEventListener('input', function() {
var lib = this.value.toLowerCase();
var catSelect = document.getElementById('category-select');
if (lib.includes('carrefour') || lib.includes('courses') || lib.includes('lidl') || lib.includes('auchan') || lib.includes('boutique')) catSelect.value = 'Courses';
else if (lib.includes('essence') || lib.includes('carburant') || lib.includes('gazole') || lib.includes('total') || lib.includes('station')) catSelect.value = 'Transport';
else if (lib.includes('loto') || lib.includes('euromillion') || lib.includes('turf') || lib.includes('bar') || lib.includes('cafe') || lib.includes('cinéma')) catSelect.value = 'Loisirs';
else if (lib.includes('controle') || lib.includes('ct ') || lib.includes('médicament') || lib.includes('pharmacie') || lib.includes('dentiste')) catSelect.value = 'Santé';
else if (lib.includes('lumiere') || lib.includes('lampe') || lib.includes('brico') || lib.includes('maison') || lib.includes('ikea')) catSelect.value = 'Maison';
});
}
function checkPage() {
var params = new URLSearchParams(window.location.search);
var page = params.get('page') || 'saisie';
showPage(page);
}
function showPage(page) {
document.getElementById('saisie').style.display = page === 'saisie' ? 'block' : 'none';
document.getElementById('config').style.display = page === 'config' ? 'block' : 'none';
document.getElementById('nav-saisie').className = page === 'saisie' ? 'nav a active' : 'nav a';
document.getElementById('nav-config').className = page === 'config' ? 'nav a active' : 'nav a';
}
function render() {
// Prenoms
document.getElementById('prenom-select').innerHTML = '<option value="">Choisir...</option>' + config.prenoms.map(function(p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
document.getElementById('prenom-tags').innerHTML = config.prenoms.map(function(p, i) { return '<span class="prenom-tag">' + p + '<a href="/api/prenom/del/' + i + '" style="color:#fff;text-decoration:none;margin-left:5px">×</a></span>'; }).join('');
// Categories
var cats = config.categories || ['Courses', 'Essence', 'Loisirs', 'Maison', 'Santé', 'Transport', 'Autre'];
document.getElementById('category-select').innerHTML = cats.map(function(c) { return '<option value="' + c + '">' + c + '</option>'; }).join('');
document.getElementById('category-tags').innerHTML = cats.map(function(c, i) { return '<span class="prenom-tag">' + c + '<a href="/api/category/del/' + i + '" style="color:#fff;text-decoration:none;margin-left:5px">×</a></span>'; }).join('');
document.getElementById('recurring-cat').innerHTML = cats.map(function(c) { return '<option value="' + c + '">' + c + '</option>'; }).join('');
// Edit dropdowns
document.getElementById('edit-prenom').innerHTML = config.prenoms.map(function(p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
document.getElementById('edit-category').innerHTML = cats.map(function(c) { return '<option value="' + c + '">' + c + '</option>'; }).join('');
// Format
document.getElementById('format-input').value = config.format || '{prenom} - {date} - {libelle} - {montant}€';
// Budget
document.getElementById('budget-input').value = budget.budget || '';
// Trello
if (config.trello) {
document.getElementById('trello-api_key').value = config.trello.api_key || '';
document.getElementById('trello-token').value = config.trello.token || '';
document.getElementById('trello-list_id').value = config.trello.list_id || '';
}
// Expenses (with filter)
var filtered = filterCategory ? depenses.filter(function(d) { return d.category === filterCategory; }) : depenses;
document.getElementById('count').textContent = filtered.length;
var total = filtered.reduce(function(s, d) { return s + (parseFloat(d.montant) || 0); }, 0);
document.getElementById('total').textContent = total.toFixed(2) + '€';
var html = '';
for (var i = 0; i < filtered.length; i++) {
var d = filtered[i];
var isSent = d.status === 'Envoyé ✅';
var statusClass = isSent ? 'sent' : 'pending';
var sendBtn = isSent ? '' : '<button class="btn btn-sm" style="background:#0088cc;padding:6px 10px" onclick="sendOne(' + d.id + ')">🟦</button>';
html += '<div class="expense-item">';
html += '<div style="display:flex;flex-direction:column;gap:5px">';
html += '<span class="expense-prenom">' + d.prenom + '</span>';
html += '<span class="expense-cat">' + (d.category || 'Autre') + '</span>';
html += '</div>';
html += '<div style="flex:1;padding:0 10px">' + formatDate(d.date) + ' - ' + d.libelle + '</div>';
html += '<div style="display:flex;align-items:center;gap:10px">';
html += '<span class="status ' + statusClass + '">' + (d.status || 'En attente') + '</span>';
html += '<span class="expense-montant">' + parseFloat(d.montant).toFixed(2) + '€</span>';
html += sendBtn;
html += '<button class="btn btn-sm" style="background:#ffc800;padding:6px 10px" onclick="editDepense(' + d.id + ')">✏️</button>';
html += '<a href="/api/del/' + d.id + '" style="color:var(--danger);text-decoration:none;font-size:1.2em">🗑️</a>';
html += '</div></div>';
}
document.getElementById('expenses').innerHTML = html;
var today = new Date();
document.getElementById('depense-date').value = String(today.getDate()).padStart(2, '0') + '/' + String(today.getMonth() + 1).padStart(2, '0') + '/' + today.getFullYear();
}
function editDepense(id) {
var d = depenses.find(function(x) { return x.id === id; });
if (!d) return;
document.getElementById('edit-id').value = id;
document.getElementById('edit-prenom').value = d.prenom;
document.getElementById('edit-category').value = d.category || 'Autre';
document.getElementById('edit-date').value = formatDate(d.date);
document.getElementById('edit-libelle').value = d.libelle;
document.getElementById('edit-montant').value = d.montant;
document.getElementById('edit-modal').style.display = 'block';
}
function closeModal() {
document.getElementById('edit-modal').style.display = 'none';
}
function resetForm() {
document.getElementById('form-add').reset();
var today = new Date();
document.getElementById('depense-date').value = String(today.getDate()).padStart(2, '0') + '/' + String(today.getMonth() + 1).padStart(2, '0') + '/' + today.getFullYear();
}
function loadDepenses() {
fetch('/api/depenses').then(function(r) { return r.json(); }).then(function(d) {
depenses = d;
render();
checkBudget();
});
}
async function sendOne(id) {
if (!config.trello || !config.trello.api_key) { alert('Configure Trello!'); showPage('config'); return; }
var r = await fetch('/api/trello/send_one/' + id, { method: 'POST' });
if (r.ok) { loadDepenses(); }
else { var e = await r.json(); alert('Erreur: ' + e.error); }
}
document.getElementById('form-add').onsubmit = function(e) {
e.preventDefault();
var fd = new FormData(e.target);
fetch('/api/add', { method: 'POST', body: fd }).then(function() {
e.target.reset();
loadDepenses();
});
};
document.getElementById('form-edit').onsubmit = function(e) {
e.preventDefault();
var id = document.getElementById('edit-id').value;
var fd = new FormData();
fd.append('prenom', document.getElementById('edit-prenom').value);
fd.append('category', document.getElementById('edit-category').value);
fd.append('date', document.getElementById('edit-date').value);
fd.append('libelle', document.getElementById('edit-libelle').value);
fd.append('montant', document.getElementById('edit-montant').value);
fetch('/api/update/' + id, { method: 'POST', body: fd }).then(function() {
closeModal();
loadDepenses();
});
};
document.getElementById('form-prenom').onsubmit = function(e) {
e.preventDefault();
var fd = new FormData(e.target);
fetch('/api/prenom/add', { method: 'POST', body: fd }).then(function() { load(); });
};
document.getElementById('form-category').onsubmit = function(e) {
e.preventDefault();
var fd = new FormData(e.target);
fetch('/api/category/add', { method: 'POST', body: fd }).then(function() { load(); });
};
document.getElementById('form-format').onsubmit = function(e) {
e.preventDefault();
var fd = new FormData(e.target);
fetch('/api/format', { method: 'POST', body: fd }).then(function() { load(); });
};
document.getElementById('form-trello').onsubmit = function(e) {
e.preventDefault();
var fd = new FormData(e.target);
fetch('/api/trello/save', { method: 'POST', body: fd }).then(function() { load(); });
};
document.getElementById('form-budget').onsubmit = function(e) {
e.preventDefault();
var fd = new FormData(e.target);
fetch('/api/budget', { method: 'POST', body: fd }).then(function() { load(); });
};
document.getElementById('form-recurring').onsubmit = function(e) {
e.preventDefault();
var fd = new FormData(e.target);
fetch('/api/recurring/add', { method: 'POST', body: fd }).then(function() { load(); });
};
async function generate() {
var r = await fetch('/api/generate', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({depenses: depenses}) });
var d = await r.json();
alert(d.text);
}
async function sendAll() {
if (!config.trello || !config.trello.api_key) { alert('Configure Trello!'); showPage('config'); return; }
var sent = 0;
for (var i = 0; i < depenses.length; i++) {
if (depenses[i].status !== 'Envoyé ✅') {
var r = await fetch('/api/trello/send_one/' + depenses[i].id, { method: 'POST' });
if (r.ok) sent++;
}
}
alert('Envoye(s): ' + sent);
loadDepenses();
}
function exportCSV() { window.location.href = '/api/export'; }
function exportPDF() {
var { jsPDF } = window.jspdf;
var doc = new jsPDF();
doc.setFontSize(18);
doc.text("Depenses Trello", 105, 20, { align: 'center' });
doc.setFontSize(10);
var y = 35;
doc.text("Date", 20, y);
doc.text("Personne", 50, y);
doc.text("Categorie", 80, y);
doc.text("Libelle", 110, y);
doc.text("Montant", 170, y);
y += 5;
doc.line(20, y, 190, y);
y += 5;
for (var i = 0; i < depenses.length; i++) {
var d = depenses[i];
if (y > 270) { doc.addPage(); y = 20; }
doc.text(formatDate(d.date), 20, y);
doc.text(d.prenom || '', 50, y);
doc.text(d.category || 'Autre', 80, y);
doc.text((d.libelle || '').substring(0, 25), 110, y);
doc.text(d.montant + ' eur', 170, y);
y += 8;
}
var total = depenses.reduce(function(s, d) { return s + (parseFloat(d.montant) || 0); }, 0);
y += 5;
doc.line(20, y, 190, y);
y += 8;
doc.setFontSize(12);
doc.text("Total: " + total.toFixed(2) + " eur", 170, y);
doc.save('depenses_' + new Date().toISOString().split('T')[0] + '.pdf');
}
function importCSV(input) {
var file = input.files[0];
if (!file) return;
var fd = new FormData();
fd.append('file', file);
fetch('/api/import', { method: 'POST', body: fd }).then(function(r) { return r.json(); }).then(function(d) {
if (d.success) { loadDepenses(); alert('Importe: ' + d.imported); }
else { alert('Erreur: ' + d.error); }
});
input.value = '';
}
load();
</script>
</body>
</html>