From 630a3de88ac68b077f5bb68d3c9c452962611610 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 5 Apr 2026 10:06:29 +0200 Subject: [PATCH] feat: Refactor analysis navigation to use category-based grouping and update state management --- frontend/src/app.css | 84 ++------------- frontend/src/pages/Analysis.jsx | 184 ++++++++++++++------------------ 2 files changed, 90 insertions(+), 178 deletions(-) diff --git a/frontend/src/app.css b/frontend/src/app.css index a9a4426..e21136b 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -291,82 +291,16 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we color: rgba(255, 255, 255, 0.88) !important; } -/* Analyse „Analysen starten“: Unternavigation nach DB-Kategorie gruppiert */ -.analysis-split__nav--grouped { - flex-direction: column; - overflow-x: visible; - overflow-y: auto; - padding-bottom: 0; - gap: 0; - max-height: min(70vh, 560px); -} - -.analysis-split__nav-group { - margin-bottom: 14px; -} - -.analysis-split__nav-group:last-child { - margin-bottom: 0; -} - -.analysis-split__nav-group-title { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; +.analysis-split__nav-cat-count { + margin-left: 6px; font-size: 11px; - font-weight: 700; - color: var(--text3); - text-transform: uppercase; - letter-spacing: 0.05em; - padding: 6px 4px 10px; - border-bottom: 1px solid var(--border); + font-weight: 500; + opacity: 0.92; } -.analysis-split__nav-group-count { - font-size: 10px; - font-weight: 600; - text-transform: none; - letter-spacing: 0; - color: var(--text3); - background: var(--surface2); - padding: 2px 7px; - border-radius: 8px; -} - -.analysis-split__nav-group-items { - display: flex; - flex-direction: column; - gap: 8px; - padding-top: 10px; -} - -.analysis-split__nav--grouped .analysis-split__nav-item { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 10px; - width: 100%; - white-space: normal; - text-align: left; - border-radius: 10px; -} - -.analysis-split__nav-item-label { - flex: 1; - min-width: 0; - line-height: 1.35; -} - -.analysis-split__nav-item-meta { - font-size: 11px; - font-weight: 400; - flex-shrink: 0; - line-height: 1.35; -} - -.analysis-split__nav-item--active .analysis-split__nav-item-meta { - color: rgba(255, 255, 255, 0.9); +.analysis-split__nav-item--active .analysis-split__nav-cat-count { + color: rgba(255, 255, 255, 0.95); + opacity: 1; } .analysis-split__main { @@ -397,10 +331,6 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we gap: 8px; } - .analysis-split__nav--grouped { - max-height: calc(100vh - 140px); - } - .analysis-split__nav-item { width: 100%; justify-content: flex-start; diff --git a/frontend/src/pages/Analysis.jsx b/frontend/src/pages/Analysis.jsx index 529ea61..487a98e 100644 --- a/frontend/src/pages/Analysis.jsx +++ b/frontend/src/pages/Analysis.jsx @@ -45,11 +45,6 @@ function analysisCategoryLabel(key) { return ANALYSIS_CATEGORY_LABELS[k] || String(key) } -/** Statische DOM-Id für aria-labelledby (Kategorie kann Umlaute enthalten) */ -function analysisNavGroupDomId(categoryKey) { - return `analysis-nav-cat-${String(categoryKey).replace(/[^a-zA-Z0-9_-]/g, '_')}` -} - /** Pipeline-Prompts nach `category` gruppieren (Backend-Feld), innerhalb Gruppe nach sort_order */ function buildPipelineGroups(pipelinePrompts) { const m = new Map() @@ -343,7 +338,8 @@ export default function Analysis() { const [tab, setTab] = useState('run') const [newResult, setNewResult] = useState(null) const [aiUsage, setAiUsage] = useState(null) - const [activePipelineSlug, setActivePipelineSlug] = useState(null) + /** Kategorie-Schlüssel aus `buildPipelineGroups` (Navigation); Detail = alle Pipelines dieser Kategorie */ + const [activeCategoryKey, setActiveCategoryKey] = useState(null) const [historyScopePick, setHistoryScopePick] = useState(null) const loadAll = async () => { @@ -366,17 +362,22 @@ export default function Analysis() { useEffect(() => { const list = prompts.filter(p => p.active && p.type === 'pipeline') - setActivePipelineSlug(prev => { + setActiveCategoryKey(prev => { if (!list.length) return null - if (prev && list.some(p => p.slug === prev)) return prev const groups = buildPipelineGroups(list) - return groups[0]?.prompts[0]?.slug ?? list[0].slug + const keys = groups.map(g => g.categoryKey) + if (prev && keys.includes(prev)) return prev + return groups[0]?.categoryKey ?? null }) }, [prompts]) useEffect(() => { - if (newResult?.scope) setActivePipelineSlug(newResult.scope) - }, [newResult?.scope]) + if (!newResult?.scope) return + const list = prompts.filter(p => p.active && p.type === 'pipeline') + const groups = buildPipelineGroups(list) + const g = groups.find(gg => gg.prompts.some(p => p.slug === newResult.scope)) + if (g) setActiveCategoryKey(g.categoryKey) + }, [newResult?.scope, prompts]) const runPrompt = async (slug) => { setLoading(slug); setError(null); setNewResult(null) @@ -539,106 +540,87 @@ export default function Analysis() { {canUseAI && pipelinePrompts.length > 0 && ( <>

- Analysen sind nach Kategorie gruppiert (Feld „Kategorie“ beim Prompt, wie im Admin). - Wähle einen Eintrag; der Detailbereich erscheint auf dem Desktop rechts, auf schmalen Screens darunter. + Zuerst die Kategorie wählen (Chip-Leiste bzw. Seitenleiste). Alle Pipeline-Analysen + dieser Kategorie erscheinen im Detailbereich (rechts auf Desktop, darunter auf Mobil). + Kategorien kommen aus dem Feld „Kategorie“ beim jeweiligen Prompt im Admin.

-
- {activePipelineSlug && (() => { - const p = pipelinePrompts.find(x => x.slug === activePipelineSlug) - if (!p) return null - const existing = allInsights.find(i => i.scope === p.slug) + {activeCategoryKey && (() => { + const group = pipelineGroups.find(g => g.categoryKey === activeCategoryKey) + if (!group?.prompts?.length) return null return ( -
-
-
-
- {p.display_name || SLUG_LABELS[p.slug] || p.name} - {aiUsage && } -
- {p.description && ( -
- {p.description} -
- )} - {existing && ( -
- Letzte Analyse: {dayjs(existing.created).format('DD.MM.YYYY, HH:mm')} -
- )} -
-
- -
+ <> +
+ {group.label} · {group.prompts.length} {group.prompts.length === 1 ? 'Analyse' : 'Analysen'}
- {existing && newResult?.id !== existing.id && ( -
- -
- )} -
+ {group.prompts.map(p => { + const existing = allInsights.find(i => i.scope === p.slug) + return ( +
+
+
+
+ {p.display_name || SLUG_LABELS[p.slug] || p.name} + {aiUsage && } +
+ {p.description && ( +
+ {p.description} +
+ )} + {existing && ( +
+ Letzte Analyse: {dayjs(existing.created).format('DD.MM.YYYY, HH:mm')} +
+ )} +
+
+ +
+
+ {existing && newResult?.id !== existing.id && ( +
+ +
+ )} +
+ ) + })} + ) })()}