import { useState, useMemo } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useStore } from "@/contexts/StoreContext"; import { useToast } from "@/hooks/use-toast"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Loader2, Calendar, DollarSign, Building, CreditCard, AlertCircle, FileSpreadsheet, Download } from "lucide-react"; import { format, parseISO, startOfMonth, endOfMonth, eachMonthOfInterval, isWithinInterval } from "date-fns"; import { fr } from "date-fns/locale"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; interface PaymentSchedule { id: string | number; invoiceReference: string; dueDate: string; amount: number; amountTTC: number; supplierName: string; paymentMethod: string | null; groupId: number; groupName: string; } interface PaymentScheduleResponse { schedules: PaymentSchedule[]; message?: string; } export default function PaymentSchedulePage() { const { selectedStoreId } = useStore(); const { toast } = useToast(); const queryClient = useQueryClient(); const [selectedMonth, setSelectedMonth] = useState(() => { const now = new Date(); return format(now, 'yyyy-MM'); }); // États pour la modale d'export const [showExportModal, setShowExportModal] = useState(false); const [selectedPaymentMethods, setSelectedPaymentMethods] = useState([]); const [includeHT, setIncludeHT] = useState(true); const [includeTTC, setIncludeTTC] = useState(true); const [isExporting, setIsExporting] = useState(false); // Récupérer les échéances const { data, isLoading, error } = useQuery({ queryKey: ['/api/payment-schedule', selectedStoreId], queryFn: async () => { const response = await fetch(`/api/payment-schedule?groupId=${selectedStoreId}`, { credentials: 'include' }); if (!response.ok) { throw new Error(`Erreur ${response.status}: ${response.statusText}`); } const result = await response.json(); // Ce calcul peut compléter en base les échéances et montants manquants des // livraisons : les listes de livraisons en cache sont marquées périmées // (rechargées à leur prochain affichage, pas immédiatement) queryClient.invalidateQueries({ queryKey: ['/api/deliveries'], refetchType: 'none' }); return result; }, enabled: !!selectedStoreId, }); // Calculer les mois disponibles (12 derniers mois + 12 prochains mois) const availableMonths = useMemo(() => { const now = new Date(); const startDate = new Date(now.getFullYear(), now.getMonth() - 12, 1); const endDate = new Date(now.getFullYear(), now.getMonth() + 12, 1); return eachMonthOfInterval({ start: startDate, end: endDate }) .map(date => ({ value: format(date, 'yyyy-MM'), label: format(date, 'MMMM yyyy', { locale: fr }) })); }, []); // Filtrer les échéances par mois sélectionné const filteredSchedules = useMemo(() => { if (!data?.schedules) return []; const monthStart = parseISO(`${selectedMonth}-01`); const monthEnd = endOfMonth(monthStart); return data.schedules .filter(schedule => { try { const dueDate = parseISO(schedule.dueDate); return isWithinInterval(dueDate, { start: monthStart, end: monthEnd }); } catch { return false; } }) .sort((a, b) => { const dateA = parseISO(a.dueDate); const dateB = parseISO(b.dueDate); return dateA.getTime() - dateB.getTime(); }); }, [data?.schedules, selectedMonth]); // Calculer le total du mois (HT) const monthTotal = useMemo(() => { return filteredSchedules.reduce((sum, schedule) => sum + schedule.amount, 0); }, [filteredSchedules]); // Calculer le total du mois (TTC) const monthTotalTTC = useMemo(() => { return filteredSchedules.reduce((sum, schedule) => sum + schedule.amountTTC, 0); }, [filteredSchedules]); // Grouper par mode de paiement (HT) const paymentMethodTotals = useMemo(() => { const totals: Record = {}; filteredSchedules.forEach(schedule => { const method = schedule.paymentMethod || 'Non défini'; totals[method] = (totals[method] || 0) + schedule.amount; }); return totals; }, [filteredSchedules]); // Grouper par mode de paiement (TTC) const paymentMethodTotalsTTC = useMemo(() => { const totals: Record = {}; filteredSchedules.forEach(schedule => { const method = schedule.paymentMethod || 'Non défini'; totals[method] = (totals[method] || 0) + schedule.amountTTC; }); return totals; }, [filteredSchedules]); // Récupérer tous les modes de paiement disponibles const availablePaymentMethods = useMemo(() => { const methods = new Set(); filteredSchedules.forEach(schedule => { methods.add(schedule.paymentMethod || 'Non défini'); }); return Array.from(methods).sort(); }, [filteredSchedules]); // Handler pour ouvrir la modale d'export const handleOpenExportModal = () => { // Pré-sélectionner tous les modes de paiement par défaut setSelectedPaymentMethods(availablePaymentMethods); setShowExportModal(true); }; // Handler pour basculer la sélection d'un mode de paiement const togglePaymentMethod = (method: string) => { setSelectedPaymentMethods(prev => prev.includes(method) ? prev.filter(m => m !== method) : [...prev, method] ); }; // Handler pour sélectionner/désélectionner tous les modes de paiement const toggleAllPaymentMethods = () => { if (selectedPaymentMethods.length === availablePaymentMethods.length) { setSelectedPaymentMethods([]); } else { setSelectedPaymentMethods(availablePaymentMethods); } }; // Handler pour exporter vers Excel const handleExport = async () => { if (selectedPaymentMethods.length === 0) { return; } setIsExporting(true); try { const response = await fetch('/api/payment-schedule/export', { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ groupId: selectedStoreId, month: selectedMonth, paymentMethods: selectedPaymentMethods, includeHT, includeTTC, }), }); if (!response.ok) { throw new Error('Erreur lors de l\'export'); } // Télécharger le fichier const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `echeancier_${selectedMonth}.csv`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); setShowExportModal(false); toast({ title: "Export réussi", description: "Le fichier CSV a été téléchargé avec succès. Ouvrez-le dans Excel.", }); } catch (error) { console.error('Erreur export:', error); toast({ title: "Erreur d'export", description: "Une erreur est survenue lors de l'export du fichier Excel.", variant: "destructive", }); } finally { setIsExporting(false); } }; if (!selectedStoreId) { return (
Sélectionner un magasin Veuillez sélectionner un magasin pour afficher l'échéancier
); } // En-tête (titre, export, choix du mois) : conservé pendant le chargement, // notamment au changement de magasin, au lieu d'un écran entièrement vide const header = (

Échéancier des Paiements

Gestion des échéances fournisseurs

); if (isLoading) { return (
{header}
); } if (error) { return (
Erreur Une erreur est survenue lors du chargement des échéances
); } if (data?.message) { return (
Configuration requise {data.message}
); } return (
{/* En-tête */} {header} {/* Statistiques du mois */}
Total du Mois
HT: {monthTotal.toFixed(2)} €
TTC: {monthTotalTTC.toFixed(2)} €

{filteredSchedules.length} échéance{filteredSchedules.length > 1 ? 's' : ''}

Modes de Paiement
{Object.entries(paymentMethodTotals).map(([method, totalHT]) => (
{method}
HT: {totalHT.toFixed(2)} €
TTC: {paymentMethodTotalsTTC[method]?.toFixed(2) || '0.00'} €
))}
Période
{format(parseISO(`${selectedMonth}-01`), 'MMMM', { locale: fr })}

{format(parseISO(`${selectedMonth}-01`), 'yyyy', { locale: fr })}

{/* Tableau des échéances */} Échéances du Mois Liste détaillée des paiements à effectuer pour le mois sélectionné {filteredSchedules.length === 0 ? (
Aucune échéance pour ce mois
) : (
Date Fournisseur Facture Mode de paiement Montant HT Montant TTC {filteredSchedules.map((schedule, index) => ( {format(parseISO(schedule.dueDate), 'dd/MM/yyyy', { locale: fr })} {schedule.supplierName} {schedule.invoiceReference} {schedule.paymentMethod || 'Non défini'} {schedule.amount.toFixed(2)} € {schedule.amountTTC.toFixed(2)} € ))}
)}
{/* Modale d'export CSV */} Exporter vers CSV Sélectionnez les modes de paiement et les colonnes à inclure dans l'export (ouvrez le fichier dans Excel)
{/* Sélection des modes de paiement */}
{availablePaymentMethods.map((method) => (
togglePaymentMethod(method)} data-testid={`checkbox-payment-method-${method}`} />
))}
{/* Options de colonnes */}
setIncludeHT(!!checked)} data-testid="checkbox-include-ht" />
setIncludeTTC(!!checked)} data-testid="checkbox-include-ttc" />
); }