import { useState, useMemo } from "react"; import DashboardLayout from "@/components/DashboardLayout"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { trpc } from "@/lib/trpc"; import { CheckCircle2, XCircle, Copy, AlertTriangle, FileText, CalendarDays, TrendingUp, Download, Eye, } from "lucide-react"; type ImportLog = { id: number; fileName: string; totalInvoicesDetected: number; invoicesImported: number; duplicatesIgnored: number; errors: number; duplicateDetails: string | null; errorDetails: string | null; warningMessage?: string | null; importedAt: Date | string; }; type DuplicateDetail = { supplierName?: string; invoiceNumber?: string; invoiceDate?: string; }; type ErrorDetail = { message?: string; field?: string; value?: string; }; function parseJson(str: string | null | undefined): T[] { if (!str) return []; try { const parsed = JSON.parse(str); return Array.isArray(parsed) ? parsed : []; } catch { return []; } } function StatusBadge({ imported, duplicates, errors }: { imported: number; duplicates: number; errors: number }) { if (errors > 0) return Erreurs ({errors}); if (duplicates > 0) return Doublons ({duplicates}); if (imported > 0) return Importée; return Ignorée; } export default function ImportReport() { const { data: logs, isLoading } = trpc.importLogs.getByUser.useQuery(); const [yearFilter, setYearFilter] = useState(String(new Date().getFullYear())); const [monthFilter, setMonthFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all"); const [selectedLog, setSelectedLog] = useState(null); // Années disponibles const availableYears = useMemo(() => { if (!logs) return [String(new Date().getFullYear())]; const years = Array.from(new Set(logs.map(l => new Date(l.importedAt).getFullYear()))); return years.sort((a, b) => b - a).map(String); }, [logs]); // Filtrage const filteredLogs = useMemo(() => { if (!logs) return []; return logs.filter((log) => { const d = new Date(log.importedAt); if (yearFilter !== "all" && d.getFullYear() !== parseInt(yearFilter)) return false; if (monthFilter !== "all" && d.getMonth() + 1 !== parseInt(monthFilter)) return false; if (statusFilter === "imported" && log.invoicesImported === 0) return false; if (statusFilter === "not_imported" && (log.duplicatesIgnored === 0 && log.errors === 0)) return false; if (statusFilter === "duplicates" && log.duplicatesIgnored === 0) return false; if (statusFilter === "errors" && log.errors === 0) return false; return true; }); }, [logs, yearFilter, monthFilter, statusFilter]); // Compteurs globaux (sur les logs filtrés par période uniquement) const periodLogs = useMemo(() => { if (!logs) return []; return logs.filter((log) => { const d = new Date(log.importedAt); if (yearFilter !== "all" && d.getFullYear() !== parseInt(yearFilter)) return false; if (monthFilter !== "all" && d.getMonth() + 1 !== parseInt(monthFilter)) return false; return true; }); }, [logs, yearFilter, monthFilter]); const totals = useMemo(() => ({ detected: periodLogs.reduce((s, l) => s + l.totalInvoicesDetected, 0), imported: periodLogs.reduce((s, l) => s + l.invoicesImported, 0), duplicates: periodLogs.reduce((s, l) => s + l.duplicatesIgnored, 0), errors: periodLogs.reduce((s, l) => s + l.errors, 0), files: periodLogs.length, }), [periodLogs]); const statusCounts = useMemo(() => ({ all: periodLogs.length, imported: periodLogs.filter(l => l.invoicesImported > 0 && l.duplicatesIgnored === 0 && l.errors === 0).length, not_imported: periodLogs.filter(l => l.invoicesImported === 0).length, duplicates: periodLogs.filter(l => l.duplicatesIgnored > 0).length, errors: periodLogs.filter(l => l.errors > 0).length, }), [periodLogs]); const importRate = totals.detected > 0 ? Math.round((totals.imported / totals.detected) * 100) : 0; const months = [ { value: "1", label: "Janvier" }, { value: "2", label: "Février" }, { value: "3", label: "Mars" }, { value: "4", label: "Avril" }, { value: "5", label: "Mai" }, { value: "6", label: "Juin" }, { value: "7", label: "Juillet" }, { value: "8", label: "Août" }, { value: "9", label: "Septembre" }, { value: "10", label: "Octobre" }, { value: "11", label: "Novembre" }, { value: "12", label: "Décembre" }, ]; // Export CSV const exportCsv = () => { const headers = ["Fichier", "Date import", "Détectées", "Importées", "Doublons", "Erreurs", "Statut"]; const rows = filteredLogs.map(l => [ l.fileName, new Date(l.importedAt).toLocaleDateString("fr-FR"), l.totalInvoicesDetected, l.invoicesImported, l.duplicatesIgnored, l.errors, l.errors > 0 ? "Erreur" : l.duplicatesIgnored > 0 ? "Doublon" : l.invoicesImported > 0 ? "Importée" : "Ignorée", ]); const csv = [headers, ...rows].map(r => r.join(";")).join("\n"); const blob = new Blob(["\uFEFF" + csv], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `rapport_imports_${new Date().toLocaleDateString("fr-CA")}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); }; return (
{/* En-tête */}

Rapport des imports

Analyse détaillée des factures importées et non importées

{/* Compteurs synthèse */}
Fichiers

{totals.files}

imports traités

Détectées

{totals.detected}

factures trouvées

Importées

{totals.imported}

taux : {importRate}%

Doublons

{totals.duplicates}

ignorés

Erreurs

{totals.errors}

échecs extraction

{/* Barre de progression globale */} {totals.detected > 0 && (
Taux d'import global {importRate}% ({totals.imported}/{totals.detected})
Importées Doublons Erreurs
)} {/* Filtres */}
{/* Filtres statut */}
{[ { key: "all", label: `Tous (${statusCounts.all})`, color: "default" }, { key: "imported", label: `Importées (${statusCounts.imported})`, color: "green" }, { key: "not_imported", label: `Non importées (${statusCounts.not_imported})`, color: "gray" }, { key: "duplicates", label: `Doublons (${statusCounts.duplicates})`, color: "amber" }, { key: "errors", label: `Erreurs (${statusCounts.errors})`, color: "red" }, ].map(({ key, label }) => ( ))}
{/* Séparateur */}
{/* Année */} {/* Mois */} {(yearFilter !== String(new Date().getFullYear()) || monthFilter !== "all" || statusFilter !== "all") && ( )}
{/* Tableau détaillé */} Détail par fichier ({filteredLogs.length} entrée{filteredLogs.length > 1 ? "s" : ""}) {isLoading ? (
Chargement...
) : filteredLogs.length === 0 ? (

Aucun import pour cette période

) : ( Fichier Date import Détectées Importées Doublons Erreurs Statut Détails {filteredLogs.map((log) => ( {log.fileName} {new Date(log.importedAt).toLocaleDateString("fr-FR")}
{new Date(log.importedAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })}
{log.totalInvoicesDetected} 0 ? "text-green-700" : "text-gray-400"}`}> {log.invoicesImported} 0 ? "text-amber-600" : "text-gray-400"}`}> {log.duplicatesIgnored} 0 ? "text-red-600" : "text-gray-400"}`}> {log.errors} {(log.duplicatesIgnored > 0 || log.errors > 0 || (log as any).warningMessage) && ( )}
))}
)}
{/* Dialog détails */} {selectedLog && ( setSelectedLog(null)}> Détails de l'import

Fichier

{selectedLog.fileName}

Importé le {new Date(selectedLog.importedAt).toLocaleDateString("fr-FR")} à{" "} {new Date(selectedLog.importedAt).toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" })}

{/* Résumé chiffres */}
{[ { label: "Détectées", value: selectedLog.totalInvoicesDetected, color: "purple" }, { label: "Importées", value: selectedLog.invoicesImported, color: "green" }, { label: "Doublons", value: selectedLog.duplicatesIgnored, color: "amber" }, { label: "Erreurs", value: selectedLog.errors, color: "red" }, ].map(({ label, value, color }) => (

{value}

{label}

))}
{/* Doublons */} {selectedLog.duplicatesIgnored > 0 && (

Factures ignorées — Doublons ({selectedLog.duplicatesIgnored})

{parseJson(selectedLog.duplicateDetails).map((dup, i) => (

Fournisseur

{dup.supplierName || "—"}

N° Facture

{dup.invoiceNumber || "—"}

Date

{dup.invoiceDate ? new Date(dup.invoiceDate).toLocaleDateString("fr-FR") : "—"}

⚠ Cette facture existe déjà en base — elle a été ignorée pour éviter les doublons.

))} {parseJson(selectedLog.duplicateDetails).length === 0 && (

Détails non disponibles

)}
)} {/* Avertissement quota IA */} {selectedLog.warningMessage && (

Avertissement

{selectedLog.warningMessage}

)} {/* Erreurs */} {selectedLog.errors > 0 && (

Erreurs d'extraction ({selectedLog.errors})

{parseJson(selectedLog.errorDetails).map((err, i) => (

{err.message || "Erreur inconnue"}

{err.field && (

Champ : {err.field} = {err.value || "vide"}

)}
))} {parseJson(selectedLog.errorDetails).length === 0 && (

Détails non disponibles

)}
)}
)} ); }