Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into feat/FE/US-161/TASK-208-212-slicing-ui-and-validation-create-purchase-request-form

This commit is contained in:
rstubryan
2025-11-25 11:30:49 +07:00
20 changed files with 548 additions and 407 deletions
+1 -1
View File
@@ -215,7 +215,7 @@ const DateInput = ({
<div
className={cn(
'input h-12 px-4 py-2 text-base font-normal leading-6 w-full rounded transition-all duration-200 flex items-center border',
'input h-12 bg-inherit px-4 py-2 text-base font-normal leading-6 w-full rounded transition-all duration-200 flex items-center border',
{
'border-error': finalIsError,
'border-success': externalValid && !finalIsError,
@@ -71,9 +71,8 @@ const InventoryAdjustmentForm = ({
Partial<InventoryAdjustmentFormValues>
>(() => {
return {
product_category_id: initialValues?.product_category?.id ?? 0,
product_id: initialValues?.product?.id ?? 0,
warehouse_id: initialValues?.warehouse?.id ?? 0,
product_id: initialValues?.product_warehouse?.product_id ?? 0,
warehouse_id: initialValues?.product_warehouse?.warehouse_id ?? 0,
product_category: undefined,
product: undefined,
warehouse: undefined,
@@ -2,7 +2,7 @@
import Button from '@/components/Button';
import CheckboxInput from '@/components/input/CheckboxInput';
import { OptionType } from '@/components/input/SelectInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import Modal, { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import ConfirmationModalWithNotes from '@/components/modal/ConfirmationModalWithNotes';
@@ -183,14 +183,18 @@ const MarketingTable = () => {
);
const hasApprovable = selectedRowsData.some(
(row) => row.latest_approval.step_number === 1
(row) =>
row.latest_approval.step_number === 1 &&
row.latest_approval.action !== 'REJECTED'
);
const hasRejectable = selectedRowsData.some(
(row) => row.latest_approval.step_number === 2
(row) =>
row.latest_approval.step_number === 1 &&
row.latest_approval.action !== 'REJECTED'
);
const disableApprove = !hasApprovable || hasRejectable;
// const disableReject = !hasRejectable || hasApprovable;
const disableApprove = !hasApprovable;
const disableReject = !hasRejectable;
const idsToProcess =
approveAction === 'APPROVED'
@@ -204,15 +208,9 @@ const MarketingTable = () => {
const approveMarketingHandler = async (notes: string) => {
let idsToProcess: number[] = [];
if (approveAction === 'APPROVED') {
idsToProcess = selectedRowsData
.filter((row) => row.latest_approval.step_number === 1)
.map((row) => row.id);
} else if (approveAction === 'REJECTED') {
idsToProcess = selectedRowsData
.filter((row) => row.latest_approval.step_number === 2)
.map((row) => row.id);
}
idsToProcess = selectedRowsData
.filter((row) => row.latest_approval.step_number === 1)
.map((row) => row.id);
if (idsToProcess.length === 0) {
toast.error(`Tidak ada data yang valid untuk di ${approveAction}.`);
@@ -263,8 +261,8 @@ const MarketingTable = () => {
});
const getRowCanSelect = (row: Row<Marketing>): boolean => {
const step = row.original.latest_approval?.step_number;
return step === 1;
const approval = row.original.latest_approval;
return approval?.step_number === 1 && approval?.action !== 'REJECTED';
};
return (
@@ -282,11 +280,6 @@ const MarketingTable = () => {
placeholder: 'Cari Sales Order',
}}
/>
<TableRowSizeSelector
value={pageSize}
onChange={pageSizeChangeHandler}
options={ROWS_OPTIONS}
/>
<div className='flex flex-row gap-2'>
<Button
color='success'
@@ -298,7 +291,7 @@ const MarketingTable = () => {
Approve
</Button>
{/* <Button
<Button
color='error'
onClick={rejectClickHandler}
className='justify-start text-sm'
@@ -306,8 +299,36 @@ const MarketingTable = () => {
>
<Icon icon='material-symbols:close' width={24} height={24} />
Reject
</Button> */}
</Button>
</div>
<TableRowSizeSelector
value={pageSize}
onChange={pageSizeChangeHandler}
options={ROWS_OPTIONS}
>
{/* select multiple product */}
<SelectInput
label='Product'
isClearable
placeholder='Pilih product'
options={[]}
isMulti
/>
{/* select status */}
<SelectInput
label='Status'
isClearable
placeholder='Pilih status'
options={[]}
/>
{/* select customer */}
<SelectInput
label='Customer'
isClearable
placeholder='Pilih customer'
options={[]}
/>
</TableRowSizeSelector>
</div>
<Table
rowSelection={rowSelection}
@@ -32,7 +32,7 @@ import { useRouter } from 'next/navigation';
import { useState } from 'react';
import toast from 'react-hot-toast';
import SalesOrderExport from '@/components/pages/marketing/pdf/SalesOrderExport';
import DeliveryOrderExport from '../pdf/DeliveryOrderExport';
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
const MarketingDetail = ({
initialValues,
@@ -71,10 +71,10 @@ const MarketingDetail = ({
confirmationModal.openModal();
};
// const rejectClickHandler = () => {
// setApprovalAction('REJECTED');
// confirmationModal.openModal();
// };
const rejectClickHandler = () => {
setApprovalAction('REJECTED');
confirmationModal.openModal();
};
const deliveryClickHandler = () => {
deliveryModal.openModal();
@@ -87,10 +87,11 @@ const MarketingDetail = ({
const confirmationModalDeleteClickHandler = async () => {
setIsLoading(true);
const res = await MarketingApi.delete(initialValues?.id as number);
setIsLoading(false);
deleteModal.closeModal();
router.push('/marketing');
toast.success(res?.message as string);
refresh?.();
setIsLoading(false);
};
const confirmationModalApproveClickHandler = async (notes: string) => {
@@ -131,32 +132,45 @@ const MarketingDetail = ({
<ApprovalSteps approvals={approvals} />
)}
<div className='flex-row flex gap-3'>
{initialValues?.latest_approval?.step_number != 3 && (
{initialValues?.latest_approval?.step_number == 1 && (
<>
<Button
color='success'
onClick={approveClickHandler}
disabled={initialValues?.latest_approval?.step_number != 1}
disabled={
initialValues?.latest_approval?.step_number == 1 &&
initialValues?.latest_approval?.action == 'REJECTED'
}
>
<Icon icon='mdi:check' width={24} height={24} />
Approve
</Button>
{/* <Button
<Button
color='error'
onClick={rejectClickHandler}
disabled={initialValues?.latest_approval?.step_number != 2}
disabled={
initialValues?.latest_approval?.step_number == 1 &&
initialValues?.latest_approval?.action == 'REJECTED'
}
>
<Icon icon='mdi:close' width={24} height={24} />
Reject
</Button> */}
</Button>
</>
)}
{initialValues?.latest_approval?.step_number == 2 && (
{initialValues?.latest_approval?.step_number != 1 && (
<Button
color='success'
href={`/marketing/add/delivery-orders?marketingId=${initialValues?.id}`}
href={
initialValues?.latest_approval?.step_number == 3
? `/marketing/detail/delivery-orders/edit?marketingId=${initialValues?.id}`
: `/marketing/add/delivery-orders?marketingId=${initialValues?.id}`
}
>
<Icon icon='mdi:truck' width={24} height={24} />
{initialValues?.latest_approval?.step_number == 3
? 'Edit '
: 'Tambah '}
Delivery Order
</Button>
)}
@@ -398,14 +412,16 @@ const MarketingDetail = ({
</Card>
)}
<div className='flex flex-row gap-3'>
<Button
color='warning'
type='button'
href={`/marketing/detail/${initialValues?.latest_approval.step_number == 3 ? 'delivery-orders' : 'sales-orders'}/edit?marketingId=${initialValues?.id}`}
>
<Icon icon='mdi:pencil' width={24} height={24} />
Edit
</Button>
{initialValues?.latest_approval?.step_number != 3 && (
<Button
color='warning'
type='button'
href={`/marketing/detail/${initialValues?.latest_approval.step_number == 3 ? 'delivery-orders' : 'sales-orders'}/edit?marketingId=${initialValues?.id}`}
>
<Icon icon='mdi:pencil' width={24} height={24} />
Edit
</Button>
)}
<Button color='error' onClick={deleteClickHandler}>
<Icon icon='mdi:delete' width={24} height={24} />
Hapus
@@ -429,7 +445,7 @@ const MarketingDetail = ({
<ConfirmationModalWithNotes
ref={confirmationModal.ref}
type={approvalAction === 'APPROVED' ? 'success' : 'error'}
text={`Apakah anda yakin ingin ${approvalAction} data penjualan ini?`}
text={`Apakah anda yakin ingin ${approvalAction == 'APPROVED' ? 'approve' : 'reject'} data penjualan ini?`}
secondaryButton={{
text: 'Tidak',
}}
@@ -49,22 +49,23 @@ export const SalesOrderSchema: Yup.ObjectSchema<SalesOrderSchemaType> =
export const DeliveryOrderSchema: Yup.ObjectSchema<DeliveryOrderSchemaType> =
Yup.object({
delivery_order: Yup.array()
.of(DeliveryOrderProductSchema)
.min(1, 'Pengiriman wajib diisi!')
.required()
.required('Pengiriman wajib diisi!')
.test(
'at-least-one-delivery-date',
'Minimal ada satu tanggal pengiriman yang harus diisi!',
(value) => {
if (!value || value.length == 0) {
return false;
}
return value.some(
(item) =>
item.delivery_date !== null &&
item.delivery_date !== undefined &&
item.delivery_date !== ''
);
'at-least-one-valid-row',
'Minimal ada satu baris pengiriman yang valid!',
function (items) {
if (!items || items.length === 0) return false;
// VALIDASI: minimal 1 item valid full
const itemSchema = DeliveryOrderProductSchema;
const hasValidItem = items.some((item) => {
if (!item) return false;
return itemSchema.isValidSync(item, { abortEarly: true });
});
return hasValidItem;
}
),
});
@@ -47,13 +47,23 @@ import SalesOrderProductForm from './repeater/sales-order/SalesOrderProductForm'
import DeliveryOrderProductTable from './table-view/DeliveryOrderProductTable';
import DeliveryOrderProductForm from './repeater/delivery-order/DeliverOrderProduct';
import { DeliveryOrderProductFormValues } from './repeater/delivery-order/DeliverOrderProduct.schema';
import DebouncedTextArea from '@/components/input/DebouncedTextArea';
const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable);
const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm);
const MemoizedDeliveryOrderProductTable = memo(DeliveryOrderProductTable);
const MemoizedDeliveryOrderProductForm = memo(DeliveryOrderProductForm);
const MarketingProductToFieldValues = (
// ================== EXTERNAL HELPER FUNCTION ==================
export interface ProductCalculationFields {
qty: string | number | undefined;
unit_price: string | number | undefined;
total_price: string | number | undefined;
avg_weight: string | number | undefined;
total_weight: string | number | undefined;
}
export const SalesProductToFieldValues = (
product: BaseSalesOrder
): SalesOrderProductFormValues => {
return {
@@ -76,8 +86,7 @@ const MarketingProductToFieldValues = (
total_price: product.total_price,
};
};
const DeliveryProductToFieldValues = (
export const DeliveryProductToFieldValues = (
salesOrders: BaseSalesOrder[],
delivery: BaseDeliveryOrder
): DeliveryOrderProductFormValues[] => {
@@ -119,8 +128,7 @@ const DeliveryProductToFieldValues = (
});
return data;
};
const mergeSOwithDO = (
export const mergeSOwithDO = (
salesOrders: SalesOrderProductFormValues[],
deliveryOrders: DeliveryOrderProductFormValues[]
): DeliveryOrderProductFormValues[] => {
@@ -135,15 +143,63 @@ const mergeSOwithDO = (
delivery_date: delivery?.delivery_date || undefined,
do_number: delivery?.do_number || undefined,
vehicle_number: delivery?.vehicle_number || so.vehicle_number,
unit_price: delivery?.unit_price ?? so.unit_price,
total_weight: delivery?.total_weight ?? so.total_weight,
qty: delivery?.qty ?? so.qty,
avg_weight: delivery?.avg_weight ?? so.avg_weight,
total_price: delivery?.total_price ?? so.total_price,
unit_price: delivery?.unit_price,
total_weight: delivery?.total_weight,
qty: delivery?.qty,
avg_weight: delivery?.avg_weight,
total_price: delivery?.total_price,
marketing_product: so, // jika ada, override
} as DeliveryOrderProductFormValues;
});
};
export const recalculate = (
field: string,
values: ProductCalculationFields
) => {
console.log('Values');
console.log(values);
const { qty, unit_price, total_price, avg_weight, total_weight } = values;
const result: Partial<ProductCalculationFields> = {};
if (field == 'unit_price' || field == 'total_price' || field == 'qty') {
if (qty && unit_price && (field == 'unit_price' || field == 'qty')) {
result.total_price = Number(qty) * Number(unit_price);
} else if (qty && total_price && field == 'total_price') {
result.unit_price = Number(total_price) / Number(qty);
}
}
if (field == 'avg_weight' || field == 'total_weight' || field == 'qty') {
if (qty && avg_weight && (field == 'avg_weight' || field == 'qty')) {
result.total_weight = Number(qty) * Number(avg_weight);
} else if (qty && total_weight && field == 'total_weight') {
result.avg_weight = Number(total_weight) / Number(qty);
}
}
console.log('Result');
console.log(result);
return result;
};
export const getSubmitField = (values: ProductCalculationFields) => {
const { qty, unit_price, total_price, avg_weight, total_weight } = values;
// Harga logic
if (qty && unit_price && !total_price) {
return 'unit_price';
}
if (qty && total_price && !unit_price) {
return 'total_price';
}
// Bobot logic
if (qty && avg_weight && !total_weight) {
return 'avg_weight';
}
if (qty && total_weight && !avg_weight) {
return 'total_weight';
}
// Tidak ada yang perlu dihitung
return '';
};
const MarketingForm = ({
formType = 'add',
@@ -162,19 +218,21 @@ const MarketingForm = ({
useState<SalesOrderProductFormValues | null>(null);
const [selectedDeliveryProduct, setSelectedDeliveryProduct] =
useState<DeliveryOrderProductFormValues | null>(null);
const [deliveryFormState, setDeliveryFormState] = useState<'add' | 'edit'>(
'add'
);
const [deliveryOrderValues, setDeliveryOrderValues] = useState<
DeliveryOrderProductFormValues[]
>(
mergeSOwithDO(
initialValues?.sales_order?.map(MarketingProductToFieldValues) ?? [],
initialValues?.sales_order?.map(SalesProductToFieldValues) ?? [],
initialValues?.delivery_order?.flatMap((delivery) =>
DeliveryProductToFieldValues(initialValues.sales_order, delivery)
) ?? []
)
);
// Repeater Props
// ================== REPEATER ==================
const addSOModal = useModal();
const addDOModal = useModal();
const [rowSOSelection, setRowSOSelection] = useState<Record<string, boolean>>(
@@ -183,19 +241,14 @@ const MarketingForm = ({
const selectedRowSOIds = Object.keys(rowSOSelection).map((item) =>
parseInt(item)
);
const [rowDOSelection, setRowDOSelection] = useState<Record<string, boolean>>(
{}
);
const selectedRowDOIds = Object.keys(rowDOSelection).map((item) =>
parseInt(item)
);
// End Repeater Props
// ================== FETCH OPTIONS ==================
const {
options: customerOptions,
isLoadingOptions: isLoadingCustomerOptions,
} = useSelect<Customer>(CustomerApi.basePath, 'id', 'name');
// ================== SETUP FORMIK ==================
const formikInitialValues = useMemo<
SalesOrderFormValues & DeliveryOrderFormValues
>(() => {
@@ -212,17 +265,16 @@ const MarketingForm = ({
: null,
sales_order:
initialValues?.sales_order?.map((product) =>
MarketingProductToFieldValues(product)
SalesProductToFieldValues(product)
) ?? [],
delivery_order: mergeSOwithDO(
initialValues?.sales_order?.map(MarketingProductToFieldValues) ?? [],
initialValues?.sales_order?.map(SalesProductToFieldValues) ?? [],
initialValues?.delivery_order?.flatMap((delivery) =>
DeliveryProductToFieldValues(initialValues.sales_order, delivery)
) ?? []
),
};
}, [initialValues]);
const formik = useFormik<SalesOrderFormValues & DeliveryOrderFormValues>({
enableReinitialize: true,
initialValues: formikInitialValues,
@@ -297,14 +349,7 @@ const MarketingForm = ({
},
});
const grandTotal = useMemo(() => {
return formik.values.sales_order.reduce(
(total, product) =>
total + parseFloat((product.total_price as string) || '0'),
0
);
}, [formik.values.sales_order]);
// ================== FORM REPEATER HANDLER ==================
const createMarketingHandler = async (values: CreateSalesOrderPayload) => {
setIsLoading(true);
console.log(values);
@@ -334,7 +379,6 @@ const MarketingForm = ({
}
setIsLoading(false);
};
const createDeliveryHandler = async (values: CreateDeliveryOrderPayload) => {
setIsLoading(true);
console.log(initialValues?.id);
@@ -350,9 +394,7 @@ const MarketingForm = ({
)
) ?? []
);
router.push(
`/marketing/detail/delivery-orders/edit?marketingId=${initialValues?.id}`
);
router.push(`/marketing/detail?marketingId=${initialValues?.id}`);
}
if (isResponseError(createDeliveryRes)) {
console.log(createDeliveryRes);
@@ -360,7 +402,6 @@ const MarketingForm = ({
}
setIsLoading(false);
};
const updateDeliveryHandler = async (values: UpdateDeliveryOrderPayload) => {
setIsLoading(true);
console.log(initialValues?.id);
@@ -371,15 +412,18 @@ const MarketingForm = ({
if (isResponseSuccess(updateDeliveryRes)) {
console.log(updateDeliveryRes);
toast.success(updateDeliveryRes?.message as string);
// router.push(`/marketing/detail?marketingId=${initialValues?.id}`);
setDeliveryOrderValues(
updateDeliveryRes.data?.delivery_order?.flatMap((delivery) =>
DeliveryProductToFieldValues(
updateDeliveryRes.data?.sales_order,
delivery
)
) ?? []
mergeSOwithDO(
formik.values.sales_order,
updateDeliveryRes.data?.delivery_order?.flatMap((delivery) =>
DeliveryProductToFieldValues(
updateDeliveryRes.data?.sales_order,
delivery
)
) ?? []
)
);
router.push(`/marketing/detail?marketingId=${initialValues?.id}`);
}
if (isResponseError(updateDeliveryRes)) {
console.log(updateDeliveryRes);
@@ -388,6 +432,7 @@ const MarketingForm = ({
setIsLoading(false);
};
// ================== MARKETING HANDLER ==================
const deleteMarketingHandler = async () => {
setIsLoading(true);
console.log(initialValues?.id);
@@ -404,28 +449,27 @@ const MarketingForm = ({
}
setIsLoading(false);
deleteModal.closeModal();
router.push('/marketing/sales-orders');
router.push('/marketing');
};
const handleChangeCustomer = useCallback(
(val: OptionType | OptionType[] | null) => {
formik.setFieldValue('customer_id', (val as OptionType)?.value);
formik.setFieldValue('customer', val as OptionType);
},
[formik]
[]
);
const handleDelete = useCallback(() => {
deleteModal.openModal();
}, [deleteModal]);
// Repeater Handle
const handleDeleteSO = useCallback(
(id: number) => {
const currentProducts = formik.values.sales_order;
formik.setFieldValue(
'sales_order',
currentProducts.filter((p) => p.id != id)
);
},
[formik]
);
// ================== SALES ORDER HANDLER ==================
const handleDeleteSO = useCallback((id: number) => {
const currentProducts = formik.values.sales_order;
formik.setFieldValue(
'sales_order',
currentProducts.filter((p) => p.id != id)
);
}, []);
const handleBulkDeleteSO = useCallback(() => {
const currentProducts = formik.values.sales_order;
formik.setFieldValue(
@@ -435,10 +479,7 @@ const MarketingForm = ({
)
);
setRowSOSelection({});
}, [formik, selectedRowSOIds]);
const handleDelete = useCallback(() => {
deleteModal.openModal();
}, [deleteModal]);
}, [selectedRowSOIds]);
const handleAddSOClick = useCallback(() => {
setSelectedMarketingProduct(null);
addSOModal.openModal();
@@ -446,48 +487,54 @@ const MarketingForm = ({
const handleAddSubmitSO = useCallback(
async (values: SalesOrderProductFormValues) => {
const currentProducts = formik.values.sales_order;
const newValues = {
...values,
id: values.id ?? Date.now(),
};
formik.setFieldValue('sales_order', [...currentProducts, newValues]);
const existingIndex = currentProducts.findIndex(
(item) =>
item.kandang_id === newValues.kandang_id &&
item.product_warehouse_id === newValues.product_warehouse_id
);
let updatedProducts = [];
if (existingIndex !== -1) {
// Overwrite
updatedProducts = currentProducts.map((item, index) =>
index === existingIndex ? newValues : item
);
} else {
// Add new item
updatedProducts = [...currentProducts, newValues];
}
formik.setFieldValue('sales_order', updatedProducts);
addSOModal.closeModal();
},
[formik, addSOModal]
[addSOModal]
);
const handleDeleteDO = useCallback(
(id: number) => {
const currentProducts = formik.values.delivery_order;
setDeliveryOrderValues((prev) => prev.filter((p) => p.id !== id));
},
[formik]
);
// ================== DELIVERY ORDER HANDLER ==================
const handleEditDO = useCallback(
(id: number) => {
(id: number, values?: DeliveryOrderProductFormValues) => {
setDeliveryFormState('edit');
const currentProducts = formik.values.delivery_order.find(
(product) => product.id == id
);
setSelectedDeliveryProduct(currentProducts ?? null);
setSelectedDeliveryProduct(values ?? currentProducts ?? null);
addDOModal.openModal();
},
[formik]
[addDOModal]
);
const handleBulkDeleteDO = useCallback(() => {
setDeliveryOrderValues((prev) =>
prev.filter((product) => !selectedRowDOIds.includes(product.id ?? -1))
);
setRowDOSelection({});
}, [formik, selectedRowDOIds]);
const handleAddDOClick = useCallback(() => {
setDeliveryFormState('add');
setSelectedDeliveryProduct(null);
addDOModal.openModal();
}, [addDOModal]);
const handleAddSubmitDO = useCallback(
async (values: DeliveryOrderProductFormValues) => {
const newValues = {
@@ -496,23 +543,10 @@ const MarketingForm = ({
};
setDeliveryOrderValues((prev) => [...prev, newValues]);
addDOModal.closeModal();
setSelectedDeliveryProduct(null);
},
[formik, addDOModal]
);
const handleInputDate = useCallback(
(newData: DeliveryOrderProductFormValues) => {
setDeliveryOrderValues((prev) => {
return prev.map((item) => {
if (item.marketing_product_id == newData.marketing_product_id) {
return newData;
}
return item;
});
});
},
[]
[addDOModal]
);
const handleUpdateDO = useCallback(
async (id: number, values: DeliveryOrderProductFormValues) => {
@@ -521,12 +555,11 @@ const MarketingForm = ({
product.id === id ? { ...product, ...values } : product
)
);
setSelectedDeliveryProduct(null);
addDOModal.closeModal();
setSelectedDeliveryProduct(null);
},
[formik, addDOModal]
[addDOModal]
);
// End Repeater Handle
const memoSalesOrder = formik.values.sales_order;
@@ -534,6 +567,14 @@ const MarketingForm = ({
formik.setFieldValue('delivery_order', deliveryOrderValues);
}, [deliveryOrderValues, initialValues]);
const grandTotal = useMemo(() => {
return memoSalesOrder.reduce(
(total, product) =>
total + parseFloat((product.total_price as string) || '0'),
0
);
}, [memoSalesOrder]);
return (
<>
<form
@@ -545,6 +586,7 @@ const MarketingForm = ({
title={`${formType == 'add' || formType == 'add_deliver' ? 'Tambah' : 'Edit'} ${formType === 'add_deliver' || formType === 'edit_deliver' ? 'Delivery' : 'Sales'} Order`}
backUrl='/marketing'
/>
{/* Input Cutomer And Date */}
<Card
title='Informasi Order'
className={{
@@ -580,28 +622,27 @@ const MarketingForm = ({
/>
</div>
</Card>
{(formType == 'add' || formType == 'edit') && (
<Card
title='Informasi Produk'
className={{
wrapper: 'bg-white w-full',
}}
>
{/* <div className='text-blue-500'>{JSON.stringify(initialValues)}</div>
<div className='text-green-500'>{JSON.stringify(formik.values)}</div>
<div className='text-red-500'>{JSON.stringify(formik.errors)}</div> */}
<MemoizedSalesOrderProductTable
formType={formType}
data={memoSalesOrder}
rowSelection={rowSOSelection}
setRowSelection={setRowSOSelection}
selectedRowIds={selectedRowSOIds}
onDelete={handleDeleteSO}
onBulkDelete={handleBulkDeleteSO}
onAddProductClick={handleAddSOClick}
/>
</Card>
)}
{/* Input Table Repeater Sales Order */}
<Card
title='Informasi Produk'
className={{
wrapper: 'bg-white w-full',
}}
>
<MemoizedSalesOrderProductTable
formType={formType}
data={memoSalesOrder}
rowSelection={rowSOSelection}
setRowSelection={setRowSOSelection}
selectedRowIds={selectedRowSOIds}
onDelete={handleDeleteSO}
onBulkDelete={handleBulkDeleteSO}
onAddProductClick={handleAddSOClick}
/>
</Card>
{/* Input Table Repeater Delivery Order */}
{(formType == 'add_deliver' || formType == 'edit_deliver') &&
initialValues?.sales_order &&
initialValues?.sales_order.length > 0 && (
@@ -611,29 +652,24 @@ const MarketingForm = ({
wrapper: 'bg-white w-full',
}}
>
{/* {JSON.stringify(memoSalesOrder)} */}
{/* <small>{JSON.stringify(memoDeliveryOrder)}</small> */}
{/* <small className='block text-error'>
{/* <div className='text-blue-500'>
{JSON.stringify(formik.values)}
</div>
<div className='text-red-500'>
{JSON.stringify(formik.errors)}
</small> */}
</div> */}
<MemoizedDeliveryOrderProductTable
formType={formType}
data={deliveryOrderValues}
salesOrder={memoSalesOrder}
rowSelection={rowDOSelection}
setRowSelection={setRowDOSelection}
selectedRowIds={selectedRowDOIds}
onDelete={handleDeleteDO}
onEdit={handleEditDO}
onBulkDelete={handleBulkDeleteDO}
onAddProductClick={handleAddDOClick}
onInputDate={handleInputDate}
/>
</Card>
)}
{/* Input Notes */}
<div className='grid grid-cols-2 gap-3'>
<TextArea
<DebouncedTextArea
required
name='notes'
label='Catatan'
@@ -652,6 +688,8 @@ const MarketingForm = ({
</span>
</div>
</div>
{/* Form Actions */}
<div className='flex flex-row items-start justify-center gap-2 mt-4'>
<Button type='reset' color='warning' disabled={formik.isSubmitting}>
Reset
@@ -665,6 +703,8 @@ const MarketingForm = ({
</Button>
</div>
</form>
{/* Actions button */}
{formType == 'edit' && (
<div className='flex flex-row justify-start'>
<Button
@@ -678,6 +718,8 @@ const MarketingForm = ({
</Button>
</div>
)}
{/* Modals */}
<Modal
ref={addSOModal.ref}
closeOnBackdrop
@@ -701,6 +743,7 @@ const MarketingForm = ({
<MemoizedSalesOrderProductForm
onSubmitForm={handleAddSubmitSO}
initialValues={selectedMarketingProduct ?? undefined}
exisitingValues={memoSalesOrder}
/>
</div>
</div>
@@ -728,7 +771,9 @@ const MarketingForm = ({
</div>
<div>
<MemoizedDeliveryOrderProductForm
formState={deliveryFormState}
salesOrders={initialValues?.sales_order ?? []}
exisitingValues={deliveryOrderValues}
onSubmitForm={handleAddSubmitDO}
initialValues={selectedDeliveryProduct ?? undefined}
onUpdateForm={handleUpdateDO}
@@ -1,9 +1,5 @@
import * as Yup from 'yup';
import {
SalesOrderProductFormValues,
SalesOrderProductSchema,
} from '../sales-order/SalesOrderProduct.schema';
import { de } from 'react-day-picker/locale';
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
type DeliveryOrderProductSchemaType = {
id?: number | undefined;
@@ -42,22 +38,10 @@ export const DeliveryOrderProductSchema: Yup.ObjectSchema<DeliveryOrderProductSc
.min(1, 'Total Penjualan wajib diisi!')
.required('Total Penjualan wajib diisi!'),
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
delivery_date: Yup.string()
.required('Tanggal Pengiriman wajib diisi!')
.nullable()
.optional(),
delivery_date: Yup.string().required('Tanggal Pengiriman wajib diisi!'),
do_number: Yup.string().nullable().optional(),
});
export type DeliveryOrderProductFormValues = Yup.InferType<
typeof DeliveryOrderProductSchema
>;
// "marketing_product_id": 3,
// "qty": 20,
// "unit_price": 1000,
// "avg_weight": 1.1,
// "total_weight": 220,
// "total_price": 20000,
// "delivery_date": "2025-11-09",
// "vehicle_number": "D 4321 XXX"
@@ -10,20 +10,24 @@ import NumberInput from '@/components/input/NumberInput';
import PatternInput from '@/components/input/PatternInput';
import { formatVechicleNumber } from '@/lib/helper';
import DateInput from '@/components/input/DateInput';
import TextInput from '@/components/input/TextInput';
import SelectInput, { OptionType } from '@/components/input/SelectInput';
import { SalesOrderProductFormValues } from '../sales-order/SalesOrderProduct.schema';
import { BaseSalesOrder } from '@/types/api/marketing/marketing';
import Badge from '@/components/Badge';
import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm';
import * as Yup from 'yup';
const DeliveryOrderProductForm = ({
formState,
salesOrders,
initialValues,
exisitingValues,
onSubmitForm,
onUpdateForm,
}: {
formState: 'add' | 'edit';
salesOrders: BaseSalesOrder[];
initialValues?: DeliveryOrderProductFormValues;
exisitingValues?: DeliveryOrderProductFormValues[];
onSubmitForm?: (value: DeliveryOrderProductFormValues) => Promise<void>;
onUpdateForm?: (
id: number,
@@ -34,13 +38,18 @@ const DeliveryOrderProductForm = ({
const [selectedProduct, setSelectedProduct] = useState<OptionType | null>(
null
);
const [currentInput, setCurrentInput] = useState<string>('');
const salesOrder = salesOrders.find(
(item) => item.id === initialValues?.marketing_product_id
);
const formik = useFormik<DeliveryOrderProductFormValues>({
enableReinitialize: true,
initialValues: {
delivery_date: initialValues?.delivery_date || undefined,
vehicle_number: initialValues?.vehicle_number || undefined,
marketing_product_id: initialValues?.marketing_product_id || undefined,
marketing_product_id:
salesOrder?.id || initialValues?.marketing_product_id || undefined,
unit_price: initialValues?.unit_price || undefined,
total_weight: initialValues?.total_weight || undefined,
qty: initialValues?.qty || undefined,
@@ -48,15 +57,33 @@ const DeliveryOrderProductForm = ({
total_price: initialValues?.total_price || undefined,
marketing_product: initialValues?.marketing_product || undefined,
},
validationSchema: DeliveryOrderProductSchema,
isInitialValid: false,
validationSchema: Yup.object().shape({
...DeliveryOrderProductSchema.fields,
qty: Yup.lazy((_, context) => {
// values diambil aman dari context
const { parent } = context;
const mpId = parent?.marketing_product_id;
const selectedSO = salesOrders.find((item) => item.id === mpId);
const maxQty = selectedSO?.qty ?? Infinity;
return Yup.number()
.min(1, 'Kuantitas wajib diisi!')
.max(maxQty, `Maksimal kuantitas adalah ${maxQty}`)
.required('Kuantitas wajib diisi!');
}),
}),
validateOnChange: true,
validateOnBlur: true,
validateOnChange: false,
onSubmit: async (values) => {
setFormErrorMessage('');
if (initialValues?.id) {
await onUpdateForm?.(initialValues.id, values);
} else {
await onSubmitForm?.(values);
await onUpdateForm?.(values.marketing_product_id as number, values);
}
handleResetForm();
},
@@ -81,6 +108,7 @@ const DeliveryOrderProductForm = ({
};
const handleBlurField = (field: string) => {
setCurrentInput(field);
const { qty, unit_price, total_price, avg_weight, total_weight } =
formik.values;
@@ -101,47 +129,37 @@ const DeliveryOrderProductForm = ({
}
};
const MarketingProductToFieldValues = (
product: BaseSalesOrder
): SalesOrderProductFormValues => {
return {
id: product.id,
vehicle_number: product.vehicle_number,
kandang_id: product.product_warehouse.warehouse.id,
kandang: {
value: product.product_warehouse.warehouse.id,
label: product.product_warehouse.warehouse.name,
},
product_warehouse: {
value: product.product_warehouse.id,
label: product.product_warehouse.product.name,
},
product_warehouse_id: product.product_warehouse.id,
unit_price: product.unit_price,
total_weight: product.total_weight,
qty: product.qty,
avg_weight: product.avg_weight,
total_price: product.total_price,
};
};
const options = salesOrders.map((item) => ({
value: item.id,
label: `${item.product_warehouse.product.name} - ${item.product_warehouse.warehouse.name}`,
}));
const options = exisitingValues
?.map((item) => {
if (!Boolean(item.qty)) {
return {
value: item.id,
label: `${item.marketing_product?.product_warehouse?.label} - ${item.marketing_product?.kandang?.label}`,
} as OptionType;
} else {
return null;
}
})
?.filter((item) => item != null) as OptionType[];
const { setValues: setFormikValues } = formik;
useEffect(() => {
if (initialValues) {
setFormikValues(initialValues);
const value = salesOrders.find(
(item) => item.id === initialValues.marketing_product_id
);
setSelectedProduct({
value: value?.id,
label: `${value?.product_warehouse.product.name} - ${value?.product_warehouse.warehouse.name}`,
} as OptionType);
if (!Boolean(initialValues.qty)) {
handleResetForm();
} else {
setFormikValues(initialValues);
// const value = exisitingValues?.find(
// (item) => item.id === initialValues?.id
// );
if (initialValues?.marketing_product_id) {
setSelectedProduct({
value: initialValues?.id,
label: `${initialValues?.marketing_product?.product_warehouse?.label} - ${initialValues?.marketing_product?.kandang?.label}`,
} as OptionType);
}
}
}
}, [initialValues]);
@@ -149,17 +167,21 @@ const DeliveryOrderProductForm = ({
<>
<form
className='size-full'
onSubmit={formik.handleSubmit}
onSubmit={(e) => {
e.preventDefault();
handleBlurField(currentInput);
formik.handleSubmit(e);
}}
onReset={handleResetForm}
>
{/* <small className='block text-blue-500'>
{JSON.stringify(initialValues)}
</small>
<small className='block text-red-500'>
{JSON.stringify(formik.errors)}
{JSON.stringify(exisitingValues)}
</small>
<small className='block text-emerald-500'>
{JSON.stringify(formik.values)}
</small> */}
{/* <small className='block text-red-500'>
{JSON.stringify(formik.errors)}
</small>
<div className='hidden'>
{JSON.stringify(formik.values.marketing_product)}
@@ -176,14 +198,14 @@ const DeliveryOrderProductForm = ({
options={options}
label='Produk'
placeholder='Pilih Produk'
isDisabled
isDisabled={formState == 'edit'}
value={
selectedProduct
? ({
value: selectedProduct?.value,
label: salesOrders.find(
label: exisitingValues?.find(
(item) => item.id === selectedProduct?.value
)?.product_warehouse.product.name,
)?.marketing_product?.product_warehouse?.label,
} as OptionType)
: null
}
@@ -191,7 +213,7 @@ const DeliveryOrderProductForm = ({
const selected = value as OptionType;
setSelectedProduct(selected);
const so = salesOrders.find(
const so = salesOrders?.find(
(item) => item.id === selected?.value
);
if (!so) {
@@ -212,7 +234,7 @@ const DeliveryOrderProductForm = ({
formik.setValues({
...formik.values,
marketing_product_id: selected.value as number,
marketing_product: MarketingProductToFieldValues(so),
marketing_product: SalesProductToFieldValues(so),
qty: formik.values.qty || so.qty,
unit_price: so.unit_price,
total_price: so.total_price,
@@ -230,9 +252,9 @@ const DeliveryOrderProductForm = ({
className={{ badge: 'whitespace-nowrap font-semibold' }}
>
{
salesOrders.find(
exisitingValues?.find(
(item) => item.id === selectedProduct?.value
)?.product_warehouse?.warehouse?.name
)?.marketing_product?.kandang?.label
}
</Badge>
)
@@ -254,6 +276,9 @@ const DeliveryOrderProductForm = ({
}
errorMessage={formik.errors.delivery_date}
placeholder='Pilih Tanggal'
className={{
inputWrapper: 'bg-white',
}}
required
/>
@@ -278,7 +303,10 @@ const DeliveryOrderProductForm = ({
label='Kuantitas'
name='qty'
value={formik.values.qty}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('qty')}
isError={Boolean(formik.errors.qty)}
errorMessage={formik.errors.qty}
@@ -290,7 +318,10 @@ const DeliveryOrderProductForm = ({
label='Avg. Bobot (Kg)'
name='avg_weight'
value={formik.values.avg_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('avg_weight')}
isError={Boolean(formik.errors.avg_weight)}
errorMessage={formik.errors.avg_weight}
@@ -302,7 +333,10 @@ const DeliveryOrderProductForm = ({
label='Harga Satuan (Rp)'
name='unit_price'
value={formik.values.unit_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('unit_price')}
isError={Boolean(formik.errors.unit_price)}
errorMessage={formik.errors.unit_price}
@@ -314,7 +348,10 @@ const DeliveryOrderProductForm = ({
label='Total Bobot (Kg)'
name='total_weight'
value={formik.values.total_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_weight')}
isError={Boolean(formik.errors.total_weight)}
errorMessage={formik.errors.total_weight}
@@ -326,7 +363,10 @@ const DeliveryOrderProductForm = ({
label='Total Penjualan (Rp)'
name='total_price'
value={formik.values.total_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_price')}
isError={Boolean(formik.errors.total_price)}
errorMessage={formik.errors.total_price}
@@ -5,7 +5,7 @@ import {
SalesOrderProductFormValues,
SalesOrderProductSchema,
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
import { RefObject, useState } from 'react';
import { RefObject, useMemo, useState } from 'react';
import SelectInput, {
OptionType,
useSelect,
@@ -23,13 +23,16 @@ import Alert from '@/components/Alert';
const SalesOrderProductForm = ({
initialValues,
exisitingValues,
onSubmitForm,
}: {
initialValues?: SalesOrderProductFormValues;
exisitingValues?: SalesOrderProductFormValues[];
modalRef?: RefObject<HTMLDialogElement | null>;
onSubmitForm?: (value: SalesOrderProductFormValues) => Promise<void>;
}) => {
const [formErrorMessage, setFormErrorMessage] = useState('');
const [currentInput, setCurrentInput] = useState<string>('');
const formik = useFormik<SalesOrderProductFormValues>({
enableReinitialize: true,
@@ -51,6 +54,8 @@ const SalesOrderProductForm = ({
onSubmitForm?.(values);
handleResetForm();
},
validateOnBlur: true,
isInitialValid: false,
});
const {
@@ -72,6 +77,15 @@ const SalesOrderProductForm = ({
}
);
const productOptionsFiltered = useMemo(() => {
return warehouseSourceOptions.filter(
(product) =>
!exisitingValues
?.map((item) => item.product_warehouse_id)
.includes(product.value)
);
}, [warehouseSourceOptions, exisitingValues]);
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldValue('kandang', val as OptionType);
formik.setFieldValue('kandang_id', (val as OptionType)?.value);
@@ -115,6 +129,7 @@ const SalesOrderProductForm = ({
};
const handleBlurField = (field: string) => {
setCurrentInput(field);
const { qty, unit_price, total_price, avg_weight, total_weight } =
formik.values;
@@ -151,7 +166,11 @@ const SalesOrderProductForm = ({
<>
<form
className='size-full'
onSubmit={formik.handleSubmit}
onSubmit={(e) => {
e.preventDefault();
handleBlurField(currentInput);
formik.handleSubmit(e);
}}
onReset={handleResetForm}
>
{formErrorMessage && (
@@ -200,12 +219,18 @@ const SalesOrderProductForm = ({
<SelectInput
required
label='Produk'
options={warehouseSourceOptions}
options={productOptionsFiltered}
isLoading={isLoadingWarehouseSourceOptions}
value={formik.values.product_warehouse}
onChange={warehouseChangeHandler}
isClearable
placeholder='Pilih Kandang Terlebih Dahulu'
placeholder={
formik.values.kandang_id
? productOptionsFiltered.length == 0
? 'Tidak ada produk yang tersedia'
: 'Pilih produk'
: 'Pilih Kandang Terlebih Dahulu'
}
isDisabled={!formik.values.kandang_id}
isError={
formik.touched.product_warehouse_id &&
@@ -218,7 +243,10 @@ const SalesOrderProductForm = ({
label='Kuantitas'
name='qty'
value={formik.values.qty}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('qty')}
isError={formik.touched.qty && Boolean(formik.errors.qty)}
errorMessage={formik.errors.qty}
@@ -229,7 +257,10 @@ const SalesOrderProductForm = ({
label='Avg. Bobot (Kg)'
name='avg_weight'
value={formik.values.avg_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('avg_weight')}
isError={
formik.touched.avg_weight && Boolean(formik.errors.avg_weight)
@@ -242,7 +273,10 @@ const SalesOrderProductForm = ({
label='Harga Satuan (Rp)'
name='unit_price'
value={formik.values.unit_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('unit_price')}
isError={
formik.touched.unit_price && Boolean(formik.errors.unit_price)
@@ -255,7 +289,10 @@ const SalesOrderProductForm = ({
label='Total Bobot (Kg)'
name='total_weight'
value={formik.values.total_weight}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_weight')}
isError={
formik.touched.total_weight && Boolean(formik.errors.total_weight)
@@ -268,7 +305,10 @@ const SalesOrderProductForm = ({
label='Total Penjualan (Rp)'
name='total_price'
value={formik.values.total_price}
onChange={formik.handleChange}
onChange={(e) => {
formik.handleChange(e);
setCurrentInput(e.target.name);
}}
onBlur={() => handleBlurField('total_price')}
isError={
formik.touched.total_price && Boolean(formik.errors.total_price)
@@ -1,10 +1,9 @@
import Table from '@/components/Table';
import { DeliveryOrderProductFormValues } from '../repeater/delivery-order/DeliverOrderProduct.schema';
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
import Button from '@/components/Button';
import { Icon } from '@iconify/react';
import * as TanStack from '@tanstack/react-table';
import { useMemo, useRef } from 'react';
import CheckboxInput from '@/components/input/CheckboxInput';
import {
cn,
formatCurrency,
@@ -12,49 +11,24 @@ import {
formatNumber,
formatVechicleNumber,
} from '@/lib/helper';
import { SalesOrderProductFormValues } from '../repeater/sales-order/SalesOrderProduct.schema';
import DateInput from '@/components/input/DateInput';
type DeliveryOrderProductTableProps = {
data: DeliveryOrderProductFormValues[];
salesOrder: SalesOrderProductFormValues[];
formType?: 'add' | 'edit' | 'add_deliver' | 'edit_deliver';
rowSelection: Record<string, boolean>;
setRowSelection: React.Dispatch<
React.SetStateAction<Record<string, boolean>>
>;
selectedRowIds: number[];
onDelete: (id: number) => void;
onEdit: (id: number) => void;
onBulkDelete: () => void;
onAddProductClick: () => void;
onInputDate: (data: DeliveryOrderProductFormValues) => void;
};
const DeliveryOrderProductTable = ({
data,
salesOrder,
formType,
rowSelection,
setRowSelection,
selectedRowIds,
onDelete,
onEdit,
onBulkDelete,
onAddProductClick,
onInputDate,
}: DeliveryOrderProductTableProps) => {
const onDeleteRef = useRef(onDelete);
const onEditRef = useRef(onDelete);
onDeleteRef.current = onDelete;
const onEditRef = useRef(onEdit);
onEditRef.current = onEdit;
const canAddData = salesOrder.reduce((acc, curr) => {
const deliveredQty = data.filter(
(deliveryItem) => deliveryItem.marketing_product_id == curr.id
);
return acc && deliveredQty.length != salesOrder.length;
}, true);
const canAddData = data.filter((item) => !Boolean(item.qty));
const columns = useMemo(() => {
const cols = [
@@ -93,54 +67,23 @@ const DeliveryOrderProductTable = ({
{
accessorFn: (row: DeliveryOrderProductFormValues) => row.do_number,
header: 'No. Pengiriman',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) => <div>{props.row.original.do_number}</div>,
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
row.delivery_date
? formatDate(row.delivery_date as string, 'DD MMM YYYY')
: '-',
header: 'Tanggal Delivery',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) => (
<>
{formType == 'add_deliver' && (
<DateInput
name={`delivery_date_${props.row.original.marketing_product_id}`}
className={{
input: 'p-0',
inputWrapper: 'py-1 px-3 h-fit w-fit bg-white',
wrapper: 'p-0',
}}
value={
props.row.original.delivery_date
? formatDate(props.row.original.delivery_date, 'yyyy-MM-DD')
: undefined
}
onChange={(val) => {
onInputDate({
...props.row.original,
delivery_date: val.target.value,
});
}}
/>
)}
{formType == 'edit_deliver' &&
formatDate(
props.row.original.delivery_date as string,
'DD MMM YYYY'
)}
{props.row.original.do_number ? props.row.original.do_number : '-'}
</>
),
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
formatVechicleNumber(row.vehicle_number as string),
accessorFn: (row: DeliveryOrderProductFormValues) => row.vehicle_number,
header: 'No. Polisi',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) =>
props.row.original.vehicle_number
? formatVechicleNumber(props.row.original.vehicle_number as string)
: '-',
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
@@ -154,29 +97,77 @@ const DeliveryOrderProductTable = ({
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
formatCurrency(parseFloat(row.unit_price as string)),
row.delivery_date
? formatDate(row.delivery_date as string, 'DD MMM YYYY')
: '-',
header: 'Tanggal Delivery',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) =>
props.row.original.delivery_date
? formatDate(
props.row.original.delivery_date as string,
'DD MMM YYYY'
)
: '-',
},
{
accessorFn: (row: DeliveryOrderProductFormValues) => row.unit_price,
header: 'Harga Satuan (Rp)',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) =>
props.row.original.unit_price
? formatCurrency(
parseFloat(props.row.original.unit_price as string)
)
: '-',
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
formatNumber(parseFloat(row.total_weight as string)),
accessorFn: (row: DeliveryOrderProductFormValues) => row.total_weight,
header: 'Total Bobot (Kg)',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) =>
props.row.original.total_weight
? formatNumber(
parseFloat(props.row.original.total_weight as string)
)
: '-',
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
formatNumber(parseFloat(row.qty as string)),
accessorFn: (row: DeliveryOrderProductFormValues) => row.qty,
header: 'Kuantitas',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) =>
props.row.original.qty
? formatNumber(parseFloat(props.row.original.qty as string))
: '-',
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
formatNumber(parseFloat(row.avg_weight as string)),
accessorFn: (row: DeliveryOrderProductFormValues) => row.avg_weight,
header: 'Avg. Bobot (Kg)',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) =>
props.row.original.avg_weight
? formatNumber(parseFloat(props.row.original.avg_weight as string))
: '-',
},
{
accessorFn: (row: DeliveryOrderProductFormValues) =>
formatCurrency(parseFloat(row.total_price as string)),
accessorFn: (row: DeliveryOrderProductFormValues) => row.total_price,
header: 'Total Penjualan (Rp)',
cell: (
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
) =>
props.row.original.total_price
? formatCurrency(
parseFloat(props.row.original.total_price as string)
)
: '-',
},
{
header: 'Aksi',
cell: (
@@ -184,44 +175,45 @@ const DeliveryOrderProductTable = ({
) => (
<div className='flex flex-row gap-1 items-center justify-end h-full mt-2'>
<>
<Button
color='warning'
className='px-2 py-1 text-sm'
onClick={() =>
onEditRef.current(props.row.original.id as number)
}
type='button'
>
<Icon icon='mdi:edit' 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} />
</Button> */}
{props.row.original.qty && (
<Button
color='warning'
className='px-2 py-1 text-sm'
onClick={() =>
onEditRef.current(props.row.original.id as number)
}
type='button'
>
<Icon icon='mdi:edit' width={16} height={16} /> Edit
</Button>
)}
{!props.row.original.qty && '-'}
{/* {formType == 'add_deliver' && (
<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} />
</Button>
)} */}
</>
</div>
),
},
];
if (formType == 'add_deliver') {
return cols.filter(
(col) => col.header != 'Aksi' && col.header != 'No. Pengiriman'
);
return cols.filter((col) => col.header != 'No. Pengiriman');
}
return cols;
}, [formType, onInputDate, onEditRef]);
}, [formType, onEditRef]);
return (
<>
<Table<DeliveryOrderProductFormValues>
rowSelection={rowSelection}
setRowSelection={setRowSelection}
data={data}
columns={columns}
className={{
@@ -246,17 +238,17 @@ const DeliveryOrderProductTable = ({
}
/>
<div className='flex flex-row gap-3 mt-3'>
{/* <Button
<Button
type='button'
variant='outline'
className='justify-start w-fit py-1 text-sm'
onClick={onAddProductClick}
// disabled={!canAddData}
disabled={!canAddData}
>
<Icon icon='mdi:plus' width={16} height={16} />
Tambah Pengiriman
</Button> */}
{selectedRowIds.length > 0 && (
</Button>
{/* {selectedRowIds.length > 0 && (
<Button
type='button'
variant='outline'
@@ -271,7 +263,7 @@ const DeliveryOrderProductTable = ({
: ''}{' '}
Pengiriman
</Button>
)}
)} */}
</div>
</>
);
@@ -16,7 +16,7 @@ import CheckboxInput from '@/components/input/CheckboxInput';
type SalesOrderProductTableProps = {
data: SalesOrderProductFormValues[];
formType?: 'add' | 'edit' | 'deliver';
formType: 'add' | 'edit' | 'add_deliver' | 'edit_deliver';
rowSelection: Record<string, boolean>;
setRowSelection: React.Dispatch<
React.SetStateAction<Record<string, boolean>>
@@ -140,7 +140,7 @@ const SalesOrderProductTable = ({
setRowSelection={setRowSelection}
data={data}
columns={
formType == 'deliver'
formType == 'add_deliver' || formType == 'edit_deliver'
? columns.filter(
(col) => col.header != 'Aksi' && col.id != 'select'
)
@@ -167,7 +167,7 @@ const SalesOrderProductTable = ({
</div>
}
/>
{formType != 'deliver' && (
{formType != 'add_deliver' && formType != 'edit_deliver' && (
<div className='flex flex-row gap-3 mt-3'>
<Button
type='button'
@@ -6,7 +6,7 @@ import {
ChickinFormValues,
ChickinRequestFormValues,
ChickinSchema,
} from '../ChickinForm.schema';
} from '@/components/pages/production/chickin/form/ChickinForm.schema';
import DateInput from '@/components/input/DateInput';
import Button from '@/components/Button';
import { useCallback, useEffect, useState } from 'react';
+1 -1
View File
@@ -1,5 +1,5 @@
import { Icon } from '@iconify/react';
import Button from '../Button';
import Button from '@/components/Button';
import RowOptionsMenuWrapper from '@/components/table/RowOptionsMenuWrapper';
interface TableRowOptionsProps {
@@ -1,4 +1,4 @@
import SelectInput from '../input/SelectInput';
import SelectInput from '@/components/input/SelectInput';
export interface OptionType {
label: string;
@@ -9,15 +9,18 @@ interface TableRowSizeSelectorProps {
value: number;
onChange: (val: OptionType | OptionType[] | null) => void;
options: OptionType[];
children?: React.ReactNode;
}
export const TableRowSizeSelector = ({
value,
onChange,
options,
children,
}: TableRowSizeSelectorProps) => {
return (
<div className='flex flex-row justify-end'>
<div className='flex flex-row gap-3 items-end justify-end'>
{children}
<SelectInput
label='Baris'
options={options}
+2 -2
View File
@@ -1,6 +1,6 @@
import { Icon } from '@iconify/react';
import Button from '../Button';
import DebouncedTextInput from '../input/DebouncedTextInput';
import Button from '@/components/Button';
import DebouncedTextInput from '@/components/input/DebouncedTextInput';
interface TableToolbarProps {
addButton?: {
+1 -1
View File
@@ -3,7 +3,7 @@ import {
CreateChickinPayload,
UpdateChickinPayload,
} from '@/types/api/production/chickin';
import { BaseApiService } from '../base';
import { BaseApiService } from '@/services/api/base';
import { BaseApiResponse } from '@/types/api/api-general';
import { httpClient } from '@/services/http/client';
+2 -2
View File
@@ -3,7 +3,7 @@ import {
ProjectFlock,
UpdateProjectFlockPayload,
} from '@/types/api/production/project-flock';
import { BaseApiService } from '../base';
import { BaseApiService } from '@/services/api/base';
import {
BaseApiResponse,
BaseGroupedApproval,
@@ -120,7 +120,7 @@ export class ProjectFlockService extends BaseApiService<
| undefined
> {
try {
const path = `${this.basePath}/location/${locationId.toString()}/periods`;
const path = `${this.basePath}/locations/${locationId.toString()}/periods`;
return await httpClient<
SuccessApiResponse<
{
+1 -1
View File
@@ -1,6 +1,6 @@
import { Product } from '@/types/api/master-data/product';
import { BaseMetadata } from '../base-metadata';
import { Warehouse } from '@/types/api/master-data/warehouse';
import { BaseMetadata } from '@/types/api/api-general';
export type BaseInventoryAdjustment = {
id: number;
+1 -1
View File
@@ -7,7 +7,7 @@ import {
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { Kandang } from '@/types/api/master-data/kandang';
import { id } from 'react-day-picker/locale';
import { Warehouse } from '../master-data/warehouse';
import { Warehouse } from '@/types/api/master-data/warehouse';
/**
* Base Data Response
+2 -2
View File
@@ -1,8 +1,8 @@
import { Kandang } from '@/type/master-data/kandang';
import { ProjectFlock } from '@/types/api/production/project-flock';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
import { Supplier } from '../master-data/supplier';
import { BaseApproval } from '../api-general';
import { Supplier } from '@/types/api/master-data/supplier';
import { BaseApproval } from '@/types/api/api-general';
export type BaseProjectFlockKandang = {
id: number;