diff --git a/src/App.jsx b/src/App.jsx
index c982aca..7a6413c 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -20,6 +20,7 @@ import Store from './pages/app/Store';
import Checkout from './pages/app/Checkout';
import Services from './pages/app/Services';
import Support from './pages/app/Support';
+import BillingHistory from './pages/app/BillingHistory';
export default function App() {
return (
@@ -49,6 +50,7 @@ export default function App() {
} />
} />
} />
+ } />
diff --git a/src/components/dashboard/WebServiceSubscriptionManager.jsx b/src/components/dashboard/WebServiceSubscriptionManager.jsx
index 19f1092..fb3ca8e 100644
--- a/src/components/dashboard/WebServiceSubscriptionManager.jsx
+++ b/src/components/dashboard/WebServiceSubscriptionManager.jsx
@@ -246,8 +246,9 @@ export default function WebServiceSubscriptionManager({ order, onClose, onRefres
// 🛡️ NOUVELLE LOGIQUE DE RÉSILIATION (US 2.1 / 2.2)
// ==========================================
const calculateRefundEligibility = () => {
- if (!order || !order.created_at) return false;
- const orderDate = new Date(order.created_at);
+ if (!order || !order.activated_at) return false;
+ const orderDate = new Date(order.activated_at);
+ console.log("Order Date:", orderDate);
const now = new Date();
const diffDays = Math.ceil(Math.abs(now - orderDate) / (1000 * 60 * 60 * 24));
return diffDays <= 14;
@@ -508,7 +509,7 @@ export default function WebServiceSubscriptionManager({ order, onClose, onRefres
Votre abonnement a été activé il y a moins de 14 jours.
Vous allez être intégralement remboursé.
- Vos services HestiaCP et bases de données seront supprimés immédiatement.
+ Vos services seront supprimés immédiatement.
) : (
diff --git a/src/layouts/AppLayout.jsx b/src/layouts/AppLayout.jsx
index 158dc86..d44ecea 100644
--- a/src/layouts/AppLayout.jsx
+++ b/src/layouts/AppLayout.jsx
@@ -74,10 +74,7 @@ export default function AppLayout() {
-
Profil & Sécurité
WIP
diff --git a/src/pages/app/BillingHistory.jsx b/src/pages/app/BillingHistory.jsx
new file mode 100644
index 0000000..3038973
--- /dev/null
+++ b/src/pages/app/BillingHistory.jsx
@@ -0,0 +1,336 @@
+import { useState, useEffect } from 'react';
+import { FileText, Download, CreditCard, Clock, CheckCircle2, XCircle, AlertCircle, Loader, X, Receipt, Trash2, ArrowRight, RefreshCcw } from 'lucide-react';
+import NotificationModal from '../../components/ui/NotificationModal'; // Vérifie ton chemin
+import { getClient, getInvoiceList } from '../../services/billing_api'; // Vérifie ton chemin
+
+export default function BillingHistory() {
+ const [invoices, setInvoices] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [customAlert, setCustomAlert] = useState(null);
+
+ // NOUVEL ÉTAT : La facture actuellement ouverte dans la modale
+ const [selectedInvoice, setSelectedInvoice] = useState(null);
+
+ const triggerAlert = (title, message, type) => {
+ setCustomAlert({ title, message, type });
+ };
+
+ useEffect(() => {
+ const fetchBillingData = async () => {
+ setIsLoading(true);
+ setError(null);
+ try {
+ const profileData = await getClient();
+ if (!profileData || !profileData.id) {
+ throw new Error("Impossible de vérifier votre identité ou session expirée.");
+ }
+ const invoicesData = await getInvoiceList(profileData.id);
+ setInvoices(invoicesData.list || []);
+ } catch (err) {
+ setError(err.message || "Problème de connexion avec le serveur.");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ fetchBillingData();
+ }, []);
+
+ const getStatusBadge = (status, total = 0) => {
+ const isNegative = parseFloat(total) < 0;
+
+ switch (status?.toLowerCase()) {
+ case 'paid':
+ if (isNegative) {
+ return { label: 'REMBOURSÉE', css: 'bg-blue-500/10 text-blue-400 border-blue-500/20', icon:
};
+ }
+ return { label: 'PAYÉE', css: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20', icon:
};
+ case 'refunded':
+ // NOUVEAU STATUT : REMBOURSÉE
+ case 'unpaid':
+ // SI IMPAYÉE MAIS NÉGATIVE = EN COURS DE TRAITEMENT
+ if (isNegative) {
+ return { label: 'REMBOURSEMENT EN COURS', css: 'bg-amber-500/10 text-amber-400 border-amber-500/20', icon:
};
+ }
+ return { label: 'EN ATTENTE', css: 'bg-amber-500/10 text-amber-400 border-amber-500/20', icon:
};
+ case 'canceled':
+ return { label: 'ANNULÉE', css: 'bg-gray-500/10 text-gray-400 border-gray-500/20', icon:
};
+ default:
+ return { label: status?.toUpperCase() || 'INCONNU', css: 'bg-blue-500/10 text-blue-400 border-blue-500/20', icon:
};
+ }
+ };
+
+ const formatDate = (dateString) => {
+ if (!dateString) return '--/--/----';
+ return new Date(dateString).toLocaleDateString('fr-FR', { year: 'numeric', month: 'long', day: 'numeric' });
+ };
+
+ // Fonctions vides pour la future logique
+ const handlePayInvoice = (invoiceId) => {
+ triggerAlert("Paiement initié", `Logique de paiement Stripe/PayPal à venir pour la facture #${invoiceId}.`, "info");
+ };
+
+ const handleCancelInvoice = (invoiceId) => {
+ triggerAlert("Annulation", `Logique d'annulation Ă venir pour la facture #${invoiceId}.`, "info");
+ };
+
+ return (
+
+
+
+
+ {isLoading && (
+
+
+ Synchronisation avec le registre comptable...
+
+ )}
+
+ {error && (
+
+ )}
+
+ {!isLoading && !error && (
+
+ {invoices.length === 0 ? (
+
+
+
Aucune facture
+
Vos transactions apparaîtront ici dès qu'elles seront générées.
+
+ ) : (
+
+
+
+
+ | Référence |
+ Date d'émission |
+ Statut |
+ Montant |
+ Actions |
+
+
+
+ {invoices.map((invoice) => {
+ const badge = getStatusBadge(invoice.status, invoice.total);
+ return (
+
+ |
+ {invoice.invoice_number}
+ |
+
+ {formatDate(invoice.created_at)}
+ |
+
+
+ {badge.icon}
+ {badge.label}
+
+ |
+
+ {parseFloat(invoice.total).toFixed(2)} {invoice.currency || '€'}
+ |
+
+
+ {invoice.status?.toLowerCase() === 'unpaid' && parseFloat(invoice.total) >= 0 ? (
+ // 🎯 Facture positive et impayée : BOUTON PAYER
+
+ ) : (
+ // 🎯 Facture payée, annulée, ou NÉGATIVE (Remboursement) : BOUTON DÉTAILS UNIQUEMENT
+
+ )}
+
+ |
+
+ );
+ })}
+
+
+
+ )}
+
+ )}
+
+ {/* ========================================== */}
+ {/* LA MODALE DE DÉTAILS DE FACTURE */}
+ {selectedInvoice && (
+
+
+
+ {/* HEADER MODALE */}
+
+
+
+
+
+
+
+ FACTURE {selectedInvoice.invoice_number}
+
+
+ Émise le {formatDate(selectedInvoice.created_at)}
+
+
+
+
+
+
+ {/* CORPS DE LA MODALE */}
+
+
+ {/* Statut Badge */}
+
+ Statut du document
+
+ {getStatusBadge(selectedInvoice.status).icon}
+ {getStatusBadge(selectedInvoice.status).label}
+
+
+
+ {/* Lignes de facture (Filtrées : uniquement les prix > 0) */}
+
+
Détail des prestations
+
+ {(() => {
+ // FILTRE : On ne garde que les items dont le prix est strictement supérieur à 0
+ const validItems = selectedInvoice.lines
+
+ if (validItems.length > 0) {
+ return validItems.map((item, idx) => (
+
+
+
{item.title}
+
Quantité : {item.quantity}
+
+
+ {(parseFloat(item.price) * parseInt(item.quantity)).toFixed(2)} {selectedInvoice.currency || '€'}
+
+
+ ));
+ } else {
+ // Fallback si la facture n'a que des items à 0€ ou aucun détail
+ return (
+
+
+
Services d'infrastructure technique
+
Détail inclus ou facturation sans frais complémentaires.
+
+
+ {parseFloat(selectedInvoice.total).toFixed(2)} {selectedInvoice.currency || '€'}
+
+
+ );
+ }
+ })()}
+
+
+
+ {/* CALCULS (EXTRACTION TVA DEPUIS LE TTC) */}
+ {(() => {
+ // Le total récupéré de la BDD est considéré comme le PRIX FINAL (TTC)
+ const totalTTC = parseFloat(selectedInvoice.total) || 0;
+ const vatRate = 0.21; // TVA Belge Ă 21%
+
+ // Extraction du Hors Taxe : TTC / 1.21
+ const subtotalHT = totalTTC / (1 + vatRate);
+ // Le montant exact de la taxe
+ const vatAmount = totalTTC - subtotalHT;
+
+ return (
+
+
+ Sous-total (HT)
+ {subtotalHT.toFixed(2)} {selectedInvoice.currency || '€'}
+
+
+ TVA (21%)
+ {vatAmount.toFixed(2)} {selectedInvoice.currency || '€'}
+
+
+ Total TTC
+ {totalTTC.toFixed(2)} {selectedInvoice.currency || '€'}
+
+
+ );
+ })()}
+
+
+
+ {/* PIED DE LA MODALE (Actions) */}
+
+
+ {selectedInvoice.status?.toLowerCase() === 'unpaid' && parseFloat(selectedInvoice.total) >= 0 ? (
+ <>
+ {/* Cas standard : Facture positive en attente de paiement */}
+
+
+
+ >
+ ) : (
+ <>
+ {/* Cas alternatif : Facture payée, annulée, ou NÉGATIVE (Remboursement) */}
+
+
+ {parseFloat(selectedInvoice.total) < 0 && selectedInvoice.status?.toLowerCase() === 'unpaid' && (
+
+ ⚡ Flux financier en cours de traitement
+
+ )}
+
+ {selectedInvoice.status?.toLowerCase() === 'refunded' && (
+
+ ✓ Fonds reversés au client
+
+ )}
+
+ {selectedInvoice.status?.toLowerCase() === 'paid' && (
+
+ Télécharger le reçu
+
+ )}
+ >
+ )}
+
+
+
+ )}
+
+
setCustomAlert(null)} />
+
+ );
+}
\ No newline at end of file
diff --git a/src/pages/app/Dashboard.jsx b/src/pages/app/Dashboard.jsx
index 85f9dd3..4ca61aa 100644
--- a/src/pages/app/Dashboard.jsx
+++ b/src/pages/app/Dashboard.jsx
@@ -70,6 +70,7 @@ export default function Dashboard() {
// 🌟 Plus besoin de charger les IPs, on ouvre juste la modale comptable !
const handleManageSubscription = (order) => {
+ console.log("Gestion de l'abonnement pour la commande :", order);
// 1. Détection du type de service
const titleLower = (order.title || '').toLowerCase();
const isVPS = titleLower.includes('vps') || titleLower.includes('compute');
diff --git a/src/services/billing_api.js b/src/services/billing_api.js
index 19a67fe..ffc5b14 100644
--- a/src/services/billing_api.js
+++ b/src/services/billing_api.js
@@ -305,12 +305,12 @@ export const updateHostingPlan = (order_id, new_plan_id) =>
// Suppression
// Modification
// Lister
-export const getInvoiceList = () =>
- apiCall(`${BASE_URL}/api/client/invoice/get_list`);
+export const getInvoiceList = (client_id) =>
+ apiCall(`${BASE_URL}/api/client/invoice/get_list`, { client_id: client_id });
// Lire
-export const getInvoiceDetails = (InvoiceHash) =>
- apiCall(`${BASE_URL}/api/client/invoice/get`);
+export const getInvoiceDetails = (invoiceHash) =>
+ apiCall(`${BASE_URL}/api/client/invoice/get`, { hash: invoiceHash });
// ==========================================