Merge branch 'fix/marketing' into 'development'

[FIX/FE] Adding Button Edit and Delete per Product on Marketing Form

See merge request mbugroup/lti-web-client!213
This commit is contained in:
Rivaldi A N S
2026-01-20 04:36:51 +00:00
6 changed files with 126 additions and 34 deletions
@@ -361,6 +361,8 @@ const MarketingForm = ({
}, },
}); });
const memoSalesOrder = formik.values.sales_order;
// ================== FORM REPEATER HANDLER ================== // ================== FORM REPEATER HANDLER ==================
const createMarketingHandler = async (values: CreateSalesOrderPayload) => { const createMarketingHandler = async (values: CreateSalesOrderPayload) => {
setIsLoading(true); setIsLoading(true);
@@ -471,13 +473,25 @@ const MarketingForm = ({
}, [deleteModal]); }, [deleteModal]);
// ================== SALES ORDER HANDLER ================== // ================== SALES ORDER HANDLER ==================
const handleDeleteSO = useCallback((id: number) => { const handleDeleteSO = useCallback(
const currentProducts = formik.values.sales_order; (id: number) => {
formik.setFieldValue( const currentProducts = formik.values.sales_order;
'sales_order', formik.setFieldValue(
currentProducts.filter((p) => p.id != id) 'sales_order',
); currentProducts.filter((p) => p.id != id)
}, []); );
},
[memoSalesOrder]
);
const handleEditSO = useCallback(
(id: number) => {
const currentProducts = formik.values.sales_order;
const selectedProduct = currentProducts.find((p) => p.id == id);
setSelectedMarketingProduct(selectedProduct ?? null);
addSOModal.openModal();
},
[memoSalesOrder]
);
const handleBulkDeleteSO = useCallback(() => { const handleBulkDeleteSO = useCallback(() => {
const currentProducts = formik.values.sales_order; const currentProducts = formik.values.sales_order;
formik.setFieldValue( formik.setFieldValue(
@@ -487,7 +501,7 @@ const MarketingForm = ({
) )
); );
setRowSOSelection({}); setRowSOSelection({});
}, [selectedRowSOIds]); }, [selectedRowSOIds, memoSalesOrder]);
const handleAddSOClick = useCallback(() => { const handleAddSOClick = useCallback(() => {
setSelectedMarketingProduct(null); setSelectedMarketingProduct(null);
addSOModal.openModal(); addSOModal.openModal();
@@ -523,7 +537,7 @@ const MarketingForm = ({
addSOModal.closeModal(); addSOModal.closeModal();
}, },
[addSOModal] [addSOModal, memoSalesOrder]
); );
// ================== DELIVERY ORDER HANDLER ================== // ================== DELIVERY ORDER HANDLER ==================
@@ -568,8 +582,30 @@ const MarketingForm = ({
}, },
[addDOModal] [addDOModal]
); );
const handleDeleteDO = useCallback(
const memoSalesOrder = formik.values.sales_order; async (id: number) => {
setDeliveryOrderValues((prev) =>
prev.map((product) =>
product.id === id
? {
...product,
...{
unit_price: '',
total_weight: '',
qty: '',
avg_weight: '',
total_price: '',
delivery_date: '',
},
}
: product
)
);
addDOModal.closeModal();
setSelectedDeliveryProduct(null);
},
[addDOModal]
);
useEffect(() => { useEffect(() => {
formik.setFieldValue('delivery_order', deliveryOrderValues); formik.setFieldValue('delivery_order', deliveryOrderValues);
@@ -654,6 +690,7 @@ const MarketingForm = ({
setRowSelection={setRowSOSelection} setRowSelection={setRowSOSelection}
selectedRowIds={selectedRowSOIds} selectedRowIds={selectedRowSOIds}
onDelete={handleDeleteSO} onDelete={handleDeleteSO}
onEdit={handleEditSO}
onBulkDelete={handleBulkDeleteSO} onBulkDelete={handleBulkDeleteSO}
onAddProductClick={handleAddSOClick} onAddProductClick={handleAddSOClick}
/> />
@@ -673,6 +710,7 @@ const MarketingForm = ({
formType={formType} formType={formType}
data={deliveryOrderValues} data={deliveryOrderValues}
onEdit={handleEditDO} onEdit={handleEditDO}
onDelete={handleDeleteDO}
onAddProductClick={handleAddDOClick} onAddProductClick={handleAddDOClick}
/> />
</Card> </Card>
@@ -106,6 +106,7 @@ const DeliveryOrderProductForm = ({
await onUpdateForm?.(values.marketing_product_id as number, values); await onUpdateForm?.(values.marketing_product_id as number, values);
} }
handleResetForm(); handleResetForm();
setSelectedProduct(null);
}, },
}); });
@@ -124,7 +125,7 @@ const DeliveryOrderProductForm = ({
marketing_product: undefined, marketing_product: undefined,
}, },
}); });
setSelectedProduct(null); // setSelectedProduct(null);
}; };
const handleBlurField = (field: string) => { const handleBlurField = (field: string) => {
@@ -18,6 +18,7 @@ type SalesOrderProductSchemaType = {
avg_weight: string | number | undefined; avg_weight: string | number | undefined;
total_price: string | number | undefined; total_price: string | number | undefined;
vehicle_number?: string | undefined; vehicle_number?: string | undefined;
uom?: string | null | undefined;
}; };
export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaType> = export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaType> =
@@ -57,6 +58,7 @@ export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaTy
total_price: Yup.number() total_price: Yup.number()
.min(1, 'Total Penjualan wajib diisi!') .min(1, 'Total Penjualan wajib diisi!')
.required('Total Penjualan wajib diisi!'), .required('Total Penjualan wajib diisi!'),
uom: Yup.string().nullable().optional().notRequired(),
}); });
export type SalesOrderProductFormValues = Yup.InferType< export type SalesOrderProductFormValues = Yup.InferType<
@@ -61,16 +61,17 @@ const SalesOrderProductForm = ({
const formik = useFormik<SalesOrderProductFormValues>({ const formik = useFormik<SalesOrderProductFormValues>({
enableReinitialize: true, enableReinitialize: true,
initialValues: { initialValues: {
vehicle_number: initialValues?.vehicle_number || undefined, vehicle_number: initialValues?.vehicle_number || '',
kandang_id: initialValues?.kandang_id || undefined, kandang_id: initialValues?.kandang_id || undefined,
kandang: initialValues?.kandang || undefined, kandang: initialValues?.kandang || null,
product_warehouse: initialValues?.product_warehouse || undefined, product_warehouse: initialValues?.product_warehouse || null,
product_warehouse_id: initialValues?.product_warehouse_id || undefined, product_warehouse_id: initialValues?.product_warehouse_id || undefined,
unit_price: initialValues?.unit_price || undefined, unit_price: initialValues?.unit_price || '',
total_weight: initialValues?.total_weight || undefined, total_weight: initialValues?.total_weight || '',
qty: initialValues?.qty || undefined, qty: initialValues?.qty || '',
avg_weight: initialValues?.avg_weight || undefined, avg_weight: initialValues?.avg_weight || '',
total_price: initialValues?.total_price || undefined, total_price: initialValues?.total_price || '',
uom: initialValues?.uom || '',
}, },
validationSchema: SalesOrderProductSchema, validationSchema: SalesOrderProductSchema,
onSubmit: async (values) => { onSubmit: async (values) => {
@@ -220,7 +221,19 @@ const SalesOrderProductForm = ({
}; };
// ===== Formik Error List ===== // ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(
formik,
{
onBeforeSubmit(e) {
e.preventDefault();
handleBlurField(currentInput);
formik.setFieldValue(
'uom',
isResponseSuccess(productData) ? productData?.data?.uom.name : ''
);
},
}
);
return ( return (
<> <>
@@ -16,6 +16,7 @@ type DeliveryOrderProductTableProps = {
data: DeliveryOrderProductFormValues[]; data: DeliveryOrderProductFormValues[];
formType?: 'add' | 'edit' | 'add_deliver' | 'edit_deliver'; formType?: 'add' | 'edit' | 'add_deliver' | 'edit_deliver';
onEdit: (id: number) => void; onEdit: (id: number) => void;
onDelete: (id: number) => void;
onAddProductClick: () => void; onAddProductClick: () => void;
}; };
@@ -23,10 +24,13 @@ const DeliveryOrderProductTable = ({
data, data,
formType, formType,
onEdit, onEdit,
onDelete,
onAddProductClick, onAddProductClick,
}: DeliveryOrderProductTableProps) => { }: DeliveryOrderProductTableProps) => {
const onEditRef = useRef(onEdit); const onEditRef = useRef(onEdit);
onEditRef.current = onEdit; onEditRef.current = onEdit;
const onDeleteRef = useRef(onDelete);
onDeleteRef.current = onDelete;
const canAddData = data.filter((item) => !Boolean(item.qty)); const canAddData = data.filter((item) => !Boolean(item.qty));
@@ -144,16 +148,29 @@ 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'>
<> <>
{props.row.original.qty && ( {props.row.original.qty && (
<Button <>
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='px-2 py-1 text-sm'
onClick={() =>
onDeleteRef.current(props.row.original.id as number)
}
type='button'
disabled={!!props.row.original.do_number}
>
<Icon icon='mdi:delete' width={16} height={16} /> Hapus
</Button>
</>
)} )}
{!props.row.original.qty && '-'} {!props.row.original.qty && '-'}
</> </>
@@ -23,6 +23,7 @@ type SalesOrderProductTableProps = {
>; >;
selectedRowIds: number[]; selectedRowIds: number[];
onDelete: (id: number) => void; onDelete: (id: number) => void;
onEdit: (id: number) => void;
onBulkDelete: () => void; onBulkDelete: () => void;
onAddProductClick: () => void; onAddProductClick: () => void;
}; };
@@ -34,11 +35,14 @@ const SalesOrderProductTable = ({
setRowSelection, setRowSelection,
selectedRowIds, selectedRowIds,
onDelete, onDelete,
onEdit,
onBulkDelete, onBulkDelete,
onAddProductClick, onAddProductClick,
}: SalesOrderProductTableProps) => { }: SalesOrderProductTableProps) => {
const onDeleteRef = useRef(onDelete); const onDeleteRef = useRef(onDelete);
onDeleteRef.current = onDelete; onDeleteRef.current = onDelete;
const onEditRef = useRef(onEdit);
onEditRef.current = onEdit;
const columns = useMemo( const columns = useMemo(
() => [ () => [
@@ -92,17 +96,26 @@ const SalesOrderProductTable = ({
}, },
{ {
accessorFn: (row: SalesOrderProductFormValues) => accessorFn: (row: SalesOrderProductFormValues) =>
formatNumber(parseFloat(row.total_weight as string)), formatNumber(parseFloat(row.total_weight as string), undefined, 0, 5),
header: 'Total Bobot (Kg)', header: 'Total Bobot (Kg)',
}, },
{ {
accessorFn: (row: SalesOrderProductFormValues) => accessorFn: (row: SalesOrderProductFormValues) =>
formatNumber(parseFloat(row.qty as string)), formatNumber(parseFloat(row.qty as string)),
header: 'Kuantitas', header: 'Kuantitas',
cell: ({ row }: { row: TanStack.Row<SalesOrderProductFormValues> }) =>
formatNumber(
parseFloat(row.original.qty as string),
undefined,
0,
5
) +
' ' +
(row.original.uom ?? ''),
}, },
{ {
accessorFn: (row: SalesOrderProductFormValues) => accessorFn: (row: SalesOrderProductFormValues) =>
formatNumber(parseFloat(row.avg_weight as string)), formatNumber(parseFloat(row.avg_weight as string), undefined, 0, 5),
header: 'Avg. Bobot (Kg)', header: 'Avg. Bobot (Kg)',
}, },
{ {
@@ -116,6 +129,14 @@ const SalesOrderProductTable = ({
props: TanStack.CellContext<SalesOrderProductFormValues, unknown> props: TanStack.CellContext<SalesOrderProductFormValues, unknown>
) => ( ) => (
<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
color='warning'
className='p-1'
onClick={() => onEditRef.current(props.row.original.id as number)}
type='button'
>
<Icon icon='mdi:pencil' width={16} height={16} /> Edit
</Button>
<Button <Button
color='error' color='error'
className='p-1' className='p-1'
@@ -124,7 +145,7 @@ const SalesOrderProductTable = ({
} }
type='button' type='button'
> >
<Icon icon='mdi:trash' width={16} height={16} /> <Icon icon='mdi:trash' width={16} height={16} /> Hapus
</Button> </Button>
</div> </div>
), ),