import { useState, useMemo } from "react"; import { trpc } from "@/lib/trpc"; import { FilterBar } from "@/components/FilterBar"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader } from "@/components/ui/card"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { LayoutGrid, List, ExternalLink, Calendar, MapPin, Tag, Layers, FileSearch, Loader2, ChevronLeft, ChevronRight, Eye, Globe, BookOpen, } from "lucide-react"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; type TypeVeille = "reglementaire" | "concurrentielle" | "technologique" | "generale"; interface VeilleItem { id: number; titre: string; typeVeille: string; categorie: string | null; niveau: string | null; territoire: string | null; resume: string | null; source: string | null; passage: string | null; lien: string | null; datePublication: Date | null; importedAt: Date; } const TYPE_LABELS: Record = { reglementaire: "Réglementaire", concurrentielle: "Concurrentielle", technologique: "Technologique", generale: "Générale", }; const TYPE_COLORS: Record = { reglementaire: "bg-blue-100 text-blue-800 border-blue-200", concurrentielle: "bg-purple-100 text-purple-800 border-purple-200", technologique: "bg-emerald-100 text-emerald-800 border-emerald-200", generale: "bg-amber-100 text-amber-800 border-amber-200", }; const TYPE_ACCENT: Record = { reglementaire: "border-l-blue-500", concurrentielle: "border-l-purple-500", technologique: "border-l-emerald-500", generale: "border-l-amber-500", }; const TYPE_HEADER_BG: Record = { reglementaire: "from-blue-50 to-blue-100/30 border-blue-200", concurrentielle: "from-purple-50 to-purple-100/30 border-purple-200", technologique: "from-emerald-50 to-emerald-100/30 border-emerald-200", generale: "from-amber-50 to-amber-100/30 border-amber-200", }; const PAGE_SIZE = 24; function formatDate(d: Date | null | undefined): string | null { if (!d) return null; try { return format(new Date(d), "d MMMM yyyy", { locale: fr }); } catch { return null; } } // ─── Boîte de dialogue Détail ───────────────────────────────────────────────── function VeilleDetailDialog({ item, open, onClose, }: { item: VeilleItem | null; open: boolean; onClose: () => void; }) { if (!item) return null; const typeKey = item.typeVeille as TypeVeille; const headerBg = TYPE_HEADER_BG[typeKey] || "from-muted to-muted/30 border-border"; return ( !v && onClose()}> {/* En-tête coloré */}
{TYPE_LABELS[typeKey] || item.typeVeille} {item.datePublication && ( {formatDate(item.datePublication)} )}
{item.titre}
{/* Corps */}
{/* Métadonnées en grille */}
{item.categorie && (

Catégorie

{item.categorie}

)} {item.niveau && (

Niveau

{item.niveau}

)} {item.territoire && (

Territoire

{item.territoire}

)} {item.source && (

Source

{item.source}

)} {item.passage && (

Passage en vigueur

{item.passage}

)}
{/* Résumé complet */} {item.resume && (

Résumé

{item.resume}

)} {/* Lien externe */} {item.lien && ( )}
); } // ─── Composant principal ────────────────────────────────────────────────────── export default function VeilleDashboard() { const [viewMode, setViewMode] = useState<"list" | "grid">("list"); const [activeTab, setActiveTab] = useState("all"); const [page, setPage] = useState(1); const [filterValues, setFilterValues] = useState>({}); const [selectedItem, setSelectedItem] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const filtersQuery = trpc.veille.filters.useQuery(); const queryInput = useMemo(() => ({ typeVeille: activeTab !== "all" ? activeTab : undefined, categorie: filterValues.categorie || undefined, niveau: filterValues.niveau || undefined, territoire: filterValues.territoire || undefined, search: filterValues.search || undefined, dateFrom: filterValues.dateFrom ? new Date(filterValues.dateFrom) : undefined, dateTo: filterValues.dateTo ? new Date(filterValues.dateTo) : undefined, page, pageSize: PAGE_SIZE, }), [activeTab, filterValues, page]); const itemsQuery = trpc.veille.list.useQuery(queryInput); const handleFilterChange = (key: string, value: string) => { setFilterValues((prev) => ({ ...prev, [key]: value })); setPage(1); }; const handleReset = () => { setFilterValues({}); setPage(1); }; const openDetail = (item: VeilleItem) => { setSelectedItem(item); setDialogOpen(true); }; const items = (itemsQuery.data?.items ?? []) as VeilleItem[]; const total = itemsQuery.data?.total ?? 0; const totalPages = Math.ceil(total / PAGE_SIZE); const filterOptions = [ { key: "categorie", label: "Catégorie", options: filtersQuery.data?.categories ?? [] }, { key: "niveau", label: "Niveau", options: filtersQuery.data?.niveaux ?? [] }, { key: "territoire", label: "Territoire", options: filtersQuery.data?.territoires ?? [] }, { key: "dateFrom", label: "Date depuis", type: "date" as const }, { key: "dateTo", label: "Date jusqu'à", type: "date" as const }, ]; return (
{/* En-tête */}

Veille Stratégique

Suivi réglementaire, concurrentiel, technologique et général

{/* Onglets */} { setActiveTab(v as TypeVeille | "all"); setPage(1); }}> Tous {(Object.keys(TYPE_LABELS) as TypeVeille[]).map((t) => ( {TYPE_LABELS[t]} ))} {/* Filtres */} {/* Contenu */} {itemsQuery.isLoading ? (
) : items.length === 0 ? (

Aucun résultat trouvé

Modifiez vos filtres ou importez des données

) : viewMode === "list" ? ( ) : ( )} {/* Pagination */} {totalPages > 1 && (
Page {page} / {totalPages}
)} {/* Boîte de dialogue détail */} setDialogOpen(false)} />
); } // ─── Vue Liste ──────────────────────────────────────────────────────────────── function VeilleListView({ items, onDetail }: { items: VeilleItem[]; onDetail: (item: VeilleItem) => void }) { return (
{items.map((item, idx) => ( ))}
# Titre Type Catégorie Niveau Territoire Date Actions
{idx + 1}

{item.titre}

{item.resume &&

{item.resume}

}
{TYPE_LABELS[item.typeVeille as TypeVeille] || item.typeVeille} {item.categorie || "—"} {item.niveau || "—"} {item.territoire || "—"} {formatDate(item.datePublication) || "—"}
{/* Bouton Détail — bleu */} {/* Bouton Lien externe — vert */} {item.lien && ( )}
); } // ─── Vue Vignettes ──────────────────────────────────────────────────────────── function VeilleGridView({ items, onDetail }: { items: VeilleItem[]; onDetail: (item: VeilleItem) => void }) { return (
{items.map((item) => (
{TYPE_LABELS[item.typeVeille as TypeVeille] || item.typeVeille}
{/* Bouton Détail */} {item.lien && ( )}

{item.titre}

{item.resume &&

{item.resume}

}
{item.categorie && {item.categorie}} {item.territoire && {item.territoire}} {item.niveau && {item.niveau}}
{item.datePublication && (
{formatDate(item.datePublication)}
)}
))}
); }