From c3c1bbbe9678d69e2b4b1acbc85eb73c32b47934 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 08:57:57 +0700 Subject: [PATCH 01/12] feat(FE-326): Add egg weight field to recording forms --- .../recording/form/RecordingForm.schema.ts | 8 ++++ .../recording/form/RecordingForm.tsx | 40 ++++++++++++++++++- src/types/api/production/recording.d.ts | 2 + 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.schema.ts b/src/components/pages/production/recording/form/RecordingForm.schema.ts index 4d72e053..99496843 100644 --- a/src/components/pages/production/recording/form/RecordingForm.schema.ts +++ b/src/components/pages/production/recording/form/RecordingForm.schema.ts @@ -32,6 +32,7 @@ type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & { eggs: { product_warehouse_id: number; qty: number | string; + weight: number | string; }[]; }; @@ -62,6 +63,7 @@ export type DepletionSchema = { export type EggSchema = { product_warehouse_id: number; qty: number | string; + weight: number | string; }; const BodyWeightObjectSchema: Yup.ObjectSchema = Yup.object({ @@ -109,6 +111,10 @@ const EggObjectSchema: Yup.ObjectSchema = Yup.object({ .required('Jumlah telur wajib diisi!') .min(1, 'Jumlah telur tidak boleh 0!') .typeError('Jumlah telur harus berupa angka!'), + weight: Yup.number() + .required('Berat telur wajib diisi!') + .min(1, 'Berat telur minimal 1 gram!') + .typeError('Berat telur harus berupa angka!'), }); export const RecordingGrowingFormSchema: Yup.ObjectSchema = @@ -295,10 +301,12 @@ export const getRecordingLayingFormInitialValues = ( eggs: initialValues?.eggs?.map((egg: CreateEggPayload) => ({ product_warehouse_id: egg.product_warehouse_id, qty: egg.qty, + weight: egg.weight, })) ?? [ { product_warehouse_id: 0, qty: '', + weight: '', }, ], }); diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 43ffc98b..582e8e78 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -181,6 +181,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { eggs: (values.eggs ?? []).map((egg) => ({ product_warehouse_id: egg.product_warehouse_id, qty: Number(egg.qty) || 0, + weight: + typeof egg.weight === 'number' + ? egg.weight + : parseFloat(String(egg.weight)) || 0, })), }; }, @@ -1148,7 +1152,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { if (hasSameDayRecording) { toast.error( - `Recording untuk hari ${nextDayRecording.next_day} sudah ada. + `Recording untuk hari ${nextDayRecording.next_day} sudah ada. Tidak bisa membuat recording duplikat, mohon perbarui recording yang sudah ada terlebih dahulu.` ); return; @@ -1485,6 +1489,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { [formik] ); + const handleEggWeightChangeWrapper = useCallback( + (idx: number) => (e: React.ChangeEvent) => { + const value = parseFloat(e.target.value) || 0; + formik.setFieldValue(`eggs.${idx}.weight`, value); + }, + [formik] + ); + const removeEgg = (idx: number) => { const updatedEggs = ( formik.values as RecordingLayingFormValues @@ -2688,6 +2700,32 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { placeholder='Masukkan jumlah telur' /> +
+ +
{(type as 'add' | 'edit' | 'detail') !== 'detail' && ( diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index e7b28f47..46579509 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -53,6 +53,7 @@ export type RecordingEgg = { recording_id: number; product_warehouse_id: number; qty: number; + weight: number; created_by: User; product_warehouse: ProductWarehouse; gradings?: { @@ -129,6 +130,7 @@ export type CreateGradingRecordingPayload = { export type CreateEggPayload = { product_warehouse_id: number; qty: number; + weight: number; }; export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & { From df3f3422145677d8469d39a73a2097492d342aba Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 10:07:00 +0700 Subject: [PATCH 02/12] chore(CVE): update Next.js version to ^15.5.7 in package.json and package-lock.json --- package-lock.json | 92 ++++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/package-lock.json b/package-lock.json index ec1316ae..535bb986 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "clsx": "^2.1.1", "formik": "^2.4.6", "moment": "^2.30.1", - "next": "15.5.3", + "next": "^15.5.7", "react": "19.1.0", "react-day-picker": "^9.11.1", "react-dom": "19.1.0", @@ -1082,9 +1082,9 @@ } }, "node_modules/@next/env": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.3.tgz", - "integrity": "sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.7.tgz", + "integrity": "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -1098,9 +1098,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.3.tgz", - "integrity": "sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.7.tgz", + "integrity": "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==", "cpu": [ "arm64" ], @@ -1114,9 +1114,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.3.tgz", - "integrity": "sha512-w83w4SkOOhekJOcA5HBvHyGzgV1W/XvOfpkrxIse4uPWhYTTRwtGEM4v/jiXwNSJvfRvah0H8/uTLBKRXlef8g==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.7.tgz", + "integrity": "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==", "cpu": [ "x64" ], @@ -1130,9 +1130,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.3.tgz", - "integrity": "sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.7.tgz", + "integrity": "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==", "cpu": [ "arm64" ], @@ -1146,9 +1146,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.3.tgz", - "integrity": "sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.7.tgz", + "integrity": "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==", "cpu": [ "arm64" ], @@ -1162,9 +1162,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.3.tgz", - "integrity": "sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.7.tgz", + "integrity": "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==", "cpu": [ "x64" ], @@ -1178,9 +1178,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.3.tgz", - "integrity": "sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.7.tgz", + "integrity": "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==", "cpu": [ "x64" ], @@ -1194,9 +1194,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.3.tgz", - "integrity": "sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.7.tgz", + "integrity": "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==", "cpu": [ "arm64" ], @@ -1210,9 +1210,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.3.tgz", - "integrity": "sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.7.tgz", + "integrity": "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==", "cpu": [ "x64" ], @@ -1855,6 +1855,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" } @@ -1924,6 +1925,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", @@ -2447,6 +2449,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3060,7 +3063,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/daisyui": { "version": "5.3.10", @@ -3516,6 +3520,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3689,6 +3694,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5654,12 +5660,12 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.3", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.3.tgz", - "integrity": "sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw==", + "version": "15.5.7", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.7.tgz", + "integrity": "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==", "license": "MIT", "dependencies": { - "@next/env": "15.5.3", + "@next/env": "15.5.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -5672,14 +5678,14 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.3", - "@next/swc-darwin-x64": "15.5.3", - "@next/swc-linux-arm64-gnu": "15.5.3", - "@next/swc-linux-arm64-musl": "15.5.3", - "@next/swc-linux-x64-gnu": "15.5.3", - "@next/swc-linux-x64-musl": "15.5.3", - "@next/swc-win32-arm64-msvc": "15.5.3", - "@next/swc-win32-x64-msvc": "15.5.3", + "@next/swc-darwin-arm64": "15.5.7", + "@next/swc-darwin-x64": "15.5.7", + "@next/swc-linux-arm64-gnu": "15.5.7", + "@next/swc-linux-arm64-musl": "15.5.7", + "@next/swc-linux-x64-gnu": "15.5.7", + "@next/swc-linux-x64-musl": "15.5.7", + "@next/swc-win32-arm64-msvc": "15.5.7", + "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { @@ -6167,6 +6173,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" } @@ -6197,6 +6204,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" }, @@ -7083,6 +7091,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -7250,6 +7259,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 7396d49d..85485ee3 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "clsx": "^2.1.1", "formik": "^2.4.6", "moment": "^2.30.1", - "next": "15.5.3", + "next": "^15.5.7", "react": "19.1.0", "react-day-picker": "^9.11.1", "react-dom": "19.1.0", From 86a0faaa52f9464bb3033413ca3d70595dfdd983 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 10:09:21 +0700 Subject: [PATCH 03/12] chore(ci): clean up .gitlab-ci.yml by removing unnecessary whitespace --- .gitlab-ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 91da62b9..c37bfd35 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -140,7 +140,6 @@ deploy:dev: environment: name: development url: https://dev-lti-erp.mbugroup.id - # ====== PRODUCTION ====== # build:production: # <<: *build_template @@ -163,4 +162,3 @@ deploy:dev: # environment: # name: production - From b464432581a70e978e6f6c71addf615643cb8deb Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 18:49:17 +0700 Subject: [PATCH 04/12] chore(FE): Add .claude to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d86875dd..e47b8ec3 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ next-env.d.ts # idea .idea + +# claude +.claude From 5deca5739fffed9f15a668cbb4c4dfde132945d5 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 20:08:35 +0700 Subject: [PATCH 05/12] refactor(FE-318): Add egg weight column and separate inputs --- .../recording/form/RecordingForm.tsx | 114 +++++++++--------- 1 file changed, 60 insertions(+), 54 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index 582e8e78..f9314a9d 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -2599,6 +2599,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { * + + Berat (gram) + + * + + {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( Action )} @@ -2674,58 +2683,55 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { /> -
- -
-
- -
+ + + + {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( @@ -2908,7 +2914,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { > Submit - {isLayingCategory && ( + {/*{isLayingCategory && ( { Next Step: Grading - )} + )}*/} )} From 305b8e5005d47a1b8f86eb9eab9790db1a906d79 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 20:12:18 +0700 Subject: [PATCH 06/12] refactor(FE-319): Remove Grading-Telur step from RECORDINGS workflow --- src/config/approval-line.ts | 18 +++--------------- src/config/constant.ts | 4 ---- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/config/approval-line.ts b/src/config/approval-line.ts index 3af866c6..48b1268b 100644 --- a/src/config/approval-line.ts +++ b/src/config/approval-line.ts @@ -51,14 +51,10 @@ export const MARKETING_APPROVAL_LINE: ApprovalLine = [ export const RECORDING_APPROVAL_LINE: ApprovalLine = [ { step_number: 1, - step_name: 'Grading-Telur', - }, - { - step_number: 2, step_name: 'Pengajuan', }, { - step_number: 3, + step_number: 2, step_name: 'Disetujui', }, ] as const; @@ -66,14 +62,10 @@ export const RECORDING_APPROVAL_LINE: ApprovalLine = [ export const GROWING_RECORDING_APPROVAL_LINE: ApprovalLine = [ { step_number: 1, - step_name: 'Grading-Telur', - }, - { - step_number: 2, step_name: 'Pengajuan', }, { - step_number: 3, + step_number: 2, step_name: 'Disetujui', }, ] as const; @@ -81,14 +73,10 @@ export const GROWING_RECORDING_APPROVAL_LINE: ApprovalLine = [ export const LAYING_RECORDING_APPROVAL_LINE: ApprovalLine = [ { step_number: 1, - step_name: 'Grading-Telur', - }, - { - step_number: 2, step_name: 'Pengajuan', }, { - step_number: 3, + step_number: 2, step_name: 'Disetujui', }, ] as const; diff --git a/src/config/constant.ts b/src/config/constant.ts index dc36025b..d4e08942 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -261,10 +261,6 @@ export const APPROVAL_WORKFLOWS = [ { key: 'RECORDINGS', steps: [ - { - step_number: 1, - step_name: 'Grading-Telur', - }, { step_number: 2, step_name: 'Pengajuan', From 2e6a724b2f2361268fb86cbe29e91edc19cc396b Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 20:13:10 +0700 Subject: [PATCH 07/12] refactor(FE-319): Use approval step 2 and remove grading button --- .../recording/form/RecordingForm.tsx | 74 +------------------ 1 file changed, 1 insertion(+), 73 deletions(-) diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx index f9314a9d..f6058d70 100644 --- a/src/components/pages/production/recording/form/RecordingForm.tsx +++ b/src/components/pages/production/recording/form/RecordingForm.tsx @@ -112,7 +112,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { return ( recording?.approval?.action === 'APPROVED' && recording?.approval?.step_name === 'Disetujui' && - recording?.approval?.step_number === 3 + recording?.approval?.step_number === 2 ); }, []); @@ -2914,78 +2914,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { > Submit - {/*{isLayingCategory && ( - - - - )}*/} )} From 545af8267a93cc3ad738dc6e5fb6dbb3d8ffed42 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 23:33:34 +0700 Subject: [PATCH 08/12] feat(FE-319): Refactor recording types and simplify payloads --- src/types/api/production/recording.d.ts | 46 +++---------------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/src/types/api/production/recording.d.ts b/src/types/api/production/recording.d.ts index 46579509..9bed7685 100644 --- a/src/types/api/production/recording.d.ts +++ b/src/types/api/production/recording.d.ts @@ -9,8 +9,7 @@ export type ProductionMetrics = { cum_intake: number; fcr_value: number; total_chick_qty: number; - daily_depletion_rate?: number; - cum_depletion?: number; + cum_depletion: number; }; export type BaseRecording = { @@ -18,43 +17,33 @@ export type BaseRecording = { project_flock_kandang_id: number; record_datetime: string; day: number; - created_by: User; + project_flock_category?: 'GROWING' | 'LAYING'; } & ProductionMetrics; export type RecordingBW = { - id: number; - recording_id: number; avg_weight: number; qty: number; total_weight: number; }; export type RecordingDepletion = { - id: number; - recording_id: number; product_warehouse_id: number; qty: number; product_warehouse: ProductWarehouse; }; export type RecordingStock = { - id: number; - recording_id: number; product_warehouse_id: number; usage_amount?: number; - usage_qty: number; - qty: number; pending_qty: number; product_warehouse: ProductWarehouse; }; export type RecordingEgg = { id: number; - recording_id: number; product_warehouse_id: number; qty: number; weight: number; - created_by: User; product_warehouse: ProductWarehouse; gradings?: { grade: string; @@ -72,19 +61,12 @@ export type GradingEgg = { export type Recording = BaseMetadata & BaseRecording & { - project_flock_category?: 'GROWING' | 'LAYING'; approval?: BaseApproval; - egg_grading_status?: string | null; - egg_grading_pending_qty?: number | null; - egg_grading_completed_qty?: number | null; + created_user: User; body_weights?: RecordingBW[]; depletions?: RecordingDepletion[]; stocks?: RecordingStock[]; eggs?: RecordingEgg[]; - recording_bws?: RecordingBW[]; - recording_depletions?: RecordingDepletion[]; - recording_stocks?: RecordingStock[]; - recording_eggs?: RecordingEgg[]; grading_eggs?: GradingEgg[]; }; @@ -109,24 +91,6 @@ export type CreateGrowingRecordingPayload = { }[]; }; -export type CreateGradingPayload = { - eggs_grading: { - recording_egg_id: number; - grade: string; - qty: number; - }[]; -}; - -export type UpdateGradingPayload = CreateGradingPayload; - -export type CreateGradingRecordingPayload = { - eggs_grading: { - recording_egg_id: number; - grade: string; - qty: number; - }[]; -}; - export type CreateEggPayload = { product_warehouse_id: number; qty: number; @@ -139,11 +103,9 @@ export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & { export type CreateRecordingPayload = | CreateGrowingRecordingPayload - | CreateLayingRecordingPayload - | CreateGradingRecordingPayload; + | CreateLayingRecordingPayload; export type UpdateGrowingRecordingPayload = CreateGrowingRecordingPayload; export type UpdateLayingRecordingPayload = CreateLayingRecordingPayload; -export type UpdateGradingRecordingPayload = CreateGradingRecordingPayload; export type UpdateRecordingPayload = CreateRecordingPayload; From 7c4bd81364108bfce196bc1a7e0dc438295e41f4 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 23:34:01 +0700 Subject: [PATCH 09/12] feat(FE-319): Remove recording grading feature --- .../production/recording/grading/add/page.tsx | 49 - .../recording/grading/detail/edit/page.tsx | 53 - .../recording/grading/detail/page.tsx | 52 - .../production/recording/grading/layout.tsx | 11 - .../recording/grading/form/GradingForm.tsx | 1050 ----------------- src/services/api/production.ts | 24 - 6 files changed, 1239 deletions(-) delete mode 100644 src/app/production/recording/grading/add/page.tsx delete mode 100644 src/app/production/recording/grading/detail/edit/page.tsx delete mode 100644 src/app/production/recording/grading/detail/page.tsx delete mode 100644 src/app/production/recording/grading/layout.tsx delete mode 100644 src/components/pages/production/recording/grading/form/GradingForm.tsx diff --git a/src/app/production/recording/grading/add/page.tsx b/src/app/production/recording/grading/add/page.tsx deleted file mode 100644 index 9b918d98..00000000 --- a/src/app/production/recording/grading/add/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; -import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm'; -import { RecordingApi } from '@/services/api/production'; -import { isResponseSuccess } from '@/lib/api-helper'; - -const AddGrading = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const recordingId = searchParams.get('recording_id'); - - const { data: recording, isLoading: isLoadingRecording } = useSWR( - recordingId && recordingId !== 'new' ? [recordingId] : null, - ([id]) => RecordingApi.getSingle(parseInt(id)) - ); - - if ( - recordingId && - recordingId !== 'new' && - !isLoadingRecording && - (!recording || !isResponseSuccess(recording)) - ) { - router.replace('/404'); - return; - } - - return ( -
- {recordingId && recordingId !== 'new' && isLoadingRecording && ( - - )} - {(!recordingId || - recordingId === 'new' || - (!isLoadingRecording && recording && isResponseSuccess(recording))) && ( - - )} -
- ); -}; - -export default AddGrading; diff --git a/src/app/production/recording/grading/detail/edit/page.tsx b/src/app/production/recording/grading/detail/edit/page.tsx deleted file mode 100644 index 0a65f528..00000000 --- a/src/app/production/recording/grading/detail/edit/page.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; -import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm'; -import { RecordingApi } from '@/services/api/production'; -import { isResponseSuccess } from '@/lib/api-helper'; - -const EditGrading = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const recordingId = searchParams.get('recordingId'); - const gradingId = searchParams.get('gradingId'); - - const { data: recording, isLoading: isLoadingRecording } = useSWR( - recordingId ? [recordingId] : null, - ([id]) => RecordingApi.getSingle(parseInt(id)) - ); - - if (!recordingId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoadingRecording && (!recording || !isResponseSuccess(recording))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoadingRecording && ( - - )} - {!isLoadingRecording && recording && isResponseSuccess(recording) && ( - egg.id === parseInt(gradingId || '0') - )} - /> - )} -
- ); -}; - -export default EditGrading; diff --git a/src/app/production/recording/grading/detail/page.tsx b/src/app/production/recording/grading/detail/page.tsx deleted file mode 100644 index 6a5fbcba..00000000 --- a/src/app/production/recording/grading/detail/page.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import { useRouter, useSearchParams } from 'next/navigation'; -import useSWR from 'swr'; -import GradingForm from '@/components/pages/production/recording/grading/form/GradingForm'; -import { RecordingApi } from '@/services/api/production'; -import { isResponseSuccess } from '@/lib/api-helper'; - -const DetailGrading = () => { - const router = useRouter(); - const searchParams = useSearchParams(); - - const gradingId = searchParams.get('gradingId'); - - const { data: grading, isLoading: isLoadingGrading } = useSWR( - gradingId ? [gradingId] : null, - ([id]) => RecordingApi.getSingle(parseInt(id)) - ); - - if (!gradingId) { - router.back(); - - return ( -
- -
- ); - } - - if (!isLoadingGrading && (!grading || !isResponseSuccess(grading))) { - router.replace('/404'); - return; - } - - return ( -
- {isLoadingGrading && ( - - )} - {!isLoadingGrading && grading && isResponseSuccess(grading) && ( - egg.id === parseInt(gradingId) - )} - /> - )} -
- ); -}; - -export default DetailGrading; diff --git a/src/app/production/recording/grading/layout.tsx b/src/app/production/recording/grading/layout.tsx deleted file mode 100644 index 7220dfa1..00000000 --- a/src/app/production/recording/grading/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import SuspenseHelper from '@/components/helper/SuspenseHelper'; - -const Layout = ({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) => { - return {children}; -}; - -export default Layout; diff --git a/src/components/pages/production/recording/grading/form/GradingForm.tsx b/src/components/pages/production/recording/grading/form/GradingForm.tsx deleted file mode 100644 index 417c6356..00000000 --- a/src/components/pages/production/recording/grading/form/GradingForm.tsx +++ /dev/null @@ -1,1050 +0,0 @@ -'use client'; - -import { useMemo, useState, useEffect, useCallback } from 'react'; -import { useRouter, useSearchParams } from 'next/navigation'; -import { useFormik } from 'formik'; -import { Icon } from '@iconify/react'; - -import Button from '@/components/Button'; -import NumberInput from '@/components/input/NumberInput'; -import SelectInput, { OptionType } from '@/components/input/SelectInput'; -import CheckboxInput from '@/components/input/CheckboxInput'; -import ConfirmationModal from '@/components/modal/ConfirmationModal'; -import Card from '@/components/Card'; -import Badge from '@/components/Badge'; - -import { - CreateGradingPayload, - UpdateGradingPayload, - RecordingEgg, - GradingEgg, - Recording, -} from '@/types/api/production/recording'; -import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; -import { type BaseApiResponse } from '@/types/api/api-general'; - -import { - RecordingGradingFormSchema, - RecordingGradingFormValues, - UpdateRecordingGradingFormSchema, - getRecordingGradingFormInitialValues, -} from '@/components/pages/production/recording/form/RecordingForm.schema'; - -import { cn, formatDate } from '@/lib/helper'; -import toast from 'react-hot-toast'; -import { isResponseError } from '@/lib/api-helper'; - -import { - RecordingApi, - ProjectFlockKandangApi, -} from '@/services/api/production'; - -import { useModal } from '@/components/Modal'; -import useSWR from 'swr'; - -// INTERFACES & PROPS -interface GradingFormProps { - type?: 'add' | 'edit' | 'detail'; - initialValues?: RecordingEgg & { - grading_eggs?: GradingEgg[]; - gradings?: { grade: string; qty: number }[]; - }; -} - -const GradingForm = ({ type = 'add', initialValues }: GradingFormProps) => { - // HOOKS & ROUTER - const router = useRouter(); - const searchParams = useSearchParams(); - const recordingId = searchParams.get('recording_id'); - - // STATE MANAGEMENT - const [selectedGradingItems, setSelectedGradingItems] = useState( - [] - ); - const [gradingFormErrorMessage, setGradingFormErrorMessage] = useState(''); - const [isDeleteLoading, setIsDeleteLoading] = useState(false); - const deleteModal = useModal(); - - // API DATA FETCHING - const recordingUrl = useMemo(() => { - const recordingIdToUse = recordingId; - if (!recordingIdToUse) return null; - return `${RecordingApi.basePath}/${recordingIdToUse}`; - }, [recordingId]); - - const { data: recordingData } = useSWR( - recordingUrl, - recordingUrl ? RecordingApi.getAllFetcher : null - ); - - // DATA PROCESSING - const recording = - recordingData?.status === 'success' - ? (recordingData.data as unknown as Recording) - : undefined; - - const projectFlockKandangUrl = useMemo(() => { - if (!recording?.project_flock_kandang_id) return null; - return `${ProjectFlockKandangApi.basePath}/${recording.project_flock_kandang_id}`; - }, [recording?.project_flock_kandang_id]); - - const { data: projectFlockKandangData } = useSWR( - projectFlockKandangUrl, - projectFlockKandangUrl ? ProjectFlockKandangApi.getAllFetcher : null - ); - - const projectFlockKandang = - projectFlockKandangData?.status === 'success' - ? (projectFlockKandangData.data as unknown as ProjectFlockKandang) - : undefined; - - const konsumsiBaikEggData = useMemo(() => { - if (!recording?.eggs) return null; - - const konsumsiBaikEgg = recording.eggs.find((egg: RecordingEgg) => - egg.product_warehouse?.product?.name - ?.toLowerCase() - .includes('konsumsi baik') - ); - - return konsumsiBaikEgg || null; - }, [recording]); - - const totalKonsumsiBaikEggs = konsumsiBaikEggData?.qty || 0; - const konsumsiBaikEggId = konsumsiBaikEggData?.id; - - const isDataLoading = - !recording || - (totalKonsumsiBaikEggs === 0 && - recording?.project_flock_category === 'LAYING'); - - // FORM HANDLERS - const createGradingHandler = useCallback( - async (payload: CreateGradingPayload) => { - const res = (await RecordingApi.createGrading(payload)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setGradingFormErrorMessage(res?.message || 'Failed to add Grading'); - return; - } - - toast.success(res?.message || 'Successfully added Grading!'); - router.push('/production/recording'); - }, - [router] - ); - - const updateGradingHandler = useCallback( - async (gradingId: number, payload: UpdateGradingPayload) => { - const res = (await RecordingApi.updateGrading(gradingId, payload)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setGradingFormErrorMessage(res?.message || 'Failed to update Grading'); - return; - } - toast.success(res?.message || 'Successfully updated Grading!'); - router.refresh(); - router.push('/production/recording'); - }, - [router] - ); - - const deleteRecordingClickHandler = useCallback(() => { - deleteModal.openModal(); - }, [deleteModal]); - - const confirmationModalDeleteClickHandler = useCallback(async () => { - if (!initialValues?.id) return; - - setIsDeleteLoading(true); - try { - const res = (await RecordingApi.deleteGrading(initialValues.id)) as - | BaseApiResponse - | undefined; - - if (!res || isResponseError(res)) { - setGradingFormErrorMessage(res?.message || 'Failed to delete Grading'); - return; - } - deleteModal.closeModal(); - toast.success(res?.message || 'Successfully delete Grading!'); - router.push('/production/recording'); - } catch { - setGradingFormErrorMessage('Failed to delete Grading'); - } finally { - setIsDeleteLoading(false); - } - }, [deleteModal, initialValues?.id, router]); - - // FORMIK SETUP - const formikInitialValues = useMemo(() => { - let recordingEggId: number | undefined = konsumsiBaikEggId; - - if (!recordingEggId && initialValues?.id) { - recordingEggId = initialValues.id; - } - - if (!recordingEggId) { - recordingEggId = parseInt(recordingId || '0') || 0; - } - - let gradingData: { - recording_egg_id: number; - grade: string; - qty: number; - }[] = []; - - if (initialValues?.grading_eggs && initialValues.grading_eggs.length > 0) { - gradingData = initialValues.grading_eggs.map((grading: GradingEgg) => ({ - recording_egg_id: recordingEggId, - grade: grading.grade, - qty: grading.qty, - })); - } else if (initialValues?.gradings && initialValues.gradings.length > 0) { - gradingData = initialValues.gradings.map( - (grading: { grade: string; qty: number }) => ({ - recording_egg_id: recordingEggId, - grade: grading.grade, - qty: grading.qty, - }) - ); - } - - return getRecordingGradingFormInitialValues({ - recording_egg_id: recordingEggId, - eggs_grading: gradingData, - }); - }, [initialValues, recordingId, konsumsiBaikEggId]); - - const formik = useFormik({ - initialValues: formikInitialValues, - enableReinitialize: true, - validationSchema: (() => { - return type === 'edit' - ? UpdateRecordingGradingFormSchema - : RecordingGradingFormSchema; - })(), - validateOnChange: true, - validateOnBlur: true, - onSubmit: async (values) => { - const gradingPayload = { - eggs_grading: (values.eggs_grading ?? []).map((grading) => ({ - recording_egg_id: grading.recording_egg_id, - grade: grading.grade, - qty: grading.qty || 0, - })), - }; - - switch (type) { - case 'add': - await createGradingHandler(gradingPayload as CreateGradingPayload); - break; - case 'edit': - await updateGradingHandler( - initialValues?.id as number, - gradingPayload as UpdateGradingPayload - ); - break; - } - }, - }); - - const currentGradingTotal = useMemo(() => { - return (formik.values.eggs_grading || []).reduce((total, grading) => { - return total + (Number(grading.qty) || 0); - }, 0); - }, [formik.values.eggs_grading]); - - const isGradingExceedsAvailable = currentGradingTotal > totalKonsumsiBaikEggs; - const isGradingIncomplete = - currentGradingTotal < totalKonsumsiBaikEggs && totalKonsumsiBaikEggs > 0; - const hasUserStartedGrading = currentGradingTotal > 0; - - // GRADING HANDLERS - const addGrading = () => { - let recordingEggId: number | undefined = konsumsiBaikEggId; - - if (!recordingEggId && initialValues?.id) { - recordingEggId = initialValues.id; - } - - if (!recordingEggId) { - recordingEggId = parseInt(recordingId || '0') || 0; - } - - const newGrading = [ - ...(formik.values.eggs_grading || []), - { - recording_egg_id: recordingEggId, - grade: '', - qty: '', - }, - ]; - formik.setFieldValue('eggs_grading', newGrading); - }; - - const handleGradingGradeChangeWrapper = useCallback( - (idx: number) => (selectedOption: OptionType | OptionType[] | null) => { - const option = selectedOption as OptionType | null; - formik.setFieldValue(`eggs_grading.${idx}.grade`, option?.label || ''); - }, - [formik] - ); - - const handleGradingQtyChangeWrapper = useCallback( - (idx: number) => (e: React.ChangeEvent) => { - const value = parseFloat(e.target.value) || 0; - formik.setFieldValue(`eggs_grading.${idx}.qty`, value); - }, - [formik] - ); - - const removeGrading = (idx: number) => { - const updatedGrading = formik.values.eggs_grading?.filter( - (_, i) => i !== idx - ); - formik.setFieldValue('eggs_grading', updatedGrading); - }; - - const removeSelectedGrading = () => { - const updatedGrading = formik.values.eggs_grading?.filter( - (_, idx) => !selectedGradingItems.includes(idx) - ); - formik.setFieldValue('eggs_grading', updatedGrading); - setSelectedGradingItems([]); - }; - - // VALIDATION HELPERS - const isRepeaterInputError = ( - arrayName: 'eggs_grading', - column: string, - idx: number - ) => { - const touched = formik.touched as Record; - const errors = formik.errors as Record; - - if (!touched[arrayName] || !Array.isArray(touched[arrayName])) { - return { - isError: false, - errorMessage: '', - }; - } - - const touchedField = (touched[arrayName] as unknown[])?.[idx] as Record< - string, - unknown - >; - const errorField = (errors[arrayName] as unknown[])?.[idx] as Record< - string, - unknown - >; - - return { - isError: touchedField && Boolean(errorField?.[column]), - errorMessage: - touchedField && errorField?.[column] - ? (errorField[column] as string) - : '', - }; - }; - - // EFFECTS - useEffect(() => { - if (isDataLoading) { - toast.dismiss('grading-exceeds'); - toast.dismiss('grading-incomplete'); - return; - } - - if (isGradingExceedsAvailable && currentGradingTotal > 0) { - toast.error( - `Total grading (${currentGradingTotal}) melebihi telur yang tersedia (${totalKonsumsiBaikEggs})!`, - { - id: 'grading-exceeds', - duration: 3000, - } - ); - toast.dismiss('grading-incomplete'); - } else if (isGradingIncomplete && hasUserStartedGrading) { - toast.error( - `Total grading (${currentGradingTotal}) tidak sama dengan total telur konsumsi baik yang tersedia (${totalKonsumsiBaikEggs})! Semua telur harus digrading.`, - { - id: 'grading-incomplete', - duration: 3000, - } - ); - toast.dismiss('grading-exceeds'); - } else { - toast.dismiss('grading-exceeds'); - toast.dismiss('grading-incomplete'); - } - }, [ - isDataLoading, - isGradingExceedsAvailable, - isGradingIncomplete, - hasUserStartedGrading, - currentGradingTotal, - totalKonsumsiBaikEggs, - ]); - - useEffect(() => { - if ( - konsumsiBaikEggId && - formik.values.eggs_grading && - formik.values.eggs_grading.length === 0 - ) { - formik.setFieldValue('eggs_grading', [ - { recording_egg_id: konsumsiBaikEggId, grade: '', qty: '' }, - ]); - } - }, [konsumsiBaikEggId, formik.values.eggs_grading.length]); - - return ( - <> -
-
- -

- {type === 'add' && 'Tambah Grading'} - {type === 'edit' && 'Edit Grading'} - {type === 'detail' && 'Detail Grading'} -

-
- -
- {/* Basic Info Card */} - -
- {/* Status Approval */} - {recording?.approval && ( -
- Status Approval -
- - {(() => { - const actionText = (() => { - switch (recording.approval.action) { - case 'APPROVED': - return 'Disetujui'; - case 'REJECTED': - return 'Ditolak'; - case 'CREATED': - return 'Dibuat'; - case 'UPDATED': - return 'Diperbarui'; - default: - return recording.approval.action; - } - })(); - - const stepName = recording.approval.step_name; - - if (stepName === actionText) { - return stepName; - } - - return `${stepName} - ${actionText}`; - })()} - -
-
- )} - {/* Recording Info */} -
- Lokasi -

- {projectFlockKandang?.project_flock?.location?.name || '-'} -

-
-
- Project Flock -

- {projectFlockKandang?.project_flock?.flock_name || '-'} -

-
-
- Kandang -

- {projectFlockKandang?.kandang?.name || '-'} -

-
-
- Tanggal Recording -

- {recording - ? formatDate(recording.record_datetime, 'DD MMMM YYYY') - : '-'} -

-
-
- Hari -

Hari ke-{recording?.day || '-'}

-
-
- Kategori -

- - {recording?.project_flock_category || '-'} - -

-
-
- Periode -

- - Periode {projectFlockKandang?.project_flock?.period || '-'} - -

-
-
- -
- {/* Additional Recording Info */} -
-
-
- -
- - Detail Recording - -
-
-
-

Area

-

- {projectFlockKandang?.project_flock?.area?.name || '-'} -

-
-
-

Status Kandang

-

- {projectFlockKandang?.kandang?.status || '-'} -

-
-
-
- - {/* Total Telur Konsumsi Baik Info */} -
-
-
-

- Total Telur Konsumsi Baik -

-
-

- {isDataLoading ? ( - - ) : ( - totalKonsumsiBaikEggs - )}{' '} - - telur - -

-
-
-
- -
-
- - {/* Progress Bar */} -
-
- Total yang digrading: - - {isDataLoading ? ( - - ) : ( - `${currentGradingTotal} / ${totalKonsumsiBaikEggs}` - )} - -
-
-
-
- {!isDataLoading && isGradingExceedsAvailable && ( -
- - Melebihi batas tersedia -
- )} - {!isDataLoading && - isGradingIncomplete && - hasUserStartedGrading && ( -
- - - Grading belum lengkap, semua telur harus digrading - -
- )} - {isDataLoading && ( -
- - Memuat data telur konsumsi baik... -
- )} -
-
-
- - - {/* Grading Table */} - -
- - - - {type !== 'detail' && ( - - )} - - - {type !== 'detail' && } - - - - {formik.values.eggs_grading?.map((grading, idx) => ( - - {type !== 'detail' && ( - - )} - - - {type !== 'detail' && ( - - )} - - ))} - -
- 0 - } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedGradingItems( - formik.values.eggs_grading?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedGradingItems([]); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - - Grade - - * - - - Jumlah - - * - - Action
- - ) => { - if (e.target.checked) { - setSelectedGradingItems([ - ...selectedGradingItems, - idx, - ]); - } else { - setSelectedGradingItems( - selectedGradingItems.filter((i) => i !== idx) - ); - } - }} - classNames={{ - wrapper: 'flex justify-center', - checkbox: 'checkbox checkbox-sm', - }} - /> - - - - - -
- -
-
-
- {type !== 'detail' && ( -
- {selectedGradingItems.length > 0 && ( - - )} - -
- )} -
- - {/* Action buttons */} -
- {type !== 'add' && ( -
- {deleteRecordingClickHandler && ( - - )} - {type !== 'edit' && initialValues && ( - - )} -
- )} - {type !== 'detail' && ( -
- - -
- )} -
- {gradingFormErrorMessage && ( -
- - {gradingFormErrorMessage} -
- )} - -
- - {/* ===== MODALS ===== */} - {type !== 'add' && ( - <> - - - )} - - ); -}; - -export default GradingForm; diff --git a/src/services/api/production.ts b/src/services/api/production.ts index 4266f6b7..ea06615a 100644 --- a/src/services/api/production.ts +++ b/src/services/api/production.ts @@ -9,8 +9,6 @@ import { CreateRecordingPayload, Recording, UpdateRecordingPayload, - CreateGradingPayload, - UpdateGradingPayload, NextDayRecording, } from '@/types/api/production/recording'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; @@ -64,28 +62,6 @@ export class RecordingService extends BaseApiService< }); } - async createGrading( - payload: CreateGradingPayload - ): Promise | undefined> { - return await this.customRequest>('gradings', { - method: 'POST', - payload, - }); - } - - async updateGrading( - gradingId: number, - payload: UpdateGradingPayload - ): Promise | undefined> { - return await this.customRequest>( - `gradings/${gradingId}`, - { - method: 'PUT', - payload, - } - ); - } - async deleteGrading( gradingId: number ): Promise | undefined> { From c3835d51286f370142327459931f324e0595af94 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 23:35:12 +0700 Subject: [PATCH 10/12] refactor(FE-319): Renumber RECORDINGS approval workflow steps --- src/config/constant.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/constant.ts b/src/config/constant.ts index d4e08942..2786e951 100644 --- a/src/config/constant.ts +++ b/src/config/constant.ts @@ -262,11 +262,11 @@ export const APPROVAL_WORKFLOWS = [ key: 'RECORDINGS', steps: [ { - step_number: 2, + step_number: 1, step_name: 'Pengajuan', }, { - step_number: 3, + step_number: 2, step_name: 'Disetujui', }, ], From 012fe800bcdf141f0c18e71a9c8f60bf7aa469f5 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Mon, 8 Dec 2025 23:35:55 +0700 Subject: [PATCH 11/12] refactor(FE-318,319): Remove laying grading checks and simplify approval --- .../production/recording/RecordingTable.tsx | 133 ++++-------------- 1 file changed, 27 insertions(+), 106 deletions(-) diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx index 6cf254e7..27b2d5c6 100644 --- a/src/components/pages/production/recording/RecordingTable.tsx +++ b/src/components/pages/production/recording/RecordingTable.tsx @@ -35,28 +35,22 @@ const RowOptionsMenu = ({ deleteClickHandler, approveClickHandler, rejectClickHandler, - isGradingCompleted, }: { type: 'dropdown' | 'collapse'; props: CellContext; deleteClickHandler: () => void; approveClickHandler: () => void; rejectClickHandler: () => void; - isGradingCompleted: (recording: Recording) => boolean; }) => { - const isLayingCategory = - props.row.original.project_flock_category === 'LAYING'; - const isRecordingApproved = (recording: Recording) => { return ( recording.approval?.action === 'APPROVED' && - recording.approval?.step_name === 'Disetujui' && - recording.approval?.step_number === 3 + recording.approval?.step_number === 2 && + recording.approval?.step_name === 'Disetujui' ); }; const isApproved = isRecordingApproved(props.row.original); - const isGradingDone = isGradingCompleted(props.row.original); return ( @@ -78,7 +72,7 @@ const RowOptionsMenu = ({ Edit - {!isApproved && !(isLayingCategory && !isGradingDone) && ( + {!isApproved && ( - {type === 'detail' && - !isRecordingApproved(initialValues) && - (!isLayingCategory || hasGradingData(initialValues)) && ( -
- + {type === 'detail' && !isRecordingApproved(initialValues) && ( +
+ - -
- )} + +
+ )}

@@ -1928,7 +1820,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => { {formik.values.body_weights?.map((bw, idx) => ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - + { {formik.values.stocks?.map((stock, idx) => ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - + { {formik.values.depletions?.map((depletion, idx) => ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - + { (egg, idx) => ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && ( - + { {/* Right side actions */}
- {type === 'detail' && isLayingCategory && ( - - - - )} - {type === 'edit' && (