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:
Rivaldi A N S
2026-02-09 07:16:56 +00:00
8 changed files with 569 additions and 463 deletions
@@ -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]);
@@ -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' &&
+1 -5
View File
@@ -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',
+1
View File
@@ -11,6 +11,7 @@ export type BaseProductWarehouse = {
quantity: number;
product: Product;
warehouse: Warehouse;
week?: number | null;
};
export type ProductWarehouse = BaseMetadata & BaseProductWarehouse;