# Enhancement Plan This document identifies current gaps and improvement opportunities in the CRUD flow. Items are grouped by priority and annotated with implementation effort. --- ## Summary The current CRUD pattern is clean, composable, and type-safe. Most issues are edge cases or developer-experience refinements rather than critical bugs. The highest-priority items are the ones marked **High**. --- ## High Priority ### 1. `useAuthApi` — Missing Memoization **File:** `shared/useApi.ts` **Problem:** `createApi()` is called on every render. Every component that calls `useAuthApi()` creates new `ApiClient` instances per render cycle. This can cause unintended React Query cache misses or stale closures in edge cases. **Fix:** ```ts // shared/useApi.ts import { useMemo } from "react" export const useAuthApi = () => { const token = useAuthStore(s => s.token) return useMemo( () => createApi({ headers: token ? { Authorization: `Bearer ${token}` } : undefined }), [token], ) } ``` **Effort:** XS (2 lines) — no API changes required. --- ### 2. Edit Mode — No Server Re-fetch on Dialog Open **File:** `shared/hooks/use-resource-form.ts`, `modules/customers/customer-form.tsx` **Problem:** When the "Edit" button is clicked, `useResourcePage` passes `selectedItem` (the in-memory table row) as `initialData`. The form is pre-populated from this snapshot. However: - The snapshot may be stale if the item was modified elsewhere. - On page refresh with `?dialog=true&resourceId=5` in the URL, `selectedItem` is `null` (not hydrated) → the form opens empty. **Fix:** Add an `initialize` function to each feature form that fetches the full resource by ID: ```ts // In CustomerForm (use-resource-form options) initialize: (id) => api.customers.show(id), // requires CrudClient.show() queryKey: [CUSTOMER_ROUTES.BY_ID, resourceId], // In CrudClient (packages/api/src/infra/crud-client.ts) async show(id: string) { return this.get(this.byIdRoute, { params: { id } } as never) } ``` **Effort:** S — needs `CrudClient.show()` added and each form updated to pass `initialize`. --- ### 3. `FormDialog` — Single Dialog Limitation **File:** `shared/components/form-dialog.tsx` **Problem:** Dialog state is keyed to fixed URL params `dialog` and `resourceId`. If two independent `FormDialog` instances are on the same page (e.g., a main resource form and a nested "Add Customer" side panel), they share the same URL params and will conflict. **Fix:** Accept a configurable `paramKey` prop: ```ts export const createFormDialogParams = (key: string) => ({ [`${key}_dialog`]: parseAsBoolean.withDefault(false), [`${key}_resourceId`]: parseAsString, }) ``` **Effort:** S — requires updating `FormDialog`, `useFormDialog`, and `useResourcePage` to accept a `paramKey`. --- ## Medium Priority ### 4. No Global Search / Filter Support **File:** `shared/data-view/table-view/use-data-table-query.ts`, `shared/data-view/table-view/search-params.ts` **Problem:** `dataTableSearchParams` only supports `page`, `per_page`, `sort_by`, `sort_order`. There is no standard way to add resource-specific filters (e.g., search by name, filter by status). **Proposed:** Add an optional `filters` object to `useDataTableQuery` that maps to additional query params: ```ts useDataTableQuery({ queryKey: ["customers"], client, filters: { search: parseAsString, status: parseAsStringEnum(["active", "inactive"] as const), }, }) // → adds ?search=&status= params and includes them in client.list() ``` **Effort:** M — requires a design decision and updates to `use-data-table-query`, `data-table.tsx`, and `resource-page`. --- ### 5. Grid View — Not Implemented **File:** `shared/data-view/grid-view/` (empty directory) **Problem:** The `grid-view` folder was scaffolded but never implemented. The data-view layer is clearly designed to support multiple views (table/grid), but no toggle exists. **Proposed:** - Implement a `GridView` component that accepts the same `DataViewProps` as `DataTable`. - Add a view toggle (Table | Grid) in the `ResourcePage` header or `DashboardHeader`. - Persist the selected view in a URL param (`?view=grid`). **Effort:** M–L depending on grid card design requirements. --- ### 6. `useMutation` Error Handling — Not Reusable **File:** `modules/customers/customer-form.tsx` (and all other feature forms) **Problem:** The pattern of mapping `ApiError.validationErrors` to `form.setError` is duplicated in every form's `onError` handler. **Fix:** Extract a `useFormMutation` hook: ```ts // shared/hooks/use-form-mutation.ts export function useFormMutation( form: UseFormReturn, options: UseMutationOptions, ) { return useMutation({ ...options, onError: (err, vars, ctx) => { if (err instanceof ApiError && err.validationErrors) { Object.entries(err.validationErrors).forEach(([field, msgs]) => { form.setError(field as keyof TValues as any, { message: msgs[0] }) }) } options.onError?.(err, vars, ctx) }, }) } ``` **Effort:** XS — purely additive. Existing forms can be migrated incrementally. --- ### 7. `CUSTOMER_CREATED_EVENT` — Unused Custom Event **File:** `modules/customers/customer-form.tsx` **Problem:** `CustomerForm` dispatches `window.dispatchEvent(new CustomEvent("customer:created"))` on success, but nothing in the codebase listens to this event. Cache invalidation is already handled via `onSuccess` → `invalidateQuery()`. The event dispatch is dead code. **Fix:** Remove the event dispatch from `CustomerForm` (and the `CUSTOMER_CREATED_EVENT` export) unless there is a known future use case, such as notifying a sibling component outside the React tree. **Effort:** XS. --- ### 8. Pagination Meta Split — Inconsistency **File:** `shared/data-view/table-view/use-data-table-query.ts` **Problem:** `useDataTableQuery` returns `pagination` with `pageCount: 1, total: 0` as placeholders. The real values come from `data.meta` and are calculated inside `ResourcePage.tsx`. This means: - `useResourcePage` consumers who render the table directly (outside `ResourcePage`) need to duplicate the `pageCount`/`total` derivation. - The `pagination` object returned by the hook is misleading until data loads. **Fix:** Either move the meta derivation inside `useDataTableQuery` (requiring it to accept a response shape hint), or document this as an intentional split and annotate it. **Effort:** XS–S. --- ## Low Priority / Nice to Have ### 9. Row Selection for Bulk Actions There is no row selection or bulk-delete support. TanStack Table supports `rowSelection` state natively. Adding a checkbox column and a "Delete selected" toolbar would benefit resource-heavy pages. ### 10. Error Boundary Around Table and Form If a render error occurs inside `DataTable` or a feature form, it will bubble up and crash the whole page. Wrapping with `` (e.g., via `react-error-boundary`) would improve resilience. ### 11. `ConfirmDialog` — Not Enforced in Layout **Problem:** `` must be manually mounted in `app/(authenticated)/layout.tsx`. There is no lint rule or runtime warning if it is missing. If a developer forgets to add it to a new layout, `confirm()` calls will silently resolve to `false` (no dialog shown, deletion blocked). **Fix:** Add a development-mode warning inside the `confirm()` function if the store's `resolve` is never set after a timeout. ### 12. Column Visibility / Hide `ColumnHeader` has a "Hide" dropdown menu item (via `column.toggleVisibility(false)`), but there is no global "Show columns" control to restore hidden columns. Either remove the hide option or add a column visibility popup to `DataTable`. ### 13. `dataTableSearchParamsCache` — Imported but Unused in App Router `dataTableSearchParamsCache` is exported but the pages use `"use client"` throughout. If server components are introduced for any list page, the cache needs wiring in the layout via `nuqs`'s `SearchParamsProvider`. --- ## Checklist - [x] #1 — Memoize `useAuthApi` - [x] #2 — Add `CrudClient.show()` and wire `initialize` in feature forms - [x] #3 — Make `FormDialog` param key configurable - [ ] #4 — Design and implement filter/search param support in `useDataTableQuery` - [ ] #5 — Implement `GridView` and view toggle - [x] #6 — Extract `useFormMutation` hook - [x] #7 — Remove unused `CUSTOMER_CREATED_EVENT` - [x] #8 — Move pagination meta derivation into `useDataTableQuery` - [ ] #9 — Row selection + bulk actions - [ ] #10 — Error boundaries - [x] #11 — Dev-mode warning for missing `ConfirmDialog` - [ ] #12 — Column visibility restore control - [ ] #13 — Wire `SearchParamsProvider` if server components are adopted