From ff2ed8757f555dda5ed67371daf67972fb3352e6 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 27 Apr 2026 10:43:30 +0700 Subject: [PATCH 01/10] fix: set fallback timeout to 30s --- src/services/http/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/http/client.ts b/src/services/http/client.ts index cb22c2f4..c9fbfe2c 100644 --- a/src/services/http/client.ts +++ b/src/services/http/client.ts @@ -38,7 +38,7 @@ export async function httpClient( method: opts.method ?? 'GET', params: opts.query, data: opts.body, - timeout: opts.timeoutMs ?? 10_000, + timeout: opts.timeoutMs ?? 30_000, withCredentials: isCookieAuth && !isBearerAuth, responseType: opts.responseType, headers: { From 4206408db19f47d841ef975077cc3b9b7bc5e781 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 27 Apr 2026 10:48:42 +0700 Subject: [PATCH 02/10] fix: enable custom period --- .../form/ProjectFlockForm.schema.ts | 7 +++++++ .../project-flock/form/ProjectFlockForm.tsx | 20 ++++++++++++++++--- src/types/api/production/project-flock.d.ts | 1 + 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts index eb6f236c..4c94c6df 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts @@ -26,6 +26,7 @@ type ProjectFlockFormSchemaType = { label: string; } | null; location_id: number; + period: number | string; kandang_ids: number[]; project_budgets: ProjectFlockBudgetsSchemaType[]; }; @@ -109,6 +110,12 @@ export const ProjectFlockFormSchema: Yup.ObjectSchema k.id ) as number[], + period: initialValues?.period ?? '', project_budgets: initialValues?.project_budgets?.map((budget) => { return { nonstock: { @@ -568,6 +573,7 @@ const ProjectFlockForm = ({ category: values.category as string, production_standard_id: values.production_standard_id as number, location_id: values.location_id as number, + period: parseInt(values.period as unknown as string), kandang_ids: values.kandang_ids as number[], project_budgets: values.project_budgets.flatMap((budget) => { return { @@ -1025,10 +1031,18 @@ const ProjectFlockForm = ({ + formik.setFieldValue('period', e.target.value) + } + onBlur={formik.handleBlur} + allowNegative={false} + decimalScale={0} + isError={ + formik.touched.period && Boolean(formik.errors.period) + } + errorMessage={formik.errors.period as string} /> diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index 41a6a1c0..d1f5bb02 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -51,6 +51,7 @@ export type CreateProjectFlockPayload = { category: string; production_standard_id: number; location_id: number; + period: number; kandang_ids: number[]; project_budgets?: ProjectFlockBudget[]; }; From 6cf8e463c6569f1b1cbd857f7a705424f8d0de3c Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 27 Apr 2026 10:48:56 +0700 Subject: [PATCH 03/10] feat: create CLAUDE.md --- CLAUDE.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..8b6b07a2 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,65 @@ +# LTI Web Client + +Next.js 15 (App Router) + React 19 + TypeScript front-end for the LTI ERP system. + +## Tech stack + +- **Framework:** Next.js 15.5 (App Router, Turbopack) +- **UI:** React 19, Tailwind CSS v4, Radix UI, daisyUI, lucide-react +- **State:** zustand +- **Forms:** Formik + Yup, react-hook-form +- **Data fetching:** axios + SWR (custom `httpClient` / `httpClientFetcher` in `src/services/http`) +- **Tables:** @tanstack/react-table +- **Reporting:** @react-pdf/renderer, jspdf, exceljs, xlsx, recharts + +## Scripts + +- `npm run dev` — lint + dev server (Turbopack) +- `npm run build` — production build +- `npm run lint` — ESLint +- `npm run typecheck` — `next typegen && tsc --noEmit` +- `npm run format` — Prettier +- `npm run pre-commit` — format + lint + typecheck + build (Husky pre-commit hook) + +## Project structure + +``` +src/ + app/ # Next.js App Router routes (one folder per feature) + components/ + pages/{feature}/ # Page-specific components (mirrors src/app) + helper/ # Cross-cutting helpers (e.g. SuspenseHelper) + ui/ # Shared UI primitives + services/ + api/ # API service classes (extend BaseApiService) + http/ # httpClient / httpClientFetcher + hooks/ # Service-level hooks + stores/ # zustand stores grouped by domain + types/api/ # Request/response types per feature + lib/ # Shared helpers (api-helper, formik-helper, utils, validation, …) + config/, styles/ +``` + +## Feature development standard + +**Always follow this order when adding a new feature.** This is a team convention — deviating creates churn in code review. + +1. **Types** — Define payload and response types in `src/types/api/{feature}` (or `{feature}.d.ts` for small features). +2. **API service** — Add `src/services/api/{feature}.ts` exporting a class that extends `BaseApiService` from [src/services/api/base.ts](src/services/api/base.ts). Use a subfolder (e.g. `src/services/api/daily-checklist/`) when the feature has multiple resource classes. +3. **Page** — Create the route under `src/app/{feature}` and a matching `src/components/pages/{feature}` folder for its components. +4. **Component slicing** — Break the page UI into components inside `src/components/pages/{feature}`. +5. **Wire up the API** — Consume the service class from step 2 inside the page/components (often via SWR). +6. **Detail layout** — When a route reads URL params via `useSearchParams` (e.g. `/feature/detail?id=123`), add `src/app/{feature}/detail/layout.tsx` that wraps `children` in `` from `@/components/helper/SuspenseHelper`. +7. **Shared state** — Use zustand stores in `src/stores/{domain}` when state must cross component boundaries. +8. **Helpers** — Reuse from [src/lib](src/lib) first (`api-helper.ts`, `formik-helper.ts`, `utils/`, `validation/`, etc.). Add new helpers there. + +### Reference implementations + +`closing`, `finance`, `expense`, `production`, `inventory`, `marketing`, `master-data`, `purchase`, `report`, `daily-checklist`, `dashboard` — all live in both `src/app/{feature}` and `src/components/pages/{feature}` and follow the standard above. + +## Conventions + +- Path alias `@/` maps to `src/`. +- Detail pages that read `useSearchParams` MUST be wrapped in `` 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. +- Pre-commit runs format + lint + typecheck + build; do not bypass with `--no-verify`. From f9d2a875e223f7df3a95504c3b20ddf925f78281 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 27 Apr 2026 10:49:07 +0700 Subject: [PATCH 04/10] chore: prettier format --- src/components/pages/finance/FinanceTable.tsx | 11 +- .../adjustment/InventoryAdjustmentTable.tsx | 31 ++- .../inventory/movement/MovementTable.tsx | 34 ++- .../product/InventoryProductTable.tsx | 203 ++++++++++-------- .../project-flock/ProjectFlockTable.tsx | 51 ++++- .../TransferToLayingsTable.tsx | 20 +- .../report/expense/tab/ReportExpenseTab.tsx | 32 ++- .../report/finance/tab/CustomerPaymentTab.tsx | 4 +- .../report/finance/tab/DebtSupplierTab.tsx | 7 +- ...ProductionResultProjectFlockKandangTab.tsx | 29 ++- .../daily-checklist/DailyChecklistContent.tsx | 6 +- 11 files changed, 280 insertions(+), 148 deletions(-) diff --git a/src/components/pages/finance/FinanceTable.tsx b/src/components/pages/finance/FinanceTable.tsx index 2665dabb..7bf62aba 100644 --- a/src/components/pages/finance/FinanceTable.tsx +++ b/src/components/pages/finance/FinanceTable.tsx @@ -265,7 +265,11 @@ const FinanceTable = () => { updateFilter('endDate', values.end_date); // Save display names for restoration on modal reopen const toNames = (val: OptionType | OptionType[] | null) => - val ? (Array.isArray(val) ? val : [val]).map((o) => String(o.label)).join(',') : ''; + val + ? (Array.isArray(val) ? val : [val]) + .map((o) => String(o.label)) + .join(',') + : ''; updateFilter('bankNames', toNames(selectedBank)); updateFilter('customerNames', toNames(selectedCustomerId)); updateFilter('supplierNames', toNames(selectedSupplierId)); @@ -516,8 +520,9 @@ const FinanceTable = () => { // Restore sort by const restoredSortBy = - sortByOptions.find((opt) => String(opt.value) === tableFilterState.sortBy) || - null; + sortByOptions.find( + (opt) => String(opt.value) === tableFilterState.sortBy + ) || null; setSelectedSortBy(restoredSortBy); // Restore formik values diff --git a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx index 1ca68235..c04dabec 100644 --- a/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx +++ b/src/components/pages/inventory/adjustment/InventoryAdjustmentTable.tsx @@ -153,8 +153,14 @@ const InventoryAdjustmentTable = () => { updateFilter('productFilter', values.product_id || ''); updateFilter('warehouseFilter', values.warehouse_id || ''); updateFilter('transactionTypeFilter', values.transaction_type || ''); - updateFilter('productName', productIdValue?.label ? String(productIdValue.label) : ''); - updateFilter('warehouseName', warehouseIdValue?.label ? String(warehouseIdValue.label) : ''); + updateFilter( + 'productName', + productIdValue?.label ? String(productIdValue.label) : '' + ); + updateFilter( + 'warehouseName', + warehouseIdValue?.label ? String(warehouseIdValue.label) : '' + ); filterModal.closeModal(); setSubmitting(false); }, @@ -216,7 +222,10 @@ const InventoryAdjustmentTable = () => { val: OptionType | OptionType[] | null ) => { const warehouse = val as OptionType | null; - formik.setFieldValue('warehouse_id', warehouse?.value ? String(warehouse.value) : null); + formik.setFieldValue( + 'warehouse_id', + warehouse?.value ? String(warehouse.value) : null + ); }; const handleFilterTransactionTypeChange = useCallback( @@ -236,7 +245,10 @@ const InventoryAdjustmentTable = () => { ); if (found) return found; if (tableFilterState.productName) { - return { value: formik.values.product_id, label: tableFilterState.productName }; + return { + value: formik.values.product_id, + label: tableFilterState.productName, + }; } return null; }, [formik.values.product_id, productOptions, tableFilterState.productName]); @@ -248,10 +260,17 @@ const InventoryAdjustmentTable = () => { ); if (found) return found; if (tableFilterState.warehouseName) { - return { value: formik.values.warehouse_id, label: tableFilterState.warehouseName }; + return { + value: formik.values.warehouse_id, + label: tableFilterState.warehouseName, + }; } return null; - }, [formik.values.warehouse_id, warehouseOptions, tableFilterState.warehouseName]); + }, [ + formik.values.warehouse_id, + warehouseOptions, + tableFilterState.warehouseName, + ]); const transactionTypeValue = useMemo(() => { if (!formik.values.transaction_type) return null; diff --git a/src/components/pages/inventory/movement/MovementTable.tsx b/src/components/pages/inventory/movement/MovementTable.tsx index d2a808d3..58bddf08 100644 --- a/src/components/pages/inventory/movement/MovementTable.tsx +++ b/src/components/pages/inventory/movement/MovementTable.tsx @@ -149,8 +149,14 @@ const MovementTable = () => { onSubmit: (values, { setSubmitting }) => { updateFilter('productFilter', values.product_id || ''); updateFilter('warehouseFilter', values.warehouse_id || ''); - updateFilter('productName', productIdValue?.label ? String(productIdValue.label) : ''); - updateFilter('warehouseName', warehouseIdValue?.label ? String(warehouseIdValue.label) : ''); + updateFilter( + 'productName', + productIdValue?.label ? String(productIdValue.label) : '' + ); + updateFilter( + 'warehouseName', + warehouseIdValue?.label ? String(warehouseIdValue.label) : '' + ); filterModal.closeModal(); setSubmitting(false); }, @@ -216,7 +222,10 @@ const MovementTable = () => { ); if (found) return found; if (tableFilterState.productName) { - return { value: formik.values.product_id, label: tableFilterState.productName }; + return { + value: formik.values.product_id, + label: tableFilterState.productName, + }; } return null; }, [formik.values.product_id, productOptions, tableFilterState.productName]); @@ -228,10 +237,17 @@ const MovementTable = () => { ); if (found) return found; if (tableFilterState.warehouseName) { - return { value: formik.values.warehouse_id, label: tableFilterState.warehouseName }; + return { + value: formik.values.warehouse_id, + label: tableFilterState.warehouseName, + }; } return null; - }, [formik.values.warehouse_id, warehouseOptions, tableFilterState.warehouseName]); + }, [ + formik.values.warehouse_id, + warehouseOptions, + tableFilterState.warehouseName, + ]); // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { @@ -403,7 +419,13 @@ const MovementTable = () => { diff --git a/src/components/pages/inventory/product/InventoryProductTable.tsx b/src/components/pages/inventory/product/InventoryProductTable.tsx index ad712e24..ae1a5093 100644 --- a/src/components/pages/inventory/product/InventoryProductTable.tsx +++ b/src/components/pages/inventory/product/InventoryProductTable.tsx @@ -113,7 +113,10 @@ const InventoryProductTable = () => { validationSchema: object().shape({ category_id: string().nullable() }), onSubmit: (values, { setSubmitting }) => { updateFilter('categoryFilter', values.category_id || ''); - updateFilter('categoryName', categoryIdValue?.label ? String(categoryIdValue.label) : ''); + updateFilter( + 'categoryName', + categoryIdValue?.label ? String(categoryIdValue.label) : '' + ); filterModal.closeModal(); setSubmitting(false); }, @@ -145,10 +148,17 @@ const InventoryProductTable = () => { ); if (found) return found; if (tableFilterState.categoryName) { - return { value: formik.values.category_id, label: tableFilterState.categoryName }; + return { + value: formik.values.category_id, + label: tableFilterState.categoryName, + }; } return null; - }, [formik.values.category_id, categoryOptions, tableFilterState.categoryName]); + }, [ + formik.values.category_id, + categoryOptions, + tableFilterState.categoryName, + ]); // ===== HANDLE FILTER MODAL OPEN ===== const handleFilterModalOpen = () => { @@ -156,9 +166,14 @@ const InventoryProductTable = () => { filterModal.openModal(); }; - const handleFilterCategoryChange = (val: OptionType | OptionType[] | null) => { + const handleFilterCategoryChange = ( + val: OptionType | OptionType[] | null + ) => { const category = val as OptionType | null; - formik.setFieldValue('category_id', category?.value ? String(category.value) : null); + formik.setFieldValue( + 'category_id', + category?.value ? String(category.value) : null + ); }; const [sorting, setSorting] = useState([]); @@ -254,102 +269,106 @@ const InventoryProductTable = () => { return ( <> -
- {/* Header Section */} -
- {/* Action Buttons */} -
- - - -
- - {/* Search and Filter */} -
- - } - className={{ - wrapper: 'w-full min-w-24 max-w-3xs', - inputWrapper: 'rounded-xl! shadow-button-soft', - input: - 'placeholder:font-semibold placeholder:text-base-content/50', - }} - /> - -
-
- - {/* Table Section */} -
- {isLoading ? ( -
- +
+ {/* Header Section */} +
+ {/* Action Buttons */} +
+ + +
- ) : !isResponseSuccess(inventoryProducts) || - inventoryProducts.data?.length === 0 ? ( -
- + } + className={{ + wrapper: 'w-full min-w-24 max-w-3xs', + inputWrapper: 'rounded-xl! shadow-button-soft', + input: + 'placeholder:font-semibold placeholder:text-base-content/50', + }} + /> +
- ) : ( - - data={ - isResponseSuccess(inventoryProducts) - ? inventoryProducts?.data - : [] - } - columns={columns} - pageSize={tableFilterState.pageSize} - page={ - isResponseSuccess(inventoryProducts) - ? inventoryProducts?.meta?.page - : 0 - } - totalItems={ - isResponseSuccess(inventoryProducts) - ? inventoryProducts?.meta?.total_results - : 0 - } - onPageChange={setPage} - onPageSizeChange={setPageSize} - isLoading={isLoading} - sorting={sorting} - setSorting={setSorting} - className={{ - containerClassName: cn('p-3 mb-0'), - headerColumnClassName: 'text-nowrap', - }} - /> - )} +
+ + {/* Table Section */} +
+ {isLoading ? ( +
+ +
+ ) : !isResponseSuccess(inventoryProducts) || + inventoryProducts.data?.length === 0 ? ( +
+ + } + /> +
+ ) : ( + + data={ + isResponseSuccess(inventoryProducts) + ? inventoryProducts?.data + : [] + } + columns={columns} + pageSize={tableFilterState.pageSize} + page={ + isResponseSuccess(inventoryProducts) + ? inventoryProducts?.meta?.page + : 0 + } + totalItems={ + isResponseSuccess(inventoryProducts) + ? inventoryProducts?.meta?.total_results + : 0 + } + onPageChange={setPage} + onPageSizeChange={setPageSize} + isLoading={isLoading} + sorting={sorting} + setSorting={setSorting} + className={{ + containerClassName: cn('p-3 mb-0'), + headerColumnClassName: 'text-nowrap', + }} + /> + )} +
-
{/* Filter Modal */} void }) => { updateFilter('kandang_id', values.kandang_id || ''); updateFilter('category', values.category || ''); updateFilter('period', values.period || ''); - updateFilter('area_name', areaValue?.label ? String(areaValue.label) : ''); - updateFilter('location_name', locationValue?.label ? String(locationValue.label) : ''); - updateFilter('kandang_name', kandangValue?.label ? String(kandangValue.label) : ''); + updateFilter( + 'area_name', + areaValue?.label ? String(areaValue.label) : '' + ); + updateFilter( + 'location_name', + locationValue?.label ? String(locationValue.label) : '' + ); + updateFilter( + 'kandang_name', + kandangValue?.label ? String(kandangValue.label) : '' + ); filterModal.closeModal(); setSubmitting(false); }, @@ -329,10 +338,15 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { // ===== FILTER HELPERS ===== const areaValue = useMemo(() => { if (!formik.values.area_id) return null; - const found = areaOptions.find((opt) => String(opt.value) === formik.values.area_id); + const found = areaOptions.find( + (opt) => String(opt.value) === formik.values.area_id + ); if (found) return found; if (tableFilterState.area_name) { - return { value: formik.values.area_id, label: tableFilterState.area_name }; + return { + value: formik.values.area_id, + label: tableFilterState.area_name, + }; } return null; }, [formik.values.area_id, areaOptions, tableFilterState.area_name]); @@ -344,10 +358,17 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { ); if (found) return found; if (tableFilterState.location_name) { - return { value: formik.values.location_id, label: tableFilterState.location_name }; + return { + value: formik.values.location_id, + label: tableFilterState.location_name, + }; } return null; - }, [formik.values.location_id, locationOptions, tableFilterState.location_name]); + }, [ + formik.values.location_id, + locationOptions, + tableFilterState.location_name, + ]); const kandangValue = useMemo(() => { if (!formik.values.kandang_id) return null; @@ -356,7 +377,10 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { ); if (found) return found; if (tableFilterState.kandang_name) { - return { value: formik.values.kandang_id, label: tableFilterState.kandang_name }; + return { + value: formik.values.kandang_id, + label: tableFilterState.kandang_name, + }; } return null; }, [formik.values.kandang_id, kandangOptions, tableFilterState.kandang_name]); @@ -446,7 +470,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { }; const searchChangeHandler: ChangeEventHandler = (e) => { - updateFilter('search', e.target.value); + updateFilter('search', e.target.value, true); }; const confirmApprovalHandler = async ( @@ -984,7 +1008,14 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx index 3891cd4a..6bf8ecb0 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx @@ -496,11 +496,23 @@ const TransferToLayingsTable = () => { updateFilter('startDate', values.startDate || ''); updateFilter('endDate', values.endDate || ''); - updateFilter('flockSource', flockSourceOpts.map((o) => String(o.value)).join(',')); - updateFilter('flockDestination', flockDestOpts.map((o) => String(o.value)).join(',')); + updateFilter( + 'flockSource', + flockSourceOpts.map((o) => String(o.value)).join(',') + ); + updateFilter( + 'flockDestination', + flockDestOpts.map((o) => String(o.value)).join(',') + ); updateFilter('status', statusOpts.map((o) => String(o.value)).join(',')); - updateFilter('flockSourceNames', flockSourceOpts.map((o) => String(o.label)).join(',')); - updateFilter('flockDestinationNames', flockDestOpts.map((o) => String(o.label)).join(',')); + updateFilter( + 'flockSourceNames', + flockSourceOpts.map((o) => String(o.label)).join(',') + ); + updateFilter( + 'flockDestinationNames', + flockDestOpts.map((o) => String(o.label)).join(',') + ); }; const filterResetHandler = () => { diff --git a/src/components/pages/report/expense/tab/ReportExpenseTab.tsx b/src/components/pages/report/expense/tab/ReportExpenseTab.tsx index 78627fc7..edd5b725 100644 --- a/src/components/pages/report/expense/tab/ReportExpenseTab.tsx +++ b/src/components/pages/report/expense/tab/ReportExpenseTab.tsx @@ -127,23 +127,37 @@ const ReportExpenseTab = ({ tabId }: ReportExpenseTabProps) => { handleFilterModalOpenRef.current = () => { const restoredLocation = filterParams.location_id - ? locationOptions.find((opt) => String(opt.value) === filterParams.location_id) || - { value: filterParams.location_id, label: filterParams.location_id } + ? locationOptions.find( + (opt) => String(opt.value) === filterParams.location_id + ) || { + value: filterParams.location_id, + label: filterParams.location_id, + } : null; const restoredSupplier = filterParams.supplier_id - ? supplierOptions.find((opt) => String(opt.value) === filterParams.supplier_id) || - { value: filterParams.supplier_id, label: filterParams.supplier_id } + ? supplierOptions.find( + (opt) => String(opt.value) === filterParams.supplier_id + ) || { + value: filterParams.supplier_id, + label: filterParams.supplier_id, + } : null; const restoredKandang = filterParams.kandang_id - ? projectFlockKandangOptions.find((opt) => String(opt.value) === filterParams.kandang_id) || - { value: filterParams.kandang_id, label: filterParams.kandang_id } + ? projectFlockKandangOptions.find( + (opt) => String(opt.value) === filterParams.kandang_id + ) || { value: filterParams.kandang_id, label: filterParams.kandang_id } : null; const restoredNonstock = filterParams.nonstock_id - ? nonstockOptions.find((opt) => String(opt.value) === filterParams.nonstock_id) || - { value: filterParams.nonstock_id, label: filterParams.nonstock_id } + ? nonstockOptions.find( + (opt) => String(opt.value) === filterParams.nonstock_id + ) || { + value: filterParams.nonstock_id, + label: filterParams.nonstock_id, + } : null; const restoredCategory = filterParams.category - ? categoryOptions.find((opt) => opt.value === filterParams.category) || null + ? categoryOptions.find((opt) => opt.value === filterParams.category) || + null : null; formik.setValues({ diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx index 8ca4a3ec..ede0ab05 100644 --- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx +++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx @@ -742,9 +742,7 @@ const CustomerPaymentTab = ({ tabId }: CustomerPaymentTabProps) => { } onNextPage={() => setCurrentPage((curr) => - meta.total_pages && curr < meta.total_pages - ? curr + 1 - : curr + meta.total_pages && curr < meta.total_pages ? curr + 1 : curr ) } onPageChange={(pageNumber) => setCurrentPage(pageNumber)} diff --git a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx index c9a772b4..4b3aeaad 100644 --- a/src/components/pages/report/finance/tab/DebtSupplierTab.tsx +++ b/src/components/pages/report/finance/tab/DebtSupplierTab.tsx @@ -149,7 +149,8 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { handleFilterModalOpenRef.current = () => { const restoredFilterBy = - dataTypeOptions.find((opt) => opt.value === filterParams.filter_by) || null; + dataTypeOptions.find((opt) => opt.value === filterParams.filter_by) || + null; const supplierIdList = filterParams.supplier_ids ? filterParams.supplier_ids.split(',') @@ -673,9 +674,7 @@ const DebtSupplierTab = ({ tabId }: DebtSupplierTabProps) => { } onNextPage={() => setCurrentPage((curr) => - meta.total_pages && curr < meta.total_pages - ? curr + 1 - : curr + meta.total_pages && curr < meta.total_pages ? curr + 1 : curr ) } onPageChange={(pageNumber) => setCurrentPage(pageNumber)} diff --git a/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx b/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx index 973b3083..08e44a04 100644 --- a/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx +++ b/src/components/pages/report/production-result/tab/ProductionResultProjectFlockKandangTab.tsx @@ -264,20 +264,33 @@ const ProductionResultContent = ({ tabId }: ProductionResultTabProps) => { handleFilterModalOpenRef.current = () => { const restoredAreaId = filterParams.area_id - ? areaOptions.find((opt) => String(opt.value) === filterParams.area_id) || - { value: filterParams.area_id, label: filterParams.area_id } + ? areaOptions.find( + (opt) => String(opt.value) === filterParams.area_id + ) || { value: filterParams.area_id, label: filterParams.area_id } : null; const restoredLocationId = filterParams.location_id - ? locationOptions.find((opt) => String(opt.value) === filterParams.location_id) || - { value: filterParams.location_id, label: filterParams.location_id } + ? locationOptions.find( + (opt) => String(opt.value) === filterParams.location_id + ) || { + value: filterParams.location_id, + label: filterParams.location_id, + } : null; const restoredProjectFlockId = filterParams.project_flock_id - ? projectFlockOptions.find((opt) => String(opt.value) === filterParams.project_flock_id) || - { value: filterParams.project_flock_id, label: filterParams.project_flock_id } + ? projectFlockOptions.find( + (opt) => String(opt.value) === filterParams.project_flock_id + ) || { + value: filterParams.project_flock_id, + label: filterParams.project_flock_id, + } : null; const restoredKandangId = filterParams.project_flock_kandang_id - ? projectFlockKandangOptions.find((opt) => String(opt.value) === filterParams.project_flock_kandang_id) || - { value: filterParams.project_flock_kandang_id, label: filterParams.project_flock_kandang_id } + ? projectFlockKandangOptions.find( + (opt) => String(opt.value) === filterParams.project_flock_kandang_id + ) || { + value: filterParams.project_flock_kandang_id, + label: filterParams.project_flock_kandang_id, + } : null; formik.setValues({ diff --git a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx index b83c7b6a..50119101 100644 --- a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx +++ b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx @@ -880,9 +880,9 @@ export function DailyChecklistContent() { setChecklistStatus('SUBMITTED'); const shareToWhatsApp = () => { - const kandangName = kandangOptions.find( - (k) => String(k.value) === kandangId - )?.label || kandangId; + const kandangName = + kandangOptions.find((k) => String(k.value) === kandangId)?.label || + kandangId; const statusMsg = getStatusMessage(); const category = selectedCategory || ''; const message = encodeURIComponent( From 7ab9518a556297d765a90307d0fb7372ae2ffed6 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 27 Apr 2026 12:02:43 +0700 Subject: [PATCH 05/10] fix: change 'period' to 'periode' --- .../project-flock/form/ProjectFlockForm.schema.ts | 4 ++-- .../project-flock/form/ProjectFlockForm.tsx | 12 ++++++------ src/types/api/production/project-flock.d.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts index 4c94c6df..039ea6b9 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.schema.ts @@ -26,7 +26,7 @@ type ProjectFlockFormSchemaType = { label: string; } | null; location_id: number; - period: number | string; + periode: number | string; kandang_ids: number[]; project_budgets: ProjectFlockBudgetsSchemaType[]; }; @@ -111,7 +111,7 @@ export const ProjectFlockFormSchema: Yup.ObjectSchema k.id ) as number[], - period: initialValues?.period ?? '', + periode: initialValues?.period ?? '', project_budgets: initialValues?.project_budgets?.map((budget) => { return { nonstock: { @@ -573,7 +573,7 @@ const ProjectFlockForm = ({ category: values.category as string, production_standard_id: values.production_standard_id as number, location_id: values.location_id as number, - period: parseInt(values.period as unknown as string), + periode: parseInt(values.periode as unknown as string), kandang_ids: values.kandang_ids as number[], project_budgets: values.project_budgets.flatMap((budget) => { return { @@ -1032,7 +1032,7 @@ const ProjectFlockForm = ({ name='period' label='Periode' placeholder='Periode Flock' - value={formik.values.period} + value={formik.values.periode} onChange={(e) => formik.setFieldValue('period', e.target.value) } @@ -1040,9 +1040,9 @@ const ProjectFlockForm = ({ allowNegative={false} decimalScale={0} isError={ - formik.touched.period && Boolean(formik.errors.period) + formik.touched.periode && Boolean(formik.errors.periode) } - errorMessage={formik.errors.period as string} + errorMessage={formik.errors.periode as string} />
diff --git a/src/types/api/production/project-flock.d.ts b/src/types/api/production/project-flock.d.ts index d1f5bb02..82485828 100644 --- a/src/types/api/production/project-flock.d.ts +++ b/src/types/api/production/project-flock.d.ts @@ -51,7 +51,7 @@ export type CreateProjectFlockPayload = { category: string; production_standard_id: number; location_id: number; - period: number; + periode: number; kandang_ids: number[]; project_budgets?: ProjectFlockBudget[]; }; From 5cccc0b3c61c49ba9116109e223ae05874d3d6da Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 27 Apr 2026 12:03:09 +0700 Subject: [PATCH 06/10] fix: set background color for shared image --- .../detail/DetailDailyChecklistContent.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx index 9af93ee8..8c3e30e7 100644 --- a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx +++ b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx @@ -593,7 +593,9 @@ export function DetailDailyChecklistContent() { let shareData: ShareData; if (isMobile) { - const htmlBlob = await htmlToImage.toBlob(document.body); + const htmlBlob = await htmlToImage.toBlob(document.body, { + backgroundColor: '#ffffff', + }); const imgFile = new File( [htmlBlob!], `daily-checklist-${header?.date}-${header?.kandang_name}-${header?.category}.png`, @@ -606,7 +608,6 @@ export function DetailDailyChecklistContent() { files: [imgFile], title: baseTitle, text: fullMessage, - url: window.location.href, }; } else { shareData = { From 06b5a97de3d28e02c43bdde017e503169e336845 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 27 Apr 2026 12:03:17 +0700 Subject: [PATCH 07/10] chore: prettier format --- .../pages/purchase/order/PurchaseOrderDetail.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx index f1243026..8b17a076 100644 --- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx +++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx @@ -445,7 +445,13 @@ const PurchaseOrderDetail = ({ setEditPoDate(''); editPoDateModal.closeModal(); refetchData?.(); - }, [initialValues?.id, searchParams, editPoDate, editPoDateModal, refetchData]); + }, [ + initialValues?.id, + searchParams, + editPoDate, + editPoDateModal, + refetchData, + ]); // ===== APPROVAL/REJECTION HANDLERS ===== const managerApprovalHandler = async () => { @@ -873,8 +879,7 @@ const PurchaseOrderDetail = ({
- :{' '} - {formatDate(purchaseData.po_date, 'DD MMM YYYY')} + : {formatDate(purchaseData.po_date, 'DD MMM YYYY')} +
+ ); +} + +export function SystemConfigContent() { + const [toggling, setToggling] = useState(null); + + const { + data: settingsResponse, + isLoading, + mutate: refreshSettings, + } = useSWR(SystemSettingsApi.basePath, SystemSettingsApi.getAllFetcher, { + keepPreviousData: true, + }); + + const handleToggle = async (key: string, currentValue: boolean) => { + if (key !== ALLOW_NEGATIVE_PAKAN_OVK_KEY) return; + + setToggling(key); + try { + const res = await SystemSettingsApi.setAllowNegativePakanOvk({ + value: !currentValue, + }); + + if (isResponseError(res)) { + toast.error(res.message || 'Gagal mengubah pengaturan'); + return; + } + + await refreshSettings(); + toast.success( + !currentValue + ? 'Mode migrasi PAKAN & OVK diaktifkan' + : 'Mode migrasi PAKAN & OVK dinonaktifkan' + ); + } catch { + toast.error('Terjadi kesalahan saat mengubah pengaturan'); + } finally { + setToggling(null); + } + }; + + const settings = isResponseSuccess(settingsResponse) + ? settingsResponse.data + : []; + + if (isLoading && !settingsResponse) { + return ( +
+
+
+

+ Konfigurasi Sistem +

+

+ Master Data •{' '} + Konfigurasi Sistem +

+
+ + + Memuat data... + + +
+
+ ); + } + + return ( +
+
+
+

+ Konfigurasi Sistem +

+

+ Master Data •{' '} + Konfigurasi Sistem +

+
+ + + +
+

+ Pengaturan Global +

+

+ Pengaturan ini berlaku untuk seluruh sistem. +

+
+ +
+ {settings.length === 0 ? ( +

+ Tidak ada pengaturan tersedia. +

+ ) : ( + settings.map((setting) => ( + + )) + )} +
+
+
+
+
+ ); +} diff --git a/src/services/api/system-settings.ts b/src/services/api/system-settings.ts new file mode 100644 index 00000000..06ef4ace --- /dev/null +++ b/src/services/api/system-settings.ts @@ -0,0 +1,48 @@ +import axios from 'axios'; +import { httpClient, httpClientFetcher } from '@/services/http/client'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { + SetAllowNegativePakanOvkPayload, + SystemSetting, +} from '@/types/api/system-settings/system-setting'; + +const BASE_PATH = '/system-settings'; + +export const SystemSettingsApi = { + basePath: BASE_PATH, + + getAllFetcher: ( + endpoint: string + ): Promise> => + httpClientFetcher>(endpoint), + + async getAll(): Promise | undefined> { + try { + return await httpClient>(BASE_PATH); + } catch (error) { + if (axios.isAxiosError>(error)) { + return error.response?.data; + } + return undefined; + } + }, + + async setAllowNegativePakanOvk( + payload: SetAllowNegativePakanOvkPayload + ): Promise { + try { + return await httpClient( + `${BASE_PATH}/allow-negative-pakan-ovk`, + { + method: 'PATCH', + body: payload, + } + ); + } catch (error) { + if (axios.isAxiosError(error)) { + return error.response?.data; + } + return undefined; + } + }, +}; diff --git a/src/types/api/system-settings/system-setting.d.ts b/src/types/api/system-settings/system-setting.d.ts new file mode 100644 index 00000000..98b71f6b --- /dev/null +++ b/src/types/api/system-settings/system-setting.d.ts @@ -0,0 +1,11 @@ +export type SystemSetting = { + key: string; + value: string; + description: string; + created_at: string; + updated_at: string; +}; + +export type SetAllowNegativePakanOvkPayload = { + value: boolean; +};