Files
depenses_trello/templates/index.html

290 lines
16 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">
<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; }
* { 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; }
.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; }
.form-group input:focus { border-color: var(--accent); outline: none; }
.btn { width: 100%; padding: 14px; border: none; border-radius: 10px; font-weight: 600; cursor: pointer; margin-top: 10px; }
.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-send { background: linear-gradient(135deg, #0088cc, #006699); color: #fff; }
.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; flex-wrap: wrap; gap: 5px; }
.expense-prenom { background: linear-gradient(135deg, var(--primary), var(--secondary)); padding: 4px 10px; border-radius: 15px; font-size: 0.75em; white-space: nowrap; }
.expense-montant { font-weight: 700; color: var(--accent); }
.expense-status { font-size: 0.75em; padding: 3px 8px; border-radius: 10px; }
.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; }
.total span { font-weight: 700; color: var(--accent); }
.preview { background: var(--bg-input); padding: 12px; border-radius: 10px; white-space: pre-wrap; color: var(--success); font-family: monospace; margin-top: 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; }
</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>📅 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" 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"> Ajouter</button>
</form>
</div>
<div class="card">
<h2>📋 Dépenses (<span id="count">0</span>)</h2>
<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()">📥 Exporter CSV</button>
<button class="btn btn-export" onclick="document.getElementById('file-import').click()">📤 Importer CSV</button>
<input type="file" id="file-import" class="file-input" accept=".csv" onchange="importCSV(this)">
</div>
<button class="btn btn-danger" onclick="clearAll()">🗑️ Tout effacer</button>
</div>
</div>
</div>
<div id="config" style="display:none">
<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>📄 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 class="preview" id="format-preview"></div>
</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>
<script>
var config = { prenoms: [], format: '', trello: {} };
var depenses = [];
function formatDate(d) { if(!d) return ""; return d.split("-").reverse().join("/"); }
async function load() {
var r = await fetch('/api/config');
config = await r.json();
r = await fetch('/api/depenses');
depenses = await r.json();
render();
checkPage();
}
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() {
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('');
document.getElementById('format-input').value = config.format || '{prenom} - {date} - {libelle} - {montant}€';
var fmt = config.format || '{prenom} - {date} - {libelle} - {montant}€';
fmt = fmt.replace('{prenom}', 'Papa').replace('{date}', '27/02/2026').replace('{libelle}', 'Courses').replace('{montant}', '45.50');
document.getElementById('format-preview').textContent = fmt;
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 || '';
}
document.getElementById('count').textContent = depenses.length;
var total = depenses.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 < depenses.length; i++) {
var d = depenses[i];
var isSent = d.status === 'Envoyé ✅';
var statusClass = isSent ? 'status-sent' : 'status-pending';
var sendBtn = isSent ? '' : '<button class="btn btn-sm btn-send" onclick="sendOne(' + d.id + ')">🟦</button>';
html += '<div class="expense-item">';
html += '<div style="display:flex;align-items:center;gap:10px">';
html += '<span class="expense-prenom">' + d.prenom + '</span>';
html += '<span>' + formatDate(d.date) + ' - ' + d.libelle + '</span>';
html += '</div>';
html += '<div style="display:flex;align-items:center;gap:10px">';
html += '<span class="expense-status ' + statusClass + '">' + (d.status || 'En attente') + '</span>';
html += '<span class="expense-montant">' + parseFloat(d.montant).toFixed(2) + '€</span>';
html += sendBtn;
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();
var dd = String(today.getDate()).padStart(2, '0');
var mm = String(today.getMonth() + 1).padStart(2, '0');
var yyyy = today.getFullYear();
document.getElementById('depense-date').value = dd + '/' + mm + '/' + yyyy;
}
function exportCSV() {
window.location.href = '/api/export';
}
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) {
alert('Importé: ' + d.imported + ' dépenses!');
load();
} else {
alert('Erreur: ' + d.error);
}
});
input.value = '';
}
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) { alert('Envoyé sur Trello!'); load(); }
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();
load();
});
};
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() {
e.target.reset();
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() {
alert('Format sauvegardé!');
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() {
alert('Config Trello sauvegardée!');
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('Envoyé(s): ' + sent);
load();
}
async function clearAll() {
if (confirm('Tout effacer?')) {
await fetch('/api/clear', { method: 'POST' });
load();
}
}
load();
</script>
</body>
</html>