From ea7f8a68f439245372135f196036c5005ab7004a Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 29 Dec 2025 13:19:16 +0700 Subject: [PATCH 01/22] fix(FE): change select option warehouse marketing to correct data warehouses --- .../FormFinanceAddInitialBalance.schema.ts | 13 ------------- .../repeater/delivery-order/DeliverOrderProduct.tsx | 13 ------------- .../repeater/sales-order/SalesOrderProductForm.tsx | 4 ++-- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.schema.ts b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.schema.ts index c700f973..776028c6 100644 --- a/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.schema.ts +++ b/src/components/pages/finance/add/initial-balance/FormFinanceAddInitialBalance.schema.ts @@ -1,19 +1,6 @@ import * as Yup from 'yup'; import { OptionType } from '@/components/input/SelectInput'; -/** - * API Payload format for Initial Balance: - * { - "party_type": "CUSTOMER", - "party_id": 1, - "bank_id": 1, - "reference_number": "IB.MBU.001", - "initial_balance_type": "DEBIT", - "nominal": 5000000, - "note": "Saldo awal piutang customer" - } - */ - // Type for form values (includes option objects for SelectInput) export type InitialBalanceFormValues = { party_type_option: OptionType | null; diff --git a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx index f169eb3c..a0eed811 100644 --- a/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx +++ b/src/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.tsx @@ -174,19 +174,6 @@ const DeliveryOrderProductForm = ({ }} onReset={handleResetForm} > - {/* - {JSON.stringify(exisitingValues)} - - - {JSON.stringify(formik.values)} - */} - {/* - {JSON.stringify(formik.errors)} - -
- {JSON.stringify(formik.values.marketing_product)} -
*/} - {formikErrorMessage && (
setFormErrorMessage('')} className='my-3 w-full'> {formikErrorMessage} diff --git a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx index ad50a927..75aa3ba6 100644 --- a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx +++ b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm.tsx @@ -11,7 +11,7 @@ import SelectInput, { useSelect, } from '@/components/input/SelectInput'; import { Kandang } from '@/types/api/master-data/kandang'; -import { KandangApi } from '@/services/api/master-data'; +import { KandangApi, WarehouseApi } from '@/services/api/master-data'; import { ProductWarehouse } from '@/types/api/inventory/product-warehouse'; import { ProductWarehouseApi } from '@/services/api/inventory'; import NumberInput from '@/components/input/NumberInput'; @@ -61,7 +61,7 @@ const SalesOrderProductForm = ({ const { options: kandangSourceOptions, isLoadingOptions: isLoadingKandangSourceOptions, - } = useSelect(KandangApi.basePath, 'id', 'name'); + } = useSelect(WarehouseApi.basePath, 'id', 'name'); const { options: warehouseSourceOptions, From cd42bd6bc08bf3c891c644bf358499160159af5d Mon Sep 17 00:00:00 2001 From: randy-ar Date: Mon, 29 Dec 2025 13:26:39 +0700 Subject: [PATCH 02/22] fix(FE): change hatchery to optional in master data supplier --- .../pages/master-data/nonstock/form/NonstockForm.tsx | 8 ++++---- .../master-data/supplier/form/SupplierForm.schema.ts | 2 +- .../pages/master-data/supplier/form/SupplierForm.tsx | 9 ++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx index 47875902..af72f22f 100644 --- a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx +++ b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx @@ -79,14 +79,14 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => { uomId: initialValues?.uom_id ?? 0, uom: initialValues?.uom ? { - value: initialValues?.uom.id, - label: initialValues?.uom.name, + value: initialValues?.uom?.id, + label: initialValues?.uom?.name, } : null, supplierIds: - initialValues?.suppliers.map((supplier) => supplier.id) ?? [], + initialValues?.suppliers?.map((supplier) => supplier.id) ?? [], suppliers: - initialValues?.suppliers.map((supplier) => ({ + initialValues?.suppliers?.map((supplier) => ({ value: supplier.id, label: supplier.name, })) ?? [], diff --git a/src/components/pages/master-data/supplier/form/SupplierForm.schema.ts b/src/components/pages/master-data/supplier/form/SupplierForm.schema.ts index 12c70b1c..9ec3890a 100644 --- a/src/components/pages/master-data/supplier/form/SupplierForm.schema.ts +++ b/src/components/pages/master-data/supplier/form/SupplierForm.schema.ts @@ -18,7 +18,7 @@ export const SupplierFormSchema = Yup.object({ value: Yup.string().required(), label: Yup.string().required(), }).required('Tipe wajib diisi!'), - hatchery: Yup.string().required('Hatchery wajib diisi!'), + hatchery: Yup.string().optional(), phone: Yup.string() .matches(/^[0-9]+$/, 'Nomor telepon hanya boleh berisi angka!') .min(10, 'Nomor telepon minimal 10 digit!') diff --git a/src/components/pages/master-data/supplier/form/SupplierForm.tsx b/src/components/pages/master-data/supplier/form/SupplierForm.tsx index d410ac11..2cacea89 100644 --- a/src/components/pages/master-data/supplier/form/SupplierForm.tsx +++ b/src/components/pages/master-data/supplier/form/SupplierForm.tsx @@ -142,7 +142,7 @@ const SupplierForm = ({ pic: values.pic, type: values.type.value, category: values.category.value, - hatchery: values.hatchery, + hatchery: values.hatchery ?? '', phone: values.phone, email: values.email, address: values.address, @@ -171,12 +171,12 @@ const SupplierForm = ({ useEffect(() => { formikSetValues(formikInitialValues); if (formType != 'add') { - const hatcheryArrays = formikInitialValues.hatchery.split(','); - const hatcheryCreatedOptions = hatcheryArrays.map((item) => ({ + const hatcheryArrays = formikInitialValues.hatchery?.split(','); + const hatcheryCreatedOptions = hatcheryArrays?.map((item) => ({ value: item, label: item, })); - setHatcheryOptionValues(hatcheryCreatedOptions); + setHatcheryOptionValues(hatcheryCreatedOptions ?? []); } }, [formikSetValues, formikInitialValues, setHatcheryOptionValues]); useEffect(() => { @@ -302,7 +302,6 @@ const SupplierForm = ({ Date: Mon, 29 Dec 2025 13:50:23 +0700 Subject: [PATCH 03/22] feat(FE): adding production standard select options for project flock --- .../project-flock/ProjectFlockTable.tsx | 4 +-- .../form/ProjectFlockForm.schema.ts | 14 +++++++++ .../project-flock/form/ProjectFlockForm.tsx | 31 +++++++++++++++++++ src/types/api/production/project-flock.d.ts | 3 ++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index 233c43d7..025e7186 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -618,7 +618,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { void }) => { , fcr_id: initialValues?.fcr?.id ?? 0, + production_standard_id: initialValues?.production_standard?.id ?? 0, location_id: initialValues?.location?.id ?? 0, kandang_ids: initialValues?.kandangs?.map( (k: Kandang) => k.id @@ -400,6 +413,7 @@ const ProjectFlockForm = ({ area_id: values.area_id as number, category: values.category as string, fcr_id: values.fcr_id as number, + production_standard_id: values.production_standard_id as number, location_id: values.location_id as number, kandang_ids: values.kandang_ids as number[], project_budgets: values.project_budgets.flatMap((budget) => { @@ -858,6 +872,23 @@ const ProjectFlockForm = ({ isClearable isDisabled={formType != 'add'} /> + { + optionChangeHandler(val, 'production_standard'); + }} + options={optionsProductionStandards} + isLoading={isLoadingProductionStandards} + isError={ + formik.touched.production_standard && + Boolean(formik.errors.production_standard) + } + errorMessage={formik.errors.production_standard as string} + isClearable + isDisabled={formType != 'add'} + /> Date: Mon, 29 Dec 2025 15:01:42 +0700 Subject: [PATCH 04/22] fix(FE): fix delete and update button row in master data production standards --- .../form/ProductionStandardForm.schema.ts | 4 +- .../form/ProductionStandardForm.tsx | 91 +++++++++++-------- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts index 6fc3799b..55e68039 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts @@ -68,7 +68,9 @@ export const createProductionStandardRepeaterFormSchema = ( export const createProductionStandardFormSchema = (category: string) => { return Yup.object({ name: Yup.string().required('Nama wajib diisi!'), - project_category: Yup.string().required('Kategori proyek wajib diisi!'), + project_category: Yup.string() + .min(1, 'Kategori proyek wajib diisi!') + .required('Kategori proyek wajib diisi!'), details: Yup.array().of( createProductionStandardRepeaterFormSchema(category) ), diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx index 99edb852..fe424838 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx @@ -29,6 +29,7 @@ import toast from 'react-hot-toast'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import { useModal } from '@/components/Modal'; import RequirePermission from '@/components/helper/RequirePermission'; +import Tooltip from '@/components/Tooltip'; type TableRowsType = { customRow: boolean; @@ -175,13 +176,15 @@ const ProductionStandardForm = ({ } = useFormStore(); // ===== Formik ===== + // Initial values - only recalculate when initialValue changes (for edit/detail mode) + // For add mode, we load from cache via useEffect instead to avoid race conditions const formikInitialValues = useMemo(() => { - // For add mode, merge cached data with initial values - if (formType === 'add' && formData) { + if (formType === 'add') { + // Don't use formData here - will be loaded via useEffect return { - name: formData.name || '', - project_category: formData.project_category || '', - details: formData.details || [], + name: '', + project_category: '', + details: [], } as ProductionStandardFormValues; } @@ -190,10 +193,11 @@ const ProductionStandardForm = ({ project_category: initialValue?.project_category || '', details: convertStandardValueToFormValues(initialValue?.details || []), } as ProductionStandardFormValues; - }, [initialValue, formData, formType]); + }, [initialValue, formType]); const formik = useFormik({ initialValues: formikInitialValues as ProductionStandardFormValues, - enableReinitialize: true, + // Only enable reinitialize for edit/detail mode, not add mode + enableReinitialize: formType !== 'add', onSubmit: (values) => { switch (formType) { case 'add': @@ -255,36 +259,38 @@ const ProductionStandardForm = ({ const { setValues: repeaterFormikSetValues } = repeaterFormik; // ===== Effect ===== - // Load initial values only when component mounts or when initialValue changes (for edit mode) - // This allows: - // 1. Add mode: Load cached data from formData store - // 2. Edit mode: Load existing data from initialValue - // We use initialValue?.id as dependency to avoid infinite loops + // Load cached data only once on mount for add mode + const [isInitialized, setIsInitialized] = useState(false); + useEffect(() => { - if (formType === 'add' && formData) { - // For add mode, load from cache + if (formType === 'add' && formData && !isInitialized) { + // For add mode, load from cache only on initial mount formikSetValues({ name: formData.name || '', project_category: formData.project_category || '', details: formData.details || [], } as ProductionStandardFormValues); - } else if (formType === 'detail' && initialValue) { - // For detail mode, load from initialValue and convert the details + setIsInitialized(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Only run once on mount + + // For edit/detail mode, update when initialValue changes + useEffect(() => { + if (formType === 'detail' && initialValue) { formikSetValues({ name: initialValue.name || '', project_category: initialValue.project_category || '', details: convertStandardValueToFormValues(initialValue.details || []), } as ProductionStandardFormValues); } else if (formType === 'edit' && initialValue) { - // For edit mode, load from initialValue and convert the details formikSetValues({ name: initialValue.name || '', project_category: initialValue.project_category || '', details: convertStandardValueToFormValues(initialValue.details || []), } as ProductionStandardFormValues); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [formData, initialValue?.id]); // Trigger when formData or initialValue.id changes + }, [initialValue?.id, formType]); // ===== Data Table ===== const tableRows = useMemo(() => { @@ -323,11 +329,6 @@ const ProductionStandardForm = ({ }, [formik.values.details]); const columns = useMemo[]>(() => { const baseColumns: ColumnDef[] = [ - { - header: 'No', - accessorFn: (row, index) => index + 1, - enableSorting: false, - }, { header: 'Minggu', accessorKey: 'week', @@ -407,6 +408,7 @@ const ProductionStandardForm = ({ variant='outline' color='warning' className='p-2' + type='button' onClick={() => handleEditClick(row.row.original.week)} > @@ -415,6 +417,7 @@ const ProductionStandardForm = ({ variant='outline' color='error' className='p-2' + type='button' onClick={() => handleRemoveRow(row.row.original.week)} > @@ -430,7 +433,7 @@ const ProductionStandardForm = ({ ...uniformityColumns, ...(formType !== 'detail' ? [actionColumn] : []), ]; - }, [formik.values.project_category, formType]); + }, [formik.values, formType]); // ===== Handler ===== const handleAddRow = async ( @@ -488,9 +491,11 @@ const ProductionStandardForm = ({ setIsAddingRow(false); }; - const handleRemoveRow = (week: number) => { - const newValues = (formik.values.details || []).filter( - (detail) => detail.week !== week + const handleRemoveRow = async (week: number) => { + // Access formik.values directly to get the latest values + const currentDetails = formik.values.details || []; + const newValues = currentDetails.filter( + (detail) => Number(detail.week) !== Number(week) ); const updatedFormValues = { @@ -745,6 +750,7 @@ const ProductionStandardForm = ({ } required isDisabled={formType === 'detail'} + isClearable />
@@ -1105,16 +1111,27 @@ const ProductionStandardForm = ({ Batal )} - + + {/* Should not be absolute */} + + + + + {/* Dashboard Statistics */} + + + {/* Charts Grid */} +
+ {/* Production Line Chart */} + + + + + {/* Standard Line Chart */} + + + + + {/* Bar Charts Grid - 2 columns */} +
+ {/* FCR Bar Chart */} + + + + + {/* Egg Weight Bar Chart */} + + + +
+
+ + +
+ {/* Modal Header */} +
+
+ +

Filter Data

+
+ +
+ +
+ {/* Rentang Waktu */} +
+ +
+ + — + +
+
+ + {/* Flock */} +
+ formik.setFieldValue('flock', selected)} + errorMessage={formik.errors.flock as string} + options={flockOptions} + isLoading={isLoadingFlockOptions} + isMulti + isError={ + Boolean(formik.errors.flock) && Boolean(formik.touched.flock) + } + /> +
+ + {/* Production */} +
+ + formik.setFieldValue('standard_production_id', selected) + } + errorMessage={formik.errors.standard_production_id as string} + options={standardProductionOptions} + isLoading={isLoadingStandardProductionOptions} + isMulti + isError={ + Boolean(formik.errors.standard_production_id) && + Boolean(formik.touched.standard_production_id) + } + /> +
+ + {/* Standard */} +
+ ({ + value: s, + label: + s === 'hen_day' + ? 'Hen Day' + : s === 'hen_house' + ? 'Hen House' + : s === 'uniformity' + ? 'Uniformity' + : s === 'egg_weight' + ? 'Egg Weight' + : 'Egg Mass', + }))} + options={[ + { value: 'hen_day', label: 'Hen Day' }, + { value: 'hen_house', label: 'Hen House' }, + { value: 'uniformity', label: 'Uniformity' }, + { value: 'egg_weight', label: 'Egg Weight' }, + { value: 'egg_mass', label: 'Egg Mass' }, + ]} + isMulti + onChange={(selected: OptionType | OptionType[] | null) => { + const values = Array.isArray(selected) + ? selected.map((item) => String(item.value)) + : []; + setSelectedStandards( + values.length > 0 ? values : ['hen_day'] + ); + }} + isError={ + Boolean(formik.errors.standard_productions) && + Boolean(formik.touched.standard_productions) + } + /> +
+ + {/* Periode Perbandingan */} +
+ +
+ + + + +
+
+ + {/* Action Buttons */} +
+ + +
+
+
+
+ + ); +}; + +export default DashboardProduction; diff --git a/src/components/pages/dashboard/chart/EggWeightBarChart.tsx b/src/components/pages/dashboard/chart/EggWeightBarChart.tsx new file mode 100644 index 00000000..7a9a02c6 --- /dev/null +++ b/src/components/pages/dashboard/chart/EggWeightBarChart.tsx @@ -0,0 +1,89 @@ +'use client'; + +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Cell, +} from 'recharts'; +import { DashboardProductionEggWeights } from '@/types/api/dashboard/dashboard-production'; + +interface EggWeightBarChartProps { + data?: DashboardProductionEggWeights[]; +} + +const EggWeightBarChart = ({ data }: EggWeightBarChartProps) => { + // Show loading state if no data + if (!data || data.length === 0) { + return ( +
+

+ Rata-rata Berat Telur (EW) +

+
+

Memuat data...

+
+
+ ); + } + + return ( +
+

Rata-rata Berat Telur (EW)

+ + + + + + + value !== undefined ? [`${value} gram`, ''] : ['', ''] + } + cursor={{ fill: 'rgba(59, 130, 246, 0.1)' }} + /> + + {data.map((entry, index) => ( + + ))} + + + +
+ ); +}; + +export default EggWeightBarChart; diff --git a/src/components/pages/dashboard/chart/FCRBarChart.tsx b/src/components/pages/dashboard/chart/FCRBarChart.tsx new file mode 100644 index 00000000..2647c7f7 --- /dev/null +++ b/src/components/pages/dashboard/chart/FCRBarChart.tsx @@ -0,0 +1,97 @@ +'use client'; + +import { + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Cell, +} from 'recharts'; +import { DashboardProductionFcrData } from '@/types/api/dashboard/dashboard-production'; + +interface FCRBarChartProps { + data?: DashboardProductionFcrData[]; +} + +// Alternating colors: green and red +const colors = ['#10b981', '#ef4444']; + +const FCRBarChart = ({ data }: FCRBarChartProps) => { + // Show loading state if no data + if (!data || data.length === 0) { + return ( +
+

+ Feed Conversion Ratio (FCR) +

+
+

Memuat data...

+
+
+ ); + } + + return ( +
+

+ Feed Conversion Ratio (FCR) +

+ + + + + + + value !== undefined ? [value.toFixed(2), 'FCR'] : ['', ''] + } + cursor={{ fill: 'rgba(16, 185, 129, 0.1)' }} + /> + + {data.map((entry, index) => ( + + ))} + + + +
+ ); +}; + +export default FCRBarChart; diff --git a/src/components/pages/dashboard/chart/ProductionLineChart.tsx b/src/components/pages/dashboard/chart/ProductionLineChart.tsx new file mode 100644 index 00000000..470e09c9 --- /dev/null +++ b/src/components/pages/dashboard/chart/ProductionLineChart.tsx @@ -0,0 +1,357 @@ +'use client'; + +import { useState } from 'react'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + +// Sample data in API format +const sampleApiData: ProductionChartItem[] = [ + { + date: '2025-12-01T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 88 }, + { id: 2, name: 'Flock A-001', data: 92 }, + { id: 3, name: 'Flock B-001', data: 90 }, + { id: 4, name: 'Flock B-002', data: 85 }, + ], + }, + { + date: '2025-12-03T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 85 }, + { id: 2, name: 'Flock A-001', data: 95 }, + { id: 3, name: 'Flock B-001', data: 93 }, + { id: 4, name: 'Flock B-002', data: 87 }, + ], + }, + { + date: '2025-12-05T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 82 }, + { id: 2, name: 'Flock A-001', data: 98 }, + { id: 3, name: 'Flock B-001', data: 91 }, + { id: 4, name: 'Flock B-002', data: 84 }, + ], + }, + { + date: '2025-12-07T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 80 }, + { id: 2, name: 'Flock A-001', data: 89 }, + { id: 3, name: 'Flock B-001', data: 88 }, + { id: 4, name: 'Flock B-002', data: 82 }, + ], + }, + { + date: '2025-12-08T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 83 }, + { id: 2, name: 'Flock A-001', data: 92 }, + { id: 3, name: 'Flock B-001', data: 95 }, + { id: 4, name: 'Flock B-002', data: 85 }, + ], + }, + { + date: '2025-12-11T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 81 }, + { id: 2, name: 'Flock A-001', data: 88 }, + { id: 3, name: 'Flock B-001', data: 92 }, + { id: 4, name: 'Flock B-002', data: 83 }, + ], + }, + { + date: '2025-12-13T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 84 }, + { id: 2, name: 'Flock A-001', data: 90 }, + { id: 3, name: 'Flock B-001', data: 89 }, + { id: 4, name: 'Flock B-002', data: 86 }, + ], + }, + { + date: '2025-12-15T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 82 }, + { id: 2, name: 'Flock A-001', data: 94 }, + { id: 3, name: 'Flock B-001', data: 96 }, + { id: 4, name: 'Flock B-002', data: 84 }, + ], + }, + { + date: '2025-12-17T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 80 }, + { id: 2, name: 'Flock A-001', data: 91 }, + { id: 3, name: 'Flock B-001', data: 93 }, + { id: 4, name: 'Flock B-002', data: 82 }, + ], + }, + { + date: '2025-12-19T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 79 }, + { id: 2, name: 'Flock A-001', data: 88 }, + { id: 3, name: 'Flock B-001', data: 90 }, + { id: 4, name: 'Flock B-002', data: 81 }, + ], + }, + { + date: '2025-12-21T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 81 }, + { id: 2, name: 'Flock A-001', data: 97 }, + { id: 3, name: 'Flock B-001', data: 92 }, + { id: 4, name: 'Flock B-002', data: 83 }, + ], + }, + { + date: '2025-12-23T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 83 }, + { id: 2, name: 'Flock A-001', data: 95 }, + { id: 3, name: 'Flock B-001', data: 98 }, + { id: 4, name: 'Flock B-002', data: 85 }, + ], + }, + { + date: '2025-12-25T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 80 }, + { id: 2, name: 'Flock A-001', data: 89 }, + { id: 3, name: 'Flock B-001', data: 94 }, + { id: 4, name: 'Flock B-002', data: 82 }, + ], + }, + { + date: '2025-12-27T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 82 }, + { id: 2, name: 'Flock A-001', data: 93 }, + { id: 3, name: 'Flock B-001', data: 96 }, + { id: 4, name: 'Flock B-002', data: 84 }, + ], + }, + { + date: '2025-12-28T00:00:00Z', + flocks: [ + { id: 1, name: 'Flock A-002', data: 85 }, + { id: 2, name: 'Flock A-001', data: 96 }, + { id: 3, name: 'Flock B-001', data: 95 }, + { id: 4, name: 'Flock B-002', data: 87 }, + ], + }, +]; + +// Helper function to format date based on period +const formatDateByPeriod = ( + dateString: string, + period: 'daily' | 'weekly' | 'monthly' | 'yearly' +): string => { + const date = new Date(dateString); + const monthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'Mei', + 'Jun', + 'Jul', + 'Agu', + 'Sep', + 'Okt', + 'Nov', + 'Des', + ]; + + switch (period) { + case 'daily': + // Format: "1 Des" + return `${date.getDate()} ${monthNames[date.getMonth()]}`; + + case 'weekly': + // Format: "Week 1 Des" + const weekNumber = Math.ceil(date.getDate() / 7); + return `Week ${weekNumber} ${monthNames[date.getMonth()]}`; + + case 'monthly': + // Format: "Des" + return monthNames[date.getMonth()]; + + case 'yearly': + // Format: "2025" + return date.getFullYear().toString(); + + default: + return dateString; + } +}; + +// Type definitions for API data +interface FlockData { + id: number; + name: string; + data: number; +} + +interface ProductionChartItem { + date: string; + flocks: FlockData[]; +} + +interface ProductionChartsData { + production_charts: ProductionChartItem[]; +} + +// Transform API data to Recharts format +const transformProductionData = (apiData: ProductionChartItem[]) => { + return apiData.map((item) => { + const transformed: Record = { + date: item.date.split('T')[0], // Extract YYYY-MM-DD from ISO string + }; + + // Add each flock's data as a property + item.flocks.forEach((flock) => { + transformed[flock.name] = flock.data; + }); + + return transformed; + }); +}; + +interface ProductionLineChartProps { + period?: 'daily' | 'weekly' | 'monthly' | 'yearly'; + data?: ProductionChartItem[]; // Optional API data +} + +const ProductionLineChart = ({ + period = 'daily', + data: apiData, +}: ProductionLineChartProps) => { + // State to track which lines are hidden + const [hiddenLines, setHiddenLines] = useState([]); + + // Use API data if provided, otherwise use sample data + const chartData = apiData + ? transformProductionData(apiData) + : transformProductionData(sampleApiData); + + // Handle legend click to show/hide lines + const handleLegendClick = (dataKey: string) => { + setHiddenLines((prev) => + prev.includes(dataKey) + ? prev.filter((key) => key !== dataKey) + : [...prev, dataKey] + ); + }; + + return ( +
+

+ Performa Produksi per Flock +

+ + + + formatDateByPeriod(value, period)} + /> + + + formatDateByPeriod(value as string, period) + } + /> + { + if (e.dataKey) handleLegendClick(e.dataKey as string); + }} + style={{ cursor: 'pointer' }} + /> + + + + + + +
+ ); +}; + +export default ProductionLineChart; + +// Export types for external use +export type { FlockData, ProductionChartItem, ProductionChartsData }; diff --git a/src/components/pages/dashboard/chart/ProductionStat.tsx b/src/components/pages/dashboard/chart/ProductionStat.tsx new file mode 100644 index 00000000..7e299223 --- /dev/null +++ b/src/components/pages/dashboard/chart/ProductionStat.tsx @@ -0,0 +1,107 @@ +import Card from '@/components/Card'; +import { Icon } from '@iconify/react'; +import { DashboardProductionStatisticsData } from '@/types/api/dashboard/dashboard-production'; +import { formatCurrency } from '@/lib/helper'; + +interface ProductionStatProps { + data?: DashboardProductionStatisticsData[]; +} + +const ProductionStat = ({ data }: ProductionStatProps) => { + // Helper function to get icon based on title + const getIcon = (title: string) => { + if (title.toLowerCase().includes('keuangan')) + return 'heroicons:currency-dollar'; + if (title.toLowerCase().includes('penjualan')) + return 'heroicons:arrow-trending-up'; + if (title.toLowerCase().includes('pembelian')) + return 'heroicons:shopping-cart'; + if (title.toLowerCase().includes('overhead')) return 'heroicons:calculator'; + return 'heroicons:chart-bar'; + }; + + // Helper function to get icon background color + const getIconBgColor = (title: string) => { + if (title.toLowerCase().includes('keuangan')) return 'bg-blue-500'; + if (title.toLowerCase().includes('penjualan')) return 'bg-green-500'; + if (title.toLowerCase().includes('pembelian')) return 'bg-orange-500'; + if (title.toLowerCase().includes('overhead')) return 'bg-purple-500'; + return 'bg-gray-500'; + }; + + // Show loading state if no data + if (!data || data.length === 0) { + return ( +
+ {[1, 2, 3, 4].map((i) => ( + +
+
+
+
+
+
+ ))} +
+ ); + } + + return ( +
+ {data.map((stat, index) => ( + +
+
+

{stat.title}

+

+ {formatCurrency(stat.value)} +

+

+ + {stat.change > 0 ? '+' : ''} + {stat.change}% vs{' '} + {stat.period === 'monthly' ? 'bulan lalu' : 'periode lalu'} +

+
+
+
+ +
+
+
+
+ ))} +
+ ); +}; + +export default ProductionStat; diff --git a/src/components/pages/dashboard/chart/StandardLineChart.tsx b/src/components/pages/dashboard/chart/StandardLineChart.tsx new file mode 100644 index 00000000..18bcabf6 --- /dev/null +++ b/src/components/pages/dashboard/chart/StandardLineChart.tsx @@ -0,0 +1,691 @@ +'use client'; + +import { useState } from 'react'; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; + +// Type definitions for API data +interface FlockData { + id: number; + name: string; + data: number; +} + +interface StandardData { + name: string; + value: number; +} + +interface StandardChartItem { + week: number; + standards: StandardData[]; + flocks: FlockData[]; +} + +// Sample data in API format +const sampleApiData: StandardChartItem[] = [ + { + week: 18, + standards: [ + { name: 'hen_day', value: 40 }, + { name: 'hen_house', value: 38 }, + { name: 'uniformity', value: 85 }, + { name: 'egg_weight', value: 52 }, + { name: 'egg_mass', value: 20 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 38 }, + { id: 2, name: 'Flock A-002', data: 37 }, + { id: 3, name: 'Flock B-001', data: 39 }, + { id: 4, name: 'Flock B-002', data: 36 }, + ], + }, + { + week: 20, + standards: [ + { name: 'hen_day', value: 45 }, + { name: 'hen_house', value: 43 }, + { name: 'uniformity', value: 86 }, + { name: 'egg_weight', value: 54 }, + { name: 'egg_mass', value: 24 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 43 }, + { id: 2, name: 'Flock A-002', data: 42 }, + { id: 3, name: 'Flock B-001', data: 44 }, + { id: 4, name: 'Flock B-002', data: 41 }, + ], + }, + { + week: 22, + standards: [ + { name: 'hen_day', value: 48 }, + { name: 'hen_house', value: 46 }, + { name: 'uniformity', value: 87 }, + { name: 'egg_weight', value: 55 }, + { name: 'egg_mass', value: 26 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 47 }, + { id: 2, name: 'Flock A-002', data: 46 }, + { id: 3, name: 'Flock B-001', data: 48 }, + { id: 4, name: 'Flock B-002', data: 45 }, + ], + }, + { + week: 24, + standards: [ + { name: 'hen_day', value: 50 }, + { name: 'hen_house', value: 48 }, + { name: 'uniformity', value: 88 }, + { name: 'egg_weight', value: 56 }, + { name: 'egg_mass', value: 28 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 49 }, + { id: 2, name: 'Flock A-002', data: 48 }, + { id: 3, name: 'Flock B-001', data: 50 }, + { id: 4, name: 'Flock B-002', data: 47 }, + ], + }, + { + week: 26, + standards: [ + { name: 'hen_day', value: 52 }, + { name: 'hen_house', value: 50 }, + { name: 'uniformity', value: 89 }, + { name: 'egg_weight', value: 57 }, + { name: 'egg_mass', value: 30 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 50 }, + { id: 2, name: 'Flock A-002', data: 49 }, + { id: 3, name: 'Flock B-001', data: 51 }, + { id: 4, name: 'Flock B-002', data: 48 }, + ], + }, + { + week: 28, + standards: [ + { name: 'hen_day', value: 55 }, + { name: 'hen_house', value: 53 }, + { name: 'uniformity', value: 90 }, + { name: 'egg_weight', value: 58 }, + { name: 'egg_mass', value: 32 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 53 }, + { id: 2, name: 'Flock A-002', data: 52 }, + { id: 3, name: 'Flock B-001', data: 54 }, + { id: 4, name: 'Flock B-002', data: 51 }, + ], + }, + { + week: 30, + standards: [ + { name: 'hen_day', value: 58 }, + { name: 'hen_house', value: 56 }, + { name: 'uniformity', value: 91 }, + { name: 'egg_weight', value: 59 }, + { name: 'egg_mass', value: 34 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 55 }, + { id: 2, name: 'Flock A-002', data: 54 }, + { id: 3, name: 'Flock B-001', data: 56 }, + { id: 4, name: 'Flock B-002', data: 53 }, + ], + }, + { + week: 32, + standards: [ + { name: 'hen_day', value: 60 }, + { name: 'hen_house', value: 58 }, + { name: 'uniformity', value: 92 }, + { name: 'egg_weight', value: 60 }, + { name: 'egg_mass', value: 36 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 58 }, + { id: 2, name: 'Flock A-002', data: 57 }, + { id: 3, name: 'Flock B-001', data: 59 }, + { id: 4, name: 'Flock B-002', data: 56 }, + ], + }, + { + week: 34, + standards: [ + { name: 'hen_day', value: 62 }, + { name: 'hen_house', value: 60 }, + { name: 'uniformity', value: 92 }, + { name: 'egg_weight', value: 61 }, + { name: 'egg_mass', value: 38 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 60 }, + { id: 2, name: 'Flock A-002', data: 59 }, + { id: 3, name: 'Flock B-001', data: 61 }, + { id: 4, name: 'Flock B-002', data: 58 }, + ], + }, + { + week: 36, + standards: [ + { name: 'hen_day', value: 64 }, + { name: 'hen_house', value: 62 }, + { name: 'uniformity', value: 93 }, + { name: 'egg_weight', value: 62 }, + { name: 'egg_mass', value: 40 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 62 }, + { id: 2, name: 'Flock A-002', data: 61 }, + { id: 3, name: 'Flock B-001', data: 63 }, + { id: 4, name: 'Flock B-002', data: 60 }, + ], + }, + { + week: 38, + standards: [ + { name: 'hen_day', value: 66 }, + { name: 'hen_house', value: 64 }, + { name: 'uniformity', value: 93 }, + { name: 'egg_weight', value: 63 }, + { name: 'egg_mass', value: 42 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 64 }, + { id: 2, name: 'Flock A-002', data: 63 }, + { id: 3, name: 'Flock B-001', data: 65 }, + { id: 4, name: 'Flock B-002', data: 62 }, + ], + }, + { + week: 40, + standards: [ + { name: 'hen_day', value: 68 }, + { name: 'hen_house', value: 66 }, + { name: 'uniformity', value: 94 }, + { name: 'egg_weight', value: 64 }, + { name: 'egg_mass', value: 44 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 66 }, + { id: 2, name: 'Flock A-002', data: 65 }, + { id: 3, name: 'Flock B-001', data: 67 }, + { id: 4, name: 'Flock B-002', data: 64 }, + ], + }, + { + week: 42, + standards: [ + { name: 'hen_day', value: 70 }, + { name: 'hen_house', value: 68 }, + { name: 'uniformity', value: 94 }, + { name: 'egg_weight', value: 65 }, + { name: 'egg_mass', value: 46 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 68 }, + { id: 2, name: 'Flock A-002', data: 67 }, + { id: 3, name: 'Flock B-001', data: 69 }, + { id: 4, name: 'Flock B-002', data: 66 }, + ], + }, + { + week: 44, + standards: [ + { name: 'hen_day', value: 72 }, + { name: 'hen_house', value: 70 }, + { name: 'uniformity', value: 95 }, + { name: 'egg_weight', value: 66 }, + { name: 'egg_mass', value: 48 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 70 }, + { id: 2, name: 'Flock A-002', data: 69 }, + { id: 3, name: 'Flock B-001', data: 71 }, + { id: 4, name: 'Flock B-002', data: 68 }, + ], + }, + { + week: 46, + standards: [ + { name: 'hen_day', value: 74 }, + { name: 'hen_house', value: 72 }, + { name: 'uniformity', value: 95 }, + { name: 'egg_weight', value: 67 }, + { name: 'egg_mass', value: 50 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 72 }, + { id: 2, name: 'Flock A-002', data: 71 }, + { id: 3, name: 'Flock B-001', data: 73 }, + { id: 4, name: 'Flock B-002', data: 70 }, + ], + }, + { + week: 48, + standards: [ + { name: 'hen_day', value: 76 }, + { name: 'hen_house', value: 74 }, + { name: 'uniformity', value: 95 }, + { name: 'egg_weight', value: 68 }, + { name: 'egg_mass', value: 52 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 74 }, + { id: 2, name: 'Flock A-002', data: 73 }, + { id: 3, name: 'Flock B-001', data: 75 }, + { id: 4, name: 'Flock B-002', data: 72 }, + ], + }, + { + week: 50, + standards: [ + { name: 'hen_day', value: 78 }, + { name: 'hen_house', value: 76 }, + { name: 'uniformity', value: 96 }, + { name: 'egg_weight', value: 69 }, + { name: 'egg_mass', value: 54 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 76 }, + { id: 2, name: 'Flock A-002', data: 75 }, + { id: 3, name: 'Flock B-001', data: 77 }, + { id: 4, name: 'Flock B-002', data: 74 }, + ], + }, + { + week: 52, + standards: [ + { name: 'hen_day', value: 80 }, + { name: 'hen_house', value: 78 }, + { name: 'uniformity', value: 96 }, + { name: 'egg_weight', value: 70 }, + { name: 'egg_mass', value: 56 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 78 }, + { id: 2, name: 'Flock A-002', data: 77 }, + { id: 3, name: 'Flock B-001', data: 79 }, + { id: 4, name: 'Flock B-002', data: 76 }, + ], + }, + { + week: 54, + standards: [ + { name: 'hen_day', value: 82 }, + { name: 'hen_house', value: 80 }, + { name: 'uniformity', value: 96 }, + { name: 'egg_weight', value: 71 }, + { name: 'egg_mass', value: 58 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 80 }, + { id: 2, name: 'Flock A-002', data: 79 }, + { id: 3, name: 'Flock B-001', data: 81 }, + { id: 4, name: 'Flock B-002', data: 78 }, + ], + }, + { + week: 56, + standards: [ + { name: 'hen_day', value: 84 }, + { name: 'hen_house', value: 82 }, + { name: 'uniformity', value: 97 }, + { name: 'egg_weight', value: 72 }, + { name: 'egg_mass', value: 60 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 82 }, + { id: 2, name: 'Flock A-002', data: 81 }, + { id: 3, name: 'Flock B-001', data: 83 }, + { id: 4, name: 'Flock B-002', data: 80 }, + ], + }, + { + week: 58, + standards: [ + { name: 'hen_day', value: 86 }, + { name: 'hen_house', value: 84 }, + { name: 'uniformity', value: 97 }, + { name: 'egg_weight', value: 73 }, + { name: 'egg_mass', value: 62 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 84 }, + { id: 2, name: 'Flock A-002', data: 83 }, + { id: 3, name: 'Flock B-001', data: 85 }, + { id: 4, name: 'Flock B-002', data: 82 }, + ], + }, + { + week: 60, + standards: [ + { name: 'hen_day', value: 88 }, + { name: 'hen_house', value: 86 }, + { name: 'uniformity', value: 97 }, + { name: 'egg_weight', value: 74 }, + { name: 'egg_mass', value: 64 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 86 }, + { id: 2, name: 'Flock A-002', data: 85 }, + { id: 3, name: 'Flock B-001', data: 87 }, + { id: 4, name: 'Flock B-002', data: 84 }, + ], + }, + { + week: 62, + standards: [ + { name: 'hen_day', value: 90 }, + { name: 'hen_house', value: 88 }, + { name: 'uniformity', value: 98 }, + { name: 'egg_weight', value: 75 }, + { name: 'egg_mass', value: 66 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 88 }, + { id: 2, name: 'Flock A-002', data: 87 }, + { id: 3, name: 'Flock B-001', data: 89 }, + { id: 4, name: 'Flock B-002', data: 86 }, + ], + }, + { + week: 64, + standards: [ + { name: 'hen_day', value: 92 }, + { name: 'hen_house', value: 90 }, + { name: 'uniformity', value: 98 }, + { name: 'egg_weight', value: 76 }, + { name: 'egg_mass', value: 68 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 90 }, + { id: 2, name: 'Flock A-002', data: 89 }, + { id: 3, name: 'Flock B-001', data: 91 }, + { id: 4, name: 'Flock B-002', data: 88 }, + ], + }, + { + week: 66, + standards: [ + { name: 'hen_day', value: 94 }, + { name: 'hen_house', value: 92 }, + { name: 'uniformity', value: 98 }, + { name: 'egg_weight', value: 77 }, + { name: 'egg_mass', value: 70 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 92 }, + { id: 2, name: 'Flock A-002', data: 91 }, + { id: 3, name: 'Flock B-001', data: 93 }, + { id: 4, name: 'Flock B-002', data: 90 }, + ], + }, + { + week: 68, + standards: [ + { name: 'hen_day', value: 95 }, + { name: 'hen_house', value: 93 }, + { name: 'uniformity', value: 98 }, + { name: 'egg_weight', value: 78 }, + { name: 'egg_mass', value: 72 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 93 }, + { id: 2, name: 'Flock A-002', data: 92 }, + { id: 3, name: 'Flock B-001', data: 94 }, + { id: 4, name: 'Flock B-002', data: 91 }, + ], + }, + { + week: 70, + standards: [ + { name: 'hen_day', value: 96 }, + { name: 'hen_house', value: 94 }, + { name: 'uniformity', value: 99 }, + { name: 'egg_weight', value: 79 }, + { name: 'egg_mass', value: 74 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 94 }, + { id: 2, name: 'Flock A-002', data: 93 }, + { id: 3, name: 'Flock B-001', data: 95 }, + { id: 4, name: 'Flock B-002', data: 92 }, + ], + }, + { + week: 72, + standards: [ + { name: 'hen_day', value: 97 }, + { name: 'hen_house', value: 95 }, + { name: 'uniformity', value: 99 }, + { name: 'egg_weight', value: 80 }, + { name: 'egg_mass', value: 76 }, + ], + flocks: [ + { id: 1, name: 'Flock A-001', data: 95 }, + { id: 2, name: 'Flock A-002', data: 94 }, + { id: 3, name: 'Flock B-001', data: 96 }, + { id: 4, name: 'Flock B-002', data: 93 }, + ], + }, +]; + +// Transform API data to Recharts format +const transformStandardData = ( + apiData: StandardChartItem[], + selectedStandards: string[] = [ + 'hen_day', + 'hen_house', + 'uniformity', + 'egg_weight', + 'egg_mass', + ] +) => { + return apiData.map((item) => { + const transformed: Record = { + week: item.week, + }; + + // Add selected standards as properties + selectedStandards.forEach((standardName) => { + const standardData = item.standards.find((s) => s.name === standardName); + if (standardData) { + transformed[standardName] = standardData.value; + } + }); + + // Add each flock's data as a property + item.flocks.forEach((flock) => { + transformed[flock.name] = flock.data; + }); + + return transformed; + }); +}; + +interface StandardLineChartProps { + data?: StandardChartItem[]; + selectedStandards?: string[]; +} + +const StandardLineChart = ({ + data: apiData, + selectedStandards = [ + 'hen_day', + 'hen_house', + 'uniformity', + 'egg_weight', + 'egg_mass', + ], +}: StandardLineChartProps) => { + // State to track which lines are hidden + const [hiddenLines, setHiddenLines] = useState([]); + + // Use API data if provided, otherwise use sample data + const chartData = apiData + ? transformStandardData(apiData, selectedStandards) + : transformStandardData(sampleApiData, selectedStandards); + + // Handle legend click to show/hide lines + const handleLegendClick = (dataKey: string) => { + setHiddenLines((prev) => + prev.includes(dataKey) + ? prev.filter((key) => key !== dataKey) + : [...prev, dataKey] + ); + }; + + // Standard line colors mapping + const standardColors: Record = { + hen_day: '#94a3b8', + hen_house: '#64748b', + uniformity: '#475569', + egg_weight: '#334155', + egg_mass: '#1e293b', + }; + + // Standard names mapping for display + const standardLabels: Record = { + hen_day: 'Hen Day', + hen_house: 'Hen House', + uniformity: 'Uniformity', + egg_weight: 'Egg Weight', + egg_mass: 'Egg Mass', + }; + + return ( +
+

+ Perbandingan Henday per Umur +

+ + + + + + + value !== undefined ? [`${value}%`, ''] : ['', ''] + } + labelFormatter={(label) => `Minggu ${label}`} + /> + { + if (e.dataKey) handleLegendClick(e.dataKey as string); + }} + style={{ cursor: 'pointer' }} + /> + {/* Dynamic Standard Lines */} + {selectedStandards.map((standardName) => ( + + ))} + {/* Flock Lines */} + + + + + + +
+ ); +}; + +export default StandardLineChart; + +// Export types for external use +export type { FlockData, StandardData, StandardChartItem }; diff --git a/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts b/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts new file mode 100644 index 00000000..4ed86a48 --- /dev/null +++ b/src/components/pages/dashboard/filter/DashboardProductionFilter.schema.ts @@ -0,0 +1,16 @@ +import * as yup from 'yup'; + +const dashboardProductionFilterSchema = yup.object({ + startDate: yup.string().optional(), + endDate: yup.string().optional(), + flock: yup.array().optional(), + standard_production_id: yup.array().optional(), + standard_productions: yup.array().optional(), + period: yup.string().optional(), +}); + +export type DashboardProductionFilterValues = yup.InferType< + typeof dashboardProductionFilterSchema +>; + +export default dashboardProductionFilterSchema; diff --git a/src/config/constant.ts b/src/config/constant.ts index 69669141..48e5a435 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -308,7 +308,7 @@ export const FINANCE_INITIAL_BALANCE_TYPE_OPTIONS = [ { label: 'Saldo Awal Negatif', value: 'NEGATIVE' }, ]; -export const FINANCE_TRANSACTION_STATUS = ['PENJUALAN', 'PEMBELIAN']; +export const FINANCE_TRANSACTION_STATUS = ['PENJUALAN', 'PEMBELIAN', 'BIAYA']; export const FINANCE_INITIAL_BALANCE_STATUS = ['SALDO_AWAL']; diff --git a/src/dummy/dashboard/dashboard.production.dummy.json b/src/dummy/dashboard/dashboard.production.dummy.json new file mode 100644 index 00000000..4b2fbd1a --- /dev/null +++ b/src/dummy/dashboard/dashboard.production.dummy.json @@ -0,0 +1,1801 @@ +{ + "statistics_data": [ + { + "title": "Total Keuangan", + "value": 2850000000, + "change": 12.5, + "period": "monthly", + "changeType": "increase" + }, + { + "title": "Penjualan", + "value": 3200000, + "change": 8.3, + "period": "monthly", + "changeType": "increase" + }, + { + "title": "Pembelian", + "value": 1850000000, + "change": -3.2, + "period": "monthly", + "changeType": "decrease" + }, + { + "title": "Biaya Overhead", + "value": 160000000, + "change": -1.5, + "period": "monthly", + "changeType": "decrease" + } + ], + "production_charts": [ + { + "date": "2025-12-01T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 88 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 92 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 90 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 85 + } + ] + }, + { + "date": "2025-12-03T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 85 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 95 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 93 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 87 + } + ] + }, + { + "date": "2025-12-05T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 82 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 98 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 91 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 84 + } + ] + }, + { + "date": "2025-12-07T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 80 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 89 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 88 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 82 + } + ] + }, + { + "date": "2025-12-08T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 83 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 92 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 95 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 85 + } + ] + }, + { + "date": "2025-12-11T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 81 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 88 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 92 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 83 + } + ] + }, + { + "date": "2025-12-13T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 84 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 90 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 89 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 86 + } + ] + }, + { + "date": "2025-12-15T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 82 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 94 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 96 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 84 + } + ] + }, + { + "date": "2025-12-17T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 80 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 91 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 93 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 82 + } + ] + }, + { + "date": "2025-12-19T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 79 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 88 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 90 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 81 + } + ] + }, + { + "date": "2025-12-21T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 81 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 97 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 92 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 83 + } + ] + }, + { + "date": "2025-12-23T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 83 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 95 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 98 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 85 + } + ] + }, + { + "date": "2025-12-25T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 80 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 89 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 94 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 82 + } + ] + }, + { + "date": "2025-12-27T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 82 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 93 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 96 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 84 + } + ] + }, + { + "date": "2025-12-28T00:00:00Z", + "flocks": [ + { + "id": 1, + "name": "Flock A-002", + "data": 85 + }, + { + "id": 2, + "name": "Flock A-001", + "data": 96 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 95 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 87 + } + ] + } + ], + "standard_productions": [ + { + "week": 18, + "standards": [ + { + "name": "hen_day", + "value": 40 + }, + { + "name": "hen_house", + "value": 38 + }, + { + "name": "uniformity", + "value": 85 + }, + { + "name": "egg_weight", + "value": 52 + }, + { + "name": "egg_mass", + "value": 20 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 38 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 37 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 39 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 36 + } + ] + }, + { + "week": 20, + "standards": [ + { + "name": "hen_day", + "value": 45 + }, + { + "name": "hen_house", + "value": 43 + }, + { + "name": "uniformity", + "value": 86 + }, + { + "name": "egg_weight", + "value": 54 + }, + { + "name": "egg_mass", + "value": 24 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 43 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 42 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 44 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 41 + } + ] + }, + { + "week": 22, + "standards": [ + { + "name": "hen_day", + "value": 48 + }, + { + "name": "hen_house", + "value": 46 + }, + { + "name": "uniformity", + "value": 87 + }, + { + "name": "egg_weight", + "value": 55 + }, + { + "name": "egg_mass", + "value": 26 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 47 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 46 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 48 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 45 + } + ] + }, + { + "week": 24, + "standards": [ + { + "name": "hen_day", + "value": 50 + }, + { + "name": "hen_house", + "value": 48 + }, + { + "name": "uniformity", + "value": 88 + }, + { + "name": "egg_weight", + "value": 56 + }, + { + "name": "egg_mass", + "value": 28 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 49 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 48 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 50 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 47 + } + ] + }, + { + "week": 26, + "standards": [ + { + "name": "hen_day", + "value": 52 + }, + { + "name": "hen_house", + "value": 50 + }, + { + "name": "uniformity", + "value": 89 + }, + { + "name": "egg_weight", + "value": 57 + }, + { + "name": "egg_mass", + "value": 30 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 50 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 49 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 51 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 48 + } + ] + }, + { + "week": 28, + "standards": [ + { + "name": "hen_day", + "value": 55 + }, + { + "name": "hen_house", + "value": 53 + }, + { + "name": "uniformity", + "value": 90 + }, + { + "name": "egg_weight", + "value": 58 + }, + { + "name": "egg_mass", + "value": 32 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 53 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 52 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 54 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 51 + } + ] + }, + { + "week": 30, + "standards": [ + { + "name": "hen_day", + "value": 58 + }, + { + "name": "hen_house", + "value": 56 + }, + { + "name": "uniformity", + "value": 91 + }, + { + "name": "egg_weight", + "value": 59 + }, + { + "name": "egg_mass", + "value": 34 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 55 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 54 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 56 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 53 + } + ] + }, + { + "week": 32, + "standards": [ + { + "name": "hen_day", + "value": 60 + }, + { + "name": "hen_house", + "value": 58 + }, + { + "name": "uniformity", + "value": 92 + }, + { + "name": "egg_weight", + "value": 60 + }, + { + "name": "egg_mass", + "value": 36 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 58 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 57 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 59 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 56 + } + ] + }, + { + "week": 34, + "standards": [ + { + "name": "hen_day", + "value": 62 + }, + { + "name": "hen_house", + "value": 60 + }, + { + "name": "uniformity", + "value": 92 + }, + { + "name": "egg_weight", + "value": 61 + }, + { + "name": "egg_mass", + "value": 38 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 60 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 59 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 61 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 58 + } + ] + }, + { + "week": 36, + "standards": [ + { + "name": "hen_day", + "value": 64 + }, + { + "name": "hen_house", + "value": 62 + }, + { + "name": "uniformity", + "value": 93 + }, + { + "name": "egg_weight", + "value": 62 + }, + { + "name": "egg_mass", + "value": 40 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 62 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 61 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 63 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 60 + } + ] + }, + { + "week": 38, + "standards": [ + { + "name": "hen_day", + "value": 66 + }, + { + "name": "hen_house", + "value": 64 + }, + { + "name": "uniformity", + "value": 93 + }, + { + "name": "egg_weight", + "value": 63 + }, + { + "name": "egg_mass", + "value": 42 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 64 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 63 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 65 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 62 + } + ] + }, + { + "week": 40, + "standards": [ + { + "name": "hen_day", + "value": 68 + }, + { + "name": "hen_house", + "value": 66 + }, + { + "name": "uniformity", + "value": 94 + }, + { + "name": "egg_weight", + "value": 64 + }, + { + "name": "egg_mass", + "value": 44 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 66 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 65 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 67 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 64 + } + ] + }, + { + "week": 42, + "standards": [ + { + "name": "hen_day", + "value": 70 + }, + { + "name": "hen_house", + "value": 68 + }, + { + "name": "uniformity", + "value": 94 + }, + { + "name": "egg_weight", + "value": 65 + }, + { + "name": "egg_mass", + "value": 46 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 68 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 67 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 69 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 66 + } + ] + }, + { + "week": 44, + "standards": [ + { + "name": "hen_day", + "value": 72 + }, + { + "name": "hen_house", + "value": 70 + }, + { + "name": "uniformity", + "value": 95 + }, + { + "name": "egg_weight", + "value": 66 + }, + { + "name": "egg_mass", + "value": 48 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 70 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 69 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 71 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 68 + } + ] + }, + { + "week": 46, + "standards": [ + { + "name": "hen_day", + "value": 74 + }, + { + "name": "hen_house", + "value": 72 + }, + { + "name": "uniformity", + "value": 95 + }, + { + "name": "egg_weight", + "value": 67 + }, + { + "name": "egg_mass", + "value": 50 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 72 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 71 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 73 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 70 + } + ] + }, + { + "week": 48, + "standards": [ + { + "name": "hen_day", + "value": 76 + }, + { + "name": "hen_house", + "value": 74 + }, + { + "name": "uniformity", + "value": 95 + }, + { + "name": "egg_weight", + "value": 68 + }, + { + "name": "egg_mass", + "value": 52 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 74 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 73 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 75 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 72 + } + ] + }, + { + "week": 50, + "standards": [ + { + "name": "hen_day", + "value": 78 + }, + { + "name": "hen_house", + "value": 76 + }, + { + "name": "uniformity", + "value": 96 + }, + { + "name": "egg_weight", + "value": 69 + }, + { + "name": "egg_mass", + "value": 54 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 76 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 75 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 77 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 74 + } + ] + }, + { + "week": 52, + "standards": [ + { + "name": "hen_day", + "value": 80 + }, + { + "name": "hen_house", + "value": 78 + }, + { + "name": "uniformity", + "value": 96 + }, + { + "name": "egg_weight", + "value": 70 + }, + { + "name": "egg_mass", + "value": 56 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 78 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 77 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 79 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 76 + } + ] + }, + { + "week": 54, + "standards": [ + { + "name": "hen_day", + "value": 82 + }, + { + "name": "hen_house", + "value": 80 + }, + { + "name": "uniformity", + "value": 96 + }, + { + "name": "egg_weight", + "value": 71 + }, + { + "name": "egg_mass", + "value": 58 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 80 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 79 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 81 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 78 + } + ] + }, + { + "week": 56, + "standards": [ + { + "name": "hen_day", + "value": 84 + }, + { + "name": "hen_house", + "value": 82 + }, + { + "name": "uniformity", + "value": 97 + }, + { + "name": "egg_weight", + "value": 72 + }, + { + "name": "egg_mass", + "value": 60 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 82 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 81 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 83 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 80 + } + ] + }, + { + "week": 58, + "standards": [ + { + "name": "hen_day", + "value": 86 + }, + { + "name": "hen_house", + "value": 84 + }, + { + "name": "uniformity", + "value": 97 + }, + { + "name": "egg_weight", + "value": 73 + }, + { + "name": "egg_mass", + "value": 62 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 84 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 83 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 85 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 82 + } + ] + }, + { + "week": 60, + "standards": [ + { + "name": "hen_day", + "value": 88 + }, + { + "name": "hen_house", + "value": 86 + }, + { + "name": "uniformity", + "value": 97 + }, + { + "name": "egg_weight", + "value": 74 + }, + { + "name": "egg_mass", + "value": 64 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 86 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 85 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 87 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 84 + } + ] + }, + { + "week": 62, + "standards": [ + { + "name": "hen_day", + "value": 90 + }, + { + "name": "hen_house", + "value": 88 + }, + { + "name": "uniformity", + "value": 98 + }, + { + "name": "egg_weight", + "value": 75 + }, + { + "name": "egg_mass", + "value": 66 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 88 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 87 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 89 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 86 + } + ] + }, + { + "week": 64, + "standards": [ + { + "name": "hen_day", + "value": 92 + }, + { + "name": "hen_house", + "value": 90 + }, + { + "name": "uniformity", + "value": 98 + }, + { + "name": "egg_weight", + "value": 76 + }, + { + "name": "egg_mass", + "value": 68 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 90 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 89 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 91 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 88 + } + ] + }, + { + "week": 66, + "standards": [ + { + "name": "hen_day", + "value": 94 + }, + { + "name": "hen_house", + "value": 92 + }, + { + "name": "uniformity", + "value": 98 + }, + { + "name": "egg_weight", + "value": 77 + }, + { + "name": "egg_mass", + "value": 70 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 92 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 91 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 93 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 90 + } + ] + }, + { + "week": 68, + "standards": [ + { + "name": "hen_day", + "value": 95 + }, + { + "name": "hen_house", + "value": 93 + }, + { + "name": "uniformity", + "value": 98 + }, + { + "name": "egg_weight", + "value": 78 + }, + { + "name": "egg_mass", + "value": 72 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 93 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 92 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 94 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 91 + } + ] + }, + { + "week": 70, + "standards": [ + { + "name": "hen_day", + "value": 96 + }, + { + "name": "hen_house", + "value": 94 + }, + { + "name": "uniformity", + "value": 99 + }, + { + "name": "egg_weight", + "value": 79 + }, + { + "name": "egg_mass", + "value": 74 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 94 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 93 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 95 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 92 + } + ] + }, + { + "week": 72, + "standards": [ + { + "name": "hen_day", + "value": 97 + }, + { + "name": "hen_house", + "value": 95 + }, + { + "name": "uniformity", + "value": 99 + }, + { + "name": "egg_weight", + "value": 80 + }, + { + "name": "egg_mass", + "value": 76 + } + ], + "flocks": [ + { + "id": 1, + "name": "Flock A-001", + "data": 95 + }, + { + "id": 2, + "name": "Flock A-002", + "data": 94 + }, + { + "id": 3, + "name": "Flock B-001", + "data": 96 + }, + { + "id": 4, + "name": "Flock B-002", + "data": 93 + } + ] + } + ], + "egg_weights": [ + { + "flock": { + "id": 1, + "name": "Flock A-001" + }, + "weight": 62 + }, + { + "flock": { + "id": 2, + "name": "Flock A-002" + }, + "weight": 61 + }, + { + "flock": { + "id": 3, + "name": "Flock B-001" + }, + "weight": 63 + }, + { + "flock": { + "id": 4, + "name": "Flock B-002" + }, + "weight": 60 + }, + { + "flock": { + "id": 5, + "name": "Flock C-001" + }, + "weight": 62 + } + ], + "fcr_data": [ + { + "flock": { + "id": 1, + "name": "Flock A-001" + }, + "fcr": 2.1 + }, + { + "flock": { + "id": 2, + "name": "Flock A-002" + }, + "fcr": 2.3 + }, + { + "flock": { + "id": 3, + "name": "Flock B-001" + }, + "fcr": 2 + }, + { + "flock": { + "id": 4, + "name": "Flock B-002" + }, + "fcr": 2.4 + }, + { + "flock": { + "id": 5, + "name": "Flock C-001" + }, + "fcr": 2.2 + } + ] +} \ No newline at end of file diff --git a/src/dummy/dashboard/dashboard.production.dummy.ts b/src/dummy/dashboard/dashboard.production.dummy.ts new file mode 100644 index 00000000..b1e2911b --- /dev/null +++ b/src/dummy/dashboard/dashboard.production.dummy.ts @@ -0,0 +1,27 @@ +/** + * Dummy data for DashboardProduction + * Generated from: dashboard.production.dummy.json + * + * This file is auto-generated. Do not edit manually. + */ + +import { DashboardProductionStatisticsData, DashboardProductionProductionChartsFlocks, DashboardProductionProductionCharts, DashboardProductionStandardProductionsStandards, DashboardProductionStandardProductions, DashboardProductionFcrDataFlock, DashboardProductionEggWeights, DashboardProductionFcrData, DashboardProduction } from '../../types/api/dashboard/dashboard-production'; +import { BaseApiResponse } from '@/types/api/api-general'; +import dummyData from './dashboard.production.dummy.json'; + +/** + * Get dummy DashboardProduction data + * @returns Promise with BaseApiResponse containing DashboardProduction + */ +export async function getDummySingle(): Promise> { + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + code: 200, + status: 'success', + message: 'Data retrieved successfully', + data: dummyData as unknown as DashboardProduction, + }); + }, 500); + }); +} diff --git a/src/services/api/dashboard.ts b/src/services/api/dashboard.ts new file mode 100644 index 00000000..d45ebbde --- /dev/null +++ b/src/services/api/dashboard.ts @@ -0,0 +1,34 @@ +import { BaseApiService } from '@/services/api/base'; +import { BaseApiResponse } from '@/types/api/api-general'; +import { DashboardProduction } from '@/types/api/dashboard/dashboard-production'; +import { getDummySingle } from '@/dummy/dashboard/dashboard.production.dummy'; + +class DashboardService extends BaseApiService< + DashboardProduction, + unknown, + unknown +> { + constructor(basePath: string) { + super(basePath); + } + + /** + * Fetch dashboard production data + * @param endpoint - The endpoint URL with query parameters + * @returns Promise with BaseApiResponse containing DashboardProduction + * + * Note: Currently using dummy data. When real API is ready, + * uncomment the line below and remove getDummySingle() call: + * return await this.customRequest>(endpoint); + */ + async getDashboardProductionFetcher( + endpoint: string + ): Promise> { + // For now, we're using dummy data regardless of the endpoint + // The endpoint parameter is kept for future API integration + console.log('Fetching dashboard data with endpoint:', endpoint); + return await getDummySingle(); + } +} + +export const DashboardApi = new DashboardService('/dashboard'); diff --git a/src/types/api/dashboard/dashboard-production.d.ts b/src/types/api/dashboard/dashboard-production.d.ts new file mode 100644 index 00000000..05acddd3 --- /dev/null +++ b/src/types/api/dashboard/dashboard-production.d.ts @@ -0,0 +1,52 @@ +export interface DashboardProduction { + statistics_data: DashboardProductionStatisticsData[]; + production_charts: DashboardProductionProductionCharts[]; + standard_productions: DashboardProductionStandardProductions[]; + egg_weights: DashboardProductionEggWeights[]; + fcr_data: DashboardProductionFcrData[]; +} + +export interface DashboardProductionFcrData { + flock: DashboardProductionFcrDataFlock; + fcr: number; +} + +export interface DashboardProductionEggWeights { + flock: DashboardProductionFcrDataFlock; + weight: number; +} + +export interface DashboardProductionStandardProductions { + week: number; + standards: DashboardProductionStandardProductionsStandards[]; + flocks: DashboardProductionProductionChartsFlocks[]; +} + +export interface DashboardProductionProductionCharts { + date: string; + flocks: DashboardProductionProductionChartsFlocks[]; +} + +export interface DashboardProductionStatisticsData { + title: string; + value: number; + change: number; + period: string; + changeType: string; +} + +export interface DashboardProductionFcrDataFlock { + id: number; + name: string; +} + +export interface DashboardProductionStandardProductionsStandards { + name: string; + value: number; +} + +export interface DashboardProductionProductionChartsFlocks { + id: number; + name: string; + data: number; +} \ No newline at end of file From 10fb9fc9906588aa69b69613a2881968ba74f53a Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 30 Dec 2025 19:30:52 +0700 Subject: [PATCH 13/22] feat(FE-390): slicing UI and API integration for production dashboard --- package.json | 2 +- .../dashboard/dashboard.production.dummy.json | 2 +- .../dashboard/dashboard.production.dummy.ts | 18 +++++++++++++++--- .../api/dashboard/dashboard-production.d.ts | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e331cf3b..319f0e3d 100644 --- a/package.json +++ b/package.json @@ -51,4 +51,4 @@ "tailwindcss": "^4", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/src/dummy/dashboard/dashboard.production.dummy.json b/src/dummy/dashboard/dashboard.production.dummy.json index 4b2fbd1a..bb6e6af6 100644 --- a/src/dummy/dashboard/dashboard.production.dummy.json +++ b/src/dummy/dashboard/dashboard.production.dummy.json @@ -1798,4 +1798,4 @@ "fcr": 2.2 } ] -} \ No newline at end of file +} diff --git a/src/dummy/dashboard/dashboard.production.dummy.ts b/src/dummy/dashboard/dashboard.production.dummy.ts index b1e2911b..b663f28c 100644 --- a/src/dummy/dashboard/dashboard.production.dummy.ts +++ b/src/dummy/dashboard/dashboard.production.dummy.ts @@ -1,11 +1,21 @@ /** * Dummy data for DashboardProduction * Generated from: dashboard.production.dummy.json - * + * * This file is auto-generated. Do not edit manually. */ -import { DashboardProductionStatisticsData, DashboardProductionProductionChartsFlocks, DashboardProductionProductionCharts, DashboardProductionStandardProductionsStandards, DashboardProductionStandardProductions, DashboardProductionFcrDataFlock, DashboardProductionEggWeights, DashboardProductionFcrData, DashboardProduction } from '../../types/api/dashboard/dashboard-production'; +import { + DashboardProductionStatisticsData, + DashboardProductionProductionChartsFlocks, + DashboardProductionProductionCharts, + DashboardProductionStandardProductionsStandards, + DashboardProductionStandardProductions, + DashboardProductionFcrDataFlock, + DashboardProductionEggWeights, + DashboardProductionFcrData, + DashboardProduction, +} from '../../types/api/dashboard/dashboard-production'; import { BaseApiResponse } from '@/types/api/api-general'; import dummyData from './dashboard.production.dummy.json'; @@ -13,7 +23,9 @@ import dummyData from './dashboard.production.dummy.json'; * Get dummy DashboardProduction data * @returns Promise with BaseApiResponse containing DashboardProduction */ -export async function getDummySingle(): Promise> { +export async function getDummySingle(): Promise< + BaseApiResponse +> { return new Promise((resolve) => { setTimeout(() => { resolve({ diff --git a/src/types/api/dashboard/dashboard-production.d.ts b/src/types/api/dashboard/dashboard-production.d.ts index 05acddd3..5d873806 100644 --- a/src/types/api/dashboard/dashboard-production.d.ts +++ b/src/types/api/dashboard/dashboard-production.d.ts @@ -49,4 +49,4 @@ export interface DashboardProductionProductionChartsFlocks { id: number; name: string; data: number; -} \ No newline at end of file +} From bc6ebcfedab436374ea43fd0ebbecffd513d1fd3 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 30 Dec 2025 19:34:32 +0700 Subject: [PATCH 14/22] fix(FE): add optional chaining for sapronak calculation in closing odule --- .../ClosingSapronakCalculationTable.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/pages/closing/ClosingSapronakCalculationTable.tsx b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx index ea27fd80..b6703549 100644 --- a/src/components/pages/closing/ClosingSapronakCalculationTable.tsx +++ b/src/components/pages/closing/ClosingSapronakCalculationTable.tsx @@ -3,7 +3,7 @@ import Card from '@/components/Card'; import Table from '@/components/Table'; -import { cn, formatCurrency, formatNumber } from '@/lib/helper'; +import { formatCurrency, formatNumber } from '@/lib/helper'; import { RowSapronakCalculation, TotalSapronakCalculation, @@ -54,7 +54,7 @@ const ClosingSapronakCalculationTable = ({ footer: total ? () => (
- {formatNumber(total.qty_masuk)} + {formatNumber(total?.qty_masuk)}
) : '', @@ -66,7 +66,7 @@ const ClosingSapronakCalculationTable = ({ footer: total ? () => (
- {formatNumber(total.qty_keluar)} + {formatNumber(total?.qty_keluar)}
) : '', @@ -78,7 +78,7 @@ const ClosingSapronakCalculationTable = ({ footer: total ? () => (
- {formatNumber(total.qty_pakai)} + {formatNumber(total?.qty_pakai)}
) : '', @@ -102,7 +102,7 @@ const ClosingSapronakCalculationTable = ({ footer: total ? () => (
- {formatCurrency(total.harga_beli_per_qty)} + {formatCurrency(total?.harga_beli_per_qty)}
) : '', @@ -114,7 +114,7 @@ const ClosingSapronakCalculationTable = ({ footer: total ? () => (
- {formatCurrency(total.total_harga)} + {formatCurrency(total?.total_harga)}
) : '', @@ -131,7 +131,7 @@ const ClosingSapronakCalculationTable = ({ const docBroilerColumns = useMemo( () => isResponseSuccess(sapronakCalculation) - ? createColumns(sapronakCalculation.data?.doc_broiler.total) + ? createColumns(sapronakCalculation.data?.doc_broiler?.total) : createColumns(), [sapronakCalculation] ); @@ -139,7 +139,7 @@ const ClosingSapronakCalculationTable = ({ const ovkColumns = useMemo( () => isResponseSuccess(sapronakCalculation) - ? createColumns(sapronakCalculation.data?.ovk.total) + ? createColumns(sapronakCalculation.data?.ovk?.total) : createColumns(), [sapronakCalculation] ); @@ -147,7 +147,7 @@ const ClosingSapronakCalculationTable = ({ const pakanColumns = useMemo( () => isResponseSuccess(sapronakCalculation) - ? createColumns(sapronakCalculation.data?.pakan.total) + ? createColumns(sapronakCalculation.data?.pakan?.total) : createColumns(), [sapronakCalculation] ); @@ -166,7 +166,7 @@ const ClosingSapronakCalculationTable = ({ data={ isResponseSuccess(sapronakCalculation) - ? (sapronakCalculation.data?.doc_broiler.rows ?? []) + ? (sapronakCalculation.data?.doc_broiler?.rows ?? []) : [] } columns={docBroilerColumns} @@ -189,7 +189,7 @@ const ClosingSapronakCalculationTable = ({ data={ isResponseSuccess(sapronakCalculation) - ? (sapronakCalculation.data?.ovk.rows ?? []) + ? (sapronakCalculation.data?.ovk?.rows ?? []) : [] } columns={ovkColumns} @@ -212,7 +212,7 @@ const ClosingSapronakCalculationTable = ({ data={ isResponseSuccess(sapronakCalculation) - ? (sapronakCalculation.data?.pakan.rows ?? []) + ? (sapronakCalculation.data?.pakan?.rows ?? []) : [] } columns={pakanColumns} From 2ab7c10d5d6289ada7d8800b2e542ad3a18bc457 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 30 Dec 2025 19:53:50 +0700 Subject: [PATCH 15/22] feat(FE): adding column standard fcr in master data standar production --- .../form/ProductionStandardForm.schema.ts | 2 + .../form/ProductionStandardForm.tsx | 67 ++++++++++++++++++- .../api/master-data/production-standard.d.ts | 3 + 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts index 55e68039..13183e71 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.schema.ts @@ -18,6 +18,7 @@ const LayingRepeaterFormSchema = Yup.object({ ), target_egg_weight: Yup.number().required('Berat telur wajib diisi!'), target_egg_mass: Yup.number().required('Massa telur wajib diisi!'), + standard_fcr: Yup.number().required('FCR wajib diisi!'), }).required(), }); @@ -35,6 +36,7 @@ const GrowingRepeaterFormSchema = Yup.object({ target_hen_house_production: Yup.number().optional(), target_egg_weight: Yup.number().optional(), target_egg_mass: Yup.number().optional(), + standard_fcr: Yup.number().optional(), }).optional(), }); diff --git a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx index fe424838..640ded51 100644 --- a/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx +++ b/src/components/pages/master-data/production-standard/form/ProductionStandardForm.tsx @@ -30,6 +30,7 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal'; import { useModal } from '@/components/Modal'; import RequirePermission from '@/components/helper/RequirePermission'; import Tooltip from '@/components/Tooltip'; +import Alert from '@/components/Alert'; type TableRowsType = { customRow: boolean; @@ -42,6 +43,7 @@ type ProductionDetailsErrors = { target_hen_house_production?: string; target_egg_weight?: string; target_egg_mass?: string; + standard_fcr?: string; }; type ProductionDetailsTouched = { @@ -49,6 +51,7 @@ type ProductionDetailsTouched = { target_hen_house_production?: boolean; target_egg_weight?: boolean; target_egg_mass?: boolean; + standard_fcr?: boolean; }; const getProductionDetailsError = ( @@ -92,6 +95,9 @@ const convertPayloadToNumberTypes = (payload: ProductionStandardFormValues) => { target_egg_mass: Number( detail.production_standard_details.target_egg_mass ), + standard_fcr: Number( + detail.production_standard_details.standard_fcr + ), } : undefined, production_standard_uniformity_details: { @@ -132,6 +138,9 @@ const convertStandardValueToFormValues = ( target_egg_mass: Number( detail.egg_production_standard_detail.target_egg_mass ), + standard_fcr: Number( + detail.egg_production_standard_detail.standard_fcr + ), } : undefined, production_standard_uniformity_details: { @@ -226,6 +235,7 @@ const ProductionStandardForm = ({ target_hen_house_production: '' as unknown as number, target_egg_weight: '' as unknown as number, target_egg_mass: '' as unknown as number, + standard_fcr: '' as unknown as number, }, production_standard_uniformity_details: { target_mean_bw: '' as unknown as number, @@ -364,6 +374,12 @@ const ProductionStandardForm = ({ row.production_standard_details?.target_egg_mass, enableSorting: false, }, + { + header: 'FCR', + accessorFn: (row) => + row.production_standard_details?.standard_fcr, + enableSorting: false, + }, ] : []; @@ -676,6 +692,7 @@ const ProductionStandardForm = ({ target_hen_house_production: 0, target_egg_weight: 0, target_egg_mass: 0, + standard_fcr: 0, }, })); } @@ -809,7 +826,7 @@ const ProductionStandardForm = ({ className={cn( 'grid gap-4 items-start', formik.values.project_category === 'LAYING' - ? 'grid-cols-9' + ? 'grid-cols-10' : 'grid-cols-5' )} > @@ -968,6 +985,41 @@ const ProductionStandardForm = ({ ) } /> + + gr + + } + errorMessage={getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'standard_fcr' + )} + isError={ + Boolean( + getProductionDetailsError( + repeaterFormik.errors + .production_standard_details, + 'standard_fcr' + ) + ) && + getProductionDetailsTouched( + repeaterFormik.touched + .production_standard_details, + 'standard_fcr' + ) + } + /> )} )} + {productionStandardFormErrorMessage && ( + +
+ + {productionStandardFormErrorMessage} +
+ setProductionStandardFormErrorMessage('')} + className='ms-auto' + /> +
+ )} Date: Tue, 30 Dec 2025 19:56:31 +0700 Subject: [PATCH 16/22] refactor(FE-435): Multiply qty by price when summing expenses --- src/components/pages/expense/ExpenseRealizationContent.tsx | 4 ++-- src/components/pages/expense/ExpenseRequestContent.tsx | 2 +- src/components/pages/expense/pdf/ExpensePDF.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/pages/expense/ExpenseRealizationContent.tsx b/src/components/pages/expense/ExpenseRealizationContent.tsx index c69f089f..57dc74c4 100644 --- a/src/components/pages/expense/ExpenseRealizationContent.tsx +++ b/src/components/pages/expense/ExpenseRealizationContent.tsx @@ -211,7 +211,7 @@ const ExpenseRealizationContent = ({ let expenseGrandTotal = 0; kandangExpense.pengajuans?.forEach( - (item) => (expenseGrandTotal += item.price) + (item) => (expenseGrandTotal += item.qty * item.price) ); return ( @@ -273,7 +273,7 @@ const ExpenseRealizationContent = ({ let expenseGrandTotal = 0; kandangExpense.realisasi?.forEach( - (item) => (expenseGrandTotal += item.price) + (item) => (expenseGrandTotal += item.qty * item.price) ); return ( diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index b937c5bc..329daeab 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -558,7 +558,7 @@ const ExpenseRequestContent = ({ let expenseGrandTotal = 0; kandangExpense.pengajuans?.forEach( - (item) => (expenseGrandTotal += item.price) + (item) => (expenseGrandTotal += item.qty * item.price) ); return ( diff --git a/src/components/pages/expense/pdf/ExpensePDF.tsx b/src/components/pages/expense/pdf/ExpensePDF.tsx index 5b107127..54304015 100644 --- a/src/components/pages/expense/pdf/ExpensePDF.tsx +++ b/src/components/pages/expense/pdf/ExpensePDF.tsx @@ -326,7 +326,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => { let expenseRequestTotal = 0; kandangExpense.pengajuans?.forEach( - (item) => (expenseRequestTotal += item.price) + (item) => (expenseRequestTotal += item.qty * item.price) ); return ( @@ -484,7 +484,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => { let expenseRealizationTotal = 0; kandangExpense.realisasi?.forEach( - (item) => (expenseRealizationTotal += item.price) + (item) => (expenseRealizationTotal += item.qty * item.price) ); return ( From 13c1a82142aef43fd82292c917e8fd04a88ad3d6 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Tue, 30 Dec 2025 20:19:05 +0700 Subject: [PATCH 17/22] refactor(FE-435,436): Select Nominal Biaya by approval step --- src/components/pages/expense/ExpenseRequestContent.tsx | 9 ++++++++- src/components/pages/expense/pdf/ExpensePDF.tsx | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx index 329daeab..b561ff29 100644 --- a/src/components/pages/expense/ExpenseRequestContent.tsx +++ b/src/components/pages/expense/ExpenseRequestContent.tsx @@ -448,7 +448,14 @@ const ExpenseRequestContent = ({ Nominal Biaya : - {formatCurrency(initialValues?.grand_total ?? 0)} + + {formatCurrency( + initialValues?.latest_approval.step_number === 4 || + initialValues?.latest_approval.step_number === 5 + ? (initialValues?.total_realisasi ?? 0) + : (initialValues?.total_pengajuan ?? 0) + )} + Status Pencairan diff --git a/src/components/pages/expense/pdf/ExpensePDF.tsx b/src/components/pages/expense/pdf/ExpensePDF.tsx index 54304015..c9c63203 100644 --- a/src/components/pages/expense/pdf/ExpensePDF.tsx +++ b/src/components/pages/expense/pdf/ExpensePDF.tsx @@ -235,7 +235,12 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => { { label: 'Nama Pengaju', value: expense?.created_user.name }, { label: 'Nominal Biaya', - value: formatCurrency(expense?.grand_total ?? 0), + value: formatCurrency( + expense?.latest_approval.step_number === 4 || + expense?.latest_approval.step_number === 5 + ? (expense?.total_realisasi ?? 0) + : (expense?.total_pengajuan ?? 0) + ), }, { label: 'Nominal Pengajuan', From ab2e7db9d01d270229eccad750a832b28c7dac6c Mon Sep 17 00:00:00 2001 From: randy-ar Date: Tue, 30 Dec 2025 20:32:23 +0700 Subject: [PATCH 18/22] fix(FE): error null in marketing --- .../pages/marketing/detail/MarketingDetail.tsx | 2 +- src/config/route-permission.ts | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/components/pages/marketing/detail/MarketingDetail.tsx b/src/components/pages/marketing/detail/MarketingDetail.tsx index efb80743..3e2a28dc 100644 --- a/src/components/pages/marketing/detail/MarketingDetail.tsx +++ b/src/components/pages/marketing/detail/MarketingDetail.tsx @@ -431,7 +431,7 @@ const MarketingDetail = ({