feat: Enhance Dashboard layout with responsive greeting and metrics display
This commit is contained in:
parent
7e8422cbd7
commit
c2b2c71ccd
|
|
@ -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-05
|
> **Letzte Plan-Aktualisierung:** 2026-04-06
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
| P0 | Vorbereitung & Baseline | ☑ erledigt | Spec `RESPONSIVE_UI.md` bereinigt |
|
| P0 | Vorbereitung & Baseline | ☑ erledigt | Spec `RESPONSIVE_UI.md` bereinigt |
|
||||||
| 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) | ☐ pending | |
|
| 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) | ☐ pending | |
|
||||||
| P5 | Analyse (Prompts links / Ergebnis rechts) | ☐ pending | |
|
| P5 | Analyse (Prompts links / Ergebnis rechts) | ☐ pending | |
|
||||||
| P6 | Erfassung / Capture & Formularseiten | ☐ pending | |
|
| P6 | Erfassung / Capture & Formularseiten | ☐ pending | |
|
||||||
|
|
|
||||||
|
|
@ -344,4 +344,82 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dashboard (P3): Begrüßung + Kennzahlen-Zeile */
|
||||||
|
.dashboard-greeting {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-greeting__meta {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Dashboard layout (Mobile baseline + Desktop im Block oben teilweise) ─ */
|
||||||
|
|
||||||
|
.dashboard-page {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-greeting {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-stat-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-stat-card {
|
||||||
|
background: var(--surface);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 10px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
flex: 1 1 140px;
|
||||||
|
min-width: 80px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-summary-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-summary-row > .card {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.dashboard-stat-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-stat-grid .dashboard-stat-card {
|
||||||
|
flex: unset;
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-summary-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-summary-row > .card {
|
||||||
|
flex: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,11 +148,10 @@ function StatCard({ icon, label, value, unit, delta, deltaGoodWhenNeg=false, sub
|
||||||
const deltaColor = delta==null ? null
|
const deltaColor = delta==null ? null
|
||||||
: (deltaGoodWhenNeg ? delta<0 : delta>0) ? 'var(--accent)' : 'var(--warn)'
|
: (deltaGoodWhenNeg ? delta<0 : delta>0) ? 'var(--accent)' : 'var(--warn)'
|
||||||
return (
|
return (
|
||||||
<div onClick={onClick} style={{
|
<div
|
||||||
flex:1, minWidth:80, background:'var(--surface)', borderRadius:12,
|
className="dashboard-stat-card"
|
||||||
padding:'12px 10px', cursor:onClick?'pointer':'default',
|
onClick={onClick}
|
||||||
border:'1px solid var(--border)', transition:'border-color 0.15s',
|
style={{ cursor: onClick ? 'pointer' : 'default' }}
|
||||||
}}
|
|
||||||
onMouseEnter={e=>onClick&&(e.currentTarget.style.borderColor='var(--accent)')}
|
onMouseEnter={e=>onClick&&(e.currentTarget.style.borderColor='var(--accent)')}
|
||||||
onMouseLeave={e=>onClick&&(e.currentTarget.style.borderColor='var(--border)')}>
|
onMouseLeave={e=>onClick&&(e.currentTarget.style.borderColor='var(--border)')}>
|
||||||
<div style={{fontSize:18,marginBottom:4}}>{icon}</div>
|
<div style={{fontSize:18,marginBottom:4}}>{icon}</div>
|
||||||
|
|
@ -321,13 +320,13 @@ export default function Dashboard() {
|
||||||
console.log('[Dashboard] hasAnyData=', hasAnyData, 'latestW=', !!latestW, 'latestCal=', !!latestCal, 'nutrition.length=', nutrition.length)
|
console.log('[Dashboard] hasAnyData=', hasAnyData, 'latestW=', !!latestW, 'latestCal=', !!latestCal, 'nutrition.length=', nutrition.length)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="dashboard-page">
|
||||||
{/* Header greeting */}
|
{/* Header greeting */}
|
||||||
<div style={{marginBottom:16}}>
|
<div className="dashboard-greeting">
|
||||||
<h1 style={{fontSize:22,fontWeight:800,margin:0,color:'var(--text1)'}}>
|
<h1 style={{fontSize:22,fontWeight:800,margin:0,color:'var(--text1)'}}>
|
||||||
Hallo, {activeProfile?.name||'Nutzer'} 👋
|
Hallo, {activeProfile?.name||'Nutzer'} 👋
|
||||||
</h1>
|
</h1>
|
||||||
<div style={{fontSize:12,color:'var(--text3)',marginTop:2}}>
|
<div className="dashboard-greeting__meta" style={{fontSize:12,color:'var(--text3)',marginTop:2}}>
|
||||||
{dayjs().format('dddd, DD. MMMM YYYY')}
|
{dayjs().format('dddd, DD. MMMM YYYY')}
|
||||||
{latestW && ` · Letztes Update ${dayjs(latestW.date).format('DD.MM.')}`}
|
{latestW && ` · Letztes Update ${dayjs(latestW.date).format('DD.MM.')}`}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -362,8 +361,8 @@ export default function Dashboard() {
|
||||||
<QuickWeight onSaved={load}/>
|
<QuickWeight onSaved={load}/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Key metrics */}
|
{/* Key metrics — Mobile: flex-wrap; Desktop: 4-spaltig (RESPONSIVE_UI P3) */}
|
||||||
<div style={{display:'flex',gap:8,marginBottom:16,flexWrap:'wrap'}}>
|
<div className="dashboard-stat-grid">
|
||||||
<StatCard icon="⚖️" label="Gewicht" value={latestW?.weight??'–'} unit="kg"
|
<StatCard icon="⚖️" label="Gewicht" value={latestW?.weight??'–'} unit="kg"
|
||||||
delta={wDelta} deltaGoodWhenNeg={true}
|
delta={wDelta} deltaGoodWhenNeg={true}
|
||||||
sub={latestW ? dayjs(latestW.date).format('DD.MM.') : '–'}
|
sub={latestW ? dayjs(latestW.date).format('DD.MM.') : '–'}
|
||||||
|
|
@ -451,7 +450,7 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Activity + Nutrition summary row */}
|
{/* Activity + Nutrition summary row */}
|
||||||
<div style={{display:'flex',gap:8,marginBottom:16}}>
|
<div className="dashboard-summary-row">
|
||||||
{(avgKcal||avgProtein) && (
|
{(avgKcal||avgProtein) && (
|
||||||
<div className="card" style={{flex:1,cursor:'pointer'}} onClick={()=>nav('/history',{state:{tab:'nutrition'}})}>
|
<div className="card" style={{flex:1,cursor:'pointer'}} onClick={()=>nav('/history',{state:{tab:'nutrition'}})}>
|
||||||
<div style={{fontWeight:600,fontSize:12,marginBottom:8,color:'var(--text3)'}}>🍽️ ERNÄHRUNG (Ø 7T)</div>
|
<div style={{fontWeight:600,fontSize:12,marginBottom:8,color:'var(--text3)'}}>🍽️ ERNÄHRUNG (Ø 7T)</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user