From d507e4e0a6ed1b38bf7c85b237798a59a4815433 Mon Sep 17 00:00:00 2001 From: h3r7 Date: Fri, 27 Feb 2026 13:39:08 +0100 Subject: [PATCH] =?UTF-8?q?v1.0=20-=20D=C3=A9penses=20Trello=20avec=20stat?= =?UTF-8?q?us,=20envoi=20par=20ligne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 167 +++++++++++++++++++++++++++++++++++-------- config.json | 28 ++++++-- templates/index.html | 50 +++++++++---- 3 files changed, 194 insertions(+), 51 deletions(-) diff --git a/app.py b/app.py index 6e91088..a4ab68c 100644 --- a/app.py +++ b/app.py @@ -15,6 +15,14 @@ def save_config(data): with open(CONFIG_FILE, 'w') as f: json.dump(data, f, indent=2) +def parse_date(d): + if not d: return "" + if "/" in d: + parts = d.split("/") + if len(parts) == 3: + return f"{parts[2]}-{parts[1]}-{parts[0]}" + return d + @app.route('/') def index(): with open('/home/h3r7/depenses_trello/templates/index.html', 'r') as f: @@ -32,15 +40,16 @@ def add_depense(): data = { 'id': len(config.get('depenses', [])) + 1, 'prenom': request.form.get('prenom'), - 'date': request.form.get('date'), + 'date': parse_date(request.form.get('date', '')), 'libelle': request.form.get('libelle'), - 'montant': float(request.form.get('montant', 0)) + 'montant': float(request.form.get('montant', 0)), + 'status': 'En attente' } if 'depenses' not in config: config['depenses'] = [] config['depenses'].append(data) save_config(config) - return redirect('/') + return redirect('/?page=saisie') # Get depenses @app.route('/api/depenses') @@ -64,7 +73,128 @@ def clear_depenses(): save_config(config) return jsonify({'success': True}) -# Generate text +# Generate text for ONE expense +@app.route('/api/generate_one/', methods=['GET']) +def generate_one(id): + config = load_config() + d = None + for exp in config.get('depenses', []): + if exp.get('id') == id: + d = exp + break + if not d: + return jsonify({'text': ''}) + + template = config.get('format', '{prenom} - {date} - {libelle} - {montant}€') + line = template + ddate = d.get('date', '') or '' + if ddate: + ddate = '/'.join(ddate.split('-')[::-1]) + line = line.replace('{prenom}', d.get('prenom', '')) + line = line.replace('{date}', ddate) + line = line.replace('{libelle}', d.get('libelle', '')) + line = line.replace('{montant}', str(d.get('montant', 0))) + return jsonify({'text': line}) + +# Send to Trello - ONE card per pending expense +@app.route('/api/trello/send_one/', methods=['POST']) +def send_one(id): + config = load_config() + t = config.get('trello', {}) + if not t.get('api_key') or not t.get('token') or not t.get('list_id'): + return jsonify({'error': 'Config Trello manquante'}), 400 + + # Find the expense + d = None + for exp in config.get('depenses', []): + if exp.get('id') == id: + d = exp + break + + if not d: + return jsonify({'error': 'Dépense non trouvée'}), 404 + + # Skip if already sent + if d.get('status') == 'Envoyé ✅': + return jsonify({'error': 'Déjà envoyé'}), 400 + + # Generate text + template = config.get('format', '{prenom} - {date} - {libelle} - {montant}€') + line = template + ddate = d.get('date', '') or '' + if ddate: + ddate = '/'.join(ddate.split('-')[::-1]) + line = line.replace('{prenom}', d.get('prenom', '')) + line = line.replace('{date}', ddate) + line = line.replace('{libelle}', d.get('libelle', '')) + line = line.replace('{montant}', str(d.get('montant', 0))) + + # Send to Trello + url = 'https://api.trello.com/1/cards' + params = { + 'key': t.get('api_key'), + 'token': t.get('token'), + 'idList': t.get('list_id'), + 'name': line, + 'desc': line + } + try: + r = requests.post(url, params=params) + if r.status_code == 200: + # Mark as sent + for exp in config.get('depenses', []): + if exp.get('id') == id: + exp['status'] = 'Envoyé ✅' + save_config(config) + return jsonify({'success': True}) + return jsonify({'error': r.text}), r.status_code + except Exception as e: + return jsonify({'error': str(e)}), 500 + +# Legacy - send all (for compatibility) +@app.route('/api/trello/send', methods=['POST']) +def send_trello(): + config = load_config() + t = config.get('trello', {}) + if not t.get('api_key') or not t.get('token') or not t.get('list_id'): + return jsonify({'error': 'Config Trello manquante'}), 400 + + # Send only pending expenses + sent_count = 0 + for d in config.get('depenses', []): + if d.get('status') == 'En attente': + # Generate text + template = config.get('format', '{prenom} - {date} - {libelle} - {montant}€') + line = template + ddate = d.get('date', '') or '' + if ddate: + ddate = '/'.join(ddate.split('-')[::-1]) + line = line.replace('{prenom}', d.get('prenom', '')) + line = line.replace('{date}', ddate) + line = line.replace('{libelle}', d.get('libelle', '')) + line = line.replace('{montant}', str(d.get('montant', 0))) + + # Send to Trello + url = 'https://api.trello.com/1/cards' + params = { + 'key': t.get('api_key'), + 'token': t.get('token'), + 'idList': t.get('list_id'), + 'name': line, + 'desc': line + } + try: + r = requests.post(url, params=params) + if r.status_code == 200: + d['status'] = 'Envoyé ✅' + sent_count += 1 + except: + pass + + save_config(config) + return jsonify({'success': True, 'sent': sent_count}) + +# Generate text (all) @app.route('/api/generate', methods=['POST']) def generate_text(): config = load_config() @@ -73,10 +203,10 @@ def generate_text(): lines = [] for d in data.get('depenses', []): line = template - line = line.replace('{prenom}', d.get('prenom', '')) ddate = d.get('date', '') or '' if ddate: ddate = '/'.join(ddate.split('-')[::-1]) + line = line.replace('{prenom}', d.get('prenom', '')) line = line.replace('{date}', ddate) line = line.replace('{libelle}', d.get('libelle', '')) line = line.replace('{montant}', str(d.get('montant', 0))) @@ -88,7 +218,7 @@ def generate_text(): def add_prenom(): config = load_config() prenom = request.form.get('prenom', '').strip() - if prenom and prenom not in config.get('prenom', []): + if prenom and prenom not in config.get('prenoms', []): if 'prenoms' not in config: config['prenoms'] = [] config['prenoms'].append(prenom) @@ -124,30 +254,5 @@ def save_trello(): save_config(config) return redirect('/?page=config') -# Send to Trello -@app.route('/api/trello/send', methods=['POST']) -def send_trello(): - config = load_config() - t = config.get('trello', {}) - if not t.get('api_key') or not t.get('token') or not t.get('list_id'): - return jsonify({'error': 'Config Trello manquante'}), 400 - - data = request.json - url = 'https://api.trello.com/1/cards' - params = { - 'key': t.get('api_key'), - 'token': t.get('token'), - 'idList': t.get('list_id'), - 'name': data.get('title', 'Dépenses'), - 'desc': data.get('text', '') - } - try: - r = requests.post(url, params=params) - if r.status_code == 200: - return jsonify({'success': True}) - return jsonify({'error': r.text}), r.status_code - except Exception as e: - return jsonify({'error': str(e)}), 500 - if __name__ == '__main__': app.run(host='0.0.0.0', port=8769, debug=False) diff --git a/config.json b/config.json index 732a730..a103d8a 100644 --- a/config.json +++ b/config.json @@ -1,13 +1,29 @@ { "prenoms": [ - "HERY" + "H3R7" ], "format": "{prenom} - {date} - {libelle} - {montant}\u20ac", - "depenses": [], + "depenses": [ + { + "id": 1, + "prenom": "H3R7", + "date": "2026-02-27", + "libelle": "COURSES CARREFOUR test ", + "montant": 25.36, + "status": "Envoy\u00e9 \u2705" + }, + { + "id": 2, + "prenom": "H3R7", + "date": "2026-02-23", + "libelle": "GAZOLE", + "montant": 33.55, + "status": "Envoy\u00e9 \u2705" + } + ], "trello": { - "api_key": "", - "token": "", - "board_id": "", - "list_id": "" + "api_key": "0e86fa879fe9cc1bd5bff8a4b32bceff", + "token": "ATTA55718bc108ac05e2828a9406b9a0bf843dc47e958bd659d215210a7b0726bd6f4C4A53CE", + "list_id": "698499e98171f1383a04dbd6" } } \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 94eda0c..c62721b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -11,7 +11,7 @@ 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 { flex: 1; padding: 12 var(--bg-cardpx; background:); 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; } @@ -20,14 +20,19 @@ .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; } .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; } @@ -47,7 +52,7 @@

➕ Nouvelle dépense

-
+
@@ -59,11 +64,8 @@
Total: 0.00€
- -
- @@ -153,19 +155,36 @@ 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 ? '' : ''; + html += '
'; html += '
'; html += '' + d.prenom + ''; html += '' + formatDate(d.date) + ' - ' + d.libelle + ''; html += '
'; html += '
'; + html += '' + (d.status || 'En attente') + ''; html += '' + parseFloat(d.montant).toFixed(2) + '€'; + html += sendBtn; html += '🗑️'; html += '
'; } document.getElementById('expenses').innerHTML = html; - document.getElementById('depense-date').value = new Date().toISOString().split('T')[0]; + 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; + } + + 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) { @@ -207,17 +226,20 @@ 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(); - document.getElementById('preview').textContent = d.text; - document.getElementById('preview').style.display = 'block'; + alert(d.text); } - async function sendTrello() { + async function sendAll() { if (!config.trello || !config.trello.api_key) { alert('Configure Trello!'); showPage('config'); return; } - await generate(); - var text = document.getElementById('preview').textContent; - var r = await fetch('/api/trello/send', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({title: 'Dépenses ' + new Date().toLocaleDateString('fr'), text: text}) }); - if (r.ok) { alert('Envoyé sur Trello!'); clearAll(); } - else { var e = await r.json(); alert('Erreur: ' + e.error); } + 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() {