Files
e-cosplay/templates/artemis/dashboard.twig
Serreau Jovann e738753a6a ```
 feat(dashboard): Ajoute affichage des serveurs Google Compute et OVH.
Ajoute le script mqtt et la class ServerCard pour afficher le status serveur.
```
2025-11-06 08:04:11 +01:00

366 lines
21 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">45 230 €</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">62 500 €</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">8 120 €</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">
- 17 270 €
</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">
<li class="flex justify-between text-sm">
<span>mondomaine.com</span>
<span class="text-red-500 font-medium">J-7</span>
</li>
<li class="flex justify-between text-sm">
<span>autre-site.fr</span>
<span class="text-orange-400 font-medium">J-30</span>
</li>
</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">
<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">28.5%</p>
</div>
<div class="text-right">
<p class="text-sm text-red-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path></svg>
+0.8 pt
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</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">35.0%</p>
</div>
<div class="text-right">
<p class="text-sm text-green-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path></svg>
-0.5 pt
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</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">1 850,00 €</p>
</div>
<div class="text-right">
<p class="text-sm text-red-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18"></path></svg>
+3.5%
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</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">425,50 €</p>
</div>
<div class="text-right">
<p class="text-sm text-green-500 font-semibold flex items-center">
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3"></path></svg>
-1.2%
</p>
<p class="text-xs text-gray-400 dark:text-gray-500">vs Mois Précédent</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">63.5%</p>
</div>
</div>
<p class="text-xs text-gray-400 dark:text-gray-500 mt-3">
(Coût Infra 28.5% + Coût Salarial 35.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 %}