Merge branch 'fix/uniformity-ttl-project-flock-issue' into 'development'

[FIX/FE] Fix Isssue on Uniformity (Week Calculation), Project Flock (Modal Position), Transfer To Laying (Copywriting)

See merge request mbugroup/lti-web-client!329
This commit is contained in:
Rivaldi A N S
2026-02-20 03:36:15 +00:00
7 changed files with 237 additions and 90 deletions
@@ -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 */}
<ConfirmationModal
ref={closingModal.ref}
type='error'
text={
!isKandangClosed
? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai'
: 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif'
}
className={{
modal: 'z-9999',
}}
secondaryButton={{
text: 'Tidak',
onClick: () => {
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();
}
}
},
}}
/>
</>
);
};
@@ -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}
>
<Icon
icon={
@@ -347,25 +350,6 @@ const ProjectFlockClosingForm = ({
</Button>
</RequirePermission>
</div>
<ConfirmationModal
ref={closeModal.ref}
type='error'
text={
!isKandangClosed
? 'Apakah kamu yakin ingin mengakhiri project ini ? *Pastikan persediaan produk di gudang terkait sudah kosong, dan BOP sudah selesai'
: 'Apakah kamu yakin ingin membuka kembali project ini ? *Project ini akan kembali ke status aktif'
}
secondaryButton={{
text: 'Tidak',
}}
primaryButton={{
text: 'Ya',
color: 'error',
isLoading: isClosingLoading,
onClick: confirmationModalCloseClickHandler,
}}
/>
</div>
</>
);
@@ -83,6 +83,9 @@ const TransferToLayingFormModal = () => {
TransferToLayingFormValues | undefined
>(undefined);
const [formErrorMessage, setFormErrorMessage] = useState<string | null>(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,21 @@ const TransferToLayingFormModal = () => {
<TransferToLayingConfirmationModal
ref={successModal.ref}
type='success'
text='Data Berhasil Ditambahkan'
subtitleText='Data transfer to laying telah berhasil disimpan.'
text={
submittedActionType === 'edit'
? 'Data Berhasil Diperbarui'
: 'Data Berhasil Ditambahkan'
}
subtitleText={
submittedActionType === 'edit'
? 'Data transfer to laying telah berhasil diperbarui.'
: 'Data transfer to laying telah berhasil disimpan.'
}
transferToLayingForm={formikLastValues}
onClose={() => setFormikLastValues(undefined)}
onClose={() => {
setFormikLastValues(undefined);
setSubmittedActionType(null);
}}
secondaryButton={undefined}
/>
</>
@@ -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,
@@ -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,
@@ -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<ProjectFlockClosingStore>()(
devtools(
(...args) => ({
...createProjectFlockClosingSlice(...args),
}),
{
name: 'ProjectFlockClosingStore',
}
)
);
@@ -0,0 +1,69 @@
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<void>) | null;
// Actions
openClosingModal: (
data: ProjectFlockKandang,
projectFlockId: number,
isClosed: boolean,
callback: (action: 'close' | 'unclose') => Promise<void>
) => 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,
}),
});