# P17 · HR-Hub (AK-P17a/b → D-HR) > Interim-UI-Referenz (AD-28) für den HR-Hub. Alle Screens rein präsentational (VM als Prop, Route besitzt > Loader/Fetcher/Actions). **Layout = Davids freie Hand** — unten nur ELEMENTE/DATEN/ACTIONS. `~/ui` ist das > gesetzte Baukasten-Inventar; `data-ui-id`-Marker existieren für Interim-Snapshots. Route-URLs: App-Host, > Ordner `routes/app.$tenant/` → `/{tenant}/hr/…`. ### HrHubPage · D-HR „HR-Hub-Start" (A1/A6) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrHubPage.tsx` - **Route(n):** `/{tenant}/hr` (`routes/app.$tenant/hr._index.tsx`) - **Daten (VM):** kein `@hivegrid`-VM — lokale Props `moduleActive: boolean`, `links: HrHubLink[]` (`{ labelKey, href }`); Loader gated Connector+Permission und liefert die Quick-Link-Liste - **Benötigte UI-Elemente (KEIN Layout):** Card, EmptyState · Icons: FeatherArrowRight, FeatherLock - **Interaktionen (Actions):** keine Callbacks — Links sind `` (Navigations-Kacheln) - **States:** Locked (`!moduleActive` → EmptyState mit Lock) / Empty (leere Link-Liste rendert leere Grid) / Permission-Denied via Loader-Gate (404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte: - [ ] Neue UI/UX-Entscheidungen (Hub-Widgets Ankündigungen/Pflichten/Geburtstage kommen laut Header erst als Folge-Nachzug): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrDirectoryPage · D-HR „MA-Verzeichnis" (A2) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrDirectoryPage.tsx` - **Route(n):** `/{tenant}/hr/directory` (`routes/app.$tenant/hr.directory._index.tsx`) - **Daten (VM):** `HrDirectoryVM` — `packages/view-models/src/hr/hr-directory.vm.ts` (nur nicht-sensible Anzeigefelder; Notfallkontakt NUR im Detail) - **Benötigte UI-Elemente (KEIN Layout):** Avatar, EmptyState, IconButton, Table · Icons: FeatherMail - **Interaktionen (Actions):** keine Action-Callbacks — Name-Link → Detail, `mailto:`-IconButton - **States:** Empty (`rows.length===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte: - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrDirectoryDetailPage · D-HR „MA-Detail" (A3) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrDirectoryDetailPage.tsx` - **Route(n):** `/{tenant}/hr/directory/{userId}` (`routes/app.$tenant/hr.directory.$userId.tsx`) - **Daten (VM):** kein VM-Typ-Import — flache Props (`name`, `email`, `position`, `team`, `isSelf`, `canRevealEmergency`, `canEditEmergency`, `hasEmergencyContact`, `revealedContact`, `revealing`, `saving`, `savingEmergency`); Domain in `hr-directory.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** Breadcrumbs, Button, Card, Dialog, TextInput · Icons: — - **Interaktionen (Actions):** `onSaveProfile({position,team})`, `onRevealEmergency()`, `onSetEmergency({name,phone,relation})` - **States:** Self-Edit vs. Read-only (`isSelf`) / Emergency: kein Kontakt / gespeichert-nicht-enthüllbar / enthüllt / Reveal-Button / Loading via `revealing`/`saving`/`savingEmergency` / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (On-Demand-Reveal Notfallkontakt, serverseitig auditiert): - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrLeavePage · D-HR „Urlaub" (A2 + A4/A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrLeavePage.tsx` - **Route(n):** `/{tenant}/hr/leave` (`routes/app.$tenant/hr.leave._index.tsx`), URL-Tabs `?tab=own|approvals|team` - **Daten (VM):** `HrLeaveOwnVM`, `HrLeaveApproverVM`, `HrVacationRequestKind` — `packages/view-models/src/hr/hr-leave.vm.ts`; plus lokaler `HrLeaveTeamAbsence` - **Benötigte UI-Elemente (KEIN Layout):** Alert, BadgeFeedback, Button, Card, Dialog, EmptyState, Select, Tabs, TextArea, TextInput, `DateField` (tasks) · Icons: FeatherPlus - **Interaktionen (Actions):** `onSubmit({requestKind,approverUserId,startDate,endDate,note})`, `onWithdraw(requestId)`, `onReply(requestId,reply)`, `onApprove(requestId)`, `onReject(requestId)`, `onClarify(requestId,question)` - **States:** Empty je Tab (own/approvals/team → EmptyState) / Loading (`status!=="ready"` → null) / Konflikt-Warnung (Alert bei `hasConflict`) / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Overlap-Konflikt als Inline-Warnung): - [ ] Neue UI/UX-Entscheidungen (Approver-Auswahl aktuell freies TextInput = userId): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrSickPage · D-HR „Krank" (A2 + A4) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrSickPage.tsx` - **Route(n):** `/{tenant}/hr/sick` (`routes/app.$tenant/hr.sick._index.tsx`), Tabs `own|office` - **Daten (VM):** `HrSickLeaveOwnVM`, `HrSickLeaveOfficeVM`, `HrSickLeaveRow` — `packages/view-models/src/hr/hr-sickleave.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Dialog, EmptyState, Tabs, TextInput, `DateField` (tasks) · Icons: FeatherPlus - **Interaktionen (Actions):** `onReport({startDate,expectedReturnDate})`, `onReportFor({employeeUserId,startDate})`, `onAttach(reportId,certificateFileId)`, `onRevealCertificate(reportId)`, `onClose(reportId,returnDate)` - **States:** Empty je Tab (own/office → EmptyState) / AU-Zertifikat nie gerendert, nur Reveal-Action / Loading (Rows via `?? []`) / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Zertifikat sensibel, reveal-on-demand): - [ ] Neue UI/UX-Entscheidungen (Attach = File-ID-TextInput; echte Datei-Auswahl offen): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrRequestsPage · D-HR „Anträge" (A2 + A4/A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrRequestsPage.tsx` - **Route(n):** `/{tenant}/hr/requests` (`routes/app.$tenant/hr.requests._index.tsx`), Tabs `own|approvals` - **Daten (VM):** `HrRequestOwnVM`, `HrRequestApproverVM`, `HrRequestKind` — `packages/view-models/src/hr/hr-request.vm.ts`; plus lokaler `HrRequestTemplateOption` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Dialog, EmptyState, Select, Tabs, TextArea, TextInput · Icons: FeatherPlus - **Interaktionen (Actions):** `onSubmit({templateId,requestKind,approverUserId,title,details})`, `onWithdraw(requestId)`, `onApprove(requestId)`, `onReject(requestId)`, `onClarify(requestId,question)` - **States:** Empty je Tab (own/approvals → EmptyState) / Loading (Rows via `?? []`) / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (optionaler Vorlagen-Prefill): - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrPayslipsPage · D-HR „Lohnabrechnungen" (A2) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrPayslipsPage.tsx` - **Route(n):** `/{tenant}/hr/payslips` (`routes/app.$tenant/hr.payslips._index.tsx`) - **Daten (VM):** `HrPayslipOwnVM` — `packages/view-models/src/hr/hr-payslip.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, EmptyState, Table · Icons: FeatherExternalLink - **Interaktionen (Actions):** `onOpen(payslipId)` (Öffnen serverseitig auditiert, Route öffnet File) - **States:** Empty (`rows.length===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Unread-Badge / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte: - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrMyDocumentsPage · D-HR „Eigene Dokumente" (A2) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrMyDocumentsPage.tsx` - **Route(n):** `/{tenant}/hr/my-documents` (`routes/app.$tenant/hr.my-documents.tsx`) - **Daten (VM):** `HrDocumentListVM`, `HrDocumentKind` — `packages/view-models/src/hr/hr-document.vm.ts`; plus injizierter `upload: HrDocumentUploadState` (idle/uploading/done/error) - **Benötigte UI-Elemente (KEIN Layout):** Alert, BadgeFeedback, Button, Dialog, EmptyState, Select, Table, TextInput; native `input[type=date]` + `input[type=file]` (bewusste Ausnahme) · Icons: FeatherUpload - **Interaktionen (Actions):** `onArchive(documentId)`, `onUpload({file,title,documentKind,validUntilIso})`, `onResetUpload()` - **States:** Empty (`rows.length===0` → EmptyState) / No-Results (Kind-Filter leer → EmptyState `no-results`) / Loading (`status!=="ready"` → leeres Div) / Upload uploading/error (Alert) / Read-only-Badge (Office-Docs) / Expired-Badge / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Kind-Filter client-seitig, Office-Docs read-only): - [ ] Neue UI/UX-Entscheidungen (Upload-Progress-Darstellung): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrAnnouncementsFeedPage · D-HR „Ankündigungen" (A2) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrAnnouncementsFeedPage.tsx` - **Route(n):** `/{tenant}/hr/announcements` (`routes/app.$tenant/hr.announcements._index.tsx`), Filter `?unread=1` - **Daten (VM):** `HrAnnouncementFeedVM` — `packages/view-models/src/hr/hr-announcement.vm.ts` (pinned first) - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Card, EmptyState, LinkButton, Tabs · Icons: FeatherPin - **Interaktionen (Actions):** keine Callbacks — Zeilen-Link → Detail, „Verwalten"-Link (`manageHref`, bei `canManage`) - **States:** Empty (`rows.length===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Ack-Required-Badge / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (pinned zuerst, Unread-Filter): - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrAnnouncementDetailPage · D-HR „Ankündigung Detail" (A3) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrAnnouncementDetailPage.tsx` - **Route(n):** `/{tenant}/hr/announcements/{id}` (`routes/app.$tenant/hr.announcements.$id.tsx`) - **Daten (VM):** `HrAnnouncementDetailVM` — `packages/view-models/src/hr/hr-announcement.vm.ts` (Markdown-Body, Reaktionen, Kommentare 1 Ebene, Read-Receipts) - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Table, TextInput · Icons: — (Emoji-Reaktionen 👍❤️🎉👀 als Literale) - **Interaktionen (Actions):** `onAck()`, `onReact(reaction)`, `onComment(body)` - **States:** Loading (`status!=="ready"` → leeres Div) / Kommentare-Empty (`commentEmptyKey`) / Ack-Zustand acknowledged vs. Button / Read-Receipt-Tabelle nur bei `canManage` / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Markdown wird aktuell `whitespace-pre-wrap` roh gerendert): - [ ] Neue UI/UX-Entscheidungen (Reaktions-Set fix vier Emojis): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrLocationsPage · D-HR „Standorte" (A2 + A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrLocationsPage.tsx` - **Route(n):** `/{tenant}/hr/locations` (`routes/app.$tenant/hr.locations._index.tsx`) - **Daten (VM):** `HrLocationListVM` — `packages/view-models/src/hr/hr-location.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Checkbox, Dialog, EmptyState, Table, TextInput · Icons: FeatherPlus - **Interaktionen (Actions):** `onCreate({name,city,isDefault})`, `onSetDefault(locationId)`, `onArchive(locationId)` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Default-Badge / Aktionen nur bei `canManage` / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte: - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrPasswordSafeListPage · D-HR „Passwort-Safe" (A2 + A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrPasswordSafeListPage.tsx` - **Route(n):** `/{tenant}/hr/password-safe` (`routes/app.$tenant/hr.password-safe._index.tsx`) - **Daten (VM):** `VaultListVM` — `packages/view-models/src/hr/hr-password-safe.vm.ts` (nur zugriffsberechtigte Vaults, keine Secrets) - **Benötigte UI-Elemente (KEIN Layout):** Button, Card, Dialog, EmptyState, TextInput · Icons: FeatherPlus - **Interaktionen (Actions):** `onCreateVault({name,description})` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Create nur bei `canCreate` / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte: - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrVaultDetailPage · D-HR „Passwort-Safe Detail" (A3 + reveal) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrVaultDetailPage.tsx` - **Route(n):** `/{tenant}/hr/password-safe/{vaultId}` (`routes/app.$tenant/hr.password-safe.$vaultId._index.tsx`); Action-only: `…$vaultId.reveal.tsx`, `…$vaultId.export.tsx` - **Daten (VM):** `VaultDetailVM`, `EntryRow` — `packages/view-models/src/hr/hr-password-safe.vm.ts` (nur `hasUsername/hasPassword/hasNotes`-Flags, `copyAutoClearMs`) - **Benötigte UI-Elemente (KEIN Layout):** Breadcrumbs, Button, Card, CopyToClipboardButton, Dialog, EmptyState, LinkButton, Select, Tag_Label, TextInput · Icons: FeatherPlus - **Interaktionen (Actions):** `onReveal(entryId,field): Promise`, `onCreateEntry({title,url,username,password,notes})`, `onArchiveEntry(entryId)`, `onGrantAccess({targetUserId,permissionLevel})` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Secret pro Feld reveal-on-demand → copy → Clipboard-Wipe nach 30s (DoD #3) / Aktionen gated via `canWrite`/`canManageAccess` / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (SECURITY: Werte nur per audited reveal, Clipboard-Auto-Clear): - [ ] Neue UI/UX-Entscheidungen (Copy-Cleared-Hinweis, Zugriff via userId-TextInput): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrSurveysListPage · D-HR „Umfragen" (A2 + A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrSurveysListPage.tsx` - **Route(n):** `/{tenant}/hr/surveys` (`routes/app.$tenant/hr.surveys._index.tsx`) - **Daten (VM):** `HrSurveyListVM` — `packages/view-models/src/hr/hr-survey.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Checkbox, Dialog, EmptyState, LinkButton, Select, TextInput · Icons: FeatherPlus - **Interaktionen (Actions):** `onCreate({title,description,isAnonymous,resultVisibility})`, `onSetStatus(surveyId,"open"|"close")`, `onArchive(surveyId)` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (Rows via `?? []`) / Status-/Anonymous-Badges / Teilnehmen/Ergebnisse/Edit gated (`hasSubmitted`,`canViewResults`,`canManage`) / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte: - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrSurveyParticipationPage · D-HR „Umfrage Teilnahme" (A3/A4) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrSurveyParticipationPage.tsx` - **Route(n):** `/{tenant}/hr/surveys/{id}` (`routes/app.$tenant/hr.surveys.$id.tsx`) - **Daten (VM):** `HrSurveyParticipationVM`, `HrSurveyItem`, `HrSurveyItemType` — `packages/view-models/src/hr/hr-survey.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** Alert, Button, Card, Checkbox, Dialog, Select, TextInput; native `input[type=date]` (sanktionierte Ausnahme) · Icons: FeatherPlus - **Interaktionen (Actions):** `onSubmit(answers: Record)`, `onAddItem({itemType,prompt,options,required})`, `onRemoveItem(itemId)` - **States:** Loading (`status!=="ready"` → leeres Div) / Anonymous-Hinweis (Alert) / bereits abgegeben (Alert success) / offen → Formular / geschlossen (Alert neutral) / Item-Editor nur bei `canManage` / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Item-Typen text/rating_5/nps/choice/boolean/date): - [ ] Neue UI/UX-Entscheidungen (Antwort-Feld-Rendering je Typ): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrSurveyResultsPage · D-HR „Umfrage Ergebnisse" (A3) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrSurveyResultsPage.tsx` - **Route(n):** `/{tenant}/hr/surveys/{id}/results` (`routes/app.$tenant/hr.surveys.$id.results.tsx`) - **Daten (VM):** `HrSurveyResultsVM` — `packages/view-models/src/hr/hr-survey.vm.ts` (Verteilung + Durchschnitt + Freitext) - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Card, EmptyState, Progress · Icons: — - **Interaktionen (Actions):** keine (read-only) - **States:** Empty (`submissionCount===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Anonymous-Badge / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Interim nutzt Progress-Balken; „richer charts" = expliziter Nachzug lt. Header): - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrAdminAnnouncementsPage · D-HR „Ankündigungen verwalten" (A2 + A4/A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrAdminAnnouncementsPage.tsx` - **Route(n):** `/{tenant}/hr/admin/announcements` (`routes/app.$tenant/hr.admin.announcements._index.tsx`) - **Daten (VM):** kein `@hivegrid`-VM — lokale Props `rows: AdminAnnouncementRow[]`, `audiences: string[]` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Checkbox, Dialog, EmptyState, Select, TextArea, TextInput, `DateField` (tasks) · Icons: FeatherPin, FeatherPlus - **Interaktionen (Actions):** `onCreate({title,body,audienceKind,audienceRef,requiresAck,pinned,publishAt,expiresAt})`, `onArchive(announcementId)` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (—, Rows als Prop) / Ack-/Pin-/Audience-Badges / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Audience, Ack, Pin, Schedule/Expiry): - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrAdminPasswordSafePage · D-HR „Passwort-Safe-Verwaltung" (A2/A6) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrAdminPasswordSafePage.tsx` - **Route(n):** `/{tenant}/hr/admin/password-safe` (`routes/app.$tenant/hr.admin.password-safe._index.tsx`) - **Daten (VM):** `AdminVaultListVM` — `packages/view-models/src/hr/hr-password-safe.vm.ts` (alle Tenant-Vaults, Entry-/Access-Counts, NIE Inhalte) - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, EmptyState, Table · Icons: — - **Interaktionen (Actions):** `onArchiveVault(vaultId)` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (`status!=="ready"` → leeres Div) / Active/Archived-Badge / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Oversight ohne Content-Zugriff): - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrAdminPayslipsPage · D-HR „Lohnabrechnungen verwalten" (A2 + A4) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrAdminPayslipsPage.tsx` - **Route(n):** `/{tenant}/hr/admin/payslips` (`routes/app.$tenant/hr.admin.payslips._index.tsx`) - **Daten (VM):** kein `@hivegrid`-VM — lokale Props `rows: PayslipAdminRow[]`, `preview: PayslipPreviewRow[]` (confidence hard/soft/none), `period: {year,month}` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, EmptyState, Table, TextArea, TextInput · Icons: — - **Interaktionen (Actions):** `onMatch({periodYear,periodMonth,files})`, `onConfirm({fileId,employeeUserId,periodYear,periodMonth})` - **States:** Empty (existing `rows.length===0` → EmptyState) / Preview-Block nur bei `preview.length>0` / Loading (Props) / Confidence-Badges / Read/Unread-Badge / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Bulk-Auto-Match-Preview + Per-Row-Confirm): - [ ] Neue UI/UX-Entscheidungen (Files-Input als Freitext-Hint, Assignee als userId-TextInput): - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrAdminRequestTemplatesPage · D-HR „Antragsvorlagen" (A2 + A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrAdminRequestTemplatesPage.tsx` - **Route(n):** `/{tenant}/hr/admin/request-templates` (`routes/app.$tenant/hr.admin.request-templates._index.tsx`) - **Daten (VM):** `HrRequestTemplateListVM`, `HrRequestKind` — `packages/view-models/src/hr/hr-request.vm.ts` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Dialog, EmptyState, Select, TextInput · Icons: FeatherPlus - **Interaktionen (Actions):** `onCreate({requestKind,defaultApproverUserId,title,description})`, `onToggle(templateId,isActive)`, `onArchive(templateId)` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (Rows via `?? []`) / Inactive-Badge / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte: - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: ### HrAdminUploadRemindersPage · D-HR „Upload-Erinnerungen" (A2 + A5) - **Interim-Screen:** `apps/web/app/components/screens/hr/HrAdminUploadRemindersPage.tsx` - **Route(n):** `/{tenant}/hr/admin/upload-reminders` (`routes/app.$tenant/hr.admin.upload-reminders._index.tsx`) - **Daten (VM):** `HrReminderListVM` — `packages/view-models/src/hr/hr-upload-reminder.vm.ts`; `HrReminderCadenceKind` aus `packages/view-models/src/hr/reminder-cadence.ts`; plus Props `documentKinds`, `audiences` - **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Checkbox, Dialog, EmptyState, Select, TextInput, `DateField` (tasks) · Icons: FeatherPlus - **Interaktionen (Actions):** `onCreate({title,documentKind,cadenceKind,intervalMonths,audienceKind,dueAt,mandatory,escalate})`, `onSetStatus(reminderId,"active"|"paused")`, `onArchive(reminderId)` - **States:** Empty (`rows.length===0` → EmptyState) / Loading (Rows via `?? []`) / Status-Badge active/paused / Permission-Denied (Loader/404) - **Davids Delta (offen):** - [ ] Überschriebene/geänderte Spec-Punkte (Cadence once/yearly/every_n_months/on_expiry; Dispatch im Worker-Cron): - [ ] Neue UI/UX-Entscheidungen: - [ ] Subframe-Screen-Link(s): - [ ] Offene Fragen an Dev: