Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8b6772148 |
No files matched your search
@@ -0,0 +1,101 @@
|
||||
# Interim-UI-Handover → David (AD-28)
|
||||
|
||||
**Stand:** 2026-07-08 · **Quelle:** HiveGrid-Cloud, `apps/web/app/components/screens/` · **Owner:** Akeshan
|
||||
|
||||
## Was ist das?
|
||||
|
||||
Der Dev-Strang hat nach **AD-28** (`.planning/INTERIM-UI-PLAYBOOK.md`) für UI-lastige Tickets **Interim-UI-
|
||||
Vorschau-Screens** gebaut — lauffähige `<X>Page`-Komponenten aus `~/ui` (VM als Prop), damit Backend/Loader/
|
||||
Action getestet sind, **bevor** deine finalen Subframe-Screens (`D-*`) stehen. Dieses Verzeichnis ist die
|
||||
Übergabe dieser Interim-Screens an dich, damit dein Agent jeden Screen dem richtigen `D-*`-Paket + Archetyp
|
||||
zuordnen und die finale Optik bauen kann.
|
||||
|
||||
**Zwei Dinge sind wichtig:**
|
||||
1. **Layout ist deine freie Hand** (Designer-First). Die Detailseiten listen nur *benötigte Elemente / Daten /
|
||||
Aktionen / States* — **keine** Anordnung. Du überschreibst Spec/UX in der „Davids Delta"-Sektion je Screen.
|
||||
2. Die Interim-Screens sind **lauffähige Referenz**: die `.tsx`-Pfade zeigen in den gebundelten Checkout unter
|
||||
`03 Quellen und Referenzen/hivegrid-cloud/` — du kannst jeden Screen real anschauen. Die stabile Grenze ist
|
||||
das **View-Model (VM) + der Loader**; dein Subframe-Screen ersetzt nur das `<X>Page`-Innere (Component-Swap,
|
||||
kein Neubau). Danach: `🎨 design-nachzug` in der Ticket-Doc auf `erledigt`.
|
||||
|
||||
## Domänen-Übersicht
|
||||
|
||||
| Seite | Domäne | Bau-Ticket | D-*-Ziel | Screens | Status |
|
||||
|---|---|---|---|---|---|
|
||||
| [P06-CRM](P06-CRM.md) | CRM-Kern | R-P6 | D-CRM | 6 | offen |
|
||||
| [P07-Leads](P07-Leads.md) | Lead-Inbox | R-P7 | D-P7 | 6 | offen |
|
||||
| [P17-HR](P17-HR.md) | HR-Hub | AK-P17a/b | D-HR | 21 | offen |
|
||||
| [P04-Files-Tasks](P04-Files-Tasks.md) | Files & Tasks | AK-P4a/b | D-P4 | 6 | offen |
|
||||
| [P03-Audit-Notifications](P03-Audit-Notifications.md) | Audit & Notifications | AK-P3 | D-P3 | 5 | offen |
|
||||
| [P05-Integrations](P05-Integrations.md) | Integrations/Apps-Engine | R-P5 | D-P5 | 6 | offen |
|
||||
| [P02-Auth-RBAC](P02-Auth-RBAC.md) | Auth & RBAC | R-P2a/b | D-P2 | 5 | ✅ David fertig (nur Abgleich) |
|
||||
|
||||
**Gesamt: 55 Interim-Screens** über 10 Domänen-Ordner. Archetyp-Codes (A1–A7) stammen aus den Header-
|
||||
Kommentaren der Interim-Screens bzw. der `D-*`-Screen-Tabelle; bei Unklarheit gilt deine `D0`-Archetypen-Doku.
|
||||
|
||||
## Screen-Index (alle 55)
|
||||
|
||||
| Screen | D-*-Ziel | Archetyp | Route | Status |
|
||||
|---|---|---|---|---|
|
||||
| KontaktListPage | D-CRM | A2 | /app/:tenant/contacts (+ /contacts/companies) | offen |
|
||||
| KontaktDetailPage | D-CRM | A3 | /app/:tenant/contacts/:id (+ /contacts/companies/:id) | offen |
|
||||
| KontaktNewPage | D-CRM | A4 | /app/:tenant/contacts/new | offen |
|
||||
| CustomFieldAdminPage | D-CRM | A2 | /app/:tenant/contacts/admin/custom-fields | offen |
|
||||
| TagAdminPage | D-CRM | A2 | /app/:tenant/contacts/admin/tags | offen |
|
||||
| DuplicateToolPage | D-CRM | A5 | /app/:tenant/contacts/admin/duplicates | offen |
|
||||
| LeadInboxPage | D-P7 | A2 | /:tenant/leads | offen |
|
||||
| LeadDetailPage | D-P7 | A3 | /:tenant/leads/:id | offen |
|
||||
| LeadNewPage | D-P7 | A4 | /:tenant/leads/new | offen |
|
||||
| LeadStatisticsPage | D-P7 | A2 | /:tenant/leads/statistics | offen |
|
||||
| LeadSourcesAdminPage | D-P7 | A6 | /:tenant/admin/lead-sources | offen |
|
||||
| LeadAntiSpamAdminPage | D-P7 | A6 | /:tenant/admin/lead-anti-spam | offen |
|
||||
| HrHubPage | D-HR | A1/A6 | /:tenant/hr | offen |
|
||||
| HrDirectoryPage | D-HR | A2 | /:tenant/hr/directory | offen |
|
||||
| HrDirectoryDetailPage | D-HR | A3 | /:tenant/hr/directory/:userId | offen |
|
||||
| HrLeavePage | D-HR | A2+A4/A5 | /:tenant/hr/leave | offen |
|
||||
| HrSickPage | D-HR | A2+A4 | /:tenant/hr/sick | offen |
|
||||
| HrRequestsPage | D-HR | A2+A4/A5 | /:tenant/hr/requests | offen |
|
||||
| HrPayslipsPage | D-HR | A2 | /:tenant/hr/payslips | offen |
|
||||
| HrMyDocumentsPage | D-HR | A2 | /:tenant/hr/my-documents | offen |
|
||||
| HrAnnouncementsFeedPage | D-HR | A2 | /:tenant/hr/announcements | offen |
|
||||
| HrAnnouncementDetailPage | D-HR | A3 | /:tenant/hr/announcements/:id | offen |
|
||||
| HrLocationsPage | D-HR | A2+A5 | /:tenant/hr/locations | offen |
|
||||
| HrPasswordSafeListPage | D-HR | A2+A5 | /:tenant/hr/password-safe | offen |
|
||||
| HrVaultDetailPage | D-HR | A3+reveal | /:tenant/hr/password-safe/:vaultId | offen |
|
||||
| HrSurveysListPage | D-HR | A2+A5 | /:tenant/hr/surveys | offen |
|
||||
| HrSurveyParticipationPage | D-HR | A3/A4 | /:tenant/hr/surveys/:id | offen |
|
||||
| HrSurveyResultsPage | D-HR | A3 | /:tenant/hr/surveys/:id/results | offen |
|
||||
| HrAdminAnnouncementsPage | D-HR | A2+A4/A5 | /:tenant/hr/admin/announcements | offen |
|
||||
| HrAdminPasswordSafePage | D-HR | A2/A6 | /:tenant/hr/admin/password-safe | offen |
|
||||
| HrAdminPayslipsPage | D-HR | A2+A4 | /:tenant/hr/admin/payslips | offen |
|
||||
| HrAdminRequestTemplatesPage | D-HR | A2+A5 | /:tenant/hr/admin/request-templates | offen |
|
||||
| HrAdminUploadRemindersPage | D-HR | A2+A5 | /:tenant/hr/admin/upload-reminders | offen |
|
||||
| FilesBrowserPage | D-P4 | A2 | /:tenant/files | offen |
|
||||
| FileUploadPage | D-P4 | A4 | /:tenant/files/upload | offen |
|
||||
| FileDetailPage | D-P4 | A3 | /:tenant/files/:id | offen |
|
||||
| TasksListPage | D-P4 | A2 | /:tenant/tasks | offen |
|
||||
| TaskDetailPage | D-P4 | A3+A5 | /:tenant/tasks/:id | offen |
|
||||
| TaskNewPage | D-P4 | A5 | /:tenant/tasks/new | offen |
|
||||
| AuditListPage | D-P3 | A2 | /admin/audit | offen |
|
||||
| AuditExportPage | D-P3 | A2 | /admin/audit-export | offen |
|
||||
| NotificationCenterPage | D-P3 | A2 | /app/:tenant/notifications | offen |
|
||||
| NotificationsDrawer | D-P3 | A2 | n/a (globaler Drawer) | offen |
|
||||
| NotificationSettingsPage | D-P3 | A6 | /app/:tenant/settings/notifications | offen |
|
||||
| IntegrationsCatalogPage | D-P5 | A6 | /:tenant/integrations | offen |
|
||||
| ConnectionDetailPage | D-P5 | A6+A2 | /:tenant/integrations/:app | offen |
|
||||
| ConnectionCard | D-P5 | (sub) | n/a (Sub von ConnectionDetailPage) | offen |
|
||||
| ConnectWizardDialog | D-P5 | A5+A6 | n/a (Sub von ConnectionDetailPage) | offen |
|
||||
| SyncHistoryPage | D-P5 | A2 | /:tenant/integrations/:app/:connectionId/syncs | offen |
|
||||
| SyncRunDetailPage | D-P5 | A3 | /:tenant/integrations/:app/:connectionId/syncs/:runId | offen |
|
||||
| CreateWorkspacePage | D-P2 | A3 | /new-account | David fertig |
|
||||
| MembersPage | D-P2 | A1 | /app/:tenant/settings/members | David fertig |
|
||||
| RolesPage | D-P2 | A1 | /app/:tenant/settings/roles-and-permissions/roles | David fertig |
|
||||
| InviteMemberForm | D-P2 | A4 | n/a (Sub von MembersPage) | David fertig |
|
||||
| GroupMembersDialog | D-P2 | A4 | n/a (Sub von GroupsRoute) | David fertig |
|
||||
|
||||
## Verweise
|
||||
|
||||
- Interim-UI-Regeln: `03 Quellen und Referenzen/hivegrid-cloud/.planning/INTERIM-UI-PLAYBOOK.md`
|
||||
- Design-Standards (Spacing/Dialog/Archetypen): `.../hivegrid-cloud/.planning/INTERIM-UI-DESIGN-STANDARDS.md`
|
||||
- Phasen-Briefs (dein bisheriger Intake, jetzt mit Pointer hierher): `.../hivegrid-cloud/.planning/designer-briefs/PXX-*.md`
|
||||
- Getrackte offene Design-Nachzüge im Dev-Repo: `grep -rl "design-nachzug: offen" .planning/ticket-docs/`
|
||||
@@ -0,0 +1,53 @@
|
||||
# P02 · Auth & RBAC (R-P2a/b → D-P2)
|
||||
|
||||
> ℹ️ David-Delta liegt vor (`Tickets/Fertige Tickets/D-P2 Auth Wireframes.md`) — diese Seite dient dem
|
||||
> Konsistenz-Abgleich, **kein neuer Design-Request**. Interim-UI-Referenz (AD-28), `~/ui`-komponiert, VM als Prop.
|
||||
|
||||
## Auth
|
||||
|
||||
### CreateWorkspacePage · D-P2 „Create-workspace (ohne Tenant)" · A3 Formular/Auth-Shell (kein A-Code im Header, inferiert)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/auth/CreateWorkspacePage.tsx`
|
||||
- **Route(n):** `/new-account` (`routes/new-account.tsx`) — nicht unter `app.$tenant/` (Pre-Tenant-Screen)
|
||||
- **Daten (VM):** keine VM — reine Props `{ csrf, error?, backHref }` (route-injiziert; server-driven `/new-account`-Action erstellt den Tenant)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** `~/ui`: Alert, Button, LinkButton, TextInput · Shell: `~/components/auth/AuthShell` (nicht `~/ui`) · Icons: FeatherArrowLeft
|
||||
- **Interaktionen (Actions):** keine `…Actions` — natives `<form method="post">` postet `name` + `csrf` (+ vestigiales `action=create`); Back-Link → `backHref`
|
||||
- **States:** Error-Alert (nur idle-gated, `error`-Prop) · kein Empty/Loading · Permission-Denied n/a (Login-gated im Loader)
|
||||
- **Davids Delta:** ✅ vorhanden (D-P2) — nur Abgleich
|
||||
|
||||
## Users / RBAC
|
||||
|
||||
### MembersPage · D-P2 „WF-P2 Admin RBAC" S01 Mitarbeiterliste (+ D01/M03/V01) · A1 Liste/Tabelle (kein A-Code im Header, inferiert)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/users/MembersPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/settings/members` (`routes/app.$tenant/settings/members.tsx`)
|
||||
- **Daten (VM):** `MembersListVM` — `packages/view-models/src/users/members-list.vm.ts` (+ Helfer `filterMembersRows`, `EMPTY_MEMBERS_FILTER`, Typen `MembersListRow`/`MembersListFilterState`/`MemberStatus`)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** `~/ui`: Avatar, BadgeFeedback, Button, Checkbox, Dialog, DropdownMenu, EmptyState, IconButton, SearchInput, Select, Table, Tabs · Icons: FeatherChevronDown, FeatherMoreHorizontal, FeatherPlus, FeatherTrash, FeatherUserCheck, FeatherUserX (+ `SubframeCore.DropdownMenu`)
|
||||
- **Interaktionen (Actions):** `MembersActions` — onToggleRole({userId, roleId, add}), onDeactivate({userId}), onReactivate({userId}), onDeleteInvitation({invitationId}), onInvite()
|
||||
- **States:** Empty (`EmptyState`, `emptyHeadlineKey`/`emptyBodyKey` wenn keine Rows/Invites) · No-matches (`EmptyState`, `noMatchesKey`) · Loading (`vm.status !== "ready"` → leeres Gerüst) · M03 Deaktivieren-Confirm-Dialog · V01 Last-Admin-Blocker-Dialog · Permission-Denied → 404 im Loader (`verifyUserHasPermission`)
|
||||
- **Davids Delta:** ✅ vorhanden (D-P2) — nur Abgleich
|
||||
|
||||
### RolesPage · D-P2 „WF-P2 Admin RBAC" S02 Rollen & Berechtigungen (+ M04A/M04B/M02/D03) · A1 Liste/Tabelle + Editor-Dialog (kein A-Code im Header, inferiert)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/users/RolesPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/settings/roles-and-permissions/roles` (`routes/app.$tenant/settings/roles-and-permissions/roles.tsx`)
|
||||
- **Daten (VM):** `RolesListVM` — `packages/view-models/src/users/roles-list.vm.ts` (+ Editor-VM `buildRoleEditorVM`/`filterRoleEditorSections`/`togglePermission` + Typ `RoleEditorInputPermission` aus `packages/view-models/src/users/role-editor.vm.ts`; Helfer `filterRolesRows`, `EMPTY_ROLES_FILTER`, Typen `RolesListRow`/`RolesListFilterState`/`RoleTypeKey`)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** `~/ui`: Alert, Button, Dialog, DropdownMenu, EmptyState, IconButton, SearchInput, Select, Switch, Table, Tabs, TextArea, TextInput · Icons: FeatherCopy, FeatherEdit2, FeatherMoreHorizontal, FeatherPlus, FeatherTrash (+ `SubframeCore.DropdownMenu`)
|
||||
- **Interaktionen (Actions):** `RolesActions` — onCreate({name, description, permissionNames}), onUpdate(roleId, {name, description, permissionNames}), onDelete(roleId)
|
||||
- **States:** Empty (`EmptyState`, `emptyHeadlineKey`/`emptyBodyKey` wenn keine Rollen) · No-matches (`EmptyState variant="no-results"`, `noMatchesKey`) · Loading (`vm.status !== "ready"` → leeres Gerüst) · M02-Editor No-matches (`EmptyState`) · D03 Delete-Blocker (`deleteBlockedInUse`) · Permission-Denied → 404 im Loader (`requireWorkspaceRolesBuilderAccess`)
|
||||
- **Davids Delta:** ✅ vorhanden (D-P2) — nur Abgleich
|
||||
|
||||
### InviteMemberForm · D-P2 „WF-P2 Admin RBAC" M01 „Mitarbeiter einladen" · A4 Dialog-Formular (kein A-Code im Header, inferiert)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/users/InviteMemberForm.tsx`
|
||||
- **Route(n):** n/a — Sub-Komponente von `MembersSettingsRoute` (rendert im Outlet-Dialog; postet an `routes/app.$tenant/settings/members/new.tsx`)
|
||||
- **Daten (VM):** keine VM — nutzt `NewMemberActionData` aus `~/routes/app.$tenant/settings/members/new` (via `useActionData`) + `useRootData` (csrf)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** `~/ui`: Button, Checkbox, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** keine `…Actions` — `<Form method="post">` an `members/new` (Felder `email`, `first-name`, `last-name`, `send-invitation-email`, `csrf`); Prop-Callback `onClose()`
|
||||
- **States:** Error-Toast (`actionData.error`) · Loading (`navigation.state === "submitting"` → Submit-Button loading) · kein Empty · Permission-Denied via Parent-Route
|
||||
- **Davids Delta:** ✅ vorhanden (D-P2) — nur Abgleich
|
||||
|
||||
### GroupMembersDialog · D-P2 „WF-P2 Admin RBAC" Gruppen-Bulk (RBAC V2, offener design-nachzug) · A4 Dialog (kein A-Code im Header, inferiert)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/users/GroupMembersDialog.tsx`
|
||||
- **Route(n):** n/a — Sub-Komponente von `GroupsRoute` (`routes/app.$tenant/settings/groups.tsx`, URL `/app/:tenant/settings/groups`; postet Bulk an `settings/groups/:id`)
|
||||
- **Daten (VM):** `GroupsDetailVM` — `packages/view-models/src/users/groups-detail.vm.ts` (+ Helfer `filterGroupMemberRows`, Typ `GroupMemberRow`)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** `~/ui`: Avatar, Button, Checkbox, Dialog, EmptyState, SearchInput · Icons: keine
|
||||
- **Interaktionen (Actions):** `GroupMembersActions` — onAdd(userIds: string[]), onRemove(userIds: string[]); Prop-Callback `onClose()`
|
||||
- **States:** Loading (`vm.status !== "ready"` → `null`) · Empty pro Pick-Liste (`EmptyState variant="no-results"`, `emptyMembersKey`/`emptyCandidatesKey`) · Permission-Gate: Add/Remove-Buttons nur bei `data.canManage`
|
||||
- **Davids Delta:** ✅ vorhanden (D-P2) — nur Abgleich
|
||||
@@ -0,0 +1,74 @@
|
||||
# P03 · Audit & Notifications (AK-P3 → D-P3)
|
||||
|
||||
> Interim-UI-Referenz (AD-28). Beide Domänen sind AK-P3-Interim-Screens, rein aus `~/ui` komponiert, VM als
|
||||
> Prop. **Layout = Davids freie Hand** — Listen nennen nur Elemente/Daten/Aktionen/States. Keine der fünf
|
||||
> Dateien importiert `@subframe/core`/Icons; alle Affordances aus `~/ui`-Primitives.
|
||||
|
||||
## Audit
|
||||
|
||||
### AuditListPage · Audit-Viewer (A2 + Drawer)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/audit/AuditListPage.tsx`
|
||||
- **Route(n):** `/admin/audit` (`routes/admin/audit.tsx`) — Admin-Host, nicht `app.$tenant`. Permission-Gate `admin.auditTrails.view`.
|
||||
- **Daten (VM):** `AuditListVM` (Rows/Filter über `AuditListFilterState`, `AuditListRow`) — `packages/view-models/src/audit/audit-list.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Alert · Button · Drawer · EmptyState · Select (+ Select.Item) · Table (HeaderRow/HeaderCell/Row/Cell) · Tag_Label · TextInput (+ TextInput.Input) · DateField (lokal `../tasks/DateField`). Inhaltselemente: Header (Titel + Subtitle + Export-CTA nur bei `canExport`), Filter-Bar (from/to Datum, Actor-Type-Select, Outcome-Select, Action-Text, TargetType-Text, Apply-Button), Tabelle (Zeit/Actor+Kind-Tag/Action monospace/Subjekt/Outcome/Summary + Detail-Button bei `hasDetail`), Server-Pagination (pageInfo + prev/next), Detail-Drawer (Feld-für-Feld-Diff-Tabelle mit „masked"→Tag „Wert geändert", Metadata `<pre>`, leer→Alert). Icons: keine.
|
||||
- **Interaktionen (Actions):** `onApply(query: AuditListQuery)` — `AuditListQuery = AuditListFilterState & { page: number }`; getriggert von Filter-Submit, Select-onValueChange, prev/next. Detail-Drawer öffnet rein lokal (`useState openRowId`), kein Action-Callback.
|
||||
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Empty = `rows.length === 0` → EmptyState (`emptyHeadlineKey`/`emptyBodyKey`) · Drawer-Empty = kein Diff/Metadata → Alert · Permission-Denied = Route-Level 404 (`verifyUserHasPermission`), kein In-Component-403.
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte: Quick-Filter (Klick auf Actor/Subject/Action) und „Verlauf dieses Objekts"/„Ähnliche Einträge" aus D-P3-Zeile sind im Interim NICHT gebaut — nur Formular-Filter + Diff-Drawer.
|
||||
- [ ] Neue UI/UX-Entscheidungen: visuelle Unterscheidung der Actor-Kinds (User/Automation/MCP/Agent/Admin) — interim nur neutrales Tag_Label.
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev: Sollen Quick-Filter denselben `onApply` nutzen (Patch je Feld)?
|
||||
|
||||
### AuditExportPage · Audit-Export-Jobs (A2)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/audit/AuditExportPage.tsx`
|
||||
- **Route(n):** `/admin/audit-export` (`routes/admin/audit-export.tsx`) — Admin-Host. (Tenant-Variante `app.$tenant/settings/audit-export.tsx` = `/app/:tenant/settings/audit-export` rendert eigenes Inline-UI, NICHT diesen Interim-Screen.)
|
||||
- **Daten (VM):** `AuditExportVM` (+ `AuditExportResultVM` als `result`-Prop) — `packages/view-models/src/audit/audit-export.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Alert · Button · Select (+ Select.Item, Helper `SelectWithAny`) · TextInput (+ TextInput.Input) · DateField (lokal `../tasks/DateField`). Inhaltselemente: Header (Titel + Beschreibung mit `maxRows`), ein Formular mit drei Submit-Pfaden (GET csv / GET pdf / POST storage-export), Filterfelder (from/to, tenantId optional, regulatoryScope/actorType/outcome je Select-mit-„Any", purpose Pflicht-Textfeld, format Select), Ergebnis-Panel (Erfolg/Warn-Alert mit Download-Link + Integritäts-Warnung, oder Error-Alert). Icons: keine.
|
||||
- **Interaktionen (Actions):** KEIN `…Actions`-Typ — reine HTML-Form-Submits gegen die Route (GET streamt CSV/PDF, POST läuft Storage-Export). Einzige Prop neben `vm` ist `result` (Aktions-Ergebnis, null = noch nichts abgeschickt).
|
||||
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Ergebnis: `result.ok===false` → Error-Alert (`errorKey`); `result.ok` → Success/Warning-Alert (Row-Count, Ablauftage, Download-Link, ggf. Integritäts-Warnung); `result===null` → kein Panel · Kein Empty-State · Permission-Denied = Route-Level 404.
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte: D-P3 sieht Export-Job-**Liste** + `/new` + `/:id` (A2) vor — Interim ist ein Single-Formular ohne Job-Liste/Detail-Route.
|
||||
- [ ] Neue UI/UX-Entscheidungen: Zweck-Pflichtfeld (`purpose`) Prominenz; Format-Auswahl CSV/PDF vs. Spec CSV/NDJSON.
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev: Job-Liste/`:id`-Status als eigene D-P3-Screens nachziehen oder Single-Form-Ansatz beibehalten?
|
||||
|
||||
## Notifications
|
||||
|
||||
### NotificationCenterPage · Notification-Center (A2 + Drawer)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/notifications/NotificationCenterPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/notifications` (`routes/app.$tenant/notifications.tsx`) — Route hostet SSE-Subscription (`useNotificationsStream`).
|
||||
- **Daten (VM):** `NotificationCenterVM` (Items über `NotificationCenterItemVM`) — `packages/view-models/src/notifications/notification-center.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Alert · BadgeFeedback · Button · EmptyState · Tabs (+ Tabs.Item). Inhaltselemente: Header (Titel + Diagnostics-Subtitle + sr-only Live-Region für `connectionStatusKey`), Tabs Alle/Ungelesen mit Counts, Bulk-Clear-Button (nur bei `unreadCount>0`), Notification-Cards (Message als Deeplink-Link oder Text, Kategorie·Channel·Zeit-Metazeile, Status-Badge farbcodiert READ/FAILED/PENDING, Bounce-Alert bei `bounceKey` mit `lastErrorRedacted`, Mark-Read-Button bei ungelesen). Icons: keine.
|
||||
- **Interaktionen (Actions):** `onMarkRead(deliveryId: string)` · `onMarkAllRead()`. Tab-Umschaltung Alle/Ungelesen ist lokal (`useState`).
|
||||
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Empty = `totalCount===0` → EmptyState (`emptyHeadlineKey`/`emptyBodyKey`) · Unread-Empty = gefilterte Liste leer → EmptyState (`unreadEmptyKey`) · Permission-Denied = Route-Level (Auth-Middleware), kein In-Component-403.
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte: D-P3 nennt Prioritäts-Marker + Mark-**unread** — Interim hat nur Mark-read (kein Priority-Marker, kein Unread-Zurücksetzen).
|
||||
- [ ] Neue UI/UX-Entscheidungen: Bounce-Klartext-Darstellung („Emails an dich kommen aktuell nicht an") als Error-Alert je Card.
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev: Priority-Marker-Datenfeld im VM ergänzen?
|
||||
|
||||
### NotificationsDrawer · Notification-Center Topbar-Drawer (A2 + Drawer)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/notifications/NotificationsDrawer.tsx`
|
||||
- **Route(n):** n/a — Shell-Level-Komponente (globaler Drawer, gehostet von `apps/web/app/components/layouts/NavBar.tsx`; NavBar besitzt SSE-Subscription + Open-State).
|
||||
- **Daten (VM):** `NotificationCenterItemVM` (kein eigener VM; Items als Prop, max 10) — `packages/view-models/src/notifications/notification-center.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback · Button · Drawer (+ Drawer.Content) · EmptyState. Inhaltselemente: Header (Titel + Close-Button), Item-Liste (max 10; Message als Deeplink oder Text, ungelesen fett, Zeitstempel, „Ungelesen"-Badge bei nicht gelesen), „Alle anzeigen"-Link (`allHref`). Icons: keine.
|
||||
- **Interaktionen (Actions):** KEIN `…Actions`-Typ — Props `open`, `items`, `allHref`, `onClose()`. Deeplink-Klicks folgen validierten `deeplinkHref`; „Alle anzeigen" navigiert zu `allHref`.
|
||||
- **States:** Empty = `visibleItems.length===0` → EmptyState (`drawer.empty` + `emptyHelper`) · kein eigenes Loading/Permission-Denied (Shell steuert Sichtbarkeit).
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte: kein Mark-read/Bulk-Clear im Drawer (nur „Alle anzeigen" → Center).
|
||||
- [ ] Neue UI/UX-Entscheidungen: Verhältnis Bell-Slot (D-D0a) ↔ Drawer-Öffnung; 10-Item-Cap.
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev: Soll der Drawer Inline-Mark-read bekommen oder bewusst read-only bleiben?
|
||||
|
||||
### NotificationSettingsPage · Notification-Settings (A6)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/notifications/NotificationSettingsPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/settings/notifications` (`routes/app.$tenant/settings/notifications.tsx`) — Actions fetcher-backed.
|
||||
- **Daten (VM):** `NotificationSettingsVM` (Channel-Typ `NotificationSettingChannel`) — `packages/view-models/src/notifications/notification-settings.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Alert · Switch (+ lokaler `ChannelToggle`-Wrapper). Inhaltselemente: Header (Heading + Description), Save-State-Alert, Master-Karte „alle Emails aus" (Switch), pro Modul-Gruppe eine Karte mit Modul-Label + je Kategorie In-App/Email-Toggle. Icons: keine.
|
||||
- **Interaktionen (Actions):** `onTogglePreference({ category, channel, enabled })` · `onSetEmailMaster(disableAllEmails: boolean)` (Master-Switch schreibt alle Kategorien). Zusätzliche Prop `saveState: { ok; errorText? } | null`.
|
||||
- **States:** Loading = `vm.status !== "ready"` → leerer Marker-Div · Save = `saveState.ok` → Success-Alert / sonst Error-Alert (`errorText`) · `saveState===null` → nichts · kein Empty-State (Registry liefert immer Gruppen) · Permission-Denied = Route-Level.
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte: D-P3 nennt Priority-Selektor je Event-Typ — Interim hat nur In-App/Email-Toggles, keinen Priority-Selektor.
|
||||
- [ ] Neue UI/UX-Entscheidungen: Master-Toggle-Klartext „betrifft auch zukünftige Event-Typen".
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev: Priority pro Kategorie im VM/Registry ergänzen?
|
||||
@@ -0,0 +1,87 @@
|
||||
# P04 · Files & Tasks (AK-P4a/b → D-P4)
|
||||
|
||||
> Interim-UI-Referenz (AD-28) für zwei Domänen (Dev-DoD 100 %, `🎨 design-nachzug: offen`). Alle Screens aus
|
||||
> `~/ui` komponiert, VM als Prop, Backend/Loader/Action stabil. **Layout = Davids freie Hand.** Routen liegen
|
||||
> unter `apps/web/app/routes/app.$tenant/`, laufen real unter `/:tenant/<route>` (Kurzform wie in D-P4).
|
||||
|
||||
## Files
|
||||
|
||||
### FilesBrowserPage · Files-Browser [`/files`] (A2)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/files/FilesBrowserPage.tsx`
|
||||
- **Route(n):** `/files` (`routes/app.$tenant/files._index.tsx`)
|
||||
- **Daten (VM):** `FileBrowserVM` — `packages/view-models/src/files/file-browser.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** ToggleGroup (Table/Grid-Umschalter), Button, Select (MIME/Entität/Tag/Verantwortlicher-Filter), Checkbox (Archiv-Toggle + Row-/Select-All), Table (+ HeaderRow/HeaderCell/Row/Cell), Card (Grid-Kacheln mit Thumbnail), BadgeFeedback (Preview-/Scan-Status), Tag_Label (Tags), plus native `<form>`-Bulk-ZIP-Leiste · Icons: FeatherList, FeatherGrid, FeatherUpload, FeatherDownload, FeatherFile
|
||||
- **Interaktionen (Actions):** `onApply(query: FilesBrowserQuery)` (server-side Navigation: mimeGroup/entityType/tag/assignedToUserId/includeArchived/viewMode), `onToggleSelect(id)`, `onToggleSelectAll(ids, selectAll)`; zusätzlich Upload-CTA-Link (`uploadHref`), Cursor-Pagination-Link (`nextHref`), Bulk-ZIP-POST (`bulkZipAction`, `selectedIds`), Row-Detail-Links
|
||||
- **States:** Empty (`emptyHeadlineKey`/`emptyBodyKey` + optional Upload-CTA) · Loading/not-ready (leerer Marker `ui.files.browser`) · Permission-Denied = 404 (Loader/Guard, kein UI-State); Upload-CTA nur bei `canWrite`
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### FileUploadPage · Upload-View + Datei-Dropzone [`/files/upload`] (A4)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/files/FileUploadPage.tsx`
|
||||
- **Route(n):** `/files/upload` (`routes/app.$tenant/files.upload.tsx`; Resource-Routes: `files.upload.init.tsx`, `files.upload.$uploadId.chunk.$index.tsx`, `files.upload.$uploadId.complete.tsx`)
|
||||
- **Daten (VM):** `FileUploadVM` — `packages/view-models/src/files/file-upload.vm.ts` (liefert `dropzoneHeadlineKey`/`dropzoneBodyKey`/`heicNoticeKey`); Queue-State separat als `items: FileUploadDisplayItem[]` (Route-injiziert)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Custom Drag&Drop-Dropzone (kein i18n-fähiges `~/ui`-Dropzone vorhanden — bewusst handgebaut), Button, Progress (Per-Datei-Fortschritt, State uploading/success/error), Alert (HEIC→JPG-Hinweis), Queue-Liste mit Per-Datei-Status · Icons: FeatherUpload
|
||||
- **Interaktionen (Actions):** `onFiles(files: FileList | null)` (via Klick-Input + Drop); Client-Chunked-Pipeline (init/chunk/complete) lebt in der Route
|
||||
- **States:** Empty (`files.upload.queue.empty`) · Per-Item queued/uploading/processing/done/error (mit `errorKey`, Retry-Text) · Loading/not-ready (leerer Marker `ui.files.upload`) · Permission-Denied = 404 (`files.write`-Gate im Loader)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### FileDetailPage · File-Detail [`/files/:id`] (A3)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/files/FileDetailPage.tsx`
|
||||
- **Route(n):** `/files/:id` (`routes/app.$tenant/files.$id.tsx`); Medien via `media.$fileId.$variant.tsx` (Auth-Proxy)
|
||||
- **Daten (VM):** `FileDetailData` (entpackt aus `FileDetailVM`) — `packages/view-models/src/files/file-detail.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Breadcrumbs, Button, IconButton, Card (Stammdaten/Metadaten-Grid), Alert (Scan-Notice fail-closed + Preview-Fallback-Klartext), Tag_Label (Tags) + TextInput-Tag-Editor, Select (Entity-Type im Add-Link), TextInput (entityId/linkRole), Dialog (Add-Link S 560 + Delete XS 420), Bild-/PDF-Preview-Container · Icons: FeatherDownload, FeatherRefreshCw, FeatherArchive, FeatherTrash2, FeatherLink, FeatherX
|
||||
- **Interaktionen (Actions):** Server-`<form method=post>`+`intent` — `regeneratePreview`, `archive`, `delete`, `updateMeta` (Tags), `link` (entityType/entityId/linkRole), `unlink` (entityType/entityId); Download-Link (`downloadUrl`); lokaler State nur für Dialog-Öffnen (addLinkOpen/deleteOpen)
|
||||
- **States:** Scan-Notice (infected=error / pending=warning) · Preview vorhanden / `previewFallbackKey`-Klartext + Download · Linked-Entities Empty (`files.detail.links.empty`) · Tags-Editor/Aktionen nur bei `canWrite`/`canDelete`/`canRegeneratePreview` · Permission-Denied = 404 (Guard)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
## Tasks
|
||||
|
||||
### TasksListPage · Task-Liste [`/tasks`] (A2 — wiederverwendbare Komponente)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/tasks/TasksListPage.tsx`
|
||||
- **Route(n):** `/tasks` (`routes/app.$tenant/tasks._index.tsx`) — Host der embeddable Liste; Entity-Detail-Views betten sie ein (R-P6 ff.)
|
||||
- **Daten (VM):** `TaskListVM` — `packages/view-models/src/tasks/task-list.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** ToggleGroup (Presets Meine/Erstellt/Alle), Select (Status- + Prioritäts-Filter), Checkbox (Archiv- + Demo-Daten-Toggle), Button (Neu-CTA + Pagination), Table (Titel/Status/Priorität/Assignee/Deadline), BadgeFeedback (Status), Tag_Label (Priorität), EmptyState · Icons: FeatherPlus
|
||||
- **Interaktionen (Actions):** `onApply(query: TaskListQuery)` (server-side Navigation: preset/status/priority/includeArchived/includeDemoData); Neu-CTA-Link (`createHref`), Cursor-Pagination-Link (`nextHref`), Row-Detail-Links; `data-overdue`-Marker auf Deadline
|
||||
- **States:** Empty (`emptyHeadlineKey`/`emptyBodyKey` via `EmptyState`) · Loading/not-ready (leerer Marker `ui.tasks.list`) · Neu-CTA nur bei `canWrite` · Permission-Denied = 404 (Preset „Alle" nur mit `tasks.read_all`)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### TaskDetailPage · Task-Detail [`/tasks/:id`] (A3 + A5)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/tasks/TaskDetailPage.tsx`
|
||||
- **Route(n):** `/tasks/:id` (`routes/app.$tenant/tasks.$id.tsx`)
|
||||
- **Daten (VM):** `TaskDetailData` (entpackt aus `TaskDetailVM`) — `packages/view-models/src/tasks/task-detail.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Breadcrumbs, BadgeFeedback (Status) + Tag_Label (Priorität) + Quelle-Marker, Card (Overview-Metadaten + Edit-Formular), TextInput/TextArea (Titel/Beschreibung), Select (Status/Priorität/Assignee-Typ/Assignee/Watcher), DateField (native Date-Input-Helfer, kein `~/ui`-Äquivalent), Button/IconButton, Watcher-Liste, Dialog (Delete XS 420) · Icons: FeatherCheck, FeatherArchive, FeatherTrash2
|
||||
- **Interaktionen (Actions):** Server-`<form method=post>`+`intent` — `complete`, `archive`, `delete`, `update` (title/description/status/priority/deadline), `reassign` (assigneeType user↔group + assigneeId), `addWatcher` (userId), `removeWatcher` (userId); lokaler State nur für Delete-Dialog
|
||||
- **States:** Overdue-Marker (`data-overdue`, `isOverdue`) · linkedEntity optional · Watchers Empty (`tasks.detail.watchers.empty`) · Edit-/Reassign-/Watcher-/Complete-/Archive-Blöcke nur bei `canWrite`, Delete nur bei `canDelete`; „Erledigen" ausgeblendet wenn Status=done · Unassigned-Fallback-Text · Permission-Denied = 404 (fremde Task)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### TaskNewPage · Task-Anlage [`/tasks/new`] (A5 — Anlage-Modal/Form)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/tasks/TaskNewPage.tsx`
|
||||
- **Route(n):** `/tasks/new` (`routes/app.$tenant/tasks.new.tsx`)
|
||||
- **Daten (VM):** kein VM-Wrapper — Loader-injizierte Props `assignableUsers`/`assignableGroups`/`prefill`/`cancelHref` (`TaskPickerOption`, `TaskNewPrefill`); Typ `TaskPriority` aus `@hivegrid/view-models` (`packages/view-models/src/tasks/task-list.vm.ts`)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Breadcrumbs, Card, TextInput (Titel), TextArea (Beschreibung), Select (Priorität/Assignee-Typ user↔group/Assignee mit Creator-Default-Sentinel `__creator__`), DateField (Deadline), Button (Anlegen + Cancel-Link), optionaler Prefill-Marker · Icons: FeatherPlus
|
||||
- **Interaktionen (Actions):** Server-`<form method=post>` → Route-Action `createTask` (title/description/priority/deadline/assigneeType/assigneeId; optional hidden entityType/entityId aus `prefill`); Cancel-Link (`cancelHref`); Assignee-Default `__creator__` mappt server-seitig auf „kein expliziter Assignee"
|
||||
- **States:** Prefill-Hinweiszeile nur wenn `prefill` gesetzt (Anlage aus Entity-Detail-CTA); Permission-Denied = 404 (`tasks.write`-Gate); reines Form, kein Empty/Loading
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
@@ -0,0 +1,82 @@
|
||||
# P05 · Integrations / Apps-Engine (R-P5 → D-P5)
|
||||
|
||||
> Interim-UI-Referenz (AD-28). Lauffähige `~/ui`-Vorschau-Screens (VM als Prop) im gebundelten
|
||||
> `03 Quellen und Referenzen/hivegrid-cloud/`-Checkout. **Layout = Davids freie Hand.**
|
||||
|
||||
### IntegrationsCatalogPage · D-P5 „Integrations-Katalog" (Archetyp A6)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/integrations/IntegrationsCatalogPage.tsx`
|
||||
- **Route(n):** `/:tenant/integrations` (`routes/app.$tenant/integrations._index.tsx`)
|
||||
- **Daten (VM):** `IntegrationCatalogVM` — `packages/view-models/src/apps-engine/integration-catalog.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Card, BadgeFeedback (Verbindungs-Zähler / Status-Licht), SearchInput (client-seitiger Filter), EmptyState (inkl. `variant="no-results"`), Link · Icons: keine
|
||||
- **Interaktionen (Actions):** keine Mutationen (reines Navigations-Screen); nur client-State `query` (Suchfilter) + `Link` auf `row.detailHref`. Karten nach Kategorie gruppiert (Reihenfolge = erstes Auftreten).
|
||||
- **States:** Empty (`emptyHeadlineKey`/`emptyBodyKey` bei 0 Apps) · No-results (Filter ohne Treffer) · Loading (VM `status !== "ready"` → leerer Container) · Permission-Denied: Route wirft 404 (`integrations.read` fehlt), Modul aus → locked empty
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### ConnectionDetailPage · D-P5 „App-Detail / Connection-Verwaltung" (Archetyp A6+A2)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/integrations/ConnectionDetailPage.tsx`
|
||||
- **Route(n):** `/:tenant/integrations/:app` (`routes/app.$tenant/integrations.$app._index.tsx`)
|
||||
- **Daten (VM):** `ConnectionDetailVM` (+ `ConnectionRow`) — `packages/view-models/src/apps-engine/connection-detail.vm.ts`; zusätzlich `AppCredentialField[]` aus `@hivegrid/integrations` als Prop
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Alert (Screen-Banner bei degraded/broken), Button, Dialog (Disconnect-Confirm, XS 420), EmptyState, LinkButton (Breadcrumb zurück), plus Sub-Komponenten `ConnectionCard` (Liste, 2-spaltiges Grid) und `ConnectWizardDialog` · Icons: FeatherPlus
|
||||
- **Interaktionen (Actions `ConnectionDetailActions`):** `onConnect(input: ConnectWizardSubmit)` · `onReconnect(connectionId, input: ConnectWizardSubmit)` · `onDisconnect(connectionId)` · `onManualSync(connectionId)`; lokaler Dialog-State (`wizard`, `disconnectRow`), `closeSignal` schließt Dialoge nach Erfolg
|
||||
- **States:** Empty (`emptyHeadlineKey`/`emptyBodyKey` + Connect-CTA) · Loading (leerer Container) · Banner-State (mind. 1 degraded/broken → `error`/`warning` + Reconnect-Button) · Permission-Denied: Route 404 (`notFoundResponse` mehrfach)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### ConnectionCard · D-P5 App-Card mit Status-Ampel (Sub-Komponente, kein eigener Archetyp)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/integrations/ConnectionCard.tsx`
|
||||
- **Route(n):** n/a — Sub-Komponente von `ConnectionDetailPage` (wird von P14/P15/P16/P20-App-Connections wiederverwendet)
|
||||
- **Daten (VM):** `ConnectionRow` — `packages/view-models/src/apps-engine/connection-detail.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Card, BadgeFeedback (Status-Ampel via `connectionStatusBadge`), Button (secondary/destructive), LinkButton, Link (→ `row.syncsHref`) · Icons: keine. Zeigt Label, Klartext-`healthMessageKey`, letzter Health-Check (Intl-DateTime)
|
||||
- **Interaktionen (Actions):** `onManualSync(connectionId)` · `onReconnect(row)` · `onDisconnect(row)`; Buttons gated über `canManualSync`/`canReconnect`/`canDisconnect` + Status (`revoked`/`degraded`/`broken`)
|
||||
- **States:** kein eigenes Empty/Loading (Parent steuert); Button-`submitting`-Loading; `data-status` steuert Sichtbarkeit der Lifecycle-Buttons
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### ConnectWizardDialog · D-P5 „Connection-Setup-Wizard" (Archetyp A5+A6, Größe M 720)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/integrations/ConnectWizardDialog.tsx`
|
||||
- **Route(n):** n/a — Sub-Komponente von `ConnectionDetailPage`
|
||||
- **Daten (VM):** kein `@hivegrid/view-models`-Typ; Props aus `AppCredentialField[]` (`@hivegrid/integrations`) + lokale Typen `ConnectWizardMode`, `ConnectWizardSubmit`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Dialog (720px), StepperHorizontal (+ `.Step`: prerequisites → credentials → confirm), TextInput (+ `.Input`, Label + Secret-Felder), IconButton (Reveal-Toggle), Alert (Error / OAuth-Hinweis / no-fields), Button (Cancel/Back/Next/Connect) · Icons: FeatherEye, FeatherEyeOff
|
||||
- **Interaktionen (Actions):** `onSubmit(input: {label, credentials})` (postet connect/reconnect-Route-Action) · `onClose()`; lokal: Schritt-Navigation, `toggleReveal(name)`, Validierung (`labelMissing`/`requiredMissing`), OAuth2-ohne-Felder-Sonderfall
|
||||
- **States:** Error (`errorMessage` → Alert) · OAuth2-ohne-Formular (Info-Alert, geführter Redirect erst P14/P16) · Submitting (Connect-Button-Loading) · Disabled-Next bei unvollständigen Credentials
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### SyncHistoryPage · D-P5 „Sync-History" (Archetyp A2)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/integrations/SyncHistoryPage.tsx`
|
||||
- **Route(n):** `/:tenant/integrations/:app/:connectionId/syncs` (`routes/app.$tenant/integrations.$app.$connectionId.syncs._index.tsx`)
|
||||
- **Daten (VM):** `SyncHistoryVM` (+ `SyncKind`, `SyncStatus`) — `packages/view-models/src/apps-engine/sync-history.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Table (+ `.HeaderRow`/`.HeaderCell`/`.Row`/`.Cell`), Select (Status- + Kind-Filter, client-seitig), BadgeFeedback (`syncStatusBadge`), Button (Re-Sync), EmptyState (+ `no-results`), LinkButton, Link (→ Run-Detail `${runHrefBase}/${row.id}`) · Icons: keine. Spalten: started/kind/status/items (succeeded/total, failed ✗)/summary
|
||||
- **Interaktionen (Actions `SyncHistoryActions`):** `onManualSync(connectionId)` (gated `canManualSync`); lokaler Filter-State `status`/`kind`
|
||||
- **States:** Empty (`emptyHeadlineKey`/`emptyBodyKey`) · No-results (Filter leer) · Loading (leerer Container) · Permission-Denied: Route 404
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### SyncRunDetailPage · D-P5 „Sync-Run-Detail" (Archetyp A3)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/integrations/SyncRunDetailPage.tsx`
|
||||
- **Route(n):** `/:tenant/integrations/:app/:connectionId/syncs/:runId` (`routes/app.$tenant/integrations.$app.$connectionId.syncs.$runId.tsx`)
|
||||
- **Daten (VM):** `SyncRunDetailVM` — `packages/view-models/src/apps-engine/sync-run-detail.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Card (Counter total/succeeded/failed + Klartext-Summary), Alert (Error bei `status==="failed"`), Table (AppSyncItem: entity/externalId/operation/status/error), BadgeFeedback (`syncStatusBadge`/`syncItemStatusBadge`/`syncLogSeverityBadge`), EmptyState (leere Items), LinkButton, Link (Breadcrumb); Log-Zeilen mit Severity-Badge + Timestamp · Icons: keine
|
||||
- **Interaktionen (Actions):** keine (reines Read-Only-Detail); nur `Link` zurück (`backHref`)
|
||||
- **States:** Empty-Items (`emptyItemsHeadlineKey`/`emptyItemsBodyKey`) · Failed-Run (Error-Alert) · Loading (leerer Container) · Permission-Denied: Route 404
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
@@ -0,0 +1,83 @@
|
||||
# P06 · CRM (R-P6 → D-CRM)
|
||||
|
||||
> Interim-UI-Referenz (AD-28). Jeder Screen unten ist als lauffähiger `<X>Page`-Vorschau-Screen aus `~/ui`
|
||||
> gebaut (VM als Prop) und liegt im gebundelten Checkout unter `03 Quellen und Referenzen/hivegrid-cloud/`.
|
||||
> **Layout = Davids freie Hand** — die Listen unten nennen nur benötigte Elemente/Daten/Aktionen, keine Anordnung.
|
||||
|
||||
### KontaktListPage · D-CRM "Kontakt-Liste"/"Organisationen-Liste", archetype A2
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/crm/KontaktListPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/contacts` (`routes/app.$tenant/contacts._index.tsx`) · `/app/:tenant/contacts/companies` (`routes/app.$tenant/contacts.companies._index.tsx`) — ein Screen, prop `variant: "contacts" | "companies"`
|
||||
- **Daten (VM):** `KontaktListVM` — `packages/view-models/src/crm/kontakt-list.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Checkbox, EmptyState, Select, Table, TextInput · Icons: FeatherDownload, FeatherPlus
|
||||
- **Interaktionen (Actions):** `onApplyFilter(query: KontaktListQuery)`, `onGoToPage(page: number)`, `onBulk(intent: "bulkArchive"|"bulkReassign"|"bulkTag", ids: string[], extra?: {zustaendigerUserId?, tagId?})`, `onNew()`, `onExport()`
|
||||
- **States:** Empty (EmptyState, leere Liste) · Permission-Denied/Modul-gesperrt (EmptyState `crm.locked.*`; Route 404 wenn nicht sichtbar) · Error (BadgeFeedback `subtle-negative`). CTA/Bulk/Admin-Nav gated über `canWrite`/`canAdmin`
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### KontaktDetailPage · D-CRM "Kontakt-Detail"/"Organisation-Detail", archetype A3 (master template)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/crm/KontaktDetailPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/contacts/:id` (`routes/app.$tenant/contacts.$id.tsx`) · `/app/:tenant/contacts/companies/:id` (`routes/app.$tenant/contacts.companies.$id.tsx`)
|
||||
- **Daten (VM):** `KontaktDetailData` (aus `KontaktDetailVM`) + `ActivityStreamVM` — `packages/view-models/src/crm/kontakt-detail.vm.ts` (+ `packages/view-models/src/crm/activity-stream.vm.ts`)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Breadcrumbs, Button, Card, Checkbox, EmptyState, Select, TextArea, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** ein Callback `onSubmit(intent: string, fields?: Record<string,string>)`; Intents: `updateBasics`, `addEmail`/`removeEmail`/`setPrimaryEmail`, `addTelefon`/`removeTelefon`/`setPrimaryTelefon`, `addRelation`/`removeRelation`, `addTag`/`removeTag`, `setCustomField`, `reassign`, `addNote`, `archive`/`reactivate`, `delete`
|
||||
- **States:** Empty (Activity-Stream: EmptyState `crm.detail.activityEmpty`) · Permission-Denied(404) auf Route-Ebene · Error (BadgeFeedback). Alle Edit-Blöcke gated über `data.canWrite`/`data.canDelete`
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### KontaktNewPage · D-CRM "Kontakt anlegen", archetype A4
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/crm/KontaktNewPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/contacts/new` (`routes/app.$tenant/contacts.new.tsx`)
|
||||
- **Daten (VM):** kein `@hivegrid/view-models`-Import — Formular hält lokalen Draft `KontaktNewFields`; Live-Duplikate über prop `duplicates: KontaktNewDuplicates` (route-geliefert, `{hard, soft}` von `KontaktNewDuplicateCard`)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Breadcrumbs, Button, Card, Select, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** `onCheckDuplicates(fields: KontaktNewFields)` (live, onBlur der Felder), `onCreate(fields: KontaktNewFields)`
|
||||
- **States:** Permission-Denied/Modul-gesperrt (BadgeFeedback `crm.locked.headline`) · Error (BadgeFeedback) · Duplikat-Warnung (Card `negative` bei hard / `warning` bei soft)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### CustomFieldAdminPage · D-CRM "Custom-Felder", archetype A2
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/crm/CustomFieldAdminPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/contacts/admin/custom-fields` (`routes/app.$tenant/contacts.admin.custom-fields.tsx`)
|
||||
- **Daten (VM):** `CustomFieldAdminVM` — `packages/view-models/src/crm/custom-field-admin.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, EmptyState, Select, Table, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** `onCreate(input: {key, label, datentyp, optionen})`, `onArchive(definitionId: string)`
|
||||
- **States:** Empty (EmptyState `crm.admin.customFields.empty`) · Permission-Denied/Modul-gesperrt (BadgeFeedback `crm.locked.headline`) · Error (BadgeFeedback)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### TagAdminPage · D-CRM "Tag-Verwaltung", archetype A2
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/crm/TagAdminPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/contacts/admin/tags` (`routes/app.$tenant/contacts.admin.tags.tsx`)
|
||||
- **Daten (VM):** `TagAdminVM` (Zeilen: `TagAdminRowData`) — `packages/view-models/src/crm/tag-admin.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, EmptyState, TextInput (+ native `<input type="color">`, kein ~/ui-Äquivalent) · Icons: keine
|
||||
- **Interaktionen (Actions):** `onCreate({name, farbe})`, `onUpdate({tagId, name, farbe})`, `onDelete(tagId: string)`
|
||||
- **States:** Empty (EmptyState `crm.admin.tags.empty`) · Permission-Denied/Modul-gesperrt (BadgeFeedback `crm.locked.headline`) · Error (BadgeFeedback)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### DuplicateToolPage · D-CRM "Duplikat-Tool", archetype A5
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/crm/DuplicateToolPage.tsx`
|
||||
- **Route(n):** `/app/:tenant/contacts/admin/duplicates` (`routes/app.$tenant/contacts.admin.duplicates.tsx`)
|
||||
- **Daten (VM):** `DuplicateVM` (Paare: `pair.a`/`pair.b`, `matchType` hard/soft) — `packages/view-models/src/crm/duplicate.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Card, EmptyState · Icons: keine
|
||||
- **Interaktionen (Actions):** keine (read-only; Merge folgt später, VM `canMerge=false`). Prop `detailHrefFor(kontaktId)` liefert Detail-Links
|
||||
- **States:** Empty (EmptyState `crm.admin.duplicates.empty`) · Permission-Denied/Modul-gesperrt (BadgeFeedback `crm.locked.headline`)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
@@ -0,0 +1,83 @@
|
||||
# P07 · Leads (R-P7 → D-P7)
|
||||
|
||||
> Interim-UI-Referenz (AD-28). Lauffähige `<X>Page`-Vorschau-Screens aus `~/ui` (VM als Prop) im gebundelten
|
||||
> `03 Quellen und Referenzen/hivegrid-cloud/`-Checkout. **Layout = Davids freie Hand** — Listen nennen nur
|
||||
> Elemente/Daten/Aktionen.
|
||||
|
||||
### LeadInboxPage · D-P7 „Lead-Inbox" (Archetyp A2 + Triage)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/leads/LeadInboxPage.tsx`
|
||||
- **Route(n):** `/:tenant/leads` (`routes/app.$tenant/leads._index.tsx`)
|
||||
- **Daten (VM):** `LeadListVM` — `packages/view-models/src/leads/lead-list.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Checkbox, EmptyState, Select, Table, TextInput · Icons: FeatherBarChart2, FeatherPlus
|
||||
- **Interaktionen (Actions):** onApplyFilter(query: LeadListQuery), onSetStatus(leadId, status), onBulkStatus(leadIds[], status), onNextPage(), onNew(), onStatistics(); zusätzliche Datenpunkte: Filter (Suche/Quelle/Spam ausblenden/Converted zeigen), Bulk-Status-Select, Zeilen-Marker (duplicate/spam/converted), Per-Zeile Qualify/Spam, „nächste Seite", SSE-Realtime-Hint (`sseHref`), Admin-Quicklinks (sourcesHref/antiSpamHref)
|
||||
- **States:** Empty (EmptyState — leere Liste `emptyHeadlineKey`/`emptyBodyKey`; Modul-off = `leads.list.locked`) / Loading (synchron im Loader, kein Spinner; `submitting`-Flag an Bulk-Button) / Permission-Denied (404 auf Route bei fehlendem `leads.read_own`)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### LeadDetailPage · D-P7 „Lead-Detail" (Archetyp A3) + „Lead → Projekt" (A5)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/leads/LeadDetailPage.tsx`
|
||||
- **Route(n):** `/:tenant/leads/:id` (`routes/app.$tenant/leads.$id.tsx`)
|
||||
- **Daten (VM):** `LeadDetailVM` — `packages/view-models/src/leads/lead-detail.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Breadcrumbs, Button, Card, Select, TextArea, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** onSubmit(intent, fields?) — Intents: `update` (Stammdaten vorname/nachname/email/telefon/message/freigabe), `setStatus` (status), `convert` (kontaktId → Lead→Projekt, kein Modal-Zwang); Anzeige: Status-/Marker-Badges, Spam-Score+Reason, DSGVO-Auto-Delete-Hinweis, „konvertiert"-Badge
|
||||
- **States:** Empty (kein Data → leerer Container-Shell) / Loading (synchron; `submitting` an Save/Convert) / Permission-Denied (404 auf Route; Schreibfelder via `canWrite` disabled)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen: (Pipeline-/Start-Status-Auswahl beim Convert ist getrackter design-nachzug — Backend nimmt nur projektId/kontaktId)
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### LeadNewPage · D-P7 „Manueller Lead-Eintrag" (Archetyp A4)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/leads/LeadNewPage.tsx`
|
||||
- **Route(n):** `/:tenant/leads/new` (`routes/app.$tenant/leads.new.tsx`)
|
||||
- **Daten (VM):** kein `@hivegrid/view-models`-VM — lokaler Formular-Draft `LeadNewFields` (quelle, vorname, nachname, email, telefon, message)
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Breadcrumbs, Button, Card, Select, TextArea, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** onCreate(fields: LeadNewFields); Submit disabled bis mind. ein Identitätsfeld gefüllt (`hasIdentity`)
|
||||
- **States:** Empty (n/a — reines Formular) / Loading (`submitting` an Submit-Button) / Permission-Denied (404 auf Route); Fehler via `errorKey`-Badge
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### LeadStatisticsPage · D-P7 „Lead-Statistiken" (Archetyp A2 + KPI-Kit)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/leads/LeadStatisticsPage.tsx`
|
||||
- **Route(n):** `/:tenant/leads/statistics` (`routes/app.$tenant/leads.statistics.tsx`)
|
||||
- **Daten (VM):** `LeadStatisticsVM` — `packages/view-models/src/leads/lead-statistics.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** Breadcrumbs, Card, StatsCard · Icons: FeatherInbox
|
||||
- **Interaktionen (Actions):** keine Callbacks (read-only); Navigation zurück via `listHref`; Datenpunkte: KPI-Kacheln (`kpis`), Aufschlüsselung nach Quelle (`byQuelle`) und Status (`byStatus`)
|
||||
- **States:** Empty (kein Data → leerer Container-Shell; keine dedizierte EmptyState-Komponente) / Loading (synchron im Loader) / Permission-Denied (404 auf Route)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen: (reichere Breakdowns — Reaktionszeit, per-Owner, Wochentag, Trend-Charts — noch nicht vom Service geliefert, getrackter design-nachzug)
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### LeadSourcesAdminPage · D-P7 „Quellen-Verwaltung" (Archetyp A2/A6)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/leads/LeadSourcesAdminPage.tsx`
|
||||
- **Route(n):** `/:tenant/admin/lead-sources` (`routes/app.$tenant/admin.lead-sources.tsx`)
|
||||
- **Daten (VM):** `LeadSourceAdminVM` — `packages/view-models/src/leads/lead-source-admin.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, EmptyState, Table, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** onUpsert(input: {key, name, displayLabel}); Tabelle mit Typ-Badge, Paused-/Webhook-Status-Badges (Token nur als „vorhanden ja/nein", da gehasht); Create-Card via `canWrite`
|
||||
- **States:** Empty (EmptyState `leads.sources.empty`) / Loading (`submitting` an Save-Button) / Permission-Denied (404 auf Route; Create-Card nur bei `canWrite`)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
|
||||
### LeadAntiSpamAdminPage · D-P7 „Anti-Spam-Settings" (Archetyp A6)
|
||||
- **Interim-Screen:** `apps/web/app/components/screens/leads/LeadAntiSpamAdminPage.tsx`
|
||||
- **Route(n):** `/:tenant/admin/lead-anti-spam` (`routes/app.$tenant/admin.lead-anti-spam.tsx`)
|
||||
- **Daten (VM):** `LeadAntiSpamVM` — `packages/view-models/src/leads/lead-anti-spam.vm.ts`
|
||||
- **Benötigte UI-Elemente (KEIN Layout):** BadgeFeedback, Button, Card, Checkbox, Select, TextInput · Icons: keine
|
||||
- **Interaktionen (Actions):** onSave(input: LeadAntiSpamSaveInput) — mode (quarantine/spam), perSourcePerHourLimit, perIpPerHourLimit, anomalySpikeFactor, lostAutoDeleteDays, + Toggles disposableDomainEnabled/honeypotEnabled/fastSubmitEnabled (als „on")
|
||||
- **States:** Empty (kein Data → leerer Container-Shell) / Loading (`submitting` an Save-Button) / Permission-Denied (404 auf Route)
|
||||
- **Davids Delta (offen):**
|
||||
- [ ] Überschriebene/geänderte Spec-Punkte:
|
||||
- [ ] Neue UI/UX-Entscheidungen:
|
||||
- [ ] Subframe-Screen-Link(s):
|
||||
- [ ] Offene Fragen an Dev:
|
||||
@@ -0,0 +1,279 @@
|
||||
# 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 `<a href>` (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<string|null>`, `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<string,string>)`, `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:
|
||||
Reference in new issue
Block a user