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
+ setSelectedInvoice(invoice)}
+ className="flex items-center gap-1.5 px-4 py-2 bg-amber-500 hover:bg-amber-400 text-black text-[10px] font-black uppercase tracking-widest rounded transition-all shadow-[0_0_15px_rgba(245,158,11,0.2)]"
+ >
+ PAYER
+
+ ) : (
+ // 🎯 Facture payée, annulée, ou NÉGATIVE (Remboursement) : BOUTON DÉTAILS UNIQUEMENT
+ setSelectedInvoice(invoice)}
+ className="flex items-center gap-1.5 px-4 py-2 bg-gray-900 hover:bg-gray-800 text-gray-300 hover:text-cyan-400 border border-gray-700 hover:border-cyan-900 text-[10px] font-black uppercase tracking-widest rounded transition-all"
+ >
+ DÉTAILS
+
+ )}
+
+
+
+ );
+ })}
+
+
+
+ )}
+
+ )}
+
+ {/* ========================================== */}
+ {/* LA MODALE DE DÉTAILS DE FACTURE */}
+ {selectedInvoice && (
+
+
+
+ {/* HEADER MODALE */}
+
+
+
+
+
+
+
+ FACTURE {selectedInvoice.invoice_number}
+
+
+ Émise le {formatDate(selectedInvoice.created_at)}
+
+
+
+
setSelectedInvoice(null)} className="p-2 text-gray-500 hover:text-white bg-gray-900 hover:bg-gray-800 rounded-lg transition-colors">
+
+
+
+
+ {/* 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 */}
+
handleCancelInvoice(selectedInvoice.id)}
+ className="w-full sm:w-auto px-5 py-3 text-red-400 hover:text-white bg-red-500/10 hover:bg-red-500 border border-red-500/20 hover:border-red-500 text-xs font-bold uppercase tracking-wider rounded-xl transition-all flex items-center justify-center gap-2"
+ >
+ Annuler la facture
+
+
+
handlePayInvoice(selectedInvoice.id)}
+ className="w-full sm:w-auto px-8 py-3 bg-amber-500 hover:bg-amber-400 text-black text-xs font-black uppercase tracking-widest rounded-xl transition-all shadow-[0_0_20px_rgba(245,158,11,0.2)] flex items-center justify-center gap-2"
+ >
+ Procéder au paiement
+
+ >
+ ) : (
+ <>
+ {/* Cas alternatif : Facture payée, annulée, ou NÉGATIVE (Remboursement) */}
+
setSelectedInvoice(null)} className="w-full sm:w-auto px-5 py-3 text-gray-400 hover:text-white bg-gray-900 hover:bg-gray-800 border border-gray-800 text-xs font-bold uppercase tracking-wider rounded-xl transition-all">
+ Fermer
+
+
+ {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