import { useEffect, useRef, useState, type DragEvent } from 'react'; import { DOCUMENT_KIND_LABELS, DOCUMENT_KINDS, DOCUMENT_MAX_BYTES, type DocumentKind, } from '@siop/shared'; import { useReindexAssistant } from '@/api/assistant'; import { useDeleteDocument, useDocuments, useUploadDocument } from '@/api/gestion'; import { useAssetOptions } from '@/api/referentiel'; import { usePermissions } from '@/auth/use-permissions'; import { tailleLisible, VignetteDoc } from '@/components/carte-documents'; import { Button } from '@/components/ui/button'; import { Modale } from '@/components/ui/modale'; /** Écran 7 des maquettes R3, devenu écran 5 de R5 : la bibliothèque EST le * corpus de l'assistant — statut d'indexation visible, interrupteur * d'exclusion réversible, réindexation par geste explicite (D3). */ export default function PageBibliotheque() { const { can } = usePermissions(); const reindex = useReindexAssistant(); const [kind, setKind] = useState(''); const [assetId, setAssetId] = useState(''); const { data: options } = useAssetOptions(); const { data: documents } = useDocuments({ ...(kind ? { kind } : {}), ...(assetId ? { assetId } : {}), }); const suppression = useDeleteDocument(); const [modale, setModale] = useState(false); const [fichierDepose, setFichierDepose] = useState(null); const [survol, setSurvol] = useState(false); const totalOctets = (documents ?? []).reduce((s, d) => s + d.size, 0); const indexes = (documents ?? []).filter((d) => d.indexedAt && d.inCorpus).length; const peutEditer = can('WORK_ORDERS', 'edit') || can('ASSETS', 'edit'); const administreCorpus = can('ASSETS', 'edit'); const surDepot = (e: DragEvent) => { e.preventDefault(); setSurvol(false); const fichier = e.dataTransfer.files?.[0]; if (!fichier) return; setFichierDepose(fichier); setModale(true); }; return ( <>

Bibliothèque — corpus de l'assistant

{documents?.length ?? 0} documents · {indexes} indexés · {tailleLisible(totalOctets)}
{administreCorpus ? ( ) : null} {peutEditer ? ( ) : null}
🛡 Loi 09-08 — anonymisation à l'ingestion : noms, téléphones et e-mails des personnes ne sont JAMAIS envoyés dans les index ni aux modèles.
{reindex.isSuccess ? (
Réindexation terminée : {reindex.data.documentsIndexed} document {reindex.data.documentsIndexed > 1 ? 's' : ''} indexé {reindex.data.documentsIndexed > 1 ? 's' : ''}, {reindex.data.documentsSkipped} ignoré {reindex.data.documentsSkipped > 1 ? 's' : ''} (exclus ou non indexables),{' '} {reindex.data.reportsIndexed} bilans, {reindex.data.chunks} extraits.
) : null} {reindex.isError ? (

{reindex.error.message}

) : null}
{peutEditer ? ( ) : null} {documents?.length ? (
{documents.map((d) => ( suppression.mutate(id) : undefined} /> ))}
) : (
Aucun document.
)}
Le corpus de l'assistant, c'est cette bibliothèque (PDF indexés) plus les bilans d'intervention codés — rien d'externe. Les réponses citent leurs sources.
{peutEditer ? ( { setModale(o); if (!o) setFichierDepose(null); }} /> ) : null} ); } function ModaleTeleversement({ ouverte, fichierInitial, surFermeture, }: { ouverte: boolean; fichierInitial?: File | null; surFermeture: (o: boolean) => void; }) { const { data: options } = useAssetOptions(); const upload = useUploadDocument(); const fichierRef = useRef(null); const [kind, setKind] = useState('NOTICE'); const [assetId, setAssetId] = useState(''); const [fichier, setFichier] = useState(null); // Fichier arrivé par glisser-déposer : la modale s'ouvre déjà remplie. useEffect(() => { if (fichierInitial) setFichier(fichierInitial); }, [fichierInitial]); const envoyer = () => { if (!fichier || !assetId) return; if (fichier.size > DOCUMENT_MAX_BYTES) { window.alert('Fichier trop lourd (20 Mo max).'); return; } upload.mutate( { file: fichier, kind, assetId }, { onSuccess: () => { setFichier(null); surFermeture(false); }, }, ); }; return (
setFichier(e.target.files?.[0] ?? null)} />

Les photos prises sur une intervention se téléversent plutôt depuis la fiche OT — elles restent rattachées à l'intervention.

{upload.isError ?

{upload.error.message}

: null}
); }