PR: #366: feat(settings): document types catalog CRUD UI [Story-1519] by @ankitmaheshwari-com
View PR →
Story 1519 · EPIC-1489 Documents · Mode-1 deterministic

Document Types catalog UI — Settings → Matter Configuration

A firm needs to name the kinds of documents it works with — Contract, Affidavit, Court order — so the Documents pipeline (EPIC-1489) can classify uploads against a stable, firm-owned vocabulary. This Story ships the CRUD surface for that catalog. It is a copy of the Filing Types settings page with one structural difference: document types are firm-wide, not attached per matter-type, so there is no reference number and no per-matter-type attach step.

Why mirror Filing Types instead of inventing a page

Every settings catalog (Forums, Filing types, Proceeding types, Participant labels) is the same shape: a SettingsPageHeader + SettingsToolbar (SegmentedFilter Live/Archived tabs + SettingsSearch) + a table with a row-actions menu + a create/edit Sheet + a delete AlertDialog. Reinventing that chrome is the exact drift the repo doctrine bans. Document types compose the same primitives and the same data pattern (server-prefetch → hydrate → TanStack), so the page reads identically to its siblings.

The one real difference: firm-wide, so no reference column

AspectFiling typesDocument types
Scopeattached per matter-type / practice areafirm-wide, shared across every matter
Reference numberFT-001 columnnone — table is name / status / date / actions
Attach stepyes (matter-type picker)omitted
Mutation path paramfiling_type_iddocument_type_id

AI fields (Mode-2/3 fill & review) are EPIC-009 and live on documents, not on the document-type catalog — there are no AI columns to render here. This catalog is purely deterministic (Mode-1).

The data pattern (canonical, copied verbatim)

RSC page.tsx → documentTypesGateway.list({ includeArchived }) prefetch into server QueryClient → dehydrate → <HydrationBoundary> client DocumentTypesListPage → useAppListDocumentTypes({ includeArchived: true }) reads warm cache, no refetch flash writes → create / edit : server actions → documentTypesGateway.create / update → archive / unarchive / delete : generated useApp*DocumentType hooks (optimistic) browser → server proxy → /api/internal/document-types(/:id)(/archive|/unarchive) → toBrowserRoute mapping in kubb-client.ts

Storybook-first leaf components stories shipped

Each leaf was storied before live wiring, mirroring the filing-types stories with mock fixtures (no Clerk, no gateway):

The empty / zero-row state is a first-class story (and table branch), not an afterthought — the new-firm catalog is exactly the first real run.

i18n & tokens

Files

FileChange
src/lib/document-types/{types,gateway,presentation}.tsnew — gen-typed contracts + HR-21 gateway + date helper
src/app/api/internal/document-types/**new — list GET + delete + archive + unarchive BFF proxy routes
src/app/[locale]/(workspace)/settings/matter-configuration/document-types/{page,actions}.tsxnew — RSC prefetch + create/edit server actions
src/components/settings/document-types/**new — list page, table, toolbar, sheet, delete dialog, hooks, fixtures, error + 2 stories
src/lib/kubb-client.ts/app/document-types* → /api/internal/document-types* mapping
src/components/settings/settings-sidebar.tsxnav item + section id + Files icon
messages/{en,hi}.jsonDocumentTypes namespace + nav label (both locales)
tests/unit/document-types-contract.test.tsnew — boundary DTO + gateway contract tests (5 passing)

Verification

Files changed

27 file(s), +1776 / -0 lines

Path Role + -
messages/en.jsonConfig+45-0
messages/hi.jsonConfig+45-0
src/app/[locale]/(workspace)/settings/matter-configuration/document-types/actions.tsApp route+105-0
src/app/[locale]/(workspace)/settings/matter-configuration/document-types/page.tsxApp route+59-0
src/app/api/internal/document-types/[documentTypeId]/archive/route.tsApp route+13-0
src/app/api/internal/document-types/[documentTypeId]/route.tsApp route+36-0
src/app/api/internal/document-types/[documentTypeId]/unarchive/route.tsApp route+13-0
src/app/api/internal/document-types/route.tsApp route+27-0
src/components/settings/document-types/document-type-delete-dialog.tsxComponent+50-0
src/components/settings/document-types/document-type-sheet.stories.tsxComponent+58-0
src/components/settings/document-types/document-type-sheet.tsxComponent+107-0
src/components/settings/document-types/document-types-error.tsxComponent+14-0
src/components/settings/document-types/document-types-fixtures.tsComponent+51-0
src/components/settings/document-types/document-types-list-page.tsxComponent+141-0
src/components/settings/document-types/document-types-presentation.tsComponent+34-0
src/components/settings/document-types/document-types-table.stories.tsxComponent+113-0
src/components/settings/document-types/document-types-table.tsxComponent+167-0
src/components/settings/document-types/document-types-toolbar.tsxComponent+55-0
src/components/settings/document-types/document-types-types.tsComponent+5-0
src/components/settings/document-types/use-document-type-actions.tsComponent+153-0
src/components/settings/document-types/use-document-type-sheet.tsxComponent+117-0
src/components/settings/settings-sidebar.tsxComponent+8-0
src/lib/document-types/gateway.tsLibrary+156-0
src/lib/document-types/presentation.tsLibrary+15-0
src/lib/document-types/types.tsLibrary+14-0
src/lib/kubb-client.tsLibrary+12-0
tests/unit/document-types-contract.test.tsTest+163-0