mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r3.4): corrections de recette (8 écarts) + recherche globale ⌘K
Arbitrage du référent sur la revue pixel : tout corriger, activer la recherche. - Stock : filtre fournisseur, sous-seuil en tête, « Entrée de stock » depuis la liste ; fiche pièce : fournisseur → lien Tiers. - Statistiques : période 3/6/12 mois (paramètre months au contrat). - Tiers : rattachements syndic→site (migration r3_recette_fixes, Location.partnerId gardé CLIENT), éditable sur la fiche site, seedé. - Bibliothèque : filtre « Rattaché à » + glisser-déposer (modale préremplie, rattachement toujours requis). - Recherche globale : GET /search (73 opérations) — familles OT/ ascenseurs/sites filtrées par la matrice, « voir autre » respecté ; topbar ⌘K, debounce, résultats groupés, navigation clavier. 74 tests API (8 nouveaux sur le scoping de la recherche), 14/14 Playwright dont un parcours « recette corrigée », 18/18 contrôles en navigateur réel, zéro erreur console. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState, type DragEvent } from 'react';
|
||||
import {
|
||||
DOCUMENT_KIND_LABELS,
|
||||
DOCUMENT_KINDS,
|
||||
@@ -17,13 +17,29 @@ import { Modale } from '@/components/ui/modale';
|
||||
export default function PageBibliotheque() {
|
||||
const { can } = usePermissions();
|
||||
const [kind, setKind] = useState<DocumentKind | ''>('');
|
||||
const { data: documents } = useDocuments(kind ? { kind } : {});
|
||||
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<File | null>(null);
|
||||
const [survol, setSurvol] = useState(false);
|
||||
|
||||
const totalOctets = (documents ?? []).reduce((s, d) => s + d.size, 0);
|
||||
const peutEditer = can('WORK_ORDERS', 'edit') || 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 (
|
||||
<>
|
||||
<div className="entete-page">
|
||||
@@ -48,7 +64,35 @@ export default function PageBibliotheque() {
|
||||
<option key={k} value={k}>{DOCUMENT_KIND_LABELS[k]}</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
aria-label="Filtrer par appareil"
|
||||
value={assetId}
|
||||
onChange={(e) => setAssetId(e.target.value)}
|
||||
>
|
||||
<option value="">Rattaché à : tout le parc</option>
|
||||
{(options ?? []).map((a) => (
|
||||
<option key={a.id} value={a.id}>
|
||||
{a.reference} — {a.siteName}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{peutEditer ? (
|
||||
<button
|
||||
type="button"
|
||||
className="zone-depot"
|
||||
data-survol={survol || undefined}
|
||||
onClick={() => setModale(true)}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
setSurvol(true);
|
||||
}}
|
||||
onDragLeave={() => setSurvol(false)}
|
||||
onDrop={surDepot}
|
||||
>
|
||||
📂 Glissez un fichier ici — PDF, JPG, PNG · 20 Mo max · rattachez-le à un appareil ou un OT
|
||||
</button>
|
||||
) : null}
|
||||
{documents?.length ? (
|
||||
<div className="docs">
|
||||
{documents.map((d) => (
|
||||
@@ -66,16 +110,27 @@ export default function PageBibliotheque() {
|
||||
Ces documents nourriront l'assistant RAG en R5 (réponses citant leurs sources) —
|
||||
le rattachement propre commence ici.
|
||||
</div>
|
||||
{peutEditer ? <ModaleTeleversement ouverte={modale} surFermeture={setModale} /> : null}
|
||||
{peutEditer ? (
|
||||
<ModaleTeleversement
|
||||
ouverte={modale}
|
||||
fichierInitial={fichierDepose}
|
||||
surFermeture={(o) => {
|
||||
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();
|
||||
@@ -85,6 +140,11 @@ function ModaleTeleversement({
|
||||
const [assetId, setAssetId] = useState('');
|
||||
const [fichier, setFichier] = useState<File | null>(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) {
|
||||
|
||||
Reference in New Issue
Block a user