# Resource Page

The `ResourcePage` component is the primary generic shell for any CRUD list page. It composes layout, table, dialog, and state management into a single, reusable component.

---

## Files

| File | Description |
|---|---|
| `shared/data-view/resource-page/resource-page.tsx` | The React component |
| `shared/data-view/resource-page/use-resource-page.ts` | The state/logic hook |
| `shared/data-view/resource-page/index.ts` | Public exports |

---

## `<ResourcePage>` Component

### Props

```ts
type ResourcePageProps<TClient extends ResourcePageClient> = {
  // Required
  title: string                    // Used for the "Add" button label and dialog title
  routeKey: string                 // React Query cache key (e.g. CUSTOMER_ROUTES.INDEX)
  getClient: (api: ApiInstance) => TClient  // Selects the domain client from the API
  columns:                         // Column definitions or a factory receiving helpers
    | ColumnDef<ResourceItem<TClient>>[]
    | ((helpers: ResourcePageColumnHelpers<TClient>) => ColumnDef<ResourceItem<TClient>>[])
  renderForm: (props: ResourceFormProps<TClient>) => React.ReactNode

  // Optional
  pageTitle?: string               // Heading text (defaults to undefined)
  queryOptions?: Omit<UseQueryOptions<...>, "queryKey" | "queryFn">
}
```

### `ResourcePageColumnHelpers`

Passed to the `columns` callback, providing three pre-wired helpers:

| Helper | Type | Description |
|---|---|---|
| `actionsColumn(options?)` | `ColumnDef` | Pre-built Edit + Delete dropdown column |
| `openEdit(row)` | `(row: TItem) => void` | Opens dialog with row pre-filled |
| `deleteItem(id)` | `(id: string) => Promise<unknown>` | Deletes with toast + confirmation |

The `actionsColumn` factory (`createActionsColumn`) can be further customized:
```ts
actionsColumn({
  onEdit: (row) => customOpen(row),
  onDelete: async (row) => {
    // completely override delete behavior
  },
})
```

### `ResourceFormProps`

Passed to the `renderForm` callback:

| Prop | Type | Description |
|---|---|---|
| `resourceId` | `string \| null` | `null` on create; the item's `id` string on edit |
| `initialData` | `TItem \| null` | The full row object on edit (from the table's in-memory state) |
| `onSuccess` | `() => void` | Call this after a successful mutation to refresh the list |

---

## `useResourcePage` Hook

Encapsulates all state and logic. Returned by `ResourcePage` internally but also exported for use in custom page layouts.

```ts
const page = useResourcePage<MyClient>({ routeKey, getClient, queryOptions })
```

### Returns

| Key | Type | Description |
|---|---|---|
| `data` | `CrudListResponse<TClient>` | Raw API response |
| `isLoading` | `boolean` | True while initial fetch is in progress |
| `pagination` | `DataViewPaginationState` | `{ page, pageSize, pageCount, total }` |
| `sorting` | `DataViewSorting` | Current sort state |
| `handleChange` | `(event: DataViewChangeEvent) => void` | Handles pagination and sort events |
| `invalidateQuery` | `() => void` | Busts the React Query cache for the current query key |
| `selectedItem` | `TItem \| null` | The row being edited (populated by `openEdit`) |
| `openEdit(row)` | fn | Sets `selectedItem` and opens dialog |
| `openCreate()` | fn | Clears `selectedItem` and opens dialog |
| `openDialog(id?)` | fn | Low-level dialog open (sets `?dialog=true&resourceId=id` in URL) |
| `closeDialog()` | fn | Closes dialog (removes URL params) |
| `isDialogOpen` | `boolean` | Current dialog open state |
| `dialogResourceId` | `string \| null` | Current resource ID from URL |
| `deleteItem(id)` | `(id: string) => Promise<unknown>` | Mutation that destroys a resource |
| `actionsColumn(options?)` | fn | Generates the actions `ColumnDef` |
| `client` | `TClient` | The domain API client |
| `api` | `ApiInstance` | Full authenticated API object |

---

## `FormDialog` — URL-Driven Dialog

`FormDialog` and its companion hook `useFormDialog` manage dialog open/close state **via URL query parameters**:

| URL param | Value | Meaning |
|---|---|---|
| `dialog` | `true` / absent | Dialog open/closed |
| `resourceId` | string / absent | ID of the item being edited |

This means sharing or refreshing the URL with `?dialog=true&resourceId=5` will reopen the dialog on the same item (as long as `initialData` is in memory—see [Enhancement Plan](./enhancement-plan.md)).

### `useFormDialog`

```ts
const { isOpen, resourceId, open, close } = useFormDialog()

open("5")     // opens dialog in edit mode
open()        // opens dialog in create mode
close()       // closes dialog, clears resourceId
```

---

## `ConfirmDialog` — Imperative Async Confirm

`ConfirmDialog` is a **singleton store-driven dialog** mounted once in the root layout. It exposes an imperative `confirm()` function:

```ts
import { confirm } from "@/shared/components/confirm-dialog"

const ok = await confirm({
  title: "Delete this item?",
  description: "This action cannot be undone.",
  confirmLabel: "Delete",
  variant: "destructive",   // shows destructive styling + trash icon
})

if (ok) { /* proceed */ }
```

> **Important:** `<ConfirmDialog />` must be rendered once in the root layout. If it is not mounted, `confirm()` will open a dialog that is never displayed.

---

## `createActionsColumn`

A standalone factory for generating the standard Edit + Delete column:

```ts
import { createActionsColumn } from "@/shared/data-view/table-view"

createActionsColumn<MyItem>({
  onEdit: (row) => openEdit(row),
  onDelete: async (row) => {
    const confirmed = await confirm({ ... })
    if (confirmed) await deleteItem(String(row.id))
  },
})
```

---

## Component Tree

```
<ResourcePage>
  └─ <DashboardPage header={...} title={pageTitle}>
       ├─ <DashboardHeader>
       │    └─ <FormDialog title={title}>        ← "Add Customer" button + Dialog shell
       │         └─ renderForm(resourceId)       ← Feature-specific form
       └─ <Card>
            └─ <CardContent>
                 └─ <DataTable columns data pagination sorting onChange isLoading>
                      ├─ <DataViewProvider>      ← Shares state via context
                      ├─ TanStack Table (manual pagination + sorting)
                      ├─ Skeleton rows while loading
                      └─ <DataViewPagination>    ← Page controls + rows-per-page
```
