mitai-jinkendo/.claude/docs/architecture/FRONTEND.md
Lars 7940dc7560 docs: Struktur .claude/docs versionieren, working/, Gitea-Index, Regeln
- .gitignore: .claude/docs, rules, commands tracken; settings.local weiter ignorieren
- DOCUMENTATION.md: verbindliche Ablage functional/technical/working/issues
- .claude/README.md: Agent-Einstieg; GITEA_ISSUES_INDEX aus MCP (Stand 2026-04-08)
- Arbeitspapiere von docs/ nach .claude/docs/working/ verschoben
- docs/MEMBERSHIP_SYSTEM.md als Stub; kanonisch technical/MEMBERSHIP_SYSTEM.md
- CLAUDE.md Pflichtlektüre und Links angepasst; docs/README.md vereinfacht

Made-with: Cursor
2026-04-08 13:01:49 +02:00

3.9 KiB
Raw Blame History

Frontend-Architektur

Struktur

frontend/src/
├── App.jsx              # Root: Auth-Gates, Navigation, Routing
├── app.css              # CSS-Variablen + globale Klassen
├── main.jsx             # Vite Entry Point
├── context/
│   ├── AuthContext.jsx  # Session, Login, Logout, getToken()
│   └── ProfileContext.jsx # Aktives Profil, Profile-Liste
├── pages/               # Eine Datei pro Screen
├── utils/
│   ├── api.js           # Alle API-Calls (Token automatisch injiziert)
│   ├── calc.js          # Körperfett-Formeln (Jackson/Pollock etc.)
│   ├── interpret.js     # Regelbasierte Auswertungen
│   ├── Markdown.jsx     # Eigener Markdown-Renderer
│   └── guideData.js     # Messanleitungen (statisch)
└── public/              # Icons (Jinkendo Ensō-Logo)

API-Calls IMMER über api.js

// ✅ Richtig  Token wird automatisch injiziert:
import { api } from '../utils/api'
const data = await api.listWeight()
await api.upsertWeight(date, weight, note)

// ❌ Falsch  kein Token, gibt 401:
const r = await fetch('/api/weight')

Neue API-Methode hinzufügen

In frontend/src/utils/api.js:

export const api = {
  // ...bestehende Methoden...
  meinEndpoint: (param) => req(`/mein-endpoint?p=${param}`),
  createItem:   (data)  => req('/items', json(data)),
  updateItem:   (id, d) => req(`/items/${id}`, jput(d)),
  deleteItem:   (id)    => req(`/items/${id}`, {method:'DELETE'}),
}

Navigation (Haupt-App & Admin)

  • Hauptmenü (Mobile + Desktop): frontend/src/config/appNav.js (getMainNavItems) in App.jsx (Bottom-Nav) und DesktopSidebar.jsx nutzen.
  • Admin-Bereich: frontend/src/config/adminNav.js + layouts/AdminShell.jsx + layouts/RequireAdmin.jsx; Shell wie Analyse (.analysis-split*).
  • Bottom-Nav / Safe Area (PWA): --nav-h, .bottom-nav, .app-main in app.css.
  • Agent-Doku: docs/issues/GUI_IA_ADMIN_NAV_2026-04-05.md

Neue Seite hinzufügen

  1. frontend/src/pages/MeineSeite.jsx erstellen
  2. In App.jsx importieren und Route hinzufügen
  3. Navigation: Eintrag in config/appNav.js (und ggf. Admin in adminNav.js) nicht mehr nur in App.jsx duplizieren

Komponenten-Pattern

export default function MeineSeite() {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(null)

  useEffect(() => { load() }, [])

  const load = async () => {
    try {
      setLoading(true)
      setData(await api.meinEndpoint())
    } catch(e) { setError(e.message) }
    finally { setLoading(false) }
  }

  if (loading) return <div style={{display:'flex',justifyContent:'center',padding:40}}><div className="spinner"/></div>
  if (error) return <div style={{color:'var(--danger)',padding:16}}>{error}</div>

  return (
    <div style={{padding:'0 0 80px'}}>
      {/* Inhalt */}
    </div>
  )
}

CSS-Variablen (Kurzreferenz)

--accent: #1D9E75   --accent-dark: #085041   --accent-light: #E1F5EE
--danger: #D85A30
--bg · --surface · --surface2 · --border · --text1 · --text2 · --text3

CSS-Klassen

.card           Weißer Container, border-radius 12px
.btn            Basis-Button
.btn-primary    Grüner Button
.btn-secondary  Grauer Button
.btn-full       100% Breite
.form-input     Eingabefeld
.form-label     Label (klein, uppercase)
.spinner        Ladekreis

Bekannte Fallstricke

dayjs.week() NIEMALS verwenden

// ❌ Falsch:
dayjs(date).week()

// ✅ Richtig (ISO 8601):
const weekNum = (() => {
  const dt = new Date(date)
  dt.setHours(0,0,0,0)
  dt.setDate(dt.getDate()+4-(dt.getDay()||7))
  return Math.ceil(((dt-new Date(dt.getFullYear(),0,1))/86400000+1)/7)
})()

Recharts Bar fill

// ❌ Falsch:
<Bar fill={(entry) => entry.color}/>

// ✅ Richtig:
<Bar fill="#1D9E75"/>