mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'fix/adjustment-penjualan' into 'development'
[FIX/FE] Adjustment Penjualan UI and Data Fetch (2/2) See merge request mbugroup/lti-web-client!318
This commit is contained in:
@@ -111,6 +111,7 @@ const DeliveryOrderFormModal = ({
|
||||
const successModal = useModal();
|
||||
const rejectModal = useModal();
|
||||
const deleteModal = useModal();
|
||||
const approveModal = useModal();
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -333,6 +334,33 @@ const DeliveryOrderFormModal = ({
|
||||
refreshApproval();
|
||||
};
|
||||
|
||||
const approveMarketingHandler = async (notes: string) => {
|
||||
if (!marketingId) {
|
||||
toast.error(`Tidak ada data yang valid untuk di approve.`);
|
||||
approveModal.closeModal();
|
||||
return;
|
||||
}
|
||||
|
||||
const approveMarketingRes = await SalesOrderApi.singleApproval(
|
||||
Number(marketingId),
|
||||
'APPROVED',
|
||||
notes
|
||||
);
|
||||
|
||||
if (isResponseSuccess(approveMarketingRes)) {
|
||||
approveModal.closeModal();
|
||||
toast.success(approveMarketingRes?.message as string);
|
||||
closeModalHandler();
|
||||
router.push('/marketing');
|
||||
}
|
||||
if (isResponseError(approveMarketingRes)) {
|
||||
approveModal.closeModal();
|
||||
toast.error(approveMarketingRes?.message as string);
|
||||
}
|
||||
refreshMarketing();
|
||||
refreshApproval();
|
||||
};
|
||||
|
||||
const deleteClickHandler = () => {
|
||||
deleteModal.openModal();
|
||||
};
|
||||
@@ -380,7 +408,77 @@ const DeliveryOrderFormModal = ({
|
||||
},
|
||||
[prevButtonHandler]
|
||||
);
|
||||
const handleUpdateDO = useCallback(
|
||||
|
||||
const isApprovalStep3Approved = useMemo(() => {
|
||||
return (
|
||||
isResponseSuccess(marketing) &&
|
||||
marketing.data.latest_approval?.step_number === 3 &&
|
||||
marketing.data.latest_approval?.action === 'APPROVED'
|
||||
);
|
||||
}, [marketing]);
|
||||
|
||||
const handleUpdateDOWithAPI = useCallback(
|
||||
async (id: number, values: DeliveryOrderProductFormValues) => {
|
||||
if (!marketingId) {
|
||||
toast.error('Marketing ID tidak ditemukan');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const updatedDeliveryValues = deliveryOrderValues.map((product) =>
|
||||
product.id === id ? { ...product, ...values } : product
|
||||
);
|
||||
|
||||
const payload = {
|
||||
marketing_id: Number(marketingId),
|
||||
delivery_products: updatedDeliveryValues
|
||||
.map((product) => {
|
||||
if (Boolean(product.delivery_date)) {
|
||||
return {
|
||||
marketing_product_id: product.marketing_product_id as number,
|
||||
unit_price: parseFloat(product.unit_price as string),
|
||||
total_weight: parseFloat(product.total_weight as string),
|
||||
qty: parseFloat(product.qty as string),
|
||||
avg_weight: parseFloat(product.avg_weight as string),
|
||||
total_price: parseFloat(product.total_price as string),
|
||||
delivery_date: formatDate(
|
||||
product.delivery_date as string,
|
||||
'yyyy-MM-DD'
|
||||
),
|
||||
vehicle_number: product.vehicle_number,
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter((item) => Boolean(item)),
|
||||
} as UpdateDeliveryOrderPayload;
|
||||
|
||||
const updateDeliveryRes = await DeliveryOrderApi.update(
|
||||
Number(marketingId),
|
||||
payload
|
||||
);
|
||||
|
||||
if (isResponseSuccess(updateDeliveryRes)) {
|
||||
toast.success(updateDeliveryRes?.message as string);
|
||||
closeModalHandler();
|
||||
}
|
||||
|
||||
if (isResponseError(updateDeliveryRes)) {
|
||||
setFormErrorMessage(updateDeliveryRes?.message as string);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
},
|
||||
[
|
||||
marketingId,
|
||||
deliveryOrderValues,
|
||||
formik.values.sales_order,
|
||||
prevButtonHandler,
|
||||
refreshMarketing,
|
||||
]
|
||||
);
|
||||
|
||||
const handleUpdateDOLocal = useCallback(
|
||||
async (id: number, values: DeliveryOrderProductFormValues) => {
|
||||
setDeliveryOrderValues((prev) =>
|
||||
prev.map((product) =>
|
||||
@@ -463,7 +561,26 @@ const DeliveryOrderFormModal = ({
|
||||
);
|
||||
|
||||
formik.setValues(filledInitialValues);
|
||||
setStep(1);
|
||||
|
||||
if (modalAction === 'add_delivery') {
|
||||
// add delivery
|
||||
const firstDeliveryItem = filledInitialValues.delivery_order?.[0];
|
||||
if (firstDeliveryItem) {
|
||||
setSelectedDeliveryProduct(firstDeliveryItem);
|
||||
}
|
||||
setStep(2); // Langsung ke form delivery
|
||||
} else if (modalAction === 'edit_delivery') {
|
||||
// edit delivery
|
||||
const firstDeliveryItem = filledInitialValues.delivery_order?.[0];
|
||||
if (firstDeliveryItem) {
|
||||
setSelectedDeliveryProduct(firstDeliveryItem);
|
||||
setStep(2); // Langsung ke form edit
|
||||
} else {
|
||||
setStep(1); // Jika belum ada data, tampilkan detail view
|
||||
}
|
||||
} else {
|
||||
setStep(1); // Detail view
|
||||
}
|
||||
}
|
||||
|
||||
if (isResponseError(marketing)) {
|
||||
@@ -474,7 +591,7 @@ const DeliveryOrderFormModal = ({
|
||||
};
|
||||
|
||||
getFilledInitialValues();
|
||||
}, [marketingId, marketing]);
|
||||
}, [marketingId, marketing, modalAction]);
|
||||
|
||||
// Reset error message when step changes
|
||||
useEffect(() => {
|
||||
@@ -679,7 +796,12 @@ const DeliveryOrderFormModal = ({
|
||||
exisitingValues={deliveryOrderValues}
|
||||
onSubmitForm={handleAddSubmitDO}
|
||||
initialValues={selectedDeliveryProduct ?? undefined}
|
||||
onUpdateForm={handleUpdateDO}
|
||||
onUpdateForm={
|
||||
isApprovalStep3Approved
|
||||
? handleUpdateDOWithAPI
|
||||
: handleUpdateDOLocal
|
||||
}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -723,7 +845,15 @@ const DeliveryOrderFormModal = ({
|
||||
type='button'
|
||||
color='primary'
|
||||
onClick={() => {
|
||||
// Jika masih di step 1 approval, gunakan single approval API
|
||||
if (
|
||||
marketing?.data?.latest_approval?.step_number === 1
|
||||
) {
|
||||
approveModal.openModal();
|
||||
} else {
|
||||
// Jika sudah di step 2/3, gunakan form submit (delivery products)
|
||||
formRef.current?.requestSubmit();
|
||||
}
|
||||
}}
|
||||
className='p-3 shadow-button-soft text-base-100 rounded-lg text-sm font-semibold'
|
||||
disabled={deliveryRejected}
|
||||
@@ -741,8 +871,8 @@ const DeliveryOrderFormModal = ({
|
||||
ref={successModal.ref}
|
||||
iconPosition='left'
|
||||
type='success'
|
||||
text={`${currentModalAction === 'add' ? 'Data Berhasil Disimpan' : 'Data Berhasil Diubah'}`}
|
||||
subtitleText={`${currentModalAction === 'add' ? 'Data delivery order telah berhasil disimpan.' : 'Data delivery order telah berhasil diubah.'}`}
|
||||
text={`${currentModalAction === 'add_delivery' ? 'Data Berhasil Disimpan' : 'Data Berhasil Diubah'}`}
|
||||
subtitleText={`${currentModalAction === 'add_delivery' ? 'Data delivery order telah berhasil disimpan.' : 'Data delivery order telah berhasil diubah.'}`}
|
||||
primaryButton={{
|
||||
text: 'Oke',
|
||||
color: 'primary',
|
||||
@@ -795,6 +925,21 @@ const DeliveryOrderFormModal = ({
|
||||
onClick: confirmationModalDeleteClickHandler,
|
||||
}}
|
||||
/>
|
||||
|
||||
<ConfirmationModalWithNotes
|
||||
ref={approveModal.ref}
|
||||
type={'success'}
|
||||
text={`Apakah anda yakin ingin approve data penjualan?`}
|
||||
secondaryButton={{
|
||||
text: 'Tidak',
|
||||
onClick: approveModal.closeModal,
|
||||
}}
|
||||
primaryButton={{
|
||||
text: 'Ya',
|
||||
color: 'success',
|
||||
onClick: approveMarketingHandler,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -394,7 +394,7 @@ const SalesOrderFormModal = ({
|
||||
}
|
||||
|
||||
formik.setFieldValue('sales_order', updatedProducts);
|
||||
console.log(formik.values);
|
||||
setSelectedMarketingProduct(null);
|
||||
nextButtonHandler();
|
||||
},
|
||||
[memoSalesOrder, nextButtonHandler]
|
||||
@@ -418,6 +418,15 @@ const SalesOrderFormModal = ({
|
||||
if (modalAction === 'add' || modalAction === 'edit') {
|
||||
setCurrentModalAction(modalAction);
|
||||
formModal.openModal();
|
||||
|
||||
if (modalAction === 'add') {
|
||||
formik.resetForm();
|
||||
setStep(1);
|
||||
setSelectedMarketingProduct(null);
|
||||
setSelectedDeliveryProduct(null);
|
||||
setFormErrorMessage('');
|
||||
setFormErrorList([]);
|
||||
}
|
||||
}
|
||||
}, [modalAction]);
|
||||
|
||||
|
||||
+35
-6
@@ -36,6 +36,7 @@ const DeliveryOrderProductForm = ({
|
||||
exisitingValues,
|
||||
onSubmitForm,
|
||||
onUpdateForm,
|
||||
isLoading,
|
||||
}: {
|
||||
formState: 'add' | 'edit';
|
||||
salesOrders: BaseSalesOrder[];
|
||||
@@ -46,6 +47,7 @@ const DeliveryOrderProductForm = ({
|
||||
id: number,
|
||||
value: DeliveryOrderProductFormValues
|
||||
) => Promise<void>;
|
||||
isLoading?: boolean;
|
||||
}) => {
|
||||
const [formikErrorMessage, setFormErrorMessage] = useState('');
|
||||
const [selectedProduct, setSelectedProduct] = useState<OptionType | null>(
|
||||
@@ -178,6 +180,25 @@ const DeliveryOrderProductForm = ({
|
||||
},
|
||||
});
|
||||
|
||||
const hasWeekField = useMemo(() => {
|
||||
const marketingType = formik.values.marketing_type?.value?.toLowerCase();
|
||||
if (marketingType === 'ayam_pullet') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (formik.values.marketing_product?.product_warehouse_data) {
|
||||
return Boolean(
|
||||
formik.values.marketing_product?.product_warehouse_data?.week !==
|
||||
undefined &&
|
||||
formik.values.marketing_product?.product_warehouse_data?.week !==
|
||||
null &&
|
||||
formik.values.marketing_product?.product_warehouse_data?.week > 0
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [formik.values.marketing_product, formik.values.marketing_type]);
|
||||
|
||||
const handleResetForm = () => {
|
||||
setFormErrorMessage('');
|
||||
formik.resetForm({
|
||||
@@ -362,20 +383,24 @@ const DeliveryOrderProductForm = ({
|
||||
avg_weight: '',
|
||||
total_weight: '',
|
||||
vehicle_number: '',
|
||||
week: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const soFieldValues = SalesProductToFieldValues(so);
|
||||
|
||||
formik.setValues({
|
||||
...formik.values,
|
||||
marketing_product_id: selected.value as number,
|
||||
marketing_product: SalesProductToFieldValues(so),
|
||||
marketing_product: soFieldValues,
|
||||
qty: so.qty,
|
||||
unit_price: so.unit_price,
|
||||
total_price: so.total_price,
|
||||
avg_weight: so.avg_weight,
|
||||
total_weight: so.total_weight,
|
||||
vehicle_number: so.vehicle_number,
|
||||
week: soFieldValues.week ?? null,
|
||||
});
|
||||
}}
|
||||
startAdornment={
|
||||
@@ -509,10 +534,14 @@ const DeliveryOrderProductForm = ({
|
||||
)}
|
||||
|
||||
{/* Konversi Satuan Week Pullet */}
|
||||
{formik.values.marketing_type?.value.toLowerCase() ===
|
||||
'ayam_pullet' && (
|
||||
{(formik.values.marketing_type?.value.toLowerCase() ===
|
||||
'ayam_pullet' ||
|
||||
hasWeekField) && (
|
||||
<NumberInput
|
||||
required
|
||||
required={
|
||||
formik.values.marketing_type?.value.toLowerCase() ===
|
||||
'ayam_pullet'
|
||||
}
|
||||
label='Minggu'
|
||||
name='week'
|
||||
value={formik.values.week ?? undefined}
|
||||
@@ -793,8 +822,8 @@ const DeliveryOrderProductForm = ({
|
||||
<div className='absolute sm:w-full bottom-0 right-0 p-4'>
|
||||
<Button
|
||||
type='submit'
|
||||
isLoading={formik.isSubmitting}
|
||||
disabled={formik.isSubmitting}
|
||||
isLoading={formik.isSubmitting || isLoading}
|
||||
disabled={formik.isSubmitting || isLoading}
|
||||
className='w-full p-3 rounded-lg text-base-100 text-sm font-semibold'
|
||||
>
|
||||
Submit
|
||||
|
||||
@@ -117,6 +117,19 @@ const SalesOrderProductForm = ({
|
||||
isInitialValid: false,
|
||||
});
|
||||
|
||||
const hasWeekField = useMemo(() => {
|
||||
const marketingType = formik.values.marketing_type?.value?.toLowerCase();
|
||||
if (marketingType === 'ayam_pullet') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Boolean(
|
||||
selectedProductWarehouse?.week !== undefined &&
|
||||
selectedProductWarehouse?.week !== null &&
|
||||
selectedProductWarehouse?.week > 0
|
||||
);
|
||||
}, [selectedProductWarehouse, formik.values.marketing_type]);
|
||||
|
||||
// ===== Options =====
|
||||
const {
|
||||
options: kandangSourceOptions,
|
||||
@@ -180,10 +193,20 @@ const SalesOrderProductForm = ({
|
||||
setSelectedProductWarehouse(productWarehouse || null);
|
||||
formik.setFieldValue('qty', productWarehouse?.quantity);
|
||||
formik.setFieldValue('uom', productWarehouse?.product?.uom?.name || '');
|
||||
if (
|
||||
productWarehouse?.week !== undefined &&
|
||||
productWarehouse?.week !== null &&
|
||||
productWarehouse?.week > 0
|
||||
) {
|
||||
formik.setFieldValue('week', productWarehouse.week);
|
||||
} else {
|
||||
formik.setFieldValue('week', null);
|
||||
}
|
||||
handleBlurField('qty');
|
||||
} else {
|
||||
formik.setFieldValue('qty', '');
|
||||
formik.setFieldValue('uom', '');
|
||||
formik.setFieldValue('week', null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -465,10 +488,14 @@ const SalesOrderProductForm = ({
|
||||
)}
|
||||
|
||||
{/* Konversi Satuan Week Pullet */}
|
||||
{formik.values.marketing_type?.value.toLowerCase() ===
|
||||
'ayam_pullet' && (
|
||||
{(formik.values.marketing_type?.value.toLowerCase() ===
|
||||
'ayam_pullet' ||
|
||||
hasWeekField) && (
|
||||
<NumberInput
|
||||
required
|
||||
required={
|
||||
formik.values.marketing_type?.value.toLowerCase() ===
|
||||
'ayam_pullet'
|
||||
}
|
||||
label='Minggu'
|
||||
name='week'
|
||||
value={formik.values.week ?? undefined}
|
||||
|
||||
@@ -19,6 +19,7 @@ type DeliveryOrderProductTableProps = {
|
||||
| 'detail'
|
||||
| 'rejected'
|
||||
| 'pending'
|
||||
| 'success'
|
||||
| string
|
||||
| null;
|
||||
marketing?: Marketing;
|
||||
@@ -32,7 +33,6 @@ const DeliveryOrderProductTable = ({
|
||||
formType,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onAddProductClick,
|
||||
marketing,
|
||||
}: DeliveryOrderProductTableProps) => {
|
||||
const onEditRef = useRef(onEdit);
|
||||
@@ -42,14 +42,166 @@ const DeliveryOrderProductTable = ({
|
||||
|
||||
const approvalStepNumber = marketing?.latest_approval?.step_number;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||
{data.map((item) => {
|
||||
const renderTableContent = (item: DeliveryOrderProductFormValues) => {
|
||||
const doItem = marketing?.delivery_order?.find(
|
||||
(doItem) => doItem.do_number === item.do_number
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className='border-b border-tools-table-outline border-base-content/5'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||
<div>Value</div>
|
||||
{formType !== 'success' &&
|
||||
(formType === 'add_delivery' ||
|
||||
formType === 'edit_delivery' ||
|
||||
formType === 'detail') && (
|
||||
<div className='flex flex-row gap-1.5 items-center'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
onEditRef.current(item.id as number, item);
|
||||
}}
|
||||
className='p-0 hover:text-base-content'
|
||||
>
|
||||
<Icon icon='heroicons:pencil' width={20} height={20} />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{doItem?.warehouse?.name ||
|
||||
item.marketing_product?.product_warehouse_data?.warehouse?.name}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Produk</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.marketing_product?.product_warehouse?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Qty</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.qty
|
||||
? `${formatNumber(parseFloat(item.qty as string))} ${item.marketing_product?.uom ?? ''}`
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
{Number(item.avg_weight ?? 0) > 0 && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Avg Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(Number(item.avg_weight))} Kg
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{Number(item.total_weight ?? 0) > 0 && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(Number(item.total_weight))}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(parseFloat(item.unit_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(parseFloat(item.total_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
<tr className='border-b border-t border-tools-table-outline border-base-content/5'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||
<div>Value</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<>
|
||||
{approvalStepNumber !== 1 && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Tanggal Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.delivery_date ? (
|
||||
formatDate(item.delivery_date, 'DD MMM YYYY')
|
||||
) : formType === 'add_delivery' ||
|
||||
formType === 'edit_delivery' ||
|
||||
formType === 'detail' ? (
|
||||
<span
|
||||
className='text-error hover:text-error/70 cursor-pointer hover:underline underline-offset-4'
|
||||
onClick={() => {
|
||||
onEditRef.current(item.id as number, item);
|
||||
}}
|
||||
>
|
||||
Belum diisi
|
||||
</span>
|
||||
) : (
|
||||
<span className='text-error'>Belum diisi</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{item.do_number && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>{item.do_number}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Polisi</td>
|
||||
<td className='text-sm px-4 py-3'>{item.vehicle_number}</td>
|
||||
</tr>
|
||||
{doItem && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Dokumen Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
<DeliveryOrderExport data={marketing} deliveryOrder={doItem} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||
{data.map((item) => (
|
||||
<div key={`table-${item.id}`}>
|
||||
{formType === 'success' ? (
|
||||
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>{renderTableContent(item)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<Card
|
||||
key={`table-${item.id}`}
|
||||
title={
|
||||
@@ -71,149 +223,12 @@ const DeliveryOrderProductTable = ({
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>
|
||||
<tr className='border-b border-t border-tools-table-outline border-base-content/5'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full'>
|
||||
<div>Value</div>
|
||||
{(formType === 'add_delivery' ||
|
||||
formType === 'edit_delivery' ||
|
||||
formType === 'detail') && (
|
||||
<div className='flex flex-row gap-1.5 items-center'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
onEditRef.current(item.id as number, item);
|
||||
}}
|
||||
className='p-0 hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:pencil'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
onDeleteRef.current(item.id as number);
|
||||
}}
|
||||
className='p-0 text-error hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:trash'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<>
|
||||
{approvalStepNumber !== 1 && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
Tanggal Pengiriman
|
||||
</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.delivery_date ? (
|
||||
formatDate(item.delivery_date, 'DD MMM YYYY')
|
||||
) : (
|
||||
<span className='text-error'>Belum diisi</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{item.do_number && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>{item.do_number}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Polisi</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.vehicle_number}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{doItem?.warehouse?.name ||
|
||||
item.marketing_product?.product_warehouse_data
|
||||
?.warehouse?.name}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Produk</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.marketing_product?.product_warehouse?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Qty</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.qty
|
||||
? `${formatNumber(parseFloat(item.qty as string))} ${item.marketing_product?.uom ?? ''}`
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Avg Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.avg_weight
|
||||
? formatNumber(
|
||||
parseFloat(item.avg_weight as string)
|
||||
) + ' Kg'
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(parseFloat(item.total_weight as string))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(parseFloat(item.unit_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(parseFloat(item.total_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
{doItem && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
Dokumen Pengiriman
|
||||
</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
<DeliveryOrderExport
|
||||
data={marketing}
|
||||
deliveryOrder={doItem}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
</tbody>
|
||||
<tbody className='w-full'>{renderTableContent(item)}</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -3,15 +3,9 @@
|
||||
import Button from '@/components/Button';
|
||||
import Card from '@/components/Card';
|
||||
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||
import {
|
||||
formatCurrency,
|
||||
formatNumber,
|
||||
formatVechicleNumber,
|
||||
} from '@/lib/helper';
|
||||
import { formatCurrency, formatNumber } from '@/lib/helper';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import * as TanStack from '@tanstack/react-table';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
import { useRef } from 'react';
|
||||
|
||||
type SalesOrderProductTableProps = {
|
||||
data: SalesOrderProductFormValues[];
|
||||
@@ -33,141 +27,9 @@ const SalesOrderProductTable = ({
|
||||
const onEditRef = useRef(onEdit);
|
||||
onEditRef.current = onEdit;
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'select',
|
||||
header: ({
|
||||
table,
|
||||
}: {
|
||||
table: TanStack.Table<SalesOrderProductFormValues>;
|
||||
}) => (
|
||||
<div className='w-full flex flex-row justify-center'>
|
||||
<CheckboxInput
|
||||
name='allRow'
|
||||
checked={table.getIsAllRowsSelected()}
|
||||
indeterminate={table.getIsSomeRowsSelected()}
|
||||
onChange={table.getToggleAllRowsSelectedHandler()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }: { row: TanStack.Row<SalesOrderProductFormValues> }) => (
|
||||
<div>
|
||||
<CheckboxInput
|
||||
name='row'
|
||||
checked={row.getIsSelected()}
|
||||
disabled={!row.getCanSelect()}
|
||||
indeterminate={row.getIsSomeSelected()}
|
||||
onChange={row.getToggleSelectedHandler()}
|
||||
value={`${row.original.product_warehouse_id}${row.original.kandang_id}`}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) =>
|
||||
formatVechicleNumber(row.vehicle_number as string),
|
||||
header: 'No. Polisi',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) => row.kandang?.label,
|
||||
header: 'Kandang',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) =>
|
||||
row.product_warehouse?.label,
|
||||
header: 'Produk',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) =>
|
||||
formatCurrency(parseFloat(row.unit_price as string)),
|
||||
header: 'Harga Satuan (Rp)',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) =>
|
||||
formatNumber(parseFloat(row.total_weight as string), undefined, 0, 5),
|
||||
header: 'Total Bobot (Kg)',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) =>
|
||||
formatNumber(parseFloat(row.qty as string)),
|
||||
header: 'Kuantitas',
|
||||
cell: ({ row }: { row: TanStack.Row<SalesOrderProductFormValues> }) =>
|
||||
formatNumber(
|
||||
parseFloat(row.original.qty as string),
|
||||
undefined,
|
||||
0,
|
||||
5
|
||||
) +
|
||||
' ' +
|
||||
(row.original.uom ?? ''),
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) =>
|
||||
formatNumber(parseFloat(row.avg_weight as string), undefined, 0, 5),
|
||||
header: 'Avg. Bobot (Kg)',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: SalesOrderProductFormValues) =>
|
||||
formatCurrency(parseFloat(row.total_price as string)),
|
||||
header: 'Total Penjualan (Rp)',
|
||||
},
|
||||
{
|
||||
header: 'Aksi',
|
||||
cell: (
|
||||
props: TanStack.CellContext<SalesOrderProductFormValues, unknown>
|
||||
) => (
|
||||
<div className='flex flex-row gap-1 items-center justify-end h-full mt-2'>
|
||||
<Button
|
||||
color='warning'
|
||||
className='p-1'
|
||||
onClick={() => onEditRef.current(props.row.original.id as number)}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='mdi:pencil' width={16} height={16} /> Edit
|
||||
</Button>
|
||||
<Button
|
||||
color='error'
|
||||
className='p-1'
|
||||
onClick={() =>
|
||||
onDeleteRef.current(props.row.original.id as number)
|
||||
}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='mdi:trash' width={16} height={16} /> Hapus
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
const renderTableContent = (item: SalesOrderProductFormValues) => (
|
||||
<>
|
||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||
{data.map((item) => (
|
||||
<Card
|
||||
key={`table-${item.id}`}
|
||||
title={item.product_warehouse?.label || 'Produk'}
|
||||
collapsible={true}
|
||||
defaultCollapsed={false}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-0',
|
||||
title: 'px-2 py-1.5 font-normal text-sm',
|
||||
collapsible: 'rounded-lg',
|
||||
}}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>
|
||||
<tr className='border-b border-t border-tools-table-outline border-base-content/5'>
|
||||
<tr className='border-b border-tools-table-outline border-base-content/5'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
@@ -185,11 +47,7 @@ const SalesOrderProductTable = ({
|
||||
}}
|
||||
className='p-0 hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:pencil'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Icon icon='heroicons:pencil' width={20} height={20} />
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
@@ -200,11 +58,7 @@ const SalesOrderProductTable = ({
|
||||
}}
|
||||
className='p-0 text-error hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:trash'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
<Icon icon='heroicons:trash' width={20} height={20} />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
@@ -222,26 +76,19 @@ const SalesOrderProductTable = ({
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Kategori</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.marketing_type?.label}
|
||||
</td>
|
||||
<td className='text-sm px-4 py-3'>{item.marketing_type?.label}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Produk</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.product_warehouse?.label}
|
||||
</td>
|
||||
<td className='text-sm px-4 py-3'>{item.product_warehouse?.label}</td>
|
||||
</tr>
|
||||
{item.marketing_type?.value.toLowerCase() === 'telur' && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Tipe Konversi</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.convertion_unit?.label}
|
||||
</td>
|
||||
<td className='text-sm px-4 py-3'>{item.convertion_unit?.label}</td>
|
||||
</tr>
|
||||
)}
|
||||
{item.marketing_type?.value.toLowerCase() ===
|
||||
'ayam_pullet' && (
|
||||
{item.marketing_type?.value.toLowerCase() === 'ayam_pullet' && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Tipe Konversi</td>
|
||||
<td className='text-sm px-4 py-3'>Week {item.week}</td>
|
||||
@@ -261,9 +108,8 @@ const SalesOrderProductTable = ({
|
||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.total_weight
|
||||
? formatNumber(
|
||||
parseFloat(item.total_weight as string)
|
||||
) + ' Kg'
|
||||
? formatNumber(parseFloat(item.total_weight as string)) +
|
||||
' Kg'
|
||||
: '0 Kg'}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -271,9 +117,7 @@ const SalesOrderProductTable = ({
|
||||
<td className='text-sm px-4 py-3'>Avg Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.avg_weight
|
||||
? formatNumber(
|
||||
parseFloat(item.avg_weight as string)
|
||||
) + ' Kg'
|
||||
? formatNumber(parseFloat(item.avg_weight as string)) + ' Kg'
|
||||
: '0 Kg'}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -302,9 +146,49 @@ const SalesOrderProductTable = ({
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
</tbody>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||
{data.map((item) => (
|
||||
<div key={`table-${item.id}`}>
|
||||
{formType === 'success' ? (
|
||||
<div className='rounded-lg border border-tools-table-outline border-base-content/5'>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>{renderTableContent(item)}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<Card
|
||||
title={item.product_warehouse?.label || 'Produk'}
|
||||
collapsible={true}
|
||||
defaultCollapsed={false}
|
||||
variant='bordered'
|
||||
className={{
|
||||
wrapper: 'w-full rounded-lg',
|
||||
body: 'p-0',
|
||||
title: 'px-2 py-1.5 font-normal text-sm',
|
||||
collapsible: 'rounded-lg',
|
||||
}}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>{renderTableContent(item)}</tbody>
|
||||
</table>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{formType != 'add_deliver' &&
|
||||
formType != 'edit_deliver' &&
|
||||
|
||||
@@ -512,13 +512,9 @@ export const FILTER_TYPE_OPTIONS = [
|
||||
];
|
||||
|
||||
export const MARKETING_TYPE_OPTIONS = [
|
||||
{
|
||||
label: 'Ayam Pullet',
|
||||
value: 'AYAM_PULLET',
|
||||
},
|
||||
{
|
||||
label: 'Ayam',
|
||||
value: 'AYAM',
|
||||
value: 'AYAM,AYAM_PULLET',
|
||||
},
|
||||
{
|
||||
label: 'Trading',
|
||||
|
||||
@@ -11,6 +11,7 @@ export type BaseProductWarehouse = {
|
||||
quantity: number;
|
||||
product: Product;
|
||||
warehouse: Warehouse;
|
||||
week?: number | null;
|
||||
};
|
||||
|
||||
export type ProductWarehouse = BaseMetadata & BaseProductWarehouse;
|
||||
|
||||
Reference in New Issue
Block a user