feat(dashboard): Ajoute le tableau de bord client avec données statiques.

Crée un nouveau tableau de bord client avec affichage des factures, avis
de paiement, devis, mensualités et services actifs (données statiques).
This commit is contained in:
Serreau Jovann
2025-10-10 08:53:48 +02:00
parent 328b9dc08f
commit f701867684
2 changed files with 300 additions and 0 deletions

View File

@@ -1 +1,252 @@
{% extends 'artemis/base.twig' %}
{% block title %}Tableau de bord Client{% endblock %}
{% block content %}
{# Titre adapté au Dark Mode #}
<h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 mb-8">Espace Client - Tableau de Bord</h1>
{% if is_granted('ROLE_CUSTOMER') %}
{# Données Statiques simulant les informations de l'API/BDD #}
{% set remaining_amount = 450.75 %}
{% set static_invoices = [
{'reference': 'F-2024-0012', 'date': '2024-09-15', 'amount': 150.00, 'status': 'En attente', 'id': 12},
{'reference': 'F-2024-0011', 'date': '2024-08-20', 'amount': 300.75, 'status': 'En attente', 'id': 11},
{'reference': 'F-2024-0010', 'date': '2024-07-25', 'amount': 55.00, 'status': 'Payé', 'id': 10},
] %}
{% set static_notices = [
{'reference': 'AP-2024-0005', 'date': '2024-09-25', 'amount': 300.75, 'status': 'Validé', 'id': 5},
{'reference': 'AP-2024-0004', 'date': '2024-08-30', 'amount': 55.00, 'status': 'Traité', 'id': 4},
] %}
{% set static_quotes = [
{'reference': 'D-2024-0021', 'date': '2024-10-01', 'amount': 890.50, 'status': 'Accepté', 'id': 21},
{'reference': 'D-2024-0020', 'date': '2024-09-10', 'amount': 120.00, 'status': 'En cours', 'id': 20},
] %}
{% set payment_plan = {
'next_due_date': '2025-10-20',
'next_amount': 125.50,
'is_late': true,
'payment_id': 'FP-003'
} %}
{% set upcoming_payments = [
{'description': 'Renouvellement Hébergement PRO', 'due_date': '2025-12-01', 'amount': 180.00},
{'description': 'Renouvellement Nom de Domaine (.com)', 'due_date': '2025-12-15', 'amount': 12.99},
{'description': 'Abonnement E-mail Premium', 'due_date': '2026-01-01', 'amount': 5.00},
] %}
{# NOUVELLE DONNÉE STATIQUE: Liste des domaines et sites #}
{% set active_services = [
{'name': 'monsiteprincipal.com', 'type': 'Nom de Domaine + Hébergement', 'status': 'Actif'},
{'name': 'mon-blog-pro.fr', 'type': 'Nom de Domaine', 'status': 'Actif'},
{'name': 'projet-beta.net', 'type': 'Nom de Domaine', 'status': 'Expiré'},
] %}
{# FIN des Données Statiques #}
<div class="flex flex-col lg:flex-row gap-6">
{# COLONNE GAUCHE (1/3) : Paiements, Mensualités & Services #}
<div class="lg:w-1/3 flex flex-col gap-6">
{# 1. Carte: Montant Restant à Payer (Total Dû) #}
<div class="bg-red-600 dark:bg-red-700 text-white rounded-xl shadow-xl p-6">
<div class="text-lg font-semibold border-b border-red-500 dark:border-red-600 pb-2 mb-3">Montant Total à Payer</div>
<div class="flex flex-col justify-center">
{% if remaining_amount > 0 %}
<p class="text-5xl font-extrabold">{{ remaining_amount | number_format(2, ',', ' ') }} €</p>
<p class="mt-3 text-red-100 dark:text-red-200">Total incluant les mensualités en cours.</p>
{% else %}
<p class="text-5xl font-extrabold">0,00 €</p>
<p class="mt-3 text-red-100 dark:text-red-200">Votre compte est à jour. Merci de votre confiance !</p>
{% endif %}
</div>
</div>
{# 2. Carte: Prochaine Mensualité (Facilité de paiement) #}
{% if payment_plan is defined and payment_plan.next_amount > 0 %}
{# Définition des classes en fonction du statut (Dark Mode inclus) #}
{% set card_color = payment_plan.is_late ? 'bg-yellow-500 dark:bg-yellow-700' : 'bg-indigo-600 dark:bg-indigo-700' %}
{% set button_text = payment_plan.is_late ? 'Régulariser' : 'Payer' %}
{% set button_class = payment_plan.is_late ? 'bg-red-700 hover:bg-red-800' : 'bg-green-600 hover:bg-green-700' %}
{% set button_focus_color = payment_plan.is_late ? 'red' : 'green' %}
<div class="rounded-xl shadow-xl p-6 {{ card_color }} text-white">
<div class="text-lg font-semibold border-b border-white border-opacity-30 pb-2 mb-3">
Prochaine Mensualité
{% if payment_plan.is_late %}
<span class="inline-block ml-2 text-xs font-bold text-red-900 bg-white dark:bg-red-200 dark:text-red-900 rounded-full px-2 py-0.5">RETARD</span>
{% endif %}
</div>
<div class="flex justify-between items-center mb-4">
<div>
<p class="text-4xl font-extrabold">{{ payment_plan.next_amount | number_format(2, ',', ' ') }} €</p>
<p class="mt-1 text-sm opacity-90">Échéance: {{ payment_plan.next_due_date }}</p>
</div>
{# Bouton Payer / Régulariser #}
<a href="#" class="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white {{ button_class }} focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-{{ button_focus_color }}-200">
{{ button_text }}
</a>
</div>
{% if payment_plan.is_late %}
<p class="text-xs text-red-900 font-semibold mt-2">⚠️ Action requise : Cette mensualité est en retard.</p>
{% endif %}
</div>
{% endif %}
{# 3. Carte: Prochains Paiements à Venir #}
<div class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 rounded-xl shadow-xl p-6">
<div class="text-lg font-semibold border-b border-gray-300 dark:border-gray-600 pb-2 mb-3">Paiements Récurents à Venir</div>
{% if upcoming_payments is not empty %}
<ul class="space-y-3 text-sm">
{% for payment in upcoming_payments | slice(0, 3) %} {# Limite à 3 éléments #}
<li class="flex justify-between items-start">
<div class="pr-2">
<p class="font-medium truncate" title="{{ payment.description }}">{{ payment.description }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">Échéance : {{ payment.due_date }}</p>
</div>
<span class="font-bold text-green-700 dark:text-green-400">{{ payment.amount | number_format(2, ',', ' ') }} €</span>
</li>
{% endfor %}
</ul>
<div class="mt-3 pt-3 border-t border-gray-300 dark:border-gray-600 text-center">
<a href="#" class="text-xs font-medium text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300">Voir tous les services</a>
</div>
{% else %}
<p class="text-sm text-gray-500 dark:text-gray-400">Aucun paiement récurrent planifié pour l'instant.</p>
{% endif %}
</div>
{# 4. Carte: Sites Internet et Domaines (NOUVEAU) #}
<div class="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-100 rounded-xl shadow-xl p-6">
<div class="text-lg font-semibold border-b border-gray-300 dark:border-gray-600 pb-2 mb-3">Mes Services Actifs</div>
{% if active_services is not empty %}
<ul class="space-y-3 text-sm">
{% for service in active_services | slice(0, 3) %} {# Limite à 3 éléments #}
<li class="flex justify-between items-center">
<div class="pr-2">
<p class="font-medium text-indigo-600 dark:text-indigo-400 truncate" title="{{ service.name }}">{{ service.name }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ service.type }}</p>
</div>
{% set status_class = service.status == 'Actif' ? 'bg-green-100 text-green-800 dark:bg-green-800 dark:text-green-100' : 'bg-red-100 text-red-800 dark:bg-red-800 dark:text-red-100' %}
<span class="px-2 py-0.5 text-xs font-semibold rounded-full {{ status_class }}">{{ service.status }}</span>
</li>
{% endfor %}
</ul>
<div class="mt-3 pt-3 border-t border-gray-300 dark:border-gray-600 text-center">
<a href="#" class="text-xs font-medium text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300">Gérer mes services</a>
</div>
{% else %}
<p class="text-sm text-gray-500 dark:text-gray-400">Aucun service actif trouvé. Contactez le support pour toute question.</p>
{% endif %}
</div>
</div>
{# COLONNE DROITE (2/3) : Liste des Derniers Documents #}
<div class="lg:w-2/3">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-xl p-6">
<div class="text-xl font-semibold text-gray-700 dark:text-gray-200 border-b border-gray-200 dark:border-gray-700 pb-3 mb-4">Derniers Documents</div>
{# Onglets de Navigation (Adapté au Dark Mode) #}
<div class="border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex space-x-8" id="documentTabs" role="tablist">
{% set tab_class = "tab-link border-b-2 border-transparent py-2 px-1 text-sm font-medium text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-500 cursor-pointer transition duration-150" %}
{% set active_class = "border-indigo-500 dark:border-indigo-400 text-indigo-600 dark:text-indigo-400" %}
<a id="invoices-tab" data-target="#invoices" role="tab" aria-controls="invoices" aria-selected="true"
class="{{ tab_class }}">
Factures ({{ static_invoices|length }})
</a>
<a id="notices-tab" data-target="#notices" role="tab" aria-controls="notices" aria-selected="false"
class="{{ tab_class }}">
Avis de Paiement ({{ static_notices|length }})
</a>
<a id="quotes-tab" data-target="#quotes" role="tab" aria-controls="quotes" aria-selected="false"
class="{{ tab_class }}">
Devis ({{ static_quotes|length }})
</a>
</nav>
</div>
<div id="documentTabsContent" class="mt-4">
{# Inclusions du fichier artemis/dashboard/customer_doc_list.twig #}
<div id="invoices" role="tabpanel" aria-labelledby="invoices-tab" class="tab-pane-tailwind block">
{% include 'artemis/dashboard/customer_doc_list.twig' with {'documents': static_invoices, 'type': 'facture'} %}
</div>
<div id="notices" role="tabpanel" aria-labelledby="notices-tab" class="tab-pane-tailwind hidden">
{% include 'artemis/dashboard/customer_doc_list.twig' with {'documents': static_notices, 'type': 'avis de paiement'} %}
</div>
<div id="quotes" role="tabpanel" aria-labelledby="quotes-tab" class="tab-pane-tailwind hidden">
{% include 'artemis/dashboard/customer_doc_list.twig' with {'documents': static_quotes, 'type': 'devis'} %}
</div>
</div>
</div>
</div>
</div>
{% else %}
{% endif %}
{# PETIT SCRIPT JAVASCRIPT POUR FAIRE FONCTIONNER LES ONGLETS #}
<script>
document.addEventListener('DOMContentLoaded', () => {
const tabs = document.querySelectorAll('.tab-link');
const panes = document.querySelectorAll('.tab-pane-tailwind');
const activeClasses = ['border-indigo-500', 'dark:border-indigo-400', 'text-indigo-600', 'dark:text-indigo-400'];
const inactiveClasses = ['border-transparent', 'text-gray-500', 'dark:text-gray-400', 'hover:text-gray-700', 'dark:hover:text-gray-300', 'hover:border-gray-300', 'dark:hover:border-gray-500'];
// Fonction pour appliquer le style actif/inactif
const applyTabStyles = (tab, isActive) => {
if (isActive) {
tab.classList.add(...activeClasses);
tab.classList.remove(...inactiveClasses);
} else {
tab.classList.remove(...activeClasses);
tab.classList.add(...inactiveClasses);
}
};
tabs.forEach(tab => {
tab.addEventListener('click', (e) => {
e.preventDefault();
const targetId = tab.getAttribute('data-target');
// Désactiver tous les onglets et cacher tous les panneaux
tabs.forEach(t => applyTabStyles(t, false));
panes.forEach(p => p.classList.add('hidden'));
// Activer l'onglet cliqué
applyTabStyles(tab, true);
// Afficher le panneau correspondant
const targetPane = document.querySelector(targetId);
if (targetPane) {
targetPane.classList.remove('hidden');
targetPane.classList.add('block');
}
});
});
// Initialiser le premier onglet (Factures) comme actif au chargement
const firstTab = document.getElementById('invoices-tab');
if (firstTab) {
firstTab.click();
}
});
</script>
{% endblock %}

View File

@@ -0,0 +1,49 @@
{# customer/dashboard/_document_list_tailwind_dark.html.twig #}
{% if documents is not empty %}
<div class="overflow-x-auto shadow-lg rounded-lg">
<table class="min-w-full divide-y divide-gray-700">
{# Table Header (Dark Mode) #}
<thead class="bg-gray-800 dark:bg-gray-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Référence</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Montant</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Statut</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-400 uppercase tracking-wider">Action</th>
</tr>
</thead>
{# Table Body (Dark Mode) #}
<tbody class="bg-gray-900 dark:bg-gray-800 divide-y divide-gray-700">
{% for document in documents %}
<tr class="hover:bg-gray-700 transition duration-150 ease-in-out">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-white">{{ document.reference }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-400">{{ document.date }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-green-400 font-semibold">{{ document.amount | number_format(2, ',', ' ') }} €</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
{# Logique pour les badges de statut (Couleurs ajustées pour le Dark Mode) #}
{% set badge_class = 'bg-blue-600 text-blue-100' %}
{% if document.status == 'Payé' or document.status == 'Validé' or document.status == 'Accepté' %}
{% set badge_class = 'bg-green-600 text-green-100' %}
{% elseif document.status == 'En attente' or document.status == 'En cours' %}
{% set badge_class = 'bg-yellow-600 text-yellow-100' %}
{% endif %}
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ badge_class }}">
{{ document.status }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
{# Bouton 'Voir' stylisé pour le Dark Mode #}
<a href="#" class="text-indigo-400 hover:text-indigo-300 font-medium py-1 px-3 border border-indigo-600 rounded-md transition duration-150 ease-in-out hover:bg-indigo-700">Voir</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
{# Message si la liste est vide (Dark Mode) #}
<p class="mt-4 text-gray-400 p-4 border border-dashed border-gray-600 bg-gray-800 rounded-md">
Aucun **{{ type }}** récent n'est disponible.
</p>
{% endif %}