maj
This commit is contained in:
@@ -148,6 +148,76 @@ def dashboard():
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# ROUTES - PROFIL
|
||||
# ============================================
|
||||
@app.route('/profile', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def profile():
|
||||
"""Page de profil pour gerer la securite et les infos persos."""
|
||||
token = session['token']
|
||||
|
||||
if request.method == 'POST':
|
||||
action = request.form.get('action')
|
||||
|
||||
if action == 'update_profile':
|
||||
phone = request.form.get('phone', '')
|
||||
address = request.form.get('address', '')
|
||||
|
||||
data, status = api_request('PUT', '/api/user/profile', {
|
||||
'phone': phone,
|
||||
'address': address
|
||||
}, token=token)
|
||||
|
||||
if status == 200:
|
||||
flash('Informations personnelles mises a jour !', 'success')
|
||||
else:
|
||||
flash(data.get('error', 'Erreur lors de la mise a jour'), 'danger')
|
||||
|
||||
elif action == 'update_password':
|
||||
current_password = request.form.get('current_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
if new_password != confirm_password:
|
||||
flash('Les nouveaux mots de passe ne correspondent pas', 'danger')
|
||||
else:
|
||||
data, status = api_request('PUT', '/api/user/security/password', {
|
||||
'current_password': current_password,
|
||||
'new_password': new_password
|
||||
}, token=token)
|
||||
|
||||
if status == 200:
|
||||
flash('Mot de passe mis a jour avec succes !', 'success')
|
||||
else:
|
||||
flash(data.get('error', 'Erreur'), 'danger')
|
||||
|
||||
elif action == 'update_email':
|
||||
password = request.form.get('password')
|
||||
new_email = request.form.get('new_email')
|
||||
|
||||
data, status = api_request('PUT', '/api/user/security/email', {
|
||||
'password': password,
|
||||
'new_email': new_email
|
||||
}, token=token)
|
||||
|
||||
if status == 200:
|
||||
flash('Email mis a jour avec succes. Vous devez vous reconnecter pour valider les changements.', 'success')
|
||||
session.clear()
|
||||
return redirect(url_for('login'))
|
||||
else:
|
||||
flash(data.get('error', 'Erreur'), 'danger')
|
||||
|
||||
# Recuperation complete du profil et des comptes
|
||||
profile_data, status_profile = api_request('GET', '/api/user/profile', token=token)
|
||||
accounts_data, status_accounts = api_request('GET', '/api/accounts', token=token)
|
||||
|
||||
user_profile = profile_data.get('user', session.get('user', {})) if status_profile == 200 else session.get('user', {})
|
||||
accounts_list = accounts_data.get('accounts', []) if status_accounts == 200 else []
|
||||
|
||||
return render_template('profile.html', user_profile=user_profile, accounts=accounts_list)
|
||||
|
||||
|
||||
# ============================================
|
||||
# ROUTES - COMPTES
|
||||
# ============================================
|
||||
@@ -349,6 +419,84 @@ def transactions():
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# ROUTES - EXPORT CSV
|
||||
# ============================================
|
||||
@app.route('/transactions/export')
|
||||
@login_required
|
||||
def export_transactions():
|
||||
"""Redirige vers le backend pour télécharger le CSV des transactions."""
|
||||
import requests as req
|
||||
token = session['token']
|
||||
account_id = request.args.get('account_id', '')
|
||||
|
||||
endpoint = f"{BACKEND_URL}/api/transactions/export"
|
||||
if account_id:
|
||||
endpoint += f"?account_id={account_id}"
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {token}',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
try:
|
||||
resp = req.get(endpoint, headers=headers, timeout=15)
|
||||
if resp.status_code == 200:
|
||||
from flask import Response
|
||||
return Response(
|
||||
resp.content,
|
||||
mimetype='text/csv',
|
||||
headers={
|
||||
'Content-Disposition': 'attachment; filename="dragonbank_transactions.csv"'
|
||||
}
|
||||
)
|
||||
else:
|
||||
flash('Erreur lors de la génération du CSV', 'danger')
|
||||
return redirect(url_for('transactions'))
|
||||
except Exception as e:
|
||||
flash('Impossible de contacter le serveur', 'danger')
|
||||
return redirect(url_for('transactions'))
|
||||
|
||||
|
||||
# ============================================
|
||||
# ROUTES - SIMULATEUR D'ÉPARGNE
|
||||
# ============================================
|
||||
@app.route('/simulator', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def simulator():
|
||||
"""Simulateur de croissance de l'épargne avec graphique."""
|
||||
token = session['token']
|
||||
resultat = None
|
||||
|
||||
# Récupération des comptes épargne pour pré-remplir le simulateur
|
||||
accounts_data, _ = api_request('GET', '/api/accounts', token=token)
|
||||
comptes_epargne = [
|
||||
a for a in accounts_data.get('accounts', [])
|
||||
if a.get('account_type') in ('livret_a', 'assurance_vie')
|
||||
]
|
||||
|
||||
if request.method == 'POST':
|
||||
form_data = {
|
||||
'capital_initial': float(request.form.get('capital_initial', 0)),
|
||||
'taux_annuel': float(request.form.get('taux_annuel', 3)),
|
||||
'duree_annees': int(request.form.get('duree_annees', 10)),
|
||||
'versement_mensuel': float(request.form.get('versement_mensuel', 0))
|
||||
}
|
||||
|
||||
data, status = api_request('POST', '/api/simulator', data=form_data, token=token)
|
||||
|
||||
if status == 200:
|
||||
resultat = data
|
||||
else:
|
||||
flash(data.get('error', 'Erreur de simulation'), 'danger')
|
||||
|
||||
return render_template('simulator.html',
|
||||
user=session.get('user', {}),
|
||||
comptes_epargne=comptes_epargne,
|
||||
resultat=resultat
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# DÉMARRAGE
|
||||
# ============================================
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,14 +63,20 @@
|
||||
<i class="bi bi-clock-history"></i> Historique
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.endpoint == 'simulator' %}active{% endif %}"
|
||||
href="{{ url_for('simulator') }}">
|
||||
<i class="bi bi-graph-up-arrow"></i> Simulateur
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item me-3 d-flex align-items-center">
|
||||
<span class="user-badge">
|
||||
<a href="{{ url_for('profile') }}" class="user-badge text-decoration-none {% if request.endpoint == 'profile' %}text-warning{% endif %}">
|
||||
<i class="bi bi-person-circle"></i>
|
||||
{{ session.get('user', {}).get('first_name', '') }}
|
||||
{{ session.get('user', {}).get('last_name', '') }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-danger" href="{{ url_for('logout') }}">
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Mes Bénéficiaires{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="main-content">
|
||||
<div class="page-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1><i class="bi bi-people"></i> Mes Bénéficiaires</h1>
|
||||
<p>Gérez vos contacts de virement</p>
|
||||
</div>
|
||||
<a href="{{ url_for('add_beneficiary') }}" class="btn btn-dragon">
|
||||
<i class="bi bi-person-plus"></i> Ajouter un bénéficiaire
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{% for b in beneficiaries %}
|
||||
<div class="col-lg-6 fade-in delay-{{ loop.index }}">
|
||||
<div class="section-card">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h5 class="mb-1">
|
||||
<i class="bi bi-person-circle"></i> {{ b.beneficiary_name }}
|
||||
</h5>
|
||||
<p class="text-muted mb-1">
|
||||
<i class="bi bi-bank"></i> {{ b.bank_name }}
|
||||
</p>
|
||||
<p class="mb-1">
|
||||
<i class="bi bi-hash"></i>
|
||||
<code>{{ b.account_number }}</code>
|
||||
</p>
|
||||
{% if b.iban %}
|
||||
<p class="text-muted mb-1">
|
||||
<i class="bi bi-upc"></i> {{ b.iban }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-calendar"></i>
|
||||
Ajouté le {{ b.created_at[:10] if b.created_at else 'N/A' }}
|
||||
</small>
|
||||
</div>
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<span class="status-badge completed">
|
||||
<i class="bi bi-check-circle"></i> {{ b.status }}
|
||||
</span>
|
||||
<a href="{{ url_for('delete_beneficiary', beneficiary_id=b.id) }}"
|
||||
class="btn btn-sm btn-danger"
|
||||
onclick="return confirm('Supprimer ce bénéficiaire ?')">
|
||||
<i class="bi bi-trash"></i> Supprimer
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<a href="{{ url_for('transfer') }}" class="btn btn-sm btn-dragon">
|
||||
<i class="bi bi-send"></i> Faire un virement
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-12">
|
||||
<div class="section-card text-center py-5">
|
||||
<i class="bi bi-people" style="font-size: 4rem; color: var(--dragon-text-light);"></i>
|
||||
<h3 class="mt-3">Aucun bénéficiaire enregistré</h3>
|
||||
<p class="text-muted">Ajoutez un bénéficiaire pour effectuer des virements</p>
|
||||
<a href="{{ url_for('add_beneficiary') }}" class="btn btn-dragon mt-2">
|
||||
<i class="bi bi-person-plus"></i> Ajouter un bénéficiaire
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,93 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Ouvrir un Compte{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="main-content">
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-plus-circle"></i> Ouvrir un Compte</h1>
|
||||
<p>Choisissez le type de compte à ouvrir</p>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="section-card fade-in">
|
||||
<form method="POST" action="{{ url_for('open_account') }}">
|
||||
|
||||
<!-- Type de compte -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-wallet2"></i> Type de compte *
|
||||
</label>
|
||||
<select name="account_type" class="form-select" required>
|
||||
<option value="">-- Choisissez un type --</option>
|
||||
<option value="courant">
|
||||
Compte Courant — taux 0 %
|
||||
</option>
|
||||
<option value="livret_a">
|
||||
Livret A — taux 3 % / cycle
|
||||
</option>
|
||||
<option value="assurance_vie">
|
||||
Assurance Vie — taux 2 % / cycle
|
||||
</option>
|
||||
</select>
|
||||
<small class="text-muted">
|
||||
Un seul Livret A et une seule Assurance Vie par client.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Dépôt initial -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-currency-euro"></i> Dépôt initial (optionnel)
|
||||
</label>
|
||||
<input type="number" name="initial_deposit" class="form-control"
|
||||
min="0" step="0.01" placeholder="0.00" value="0">
|
||||
<small class="text-muted">
|
||||
Laissez 0 pour ouvrir le compte sans dépôt initial.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-dragon w-100 mt-3">
|
||||
<i class="bi bi-check-circle"></i> Ouvrir le compte
|
||||
</button>
|
||||
|
||||
<a href="{{ url_for('accounts') }}" class="btn btn-dragon-outline w-100 mt-2">
|
||||
<i class="bi bi-arrow-left"></i> Retour à mes comptes
|
||||
</a>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Récapitulatif des types de comptes -->
|
||||
<div class="row g-3 mt-2">
|
||||
<div class="col-12">
|
||||
<div class="section-card courant" style="border-left: 4px solid var(--dragon-primary);">
|
||||
<h6><i class="bi bi-credit-card"></i> Compte Courant</h6>
|
||||
<p class="text-muted mb-0 small">
|
||||
Compte de tous les jours pour vos dépenses et virements. Pas d'intérêts.
|
||||
Plusieurs comptes autorisés.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="section-card" style="border-left: 4px solid #28a745;">
|
||||
<h6><i class="bi bi-piggy-bank"></i> Livret A</h6>
|
||||
<p class="text-muted mb-0 small">
|
||||
Épargne réglementée à taux fixe de 3 % par cycle. Un seul Livret A autorisé.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="section-card" style="border-left: 4px solid #ffc107;">
|
||||
<h6><i class="bi bi-shield-check"></i> Assurance Vie</h6>
|
||||
<p class="text-muted mb-0 small">
|
||||
Placement long terme à 2 % par cycle sur les fonds euros.
|
||||
Une seule Assurance Vie autorisée.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,146 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block title %}Mon Profil{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12 text-center text-white">
|
||||
<h2 class="dragon-title"><i class="bi bi-shield-lock"></i> Mon Profil & Sécurité</h2>
|
||||
<p>Gérez vos informations personnelles, bancaires et de connexion</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<!-- COLONNE GAUCHE : INFOS PERSONNELLES & BANCAIRES -->
|
||||
<div class="col-md-6 mb-4">
|
||||
|
||||
<!-- INFORMATIONS PERSONNELLES -->
|
||||
<div class="card bg-dark text-white dragon-card mb-4">
|
||||
<div class="card-header bg-transparent border-bottom border-secondary">
|
||||
<h5 class="mb-0"><i class="bi bi-person-vcard"></i> Informations Personnelles</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('profile') }}">
|
||||
<input type="hidden" name="action" value="update_profile">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted">Prénom</label>
|
||||
<input type="text" class="form-control bg-secondary text-white border-0" value="{{ user_profile.get('first_name', '') }}" disabled>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted">Nom</label>
|
||||
<input type="text" class="form-control bg-secondary text-white border-0" value="{{ user_profile.get('last_name', '') }}" disabled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="phone" class="form-label">Numéro de téléphone</label>
|
||||
<input type="text" class="form-control" id="phone" name="phone" value="{{ user_profile.get('phone', '') or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="address" class="form-label">Adresse postale</label>
|
||||
<input type="text" class="form-control" id="address" name="address" value="{{ user_profile.get('address', '') or '' }}">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-warning w-100 dragon-btn">Mettre à jour mes informations</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INFORMATIONS BANCAIRES -->
|
||||
<div class="card bg-dark text-white dragon-card">
|
||||
<div class="card-header bg-transparent border-bottom border-secondary">
|
||||
<h5 class="mb-0"><i class="bi bi-bank"></i> Mes Comptes Bancaires</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<ul class="list-group list-group-flush bg-transparent">
|
||||
{% for account in accounts %}
|
||||
<li class="list-group-item bg-transparent text-white border-secondary">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>{{ account.account_type | capitalize | replace('_', ' ') }}</strong>
|
||||
<small class="d-block text-muted font-monospace">{{ account.account_number }}</small>
|
||||
</div>
|
||||
<span class="badge bg-success rounded-pill px-3 py-2 fs-6">{{ "%.2f"|format(account.balance) }} €</span>
|
||||
</div>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="list-group-item bg-transparent text-white border-0 text-muted text-center py-4">
|
||||
<i class="bi bi-wallet2 fs-2 d-block mb-2"></i>
|
||||
Aucun compte bancaire ouvert
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- COLONNE DROITE : EMAIL ET MOT DE PASSE -->
|
||||
<div class="col-md-6 mb-4">
|
||||
|
||||
<!-- CHANGEMENT EMAIL -->
|
||||
<div class="card bg-dark text-white dragon-card mb-4">
|
||||
<div class="card-header bg-transparent border-bottom border-secondary">
|
||||
<h5 class="mb-0"><i class="bi bi-envelope"></i> Changer d'adresse e-mail</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('profile') }}">
|
||||
<input type="hidden" name="action" value="update_email">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-muted">Adresse e-mail actuelle</label>
|
||||
<input type="text" class="form-control bg-secondary text-white border-0" value="{{ user_profile.get('email', '') }}" disabled>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="new_email" class="form-label">Nouvelle adresse e-mail</label>
|
||||
<input type="email" class="form-control" id="new_email" name="new_email" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="password_for_email" class="form-label">Mot de passe actuel (requis)</label>
|
||||
<input type="password" class="form-control" id="password_for_email" name="password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-warning w-100 dragon-btn">Modifier mon e-mail</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CHANGEMENT MOT DE PASSE -->
|
||||
<div class="card bg-dark text-white dragon-card">
|
||||
<div class="card-header bg-transparent border-bottom border-secondary">
|
||||
<h5 class="mb-0"><i class="bi bi-key"></i> Changer de mot de passe</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ url_for('profile') }}">
|
||||
<input type="hidden" name="action" value="update_password">
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="current_password" class="form-label">Mot de passe actuel</label>
|
||||
<input type="password" class="form-control" id="current_password" name="current_password" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="new_password" class="form-label">Nouveau mot de passe</label>
|
||||
<input type="password" class="form-control" id="new_password" name="new_password" required>
|
||||
<div class="form-text text-muted">Minimum 8 caractères, une majuscule et un chiffre.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="confirm_password" class="form-label">Confirmez le mot de passe</label>
|
||||
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-warning w-100 dragon-btn">Mettre à jour le mot de passe</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,321 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Simulateur d'Épargne{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="main-content">
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-graph-up-arrow"></i> Simulateur d'Épargne</h1>
|
||||
<p>Projetez la croissance de votre épargne avec les intérêts composés</p>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
<!-- Formulaire de simulation -->
|
||||
<div class="col-lg-4">
|
||||
<div class="section-card">
|
||||
<div class="section-title">Paramètres</div>
|
||||
|
||||
<form method="POST" action="{{ url_for('simulator') }}" id="simForm">
|
||||
|
||||
{% if comptes_epargne %}
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-piggy-bank"></i> Pré-remplir depuis un compte
|
||||
</label>
|
||||
<select id="presetCompte" class="form-select">
|
||||
<option value="">— Saisie manuelle —</option>
|
||||
{% for c in comptes_epargne %}
|
||||
<option value="{{ c.balance }}" data-taux="{{ c.interest_rate * 100 }}">
|
||||
{% if c.account_type == 'livret_a' %}Livret A
|
||||
{% elif c.account_type == 'assurance_vie' %}Assurance Vie
|
||||
{% else %}{{ c.account_type }}{% endif %}
|
||||
— {{ "%.2f"|format(c.balance) }} €
|
||||
({{ "%.1f"|format(c.interest_rate * 100) }} %)
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-currency-euro"></i> Capital initial (€)
|
||||
</label>
|
||||
<input type="number" name="capital_initial" id="capitalInitial"
|
||||
class="form-control" min="0" step="100" value="1000" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-percent"></i> Taux annuel (%)
|
||||
</label>
|
||||
<input type="number" name="taux_annuel" id="tauxAnnuel"
|
||||
class="form-control" min="0" max="100" step="0.1" value="3.0" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-calendar-range"></i> Durée (années)
|
||||
</label>
|
||||
<input type="number" name="duree_annees" id="dureeAnnees"
|
||||
class="form-control" min="1" max="40" step="1" value="10" required>
|
||||
<small class="form-text">Entre 1 et 40 ans</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-plus-circle"></i> Versement mensuel (€)
|
||||
</label>
|
||||
<input type="number" name="versement_mensuel" id="versementMensuel"
|
||||
class="form-control" min="0" step="50" value="0">
|
||||
<small class="form-text">Optionnel — laissez 0 si aucun</small>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-dragon w-100 mt-2">
|
||||
<i class="bi bi-calculator"></i> Calculer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Résultats -->
|
||||
<div class="col-lg-8">
|
||||
|
||||
{% if resultat %}
|
||||
<!-- Métriques clés -->
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card success fade-in">
|
||||
<div class="stat-icon">
|
||||
<i class="bi bi-trophy"></i>
|
||||
</div>
|
||||
<div class="stat-value">
|
||||
{{ "%.2f"|format(resultat.capital_final) }} €
|
||||
</div>
|
||||
<div class="stat-label">Capital final</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card info fade-in delay-1">
|
||||
<div class="stat-icon">
|
||||
<i class="bi bi-graph-up"></i>
|
||||
</div>
|
||||
<div class="stat-value">
|
||||
{{ "%.2f"|format(resultat.total_interets) }} €
|
||||
</div>
|
||||
<div class="stat-label">Intérêts générés</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card warning fade-in delay-2">
|
||||
<div class="stat-icon">
|
||||
<i class="bi bi-percent"></i>
|
||||
</div>
|
||||
<div class="stat-value">
|
||||
+{{ "%.1f"|format(resultat.gain_pourcentage) }} %
|
||||
</div>
|
||||
<div class="stat-label">Gain sur versements</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Graphique -->
|
||||
<div class="section-card fade-in delay-3">
|
||||
<div class="section-title">
|
||||
<i class="bi bi-bar-chart-line"></i> Évolution sur {{ resultat.duree_annees }} an(s)
|
||||
</div>
|
||||
<canvas id="simulatorChart" height="280"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Tableau année par année -->
|
||||
<div class="section-card fade-in delay-4">
|
||||
<div class="section-title">
|
||||
<i class="bi bi-table"></i> Détail annuel
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Année</th>
|
||||
<th class="text-end">Total versé</th>
|
||||
<th class="text-end">Intérêts</th>
|
||||
<th class="text-end">Capital total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for point in resultat.courbe %}
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge bg-secondary">An {{ point.annee }}</span>
|
||||
</td>
|
||||
<td class="text-end text-muted" style="font-family: var(--font-mono);">
|
||||
{{ "%.2f"|format(point.total_verse) }} €
|
||||
</td>
|
||||
<td class="text-end" style="font-family: var(--font-mono); color: var(--c-green); font-weight: 500;">
|
||||
+{{ "%.2f"|format(point.interets) }} €
|
||||
</td>
|
||||
<td class="text-end fw-bold" style="font-family: var(--font-mono);">
|
||||
{{ "%.2f"|format(point.solde) }} €
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<!-- État initial — pas encore simulé -->
|
||||
<div class="section-card text-center py-5 fade-in">
|
||||
<i class="bi bi-graph-up-arrow"
|
||||
style="font-size: 5rem; color: var(--c-accent); opacity: 0.3;"></i>
|
||||
<h3 class="mt-4" style="color: var(--c-text-secondary);">
|
||||
Renseignez les paramètres et cliquez sur <strong>Calculer</strong>
|
||||
</h3>
|
||||
<p class="text-muted mt-2">
|
||||
Le simulateur applique la formule des intérêts composés avec versements mensuels réguliers.
|
||||
</p>
|
||||
|
||||
<div class="row g-3 mt-4 text-start">
|
||||
<div class="col-md-4">
|
||||
<div class="section-card" style="border-left: 3px solid var(--c-green);">
|
||||
<h6 class="fw-bold mb-1">
|
||||
<i class="bi bi-piggy-bank" style="color: var(--c-green);"></i>
|
||||
Livret A
|
||||
</h6>
|
||||
<p class="text-muted mb-0" style="font-size: 0.8125rem;">
|
||||
Taux réglementé de 3 % par cycle. Essayez avec 10 ans.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="section-card" style="border-left: 3px solid var(--c-amber);">
|
||||
<h6 class="fw-bold mb-1">
|
||||
<i class="bi bi-shield-check" style="color: var(--c-amber);"></i>
|
||||
Assurance Vie
|
||||
</h6>
|
||||
<p class="text-muted mb-0" style="font-size: 0.8125rem;">
|
||||
Fonds euros à 2 % par cycle. Idéal sur le long terme.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="section-card" style="border-left: 3px solid var(--c-accent);">
|
||||
<h6 class="fw-bold mb-1">
|
||||
<i class="bi bi-plus-circle" style="color: var(--c-accent);"></i>
|
||||
Versement mensuel
|
||||
</h6>
|
||||
<p class="text-muted mb-0" style="font-size: 0.8125rem;">
|
||||
Ajoutez un versement régulier pour voir l'effet de l'épargne progressive.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
{% if resultat %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
|
||||
<script>
|
||||
var courbe = {{ resultat.courbe | tojson }};
|
||||
var labels = courbe.map(function(p) { return 'An ' + p.annee; });
|
||||
var soldes = courbe.map(function(p) { return p.solde; });
|
||||
var verses = courbe.map(function(p) { return p.total_verse; });
|
||||
var interets = courbe.map(function(p) { return p.interets; });
|
||||
|
||||
var ctx = document.getElementById('simulatorChart').getContext('2d');
|
||||
|
||||
new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Total versé',
|
||||
data: verses,
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.5)',
|
||||
borderColor: 'rgba(59, 130, 246, 0.8)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
stack: 'stack'
|
||||
},
|
||||
{
|
||||
label: 'Intérêts générés',
|
||||
data: interets,
|
||||
backgroundColor: 'rgba(0, 200, 150, 0.6)',
|
||||
borderColor: 'rgba(0, 200, 150, 0.9)',
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
stack: 'stack'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
labels: {
|
||||
font: { family: "'DM Sans', sans-serif", size: 12 },
|
||||
usePointStyle: true,
|
||||
padding: 20
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(ctx) {
|
||||
return ctx.dataset.label + ' : ' + ctx.parsed.y.toFixed(2) + ' €';
|
||||
},
|
||||
afterBody: function(items) {
|
||||
var annee = items[0].dataIndex;
|
||||
return 'Capital total : ' + soldes[annee].toFixed(2) + ' €';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true,
|
||||
grid: { display: false },
|
||||
ticks: {
|
||||
font: { family: "'DM Sans', sans-serif", size: 11 }
|
||||
}
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0,0,0,0.05)' },
|
||||
ticks: {
|
||||
font: { family: "'DM Mono', monospace", size: 11 },
|
||||
callback: function(val) { return val.toFixed(0) + ' €'; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
// Pré-remplissage depuis le compte épargne sélectionné
|
||||
var selectPreset = document.getElementById('presetCompte');
|
||||
if (selectPreset) {
|
||||
selectPreset.addEventListener('change', function () {
|
||||
var option = this.options[this.selectedIndex];
|
||||
var solde = parseFloat(option.value) || 0;
|
||||
var taux = parseFloat(option.getAttribute('data-taux')) || 0;
|
||||
|
||||
if (solde > 0) {
|
||||
document.getElementById('capitalInitial').value = solde.toFixed(2);
|
||||
document.getElementById('tauxAnnuel').value = taux.toFixed(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,195 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Historique des Transactions{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="main-content">
|
||||
<div class="page-header d-flex justify-content-between align-items-center flex-wrap gap-3">
|
||||
<div>
|
||||
<h1><i class="bi bi-clock-history"></i> Historique des Transactions</h1>
|
||||
<p>Consultez et exportez toutes vos opérations bancaires</p>
|
||||
</div>
|
||||
<a href="{{ url_for('export_transactions', account_id=selected_account or '') }}"
|
||||
class="btn btn-dragon-outline">
|
||||
<i class="bi bi-download"></i> Exporter CSV
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filtres -->
|
||||
<div class="section-card mb-4">
|
||||
<form method="GET" action="{{ url_for('transactions') }}" class="row g-3 align-items-end">
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label"><i class="bi bi-wallet2"></i> Compte</label>
|
||||
<select name="account_id" class="form-select">
|
||||
<option value="">Tous les comptes</option>
|
||||
{% for account in accounts %}
|
||||
<option value="{{ account.id }}"
|
||||
{% if selected_account == account.id|string %}selected{% endif %}>
|
||||
{{ account.account_number }} —
|
||||
{% if account.account_type == 'courant' %}Courant
|
||||
{% elif account.account_type == 'livret_a' %}Livret A
|
||||
{% elif account.account_type == 'assurance_vie' %}Assurance Vie
|
||||
{% else %}{{ account.account_type }}{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label"><i class="bi bi-tag"></i> Type</label>
|
||||
<select name="type" class="form-select">
|
||||
<option value="">Tous les types</option>
|
||||
<option value="virement_interne">Virement interne</option>
|
||||
<option value="virement_entre_personnes">Virement personne</option>
|
||||
<option value="virement_externe">Virement externe</option>
|
||||
<option value="depot">Dépôt</option>
|
||||
<option value="interets">Intérêts</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label"><i class="bi bi-search"></i> Recherche</label>
|
||||
<input type="text" id="searchInput" class="form-control"
|
||||
placeholder="Libellé, numéro de compte...">
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-dragon flex-grow-1">
|
||||
<i class="bi bi-funnel"></i> Filtrer
|
||||
</button>
|
||||
{% if selected_account %}
|
||||
<a href="{{ url_for('transactions') }}" class="btn btn-dragon-outline" title="Réinitialiser">
|
||||
<i class="bi bi-x-circle"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Tableau -->
|
||||
<div class="section-card">
|
||||
{% if transactions %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover" id="transactionsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Libellé</th>
|
||||
<th>De / Vers</th>
|
||||
<th class="text-end">Montant</th>
|
||||
<th>Statut</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="transactionsBody">
|
||||
{% for t in transactions %}
|
||||
<tr class="transaction-row">
|
||||
<td><small class="text-muted">{{ t.created_at[:16].replace('T', ' ') if t.created_at else 'N/A' }}</small></td>
|
||||
<td>
|
||||
{% if t.transaction_type == 'virement_interne' %}
|
||||
<span class="badge bg-primary">Interne</span>
|
||||
{% elif t.transaction_type == 'virement_entre_personnes' %}
|
||||
<span class="badge bg-info">Personne</span>
|
||||
{% elif t.transaction_type == 'virement_externe' %}
|
||||
<span class="badge bg-warning">Externe</span>
|
||||
{% elif t.transaction_type == 'depot' %}
|
||||
<span class="badge bg-success">Dépôt</span>
|
||||
{% elif t.transaction_type == 'retrait' %}
|
||||
<span class="badge bg-danger">Retrait</span>
|
||||
{% elif t.transaction_type == 'interets' %}
|
||||
<span class="badge bg-success">Intérêts</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ t.transaction_type }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><small>{{ t.description or '—' }}</small></td>
|
||||
<td>
|
||||
<small>
|
||||
{% if t.from_account_number %}<code>{{ t.from_account_number }}</code>{% endif %}
|
||||
{% if t.from_account_number and t.to_account_number %}<i class="bi bi-arrow-right text-muted"></i>{% endif %}
|
||||
{% if t.to_account_number %}<code>{{ t.to_account_number }}</code>{% endif %}
|
||||
{% if t.external_bank_name %}<i class="bi bi-bank text-muted"></i> {{ t.external_bank_name }}{% endif %}
|
||||
</small>
|
||||
</td>
|
||||
<td class="text-end" style="font-family: var(--font-mono); letter-spacing: -0.5px; font-weight: 500;">
|
||||
{{ "%.2f"|format(t.amount) }} €
|
||||
</td>
|
||||
<td>
|
||||
{% if t.status == 'completed' %}
|
||||
<span class="status-badge completed"><i class="bi bi-check-circle"></i> Effectué</span>
|
||||
{% elif t.status == 'pending' %}
|
||||
<span class="status-badge pending"><i class="bi bi-hourglass"></i> En cours</span>
|
||||
{% elif t.status == 'failed' %}
|
||||
<span class="status-badge failed"><i class="bi bi-x-circle"></i> Échoué</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">{{ t.status }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<small class="text-muted" id="transactionCount">
|
||||
<i class="bi bi-info-circle"></i> {{ transactions|length }} transaction(s) affichée(s)
|
||||
</small>
|
||||
<a href="{{ url_for('export_transactions', account_id=selected_account or '') }}"
|
||||
class="btn btn-sm btn-dragon-outline">
|
||||
<i class="bi bi-download"></i> Exporter en CSV
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="noResults" class="text-center py-4" style="display: none;">
|
||||
<i class="bi bi-search" style="font-size: 2.5rem; color: var(--c-text-tertiary);"></i>
|
||||
<p class="text-muted mt-2">Aucune transaction ne correspond à votre recherche.</p>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="bi bi-clock-history" style="font-size: 4rem; color: var(--c-text-tertiary);"></i>
|
||||
<h3 class="mt-3">Aucune transaction</h3>
|
||||
<p class="text-muted">Votre historique apparaîtra ici après vos premiers virements.</p>
|
||||
<a href="{{ url_for('transfer') }}" class="btn btn-dragon mt-2">
|
||||
<i class="bi bi-send"></i> Effectuer un virement
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
var inputRecherche = document.getElementById('searchInput');
|
||||
var corps = document.getElementById('transactionsBody');
|
||||
var compteur = document.getElementById('transactionCount');
|
||||
var messageVide = document.getElementById('noResults');
|
||||
|
||||
if (inputRecherche && corps) {
|
||||
inputRecherche.addEventListener('input', function () {
|
||||
var terme = this.value.toLowerCase().trim();
|
||||
var lignes = corps.querySelectorAll('.transaction-row');
|
||||
var nbVisible = 0;
|
||||
|
||||
for (var i = 0; i < lignes.length; i++) {
|
||||
var texte = lignes[i].textContent.toLowerCase();
|
||||
if (terme === '' || texte.includes(terme)) {
|
||||
lignes[i].style.display = '';
|
||||
nbVisible++;
|
||||
} else {
|
||||
lignes[i].style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (compteur) {
|
||||
compteur.innerHTML = '<i class="bi bi-info-circle"></i> ' + nbVisible + ' transaction(s) affichée(s)';
|
||||
}
|
||||
if (messageVide) {
|
||||
messageVide.style.display = (nbVisible === 0 && terme !== '') ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,178 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Effectuer un Virement{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="main-content">
|
||||
<div class="page-header">
|
||||
<h1><i class="bi bi-arrow-left-right"></i> Effectuer un Virement</h1>
|
||||
<p>Virement interne entre vos comptes ou vers un bénéficiaire</p>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-7">
|
||||
<div class="section-card fade-in">
|
||||
|
||||
<form method="POST" action="{{ url_for('transfer') }}" id="transferForm">
|
||||
|
||||
<!-- Type de virement -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-arrow-repeat"></i> Type de virement *
|
||||
</label>
|
||||
<select name="transfer_type" class="form-select" id="transferType" required>
|
||||
<option value="">-- Choisissez un type --</option>
|
||||
<option value="internal">Virement interne (entre mes comptes)</option>
|
||||
<option value="person">Virement vers un bénéficiaire</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Compte source (commun aux deux types) -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-wallet2"></i> Compte source *
|
||||
</label>
|
||||
<select name="from_account_id" class="form-select" required>
|
||||
<option value="">-- Choisissez un compte --</option>
|
||||
{% for account in accounts %}
|
||||
<option value="{{ account.id }}">
|
||||
{{ account.account_number }}
|
||||
—
|
||||
{% if account.account_type == 'courant' %}Compte Courant
|
||||
{% elif account.account_type == 'livret_a' %}Livret A
|
||||
{% elif account.account_type == 'assurance_vie' %}Assurance Vie
|
||||
{% else %}{{ account.account_type }}{% endif %}
|
||||
— {{ "%.2f"|format(account.balance) }} €
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Compte destination (virement interne) -->
|
||||
<div class="form-group" id="toAccountGroup" style="display: none;">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-wallet"></i> Compte destination *
|
||||
</label>
|
||||
<select name="to_account_id" class="form-select" id="toAccountSelect">
|
||||
<option value="">-- Choisissez un compte --</option>
|
||||
{% for account in accounts %}
|
||||
<option value="{{ account.id }}">
|
||||
{{ account.account_number }}
|
||||
—
|
||||
{% if account.account_type == 'courant' %}Compte Courant
|
||||
{% elif account.account_type == 'livret_a' %}Livret A
|
||||
{% elif account.account_type == 'assurance_vie' %}Assurance Vie
|
||||
{% else %}{{ account.account_type }}{% endif %}
|
||||
— {{ "%.2f"|format(account.balance) }} €
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Bénéficiaire (virement entre personnes) -->
|
||||
<div class="form-group" id="beneficiaryGroup" style="display: none;">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-person"></i> Bénéficiaire *
|
||||
</label>
|
||||
{% if beneficiaries %}
|
||||
<select name="beneficiary_id" class="form-select" id="beneficiarySelect">
|
||||
<option value="">-- Choisissez un bénéficiaire --</option>
|
||||
{% for b in beneficiaries %}
|
||||
<option value="{{ b.id }}">
|
||||
{{ b.beneficiary_name }} — {{ b.account_number }} ({{ b.bank_name }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-circle"></i>
|
||||
Aucun bénéficiaire enregistré.
|
||||
<a href="{{ url_for('add_beneficiary') }}" class="alert-link">
|
||||
Ajouter un bénéficiaire
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Montant -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-currency-euro"></i> Montant (€) *
|
||||
</label>
|
||||
<input type="number" name="amount" class="form-control"
|
||||
min="0.01" max="50000" step="0.01"
|
||||
placeholder="0.00" required>
|
||||
<small class="text-muted">Minimum 0,01 € — Maximum 50 000 €</small>
|
||||
</div>
|
||||
|
||||
<!-- Libellé -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">
|
||||
<i class="bi bi-chat-text"></i> Libellé (optionnel)
|
||||
</label>
|
||||
<input type="text" name="description" class="form-control"
|
||||
maxlength="200" placeholder="Ex: Remboursement restaurant">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-dragon w-100 mt-3" id="submitBtn" disabled>
|
||||
<i class="bi bi-send"></i> Effectuer le virement
|
||||
</button>
|
||||
|
||||
<a href="{{ url_for('dashboard') }}" class="btn btn-dragon-outline w-100 mt-2">
|
||||
<i class="bi bi-arrow-left"></i> Retour au tableau de bord
|
||||
</a>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Info si aucun compte -->
|
||||
{% if not accounts %}
|
||||
<div class="section-card text-center py-4 mt-3">
|
||||
<i class="bi bi-wallet" style="font-size: 3rem; color: var(--dragon-text-light);"></i>
|
||||
<h4 class="mt-3">Aucun compte disponible</h4>
|
||||
<p class="text-muted">Vous devez posséder au moins un compte pour effectuer un virement.</p>
|
||||
<a href="{{ url_for('open_account') }}" class="btn btn-dragon">
|
||||
<i class="bi bi-plus-circle"></i> Ouvrir un compte
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const selectType = document.getElementById('transferType');
|
||||
const groupToAccount = document.getElementById('toAccountGroup');
|
||||
const groupBeneficiary = document.getElementById('beneficiaryGroup');
|
||||
const selectToAccount = document.getElementById('toAccountSelect');
|
||||
const selectBeneficiary = document.getElementById('beneficiarySelect');
|
||||
const btnSubmit = document.getElementById('submitBtn');
|
||||
|
||||
// Affiche ou masque les champs selon le type de virement choisi
|
||||
selectType.addEventListener('change', function () {
|
||||
var type = this.value;
|
||||
|
||||
if (type === 'internal') {
|
||||
groupToAccount.style.display = 'block';
|
||||
groupBeneficiary.style.display = 'none';
|
||||
if (selectToAccount) { selectToAccount.required = true; }
|
||||
if (selectBeneficiary) { selectBeneficiary.required = false; }
|
||||
|
||||
} else if (type === 'person') {
|
||||
groupToAccount.style.display = 'none';
|
||||
groupBeneficiary.style.display = 'block';
|
||||
if (selectToAccount) { selectToAccount.required = false; }
|
||||
if (selectBeneficiary) { selectBeneficiary.required = true; }
|
||||
|
||||
} else {
|
||||
groupToAccount.style.display = 'none';
|
||||
groupBeneficiary.style.display = 'none';
|
||||
if (selectToAccount) { selectToAccount.required = false; }
|
||||
if (selectBeneficiary) { selectBeneficiary.required = false; }
|
||||
}
|
||||
|
||||
btnSubmit.disabled = (type === '');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user