feat(FE-177): Integrate API sales order and fixing sales order initial state

This commit is contained in:
randy-ar
2025-11-17 15:59:31 +07:00
parent d3c4706d87
commit a9bdb6c36e
9 changed files with 277 additions and 308 deletions
@@ -215,7 +215,7 @@ const SalesOrderTable = () => {
),
},
{
accessorKey: 'name',
accessorKey: 'so_number',
header: 'No. Order',
},
{
@@ -226,7 +226,7 @@ const SalesOrderTable = () => {
},
},
{
accessorKey: 'approval.step_name',
accessorKey: 'latest_approval.step_name',
header: 'Status',
},
{
@@ -11,7 +11,10 @@ import ApprovalSteps, {
import Table from '@/components/Table';
import { MARKETING_APPROVAL_LINE } from '@/config/approval-line';
import { cn, formatCurrency, formatDate, formatNumber } from '@/lib/helper';
import { MarketingApi } from '@/services/api/marketing/marketing';
import {
MarketingApi,
SalesOrderApi,
} from '@/services/api/marketing/marketing';
import { BaseSalesOrder, Marketing } from '@/types/api/marketing/marketing';
import { Icon } from '@iconify/react';
import { useState } from 'react';
@@ -77,7 +80,7 @@ const SalesOrderDetail = ({
const confirmationModalApproveClickHandler = async () => {
setIsLoading(true);
const res = await MarketingApi.singleApproval(
const res = await SalesOrderApi.singleApproval(
initialValues?.id as number,
approvalAction
);
@@ -24,7 +24,7 @@ import { CustomerApi } from '@/services/api/master-data';
import { useFormik } from 'formik';
import { SalesOrderFormValues, SalesOrderSchema } from './MarketingForm.schema';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { MarketingApi } from '@/services/api/marketing/marketing';
import { SalesOrderApi } from '@/services/api/marketing/marketing';
import { SalesOrderProductFormValues } from './repeater/sales-order/SalesOrderProduct.schema';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import toast from 'react-hot-toast';
@@ -36,6 +36,8 @@ 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,
@@ -70,87 +72,89 @@ const SalesForm = ({
const [isLoading, setIsLoading] = useState(false);
const [selectedMarketingProduct, setSelectedMarketingProduct] =
useState<SalesOrderProductFormValues | null>(null);
const [rawMarketingProducts, setRawMarketingProducts] = useState<
SalesOrderProductFormValues[]
>(
initialValues?.sales_order.map((item) =>
MarketingProductToFieldValues(item)
) || []
);
const [selectedCustomer, setSelectedCustomer] = useState<OptionType | null>(
initialValues?.customer
? { value: initialValues.customer.id, label: initialValues.customer.name }
: null
);
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const selectedRowIds = Object.keys(rowSelection).map((item) =>
parseInt(item)
);
const [grandTotal, setGrandTotal] = useState<number>(
initialValues?.sales_order
?.map((item) => item.total_price)
.reduce((a, b) => a + b, 0) ?? 0
);
const {
options: customerOptions,
isLoadingOptions: isLoadingCustomerOptions,
} = useSelect<Customer>(CustomerApi.basePath, 'id', 'name');
const handleDeleteProduct = useCallback(
(product_warehouse_id: number, kandang_id: number) => {
setRawMarketingProducts((prev) =>
prev.filter(
(p) =>
p.product_warehouse_id !== product_warehouse_id &&
p.kandang_id !== kandang_id
)
);
},
[]
);
const handleBulkDeleteProduct = () => {
setRawMarketingProducts((prev) =>
prev.filter(
(product) =>
!selectedRowIds.includes(
parseInt(`${product.product_warehouse_id}${product.kandang_id}`)
)
)
);
};
const handleDelete = () => {
deleteModal.openModal();
};
const formikInitialValues = useMemo(() => {
return {
so_date: initialValues?.so_date || undefined,
notes: initialValues?.notes || undefined,
customer_id: initialValues?.customer?.id || undefined,
sales_person_id: initialValues?.sales_person?.id || 1,
customer: initialValues?.customer
? {
value: initialValues.customer.id,
label: initialValues.customer.name,
}
: null,
sales_order:
initialValues?.sales_order?.map((product) =>
MarketingProductToFieldValues(product)
) ?? [],
};
}, [initialValues]);
const handleAddProductClick = useCallback(() => {
setSelectedMarketingProduct(null); // Pastikan form tambah
addProductModal.openModal();
}, [addProductModal]);
const handleAddSubmitProduct = useCallback(
async (values: SalesOrderProductFormValues) => {
setRawMarketingProducts((prev) => [...prev, values]);
formik.setValues({
...formik.values,
sales_order: [...formik.values.sales_order, values],
});
setGrandTotal((prev) => prev + (values.total_price as number));
addProductModal.closeModal();
const formik = useFormik<SalesOrderFormValues>({
enableReinitialize: true,
initialValues: formikInitialValues,
validationSchema: SalesOrderSchema,
validateOnMount: true,
onSubmit: async (values) => {
console.log('VALUES');
console.log(values);
const payload = {
customer_id: values.customer_id as number,
sales_person_id: values.sales_person_id as number,
date: formatDate(values.so_date as string, 'yyyy-MM-DD'),
notes: values.notes as string,
marketing_products: values.sales_order.map((product) => {
return {
vehicle_number: product.vehicle_number as string,
kandang_id: product.kandang_id as number,
product_warehouse_id: product.product_warehouse_id as number,
unit_price: parseFloat(product.unit_price as string),
total_weight: parseFloat(product.total_weight as string),
qty: parseFloat(product.qty as string),
avg_weight: parseFloat(product.avg_weight as string),
total_price: parseFloat(product.total_price as string),
} as CreateSalesOrderProductPayload;
}),
} as CreateSalesOrderPayload;
console.log('PAYLOAD');
console.log(payload);
switch (formType) {
case 'add':
await createMarketingHandler(payload);
break;
case 'edit':
await updateMarketingHandler(payload);
break;
default:
break;
}
afterSubmit?.();
},
[rawMarketingProducts.length, addProductModal]
);
const handleChangeCustomer = useCallback(
(val: OptionType | OptionType[] | null) => {
setSelectedCustomer(val as OptionType);
formik.setFieldValue('customer_id', (val as OptionType)?.value);
formik.setFieldValue('customer', val as OptionType);
},
[selectedCustomer, setSelectedCustomer]
);
});
const grandTotal = useMemo(() => {
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) => {
console.log(values);
const createMarketingRes = await MarketingApi.create(values);
const createMarketingRes = await SalesOrderApi.create(values);
if (isResponseSuccess(createMarketingRes)) {
toast.success(createMarketingRes?.message as string);
router.push('/marketing/sales-orders');
@@ -161,7 +165,7 @@ const SalesForm = ({
};
const updateMarketingHandler = async (values: CreateSalesOrderPayload) => {
console.log(values);
const updateMarketingRes = await MarketingApi.update(
const updateMarketingRes = await SalesOrderApi.update(
initialValues?.id as number,
values
);
@@ -176,7 +180,7 @@ const SalesForm = ({
const deleteMarketingHandler = async () => {
setIsLoading(true);
console.log(initialValues?.id);
const deleteMarketingRes = await MarketingApi.delete(
const deleteMarketingRes = await SalesOrderApi.delete(
initialValues?.id as number
);
if (isResponseSuccess(deleteMarketingRes)) {
@@ -191,84 +195,59 @@ const SalesForm = ({
router.push('/marketing/sales-orders');
};
const formikInitialValues = useMemo(() => {
return {
so_date: initialValues?.so_date || undefined,
notes: initialValues?.notes || undefined,
customer_id: initialValues?.customer?.id || undefined,
sales_person_id: initialValues?.sales_person?.id || 1,
customer: {
value: initialValues?.customer?.id as number,
label: initialValues?.customer?.name as string,
},
sales_order:
initialValues?.sales_order?.map((product) =>
MarketingProductToFieldValues(product)
) ?? [],
};
}, [initialValues]);
const formik = useFormik<SalesOrderFormValues>({
enableReinitialize: true,
initialValues: formikInitialValues,
validationSchema: SalesOrderSchema,
validateOnMount: true,
onSubmit: async (values) => {
const payload = {
customer_id: values.customer_id as number,
sales_person_id: values.sales_person_id as number,
date: formatDate(values.so_date as string, 'yyyy-MM-DD'),
notes: values.notes as string,
marketing_products: values.sales_order.map((product) => {
return {
vehicle_number: 'D 1234 XXXX',
kandang_id: product.kandang_id as number,
product_warehouse_id: product.product_warehouse_id as number,
unit_price: parseFloat(product.unit_price as string),
total_weight: parseFloat(product.total_weight as string),
qty: parseFloat(product.qty as string),
avg_weight: parseFloat(product.avg_weight as string),
total_price: parseFloat(product.total_price as string),
} as CreateSalesOrderProductPayload;
}),
} as CreateSalesOrderPayload;
switch (formType) {
case 'add':
createMarketingHandler(payload);
break;
case 'edit':
updateMarketingHandler(payload);
break;
default:
break;
}
afterSubmit?.();
const handleDeleteProduct = useCallback(
(id: number) => {
const currentProducts = formik.values.sales_order;
formik.setFieldValue(
'sales_order',
currentProducts.filter((p) => p.id != id)
);
},
});
[formik]
);
const { setValues: formikSetValues } = formik;
useEffect(() => {
formikSetValues(formik.initialValues);
}, [formikSetValues, formik.initialValues]);
useEffect(() => {
// Hitung Grand Total baru
const newGrandTotal = rawMarketingProducts.reduce(
(total, product) => total + parseFloat(product.total_price as string),
0
const handleBulkDeleteProduct = useCallback(() => {
const currentProducts = formik.values.sales_order;
formik.setFieldValue(
'sales_order',
currentProducts.filter(
(product) => !selectedRowIds.includes(product.id ?? -1)
)
);
// Perbarui nilai formik.values.marketing_products
formik.setFieldValue('marketing_products', rawMarketingProducts, false);
// Parameter ketiga (false) untuk menghindari validasi secara langsung
// Perbarui state grandTotal
setGrandTotal(newGrandTotal);
// Reset row selection setiap kali daftar produk berubah (opsional, tapi disarankan)
setRowSelection({});
}, [rawMarketingProducts]);
}, [formik, selectedRowIds]);
const handleDelete = () => {
deleteModal.openModal();
};
const handleAddProductClick = useCallback(() => {
setSelectedMarketingProduct(null);
addProductModal.openModal();
}, [addProductModal]);
const handleAddSubmitProduct = useCallback(
async (values: SalesOrderProductFormValues) => {
const currentProducts = formik.values.sales_order;
const newValues = {
...values,
id: values.id ?? Date.now(),
};
formik.setFieldValue('sales_order', [...currentProducts, newValues]);
addProductModal.closeModal();
},
[formik, addProductModal]
);
const handleChangeCustomer = useCallback(
(val: OptionType | OptionType[] | null) => {
formik.setFieldValue('customer_id', (val as OptionType)?.value);
formik.setFieldValue('customer', val as OptionType);
},
[formik]
);
return (
<>
@@ -292,7 +271,7 @@ const SalesForm = ({
label='Pelanggan'
options={customerOptions}
isLoading={isLoadingCustomerOptions}
value={selectedCustomer}
value={formik.values.customer}
onChange={handleChangeCustomer}
isError={
formik.touched.customer_id && Boolean(formik.errors.customer_id)
@@ -318,13 +297,14 @@ const SalesForm = ({
wrapper: 'bg-white w-full',
}}
>
{JSON.stringify(formik.values.sales_order)}
<div className='text-green-500'>
{JSON.stringify(formik.values.sales_order)}
</div>
<span className='text-red-500'>{JSON.stringify(formik.errors)}</span>
{/* <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> */}
<SalesOrderProductTable
data={rawMarketingProducts}
data={formik.values.sales_order}
rowSelection={rowSelection}
setRowSelection={setRowSelection}
selectedRowIds={selectedRowIds}
onDelete={handleDeleteProduct}
onBulkDelete={handleBulkDeleteProduct}
onAddProductClick={handleAddProductClick}
@@ -345,7 +325,7 @@ const SalesForm = ({
<div className='flex flex-col h-full justify-between items-end py-6'>
<span>Total Penjualan</span>
<span className='text-lg font-semibold'>
{formatCurrency(grandTotal)}
{formatCurrency(grandTotal)}{' '}
</span>
</div>
</div>
@@ -356,6 +336,7 @@ const SalesForm = ({
<Button
type='submit'
disabled={!formik.isValid || formik.isSubmitting}
isLoading={formik.isSubmitting}
>
Submit
</Button>
@@ -363,7 +344,12 @@ const SalesForm = ({
</form>
{formType == 'edit' && (
<div className='flex flex-row justify-start'>
<Button type='button' color='error' onClick={handleDelete}>
<Button
type='button'
color='error'
onClick={handleDelete}
isLoading={isLoading}
>
<Icon icon='mdi:trash' width={24} height={24} />
Hapus
</Button>
@@ -403,6 +389,7 @@ const SalesForm = ({
text={`Apakah anda yakin ingin menghapus data penjualan ini?`}
secondaryButton={{
text: 'Tidak',
onClick: deleteModal.closeModal,
}}
primaryButton={{
text: 'Ya',
@@ -1,6 +1,7 @@
import * as Yup from 'yup';
type SalesOrderProductSchemaType = {
id?: number | undefined;
kandang_id?: number;
kandang?: {
value: number;
@@ -16,10 +17,13 @@ type SalesOrderProductSchemaType = {
qty: string | number | undefined;
avg_weight: string | number | undefined;
total_price: string | number | undefined;
vehicle_number?: string | undefined;
};
export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaType> =
Yup.object({
id: Yup.number(),
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
kandang: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
@@ -4,8 +4,8 @@ import { useFormik } from 'formik';
import {
SalesOrderProductFormValues,
SalesOrderProductSchema,
} from './SalesOrderProduct.schema';
import { RefObject, useEffect, useState } from 'react';
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
import { RefObject, useState } from 'react';
import SelectInput, {
OptionType,
useSelect,
@@ -17,75 +17,24 @@ import { ProductWarehouseApi } from '@/services/api/inventory';
import NumberInput from '@/components/input/NumberInput';
import Button from '@/components/Button';
import { isResponseSuccess } from '@/lib/api-helper';
import { formatVechicleNumber } from '@/lib/helper';
import PatternInput from '@/components/input/PatternInput';
import Alert from '@/components/Alert';
const SalesOrderProductForm = ({
initialValues,
modalRef,
onSubmitForm,
}: {
initialValues?: SalesOrderProductFormValues;
modalRef?: RefObject<HTMLDialogElement | null>;
onSubmitForm?: (value: SalesOrderProductFormValues) => Promise<void>;
}) => {
// State
const [selectedOptionsKandang, setSelectedOptionsKandang] =
useState<OptionType | null>(null);
const [selectedOptionsWarehouse, setSelectedOptionsWarehouse] = useState<
OptionType | null | undefined
>(undefined);
const [formErrorMessage, setFormErrorMessage] = useState('');
// Options Data
const {
options: kandangSourceOptions,
rawData: kandangSourceRawData,
isLoadingOptions: isLoadingKandangSourceOptions,
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name');
const {
options: warehouseSourceOptions,
rawData: warehouseSourceRawData,
isLoadingOptions: isLoadingWarehouseSourceOptions,
} = useSelect<ProductWarehouse>(
ProductWarehouseApi.basePath,
'id',
'product.name',
'search',
{
warehouse_id: selectedOptionsKandang?.value?.toString() ?? '',
}
);
// Handler
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedOptionsKandang(val as OptionType);
formik.setFieldValue('kandang', val as OptionType);
formik.setFieldValue('kandang_id', (val as OptionType)?.value);
formik.setFieldValue('product_warehouse_id', null);
formik.setFieldValue('qty', null);
warehouseChangeHandler(null);
};
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
setSelectedOptionsWarehouse(val as OptionType);
formik.setFieldValue('product_warehouse', val as OptionType);
formik.setFieldValue('product_warehouse_id', (val as OptionType)?.value);
if (isResponseSuccess(warehouseSourceRawData)) {
const productWarehouse = warehouseSourceRawData?.data.find(
(item: ProductWarehouse) => item.id === (val as OptionType)?.value
);
if (selectedOptionsWarehouse?.value !== null) {
formik.setFieldValue('qty', productWarehouse?.quantity);
handleBlurField('qty');
} else {
formik.setFieldValue('qty', null);
}
}
};
// Formik
const formik = useFormik<SalesOrderProductFormValues>({
enableReinitialize: true,
initialValues: {
vehicle_number: initialValues?.vehicle_number || undefined,
kandang_id: initialValues?.kandang_id || undefined,
kandang: initialValues?.kandang || undefined,
product_warehouse: initialValues?.product_warehouse || undefined,
@@ -99,34 +48,59 @@ const SalesOrderProductForm = ({
validationSchema: SalesOrderProductSchema,
onSubmit: async (values) => {
setFormErrorMessage('');
if (
isResponseSuccess(kandangSourceRawData) &&
isResponseSuccess(warehouseSourceRawData)
) {
const productWarehouse = warehouseSourceRawData?.data.find(
(item: ProductWarehouse) => item.id === values.product_warehouse_id
);
const kandang = kandangSourceRawData?.data.find(
(item: Kandang) => item.id === values.kandang_id
);
onSubmitForm?.(values);
handleResetForm();
}
onSubmitForm?.(values);
handleResetForm();
},
});
const { setValues: formikSetValues } = formik;
useEffect(() => {
formikSetValues(formik.initialValues);
}, [formikSetValues, formik.initialValues]);
const {
options: kandangSourceOptions,
isLoadingOptions: isLoadingKandangSourceOptions,
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name');
const {
options: warehouseSourceOptions,
rawData: warehouseSourceRawData,
isLoadingOptions: isLoadingWarehouseSourceOptions,
} = useSelect<ProductWarehouse>(
ProductWarehouseApi.basePath,
'id',
'product.name',
'search',
{
warehouse_id: formik.values.kandang_id?.toString() ?? '',
}
);
const kandangChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldValue('kandang', val as OptionType);
formik.setFieldValue('kandang_id', (val as OptionType)?.value);
formik.setFieldValue('product_warehouse_id', null);
formik.setFieldValue('product_warehouse', null);
formik.setFieldValue('qty', null);
};
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldValue('product_warehouse', val as OptionType);
const newId = (val as OptionType)?.value;
formik.setFieldValue('product_warehouse_id', newId);
if (isResponseSuccess(warehouseSourceRawData) && newId) {
const productWarehouse = warehouseSourceRawData?.data.find(
(item: ProductWarehouse) => item.id === newId
);
formik.setFieldValue('qty', productWarehouse?.quantity);
handleBlurField('qty');
} else {
formik.setFieldValue('qty', null);
}
};
const handleResetForm = () => {
setSelectedOptionsKandang(null);
setSelectedOptionsWarehouse(null);
setFormErrorMessage('');
formik.resetForm({
values: {
vehicle_number: '',
kandang_id: undefined,
kandang: null,
product_warehouse: null,
@@ -145,7 +119,7 @@ const SalesOrderProductForm = ({
formik.values;
if (field === 'unit_price' || field === 'total_price' || field === 'qty') {
if (qty && unit_price && field === 'unit_price') {
if (qty && unit_price && (field === 'unit_price' || field === 'qty')) {
formik.setFieldValue(
'total_price',
(qty as number) * (unit_price as number)
@@ -159,7 +133,7 @@ const SalesOrderProductForm = ({
}
if (field === 'avg_weight' || field === 'total_weight' || field === 'qty') {
if (qty && avg_weight && field === 'avg_weight') {
if (qty && avg_weight && (field === 'avg_weight' || field === 'qty')) {
formik.setFieldValue(
'total_weight',
(qty as number) * (avg_weight as number)
@@ -180,8 +154,15 @@ const SalesOrderProductForm = ({
onSubmit={formik.handleSubmit}
onReset={handleResetForm}
>
{formErrorMessage && (
<div onClick={() => setFormErrorMessage('')} className='my-3 w-full'>
<Alert color='error'>
{formErrorMessage ? formErrorMessage : ''}
</Alert>
</div>
)}
<div className='grid grid-cols-2 gap-4 z-200'>
{/* <PatternInput
<PatternInput
name='vehicle_number'
label='No. Polisi'
format='AA #### AAA'
@@ -198,16 +179,15 @@ const SalesOrderProductForm = ({
Boolean(formik.errors.vehicle_number)
}
errorMessage={formik.errors.vehicle_number}
/> */}
/>
<SelectInput
required
label='Kandang'
options={kandangSourceOptions}
isLoading={isLoadingKandangSourceOptions}
value={selectedOptionsKandang}
value={formik.values.kandang}
onChange={kandangChangeHandler}
isClearable
menuPortalTarget={modalRef?.current}
isError={
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
}
@@ -219,12 +199,11 @@ const SalesOrderProductForm = ({
label='Produk'
options={warehouseSourceOptions}
isLoading={isLoadingWarehouseSourceOptions}
value={selectedOptionsWarehouse}
value={formik.values.product_warehouse}
onChange={warehouseChangeHandler}
isClearable
menuPortalTarget={modalRef?.current}
placeholder='Pilih Kandang Terlebih Dahulu'
isDisabled={!selectedOptionsKandang?.value}
isDisabled={!formik.values.kandang_id}
isError={
formik.touched.product_warehouse_id &&
Boolean(formik.errors.product_warehouse_id)
@@ -3,39 +3,38 @@
import Button from '@/components/Button';
import Table from '@/components/Table';
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
import { cn, formatCurrency, formatNumber } from '@/lib/helper';
import {
cn,
formatCurrency,
formatNumber,
formatVechicleNumber,
} from '@/lib/helper';
import { Icon } from '@iconify/react';
import { useMemo, useState } from 'react';
import * as TanStack from '@tanstack/react-table';
import CheckboxInput from '@/components/input/CheckboxInput';
// Hapus import Modal, useModal, dan MarketingProductForm
// Tentukan Tipe Props baru yang diterima dari SalesForm
type SalesOrderProductTableProps = {
data: SalesOrderProductFormValues[];
onDelete: (product_warehouse_id: number, kandang_id: number) => void;
onBulkDelete: (selectedIds: number[]) => void;
onAddProductClick: () => void; // Prop baru untuk memanggil modal di parent
rowSelection: Record<string, boolean>;
setRowSelection: React.Dispatch<
React.SetStateAction<Record<string, boolean>>
>;
selectedRowIds: number[];
onDelete: (id: number) => void;
onBulkDelete: () => void;
onAddProductClick: () => void;
};
const SalesOrderProductTable = ({
data,
rowSelection,
setRowSelection,
selectedRowIds,
onDelete,
onBulkDelete,
onAddProductClick,
}: SalesOrderProductTableProps) => {
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({});
const selectedRowIds = Object.keys(rowSelection).map((item) =>
parseInt(item)
);
const handleBulkDeleteClick = () => {
onBulkDelete(selectedRowIds);
setRowSelection({});
};
const columns = useMemo(
() => [
{
@@ -67,6 +66,11 @@ const SalesOrderProductTable = ({
</div>
),
},
{
accessorFn: (row: SalesOrderProductFormValues) =>
formatVechicleNumber(row.vehicle_number as string),
header: 'No. Polisi',
},
{
accessorFn: (row: SalesOrderProductFormValues) => row.kandang?.label,
header: 'Kandang',
@@ -110,22 +114,10 @@ const SalesOrderProductTable = ({
<Button
color='error'
className='p-1'
disabled={
!props.row.original.product_warehouse_id ||
!props.row.original.kandang_id
}
onClick={() => {
// PANGGIL CALLBACK PARENT (onDelete)
if (
props.row.original.product_warehouse_id &&
props.row.original.kandang_id
) {
onDelete(
props.row.original.product_warehouse_id,
props.row.original.kandang_id
);
}
onDelete(props.row.original.id as number);
}}
type='button'
>
<Icon icon='mdi:trash' width={16} height={16} />
</Button>
@@ -180,7 +172,7 @@ const SalesOrderProductTable = ({
variant='outline'
color='error'
className='justify-start w-fit py-1 text-sm'
onClick={handleBulkDeleteClick}
onClick={onBulkDelete}
>
<Icon icon='mdi:trash' width={16} height={16} />
Hapus
@@ -191,8 +183,6 @@ const SalesOrderProductTable = ({
</Button>
)}
</div>
{/* Modal dan Form dihapus dari sini */}
</>
);
};
+2
View File
@@ -211,6 +211,7 @@ export const dummyProductWarehouses: ProductWarehouse[] = [
// Helper untuk Sales Order (SO) Item
const soItem1: BaseSalesOrder = {
vehicle_number: 'B 1234 ABC',
id: 101,
marketing_id: 1,
product_warehouse_id: 1,
@@ -222,6 +223,7 @@ const soItem1: BaseSalesOrder = {
product_warehouse: dummyProductWarehouses[0] as ProductWarehouse,
};
const soItem2: BaseSalesOrder = {
vehicle_number: 'D 5678 EFG',
id: 102,
marketing_id: 2,
product_warehouse_id: 2,
+34 -31
View File
@@ -20,7 +20,7 @@ const createDummyResponse = <T>(data: T): BaseApiResponse<T> => ({
data: data,
});
export class MarketingService extends BaseApiService<
export class SalesOrderService extends BaseApiService<
Marketing,
CreateSalesOrderPayload,
UpdateSalesOrderPayload
@@ -29,40 +29,40 @@ export class MarketingService extends BaseApiService<
super(basePath);
}
/**
* Override: Mengambil semua data Marketing dari dummyMarketings
*/
async getAllFetcher(endpoint: string): Promise<BaseApiResponse<Marketing[]>> {
// Simulasi delay jaringan
await sleep(500);
// /**
// * Override: Mengambil semua data Marketing dari dummyMarketings
// */
// async getAllFetcher(endpoint: string): Promise<BaseApiResponse<Marketing[]>> {
// // Simulasi delay jaringan
// await sleep(500);
// Filter data marketing yang valid (jika menggunakan BaseMarketing[])
const data = dummyMarketings as Marketing[];
// // Filter data marketing yang valid (jika menggunakan BaseMarketing[])
// const data = dummyMarketings as Marketing[];
return createDummyResponse<Marketing[]>(data);
}
// return createDummyResponse<Marketing[]>(data);
// }
/**
* Override: Mengambil satu data Marketing berdasarkan ID dari dummyMarketings
*/
async getSingle(id: number): Promise<BaseApiResponse<Marketing> | undefined> {
// Simulasi delay jaringan
await sleep(300);
// /**
// * Override: Mengambil satu data Marketing berdasarkan ID dari dummyMarketings
// */
// async getSingle(id: number): Promise<BaseApiResponse<Marketing> | undefined> {
// // Simulasi delay jaringan
// await sleep(300);
const foundData = dummyMarketings.find((m) => m.id == id);
// const foundData = dummyMarketings.find((m) => m.id == id);
if (foundData) {
// Data ditemukan, kembalikan respons sukses
return createDummyResponse<Marketing>(foundData as Marketing);
} else {
// Data tidak ditemukan, simulasi respons error
return {
code: 404,
status: 'error',
message: 'Marketing data not found (MOCK)',
};
}
}
// if (foundData) {
// // Data ditemukan, kembalikan respons sukses
// return createDummyResponse<Marketing>(foundData as Marketing);
// } else {
// // Data tidak ditemukan, simulasi respons error
// return {
// code: 404,
// status: 'error',
// message: 'Marketing data not found (MOCK)',
// };
// }
// }
/**
* Approve single marketing data
@@ -111,4 +111,7 @@ export class MarketingService extends BaseApiService<
}
}
export const MarketingApi = new MarketingService('/marketing/sales-orders');
export const SalesOrderApi = new SalesOrderService('/marketing/sales-orders');
export const MarketingApi = new BaseApiService<Marketing, unknown, unknown>(
'/marketing'
);
+1
View File
@@ -35,6 +35,7 @@ export type BaseSalesOrder = {
total_weight: number;
total_price: number;
product_warehouse: ProductWarehouse;
vehicle_number: string;
};
export type BaseDeliveryOrder = {