Merge branch 'feat/FE/US-160/marketing-delivery-order' into 'development'

[FE/FE][US#160] Marketing - Delivery Order

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