Files
infoscreen/FRONTEND_DESIGN_RULES.md
Olaf b5f5f30005 feat: period-scoped holiday management, archive lifecycle, and docs/release sync
- add period-scoped holiday architecture end-to-end
	- model: scope `SchoolHoliday` to `academic_period_id`
	- migrations: add holiday-period scoping, academic-period archive lifecycle, and merge migration head
	- API: extend holidays with manual CRUD, period validation, duplicate prevention, and overlap merge/conflict handling
	- recurrence: regenerate holiday exceptions using period-scoped holiday sets

- improve frontend settings and holiday workflows
	- bind holiday import/list/manual CRUD to selected academic period
	- show detailed import outcomes (inserted/updated/merged/skipped/conflicts)
	- fix file-picker UX (visible selected filename)
	- align settings controls/dialogs with defined frontend design rules
	- scope appointments/dashboard holiday loading to active period
	- add shared date formatting utility

- strengthen academic period lifecycle handling
	- add archive/restore/delete flow and backend validations/blocker checks
	- extend API client support for lifecycle operations

- release/docs updates and cleanup
	- bump user-facing version to `2026.1.0-alpha.15` with new changelog entry
	- add tech changelog entry for alpha.15 backend changes
	- refactor README to concise index and archive historical implementation docs
	- fix Copilot instruction link diagnostics via local `.github` design-rules reference
2026-03-31 12:25:55 +00:00

329 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Frontend Design Rules
This file is the single source of truth for UI implementation conventions in the dashboard (`dashboard/src/`).
It applies to all feature work, including new pages, settings tabs, dialogs, and management surfaces.
When proposing or implementing frontend changes, follow these rules unless a specific exception is documented below.
This file should be updated whenever a new Syncfusion component is adopted, a color or pattern changes, or an exception is ratified.
---
## 1. Component Library — Syncfusion First
Use Syncfusion components as the default choice for every UI element.
The project uses the Syncfusion Material3 theme, registered globally in `dashboard/src/main.tsx`.
The following CSS packages are imported there and cover all components currently in use:
`base`, `navigations`, `buttons`, `inputs`, `dropdowns`, `popups`, `kanban`, `grids`, `schedule`, `filemanager`, `notifications`, `layouts`, `lists`, `calendars`, `splitbuttons`, `icons`.
When adding a new Syncfusion component, add its CSS import here — and add the new npm package to `optimizeDeps.include` in `vite.config.ts` to avoid Vite import-analysis errors in development.
Use non-Syncfusion elements only when:
- The Syncfusion equivalent does not exist (e.g., native `<input type="file">` for file upload)
- The Syncfusion component would require significantly more code than a simple HTML element for purely read-only or structural content (e.g., `<ul>/<li>` for plain lists)
- A layout-only structure is needed (a wrapper `<div>` for spacing is fine)
**Never** use `window.confirm()` for destructive action confirmations — use `DialogComponent` instead.
`window.confirm()` exists in one place in `dashboard.tsx` (bulk restart) and is considered a deprecated pattern to avoid.
Do not introduce Tailwind utility classes — Tailwind has been removed from the project.
---
## 2. Component Defaults by Purpose
| Purpose | Component | Notes |
|---|---|---|
| Navigation tabs | `TabComponent` + `TabItemDirective` | `heightAdjustMode="Auto"`, controlled with `selectedItem` state |
| Data list or table | `GridComponent` | `allowPaging`, `allowSorting`, custom `template` for status/actions |
| Paginated list | `PagerComponent` | When a full grid is too heavy; default page size 5 or 10 |
| Text input | `TextBoxComponent` | Use `cssClass="e-outline"` on form-heavy sections |
| Numeric input | `NumericTextBoxComponent` | Always set `min`, `max`, `step`, `format` |
| Single select | `DropDownListComponent` | Always set `fields={{ text, value }}`; do **not** add `cssClass="e-outline"` — only `TextBoxComponent` uses outline style |
| Boolean toggle | `CheckBoxComponent` | Use `label` prop, handle via `change` callback |
| Buttons | `ButtonComponent` | See section 4 |
| Modal dialogs | `DialogComponent` | `isModal={true}`, `showCloseIcon={true}`, footer with Cancel + primary |
| Notifications | `ToastComponent` | Positioned `{ X: 'Right', Y: 'Top' }`, 3000ms timeout by default |
| Inline info/error | `MessageComponent` | Use `severity` prop: `'Error'`, `'Warning'`, `'Info'`, `'Success'` |
| Status/role badges | Plain `<span>` with inline style | See section 6 for convention |
| Timeline/schedule | `ScheduleComponent` | Used for resource timeline views; see `ressourcen.tsx` |
| File management | `FileManagerComponent` | Used on the Media page for upload and organisation |
| Drag-drop board | `KanbanComponent` | Used on the Groups page; retain for drag-drop boards |
| User action menu | `DropDownButtonComponent` (`@syncfusion/ej2-react-splitbuttons`) | Used for header user menu; add to `optimizeDeps.include` in `vite.config.ts` |
| File upload | Native `<input type="file">` | No Syncfusion equivalent for raw file input |
---
## 3. Layout and Card Structure
Every settings tab section starts with a `<div style={{ padding: 20 }}>` wrapper.
Content blocks use Syncfusion card classes:
```jsx
<div className="e-card">
<div className="e-card-header">
<div className="e-card-header-caption">
<div className="e-card-header-title">Title</div>
</div>
</div>
<div className="e-card-content">
{/* content */}
</div>
</div>
```
Multiple cards in the same tab section use `style={{ marginBottom: 20 }}` between them.
For full-page views (not inside a settings tab), the top section follows this pattern:
```jsx
<div style={{ marginBottom: 24, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<h2 style={{ margin: 0, fontSize: 24, fontWeight: 600 }}>Page title</h2>
<p style={{ margin: '8px 0 0 0', color: '#6c757d' }}>Subtitle or description</p>
</div>
<ButtonComponent cssClass="e-success" iconCss="e-icons e-plus">New item</ButtonComponent>
</div>
```
---
## 4. Buttons
| Variant | `cssClass` | When to use |
|---|---|---|
| Primary action (save, confirm) | `e-primary` | Main save or confirm in forms and dialogs |
| Create / add new | `e-success` + `iconCss="e-icons e-plus"` | Top-level create action in page header |
| Destructive (delete, archive) | `e-flat e-danger` | Row actions and destructive dialog confirm |
| Secondary / cancel | `e-flat` | Cancel in dialog footer, low-priority options |
| Info / edit | `e-flat e-primary` or `e-flat e-info` | Row-level edit and info actions |
| Outline secondary | `e-outline` | Secondary actions needing a visible border (e.g., preview URL) |
All async action buttons must be `disabled` during the in-flight operation: `disabled={isBusy}`.
Button text must change to indicate the pending state: `Speichere…`, `Erstelle...`, `Archiviere…`, `Lösche...`.
---
## 5. Dialogs
All create, edit, and destructive action dialogs use `DialogComponent`:
- `isModal={true}`
- `showCloseIcon={true}`
- `width="500px"` for forms (wider if tabular data is shown inside)
- `header` prop with specific context text (include item name where applicable)
- `footerTemplate` always has at minimum: Cancel (`e-flat`) + primary action (`e-primary`)
- Dialog body wrapped in `<div style={{ padding: 16 }}>`
- All fields disabled when `formBusy` is true
For destructive confirmations (archive, delete), the dialog body must clearly explain what will happen and whether it is reversible.
For blocked actions, use `MessageComponent` with `severity="Warning"` or `severity="Error"` inside the dialog body to show exact blocker details (e.g., linked event count, recurrence spillover).
---
## 6. Status and Type Badges
Plain `<span>` badges with inline style — no external CSS classes needed:
```jsx
<span style={{
padding: '4px 12px',
borderRadius: '12px',
backgroundColor: color,
color: 'white',
fontSize: '12px',
fontWeight: 500,
display: 'inline-block',
}}>
Label
</span>
```
See section 12 for the fixed color palette.
**Icon conventions**: Use inline SVG or icon font classes for small visual indicators next to text. Established precedents:
- Skip-holidays events render a TentTree icon immediately to the left of the main event-type icon; **always black** (`color: 'black'` or no color override).
- Recurring events rely on Syncfusion's native lower-right recurrence badge — do not add a custom recurrence icon.
**Role badge color mapping** (established in `users.tsx`; apply consistently for any role display):
| Role | Color |
|---|---|
| user | `#6c757d` (neutral gray) |
| editor | `#0d6efd` (info blue) |
| admin | `#28a745` (success green) |
| superadmin | `#dc3545` (danger red) |
---
## 7. Toast Notifications
Use a component-local `ToastComponent` with a `ref`:
```jsx
const toastRef = React.useRef<ToastComponent>(null);
// ...
<ToastComponent ref={toastRef} position={{ X: 'Right', Y: 'Top' }} />
```
Default `timeOut: 3000`. Use `4000` for messages that need more reading time.
CSS class conventions:
- `e-toast-success` — successful operations
- `e-toast-danger` — errors
- `e-toast-warning` — non-critical issues or partial results
- `e-toast-info` — neutral informational messages
---
## 8. Form Fields
All form labels:
```jsx
<label style={{ display: 'block', marginBottom: 8, fontWeight: 500 }}>
Field label *
</label>
```
Help/hint text below a field:
```jsx
<div style={{ fontSize: '12px', color: '#666', marginTop: 4 }}>
Hint text here.
</div>
```
Empty state inside a card:
```jsx
<div style={{ fontSize: '14px', color: '#666' }}>Keine Einträge vorhanden.</div>
```
Vertical spacing between field groups: `marginBottom: 16`.
---
## 9. Tab Structure
Top-level and nested tabs use controlled `selectedItem` state with separate index variables per tab level.
This prevents sub-tab resets when parent state changes.
```jsx
const [academicTabIndex, setAcademicTabIndex] = React.useState(0);
<TabComponent
heightAdjustMode="Auto"
selectedItem={academicTabIndex}
selected={(e: TabSelectedEvent) => setAcademicTabIndex(e.selectedIndex ?? 0)}
>
<TabItemsDirective>
<TabItemDirective header={{ text: '🗂️ Perioden' }} content={AcademicPeriodsContent} />
<TabItemDirective header={{ text: '📥 Import & Liste' }} content={HolidaysImportAndListContent} />
</TabItemsDirective>
</TabComponent>
```
Tab header text uses an emoji prefix followed by a German label, consistent with all existing tabs.
Each nested tab level has its own separate index state variable.
---
## 10. Statistics Summary Cards
Used above grids and lists to show aggregate counts:
```jsx
<div style={{ marginBottom: 24, display: 'flex', gap: 16 }}>
<div className="e-card" style={{ flex: 1, padding: 16 }}>
<div style={{ fontSize: 14, color: '#6c757d', marginBottom: 4 }}>Label</div>
<div style={{ fontSize: 28, fontWeight: 600, color: '#28a745' }}>42</div>
</div>
</div>
```
---
## 11. Inline Warning Messages
For important warnings inside forms or dialogs:
```jsx
<div style={{
padding: 12,
backgroundColor: '#fff3cd',
border: '1px solid #ffc107',
borderRadius: 4,
marginBottom: 16,
fontSize: 14,
}}>
Warning message text here.
</div>
```
For structured in-page errors or access-denied states, use `MessageComponent`:
```jsx
<MessageComponent severity="Error" content="Fehlermeldung" />
```
---
## 12. Color Palette
Only the following colors are used in status and UI elements across the dashboard.
Do not introduce new colors for new components.
| Use | Color |
|---|---|
| Success / active / online | `#28a745` |
| Danger / delete / offline | `#dc3545` |
| Warning / partial | `#f39c12` |
| Info / edit blue | `#0d6efd` |
| Neutral / archived / subtitle | `#6c757d` |
| Help / secondary text | `#666` |
| Inactive/muted | `#868e96` |
| Warning background | `#fff3cd` |
| Warning border | `#ffc107` |
---
## 13. Dedicated CSS Files
Use inline styles for settings tab sections and simpler pages.
Only create a dedicated `.css` file if the component requires complex layout, custom animations, or selector-based styles that are not feasible with inline styles.
Existing precedents: `monitoring.css`, `ressourcen.css`.
Do not use Tailwind — it has been removed from the project.
---
## 14. Loading States
For full-page loading, use a simple centered placeholder:
```jsx
<div style={{ padding: 24 }}>
<div style={{ textAlign: 'center', padding: 40 }}>Lade Daten...</div>
</div>
```
Do not use spinners or animated components unless a Syncfusion component provides them natively (e.g., busy state on `ButtonComponent`).
---
## 15. Locale and Language
All user-facing strings are in German.
Date formatting uses `toLocaleString('de-DE')` or `toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })`.
Never use English strings in labels, buttons, tooltips, or dialog headers visible to the end user.
**UTC time parsing**: The API returns ISO timestamps **without** a `Z` suffix (e.g., `"2025-11-27T20:03:00"`). Always append `Z` before constructing a `Date` to ensure correct UTC interpretation:
```tsx
const utcStr = dateStr.endsWith('Z') ? dateStr : dateStr + 'Z';
const date = new Date(utcStr);
```
When sending dates back to the API, use `date.toISOString()` (already UTC with `Z`).