Stundenplan Phase 3d: all 15 constraint editors via shared shell
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 30s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 2m38s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 20s
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 30s
CI / test-go-edu-search (push) Successful in 29s
CI / test-python-klausur (push) Failing after 2m38s
CI / test-python-agent-core (push) Successful in 18s
CI / test-nodejs-website (push) Successful in 20s
Backend was already complete in Phase 2; this finishes the UI.
- regeln/_shell.tsx introduces useConstraintCrud (handles list/create/
delete state + reload), ConstraintShell (header, prereq banner,
form toggle, error display, empty/loading/table render), and
useShellStyles for the recurring theme tokens. Each editor now
only carries its schema-specific bits.
- Existing 4 editors (TeacherUnavailableDay/Window, SubjectMax
Consecutive/PreferredPeriod) refactored onto the shell — every
Playwright selector preserved.
- 11 new editors covering the remaining constraint tables:
TeacherMaxHours{Day,Week}, TeacherExcluded{Subject,Room},
Subject{MinDayGap,ContiguousWhenRepeated,DoubleLesson},
Class{MaxHoursDay,NoGaps},
Room{RequiresType,Unavailable}.
- RegelnHub now references all 15 editors directly — no more 'soon'
placeholders. The two duplicate 'Max. Stunden / Tag' entries
(teacher + class) are intentional and disambiguated by group.
Tests:
- e2e/stundenplan.spec.ts: mock routes added for all 11 new constraint
endpoints. RegelnHub suite gains a single test that switches
through 13 uniquely-labelled editors, plus a dedicated test for
the two duplicate 'Max. Stunden / Tag' labels.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,61 +4,91 @@ import { useState } from 'react'
|
||||
import { useTheme } from '@/lib/ThemeContext'
|
||||
import { TeacherUnavailableDayEditor } from './TeacherUnavailableDayEditor'
|
||||
import { TeacherUnavailableWindowEditor } from './TeacherUnavailableWindowEditor'
|
||||
import { TeacherMaxHoursDayEditor, TeacherMaxHoursWeekEditor } from './TeacherMaxHoursEditors'
|
||||
import { TeacherExcludedSubjectEditor, TeacherExcludedRoomEditor } from './TeacherExclusionEditors'
|
||||
import { SubjectMaxConsecutiveEditor } from './SubjectMaxConsecutiveEditor'
|
||||
import { SubjectPreferredPeriodEditor } from './SubjectPreferredPeriodEditor'
|
||||
import {
|
||||
SubjectMinDayGapEditor, SubjectContiguousWhenRepeatedEditor, SubjectDoubleLessonEditor,
|
||||
} from './SubjectSimpleEditors'
|
||||
import { ClassMaxHoursDayEditor, ClassNoGapsEditor } from './ClassEditors'
|
||||
import { RoomRequiresTypeEditor, RoomUnavailableEditor } from './RoomEditors'
|
||||
|
||||
type RuleType =
|
||||
| 'teacher-unavailable-day'
|
||||
| 'teacher-unavailable-window'
|
||||
| 'subject-max-consecutive'
|
||||
| 'subject-preferred-period'
|
||||
| 'teacher-unavailable-day' | 'teacher-unavailable-window'
|
||||
| 'teacher-max-hours-day' | 'teacher-max-hours-week'
|
||||
| 'teacher-excluded-subject' | 'teacher-excluded-room'
|
||||
| 'subject-min-day-gap' | 'subject-max-consecutive'
|
||||
| 'subject-contiguous-when-repeated' | 'subject-preferred-period'
|
||||
| 'subject-double-lesson'
|
||||
| 'class-max-hours-day' | 'class-no-gaps'
|
||||
| 'room-requires-type' | 'room-unavailable'
|
||||
|
||||
interface RuleGroup {
|
||||
group: string
|
||||
rules: { id: RuleType | string; label: string; implemented: boolean }[]
|
||||
rules: { id: RuleType; label: string }[]
|
||||
}
|
||||
|
||||
const RULE_GROUPS: RuleGroup[] = [
|
||||
{
|
||||
group: 'Lehrer',
|
||||
rules: [
|
||||
{ id: 'teacher-unavailable-day', label: 'Tag nicht verfuegbar', implemented: true },
|
||||
{ id: 'teacher-unavailable-window', label: 'Zeitfenster nicht verfuegbar', implemented: true },
|
||||
{ id: 'teacher-max-hours-day', label: 'Max. Stunden / Tag', implemented: false },
|
||||
{ id: 'teacher-max-hours-week', label: 'Max. Stunden / Woche', implemented: false },
|
||||
{ id: 'teacher-excluded-subject', label: 'Fach ausgeschlossen', implemented: false },
|
||||
{ id: 'teacher-excluded-room', label: 'Raum ausgeschlossen', implemented: false },
|
||||
{ id: 'teacher-unavailable-day', label: 'Tag nicht verfuegbar' },
|
||||
{ id: 'teacher-unavailable-window', label: 'Zeitfenster nicht verfuegbar' },
|
||||
{ id: 'teacher-max-hours-day', label: 'Max. Stunden / Tag' },
|
||||
{ id: 'teacher-max-hours-week', label: 'Max. Stunden / Woche' },
|
||||
{ id: 'teacher-excluded-subject', label: 'Fach ausgeschlossen' },
|
||||
{ id: 'teacher-excluded-room', label: 'Raum ausgeschlossen' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Fach',
|
||||
rules: [
|
||||
{ id: 'subject-max-consecutive', label: 'Max. Stunden am Stueck', implemented: true },
|
||||
{ id: 'subject-preferred-period', label: 'Bevorzugter Stunden-Bereich', implemented: true },
|
||||
{ id: 'subject-min-day-gap', label: 'Min. Tagesabstand', implemented: false },
|
||||
{ id: 'subject-contiguous-when-repeated', label: 'Bei Mehrfach: zusammenhaengend', implemented: false },
|
||||
{ id: 'subject-double-lesson', label: 'Doppelstunde bevorzugt', implemented: false },
|
||||
{ id: 'subject-max-consecutive', label: 'Max. Stunden am Stueck' },
|
||||
{ id: 'subject-preferred-period', label: 'Bevorzugter Stunden-Bereich' },
|
||||
{ id: 'subject-min-day-gap', label: 'Min. Tagesabstand' },
|
||||
{ id: 'subject-contiguous-when-repeated', label: 'Bei Mehrfach: zusammenhaengend' },
|
||||
{ id: 'subject-double-lesson', label: 'Doppelstunde bevorzugt' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Klasse',
|
||||
rules: [
|
||||
{ id: 'class-max-hours-day', label: 'Max. Stunden / Tag', implemented: false },
|
||||
{ id: 'class-no-gaps', label: 'Keine Freistunden', implemented: false },
|
||||
{ id: 'class-max-hours-day', label: 'Max. Stunden / Tag' },
|
||||
{ id: 'class-no-gaps', label: 'Keine Freistunden' },
|
||||
],
|
||||
},
|
||||
{
|
||||
group: 'Raum',
|
||||
rules: [
|
||||
{ id: 'room-requires-type', label: 'Fach benoetigt Raumtyp', implemented: false },
|
||||
{ id: 'room-unavailable', label: 'Raum nicht verfuegbar', implemented: false },
|
||||
{ id: 'room-requires-type', label: 'Fach benoetigt Raumtyp' },
|
||||
{ id: 'room-unavailable', label: 'Raum nicht verfuegbar' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const EDITORS: Record<RuleType, React.ComponentType> = {
|
||||
'teacher-unavailable-day': TeacherUnavailableDayEditor,
|
||||
'teacher-unavailable-window': TeacherUnavailableWindowEditor,
|
||||
'teacher-max-hours-day': TeacherMaxHoursDayEditor,
|
||||
'teacher-max-hours-week': TeacherMaxHoursWeekEditor,
|
||||
'teacher-excluded-subject': TeacherExcludedSubjectEditor,
|
||||
'teacher-excluded-room': TeacherExcludedRoomEditor,
|
||||
'subject-min-day-gap': SubjectMinDayGapEditor,
|
||||
'subject-max-consecutive': SubjectMaxConsecutiveEditor,
|
||||
'subject-contiguous-when-repeated': SubjectContiguousWhenRepeatedEditor,
|
||||
'subject-preferred-period': SubjectPreferredPeriodEditor,
|
||||
'subject-double-lesson': SubjectDoubleLessonEditor,
|
||||
'class-max-hours-day': ClassMaxHoursDayEditor,
|
||||
'class-no-gaps': ClassNoGapsEditor,
|
||||
'room-requires-type': RoomRequiresTypeEditor,
|
||||
'room-unavailable': RoomUnavailableEditor,
|
||||
}
|
||||
|
||||
export function RegelnHub() {
|
||||
const { isDark } = useTheme()
|
||||
const [active, setActive] = useState<RuleType>('teacher-unavailable-day')
|
||||
const Editor = EDITORS[active]
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-[260px_1fr] gap-6" data-testid="regeln-hub">
|
||||
@@ -69,24 +99,17 @@ export function RegelnHub() {
|
||||
<div className="space-y-1">
|
||||
{g.rules.map(r => {
|
||||
const isActive = active === r.id
|
||||
const isDone = r.implemented
|
||||
return (
|
||||
<button
|
||||
key={r.id}
|
||||
disabled={!isDone}
|
||||
onClick={() => isDone && setActive(r.id as RuleType)}
|
||||
onClick={() => setActive(r.id)}
|
||||
className={`w-full text-left px-3 py-2 rounded-lg text-sm transition-colors ${
|
||||
isActive
|
||||
? isDark ? 'bg-indigo-500/20 text-white' : 'bg-indigo-100 text-indigo-900'
|
||||
: isDone
|
||||
? isDark ? 'text-white/80 hover:bg-white/10' : 'text-slate-700 hover:bg-slate-100'
|
||||
: isDark ? 'text-white/30 cursor-not-allowed' : 'text-slate-400 cursor-not-allowed'
|
||||
: isDark ? 'text-white/80 hover:bg-white/10' : 'text-slate-700 hover:bg-slate-100'
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-center justify-between gap-2">
|
||||
<span className="truncate">{r.label}</span>
|
||||
{!isDone && <span className="text-xs opacity-60">soon</span>}
|
||||
</span>
|
||||
<span className="truncate">{r.label}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
@@ -96,10 +119,7 @@ export function RegelnHub() {
|
||||
</aside>
|
||||
|
||||
<div>
|
||||
{active === 'teacher-unavailable-day' && <TeacherUnavailableDayEditor />}
|
||||
{active === 'teacher-unavailable-window' && <TeacherUnavailableWindowEditor />}
|
||||
{active === 'subject-max-consecutive' && <SubjectMaxConsecutiveEditor />}
|
||||
{active === 'subject-preferred-period' && <SubjectPreferredPeriodEditor />}
|
||||
<Editor />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user