feat: instrument analysis

This commit is contained in:
OpenSquared
2026-06-28 13:35:29 +02:00
parent 6d31ec973a
commit b5d4eb71e7
2 changed files with 29 additions and 24 deletions

View File

@@ -1,4 +1,5 @@
import { useState, useEffect, useRef, useCallback } from 'react' import { useState, useEffect, useRef, useCallback } from 'react'
import { useSearchParams } from 'react-router-dom'
import { import {
FlaskConical, Library, Zap, BarChart3, RefreshCw, Edit3, FlaskConical, Library, Zap, BarChart3, RefreshCw, Edit3,
Sliders, Brain, Search, CheckCircle, XCircle, Minus, AlertCircle, Sliders, Brain, Search, CheckCircle, XCircle, Minus, AlertCircle,
@@ -372,7 +373,7 @@ function GraphLegend() {
// ── Tab: Bibliothèque ───────────────────────────────────────────────────────── // ── Tab: Bibliothèque ─────────────────────────────────────────────────────────
function TabLibrary() { function TabLibrary({ initialTemplateId }: { initialTemplateId?: number | null }) {
const [templates, setTemplates] = useState<Template[]>([]) const [templates, setTemplates] = useState<Template[]>([])
const [selected, setSelected] = useState<Template | null>(null) const [selected, setSelected] = useState<Template | null>(null)
const [catFilter, setCatFilter] = useState('') const [catFilter, setCatFilter] = useState('')
@@ -396,6 +397,13 @@ function TabLibrary() {
.finally(() => setLoading(false)) .finally(() => setLoading(false))
}, [catFilter]) }, [catFilter])
// Auto-select template when arriving from a deep-link (?template=ID)
useEffect(() => {
if (!initialTemplateId || !templates.length || selected) return
const t = templates.find(t => t.id === initialTemplateId)
if (t) selectTemplate(t)
}, [templates, initialTemplateId])
function selectTemplate(t: Template) { function selectTemplate(t: Template) {
setSelected(t) setSelected(t)
setEditCoefs(Object.fromEntries( setEditCoefs(Object.fromEntries(
@@ -1676,7 +1684,9 @@ const TABS = [
] ]
export default function CausalLab() { export default function CausalLab() {
const [tab, setTab] = useState('library') const [searchParams] = useSearchParams()
const initialTemplateId = searchParams.get('template') ? parseInt(searchParams.get('template')!) : null
const [tab, setTab] = useState('library')
return ( return (
<div className="p-6 min-h-screen"> <div className="p-6 min-h-screen">
@@ -1702,7 +1712,7 @@ export default function CausalLab() {
</div> </div>
<div className="bg-dark-800 rounded-xl p-5 border border-slate-700/30"> <div className="bg-dark-800 rounded-xl p-5 border border-slate-700/30">
{tab === 'library' && <TabLibrary />} {tab === 'library' && <TabLibrary initialTemplateId={initialTemplateId} />}
{tab === 'editor' && <TabEditor />} {tab === 'editor' && <TabEditor />}
{tab === 'analyze' && <TabAnalyze />} {tab === 'analyze' && <TabAnalyze />}
{tab === 'calib' && <TabCalibration />} {tab === 'calib' && <TabCalibration />}

View File

@@ -768,6 +768,7 @@ function EventGraphCards({
selectedDate: string | null selectedDate: string | null
instrumentCategory: string instrumentCategory: string
}) { }) {
const navigate = useNavigate()
const causalInsts = CAT_TO_CAUSAL_INST[instrumentCategory] ?? [] const causalInsts = CAT_TO_CAUSAL_INST[instrumentCategory] ?? []
const allCats = new Set( const allCats = new Set(
@@ -778,12 +779,15 @@ function EventGraphCards({
.map(ev => EVENT_TO_CAUSAL_CAT[ev.category]).filter(Boolean) .map(ev => EVENT_TO_CAUSAL_CAT[ev.category]).filter(Boolean)
) )
const relevant = templates.filter(t => allCats.has(t.category)) // Only show templates where the event is in the period AND template touches this instrument
const relevant = templates.filter(t =>
allCats.has(t.category) && causalInsts.some(ci => t.instruments.includes(ci))
)
if (!relevant.length) { if (!relevant.length) {
return ( return (
<div className="text-xs text-slate-600 italic py-3"> <div className="text-xs text-slate-600 italic py-3">
Aucun graphe causal assoc aux événements de cette période Aucun graphe causal l aux événements de cette période pour cet instrument
</div> </div>
) )
} }
@@ -791,26 +795,22 @@ function EventGraphCards({
return ( return (
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3"> <div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
{relevant.map(t => { {relevant.map(t => {
const eventActive = activeCats.has(t.category) const eventActive = activeCats.has(t.category)
const instrumentTouched = causalInsts.some(ci => t.instruments.includes(ci))
return ( return (
<div <div
key={t.id} key={t.id}
onClick={() => navigate(`/causal-lab?template=${t.id}`)}
className={clsx( className={clsx(
'rounded-lg border p-2.5 transition-colors', 'rounded-lg border p-2.5 cursor-pointer transition-colors group',
eventActive && instrumentTouched eventActive
? 'border-emerald-600/50 bg-emerald-900/20' ? 'border-emerald-600/50 bg-emerald-900/20 hover:bg-emerald-900/30'
: eventActive : 'border-slate-700/30 bg-dark-700/40 hover:bg-dark-700/70'
? 'border-amber-600/40 bg-amber-900/10'
: 'border-slate-700/30 bg-dark-700/40'
)} )}
> >
<div className="flex items-start justify-between gap-1 mb-1.5"> <div className="flex items-start justify-between gap-1 mb-1.5">
<span className={clsx( <span className={clsx(
'text-xs font-semibold leading-tight', 'text-xs font-semibold leading-tight group-hover:underline',
eventActive && instrumentTouched ? 'text-emerald-300' eventActive ? 'text-emerald-300' : 'text-slate-500'
: eventActive ? 'text-amber-300'
: 'text-slate-500'
)}> )}>
{t.name} {t.name}
</span> </span>
@@ -832,15 +832,10 @@ function EventGraphCards({
))} ))}
</div> </div>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className={clsx('text-[9px]', eventActive ? 'text-amber-500' : 'text-slate-700')}> <span className={clsx('text-[9px]', eventActive ? 'text-emerald-500' : 'text-slate-700')}>
{eventActive ? '● actif' : '○ période'} {eventActive ? '● actif' : '○ période'}
</span> </span>
<span className={clsx( <span className={clsx('w-2 h-2 rounded-full', eventActive ? 'bg-emerald-400' : 'bg-slate-700')} />
'w-2 h-2 rounded-full',
eventActive && instrumentTouched ? 'bg-emerald-400'
: eventActive ? 'bg-amber-500'
: 'bg-slate-700'
)} />
</div> </div>
</div> </div>
) )