feat: Implement CaptureShell layout with sub-navigation for capture section
This commit is contained in:
parent
a639d08037
commit
a4376d1cd8
|
|
@ -11,6 +11,7 @@ import LoginScreen from './pages/LoginScreen'
|
|||
import Register from './pages/Register'
|
||||
import Verify from './pages/Verify'
|
||||
import Dashboard from './pages/Dashboard'
|
||||
import CaptureShell from './layouts/CaptureShell'
|
||||
import CaptureHub from './pages/CaptureHub'
|
||||
import WeightScreen from './pages/WeightScreen'
|
||||
import CircumScreen from './pages/CircumScreen'
|
||||
|
|
@ -42,10 +43,12 @@ import CustomGoalsPage from './pages/CustomGoalsPage'
|
|||
import WorkflowEditorPage from './pages/WorkflowEditorPage'
|
||||
import DesktopSidebar from './components/DesktopSidebar'
|
||||
import { getMainNavItems } from './config/appNav'
|
||||
import { isCaptureSectionPath } from './config/captureNav'
|
||||
import './app.css'
|
||||
|
||||
function navItemActive(pathname, item, routerIsActive) {
|
||||
if (item.to.startsWith('/admin')) return pathname.startsWith('/admin')
|
||||
if (item.to === '/capture' && isCaptureSectionPath(pathname)) return true
|
||||
return routerIsActive
|
||||
}
|
||||
|
||||
|
|
@ -198,22 +201,24 @@ function AppShell() {
|
|||
<main className="app-main">
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard/>}/>
|
||||
<Route path="/capture" element={<CaptureHub/>}/>
|
||||
<Route path="/wizard" element={<MeasureWizard/>}/>
|
||||
<Route path="/weight" element={<WeightScreen/>}/>
|
||||
<Route path="/circum" element={<CircumScreen/>}/>
|
||||
<Route path="/caliper" element={<CaliperScreen/>}/>
|
||||
<Route element={<CaptureShell />}>
|
||||
<Route path="/capture" element={<CaptureHub />} />
|
||||
<Route path="/wizard" element={<MeasureWizard />} />
|
||||
<Route path="/weight" element={<WeightScreen />} />
|
||||
<Route path="/circum" element={<CircumScreen />} />
|
||||
<Route path="/caliper" element={<CaliperScreen />} />
|
||||
<Route path="/sleep" element={<SleepPage />} />
|
||||
<Route path="/rest-days" element={<RestDaysPage />} />
|
||||
<Route path="/vitals" element={<VitalsPage />} />
|
||||
<Route path="/custom-goals" element={<CustomGoalsPage />} />
|
||||
<Route path="/nutrition" element={<NutritionPage />} />
|
||||
<Route path="/activity" element={<ActivityPage />} />
|
||||
<Route path="/guide" element={<GuidePage />} />
|
||||
</Route>
|
||||
<Route path="/history" element={<History/>}/>
|
||||
<Route path="/sleep" element={<SleepPage/>}/>
|
||||
<Route path="/rest-days" element={<RestDaysPage/>}/>
|
||||
<Route path="/vitals" element={<VitalsPage/>}/>
|
||||
<Route path="/goals" element={<GoalsPage/>}/>
|
||||
<Route path="/custom-goals" element={<CustomGoalsPage/>}/>
|
||||
<Route path="/nutrition" element={<NutritionPage/>}/>
|
||||
<Route path="/activity" element={<ActivityPage/>}/>
|
||||
<Route path="/analysis" element={<Analysis/>}/>
|
||||
<Route path="/settings" element={<SettingsPage/>}/>
|
||||
<Route path="/guide" element={<GuidePage/>}/>
|
||||
<Route path="/admin/tier-limits" element={<AdminTierLimitsPage/>}/>
|
||||
<Route path="/admin/features" element={<AdminFeaturesPage/>}/>
|
||||
<Route path="/admin/tiers" element={<AdminTiersPage/>}/>
|
||||
|
|
|
|||
|
|
@ -363,6 +363,120 @@ body { font-family: var(--font); background: var(--bg); color: var(--text1); -we
|
|||
}
|
||||
}
|
||||
|
||||
/* Erfassung: Sub-Navigation (Mobil = Chips, Desktop = linke Spalte) */
|
||||
.capture-shell {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.capture-shell__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.capture-shell__nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 6px;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.capture-shell__nav::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.capture-shell__nav-item {
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 12px;
|
||||
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;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.capture-shell__nav-item:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--text1);
|
||||
}
|
||||
|
||||
.capture-shell__nav-item--active {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.capture-shell__nav-item--active:hover {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.capture-shell__nav-item--highlight:not(.capture-shell__nav-item--active) {
|
||||
border-color: #7f77dd88;
|
||||
background: #7f77dd14;
|
||||
}
|
||||
|
||||
.capture-shell__nav-icon {
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.capture-shell__nav-label {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.capture-shell__main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.capture-shell__layout {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.capture-shell__nav-wrap {
|
||||
flex: 0 0 260px;
|
||||
max-width: 280px;
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.capture-shell__nav {
|
||||
flex-direction: column;
|
||||
overflow-x: visible;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 140px);
|
||||
padding-bottom: 0;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.capture-shell__nav-item {
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
border-radius: 10px;
|
||||
white-space: normal;
|
||||
padding: 9px 12px;
|
||||
}
|
||||
|
||||
.capture-shell__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; }
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import { NavLink, useLocation } from 'react-router-dom'
|
|||
import { LogOut } from 'lucide-react'
|
||||
import { Avatar } from '../pages/ProfileSelect'
|
||||
import { getMainNavItems } from '../config/appNav'
|
||||
import { isCaptureSectionPath } from '../config/captureNav'
|
||||
|
||||
function sidebarLinkActive(pathname, item, routerIsActive) {
|
||||
if (item.to.startsWith('/admin')) return pathname.startsWith('/admin')
|
||||
if (item.to === '/capture' && isCaptureSectionPath(pathname)) return true
|
||||
return routerIsActive
|
||||
}
|
||||
|
||||
|
|
|
|||
103
frontend/src/config/captureNav.js
Normal file
103
frontend/src/config/captureNav.js
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Erfassungs-Routen: Kachel-Hub + Sub-Navigation (Chip / Seitenleiste).
|
||||
* Pfade müssen mit den Routes in App.jsx unter CaptureShell übereinstimmen.
|
||||
*/
|
||||
|
||||
export const CAPTURE_HUB_TILES = [
|
||||
{
|
||||
icon: '⚖️',
|
||||
label: 'Gewicht',
|
||||
sub: 'Tägliche Gewichtseingabe',
|
||||
to: '/weight',
|
||||
color: '#378ADD',
|
||||
},
|
||||
{
|
||||
icon: '🪄',
|
||||
label: 'Assistent',
|
||||
sub: 'Schritt-für-Schritt Messung (Umfänge & Caliper)',
|
||||
to: '/wizard',
|
||||
color: '#7F77DD',
|
||||
highlight: true,
|
||||
},
|
||||
{
|
||||
icon: '📏',
|
||||
label: 'Umfänge',
|
||||
sub: 'Hals, Brust, Taille, Bauch, Hüfte, Oberschenkel, Wade, Arm',
|
||||
to: '/circum',
|
||||
color: '#1D9E75',
|
||||
},
|
||||
{
|
||||
icon: '📐',
|
||||
label: 'Caliper',
|
||||
sub: 'Körperfett per Hautfaltenmessung',
|
||||
to: '/caliper',
|
||||
color: '#D85A30',
|
||||
},
|
||||
{
|
||||
icon: '🍽️',
|
||||
label: 'Ernährung',
|
||||
sub: 'FDDB CSV importieren',
|
||||
to: '/nutrition',
|
||||
color: '#EF9F27',
|
||||
},
|
||||
{
|
||||
icon: '🏋️',
|
||||
label: 'Aktivität',
|
||||
sub: 'Training manuell oder Apple Health importieren',
|
||||
to: '/activity',
|
||||
color: '#D4537E',
|
||||
},
|
||||
{
|
||||
icon: '🌙',
|
||||
label: 'Schlaf',
|
||||
sub: 'Schlafdaten erfassen oder Apple Health importieren',
|
||||
to: '/sleep',
|
||||
color: '#7B68EE',
|
||||
},
|
||||
{
|
||||
icon: '🛌',
|
||||
label: 'Ruhetage',
|
||||
sub: 'Kraft-, Cardio-, oder Entspannungs-Ruhetag erfassen',
|
||||
to: '/rest-days',
|
||||
color: '#9B59B6',
|
||||
},
|
||||
{
|
||||
icon: '❤️',
|
||||
label: 'Vitalwerte',
|
||||
sub: 'Ruhepuls und HRV morgens erfassen',
|
||||
to: '/vitals',
|
||||
color: '#E74C3C',
|
||||
},
|
||||
{
|
||||
icon: '🎯',
|
||||
label: 'Eigene Ziele',
|
||||
sub: 'Fortschritte für individuelle Ziele erfassen',
|
||||
to: '/custom-goals',
|
||||
color: '#1D9E75',
|
||||
},
|
||||
{
|
||||
icon: '📖',
|
||||
label: 'Messanleitung',
|
||||
sub: 'Wie und wo genau messen?',
|
||||
to: '/guide',
|
||||
color: '#888780',
|
||||
},
|
||||
]
|
||||
|
||||
/** Erster Eintrag: zurück zur Kachel-Übersicht */
|
||||
const OVERVIEW_ENTRY = {
|
||||
icon: '📋',
|
||||
label: 'Übersicht',
|
||||
sub: 'Alle Erfassungsarten',
|
||||
to: '/capture',
|
||||
color: 'var(--accent)',
|
||||
}
|
||||
|
||||
/** Reihenfolge für Chip- / Seitenleiste (inkl. Übersicht) */
|
||||
export const CAPTURE_SHELL_NAV_ITEMS = [OVERVIEW_ENTRY, ...CAPTURE_HUB_TILES]
|
||||
|
||||
export const CAPTURE_SECTION_PATHS = CAPTURE_SHELL_NAV_ITEMS.map((e) => e.to)
|
||||
|
||||
export function isCaptureSectionPath(pathname) {
|
||||
return CAPTURE_SECTION_PATHS.includes(pathname)
|
||||
}
|
||||
36
frontend/src/layouts/CaptureShell.jsx
Normal file
36
frontend/src/layouts/CaptureShell.jsx
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { Outlet, NavLink } from 'react-router-dom'
|
||||
import { CAPTURE_SHELL_NAV_ITEMS } from '../config/captureNav'
|
||||
|
||||
/**
|
||||
* Erfassung: Mobil Chip-Leiste, Desktop linke Spalte – Wechsel zwischen Masken ohne Hub.
|
||||
*/
|
||||
export default function CaptureShell() {
|
||||
return (
|
||||
<div className="capture-shell">
|
||||
<div className="capture-shell__layout">
|
||||
<nav className="capture-shell__nav-wrap" aria-label="Erfassungsbereiche">
|
||||
<div className="capture-shell__nav">
|
||||
{CAPTURE_SHELL_NAV_ITEMS.map((e) => (
|
||||
<NavLink
|
||||
key={e.to}
|
||||
to={e.to}
|
||||
end={e.to === '/capture'}
|
||||
className={({ isActive }) =>
|
||||
'capture-shell__nav-item' +
|
||||
(isActive ? ' capture-shell__nav-item--active' : '') +
|
||||
(e.highlight ? ' capture-shell__nav-item--highlight' : '')
|
||||
}
|
||||
>
|
||||
<span className="capture-shell__nav-icon" aria-hidden>{e.icon}</span>
|
||||
<span className="capture-shell__nav-label">{e.label}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
<div className="capture-shell__main">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,86 +1,6 @@
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { ChevronRight } from 'lucide-react'
|
||||
|
||||
const ENTRIES = [
|
||||
{
|
||||
icon: '⚖️',
|
||||
label: 'Gewicht',
|
||||
sub: 'Tägliche Gewichtseingabe',
|
||||
to: '/weight',
|
||||
color: '#378ADD',
|
||||
},
|
||||
{
|
||||
icon: '🪄',
|
||||
label: 'Assistent',
|
||||
sub: 'Schritt-für-Schritt Messung (Umfänge & Caliper)',
|
||||
to: '/wizard',
|
||||
color: '#7F77DD',
|
||||
highlight: true,
|
||||
},
|
||||
{
|
||||
icon: '📏',
|
||||
label: 'Umfänge',
|
||||
sub: 'Hals, Brust, Taille, Bauch, Hüfte, Oberschenkel, Wade, Arm',
|
||||
to: '/circum',
|
||||
color: '#1D9E75',
|
||||
},
|
||||
{
|
||||
icon: '📐',
|
||||
label: 'Caliper',
|
||||
sub: 'Körperfett per Hautfaltenmessung',
|
||||
to: '/caliper',
|
||||
color: '#D85A30',
|
||||
},
|
||||
{
|
||||
icon: '🍽️',
|
||||
label: 'Ernährung',
|
||||
sub: 'FDDB CSV importieren',
|
||||
to: '/nutrition',
|
||||
color: '#EF9F27',
|
||||
},
|
||||
{
|
||||
icon: '🏋️',
|
||||
label: 'Aktivität',
|
||||
sub: 'Training manuell oder Apple Health importieren',
|
||||
to: '/activity',
|
||||
color: '#D4537E',
|
||||
},
|
||||
{
|
||||
icon: '🌙',
|
||||
label: 'Schlaf',
|
||||
sub: 'Schlafdaten erfassen oder Apple Health importieren',
|
||||
to: '/sleep',
|
||||
color: '#7B68EE',
|
||||
},
|
||||
{
|
||||
icon: '🛌',
|
||||
label: 'Ruhetage',
|
||||
sub: 'Kraft-, Cardio-, oder Entspannungs-Ruhetag erfassen',
|
||||
to: '/rest-days',
|
||||
color: '#9B59B6',
|
||||
},
|
||||
{
|
||||
icon: '❤️',
|
||||
label: 'Vitalwerte',
|
||||
sub: 'Ruhepuls und HRV morgens erfassen',
|
||||
to: '/vitals',
|
||||
color: '#E74C3C',
|
||||
},
|
||||
{
|
||||
icon: '🎯',
|
||||
label: 'Eigene Ziele',
|
||||
sub: 'Fortschritte für individuelle Ziele erfassen',
|
||||
to: '/custom-goals',
|
||||
color: '#1D9E75',
|
||||
},
|
||||
{
|
||||
icon: '📖',
|
||||
label: 'Messanleitung',
|
||||
sub: 'Wie und wo genau messen?',
|
||||
to: '/guide',
|
||||
color: '#888780',
|
||||
},
|
||||
]
|
||||
import { CAPTURE_HUB_TILES } from '../config/captureNav'
|
||||
|
||||
export default function CaptureHub() {
|
||||
const nav = useNavigate()
|
||||
|
|
@ -88,7 +8,7 @@ export default function CaptureHub() {
|
|||
<div className="capture-page">
|
||||
<h1 className="page-title">Erfassen</h1>
|
||||
<div style={{display:'flex',flexDirection:'column',gap:10}}>
|
||||
{ENTRIES.map(e => (
|
||||
{CAPTURE_HUB_TILES.map(e => (
|
||||
<button key={e.to} onClick={()=>nav(e.to)}
|
||||
style={{
|
||||
display:'flex', alignItems:'center', gap:14,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user