diff --git a/docs/issues/PHASE_PLAN_RESPONSIVE_UI.md b/docs/issues/PHASE_PLAN_RESPONSIVE_UI.md index 54f2123..fec7466 100644 --- a/docs/issues/PHASE_PLAN_RESPONSIVE_UI.md +++ b/docs/issues/PHASE_PLAN_RESPONSIVE_UI.md @@ -3,7 +3,7 @@ > **Gitea:** [#30 – Responsive UI](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues/30) > **Spec:** `.claude/docs/functional/RESPONSIVE_UI.md` > **Breakpoint:** `<1024px` = Mobile (Bottom-Nav, bestehendes Verhalten), `≥1024px` = Desktop (Sidebar 220px) -> **Letzte Plan-Aktualisierung:** 2026-04-04 +> **Letzte Plan-Aktualisierung:** 2026-04-04 (P5) --- @@ -16,7 +16,7 @@ | P2 | Globales Layout & Content-Bereich (CSS) | ☑ erledigt | Desktop: Header aus, Content max 1200px; Mobile unverändert Bottom-Nav | | P3 | Dashboard (Desktop-Grid) | ☑ erledigt | 4-spaltige Kennzahlen; Begrüßung; Ernährung/Aktivität 2-spaltig | | P4 | Verlauf (Tabs links / Content rechts) | ☑ erledigt | `History.jsx` + `.history-*` in `app.css`; Tab-State bei `location.state.tab` | -| P5 | Analyse (Prompts links / Ergebnis rechts) | ☐ pending | | +| P5 | Analyse (Prompts links / Ergebnis rechts) | ☑ erledigt | `Analysis.jsx` + `.analysis-split*` in `app.css` | | P6 | Erfassung / Capture & Formularseiten | ☐ pending | | | P7 | Admin & restliche Vollbreiten-Seiten | ☐ pending | | | P8 | Abschluss, Regression, Spec-Pflege | ☐ pending | | diff --git a/frontend/src/app.css b/frontend/src/app.css index bb64c0e..91b989e 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -225,6 +225,113 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we } } +/* KI-Analyse (P5): Mobile Prompt-Leiste oben / horizontal, Desktop links ~300px (RESPONSIVE_UI §5.3) */ +.analysis-page__header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + gap: 12px; + flex-wrap: wrap; +} + +.analysis-split { + display: flex; + flex-direction: column; + gap: 16px; +} + +.analysis-split__nav { + display: flex; + flex-direction: row; + gap: 6px; + overflow-x: auto; + padding-bottom: 6px; + -ms-overflow-style: none; + scrollbar-width: none; +} + +.analysis-split__nav::-webkit-scrollbar { + display: none; +} + +.analysis-split__nav-item { + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 14px; + border-radius: 20px; + border: 1.5px solid var(--border2); + background: var(--surface); + color: var(--text2); + font-family: var(--font); + font-size: 13px; + font-weight: 500; + cursor: pointer; + white-space: nowrap; +} + +.analysis-split__nav-item:hover { + border-color: var(--accent); + color: var(--text1); +} + +.analysis-split__nav-item--active { + border-color: var(--accent); + background: var(--accent); + color: white; +} + +.analysis-split__nav-item--active:hover { + color: white; +} + +.analysis-split__nav-item--active .muted { + color: rgba(255, 255, 255, 0.88) !important; +} + +.analysis-split__main { + min-width: 0; +} + +@media (min-width: 1024px) { + .analysis-split { + flex-direction: row; + align-items: flex-start; + gap: 24px; + } + + .analysis-split__nav-wrap { + flex: 0 0 300px; + max-width: 320px; + position: sticky; + top: 16px; + align-self: flex-start; + } + + .analysis-split__nav { + flex-direction: column; + overflow-x: visible; + overflow-y: auto; + max-height: calc(100vh - 140px); + padding-bottom: 0; + gap: 8px; + } + + .analysis-split__nav-item { + width: 100%; + justify-content: flex-start; + text-align: left; + border-radius: 10px; + white-space: normal; + } + + .analysis-split__main { + flex: 1; + } +} + .muted { color: var(--text3); font-size: 13px; } .empty-state { text-align: center; padding: 48px 16px; color: var(--text3); } .empty-state h3 { font-size: 16px; color: var(--text2); margin-bottom: 6px; } diff --git a/frontend/src/pages/Analysis.jsx b/frontend/src/pages/Analysis.jsx index 8c3b2aa..1b8e66b 100644 --- a/frontend/src/pages/Analysis.jsx +++ b/frontend/src/pages/Analysis.jsx @@ -286,6 +286,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) + const [historyScopePick, setHistoryScopePick] = useState(null) const loadAll = async () => { const [p, i] = await Promise.all([ @@ -305,6 +307,19 @@ export default function Analysis() { }).catch(err => console.error('Failed to load usage:', err)) },[]) + useEffect(() => { + const list = prompts.filter(p => p.active && p.type === 'pipeline') + setActivePipelineSlug(prev => { + if (!list.length) return null + if (prev && list.some(p => p.slug === prev)) return prev + return list[0].slug + }) + }, [prompts]) + + useEffect(() => { + if (newResult?.scope) setActivePipelineSlug(newResult.scope) + }, [newResult?.scope]) + const runPrompt = async (slug) => { setLoading(slug); setError(null); setNewResult(null) try { @@ -386,11 +401,20 @@ export default function Analysis() { // Show only active pipeline-type prompts const pipelinePrompts = prompts.filter(p => p.active && p.type === 'pipeline') + const historyScopeKeys = Object.keys(grouped).sort((a, b) => a.localeCompare(b)) + const activeHistoryScope = + historyScopeKeys.length === 0 + ? null + : historyScopeKeys.includes(historyScopePick) + ? historyScopePick + : historyScopeKeys[0] + return ( -
-
+
+

KI-Analyse

- - + -
+ <> +

+ Wähle in der Liste eine Analyse; auf dem Desktop erscheint der Detailbereich rechts, auf schmalen Screens darunter. +

+
+
+ +
+
+ {activePipelineSlug && (() => { + const p = pipelinePrompts.find(x => x.slug === activePipelineSlug) + if (!p) return null + 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 && ( +
+ +
+ )} +
+ ) + })()}
- {/* Show existing result collapsed */} - {existing && newResult?.id !== existing.id && ( -
- -
- )}
- ) - })} + + )} {canUseAI && pipelinePrompts.length === 0 && (
@@ -519,18 +573,33 @@ export default function Analysis() { {/* ── Verlauf gruppiert ── */} {tab==='history' && (
- {allInsights.length===0 - ?

Noch keine Analysen

- : Object.entries(grouped).map(([scope, ins]) => ( -
-
- {prompts.find(p => p.slug === scope)?.display_name || SLUG_LABELS[scope] || scope} ({ins.length}) -
- {ins.map(i => )} + {allInsights.length===0 ? ( +

Noch keine Analysen

+ ) : ( +
+
+
- )) - } +
+ {activeHistoryScope && grouped[activeHistoryScope]?.map(i => ( + + ))} +
+
+ )}
)}