359 lines
20 KiB
Twig
359 lines
20 KiB
Twig
{% extends 'artemis/base.twig' %}
|
|
|
|
{% block title %}Tableau de bord Client{% endblock %}
|
|
|
|
{% block content %}
|
|
{# Titre adapté au Dark Mode #}
|
|
|
|
{% if is_granted('ROLE_CUSTOMER') %}
|
|
<h1 class="text-3xl font-bold text-gray-800 dark:text-gray-100 mb-8">Espace Client - Tableau de Bord</h1>
|
|
{# Données Statiques simulant les informations de l'API/BDD #}
|
|
|
|
|
|
{% 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},
|
|
] %}
|
|
|
|
{# 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|trans }}</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 Documents EMPILÉE #}
|
|
<div class="lg:w-2/3 flex flex-col gap-6">
|
|
|
|
{# 1. Section : Factures #}
|
|
<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">Dernières Factures</div>
|
|
{% include 'artemis/dashboard/customer_doc_list.twig' with {'documents': invoiceList, 'type': 'facture'} %}
|
|
</div>
|
|
|
|
{# 2. Section : Avis de Paiement #}
|
|
<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">Avis de Paiement</div>
|
|
{% include 'artemis/dashboard/customer_doc_list.twig' with {'documents': advertList, 'type': 'avis de paiement'} %}
|
|
</div>
|
|
|
|
{# 3. Section : Devis #}
|
|
<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">Devis</div>
|
|
{% include 'artemis/dashboard/customer_doc_list.twig' with {'documents': devisList, 'type': 'devis'} %}
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
{% else %}
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">CA En Cours</p>
|
|
<p class="text-2xl font-bold text-green-500">{{ totalCaCurrent|format_currency('EUR') }}</p>
|
|
</div>
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">CA Prévu</p>
|
|
<p class="text-2xl font-bold text-blue-400">{{ totalCaPrevu|format_currency('EUR') }}</p>
|
|
</div>
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">Total Impayés</p>
|
|
<p class="text-2xl font-bold text-red-500">{{ faults|format_currency('EUR')}}</p>
|
|
</div>
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800 border-l-4 border-yellow-500">
|
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Écart / Objectif Prévu</p>
|
|
<p class="text-2xl font-bold text-yellow-500">
|
|
{{ diffCa|format_currency('EUR') }}
|
|
</p>
|
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
|
(Reste à atteindre ce mois)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100">
|
|
<h2 class="text-xl font-semibold mb-4">🔔 Domaines à Renouveler</h2>
|
|
<ul class="space-y-2">
|
|
{% for dnsItem in dns %}
|
|
{% set expiration_date = date(dnsItem.expiredAt) %}
|
|
{% set now = date('now') %}
|
|
{% set days = ((expiration_date.timestamp - now.timestamp) / (60 * 60 * 24))|round(0, 'floor') %}
|
|
{% set text_class = 'text-gray-500 dark:text-gray-400' %}
|
|
{% if days < 0 %}
|
|
{% set text_class = 'text-gray-400 dark:text-gray-500 italic' %}
|
|
{% elseif days < 30 %}
|
|
{% set text_class = 'text-red-600 dark:text-red-500' %}
|
|
{% elseif days < 60 %}
|
|
{% set text_class = 'text-yellow-600 dark:text-yellow-400' %}
|
|
{% else %}
|
|
{% set text_class = 'text-green-600 dark:text-green-400' %}
|
|
{% endif %}
|
|
<li class="flex justify-between items-center py-2 text-sm border-b border-gray-100 dark:border-gray-700 last:border-b-0">
|
|
<span class="text-gray-900 dark:text-gray-200 font-medium truncate w-1/2">
|
|
{{ dnsItem.ndd }}
|
|
</span>
|
|
|
|
<span class="font-bold w-1/4 text-right {{ text_class }}">
|
|
{% if days < 0 %}
|
|
Expiré (J{{ days }})
|
|
{% else %}
|
|
J-{{ days }}
|
|
{% endif %}
|
|
</span>
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
|
|
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-gray-100">📑 Factures à Valider</h2>
|
|
<div class="border-l-4 border-yellow-500 bg-yellow-50 dark:bg-yellow-900/50 p-3" style="display: none">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-yellow-100">Facture #2025-001 (Client A)</p>
|
|
<p class="text-xs text-gray-600 dark:text-gray-300">Montant : 1 200 € - En attente</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
|
|
|
|
{% for server in servers %}
|
|
<div class="flex flex-col items-center p-3 rounded-lg shadow-md bg-white dark:bg-gray-800 border-l-4 border-green-500">
|
|
<span class="text-sm font-semibold text-gray-900 dark:text-white truncate w-full text-center">{{ server.name }}</span>
|
|
<span class="mt-1 px-2 text-xs font-bold text-white bg-{% if server.status == "RUNNING" %}green{% else %}red{% endif %}-500 rounded">{% if server.status == "RUNNING" %}OK{% else %}DOWN{% endif %}</span>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800 border-l-4 border-orange-500">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Ratio Coût Infra / CA</p>
|
|
<p class="text-2xl font-bold text-orange-500 mt-1">0%</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800 border-l-4 border-teal-500">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Ratio Coût Salarial / CA</p>
|
|
<p class="text-2xl font-bold text-teal-500 mt-1">0%</p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Coût Google Cloud (Mois)</p>
|
|
<p class="text-2xl font-bold text-blue-500 mt-1">0 €</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Coût OVH (Mois)</p>
|
|
<p class="text-2xl font-bold text-purple-500 mt-1">0 €</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
|
|
|
<div class="md:col-span-2 p-6 rounded-lg shadow-xl bg-white dark:bg-gray-800 border-l-4 border-green-500">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Statut de Rentabilité Globale</p>
|
|
<p class="text-3xl font-bold text-green-500 mt-1">
|
|
🟢 RENTRABLE
|
|
</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">Total Coûts / CA :</p>
|
|
<p class="text-xl font-bold text-green-500">0%</p>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-400 dark:text-gray-500 mt-3">
|
|
(Coût Infra 0% + Coût Salarial 0%)
|
|
</p>
|
|
</div>
|
|
|
|
<div class="md:col-span-2">
|
|
</div>
|
|
|
|
</div>
|
|
{% 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 %}
|