This commit is contained in:
2026-03-27 17:52:41 +01:00
parent 3cf8233054
commit 8320738acb
32 changed files with 5113 additions and 1385 deletions
+8 -2
View File
@@ -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 %}
+146
View File
@@ -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 %}
+178
View File
@@ -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 %}