feat(FE-212): enhance PurchaseOrderStaffApprovalForm to dynamically determine form type and validate product selection for duplicates

This commit is contained in:
rstubryan
2025-11-22 09:47:20 +07:00
parent ef95d1a0e8
commit 0fefe5e035
@@ -33,6 +33,7 @@ import {
Purchase, Purchase,
} from '@/types/api/purchase/purchase'; } from '@/types/api/purchase/purchase';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useApprovalSteps } from '@/components/pages/ApprovalSteps';
interface PurchaseOrderStaffApprovalFormProps { interface PurchaseOrderStaffApprovalFormProps {
type?: 'add' | 'edit'; type?: 'add' | 'edit';
@@ -44,13 +45,43 @@ interface PurchaseOrderStaffApprovalFormProps {
} }
const PurchaseOrderStaffApprovalForm = ({ const PurchaseOrderStaffApprovalForm = ({
type = 'add', type: propType,
initialValues, initialValues,
onCancel, onCancel,
refreshApprovals, refreshApprovals,
onModalClose, onModalClose,
onRefetchData, onRefetchData,
}: PurchaseOrderStaffApprovalFormProps) => { }: PurchaseOrderStaffApprovalFormProps) => {
const { rawDataApprovals } = useApprovalSteps({
latestApproval: initialValues?.approval,
approvalLines: [],
moduleName: 'PURCHASES',
moduleId: initialValues?.id?.toString() ?? '',
params: {
limit: 100,
group_step_number: true,
},
});
const type = useMemo(() => {
if (propType && (propType === 'add' || propType === 'edit')) {
return propType;
}
if (!rawDataApprovals || rawDataApprovals.length === 0) {
return 'add';
}
const firstApproval = rawDataApprovals[0];
const hasOnlyInitialStep =
rawDataApprovals.length === 1 &&
firstApproval?.step_number === 1 &&
'action' in firstApproval &&
firstApproval.action === 'CREATED';
return hasOnlyInitialStep ? 'add' : 'edit';
}, [rawDataApprovals, propType]);
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const deleteModal = useModal(); const deleteModal = useModal();
@@ -68,8 +99,6 @@ const PurchaseOrderStaffApprovalForm = ({
if (!initialValues?.approval) return false; if (!initialValues?.approval) return false;
const currentStep = initialValues.approval.step_number; const currentStep = initialValues.approval.step_number;
// Allow editing from step 3 onwards
// Hide delete/add buttons from step 4 onwards
return currentStep >= 3; return currentStep >= 3;
}, [initialValues?.approval]); }, [initialValues?.approval]);
@@ -77,8 +106,6 @@ const PurchaseOrderStaffApprovalForm = ({
if (!initialValues?.approval) return false; if (!initialValues?.approval) return false;
const currentStep = initialValues.approval.step_number; const currentStep = initialValues.approval.step_number;
// Show delete/add buttons only up to step 3
// Hide from step 4 onwards
return currentStep < 4; return currentStep < 4;
}, [initialValues?.approval]); }, [initialValues?.approval]);
@@ -231,18 +258,31 @@ const PurchaseOrderStaffApprovalForm = ({
(url: string) => SupplierApi.getSingle(Number(url.split('/').pop())) (url: string) => SupplierApi.getSingle(Number(url.split('/').pop()))
); );
const supplierProductOptions = useMemo(() => { const baseSupplierProductOptions: OptionType[] = useMemo(() => {
if (!supplierData || !isResponseSuccess(supplierData)) { if (!supplierData || !isResponseSuccess(supplierData)) {
return []; return [];
} }
const supplier = supplierData.data as SupplierProducts; const supplier = supplierData.data as SupplierProducts;
const products = supplier.products || []; const products = supplier.products || [];
if (type === 'edit' && initialValues?.items) {
const currentProductIds = initialValues.items.map(
(item) => item.product_id
);
return products
.filter((product) => currentProductIds.includes(product.id))
.map((product) => ({
value: product.id,
label: product.name,
}));
}
return products.map((product) => ({ return products.map((product) => ({
value: product.id, value: product.id,
label: product.name, label: product.name,
})); }));
}, [supplierData]); }, [supplierData, type, initialValues?.items]);
// ===== FORM CONFIGURATION ===== // ===== FORM CONFIGURATION =====
const formikInitialValues = useMemo(() => { const formikInitialValues = useMemo(() => {
@@ -364,6 +404,8 @@ const PurchaseOrderStaffApprovalForm = ({
}, },
}); });
const supplierProductOptions = baseSupplierProductOptions;
// ===== API DATA FETCHING ===== // ===== API DATA FETCHING =====
const purchaseItems = useMemo(() => { const purchaseItems = useMemo(() => {
if (initialValues?.items) { if (initialValues?.items) {
@@ -426,6 +468,10 @@ const PurchaseOrderStaffApprovalForm = ({
const itemData = { const itemData = {
purchase_item_id: purchaseItem.id, purchase_item_id: purchaseItem.id,
product_id: purchaseItem.product_id || 0, product_id: purchaseItem.product_id || 0,
product: {
value: purchaseItem.product_id || 0,
label: purchaseItem.product?.name || '',
},
warehouse_id: purchaseItem.warehouse_id || 0, warehouse_id: purchaseItem.warehouse_id || 0,
qty: originalItem?.qty || purchaseItem.quantity || 0, qty: originalItem?.qty || purchaseItem.quantity || 0,
price: type === 'edit' && originalItem ? originalItem.price : '', price: type === 'edit' && originalItem ? originalItem.price : '',
@@ -493,6 +539,34 @@ const PurchaseOrderStaffApprovalForm = ({
const product = val as OptionType | null; const product = val as OptionType | null;
const productId = (product as OptionType)?.value || 0; const productId = (product as OptionType)?.value || 0;
if (Number(productId) > 0) {
const currentItem = formik.values.items?.[idx];
const warehouseId = currentItem?.warehouse_id || 0;
const isDuplicate = formik.values.items?.some(
(item, itemIdx) =>
itemIdx !== idx &&
item.product_id === productId &&
item.warehouse_id === warehouseId
);
if (isDuplicate) {
const existingItem = formik.values.items?.find(
(item, itemIdx) =>
itemIdx !== idx &&
item.product_id === productId &&
item.warehouse_id === warehouseId
);
const existingProductName =
existingItem?.product?.label || 'produk ini';
toast.error(
`Tidak bisa menambahkan item yang sama. ${existingProductName} sudah ada di gudang ini.`
);
return;
}
}
formik.setFieldTouched(`items.${idx}.product`, true); formik.setFieldTouched(`items.${idx}.product`, true);
formik.setFieldValue(`items.${idx}.product`, product); formik.setFieldValue(`items.${idx}.product`, product);
formik.setFieldTouched(`items.${idx}.product_id`, true); formik.setFieldTouched(`items.${idx}.product_id`, true);
@@ -568,7 +642,9 @@ const PurchaseOrderStaffApprovalForm = ({
Total (Rp.) Total (Rp.)
<span className='text-error'>*</span> <span className='text-error'>*</span>
</th> </th>
{canShowDeleteAddButtons && <th>Action</th>} {canShowDeleteAddButtons && type === 'edit' && (
<th>Action</th>
)}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -595,14 +671,16 @@ const PurchaseOrderStaffApprovalForm = ({
<td> <td>
<SelectInput <SelectInput
key={`product-${purchaseItem.id}`} key={`product-${purchaseItem.id}`}
value={{ value={
value: purchaseItem?.product_id || 0, formItem?.product as OptionType | null
label: }
purchaseItem?.product?.name || '', onChange={(val) =>
}} handleProductChange(formItemIndex, val)
}
options={supplierProductOptions} options={supplierProductOptions}
isClearable={false} isClearable={false}
isSearchable={false} isSearchable={false}
isDisabled={type === 'add'}
className={{ className={{
wrapper: wrapper:
'min-w-52 md:min-w-72 lg:min-w-80', 'min-w-52 md:min-w-72 lg:min-w-80',
@@ -770,7 +848,9 @@ const PurchaseOrderStaffApprovalForm = ({
<td> <td>
<SelectInput <SelectInput
required required
value={formItem.product as OptionType | null} value={
formItem.product as OptionType | null
}
onChange={(val) => onChange={(val) =>
handleProductChange(idx, val) handleProductChange(idx, val)
} }