# CRUD Pattern — Overview

This document describes the full-stack CRUD pattern used across the dashboard. Every resource page (Customers, Vendors, Employees, etc.) is built from the same thin layer of generic, composable utilities.

---

## Architecture Layers

```
┌──────────────────────────────────────────────────────────────────┐
│  Feature Page  (apps/dashboard/app/…/page.tsx)                  │
│  • Declares columns, title, routeKey, getClient, renderForm      │
└──────────────────┬───────────────────────────────────────────────┘
                   │ uses
┌──────────────────▼───────────────────────────────────────────────┐
│  ResourcePage  (shared/data-view/resource-page)                  │
│  • Combines DashboardPage + FormDialog + DataTable               │
│  • Delegates state to useResourcePage                            │
└──────┬───────────────────────────────┬────────────────────────────┘
       │                               │
┌──────▼──────────────┐   ┌────────────▼───────────────────────────┐
│ useResourcePage     │   │ DataTable  (shared/data-view/table-view)│
│ • useDataTableQuery │   │ • TanStack Table (manual mode)          │
│ • form dialog state │   │ • Pagination, sorting, skeleton         │
│ • delete mutation   │   │ • DataViewProvider context              │
└──────┬──────────────┘   └────────────────────────────────────────┘
       │
┌──────▼──────────────────────────────────────────────────────────┐
│  useAuthApi  →  createApi()  →  CrudClient  →  openapi-fetch    │
│  (packages/api — fully type-safe from OpenAPI schema)           │
└─────────────────────────────────────────────────────────────────┘
       │
┌──────▼──────────────────────────────────────────────────────────┐
│  Feature Form  (modules/<feature>/<feature>-form.tsx)           │
│  • useResourceForm (react-hook-form + Zod + optional fetch)     │
│  • useMutation (create / update)                                │
│  • RhfTextField / RhfSelectField / RhfAsyncSelectField …        │
└─────────────────────────────────────────────────────────────────┘
```

---

## Key Files

| Layer | File | Purpose |
|---|---|---|
| Feature page | `app/(authenticated)/sales/customers/page.tsx` | Minimal feature config |
| Generic page shell | `shared/data-view/resource-page/resource-page.tsx` | Layout + wiring |
| Page logic hook | `shared/data-view/resource-page/use-resource-page.ts` | State + mutations |
| Table | `shared/data-view/table-view/data-table.tsx` | TanStack Table UI |
| Table data hook | `shared/data-view/table-view/use-data-table-query.ts` | React Query + URL state |
| Form dialog | `shared/components/form-dialog.tsx` | URL-driven dialog trigger |
| Confirm dialog | `shared/components/confirm-dialog.tsx` | Imperative async confirm |
| Form state hook | `shared/hooks/use-resource-form.ts` | RHF + Zod + optional re-fetch |
| Form field wrappers | `shared/components/form/` | RhfTextField, RhfSelectField, … |
| API layer | `packages/api/src/infra/crud-client.ts` | Generic CRUD HTTP client |
| Auth API hook | `shared/useApi.ts` | Creates authenticated API instance |

---

## Data Flow — Read (LIST)

```
URL params change (page / sort)
  → useDataTableQuery re-runs queryFn
    → client.list({ page, per_page, sort_by, sort_order })
      → CrudClient.list()
        → ApiClient.get(indexRoute, { query })
          → openapi-fetch GET /api/customers
            ← { data: [...], meta: { last_page, total, ... } }
  → DataTable renders rows + pagination
```

## Data Flow — Write (CREATE / UPDATE)

```
User fills form → submits
  → useMutation mutationFn
    → api.customers.create(payload) or .update(id, payload)
      → CrudClient.create() / .update()
        → ApiClient.post() / .put()
          → openapi-fetch POST|PUT /api/customers[/{id}]
            ← 200/201 response
  → onSuccess: invalidateQuery() → list refreshes
  → onError: ApiError.validationErrors → form.setError(field, msg)
```

## Data Flow — Delete

```
User clicks Delete in actions column
  → confirm({ title, description, variant: "destructive" }) → awaits boolean
    → if confirmed: deleteItem(id)
      → useMutation → client.destroy(id)
        → ApiClient.delete()
          ← success
      → invalidateQuery()
```

---

## Creating a New Feature Page

To add a new resource page following this pattern:

1. Create schema: `modules/<feature>/<feature>.schema.ts`
2. Create form: `modules/<feature>/<feature>-form.tsx`
3. Create page: `app/(authenticated)/<section>/<feature>/page.tsx`

The page needs only 5 props on `<ResourcePage>`:

```tsx
<ResourcePage<MyClient>
  title="My Resource"
  pageTitle="My Resources"
  routeKey={MY_ROUTES.INDEX}
  getClient={(api) => api.myClient}
  columns={({ actionsColumn }) => [
    { accessorKey: "name", header: () => <ColumnHeader ... /> },
    actionsColumn(),
  ]}
  renderForm={({ resourceId, initialData, onSuccess }) => (
    <MyForm resourceId={resourceId} initialData={initialData} onSuccess={onSuccess} />
  )}
/>
```

See the [Resource Page](./resource-page.md), [Data Fetching](./data-fetching.md), and [Form System](./form-system.md) docs for details on each layer.
