mitai-jinkendo/frontend/src/App.jsx
Lars c10da55ec6
All checks were successful
Deploy Development / deploy (push) Successful in 56s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 15s
feat(csv-templates): Introduce CSV template analysis and validation features
- Added a new endpoint for analyzing uploaded CSV files, providing suggestions for field mappings and type conversions.
- Implemented validation for required field targets to ensure all mandatory fields are mapped correctly.
- Enhanced the admin CSV templates interface with new routes and navigation options in the frontend.
- Updated API utility functions to support the new CSV analysis functionality.
- Improved error handling for CSV uploads, including file size and row count checks.
2026-04-10 06:39:41 +02:00

290 lines
12 KiB
JavaScript

import { useEffect } from 'react'
import { BrowserRouter, Routes, Route, NavLink, useLocation } from 'react-router-dom'
import { LogOut } from 'lucide-react'
import { ProfileProvider, useProfile } from './context/ProfileContext'
import { AuthProvider, useAuth } from './context/AuthContext'
import { setProfileId } from './utils/api'
import { Avatar } from './pages/ProfileSelect'
import SetupScreen from './pages/SetupScreen'
import { ResetPassword } from './pages/PasswordRecovery'
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'
import CaliperScreen from './pages/CaliperScreen'
import MeasureWizard from './pages/MeasureWizard'
import History from './pages/History'
import NutritionPage from './pages/NutritionPage'
import ActivityPage from './pages/ActivityPage'
import Analysis from './pages/Analysis'
import SettingsPage from './pages/SettingsPage'
import SettingsShell from './layouts/SettingsShell'
import ProfileReferenceValuesPage from './pages/ProfileReferenceValuesPage'
import PilotVizPage from './pages/PilotVizPage'
import DashboardLabPage from './pages/DashboardLabPage'
import DashboardConfigurePage from './pages/DashboardConfigurePage'
import GuidePage from './pages/GuidePage'
import AdminTierLimitsPage from './pages/AdminTierLimitsPage'
import AdminFeaturesPage from './pages/AdminFeaturesPage'
import AdminTiersPage from './pages/AdminTiersPage'
import AdminCouponsPage from './pages/AdminCouponsPage'
import AdminUserRestrictionsPage from './pages/AdminUserRestrictionsPage'
import AdminTrainingTypesPage from './pages/AdminTrainingTypesPage'
import AdminActivityMappingsPage from './pages/AdminActivityMappingsPage'
import AdminTrainingProfiles from './pages/AdminTrainingProfiles'
import AdminPromptsPage from './pages/AdminPromptsPage'
import AdminGoalTypesPage from './pages/AdminGoalTypesPage'
import AdminFocusAreasPage from './pages/AdminFocusAreasPage'
import AdminReferenceValueTypesPage from './pages/AdminReferenceValueTypesPage'
import AdminWidgetFeatureAssignmentsPage from './pages/AdminWidgetFeatureAssignmentsPage'
import AdminHomePage from './pages/AdminHomePage'
import AdminUsersPage from './pages/AdminUsersPage'
import AdminSystemPage from './pages/AdminSystemPage'
import AdminGroupHubPage from './pages/AdminGroupHubPage'
import RequireAdmin from './layouts/RequireAdmin'
import AdminShell from './layouts/AdminShell'
import SubscriptionPage from './pages/SubscriptionPage'
import SleepPage from './pages/SleepPage'
import RestDaysPage from './pages/RestDaysPage'
import VitalsPage from './pages/VitalsPage'
import GoalsPage from './pages/GoalsPage'
import CustomGoalsPage from './pages/CustomGoalsPage'
import UniversalCsvImportPage from './pages/UniversalCsvImportPage'
import AdminCsvTemplatesPage from './pages/AdminCsvTemplatesPage'
import AdminCsvTemplateEditorPage from './pages/AdminCsvTemplateEditorPage'
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
}
function Nav({ isAdmin }) {
const items = getMainNavItems(isAdmin)
const loc = useLocation()
return (
<nav className="bottom-nav">
{items.map((item) => (
<NavLink
key={item.to}
to={item.to}
end={!!item.end}
className={({ isActive }) =>
'nav-item' +
(navItemActive(loc.pathname, item, isActive) ? ' active' : '')
}
>
<item.Icon size={20} strokeWidth={2} />
<span>{item.shortLabel || item.label}</span>
</NavLink>
))}
</nav>
)
}
function AppShell() {
const { session, loading: authLoading, needsSetup, logout, isAdmin } = useAuth()
const { activeProfile, loading: profileLoading } = useProfile()
const handleLogout = () => {
if (confirm('Wirklich abmelden?')) {
logout()
window.location.href = '/'
}
}
useEffect(()=>{
if (session?.profile_id) {
setProfileId(session.profile_id)
localStorage.setItem('mitai-jinkendo_active_profile', session.profile_id)
}
}, [session?.profile_id])
// Handle public pages (register, verify, reset-password)
const urlParams = new URLSearchParams(window.location.search)
const currentPath = window.location.pathname
// Register page
if (currentPath === '/register') return (
<div style={{minHeight:'100vh',background:'var(--bg)',padding:24}}>
<Register/>
</div>
)
// Verify email page
if (currentPath === '/verify') return (
<div style={{minHeight:'100vh',background:'var(--bg)',padding:24}}>
<Verify/>
</div>
)
// Password reset page
const resetToken = urlParams.get('reset-password') || (currentPath === '/reset-password' ? urlParams.get('token') : null)
if (resetToken) return (
<div style={{minHeight:'100vh',display:'flex',alignItems:'center',justifyContent:'center',
background:'var(--bg)',padding:24}}>
<div style={{width:'100%',maxWidth:360}}>
<div style={{textAlign:'center',marginBottom:20}}>
<div style={{fontSize:28,fontWeight:800,color:'var(--accent)'}}>Mitai Jinkendo</div>
</div>
<div className="card" style={{padding:20}}>
<ResetPassword token={resetToken} onDone={()=>window.location.href='/'}/>
</div>
</div>
</div>
)
// Auth loading
if (authLoading) return (
<div style={{minHeight:'100vh',display:'flex',alignItems:'center',justifyContent:'center'}}>
<div className="spinner" style={{width:32,height:32}}/>
</div>
)
// First run
if (needsSetup) return <SetupScreen/>
// Need to log in
if (!session) return <LoginScreen/>
// Profile loading
if (profileLoading) return (
<div style={{minHeight:'100vh',display:'flex',alignItems:'center',justifyContent:'center'}}>
<div className="spinner" style={{width:32,height:32}}/>
</div>
)
return (
<div className="app-shell">
<DesktopSidebar
isAdmin={isAdmin}
activeProfile={activeProfile}
sessionProfile={session?.profile}
onLogout={handleLogout}
/>
<div className="app-shell__column">
<header className="app-header app-header--mobile">
<span className="app-logo">Mitai Jinkendo</span>
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
<button
type="button"
onClick={handleLogout}
title="Abmelden"
style={{
background: 'none',
border: 'none',
cursor: 'pointer',
padding: 6,
display: 'flex',
alignItems: 'center',
color: 'var(--text2)',
transition: 'color 0.15s'
}}
onMouseEnter={(e) => {
e.currentTarget.style.color = '#D85A30'
}}
onMouseLeave={(e) => {
e.currentTarget.style.color = 'var(--text2)'
}}
>
<LogOut size={18} />
</button>
<NavLink to="/settings" style={{ textDecoration: 'none' }}>
{activeProfile ? (
<Avatar profile={activeProfile} size={30} />
) : (
<div
style={{
width: 30,
height: 30,
borderRadius: '50%',
background: 'var(--accent)'
}}
/>
)}
</NavLink>
</div>
</header>
<main className="app-main">
<Routes>
<Route path="/" element={<Dashboard/>}/>
<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="/csv-import" element={<UniversalCsvImportPage />} />
<Route path="/activity" element={<ActivityPage />} />
<Route path="/guide" element={<GuidePage />} />
</Route>
<Route path="/history" element={<History/>}/>
<Route path="/goals" element={<GoalsPage/>}/>
<Route path="/analysis" element={<Analysis/>}/>
<Route path="/settings" element={<SettingsShell />}>
<Route index element={<SettingsPage />} />
<Route path="reference-values" element={<ProfileReferenceValuesPage />} />
<Route path="dashboard-layout" element={<DashboardConfigurePage />} />
</Route>
<Route element={<RequireAdmin />}>
<Route path="admin" element={<AdminShell />}>
<Route index element={<AdminHomePage />} />
<Route path="g/:groupId" element={<AdminGroupHubPage />} />
<Route path="users" element={<AdminUsersPage />} />
<Route path="system" element={<AdminSystemPage />} />
<Route path="dashboard-product-default" element={<DashboardConfigurePage adminMode />} />
<Route path="tier-limits" element={<AdminTierLimitsPage/>}/>
<Route path="features" element={<AdminFeaturesPage/>}/>
<Route path="tiers" element={<AdminTiersPage/>}/>
<Route path="coupons" element={<AdminCouponsPage/>}/>
<Route path="user-restrictions" element={<AdminUserRestrictionsPage/>}/>
<Route path="widget-features" element={<AdminWidgetFeatureAssignmentsPage />} />
<Route path="training-types" element={<AdminTrainingTypesPage/>}/>
<Route path="activity-mappings" element={<AdminActivityMappingsPage/>}/>
<Route path="training-profiles" element={<AdminTrainingProfiles/>}/>
<Route path="prompts" element={<AdminPromptsPage/>}/>
<Route path="goal-types" element={<AdminGoalTypesPage/>}/>
<Route path="focus-areas" element={<AdminFocusAreasPage/>}/>
<Route path="reference-value-types" element={<AdminReferenceValueTypesPage/>}/>
<Route path="csv-templates" element={<AdminCsvTemplatesPage />} />
<Route path="csv-templates/:id" element={<AdminCsvTemplateEditorPage />} />
</Route>
</Route>
<Route path="/workflow-editor/:id" element={<WorkflowEditorPage/>}/>
<Route path="/subscription" element={<SubscriptionPage/>}/>
<Route path="/pilot/viz" element={<PilotVizPage />} />
<Route path="/app/dashboard-lab" element={<DashboardLabPage />} />
</Routes>
</main>
</div>
<Nav isAdmin={isAdmin} />
</div>
)
}
export default function App() {
return (
<AuthProvider>
<ProfileProvider>
<BrowserRouter>
<AppShell/>
</BrowserRouter>
</ProfileProvider>
</AuthProvider>
)
}