Amélioration

This commit is contained in:
ocroguennec committed 2026-09-27 11:38:45 +02:00
1 parent add66a9310
commit e0145c8727
46 files changed
+305

No files matched your search

+54
View File
@@ -46,6 +46,10 @@ function IconChevronLeft() {
return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg>;
}
function IconWand() {
return <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72Z"/><path d="m14 7 3 3"/><path d="M5 6v4"/><path d="M19 14v4"/><path d="M10 2v2"/><path d="M7 8H3"/><path d="M21 16h-4"/><path d="M11 3H9"/></svg>;
}
/* ── Modale de création/édition d'une institution ────────────────
Formulaire simple à un seul écran (contrairement au RefModal des plateformes, à 5 étapes) :
une institution financière n'a qu'une poignée de champs, un wizard serait disproportionné. */
@@ -54,6 +58,9 @@ function InstitutionModal({ row, onClose, onSaved }) {
const [categories, setCategories] = useState([]);
const [err, setErr] = useState(null);
const [saving, setSaving] = useState(false);
const [aiAvailable, setAiAvailable] = useState(false);
const [aiLoading, setAiLoading] = useState(false);
const [aiMsg, setAiMsg] = useState(null); // { ok:boolean, text:string }
const [form, setForm] = useState({
nom: row?.nom || '',
@@ -67,8 +74,40 @@ function InstitutionModal({ row, onClose, onSaved }) {
api.get('/ref-categories-institutions').then(setCategories).catch(() => {});
}, []);
// Icône « baguette magique » (demande Olivier 27/09/26) : visible seulement si le
// connecteur IA est activé (Admin > Intelligence artificielle) — la disponibilité d'un
// fournisseur compatible (Anthropic natif) est vérifiée côté serveur lors de l'appel.
useEffect(() => {
api.get('/admin/ai/config').then(cfg => setAiAvailable(!!cfg?.actif)).catch(() => setAiAvailable(false));
}, []);
const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
const handleAiLookup = async () => {
if (!form.nom.trim()) { setAiMsg({ ok: false, text: 'Renseignez le nom avant de lancer la recherche.' }); return; }
setAiLoading(true); setAiMsg(null);
try {
const r = await api.post('/institutions-referentiel/ai-lookup', {
nom: form.nom.trim(),
url: form.url.trim() || undefined,
});
if (r.ok) {
setForm(f => ({
...f,
description: r.description || f.description,
domiciliation: r.domiciliation || f.domiciliation,
}));
setAiMsg({ ok: true, text: "Description et domiciliation renseignées automatiquement — vérifiez avant d'enregistrer." });
} else {
setAiMsg({ ok: false, text: r.error || 'Recherche IA indisponible.' });
}
} catch (ex) {
setAiMsg({ ok: false, text: ex.message });
} finally {
setAiLoading(false);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!form.nom.trim()) { setErr('Le nom est requis.'); return; }
@@ -113,6 +152,21 @@ function InstitutionModal({ row, onClose, onSaved }) {
<label>URL</label>
<input value={form.url} onChange={e => set('url', e.target.value)} placeholder="https://…" />
</div>
{aiAvailable && (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<button type="button" onClick={handleAiLookup} disabled={aiLoading || !form.nom.trim()}
style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, padding: '5px 10px',
border: '1px solid var(--border)', borderRadius: 6,
cursor: (aiLoading || !form.nom.trim()) ? 'not-allowed' : 'pointer',
background: 'var(--surface-2)', color: 'var(--text)', opacity: (aiLoading || !form.nom.trim()) ? 0.6 : 1 }}
title="Rechercher automatiquement sur le web (description, domiciliation) via l'Assistant IA">
<IconWand /> {aiLoading ? 'Recherche en cours…' : "Compléter avec l'IA"}
</button>
{aiMsg && (
<span style={{ fontSize: 12, color: aiMsg.ok ? '#16a34a' : '#dc2626' }}>{aiMsg.text}</span>
)}
</div>
)}
<div>
<label>Domiciliation</label>
<CountrySelect value={form.domiciliation} onChange={code => set('domiciliation', code)} showCode />