mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
feat: add new context to CLAUDE.md
This commit is contained in:
@@ -63,3 +63,100 @@ src/
|
|||||||
- Detail pages that read `useSearchParams` MUST be wrapped in `<SuspenseHelper>` via a `layout.tsx` (see [src/app/finance/detail/layout.tsx](src/app/finance/detail/layout.tsx) for the canonical pattern).
|
- Detail pages that read `useSearchParams` MUST be wrapped in `<SuspenseHelper>` via a `layout.tsx` (see [src/app/finance/detail/layout.tsx](src/app/finance/detail/layout.tsx) for the canonical pattern).
|
||||||
- API service classes inherit CRUD methods (`getAll`, `getSingle`, etc.) from `BaseApiService` — extend the class for feature-specific endpoints rather than calling `httpClient` directly from components.
|
- API service classes inherit CRUD methods (`getAll`, `getSingle`, etc.) from `BaseApiService` — extend the class for feature-specific endpoints rather than calling `httpClient` directly from components.
|
||||||
- Pre-commit runs format + lint + typecheck + build; do not bypass with `--no-verify`.
|
- Pre-commit runs format + lint + typecheck + build; do not bypass with `--no-verify`.
|
||||||
|
|
||||||
|
## Table filter persistence pattern
|
||||||
|
|
||||||
|
Data tables across all modules (master-data, inventory, finance, purchase, etc.) use `useTableFilter` with `persist: true` to persist filter state in localStorage. This allows users' search, pagination, and filter choices to survive page refreshes.
|
||||||
|
|
||||||
|
**Three core principles (apply to all table components):**
|
||||||
|
|
||||||
|
1. **Set formik initialValues from tableFilterState** (not hardcoded defaults)
|
||||||
|
- Ensures the filter modal displays currently active filters when opened
|
||||||
|
- Initialize directly from persisted state: `location: tableFilterState.locationFilter`
|
||||||
|
|
||||||
|
2. **Pass `true` as last parameter to updateFilter calls**
|
||||||
|
- `updateFilter('fieldName', value, true)` immediately persists to localStorage
|
||||||
|
- Resets pagination to page 1 when filters change (via SWR revalidation)
|
||||||
|
- Apply to: search handlers, filter form submissions, reset handlers
|
||||||
|
|
||||||
|
3. **Create custom formikResetHandler function**
|
||||||
|
- Clear each filter with `updateFilter(fieldName, defaultValue, true)`
|
||||||
|
- Call `formik.resetForm({ values: { ...defaults } })`
|
||||||
|
- Close the modal at the end
|
||||||
|
- Attach to both button `onClick` and form `onReset` handler
|
||||||
|
|
||||||
|
**Optimization: Avoid useCallback for simple handlers**
|
||||||
|
|
||||||
|
- `useCallback` adds overhead and is only useful for complex logic or memoized child components
|
||||||
|
- Simple pass-through handlers don't need it:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// ✅ Good: Simple handler without useCallback
|
||||||
|
const handleFilterChange = (val) => setFieldValue('location', val);
|
||||||
|
|
||||||
|
// ❌ Avoid: Unnecessary useCallback overhead
|
||||||
|
const handleFilterChange = useCallback(
|
||||||
|
(val) => setFieldValue('location', val),
|
||||||
|
[setFieldValue]
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Best practice: Store OptionType objects directly, not IDs**
|
||||||
|
|
||||||
|
For select inputs, store the complete `OptionType` object in both formik state and tableFilterState. This eliminates the need for computed helper values (like searching options arrays to find the matching object).
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Type the useTableFilter with the filter state structure
|
||||||
|
const { state: tableFilterState, updateFilter, ... } = useTableFilter<{
|
||||||
|
search: string;
|
||||||
|
locationFilter?: OptionType<string>;
|
||||||
|
picFilter?: OptionType<string>;
|
||||||
|
}>({
|
||||||
|
initial: {
|
||||||
|
search: '',
|
||||||
|
locationFilter: undefined,
|
||||||
|
picFilter: undefined
|
||||||
|
},
|
||||||
|
paramMap: {
|
||||||
|
page: 'page',
|
||||||
|
pageSize: 'limit',
|
||||||
|
locationFilter: 'location_id',
|
||||||
|
picFilter: 'pic_id',
|
||||||
|
},
|
||||||
|
persist: true,
|
||||||
|
storeName: 'kandangs-table',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize formik with tableFilterState values (now typed OptionType objects)
|
||||||
|
const formik = useFormik<KandangFilterType>({
|
||||||
|
initialValues: {
|
||||||
|
location: tableFilterState.locationFilter,
|
||||||
|
pic: tableFilterState.picFilter,
|
||||||
|
},
|
||||||
|
...
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handlers store the complete OptionType, not just the ID
|
||||||
|
const handleFilterLocationChange = useCallback(
|
||||||
|
(val) => setFieldValue('location', val),
|
||||||
|
[setFieldValue]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use formik values directly in select inputs (no computed helpers needed)
|
||||||
|
<SelectInput
|
||||||
|
value={formik.values.location}
|
||||||
|
onChange={handleFilterLocationChange}
|
||||||
|
...
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Apply this pattern to:**
|
||||||
|
|
||||||
|
- Any data table component across any module that needs persistent filters
|
||||||
|
- Master-data tables, inventory lists, finance reports, purchase orders, etc.
|
||||||
|
- Whenever users' filter/search/pagination choices should survive page refreshes
|
||||||
|
|
||||||
|
**Reference implementations:**
|
||||||
|
|
||||||
|
- `SupplierTable`, `KandangsTable`, `LocationsTable`, `CustomersTable` in `src/components/pages/master-data/`
|
||||||
|
- Use same pattern for data tables in other modules (inventory, finance, purchase, etc.)
|
||||||
|
|||||||
Reference in New Issue
Block a user