From 3b9599d16940042740a20f598f189c9952478f04 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 8 Jan 2026 13:51:28 +0700 Subject: [PATCH 1/7] refactor(FE): Set peer flag in package-lock.json --- package-lock.json | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 19a7623b..39f99f0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4506,6 +4506,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4516,6 +4517,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4597,6 +4599,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -5120,6 +5123,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5825,7 +5829,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/d3-array": { "version": "3.2.4", @@ -6201,7 +6206,8 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -6462,6 +6468,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6635,6 +6642,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -8152,6 +8160,7 @@ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz", "integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "fast-png": "^6.2.0", @@ -9371,6 +9380,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -9401,6 +9411,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -9468,7 +9479,8 @@ "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-number-format": { "version": "5.4.4", @@ -9485,6 +9497,7 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -9653,7 +9666,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -10519,6 +10533,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -10686,6 +10701,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" From c894f26d18205a9e8e96f6388905eac595773a34 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 8 Jan 2026 13:52:23 +0700 Subject: [PATCH 2/7] refactor(FE): Handle ideal/outside ranges in uniformity tooltip --- .../production/uniformity/UniformityChart.tsx | 5 +- .../uniformity/chart/UniformityBarChart.tsx | 102 ++++++++++++++++-- src/types/api/production/uniformity.d.ts | 2 + 3 files changed, 99 insertions(+), 10 deletions(-) diff --git a/src/components/pages/production/uniformity/UniformityChart.tsx b/src/components/pages/production/uniformity/UniformityChart.tsx index 52e7a24b..302d5468 100644 --- a/src/components/pages/production/uniformity/UniformityChart.tsx +++ b/src/components/pages/production/uniformity/UniformityChart.tsx @@ -39,9 +39,8 @@ const UniformityChart = ({ name: range.range, uv: range.bird_count, isIdeal: range.is_ideal_range, - idealCount: range.is_ideal_range - ? weekData.ideal_range.total_ideal_birds - : undefined, + idealRange: range.ideal_range, + outsideRange: range.outside_range, })); }, [chartData]); diff --git a/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx b/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx index 9f1f8656..88d0dc59 100644 --- a/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx +++ b/src/components/pages/production/uniformity/chart/UniformityBarChart.tsx @@ -27,7 +27,8 @@ interface BarChartData { name: string; uv: number; isIdeal?: boolean; - idealCount?: number; + idealRange?: string; + outsideRange?: string; } interface UniformityBarChartProps { @@ -40,30 +41,117 @@ function CustomTooltip({ payload, label, active }: CustomTooltipProps) { const chartData = data.payload as BarChartData; const labelStr = String(label); - if (chartData.isIdeal && chartData.idealCount !== undefined) { + // If the range has both ideal and outside ranges (like 340-344) + if (chartData.idealRange && chartData.outsideRange) { + return ( +
+

Uniformity 2025

+
+
+
+
+ Ideal +
+ + {chartData.idealRange} + +
+
+
+
+ Outside +
+ + {chartData.outsideRange} + +
+
+
+ Total Birds: + {payload[0].value} +
+
+
{labelStr}
+
+
+ ); + } + + // If the range has only ideal range + if (chartData.idealRange) { return (

Uniformity 2025

- {chartData.idealCount} of Birds + Ideal
- {labelStr} + {chartData.idealRange} +
+
+
+ Birds: + {payload[0].value} +
+
+
+ {labelStr}
); } + // If the range has only outside range + if (chartData.outsideRange) { + return ( +
+

Uniformity 2025

+
+
+
+ Outside +
+ + {chartData.outsideRange} + +
+
+
+ Birds: + {payload[0].value} +
+
+
+ {labelStr} +
+
+ ); + } + + // Fallback for backward compatibility return (

Uniformity 2025

-
- {payload[0].value} of Birds +
+ + {chartData.isIdeal ? 'Ideal' : 'Outside'} + +
+ {labelStr} +
+
+
+ Birds: + {payload[0].value}
- {labelStr}
); diff --git a/src/types/api/production/uniformity.d.ts b/src/types/api/production/uniformity.d.ts index 239de467..825607d8 100644 --- a/src/types/api/production/uniformity.d.ts +++ b/src/types/api/production/uniformity.d.ts @@ -8,6 +8,8 @@ export type WeightDistributionRange = { max_weight: number; bird_count: number; is_ideal_range: boolean; + ideal_range?: string; + outside_range?: string; }; export type IdealRange = { From badb1e141af1acf7ca36ca5c04c1459a1bd4219e Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 08:29:21 +0700 Subject: [PATCH 3/7] refactor(FE): Deduplicate delivery documents by filename --- .../inventory/movement/form/MovementForm.tsx | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 62a23595..cd01cd96 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -189,12 +189,45 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { return; } const documents: File[] = []; + const documentNameToIndex = new Map(); + let sequentialDocumentIndex = 0; + const deliveriesPayload = values.deliveries.map((d) => { - let documentIndex = 0; + let documentIndex = -1; if (d.document && d.document instanceof File) { - documents.push(d.document); - documentIndex = documents.length - 1; + const fileName = d.document.name; + + if (documentNameToIndex.has(fileName)) { + documentIndex = documentNameToIndex.get(fileName)!; + } else { + documents.push(d.document); + documentIndex = sequentialDocumentIndex; + documentNameToIndex.set(fileName, documentIndex); + sequentialDocumentIndex++; + } + } else if (d.document_path) { + const pathFileName = + d.document_path.split('/').pop() || d.document_path; + + if (documentNameToIndex.has(pathFileName)) { + documentIndex = documentNameToIndex.get(pathFileName)!; + } else { + documentIndex = sequentialDocumentIndex; + documentNameToIndex.set(pathFileName, documentIndex); + sequentialDocumentIndex++; + } + } else if (d.document && !(d.document instanceof File)) { + const existingDocFileName = + d.document.path.split('/').pop() || d.document.path; + + if (documentNameToIndex.has(existingDocFileName)) { + documentIndex = documentNameToIndex.get(existingDocFileName)!; + } else { + documentIndex = sequentialDocumentIndex; + documentNameToIndex.set(existingDocFileName, documentIndex); + sequentialDocumentIndex++; + } } return { From 4fdfe63dc9fdfe7ec1c50662618b4710be696b77 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 09:24:46 +0700 Subject: [PATCH 4/7] refactor(FE): Remove document_path from movement payload --- src/components/pages/inventory/movement/form/MovementForm.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index cd01cd96..d9aef6cd 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -235,7 +235,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { delivery_cost_per_item: parseInt((d.delivery_cost_per_item || '').toString()) || 0, document_index: documentIndex, - document_path: d.document_path, driver_name: d.driver_name, vehicle_plate: d.vehicle_plate, supplier_id: d.supplier_id, From 3ce30115f8563a249024e1c388a20543a6979310 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 09:30:51 +0700 Subject: [PATCH 5/7] refactor(FE): Add validation and error messages to filter modal --- .../production/uniformity/UniformityTable.tsx | 177 +++++++++++++----- 1 file changed, 127 insertions(+), 50 deletions(-) diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx index 9caa98a9..ff57a2ff 100644 --- a/src/components/pages/production/uniformity/UniformityTable.tsx +++ b/src/components/pages/production/uniformity/UniformityTable.tsx @@ -230,6 +230,7 @@ const UniformityTable = () => { const [filterStartDate, setFilterStartDate] = useState(''); const [filterEndDate, setFilterEndDate] = useState(''); const [projectFlockSearchValue, setProjectFlockSearchValue] = useState(''); + const [filterErrors, setFilterErrors] = useState>({}); const { setInputValue: setFilterLocationInputValue, @@ -423,9 +424,38 @@ const UniformityTable = () => { }, []); const handleApplyFilters = useCallback(() => { - setIsSubmitted(true); - filterModal.closeModal(); - }, [filterModal]); + const errors: Record = {}; + + if (!filterStartDate) { + errors.start_date = 'Tanggal mulai wajib diisi'; + } + if (!filterEndDate) { + errors.end_date = 'Tanggal akhir wajib diisi'; + } + if (!filterLocation) { + errors.location = 'Lokasi wajib dipilih'; + } + if (!filterProjectFlock) { + errors.project_flock = 'Project Flock wajib dipilih'; + } + if (!filterKandang) { + errors.kandang = 'Kandang wajib dipilih'; + } + + setFilterErrors(errors); + + if (Object.keys(errors).length === 0) { + setIsSubmitted(true); + filterModal.closeModal(); + } + }, [ + filterModal, + filterStartDate, + filterEndDate, + filterLocation, + filterProjectFlock, + filterKandang, + ]); const selectedRowIds = useMemo(() => { return Object.keys(rowSelection) @@ -1124,58 +1154,105 @@ const UniformityTable = () => {
- setFilterStartDate(e.target.value)} - className={{ wrapper: 'w-full' }} - /> +
+ { + setFilterStartDate(e.target.value); + setFilterErrors((prev) => ({ ...prev, start_date: '' })); + }} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.start_date && ( +

+ {filterErrors.start_date} +

+ )} +
- setFilterEndDate(e.target.value)} - className={{ wrapper: 'w-full' }} - /> +
+ { + setFilterEndDate(e.target.value); + setFilterErrors((prev) => ({ ...prev, end_date: '' })); + }} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.end_date && ( +

+ {filterErrors.end_date} +

+ )} +
- +
+ { + handleFilterLocationChange(value); + setFilterErrors((prev) => ({ ...prev, location: '' })); + }} + options={filterLocationOptions} + onInputChange={setFilterLocationInputValue} + isLoading={isLoadingFilterLocations} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.location && ( +

+ {filterErrors.location} +

+ )} +
- +
+ { + handleFilterProjectFlockChange(value); + setFilterErrors((prev) => ({ ...prev, project_flock: '' })); + }} + options={filterProjectFlockOptions} + onInputChange={setProjectFlockSearchValue} + isLoading={isLoadingFilterProjectFlocks} + isDisabled={!filterLocation} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.project_flock && ( +

+ {filterErrors.project_flock} +

+ )} +
- +
+ { + handleFilterKandangChange(value); + setFilterErrors((prev) => ({ ...prev, kandang: '' })); + }} + options={filterKandangOptions} + isDisabled={!filterProjectFlock} + className={{ wrapper: 'w-full' }} + /> + {filterErrors.kandang && ( +

+ {filterErrors.kandang} +

+ )} +
{/* Action Buttons */} From 88e3ec7bbc6a7772030d9755e62535ff9f892ad7 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 09:48:54 +0700 Subject: [PATCH 6/7] refactor(FE): Reduce default query limit to 100 --- src/components/pages/production/uniformity/UniformityTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/pages/production/uniformity/UniformityTable.tsx b/src/components/pages/production/uniformity/UniformityTable.tsx index ff57a2ff..6afba6dc 100644 --- a/src/components/pages/production/uniformity/UniformityTable.tsx +++ b/src/components/pages/production/uniformity/UniformityTable.tsx @@ -644,7 +644,7 @@ const UniformityTable = () => { if (filterEndDate) { queryParams.append('end_date', filterEndDate); } - queryParams.append('limit', '10000'); + queryParams.append('limit', '100'); queryParams.append('page', '1'); const queryString = queryParams.toString(); From 97c16ce59685337d0269edbf9c5f81feba6eebd4 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 9 Jan 2026 10:46:05 +0700 Subject: [PATCH 7/7] refactor(FE): Initialize and use current gauge week index --- .../production/uniformity/UniformityChart.tsx | 47 +++++++++++++------ 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/src/components/pages/production/uniformity/UniformityChart.tsx b/src/components/pages/production/uniformity/UniformityChart.tsx index 302d5468..6ddf50d3 100644 --- a/src/components/pages/production/uniformity/UniformityChart.tsx +++ b/src/components/pages/production/uniformity/UniformityChart.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; import Card from '@/components/Card'; import UniformityBarChart from '@/components/pages/production/uniformity/chart/UniformityBarChart'; import UniformityGaugeChart from '@/components/pages/production/uniformity/chart/UniformityGaugeChart'; @@ -22,13 +22,27 @@ const UniformityChart = ({ return uniformityData.chart_data; }, [uniformityData]); + useEffect(() => { + if (uniformityData?.chart_data?.gauge_chart?.week_info) { + const { current_week_index } = + uniformityData.chart_data.gauge_chart.week_info; + setCurrentWeekIndex(current_week_index); + } + }, [uniformityData]); + const barChartData = useMemo(() => { - if (!chartData?.bar_chart) { + if (!chartData?.bar_chart || !chartData?.gauge_chart) { return []; } - const { bar_chart } = chartData; - const currentWeekStr = String(bar_chart.current_week); + const { bar_chart, gauge_chart } = chartData; + const currentWeekData = gauge_chart.available_weeks[currentWeekIndex]; + + if (!currentWeekData || !currentWeekData.has_data) { + return []; + } + + const currentWeekStr = String(currentWeekData.week); const weekData = bar_chart.all_weeks[currentWeekStr]; if (!weekData || !weekData.has_data) { @@ -42,7 +56,7 @@ const UniformityChart = ({ idealRange: range.ideal_range, outsideRange: range.outside_range, })); - }, [chartData]); + }, [chartData, currentWeekIndex]); const gaugeChartData = useMemo(() => { if (!chartData?.gauge_chart || !uniformityData) return undefined; @@ -54,28 +68,33 @@ const UniformityChart = ({ return undefined; } + const hasPrevWeek = currentWeekIndex > 0; + const hasNextWeek = + currentWeekIndex < gauge_chart.available_weeks.length - 1; + return { value: currentWeekData.uniformity_percentage, label: 'Uniformity', week: `Week ${currentWeekData.week}`, currentValue: currentWeekData.ideal_count, totalValue: currentWeekData.total_count, - hasPrevWeek: gauge_chart.week_info.has_prev_week, - hasNextWeek: gauge_chart.week_info.has_next_week, + hasPrevWeek, + hasNextWeek, }; }, [chartData, currentWeekIndex, uniformityData]); const handleWeekChange = (direction: 'prev' | 'next') => { if (!chartData?.gauge_chart) return; - const { available_weeks, week_info } = chartData.gauge_chart; + const { available_weeks } = chartData.gauge_chart; - if (direction === 'prev' && week_info.has_prev_week) { - setCurrentWeekIndex((prev) => Math.max(0, prev - 1)); - } else if (direction === 'next' && week_info.has_next_week) { - setCurrentWeekIndex((prev) => - Math.min(available_weeks.length - 1, prev + 1) - ); + if (direction === 'prev' && currentWeekIndex > 0) { + setCurrentWeekIndex((prev) => prev - 1); + } else if ( + direction === 'next' && + currentWeekIndex < available_weeks.length - 1 + ) { + setCurrentWeekIndex((prev) => prev + 1); } };