diff --git a/src/components/modal/ConfirmationModalWithNotes.tsx b/src/components/modal/ConfirmationModalWithNotes.tsx
index a5551571..e862dffc 100644
--- a/src/components/modal/ConfirmationModalWithNotes.tsx
+++ b/src/components/modal/ConfirmationModalWithNotes.tsx
@@ -13,6 +13,7 @@ interface ConfirmationModalWithNotesProps
extends Omit {
rows?: number;
placeholder?: string;
+ onClose?: () => void;
primaryButton?: {
text?: string;
@@ -32,6 +33,7 @@ const ConfirmationModalWithNotes: React.FC = ({
className,
rows = 3,
placeholder = 'Catatan...',
+ onClose,
...props
}) => {
const randomId = useId();
@@ -41,6 +43,11 @@ const ConfirmationModalWithNotes: React.FC = ({
setNotes(e.target.value);
};
+ const closeModalHandler = () => {
+ onClose?.();
+ ref.current?.close();
+ };
+
return (
= ({
closeOnBackdrop={closeOnBackdrop}
primaryButton={{
...primaryButton,
- onClick: () => {
- primaryButton?.onClick?.(notes);
+ onClick: (e) => {
+ if (primaryButton && primaryButton?.onClick) {
+ primaryButton?.onClick?.(notes);
+ } else {
+ closeModalHandler();
+ }
+
setNotes('');
},
}}
- secondaryButton={secondaryButton}
+ secondaryButton={
+ secondaryButton
+ ? {
+ text: secondaryButton?.text ?? 'Tidak',
+ onClick: (e) => {
+ if (secondaryButton && secondaryButton?.onClick) {
+ secondaryButton.onClick?.(e);
+ } else {
+ closeModalHandler();
+ }
+
+ setNotes('');
+ },
+ }
+ : undefined
+ }
className={className}
{...props}
>
diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx
index 6513956e..8fbc81d7 100644
--- a/src/components/pages/expense/ExpenseRequestContent.tsx
+++ b/src/components/pages/expense/ExpenseRequestContent.tsx
@@ -100,6 +100,7 @@ const ExpenseRequestContent = ({
const [isCompleteLoading, setIsCompleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isRejectLoading, setIsRejectLoading] = useState(false);
+ const [, setApprovalNotes] = useState('');
const formik = useFormik({
initialValues: {
@@ -130,10 +131,12 @@ const ExpenseRequestContent = ({
};
const approveClickHandler = () => {
+ setApprovalNotes('');
approveModal.openModal();
};
const rejectClickHandler = () => {
+ setApprovalNotes('');
rejectModal.openModal();
};
@@ -200,6 +203,7 @@ const ExpenseRequestContent = ({
approveModal.closeModal();
toast.success(approveResponse?.message);
+ setApprovalNotes('');
router.push('/expense');
} else {
approveModal.closeModal();
@@ -234,6 +238,7 @@ const ExpenseRequestContent = ({
rejectModal.closeModal();
toast.success(rejectResponse.message);
+ setApprovalNotes('');
router.push('/expense');
} else {
rejectModal.closeModal();
@@ -710,6 +715,10 @@ const ExpenseRequestContent = ({
text='Apakah anda yakin ingin approve data biaya operasional ini?'
secondaryButton={{
text: 'Tidak',
+ onClick: () => {
+ setApprovalNotes('');
+ approveModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
@@ -725,6 +734,10 @@ const ExpenseRequestContent = ({
text='Apakah anda yakin ingin reject data biaya operasional ini?'
secondaryButton={{
text: 'Tidak',
+ onClick: () => {
+ setApprovalNotes('');
+ rejectModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
diff --git a/src/components/pages/expense/ExpensesTable.tsx b/src/components/pages/expense/ExpensesTable.tsx
index 69376992..d2d4754c 100644
--- a/src/components/pages/expense/ExpensesTable.tsx
+++ b/src/components/pages/expense/ExpensesTable.tsx
@@ -185,6 +185,7 @@ const ExpensesTable = () => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [isApproveLoading, setIsApproveLoading] = useState(false);
const [isRejectLoading, setIsRejectLoading] = useState(false);
+ const [, setApprovalNotes] = useState('');
const [sorting, setSorting] = useState([]);
const [rowSelection, setRowSelection] = useState>({});
@@ -342,6 +343,7 @@ const ExpensesTable = () => {
[String(props.row.original.id)]: true,
});
+ setApprovalNotes('');
approveModal.openModal();
};
@@ -353,6 +355,7 @@ const ExpensesTable = () => {
[String(props.row.original.id)]: true,
});
+ setApprovalNotes('');
rejectModal.openModal();
};
@@ -412,10 +415,12 @@ const ExpensesTable = () => {
// };
const bulkApproveClickHandler = () => {
+ setApprovalNotes('');
approveModal.openModal();
};
const bulkRejectClickHandler = () => {
+ setApprovalNotes('');
rejectModal.openModal();
};
@@ -468,6 +473,7 @@ const ExpensesTable = () => {
`Berhasil approve ${selectedRowIds.length} data biaya operasional!`
);
+ setApprovalNotes('');
setRowSelection({});
} else {
approveModal.closeModal();
@@ -509,6 +515,7 @@ const ExpensesTable = () => {
toast.success(
`Berhasil reject ${selectedRowIds.length} data biaya operasional!`
);
+ setApprovalNotes('');
setRowSelection({});
} else {
rejectModal.closeModal();
@@ -787,6 +794,10 @@ const ExpensesTable = () => {
text='Apakah anda yakin ingin approve data biaya operasional ini?'
secondaryButton={{
text: 'Tidak',
+ onClick: () => {
+ setApprovalNotes('');
+ approveModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
@@ -802,6 +813,10 @@ const ExpensesTable = () => {
text='Apakah anda yakin ingin reject data biaya operasional ini?'
secondaryButton={{
text: 'Tidak',
+ onClick: () => {
+ setApprovalNotes('');
+ rejectModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
diff --git a/src/components/pages/production/recording/RecordingTable.tsx b/src/components/pages/production/recording/RecordingTable.tsx
index cd98b597..36a4e69a 100644
--- a/src/components/pages/production/recording/RecordingTable.tsx
+++ b/src/components/pages/production/recording/RecordingTable.tsx
@@ -7,14 +7,12 @@ import React, {
useEffect,
useRef,
} from 'react';
-import { RefObject } from 'react';
import useSWR from 'swr';
import { Icon } from '@iconify/react';
import { SortingState, CellContext } from '@tanstack/react-table';
import { cn, formatDate, formatNumber } from '@/lib/helper';
import RequirePermission from '@/components/helper/RequirePermission';
import { useModal } from '@/components/Modal';
-import Modal from '@/components/Modal';
import Button from '@/components/Button';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
@@ -28,14 +26,51 @@ import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import { type Recording } from '@/types/api/production/recording';
import { RecordingApi } from '@/services/api/production';
-import { ApprovalApi } from '@/services/api/approval';
import { isResponseSuccess } from '@/lib/api-helper';
import { useTableFilter } from '@/services/hooks/useTableFilter';
import toast from 'react-hot-toast';
import Badge from '@/components/Badge';
+import StatusBadge from '@/components/helper/StatusBadge';
import CheckboxInput from '@/components/input/CheckboxInput';
import { useUiStore } from '@/stores/ui/ui.store';
-import { BaseApproval, BaseApiResponse } from '@/types/api/api-general';
+import { Color } from '@/types/theme';
+
+// ===== STATUS BADGE UTILITIES =====
+const statusTextMap: Record = {
+ APPROVED: 'Disetujui',
+ Disetujui: 'Disetujui',
+ REJECTED: 'Ditolak',
+ Ditolak: 'Ditolak',
+ CREATED: 'Dibuat',
+ UPDATED: 'Diperbarui',
+};
+
+const getStatusText = (status: string): string => {
+ return statusTextMap[status] || status;
+};
+
+const statusBadgeColorMap: Record = {
+ APPROVED: 'success',
+ Disetujui: 'success',
+ approved: 'success',
+ disetujui: 'success',
+ REJECTED: 'error',
+ Ditolak: 'error',
+ rejected: 'error',
+ ditolak: 'error',
+ CREATED: 'neutral',
+ Dibuat: 'neutral',
+ created: 'neutral',
+ dibuat: 'neutral',
+ UPDATED: 'warning',
+ Diperbarui: 'warning',
+ updated: 'warning',
+ diperbarui: 'warning',
+};
+
+const getStatusBadgeColor = (status: string): Color => {
+ return statusBadgeColorMap[status] || 'neutral';
+};
const RowOptionsMenu = ({
type = 'dropdown',
@@ -135,221 +170,6 @@ const RowOptionsMenu = ({
);
};
-const ApprovalHistoryModal = ({
- ref,
- currentApproval,
- module_name = 'RECORDINGS',
- module_id,
-}: {
- ref: RefObject;
- currentApproval?: BaseApproval;
- module_name?: string;
- module_id?: number | undefined;
-}) => {
- const [isModalOpen, setIsModalOpen] = useState(false);
-
- const approvalHistoryUrl = useMemo(() => {
- if (!isModalOpen) return null;
- const params = new URLSearchParams({
- module_name: module_name,
- group_step_number: 'true',
- });
-
- if (module_id) {
- params.append('module_id', module_id.toString());
- }
-
- return `${ApprovalApi.basePath}?${params.toString()}`;
- }, [module_name, module_id, isModalOpen]);
-
- type GroupedApprovalResponse = {
- step_number: number;
- step_name: string;
- approvals: BaseApproval[];
- };
-
- const fetchGroupedApprovals = async (
- url: string
- ): Promise> => {
- return (await ApprovalApi.getAllFetcher(url)) as BaseApiResponse<
- GroupedApprovalResponse[]
- >;
- };
-
- const { data: approvalHistoryData, isLoading } = useSWR<
- BaseApiResponse
- >(approvalHistoryUrl, fetchGroupedApprovals);
-
- useEffect(() => {
- const checkModalOpen = () => {
- const isOpen = ref.current?.open || false;
- setIsModalOpen(isOpen);
- };
-
- checkModalOpen();
-
- const observer = new MutationObserver(checkModalOpen);
- if (ref.current) {
- observer.observe(ref.current, {
- attributes: true,
- attributeFilter: ['open'],
- });
- }
-
- return () => observer.disconnect();
- }, [ref]);
-
- const approvalHistory = useMemo(() => {
- if (!approvalHistoryData || approvalHistoryData.status !== 'success')
- return [];
-
- const groupedData = approvalHistoryData.data || [];
- const flattenedApprovals: BaseApproval[] = [];
-
- groupedData.forEach((group) => {
- group.approvals.forEach((approval) => {
- flattenedApprovals.push(approval);
- });
- });
-
- return flattenedApprovals;
- }, [approvalHistoryData]);
-
- const closeModalHandler = () => {
- ref.current?.close();
- };
-
- return (
-
-
- {/* Header */}
-
-
Riwayat Approval
-
-
-
- {isLoading ? (
-
-
-
- ) : (
- <>
- {/* Current Status */}
- {currentApproval && (
-
-
Status Saat Ini
-
-
- {currentApproval.step_name}
-
-
- {currentApproval.action === 'APPROVED' && 'Disetujui'}
- {currentApproval.action === 'REJECTED' && 'Ditolak'}
- {currentApproval.action === 'CREATED' && 'Dibuat'}
- {currentApproval.action === 'UPDATED' && 'Diperbarui'}
-
-
- {currentApproval.notes && (
-
- Catatan:{' '}
- {currentApproval.notes}
-
- )}
-
- Oleh: {currentApproval.action_by.name} •{' '}
- {formatDate(currentApproval.action_at, 'DD MMMM YYYY HH:mm')}
-
-
- )}
-
- {/* Full History */}
- {approvalHistory.length > 0 && (
-
-
Riwayat Lengkap
-
-
-
-
- | Tahap |
- Aksi |
- Catatan |
- Oleh |
- Waktu |
-
-
-
- {approvalHistory
- .sort(
- (a: BaseApproval, b: BaseApproval) =>
- new Date(b.action_at).getTime() -
- new Date(a.action_at).getTime()
- )
- .map((approval: BaseApproval, index: number) => (
-
- | {approval.step_name} |
-
-
- {approval.action === 'APPROVED' && 'Disetujui'}
- {approval.action === 'REJECTED' && 'Ditolak'}
- {approval.action === 'CREATED' && 'Dibuat'}
- {approval.action === 'UPDATED' && 'Diperbarui'}
-
- |
-
-
- {approval.notes || '-'}
-
- |
- {approval.action_by.name} |
-
- {formatDate(
- approval.action_at,
- 'DD MMMM YYYY HH:mm'
- )}
- |
-
- ))}
-
-
-
-
- )}
- >
- )}
-
-
- );
-};
-
const RecordingTable = () => {
const { searchValue, setSearchValue, resetSearchValue } = useUiStore();
const previousPathRef = useRef(null);
@@ -395,7 +215,6 @@ const RecordingTable = () => {
const singleDeleteModal = useModal();
const approveModal = useModal();
const rejectModal = useModal();
- const approvalHistoryModal = useModal();
const {
data: recordings,
@@ -1032,32 +851,19 @@ const RecordingTable = () => {
const approval = props.row.original.approval;
if (!approval) return '-';
- const statusColor =
- approval.action === 'APPROVED'
- ? 'success'
- : approval.action === 'REJECTED'
- ? 'error'
- : approval.action === 'UPDATED'
- ? 'warning'
- : 'info';
+ const status = approval.action;
+ const statusColor = getStatusBadgeColor(status);
- const openApprovalHistory = () => {
- setSelectedRecording(props.row.original);
- approvalHistoryModal.openModal();
- };
+ const statusText = approval.step_name || getStatusText(status);
return (
-
- {approval.step_name || approval.action}
-
+ />
);
},
},
@@ -1208,7 +1014,10 @@ const RecordingTable = () => {
text={`Apakah anda yakin ingin approve data recording ini (${eligibleRowIds.length} data dari ${selectedRowIds.length} yang dipilih)?`}
secondaryButton={{
text: 'Tidak',
- onClick: () => setApprovalNotes(''),
+ onClick: () => {
+ setApprovalNotes('');
+ approveModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
@@ -1226,7 +1035,10 @@ const RecordingTable = () => {
text={`Apakah anda yakin ingin reject data recording ini (${eligibleRowIds.length} data dari ${selectedRowIds.length} yang dipilih)?`}
secondaryButton={{
text: 'Tidak',
- onClick: () => setApprovalNotes(''),
+ onClick: () => {
+ setApprovalNotes('');
+ rejectModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
@@ -1237,13 +1049,6 @@ const RecordingTable = () => {
placeholder='(Opsional) Tambahkan catatan untuk reject ini...'
rows={3}
/>
-
-
);
};
diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx
index f3d6d2f9..94f078c1 100644
--- a/src/components/pages/production/recording/form/RecordingForm.tsx
+++ b/src/components/pages/production/recording/form/RecordingForm.tsx
@@ -241,6 +241,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
new Date().toISOString().split('T')[0]
);
const [duplicateErrorShown, setDuplicateErrorShown] = useState(false);
+ const [nextDayErrorShown, setNextDayErrorShown] = useState(false);
useEffect(() => {
return () => {
@@ -553,6 +554,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const nextDayRecordingUrl = useMemo(() => {
if (!projectFlockKandangLookup) return null;
+ if (!selectedRecordDate) return null;
const projectFlockKandangId = projectFlockKandangLookup.id;
return `${RecordingApi.basePath}/next-day?project_flock_kandang_id=${projectFlockKandangId}&record_date=${selectedRecordDate}`;
}, [projectFlockKandangLookup, selectedRecordDate]);
@@ -575,10 +577,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setNextDayRecording(
nextDayRecordingData.data as unknown as NextDayRecording
);
+ if (nextDayErrorShown) {
+ toast.dismiss();
+ setNextDayErrorShown(false);
+ }
+ } else if (nextDayRecordingData?.status === 'error') {
+ setNextDayRecording(null);
+ if (!nextDayErrorShown) {
+ toast.error(
+ nextDayRecordingData.message ||
+ 'Terjadi kesalahan saat memuat data hari berikutnya',
+ { duration: Infinity }
+ );
+ setNextDayErrorShown(true);
+ }
} else {
setNextDayRecording(null);
+ if (nextDayErrorShown) {
+ toast.dismiss();
+ setNextDayErrorShown(false);
+ }
}
- }, [nextDayRecordingData]);
+ }, [nextDayRecordingData, nextDayErrorShown]);
const {
rawData: eggProductsData,
@@ -1196,6 +1216,66 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
[stockProducts, depletionProductsData, eggProductsData, initialValues, type]
);
+ const getAvailableStockProductOptions = useCallback(
+ (currentIdx: number) => {
+ const selectedProductIds =
+ formik.values.stocks
+ ?.filter((s, idx) => {
+ return (
+ idx !== currentIdx &&
+ s.product_warehouse_id &&
+ s.product_warehouse_id !== 0
+ );
+ })
+ .map((s) => s.product_warehouse_id) || [];
+
+ return unifiedStockProducts.filter(
+ (opt) => !selectedProductIds.includes(Number(opt.value))
+ );
+ },
+ [formik.values.stocks, unifiedStockProducts]
+ );
+
+ const getAvailableDepletionProductOptions = useCallback(
+ (currentIdx: number) => {
+ const selectedProductIds =
+ formik.values.depletions
+ ?.filter((d, idx) => {
+ return (
+ idx !== currentIdx &&
+ d.product_warehouse_id &&
+ d.product_warehouse_id !== 0
+ );
+ })
+ .map((d) => d.product_warehouse_id) || [];
+
+ return depletionProducts.filter(
+ (opt) => !selectedProductIds.includes(Number(opt.value))
+ );
+ },
+ [formik.values.depletions, depletionProducts]
+ );
+
+ const getAvailableEggProductOptions = useCallback(
+ (currentIdx: number) => {
+ const selectedProductIds =
+ (formik.values as RecordingLayingFormValues).eggs
+ ?.filter((e, idx) => {
+ return (
+ idx !== currentIdx &&
+ e.product_warehouse_id &&
+ e.product_warehouse_id !== 0
+ );
+ })
+ .map((e) => e.product_warehouse_id) || [];
+
+ return eggProducts.filter(
+ (opt) => !selectedProductIds.includes(Number(opt.value))
+ );
+ },
+ [formik.values, eggProducts]
+ );
+
const hasExceededStock = useMemo(() => {
if ((type as 'add' | 'edit' | 'detail') === 'detail') return false;
return (
@@ -1255,6 +1335,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
toast.dismiss();
setDuplicateErrorShown(false);
}
+ if (nextDayErrorShown) {
+ toast.dismiss();
+ setNextDayErrorShown(false);
+ }
setSelectedProjectFlockLocationId(
location ? location.value.toString() : ''
);
@@ -1275,6 +1359,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
toast.dismiss();
setDuplicateErrorShown(false);
}
+ if (nextDayErrorShown) {
+ toast.dismiss();
+ setNextDayErrorShown(false);
+ }
};
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
@@ -1291,6 +1379,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
toast.dismiss();
setDuplicateErrorShown(false);
}
+ if (nextDayErrorShown) {
+ toast.dismiss();
+ setNextDayErrorShown(false);
+ }
if (selectedLocation && kandang) {
setStockProductsLocationId(selectedLocation.value.toString());
setStockProductsKandangId(kandang.value.toString());
@@ -1320,11 +1412,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
toast.dismiss();
setDuplicateErrorShown(false);
}
+ if (nextDayErrorShown) {
+ toast.dismiss();
+ setNextDayErrorShown(false);
+ }
setTimeout(() => {
formik.validateField('project_flock_kandang_id');
}, 0);
},
- [formik, duplicateErrorShown]
+ [formik, duplicateErrorShown, nextDayErrorShown]
);
const { formErrorList, handleFormSubmit, close } = useFormikErrorList(formik);
@@ -1350,28 +1446,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setDuplicateErrorShown(false);
}
}
-
- if (
- nextDayRecording &&
- nextDayRecording.project_flock_kandang_id === projectFlockKandangId
- ) {
- const hasSameDayRecording = isResponseSuccess(existingRecordings)
- ? existingRecordings.data?.some(
- (recording: Recording) =>
- recording.project_flock.project_flock_kandang_id ===
- projectFlockKandangId &&
- recording.day === nextDayRecording.next_day
- )
- : false;
-
- if (hasSameDayRecording) {
- toast.error(
- `Recording untuk hari ke-${nextDayRecording.next_day} sudah ada datanya.
- Tidak bisa membuat recording di hari yang sama dengan project flock yang sama, mohon perbarui recording yang sudah ada terlebih dahulu.`
- );
- return;
- }
- }
}
if (formik.values.project_flock_kandang_id !== projectFlockKandangId) {
@@ -1831,8 +1905,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Umur
- {nextDayRecording
- ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${Math.ceil(nextDayRecording.next_day / 7)})`
+ {type === 'add'
+ ? nextDayRecording
+ ? `Hari ke-${nextDayRecording.next_day} (Minggu ke-${Math.ceil(nextDayRecording.next_day / 7)})`
+ : '-'
: initialValues?.day
? `Hari ke-${initialValues.day} (Minggu ke-${Math.ceil(initialValues.day / 7)})`
: '-'}
@@ -2398,7 +2474,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
option?.value || 0
);
}}
- options={unifiedStockProducts}
+ options={getAvailableStockProductOptions(idx)}
placeholder={
!formik.values.project_flock_kandang_id
? 'Pilih kandang terlebih dahulu'
@@ -2619,7 +2695,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
option?.value || 0
);
}}
- options={depletionProducts}
+ options={getAvailableDepletionProductOptions(idx)}
placeholder='Pilih Kondisi'
isLoading={isLoadingDepletionProducts}
onMenuScrollToBottom={loadMoreDepletionProducts}
@@ -2837,7 +2913,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
option?.value || 0
);
}}
- options={eggProducts}
+ options={getAvailableEggProductOptions(idx)}
placeholder='Pilih Kondisi Telur'
isLoading={isLoadingEggProducts}
onMenuScrollToBottom={loadMoreEggProducts}
@@ -3116,7 +3192,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
text='Apakah anda yakin ingin menyetujui data Recording ini?'
secondaryButton={{
text: 'Tidak',
- onClick: () => setApprovalNotes(''),
+ onClick: () => {
+ setApprovalNotes('');
+ approveModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
@@ -3138,7 +3217,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
text='Apakah anda yakin ingin menolak data Recording ini?'
secondaryButton={{
text: 'Tidak',
- onClick: () => setApprovalNotes(''),
+ onClick: () => {
+ setApprovalNotes('');
+ rejectModal.closeModal();
+ },
}}
primaryButton={{
text: 'Ya',
diff --git a/src/components/pages/purchase/PurchaseTable.tsx b/src/components/pages/purchase/PurchaseTable.tsx
index 9af19b42..d0c72dac 100644
--- a/src/components/pages/purchase/PurchaseTable.tsx
+++ b/src/components/pages/purchase/PurchaseTable.tsx
@@ -16,7 +16,7 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
import RowCollapseOptions from '@/components/table/RowCollapseOptions';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
import RequirePermission from '@/components/helper/RequirePermission';
-import Badge from '@/components/Badge';
+import StatusBadge from '@/components/helper/StatusBadge';
import { cn, formatDate } from '@/lib/helper';
import { isResponseSuccess } from '@/lib/api-helper';
@@ -25,6 +25,44 @@ import { useTableFilter } from '@/services/hooks/useTableFilter';
import { ROWS_OPTIONS } from '@/config/constant';
import { Purchase } from '@/types/api/purchase/purchase';
import { PurchaseApi } from '@/services/api/purchase';
+import { Color } from '@/types/theme';
+
+// ===== STATUS BADGE UTILITIES =====
+const statusTextMap: Record = {
+ APPROVED: 'Disetujui',
+ Disetujui: 'Disetujui',
+ REJECTED: 'Ditolak',
+ Ditolak: 'Ditolak',
+ CREATED: 'Dibuat',
+ UPDATED: 'Diperbarui',
+};
+
+const getStatusText = (status: string): string => {
+ return statusTextMap[status] || status;
+};
+
+const statusBadgeColorMap: Record = {
+ APPROVED: 'success',
+ Disetujui: 'success',
+ approved: 'success',
+ disetujui: 'success',
+ REJECTED: 'error',
+ Ditolak: 'error',
+ rejected: 'error',
+ ditolak: 'error',
+ CREATED: 'neutral',
+ Dibuat: 'neutral',
+ created: 'neutral',
+ dibuat: 'neutral',
+ UPDATED: 'warning',
+ Diperbarui: 'warning',
+ updated: 'warning',
+ diperbarui: 'warning',
+};
+
+const getStatusBadgeColor = (status: string): Color => {
+ return statusBadgeColorMap[status] || 'neutral';
+};
// ===== INTERFACES =====
interface RowOptionsMenuProps {
@@ -160,48 +198,42 @@ const PurchaseTable = () => {
const approval = props.row.original.latest_approval;
if (!approval) return '-';
- const isRejected = approval.action === 'REJECTED';
+ const status = approval.action;
- let statusColor:
- | 'warning'
- | 'success'
- | 'neutral'
- | 'error'
- | 'primary'
- | 'info' = 'neutral';
+ let statusColor: Color = 'neutral';
- switch (approval.step_number) {
- case 1:
- statusColor = 'neutral';
- break;
- case 2:
- statusColor = 'primary';
- break;
- case 3:
- statusColor = 'info';
- break;
- case 4:
- statusColor = 'warning';
- break;
- case 5:
- statusColor = 'success';
- break;
+ if (status === 'REJECTED') {
+ statusColor = getStatusBadgeColor(status);
+ } else {
+ switch (approval.step_number) {
+ case 1:
+ statusColor = 'neutral';
+ break;
+ case 2:
+ statusColor = 'primary';
+ break;
+ case 3:
+ statusColor = 'info';
+ break;
+ case 4:
+ statusColor = 'warning';
+ break;
+ case 5:
+ statusColor = 'success';
+ break;
+ }
}
- if (isRejected) {
- statusColor = 'error';
- }
+ const statusText = approval.step_name || getStatusText(status);
return (
-
- {isRejected ? 'Ditolak' : approval.step_name}
-
+ />
);
},
},
@@ -369,6 +401,7 @@ const PurchaseTable = () => {
text={`Apakah anda yakin ingin menghapus data permintaan pembelian ini?`}
secondaryButton={{
text: 'Tidak',
+ onClick: () => deleteModal.closeModal(),
}}
primaryButton={{
text: 'Ya',
diff --git a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx
index 1f8dd3c9..5324db03 100644
--- a/src/components/pages/purchase/order/PurchaseOrderDetail.tsx
+++ b/src/components/pages/purchase/order/PurchaseOrderDetail.tsx
@@ -105,6 +105,7 @@ const PurchaseOrderDetail = ({
const [rowSelection, setRowSelection] = useState>({});
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
+ const [, setApprovalNotes] = useState('');
const selectedRowIds = Object.keys(rowSelection).map((item) =>
parseInt(item)
@@ -207,12 +208,15 @@ const PurchaseOrderDetail = ({
switch (approvalStep) {
case 1:
+ setApprovalNotes('');
staffApprovalModal.openModal();
break;
case 2:
+ setApprovalNotes('');
confirmationModalWithNotes.openModal();
break;
case 3:
+ setApprovalNotes('');
acceptApprovalModal.openModal();
break;
default:
@@ -225,12 +229,15 @@ const PurchaseOrderDetail = ({
switch (approvalStep) {
case 1:
+ setApprovalNotes('');
staffRejectionModal.openModal();
break;
case 2:
+ setApprovalNotes('');
managerRejectionModal.openModal();
break;
case 3:
+ setApprovalNotes('');
acceptRejectionModal.openModal();
break;
default:
@@ -406,6 +413,56 @@ const PurchaseOrderDetail = ({
refetchData,
]);
+ // ===== APPROVAL/REJECTION HANDLERS =====
+ const managerApprovalHandler = async (notes: string) => {
+ const payload: CreateManagerApprovalRequestPayload = {
+ action: 'APPROVED',
+ notes: notes || null,
+ };
+
+ await createManagerApprovalHandler(payload);
+ await refreshApprovals();
+ await refetchData?.();
+ setApprovalNotes('');
+ confirmationModalWithNotes.closeModal();
+ };
+
+ const staffRejectionHandler = async (notes: string) => {
+ const payload: CreateStaffApprovalRequestPayload = {
+ action: 'REJECTED',
+ notes: notes || null,
+ };
+
+ await createStaffApprovalHandler(payload);
+ await refetchData?.();
+ setApprovalNotes('');
+ staffRejectionModal.closeModal();
+ };
+
+ const acceptRejectionHandler = async (notes: string) => {
+ const payload: CreateAcceptApprovalRequestPayload = {
+ action: 'REJECTED',
+ notes: notes || null,
+ };
+
+ await createAcceptApprovalHandler(payload);
+ await refetchData?.();
+ setApprovalNotes('');
+ acceptRejectionModal.closeModal();
+ };
+
+ const managerRejectionHandler = async (notes: string) => {
+ const payload: CreateManagerApprovalRequestPayload = {
+ action: 'REJECTED',
+ notes: notes || null,
+ };
+
+ await createManagerApprovalHandler(payload);
+ await refetchData?.();
+ setApprovalNotes('');
+ managerRejectionModal.closeModal();
+ };
+
if (!initialValues) {
return null;
}
@@ -969,20 +1026,14 @@ const PurchaseOrderDetail = ({
primaryButton={{
text: 'Ya, Lanjutkan',
color: 'success',
- onClick: async (notes) => {
- const payload: CreateManagerApprovalRequestPayload = {
- action: 'APPROVED',
- notes: notes || null,
- };
-
- await createManagerApprovalHandler(payload);
- await refreshApprovals();
- await refetchData?.();
- confirmationModalWithNotes.closeModal();
- },
+ onClick: managerApprovalHandler,
}}
secondaryButton={{
text: 'Batal',
+ onClick: () => {
+ setApprovalNotes('');
+ confirmationModalWithNotes.closeModal();
+ },
}}
/>
@@ -1071,19 +1122,14 @@ const PurchaseOrderDetail = ({
primaryButton={{
text: 'Ya, Tolak',
color: 'error',
- onClick: async (notes) => {
- const payload: CreateStaffApprovalRequestPayload = {
- action: 'REJECTED',
- notes: notes || null,
- };
-
- await createStaffApprovalHandler(payload);
- await refetchData?.();
- staffRejectionModal.closeModal();
- },
+ onClick: staffRejectionHandler,
}}
secondaryButton={{
text: 'Batal',
+ onClick: () => {
+ setApprovalNotes('');
+ staffRejectionModal.closeModal();
+ },
}}
/>
@@ -1098,19 +1144,14 @@ const PurchaseOrderDetail = ({
primaryButton={{
text: 'Ya, Tolak',
color: 'error',
- onClick: async (notes) => {
- const payload: CreateAcceptApprovalRequestPayload = {
- action: 'REJECTED',
- notes: notes || null,
- };
-
- await createAcceptApprovalHandler(payload);
- await refetchData?.();
- acceptRejectionModal.closeModal();
- },
+ onClick: acceptRejectionHandler,
}}
secondaryButton={{
text: 'Batal',
+ onClick: () => {
+ setApprovalNotes('');
+ acceptRejectionModal.closeModal();
+ },
}}
/>
@@ -1125,19 +1166,14 @@ const PurchaseOrderDetail = ({
primaryButton={{
text: 'Ya, Tolak',
color: 'error',
- onClick: async (notes) => {
- const payload: CreateManagerApprovalRequestPayload = {
- action: 'REJECTED',
- notes: notes || null,
- };
-
- await createManagerApprovalHandler(payload);
- await refetchData?.();
- managerRejectionModal.closeModal();
- },
+ onClick: managerRejectionHandler,
}}
secondaryButton={{
text: 'Batal',
+ onClick: () => {
+ setApprovalNotes('');
+ managerRejectionModal.closeModal();
+ },
}}
/>
diff --git a/src/figma-make/components/pages/list-daily-checklist/ListDailyChecklistContent.tsx b/src/figma-make/components/pages/list-daily-checklist/ListDailyChecklistContent.tsx
index f54ad41f..634d8716 100644
--- a/src/figma-make/components/pages/list-daily-checklist/ListDailyChecklistContent.tsx
+++ b/src/figma-make/components/pages/list-daily-checklist/ListDailyChecklistContent.tsx
@@ -36,6 +36,7 @@ import { ColumnDef } from '@tanstack/react-table';
import { useSelect } from '@/components/input/SelectInput';
import { KandangApi } from '@/services/api/master-data';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
+import RequirePermission from '@/components/helper/RequirePermission';
interface Kandang {
id: string;
@@ -389,19 +390,21 @@ export function ListDailyChecklistContent() {
{row.original.status === 'DRAFT' && (
-
+
+
+
)}
{row.original.status === 'SUBMITTED' && (
- <>
+
- >
+
)}
{row.original.status === 'DRAFT' && (
-
+
+
+
)}
),
diff --git a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx
index d8723df0..bc9653d4 100644
--- a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx
+++ b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx
@@ -23,6 +23,7 @@ import { isResponseError } from '@/lib/api-helper';
import Link from 'next/link';
import { Icon } from '@iconify/react';
import { Document } from '@/types/api/api-general';
+import RequirePermission from '@/components/helper/RequirePermission';
interface ChecklistDetailRow {
checklist_id: string;
@@ -593,25 +594,27 @@ export function DetailDailyChecklistContent() {
{header.status === 'SUBMITTED' && (
-
-
-
-
+
+
+
+
+
+
)}