# Pattern Declarative KIND_CONFIG Record — Single-page multi-kind CRUD reuse

> **Discovered:** S29 Plan CA (Master/Catalogs/CatalogsPage.tsx 321 LOC) — first proven instance.
> **Reinforced:** S35 G-H2 FE Admin (Hrm/HrmConfigsPage.tsx ~470 LOC) — Pattern 2× cumulative cross-module.

## Pattern

Single-page handle **N kind** of catalog/config via URL `:kind` param + declarative `KIND_CONFIG: Record<Kind, ...>`. KHÔNG split N page riêng (clutter routes + duplicate boilerplate).

### Structure
```tsx
type Kind = 'kind-a' | 'kind-b' | ... // union literal (TS6 erasableSyntaxOnly)

type FieldDef = {
  key: string
  label: string
  type: 'text' | 'textarea' | 'checkbox' | 'number' | 'date' | 'time' | 'select' | 'multiselect-weekday' | ...
  required?: boolean
  placeholder?: string
  step?: number
  options?: Array<{value, label}>
}

const KIND_CONFIG: Record<Kind, {
  label: string
  icon: React.ComponentType<{className?: string}>
  fields: FieldDef[]
  columns: string[]
}> = {
  'kind-a': { label, icon, fields: [...], columns: [...] },
  'kind-b': { ... },
}

export function CatalogPage() {
  const { kind } = useParams<{kind?: string}>()
  const config = KIND_CONFIG[kind ?? 'default']
  // Render sub-tab nav + table + Dialog form (declarative renderField per FieldDef.type)
}
```

## Decision

**Khi nào dùng Pattern Declarative KIND_CONFIG (vs split page):**
- N kind ≤ 6, mỗi kind có fields shape DIFFERENT nhưng UX flow SAME (List + Create/Edit dialog + Delete)
- KHÔNG có per-kind business logic phức tạp (workflow / approval flow / codeGen)
- Mỗi kind chỉ khác fields list + columns
- Total page LOC ~300-500 (acceptable single-file maintain)

**KHÔNG dùng khi:**
- Per-kind business logic phức tạp → split per-page (vd Contract type 7 có 7 form khác + 9 phase workflow)
- N kind > 6 → file > 1000 LOC khó scan
- Fields shape differ rất nhiều (vd file upload field + signature picker mix vào)

## Example proven cumulative

### Plan CA Chunk B (S29) — Master/Catalogs
- 4 kind: units / materials / services / work-items
- File: `fe-admin/src/pages/master/CatalogsPage.tsx` 321 LOC + mirror fe-user
- Field shape: code/name + variant per kind (defaultUnit/category/originCountry/etc.)
- BE: `/api/catalogs/{kind}` × 4 verb mirror controller

### S35 G-H2 (this session) — Hrm/HrmConfigs
- 4 kind: leave-types / holidays / shifts / ot-policies
- File: `fe-admin/src/pages/hrm/HrmConfigsPage.tsx` ~470 LOC + mirror fe-user
- Field shape: complex (multiselect-weekday cho ShiftPattern WorkDays + TimeOnly cho startTime/endTime + 3 multiplier OtPolicy)
- BE: `/api/hrm-configs/{kind}` × 4 verb mirror controller
- NEW FieldDef.type extend: `number/date/time/select/multiselect-weekday`

## renderField helper extend pattern

Mỗi kind có thể introduce NEW field type → extend `renderField(field, value, onChange)` switch:
- `text/date/time` → `<Input type={field.type}>`
- `number` → `<Input type="number" step={field.step}>`
- `textarea` → `<Textarea>`
- `checkbox` → `<input type="checkbox">`
- `select` → `<select>` với `field.options`
- `multiselect-weekday` (NEW S35) → 7 checkbox Mon-Sun + comma-join string

## Reusable cross-project

- VIPIX: Tags + Categories + Collections (3 catalog) — Pattern fit
- NAMGROUP: Loại NV + Phòng ban + Vị trí (3 catalog đơn giản) — Pattern fit
- ASHICO: 5-site config + 7-DB selector → có thể adapt
- DH_Y_DUOC: Loại sáng kiến + Cấp duyệt (2 catalog) — Pattern fit
- BVAAU: Loại bệnh nhân + Bảo hiểm (2-3 catalog) — Pattern fit

## Pattern relationship

| Pattern | Scope | Token cost | Notes |
|---|---|---|---|
| Declarative KIND_CONFIG | Single-page multi-kind CRUD | ~25K Implementer spawn | 1 page + KIND_CONFIG Record |
| 12-bis | Cross-module mirror BE | ~25K Implementer spawn | BE mega 4 region |
| 12-ter | Within-module N-satellite | ~22K spawn | 1 mega features + N region |
| 16-bis | Cross-app FE 4-place mirror | ~15K Implementer spawn | types + page + Routes + Layout staticMap |

Declarative KIND_CONFIG + 12-bis + 16-bis = **Trinity pattern** cho master catalog feature S29 + S35 proven 2×.

## Anti-pattern

❌ KHÔNG split N page per kind — clutter routes + duplicate boilerplate State/Mutation/Dialog
❌ KHÔNG hardcode per-kind switch trong render JSX — dùng `KIND_CONFIG` Record + `renderField` switch tách logic
❌ KHÔNG quên FieldDef.type extend khi NEW kind có shape khác — vd time picker / multiselect

## Verify checklist

- [ ] 1 page với `:kind` URL param + `useParams` extract
- [ ] `KIND_CONFIG: Record<Kind, {...}>` declarative N kind config
- [ ] 1 useQuery list + 1 useMutation save (Create + Update branch via isEdit) + 1 useMutation remove
- [ ] Sub-tab nav 4 kind (NavLink active state per kind)
- [ ] Dialog modal CRUD generic (KHÔNG per-kind dialog)
- [ ] `renderField()` helper switch case per FieldDef.type
- [ ] BE URL `/api/{prefix}/{kind}` × 4 verb (List + Create + Update + Delete) mirror controller
- [ ] Mirror SHA256 IDENTICAL × 2 app (Pattern 16-bis)
- [ ] Layout.tsx staticMap entry N leaf (Pattern 16-bis 4-place)
