mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
feat(FE-212): enhance PurchaseOrderStaffApprovalForm to dynamically determine form type and validate product selection for duplicates
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user