# 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 | --- ## `` Component ### Props ```ts type ResourcePageProps = { // 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>[] | ((helpers: ResourcePageColumnHelpers) => ColumnDef>[]) renderForm: (props: ResourceFormProps) => React.ReactNode // Optional pageTitle?: string // Heading text (defaults to undefined) queryOptions?: Omit, "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` | 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({ routeKey, getClient, queryOptions }) ``` ### Returns | Key | Type | Description | |---|---|---| | `data` | `CrudListResponse` | 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` | 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:** `` 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({ onEdit: (row) => openEdit(row), onDelete: async (row) => { const confirmed = await confirm({ ... }) if (confirmed) await deleteItem(String(row.id)) }, }) ``` --- ## Component Tree ``` └─ ├─ │ └─ ← "Add Customer" button + Dialog shell │ └─ renderForm(resourceId) ← Feature-specific form └─ └─ └─ ├─ ← Shares state via context ├─ TanStack Table (manual pagination + sorting) ├─ Skeleton rows while loading └─ ← Page controls + rows-per-page ```