Add Stundenplan frontend scaffolding in studio-v2
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 31s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 2m36s
CI / test-python-agent-core (push) Successful in 20s
CI / test-nodejs-website (push) Successful in 22s

Phase 3 — initial UI for the timetable scheduler:
  - app/stundenplan/page.tsx with tab navigation (Klassen / Lehrer /
    Faecher / Raeume / Zeitraster / Stundentafel / Lehrauftraege /
    Regeln) and a dev-mode JWT entry to authenticate against
    school-service until full auth is wired up.
  - app/stundenplan/_components/KlassenManager.tsx as the working
    prototype for one entity (list / create / delete). Pattern can be
    copied for the other 6 stammdaten + 15 constraint editors.
  - lib/stundenplan/api.ts exposing typed clients for all 22 endpoints
    (7 stammdaten + 15 constraint tables). Constraints use a factory
    to keep the file tight.
  - app/api/school/[...path]/route.ts proxies the browser through
    Next.js to school-service so HTTPS studio-v2 can reach the plain
    HTTP backend.
  - Sidebar.tsx gains a Stundenplan entry with 26-language labels.
  - docker-compose.yml exposes SCHOOL_SERVICE_URL to studio-v2 and
    declares the school-service dependency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-05-21 22:25:18 +02:00
parent 64e7176267
commit f21ecf293b
7 changed files with 757 additions and 0 deletions
+7
View File
@@ -17,6 +17,7 @@ const NAV_LABELS: Record<string, Record<string, string>> = {
nav_eltern: { de: 'Eltern', en: 'Parents', tr: 'Ebeveyn', ar: '\u0627\u0644\u0648\u0627\u0644\u062f\u064a\u0646', uk: '\u0411\u0430\u0442\u044c\u043a\u0438', ru: '\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0438', pl: 'Rodzice', fr: 'Parents', es: 'Padres', it: 'Genitori', pt: 'Pais', nl: 'Ouders', ro: 'Parinti', el: '\u0393\u03bf\u03bd\u03b5\u03af\u03c2', bg: '\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0438', hr: 'Roditelji', cs: 'Rodice', hu: 'Szulok', sv: 'Foraldrar', da: 'Foraeldre', fi: 'Vanhemmat', sk: 'Rodicia', sl: 'Starsi', lt: 'Tevai', lv: 'Vecaki', et: 'Vanemad' },
nav_woerterbuch: { de: 'Woerterbuch', en: 'Dictionary', tr: 'Sozluk', ar: '\u0627\u0644\u0642\u0627\u0645\u0648\u0633', uk: '\u0421\u043b\u043e\u0432\u043d\u0438\u043a', ru: '\u0421\u043b\u043e\u0432\u0430\u0440\u044c', pl: 'Slownik', fr: 'Dictionnaire', es: 'Diccionario', it: 'Dizionario', pt: 'Dicionario', nl: 'Woordenboek', ro: 'Dictionar', el: '\u039b\u03b5\u03be\u03b9\u03ba\u03cc', bg: '\u0420\u0435\u0447\u043d\u0438\u043a', hr: 'Rjecnik', cs: 'Slovnik', hu: 'Szotar', sv: 'Ordbok', da: 'Ordbog', fi: 'Sanakirja', sk: 'Slovnik', sl: 'Slovar', lt: 'Zodynas', lv: 'Vardnica', et: 'Sonaraamat' },
nav_meet: { de: 'Videokonferenz', en: 'Video Call', tr: 'Gorusme', ar: '\u0645\u0643\u0627\u0644\u0645\u0629', uk: '\u0412\u0456\u0434\u0435\u043e\u0434\u0437\u0432\u0456\u043d\u043e\u043a', ru: '\u0412\u0438\u0434\u0435\u043e\u0437\u0432\u043e\u043d\u043e\u043a', pl: 'Wideorozmowa', fr: 'Visioconference', es: 'Videollamada', it: 'Videochiamata', pt: 'Videochamada', nl: 'Videogesprek', ro: 'Videoconferinta', el: '\u0392\u03b9\u03bd\u03c4\u03b5\u03bf\u03ba\u03bb\u03ae\u03c3\u03b7', bg: '\u0412\u0438\u0434\u0435\u043e\u0440\u0430\u0437\u0433\u043e\u0432\u043e\u0440', hr: 'Videopoziv', cs: 'Videohovor', hu: 'Videohivas', sv: 'Videosamtal', da: 'Videoopkald', fi: 'Videopuhelu', sk: 'Videohovor', sl: 'Videoklic', lt: 'Vaizdo skambutis', lv: 'Videozvans', et: 'Videokoone' },
nav_stundenplan: { de: 'Stundenplan', en: 'Timetable', tr: 'Ders Programi', ar: 'جدول حصص', uk: 'Розклад', ru: 'Расписание', pl: 'Plan lekcji', fr: 'Emploi du temps', es: 'Horario', it: 'Orario', pt: 'Horario', nl: 'Rooster', ro: 'Orar', el: 'Πρόγραμμα', bg: 'Разписание', hr: 'Raspored', cs: 'Rozvrh', hu: 'Orarend', sv: 'Schema', da: 'Skema', fi: 'Lukujarjestys', sk: 'Rozvrh', sl: 'Urnik', lt: 'Tvarkarastis', lv: 'Stundu saraksts', et: 'Tunniplaan' },
nav_companion: { de: 'KI-Assistent', en: 'AI Assistant', tr: 'Yapay Zeka', ar: '\u0645\u0633\u0627\u0639\u062f \u0630\u0643\u064a', uk: '\u0428\u0406-\u0430\u0441\u0438\u0441\u0442\u0435\u043d\u0442', ru: '\u0418\u0418-\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043d\u0442', pl: 'Asystent AI', fr: 'Assistant IA', es: 'Asistente IA', it: 'Assistente IA', pt: 'Assistente IA', nl: 'AI-assistent', ro: 'Asistent AI', el: 'AI \u0392\u03bf\u03b7\u03b8\u03cc\u03c2', bg: 'AI \u0430\u0441\u0438\u0441\u0442\u0435\u043d\u0442', hr: 'AI pomoenik', cs: 'AI asistent', hu: 'AI asszisztens', sv: 'AI-assistent', da: 'AI-assistent', fi: 'Tekoalyavustaja', sk: 'AI asistent', sl: 'AI pomoenik', lt: 'DI asistentas', lv: 'MI paligs', et: 'Tehisabiabi' },
}
@@ -105,6 +106,11 @@ export function Sidebar({ selectedTab = 'dashboard', onTabChange }: SidebarProps
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)},
{ id: 'stundenplan', labelKey: 'nav_stundenplan', href: '/stundenplan', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
)},
{ id: 'companion', labelKey: 'nav_companion', href: '/companion', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} />
@@ -151,6 +157,7 @@ export function Sidebar({ selectedTab = 'dashboard', onTabChange }: SidebarProps
if (pathname === '/alerts-b2b') return 'alerts-b2b'
if (pathname === '/messages') return 'messages'
if (pathname?.startsWith('/korrektur')) return 'korrektur'
if (pathname?.startsWith('/stundenplan')) return 'stundenplan'
return selectedTab
}