From bbbd767cf2ae63d8318dde05002db6f9c7d5c965 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 20 Feb 2026 09:21:42 +0700 Subject: [PATCH 1/5] refactor(FE): Fix week calculation logic in UniformityForm --- .../uniformity/form/UniformityForm.tsx | 79 +++++++++---------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/src/components/pages/production/uniformity/form/UniformityForm.tsx b/src/components/pages/production/uniformity/form/UniformityForm.tsx index 33b649c4..724f7b81 100644 --- a/src/components/pages/production/uniformity/form/UniformityForm.tsx +++ b/src/components/pages/production/uniformity/form/UniformityForm.tsx @@ -203,16 +203,19 @@ const UniformityForm = ({ // ===== RECORDINGS DATA (FOR WEEK CALCULATION) ===== const recordingsUrl = useMemo(() => { + if (!projectFlockKandangLookup?.project_flock_kandang_id) return null; const params = new URLSearchParams({ page: '1', limit: '100', + project_flock_kandang_id: + projectFlockKandangLookup.project_flock_kandang_id.toString(), }); return `${RecordingApi.basePath}?${params.toString()}`; - }, []); + }, [projectFlockKandangLookup?.project_flock_kandang_id]); const { data: recordingsData } = useSWR( recordingsUrl, - RecordingApi.getAllFetcher + recordingsUrl ? RecordingApi.getAllFetcher : null ); // ===== FORM CONFIGURATION ===== @@ -400,50 +403,46 @@ const UniformityForm = ({ useEffect(() => { if ( projectFlockKandangLookup?.chick_in_date && - projectFlockKandangLookup?.project_flock_kandang_id && - isResponseSuccess(recordingsData) && - recordingsData.data + projectFlockKandangLookup?.project_flock_kandang_id ) { - const matchingRecordings = recordingsData.data.filter( - (recording: Recording) => - recording.project_flock?.project_flock_kandang_id === - projectFlockKandangLookup.project_flock_kandang_id - ); + const chickInDate = new Date(projectFlockKandangLookup.chick_in_date); + chickInDate.setHours(0, 0, 0, 0); - matchingRecordings.sort( - (a: Recording, b: Recording) => - new Date(a.record_datetime).getTime() - - new Date(b.record_datetime).getTime() - ); + let initialWeek = 18; - const earliestRecording = matchingRecordings[0]; + if ( + isResponseSuccess(recordingsData) && + recordingsData.data && + recordingsData.data.length > 0 + ) { + const sortedRecordings = [...recordingsData.data].sort( + (a: Recording, b: Recording) => + new Date(a.record_datetime).getTime() - + new Date(b.record_datetime).getTime() + ); - if (earliestRecording) { - const chickInDate = new Date(projectFlockKandangLookup.chick_in_date); - chickInDate.setHours(0, 0, 0, 0); - - const earliestRecordDate = new Date(earliestRecording.record_datetime); - earliestRecordDate.setHours(0, 0, 0, 0); - - const initialWeek = - earliestRecording.project_flock?.production_standart?.week || 18; - - if (formik.values.date) { - const selectedDate = new Date(formik.values.date); - selectedDate.setHours(0, 0, 0, 0); - - const daysDiff = Math.floor( - (selectedDate.getTime() - chickInDate.getTime()) / - (1000 * 60 * 60 * 24) - ); - - const weeksDiff = Math.floor(daysDiff / 7); - - formik.setFieldValue('week', initialWeek + weeksDiff); - } else { - formik.setFieldValue('week', initialWeek); + const earliestRecording = sortedRecordings[0]; + if (earliestRecording?.project_flock?.production_standart?.week) { + initialWeek = + earliestRecording.project_flock.production_standart.week; } } + + if (formik.values.date) { + const selectedDate = new Date(formik.values.date); + selectedDate.setHours(0, 0, 0, 0); + + const daysDiff = Math.floor( + (selectedDate.getTime() - chickInDate.getTime()) / + (1000 * 60 * 60 * 24) + ); + + const weeksDiff = Math.floor(daysDiff / 7); + + formik.setFieldValue('week', initialWeek + weeksDiff); + } else { + formik.setFieldValue('week', initialWeek); + } } }, [ projectFlockKandangLookup?.chick_in_date, From b35b6c2ab8fd536d517fb32c6ad8da3de38b8c35 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 20 Feb 2026 10:11:16 +0700 Subject: [PATCH 2/5] feat(FE): Add submittedActionType state to track modal action type --- .../TransferToLayingFormModal.tsx | 17 ++++++++++++++--- .../TransferToLayingsTable.tsx | 3 +++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx index 78de70e1..cf80be89 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx @@ -83,6 +83,9 @@ const TransferToLayingFormModal = () => { TransferToLayingFormValues | undefined >(undefined); const [formErrorMessage, setFormErrorMessage] = useState(null); + const [submittedActionType, setSubmittedActionType] = useState< + 'add' | 'edit' | null + >(null); // Flock Source const { @@ -203,6 +206,7 @@ const TransferToLayingFormModal = () => { }; setFormikLastValues(values); + setSubmittedActionType(modalAction as 'add' | 'edit'); switch (modalAction) { case 'add': @@ -1059,10 +1063,17 @@ const TransferToLayingFormModal = () => { setFormikLastValues(undefined)} + onClose={() => { + setFormikLastValues(undefined); + setSubmittedActionType(null); + }} secondaryButton={undefined} /> diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx index 438c529c..bf4c31e3 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingsTable.tsx @@ -680,6 +680,7 @@ const TransferToLayingsTable = () => { subtitleText='Are you sure you want to delete this data? ' transferToLayingIds={selectedRowIds} primaryButton={{ + text: 'Delete', isLoading: isDeleteLoading, color: 'error', onClick: confirmationModalDeleteClickHandler, @@ -704,6 +705,7 @@ const TransferToLayingsTable = () => { withNote noteLabel='Notes Approval' primaryButton={{ + text: 'Approve', isLoading: isApproveLoading, onClick: confirmationModalApproveClickHandler, }} @@ -735,6 +737,7 @@ const TransferToLayingsTable = () => { }, }} primaryButton={{ + text: 'Reject', isLoading: isRejectLoading, color: 'error', onClick: confirmationModalRejectClickHandler, From 4c3e7c615fc80abde3c566707b1a68a183e47444 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 20 Feb 2026 10:12:06 +0700 Subject: [PATCH 3/5] refactor(FE): Refactor conditional text formatting in modal component --- .../transfer-to-laying/TransferToLayingFormModal.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx b/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx index cf80be89..84e67a5b 100644 --- a/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx +++ b/src/components/pages/production/transfer-to-laying/TransferToLayingFormModal.tsx @@ -1063,7 +1063,11 @@ const TransferToLayingFormModal = () => { Date: Fri, 20 Feb 2026 10:23:47 +0700 Subject: [PATCH 4/5] feat(FE): Add project flock closing modal and zustand store --- .../project-flock/ProjectFlockTable.tsx | 58 ++++++++++++++ .../closing/ProjectFlockClosingForm.tsx | 78 ++++++++----------- .../project-flock-closing.store.ts | 19 +++++ .../slices/project-flock-closing.slice.ts | 70 +++++++++++++++++ 4 files changed, 178 insertions(+), 47 deletions(-) create mode 100644 src/stores/production/project-flock-closing/project-flock-closing.store.ts create mode 100644 src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts diff --git a/src/components/pages/production/project-flock/ProjectFlockTable.tsx b/src/components/pages/production/project-flock/ProjectFlockTable.tsx index 040948ff..040771a8 100644 --- a/src/components/pages/production/project-flock/ProjectFlockTable.tsx +++ b/src/components/pages/production/project-flock/ProjectFlockTable.tsx @@ -37,6 +37,7 @@ import ProjectFlockConfirmationModal from './ProjectFlockConfirmationModal'; import { useProjectFlockStore } from '@/stores/production/project-flock/project-flock.store'; import { ProjectFlockFormValues } from './form/ProjectFlockForm.schema'; import { useChickinStore } from '@/stores/production/chickin/chickin.store'; +import { useProjectFlockClosingStore } from '@/stores/production/project-flock-closing/project-flock-closing.store'; const RowOptionsMenu = ({ props, @@ -195,6 +196,7 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { const confirmModal = useModal(); const successModal = useModal(); const chickinApproveModal = useModal(); + const closingModal = useModal(); const [approvalAction, setApprovalAction] = useState<'APPROVED' | 'REJECTED'>( 'APPROVED' ); @@ -210,6 +212,15 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { setChickinApproveLoading, } = useChickinStore(); + const { + isClosingModalOpen, + isKandangClosed, + isClosingLoading, + closingCallback, + closeClosingModal, + setClosingLoading, + } = useProjectFlockClosingStore(); + // ===== Fetch Data ===== const { data: projectFlocks, @@ -309,6 +320,14 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { } }, [isChickinApproveModalOpen, chickinApproveModal]); + useEffect(() => { + if (isClosingModalOpen) { + closingModal.openModal(); + } else { + closingModal.closeModal(); + } + }, [isClosingModalOpen, closingModal]); + useEffect(() => { if (isSuccess) { successModal.openModal(); @@ -1025,6 +1044,45 @@ const ProjectFlockTable = ({ refresh }: { refresh?: () => void }) => { isLoading: isChickinApproveLoading, }} /> + + {/* Project Flock Closing Modal */} + { + closeClosingModal(); + closingModal.closeModal(); + }, + }} + primaryButton={{ + text: 'Ya', + color: 'error', + isLoading: isClosingLoading, + onClick: async () => { + if (closingCallback) { + setClosingLoading(true); + try { + await closingCallback(!isKandangClosed ? 'close' : 'unclose'); + } finally { + setClosingLoading(false); + closeClosingModal(); + closingModal.closeModal(); + refreshProjectFlocks(); + } + } + }, + }} + /> ); }; diff --git a/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx b/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx index f963a793..e73a157a 100644 --- a/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx +++ b/src/components/pages/production/project-flock/closing/ProjectFlockClosingForm.tsx @@ -17,9 +17,8 @@ import { Icon } from '@iconify/react'; import useSWR from 'swr'; import { ProjectFlockKandangApi } from '@/services/api/production/project-flock-kandang'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; -import { useModal } from '@/components/Modal'; -import ConfirmationModal from '@/components/modal/ConfirmationModal'; -import { useMemo, useState } from 'react'; +import { useProjectFlockClosingStore } from '@/stores/production/project-flock-closing/project-flock-closing.store'; +import { useMemo } from 'react'; import toast from 'react-hot-toast'; import { useRouter } from 'next/navigation'; import { ApprovalApi } from '@/services/api/approval'; @@ -53,9 +52,8 @@ const ProjectFlockClosingForm = ({ projectFlockKandang: ProjectFlockKandang; }) => { const router = useRouter(); - const closeModal = useModal(); - const [isClosingLoading, setIsClosingLoading] = useState(false); + const { openClosingModal } = useProjectFlockClosingStore(); const { data: closingData, isLoading } = useSWR( `${ProjectFlockKandangApi.basePath}/${projectFlockKandang.id}/closing`, @@ -80,29 +78,34 @@ const ProjectFlockClosingForm = ({ : true; }, [projectFlockKandangApprovals]); - const confirmationModalCloseClickHandler = async () => { - setIsClosingLoading(true); - const deleteProjectFlockRes = await ProjectFlockKandangApi.closing( - projectFlockKandang?.id as number, - { - closed_date: !isKandangClosed - ? formatDate(new Date(), 'YYYY-MM-DD') - : '', - action: !isKandangClosed ? 'close' : 'unclose', - } - ); - - if (isResponseSuccess(deleteProjectFlockRes)) { - toast.success(deleteProjectFlockRes?.message as string); - router.push( - `/production/project-flock/detail?projectFlockId=${projectFlock.id}` + const handleCloseClick = () => { + const closingCallback = async (action: 'close' | 'unclose') => { + const deleteProjectFlockRes = await ProjectFlockKandangApi.closing( + projectFlockKandang?.id as number, + { + closed_date: + action === 'close' ? formatDate(new Date(), 'YYYY-MM-DD') : '', + action, + } ); - } - if (isResponseError(deleteProjectFlockRes)) { - toast.error(deleteProjectFlockRes?.message as string); - } - setIsClosingLoading(false); - closeModal.closeModal(); + + if (isResponseSuccess(deleteProjectFlockRes)) { + toast.success(deleteProjectFlockRes?.message as string); + router.push( + `/production/project-flock/detail?projectFlockId=${projectFlock.id}` + ); + } + if (isResponseError(deleteProjectFlockRes)) { + toast.error(deleteProjectFlockRes?.message as string); + } + }; + + openClosingModal( + projectFlockKandang, + projectFlock.id, + isKandangClosed, + closingCallback + ); }; // const errorStock = useMemo(() => { @@ -334,7 +337,7 @@ const ProjectFlockClosingForm = ({ color='error' isLoading={isLoading} disabled={!isCanCloseValid} - onClick={() => closeModal.openModal()} + onClick={handleCloseClick} > - - ); diff --git a/src/stores/production/project-flock-closing/project-flock-closing.store.ts b/src/stores/production/project-flock-closing/project-flock-closing.store.ts new file mode 100644 index 00000000..b6543b97 --- /dev/null +++ b/src/stores/production/project-flock-closing/project-flock-closing.store.ts @@ -0,0 +1,19 @@ +'use client'; + +import { create } from 'zustand'; +import { devtools } from 'zustand/middleware'; +import { createProjectFlockClosingSlice } from '@/stores/production/project-flock-closing/slices/project-flock-closing.slice'; +import { ProjectFlockClosingSlice } from '@/stores/production/project-flock-closing/slices/project-flock-closing.slice'; + +export type ProjectFlockClosingStore = ProjectFlockClosingSlice; + +export const useProjectFlockClosingStore = create()( + devtools( + (...args) => ({ + ...createProjectFlockClosingSlice(...args), + }), + { + name: 'ProjectFlockClosingStore', + } + ) +); diff --git a/src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts b/src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts new file mode 100644 index 00000000..faff8816 --- /dev/null +++ b/src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts @@ -0,0 +1,70 @@ +import { StateCreator } from 'zustand'; +import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; + +export type ProjectFlockClosingSlice = { + // State + isClosingModalOpen: boolean; + selectedProjectFlockKandang: ProjectFlockKandang | null; + projectFlockId: number | null; + isKandangClosed: boolean; + isClosingLoading: boolean; + closingCallback: ((action: 'close' | 'unclose') => Promise) | null; + + // Actions + openClosingModal: ( + data: ProjectFlockKandang, + projectFlockId: number, + isClosed: boolean, + callback: (action: 'close' | 'unclose') => Promise + ) => void; + closeClosingModal: () => void; + setClosingLoading: (loading: boolean) => void; + resetClosing: () => void; +}; + +export const createProjectFlockClosingSlice: StateCreator< + ProjectFlockClosingSlice, + [], + [], + ProjectFlockClosingSlice +> = (set) => ({ + // Initial state + isClosingModalOpen: false, + selectedProjectFlockKandang: null, + projectFlockId: null, + isKandangClosed: false, + isClosingLoading: false, + closingCallback: null, + + // Actions + openClosingModal: (data, projectFlockId, isClosed, callback) => + set({ + isClosingModalOpen: true, + selectedProjectFlockKandang: data, + projectFlockId, + isKandangClosed: isClosed, + closingCallback: callback, + }), + + closeClosingModal: () => + set({ + isClosingModalOpen: false, + selectedProjectFlockKandang: null, + projectFlockId: null, + isKandangClosed: false, + closingCallback: null, + }), + + setClosingLoading: (loading) => + set({ isClosingLoading: loading }), + + resetClosing: () => + set({ + isClosingModalOpen: false, + selectedProjectFlockKandang: null, + projectFlockId: null, + isKandangClosed: false, + isClosingLoading: false, + closingCallback: null, + }), +}); From a0e79168b28df0e7558b61f5ca8e507682038c60 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 20 Feb 2026 10:25:30 +0700 Subject: [PATCH 5/5] refactor(FE): Refactor setClosingLoading to use single-line syntax --- .../slices/project-flock-closing.slice.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts b/src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts index faff8816..ccffd387 100644 --- a/src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts +++ b/src/stores/production/project-flock-closing/slices/project-flock-closing.slice.ts @@ -55,8 +55,7 @@ export const createProjectFlockClosingSlice: StateCreator< closingCallback: null, }), - setClosingLoading: (loading) => - set({ isClosingLoading: loading }), + setClosingLoading: (loading) => set({ isClosingLoading: loading }), resetClosing: () => set({