# 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`).
