Patrón — Admin List CRUD (tenant + platform)
Objetivo
Cualquier list CRUD admin (Permissions, Roles, Users, Audit, tenant Roles, …) MUST verse y comportarse como Locations/S-045: misma anatomía de lista, mismos organismos @ivprior/ui/data-table-*, mismo bulk/export/RBAC, mismos márgenes (AdminPageToolbar fuera + content gap-4 p-4). No inventar layouts por feature.
Ámbito
| Incluye | Excluye |
|---|---|
Listas CRUD /platform/... | Login / forgot password |
Listas CRUD finance routes (/settings/..., listados) (access, etc.) | Dashboard KPI-only sin tabla CRUD |
| Modales create/edit/delete | Impersonation flows one-off |
| Filtros, export, bulk, paginación |
Form surface (create/edit): gate admin-create-edit-surface.md — A URL full-page · B modal form · C quick-create nested. Si multi-paso o matriz compleja (ej. S-046 Roles), MUST wizard full-page (admin-wizard.md / S-012) — no AdminWizardModalShell en features nuevas. La lista sigue este patrón al 100 %.
Gate Mirror (agentes)
Antes de implementar o refactorizar una lista:
- Declarar
Mirror: <path>(ej.apps/web/features/platform-admin/roles/components/PlatformRolesListPage.tsx). - Leer Mirror +
*Tableasociado. - Replicar chrome; no copiar un hermano tenant que ya divergió.
| Concern | Mirror (path en IVPrior, DOC-024) |
|---|---|
| Lista CRUD genérica | Primer *ListPage local (Users o Platform Permissions) |
| Table + mobile cards | Primer *Table local (AdminMobileList) |
| Átomos / toolbar | packages/ui · packages/admin-layout |
| Wizard multi-paso | Primer *Wizard local + admin-wizard.md |
Esqueleto ListPage (obligatorio)
AdminPageToolbar
title + description
actions = [Add primary CTA only]
← sin sticky · sin breadcrumbs en lista · sin Export · sin bulk
div.flex.flex-1.flex-col.gap-4.p-4 ← OBLIGATORIO (alineación con toolbar px-4)
pageState: loading | error | empty | loaded | forbidden
(preferir un solo return; no envolver todo en space-y-4 sin p-4)
Card (*Table)
CardHeader → DataTableToolbar
search (debounce 300 ms)
Filter popover (Apply + Reset)
statusSlot «Refreshing…»
trailing → DataTableExportMenu
{bulkToolbar} → DataTableBulkBar (si selectedCount > 0)
CardContent → AdminMobileList (cards `<lg`) → Table (`lg+`)
checkbox col si canManage
columns …
row actions ⋮ (texto; Edit / Delete)
CardFooter → DataTablePagination (10/20/30/50/All)
Dialogs: FormDialog · DeleteDialog · BulkDeleteDialog
Anti-patrón (visto en tenant Roles): div.space-y-4 envolviendo toolbar + card sin p-4 → título inset y card full-bleed.
Checklist (no omitir)
| # | Elemento | Notas |
|---|---|---|
| 1 | AdminPageToolbar | Sin sticky blur; lista sin breadcrumbs |
| 2 | Add CTA | PlusIcon + gap-1.5 shadow-sm; solo manage |
| 3 | Bulk | DataTableBulkBar entre CardHeader y CardContent — checkboxes + «N selected / Clear / acciones dominio» |
| 3b | Mobile list cards | MUST AdminMobileList + AdminMobileListCard en todo *Table admin con filas de datos — Mirror LocationsTable; cards <lg, tabla lg+; colas courier incluidas |
| 4 | Search | Placeholder "Search" / "Buscar"; texto libre sobre code, name, description, resource, action, etc. |
| 5 | Filter | Popover: status + DateRangeFilterFields (presets + From/To) si hay createdAt/updatedAt — ADR-062 |
| 6 | Export | Solo en trailing del toolbar de tabla |
| 7 | Sort | DataTableSortHeader en columnas de datos; sortBy/sortOrder server-side |
| 8 | Pagination | DataTablePagination + preferencia page size persistida |
| 9 | Empty / error / forbidden | Ver data-states.md; forbidden = sin permiso read |
| 10 | RBAC | permissionCodes de session; read lista · manage mutaciones |
| 11 | Help ADR-080 | MDX EN/ES + registry S-xxx en el mismo slice |
| 12 | IAM FEAT-041 | Codes en catálogo; no hardcodear roles legacy |
Formulario modal (create / edit)
| Regla | Detalle |
|---|---|
| Chrome | Cancel + Save en header (derecha del título); sin ×; sin footer |
| Help | Un solo ? junto al título que resuma el form |
| Campos derivados | No mostrar inputs editables (ej. Code); generar en submit; ver en tabla |
| Selects de catálogo | Resource: SearchableCombobox. Create Actions: SearchableMultiCombobox (un permiso por action). Edit Action: single. |
| Dirty | FormCloseDialog |
| Status | Toggle + hint (paridad S-045) |
API list filters
Si la UI expone date range, el list API MUST aceptar query params alineados (ej. createdFrom / createdTo ISO YYYY-MM-DD) y filtrar en repositorio — no filtrar solo client-side con paginación server-side.
Si la UI expone sort en headers (DataTableSortHeader), el list API MUST aceptar sortBy + sortOrder (asc|desc) con whitelist de campos; default estable (Permissions: code asc).
i18n
Namespace platformAdmin.<feature>.* o tenantAdmin.<feature>.* (EN + ES). Keys mínimas de lista: search, filter*, export*, pageSize*, bulk*, empty*, error*, column*.
Referencias código
| Recurso | Dónde (este repo) |
|---|---|
| Átomos / tabla / export | packages/ui |
| Toolbar / wizard shell | packages/admin-layout |
| List / Table gold | Primer *ListPage / *Table en DOC-024 |
Nuevo CRUD admin (platform o tenant) — orden
- Spec UX
S-xxx+ wireframe approved + sitemap. - Leer este doc +
admin-ui-coherence.md+ fila enDOC-024-canonical-implementations.md. - Declarar Mirror (path IVPrior) y leerlo; copiar estructura del gold local.
- Wire API client + filters + export + bulk + RBAC.
- Elegir surface A/B/C (
admin-create-edit-surface.md): modal form header actions + un help, o wizard/form URL S-012. - Help MDX +
help-content-sync. - RTL: manage / read-only / forbidden / 409 / bulk checkboxes.
- Checklist
.specify/checklists/implementation-coherence.md(ítem Mirror).
Documentos relacionados
admin-create-edit-surface.md— modal vs URLadmin-ui-coherence.mdadmin-crud-form.md.specify/memory/platform-iam.md.specify/memory/help-content.md.cursor/rules/seed-platform-admin-crud.mdc