feat: Implement responsive history page layout with horizontal tabs for mobile and vertical tabs for desktop
This commit is contained in:
parent
422a117026
commit
ac31c5e014
|
|
@ -3,7 +3,7 @@
|
||||||
> **Gitea:** [#30 – Responsive UI](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues/30)
|
> **Gitea:** [#30 – Responsive UI](http://192.168.2.144:3000/Lars/mitai-jinkendo/issues/30)
|
||||||
> **Spec:** `.claude/docs/functional/RESPONSIVE_UI.md`
|
> **Spec:** `.claude/docs/functional/RESPONSIVE_UI.md`
|
||||||
> **Breakpoint:** `<1024px` = Mobile (Bottom-Nav, bestehendes Verhalten), `≥1024px` = Desktop (Sidebar 220px)
|
> **Breakpoint:** `<1024px` = Mobile (Bottom-Nav, bestehendes Verhalten), `≥1024px` = Desktop (Sidebar 220px)
|
||||||
> **Letzte Plan-Aktualisierung:** 2026-04-06
|
> **Letzte Plan-Aktualisierung:** 2026-04-04
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
| P1 | App-Shell: Sidebar + Breakpoint + gemeinsame Navigation | ☑ erledigt | `DesktopSidebar`, `config/appNav.js`, Admin `/admin/*`-Highlight |
|
| P1 | App-Shell: Sidebar + Breakpoint + gemeinsame Navigation | ☑ erledigt | `DesktopSidebar`, `config/appNav.js`, Admin `/admin/*`-Highlight |
|
||||||
| P2 | Globales Layout & Content-Bereich (CSS) | ☑ erledigt | Desktop: Header aus, Content max 1200px; Mobile unverändert Bottom-Nav |
|
| 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 |
|
| P3 | Dashboard (Desktop-Grid) | ☑ erledigt | 4-spaltige Kennzahlen; Begrüßung; Ernährung/Aktivität 2-spaltig |
|
||||||
| P4 | Verlauf (Tabs links / Content rechts) | ☐ pending | |
|
| 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) | ☐ pending | |
|
||||||
| P6 | Erfassung / Capture & Formularseiten | ☐ pending | |
|
| P6 | Erfassung / Capture & Formularseiten | ☐ pending | |
|
||||||
| P7 | Admin & restliche Vollbreiten-Seiten | ☐ pending | |
|
| P7 | Admin & restliche Vollbreiten-Seiten | ☐ pending | |
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,106 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
||||||
/* Section */
|
/* Section */
|
||||||
.section-gap { margin-bottom: 16px; }
|
.section-gap { margin-bottom: 16px; }
|
||||||
.page-title { font-size: 20px; font-weight: 700; margin-bottom: 16px; }
|
.page-title { font-size: 20px; font-weight: 700; margin-bottom: 16px; }
|
||||||
|
|
||||||
|
/* Verlauf: Mobile Tabs horizontale Leiste, Desktop vertikal links (P4 / RESPONSIVE_UI §5.2) */
|
||||||
|
.history-page__title {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-page__layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tabs {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tabs__scroller {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
-ms-overflow-style: none;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tabs__scroller::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tab-btn {
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 7px 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tab-btn:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--text1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tab-btn.history-tab-btn--active {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tab-btn.history-tab-btn--active:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.history-page__layout {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tabs {
|
||||||
|
flex: 0 0 260px;
|
||||||
|
max-width: 280px;
|
||||||
|
position: sticky;
|
||||||
|
top: 16px;
|
||||||
|
align-self: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tabs__scroller {
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-x: visible;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
padding-bottom: 0;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-tab-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 10px;
|
||||||
|
white-space: normal;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.muted { color: var(--text3); font-size: 13px; }
|
.muted { color: var(--text3); font-size: 13px; }
|
||||||
.empty-state { text-align: center; padding: 48px 16px; color: var(--text3); }
|
.empty-state { text-align: center; padding: 48px 16px; color: var(--text3); }
|
||||||
.empty-state h3 { font-size: 16px; color: var(--text2); margin-bottom: 6px; }
|
.empty-state h3 { font-size: 16px; color: var(--text2); margin-bottom: 6px; }
|
||||||
|
|
|
||||||
|
|
@ -985,6 +985,11 @@ export default function History() {
|
||||||
|
|
||||||
useEffect(()=>{ loadAll() },[])
|
useEffect(()=>{ loadAll() },[])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const t = location.state?.tab
|
||||||
|
if (t && TABS.some(x => x.id === t)) setTab(t)
|
||||||
|
}, [location.state?.tab])
|
||||||
|
|
||||||
const requestInsight = async (slug) => {
|
const requestInsight = async (slug) => {
|
||||||
setLoadingSlug(slug)
|
setLoadingSlug(slug)
|
||||||
try {
|
try {
|
||||||
|
|
@ -1007,27 +1012,33 @@ export default function History() {
|
||||||
const sp={insights,onRequest:requestInsight,loadingSlug,filterActiveSlugs}
|
const sp={insights,onRequest:requestInsight,loadingSlug,filterActiveSlugs}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="history-page">
|
||||||
<h1 className="page-title">Verlauf & Auswertung</h1>
|
<h1 className="page-title history-page__title">Verlauf & Auswertung</h1>
|
||||||
<div style={{display:'flex',gap:6,overflowX:'auto',paddingBottom:6,marginBottom:16,
|
<div className="history-page__layout">
|
||||||
msOverflowStyle:'none',scrollbarWidth:'none'}}>
|
<nav className="history-tabs" aria-label="Verlauf-Kategorien">
|
||||||
{TABS.map(t=>(
|
<div className="history-tabs__scroller">
|
||||||
<button key={t.id} onClick={()=>setTab(t.id)}
|
{TABS.map(t => (
|
||||||
style={{whiteSpace:'nowrap',padding:'7px 14px',borderRadius:20,flexShrink:0,
|
<button
|
||||||
border:`1.5px solid ${tab===t.id?'var(--accent)':'var(--border2)'}`,
|
key={t.id}
|
||||||
background:tab===t.id?'var(--accent)':'var(--surface)',
|
type="button"
|
||||||
color:tab===t.id?'white':'var(--text2)',
|
className={`history-tab-btn${tab === t.id ? ' history-tab-btn--active' : ''}`}
|
||||||
fontFamily:'var(--font)',fontSize:13,fontWeight:500,cursor:'pointer'}}>
|
onClick={() => setTab(t.id)}
|
||||||
{t.label}
|
aria-current={tab === t.id ? 'page' : undefined}
|
||||||
</button>
|
>
|
||||||
))}
|
{t.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div className="history-content">
|
||||||
|
{tab==='body' && <BodySection weights={weights} calipers={calipers} circs={circs} profile={profile} {...sp}/>}
|
||||||
|
{tab==='nutrition' && <NutritionSection nutrition={nutrition} weights={weights} profile={profile} {...sp}/>}
|
||||||
|
{tab==='activity' && <ActivitySection activities={activities} globalQualityLevel={activeProfile?.quality_filter_level} {...sp}/>}
|
||||||
|
{tab==='recovery' && <RecoverySection {...sp}/>}
|
||||||
|
{tab==='correlation' && <CorrelationSection corrData={corrData} profile={profile} {...sp}/>}
|
||||||
|
{tab==='photos' && <PhotoGrid/>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{tab==='body' && <BodySection weights={weights} calipers={calipers} circs={circs} profile={profile} {...sp}/>}
|
|
||||||
{tab==='nutrition' && <NutritionSection nutrition={nutrition} weights={weights} profile={profile} {...sp}/>}
|
|
||||||
{tab==='activity' && <ActivitySection activities={activities} globalQualityLevel={activeProfile?.quality_filter_level} {...sp}/>}
|
|
||||||
{tab==='recovery' && <RecoverySection {...sp}/>}
|
|
||||||
{tab==='correlation' && <CorrelationSection corrData={corrData} profile={profile} {...sp}/>}
|
|
||||||
{tab==='photos' && <PhotoGrid/>}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user