mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
refactor(FE): refactor UI Sales Order and Delivery Order
This commit is contained in:
@@ -7,6 +7,12 @@ import {
|
||||
DeliveryOrderProductFormValues,
|
||||
DeliveryOrderProductSchema,
|
||||
} from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
||||
import {
|
||||
BaseDeliveryOrder,
|
||||
BaseSalesOrder,
|
||||
Marketing,
|
||||
} from '@/types/api/marketing/marketing';
|
||||
import { formatDate } from '@/lib/helper';
|
||||
|
||||
type MarketingSchemaType = {
|
||||
customer_id: number | undefined;
|
||||
@@ -64,7 +70,7 @@ export const DeliveryOrderSchema: Yup.ObjectSchema<DeliveryOrderSchemaType> =
|
||||
.required('Pengiriman wajib diisi!')
|
||||
.test(
|
||||
'at-least-one-valid-row',
|
||||
'Minimal ada satu baris pengiriman yang valid!',
|
||||
'Minimal harus ada satu baris pengiriman yang lengkap diisi!',
|
||||
function (items) {
|
||||
if (!items || items.length === 0) return false;
|
||||
|
||||
@@ -86,3 +92,124 @@ export const UpdateSalesOrderSchema = SalesOrderSchema;
|
||||
export type SalesOrderFormValues = Yup.InferType<typeof SalesOrderSchema>;
|
||||
|
||||
export type DeliveryOrderFormValues = Yup.InferType<typeof DeliveryOrderSchema>;
|
||||
|
||||
// ================ Helper Function ================
|
||||
const SalesProductToFieldValues = (
|
||||
product: BaseSalesOrder
|
||||
): SalesOrderProductFormValues => {
|
||||
return {
|
||||
id: product.id,
|
||||
vehicle_number: product.vehicle_number,
|
||||
kandang_id: product.product_warehouse.warehouse.id,
|
||||
kandang: {
|
||||
value: product.product_warehouse.warehouse.id,
|
||||
label: product.product_warehouse.warehouse.name,
|
||||
},
|
||||
product_warehouse: {
|
||||
value: product.product_warehouse.id,
|
||||
label: product.product_warehouse.product.name,
|
||||
},
|
||||
product_warehouse_id: product.product_warehouse.id,
|
||||
unit_price: product.unit_price,
|
||||
total_weight: product.total_weight,
|
||||
qty: product.qty,
|
||||
avg_weight: product.avg_weight,
|
||||
total_price: product.total_price,
|
||||
};
|
||||
};
|
||||
const DeliveryProductToFieldValues = (
|
||||
salesOrders: BaseSalesOrder[],
|
||||
delivery: BaseDeliveryOrder
|
||||
): DeliveryOrderProductFormValues[] => {
|
||||
const data = delivery.deliveries.map((item) => {
|
||||
const soId = salesOrders.find(
|
||||
(so) => so.product_warehouse.id === item.product_warehouse.id
|
||||
)?.id;
|
||||
return {
|
||||
id: soId,
|
||||
unit_price: item.unit_price,
|
||||
total_weight: item.total_weight,
|
||||
qty: item.qty,
|
||||
avg_weight: item.avg_weight,
|
||||
total_price: item.total_price,
|
||||
vehicle_number: item.vehicle_number,
|
||||
delivery_date: formatDate(delivery.delivery_date, 'yyyy-MM-DD'),
|
||||
do_number: delivery.do_number,
|
||||
marketing_product_id: soId,
|
||||
marketing_product: {
|
||||
id: soId,
|
||||
vehicle_number: item.vehicle_number,
|
||||
kandang_id: item.product_warehouse.warehouse.id,
|
||||
kandang: {
|
||||
value: item.product_warehouse.warehouse.id,
|
||||
label: item.product_warehouse.warehouse.name,
|
||||
},
|
||||
product_warehouse: {
|
||||
value: item.product_warehouse.id,
|
||||
label: item.product_warehouse.product.name,
|
||||
},
|
||||
product_warehouse_id: item.product_warehouse.id,
|
||||
unit_price: item.unit_price,
|
||||
total_weight: item.total_weight,
|
||||
qty: item.qty,
|
||||
avg_weight: item.avg_weight,
|
||||
total_price: item.total_price,
|
||||
},
|
||||
} as DeliveryOrderProductFormValues;
|
||||
});
|
||||
return data;
|
||||
};
|
||||
export const mergeSOwithDO = (
|
||||
salesOrders: SalesOrderProductFormValues[],
|
||||
deliveryOrders: DeliveryOrderProductFormValues[],
|
||||
autofill?: boolean
|
||||
): DeliveryOrderProductFormValues[] => {
|
||||
return salesOrders.map((so) => {
|
||||
const delivery = deliveryOrders.find(
|
||||
(d) => d?.marketing_product_id === so.id
|
||||
);
|
||||
|
||||
return {
|
||||
...so, // nilai dasar dari sales order
|
||||
marketing_product_id: so.id,
|
||||
delivery_date: delivery?.delivery_date || undefined,
|
||||
do_number: delivery?.do_number || undefined,
|
||||
vehicle_number: delivery?.vehicle_number || so.vehicle_number,
|
||||
unit_price: autofill ? so.unit_price : delivery?.unit_price,
|
||||
total_weight: autofill ? so.total_weight : delivery?.total_weight,
|
||||
qty: autofill ? so.qty : delivery?.qty,
|
||||
avg_weight: autofill ? so.avg_weight : delivery?.avg_weight,
|
||||
total_price: autofill ? so.total_price : delivery?.total_price,
|
||||
marketing_product: so, // jika ada, override
|
||||
} as DeliveryOrderProductFormValues;
|
||||
});
|
||||
};
|
||||
|
||||
export const getFilledMarketingFormInitialValues = (
|
||||
marketing: Marketing
|
||||
): SalesOrderFormValues & DeliveryOrderFormValues => {
|
||||
return {
|
||||
customer_id: marketing.customer.id,
|
||||
sales_person_id: marketing.sales_person.id,
|
||||
sales_person: {
|
||||
value: marketing.sales_person.id,
|
||||
label: marketing.sales_person.name,
|
||||
},
|
||||
customer: {
|
||||
value: marketing.customer.id,
|
||||
label: marketing.customer.name,
|
||||
},
|
||||
so_date: formatDate(marketing.so_date, 'yyyy-MM-DD'),
|
||||
notes: marketing.notes,
|
||||
sales_order: marketing.sales_order.map((so) =>
|
||||
SalesProductToFieldValues(so)
|
||||
),
|
||||
delivery_order: mergeSOwithDO(
|
||||
marketing.sales_order?.map(SalesProductToFieldValues) ?? [],
|
||||
marketing.delivery_order?.flatMap((delivery) =>
|
||||
DeliveryProductToFieldValues(marketing.sales_order, delivery)
|
||||
) ?? [],
|
||||
marketing.latest_approval.step_number > 1
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -41,7 +41,6 @@ import ConfirmationModal from '@/components/modal/ConfirmationModal';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import DebouncedTextArea from '@/components/input/DebouncedTextArea';
|
||||
import SalesOrderProductTable from '@/components/pages/marketing/form/table-view/SalesOrderProductTable';
|
||||
import SalesOrderProductForm from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProductForm';
|
||||
import DeliveryOrderProductTable from '@/components/pages/marketing/form/table-view/DeliveryOrderProductTable';
|
||||
import DeliveryOrderProductForm from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct';
|
||||
@@ -53,7 +52,6 @@ import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import { CreatedUser } from '@/types/api/api-general';
|
||||
import { UserApi } from '@/services/api/user';
|
||||
|
||||
const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable);
|
||||
const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm);
|
||||
const MemoizedDeliveryOrderProductTable = memo(DeliveryOrderProductTable);
|
||||
const MemoizedDeliveryOrderProductForm = memo(DeliveryOrderProductForm);
|
||||
@@ -134,7 +132,8 @@ export const DeliveryProductToFieldValues = (
|
||||
};
|
||||
export const mergeSOwithDO = (
|
||||
salesOrders: SalesOrderProductFormValues[],
|
||||
deliveryOrders: DeliveryOrderProductFormValues[]
|
||||
deliveryOrders: DeliveryOrderProductFormValues[],
|
||||
autofill?: boolean
|
||||
): DeliveryOrderProductFormValues[] => {
|
||||
return salesOrders.map((so) => {
|
||||
const delivery = deliveryOrders.find(
|
||||
@@ -147,11 +146,11 @@ export const mergeSOwithDO = (
|
||||
delivery_date: delivery?.delivery_date || undefined,
|
||||
do_number: delivery?.do_number || undefined,
|
||||
vehicle_number: delivery?.vehicle_number || so.vehicle_number,
|
||||
unit_price: delivery?.unit_price,
|
||||
total_weight: delivery?.total_weight,
|
||||
qty: delivery?.qty,
|
||||
avg_weight: delivery?.avg_weight,
|
||||
total_price: delivery?.total_price,
|
||||
unit_price: autofill ? so.unit_price : delivery?.unit_price,
|
||||
total_weight: autofill ? so.total_weight : delivery?.total_weight,
|
||||
qty: autofill ? so.qty : delivery?.qty,
|
||||
avg_weight: autofill ? so.avg_weight : delivery?.avg_weight,
|
||||
total_price: autofill ? so.total_price : delivery?.total_price,
|
||||
marketing_product: so, // jika ada, override
|
||||
} as DeliveryOrderProductFormValues;
|
||||
});
|
||||
@@ -701,6 +700,7 @@ const MarketingForm = ({
|
||||
}}
|
||||
>
|
||||
<MemoizedDeliveryOrderProductTable
|
||||
marketing={initialValues}
|
||||
formType={formType}
|
||||
data={deliveryOrderValues}
|
||||
onEdit={handleEditDO}
|
||||
|
||||
+199
-202
@@ -10,9 +10,7 @@ import NumberInput from '@/components/input/NumberInput';
|
||||
import PatternInput from '@/components/input/PatternInput';
|
||||
import { formatVechicleNumber } from '@/lib/helper';
|
||||
import DateInput from '@/components/input/DateInput';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import { BaseSalesOrder } from '@/types/api/marketing/marketing';
|
||||
import Badge from '@/components/Badge';
|
||||
import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm';
|
||||
import * as Yup from 'yup';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
@@ -20,6 +18,9 @@ import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import useSWR from 'swr';
|
||||
import { ProductApi } from '@/services/api/master-data';
|
||||
import StatusBadge from '@/components/helper/StatusBadge';
|
||||
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||
import { OptionType } from '@/components/input/SelectInput';
|
||||
|
||||
const roundWeight = (value: number) => Number(value.toFixed(2));
|
||||
const roundPrice = (value: number) => Math.round(value);
|
||||
@@ -247,7 +248,7 @@ const DeliveryOrderProductForm = ({
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
className='size-full'
|
||||
className='size-full flex flex-col relative gap-1.5'
|
||||
onSubmit={handleFormSubmit}
|
||||
onReset={handleResetForm}
|
||||
>
|
||||
@@ -256,218 +257,214 @@ const DeliveryOrderProductForm = ({
|
||||
<Alert color='error'>{formikErrorMessage}</Alert>
|
||||
</div>
|
||||
)}
|
||||
<DateInput
|
||||
name='delivery_date'
|
||||
label='Tanggal'
|
||||
value={formik.values.delivery_date ?? undefined}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={
|
||||
formik.touched.delivery_date && Boolean(formik.errors.delivery_date)
|
||||
}
|
||||
errorMessage={formik.errors.delivery_date}
|
||||
placeholder='Pilih Tanggal'
|
||||
className={{
|
||||
inputWrapper: 'bg-white',
|
||||
}}
|
||||
required
|
||||
/>
|
||||
<SelectInputRadio
|
||||
options={options}
|
||||
label='Produk'
|
||||
placeholder='Pilih Produk'
|
||||
readOnly={formState == 'edit'}
|
||||
value={
|
||||
selectedProduct
|
||||
? ({
|
||||
value: selectedProduct?.value,
|
||||
label: exisitingValues?.find(
|
||||
(item) => item.id === selectedProduct?.value
|
||||
)?.marketing_product?.product_warehouse?.label,
|
||||
} as OptionType)
|
||||
: null
|
||||
}
|
||||
onChange={(value) => {
|
||||
const selected = value as OptionType;
|
||||
setSelectedProduct(selected);
|
||||
|
||||
<div className='grid sm:grid-cols-3 gap-4'>
|
||||
<SelectInput
|
||||
options={options}
|
||||
label='Produk'
|
||||
placeholder='Pilih Produk'
|
||||
isDisabled={formState == 'edit'}
|
||||
value={
|
||||
selectedProduct
|
||||
? ({
|
||||
value: selectedProduct?.value,
|
||||
label: exisitingValues?.find(
|
||||
(item) => item.id === selectedProduct?.value
|
||||
)?.marketing_product?.product_warehouse?.label,
|
||||
} as OptionType)
|
||||
: null
|
||||
}
|
||||
onChange={(value) => {
|
||||
const selected = value as OptionType;
|
||||
setSelectedProduct(selected);
|
||||
|
||||
const so = salesOrders?.find(
|
||||
(item) => item.id === selected?.value
|
||||
);
|
||||
if (!so) {
|
||||
formik.setValues({
|
||||
...formik.values,
|
||||
marketing_product_id: undefined,
|
||||
marketing_product: null,
|
||||
qty: '',
|
||||
unit_price: '',
|
||||
total_price: '',
|
||||
avg_weight: '',
|
||||
total_weight: '',
|
||||
vehicle_number: '',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const so = salesOrders?.find((item) => item.id === selected?.value);
|
||||
if (!so) {
|
||||
formik.setValues({
|
||||
...formik.values,
|
||||
marketing_product_id: selected.value as number,
|
||||
marketing_product: SalesProductToFieldValues(so),
|
||||
qty: so.qty,
|
||||
unit_price: so.unit_price,
|
||||
total_price: so.total_price,
|
||||
avg_weight: so.avg_weight,
|
||||
total_weight: so.total_weight,
|
||||
vehicle_number: so.vehicle_number,
|
||||
marketing_product_id: undefined,
|
||||
marketing_product: null,
|
||||
qty: '',
|
||||
unit_price: '',
|
||||
total_price: '',
|
||||
avg_weight: '',
|
||||
total_weight: '',
|
||||
vehicle_number: '',
|
||||
});
|
||||
}}
|
||||
startAdornment={
|
||||
selectedProduct && (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='success'
|
||||
size='sm'
|
||||
className={{ badge: 'whitespace-nowrap font-semibold' }}
|
||||
>
|
||||
{
|
||||
exisitingValues?.find(
|
||||
(item) => item.id === selectedProduct?.value
|
||||
)?.marketing_product?.kandang?.label
|
||||
}
|
||||
</Badge>
|
||||
)
|
||||
return;
|
||||
}
|
||||
isClearable
|
||||
isError={Boolean(formik.errors.marketing_product_id)}
|
||||
errorMessage={formik.errors.marketing_product_id}
|
||||
required
|
||||
/>
|
||||
<DateInput
|
||||
name='delivery_date'
|
||||
label='Tanggal'
|
||||
value={formik.values.delivery_date ?? undefined}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={
|
||||
formik.touched.delivery_date &&
|
||||
Boolean(formik.errors.delivery_date)
|
||||
}
|
||||
errorMessage={formik.errors.delivery_date}
|
||||
placeholder='Pilih Tanggal'
|
||||
className={{
|
||||
inputWrapper: 'bg-white',
|
||||
}}
|
||||
required
|
||||
/>
|
||||
|
||||
<PatternInput
|
||||
name='vehicle_number'
|
||||
label='No. Polisi'
|
||||
format='AA #### AAA'
|
||||
mask='_'
|
||||
inputVehicleNumber
|
||||
required
|
||||
type='text'
|
||||
placeholder='B 1234 CDE'
|
||||
value={formatVechicleNumber(formik.values.vehicle_number ?? '')}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={Boolean(formik.errors.vehicle_number)}
|
||||
errorMessage={formik.errors.vehicle_number}
|
||||
/>
|
||||
</div>
|
||||
<div className='divider my-6'></div>
|
||||
<div className='grid sm:grid-cols-3 gap-4'>
|
||||
<NumberInput
|
||||
required
|
||||
label='Kuantitas'
|
||||
name='qty'
|
||||
value={formik.values.qty}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('qty')}
|
||||
isError={Boolean(formik.errors.qty)}
|
||||
errorMessage={formik.errors.qty}
|
||||
placeholder='Masukan Kuantitas'
|
||||
endAdornment={
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-sm text-gray-500'>
|
||||
{isResponseSuccess(productData)
|
||||
? productData?.data?.uom.name
|
||||
: ''}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
bottomLabel={
|
||||
formik.values.marketing_product_id
|
||||
? 'Stok dijual: ' +
|
||||
salesOrders?.find(
|
||||
(item) => item.id === formik.values.marketing_product_id
|
||||
)?.qty +
|
||||
' ' +
|
||||
(isResponseSuccess(productData)
|
||||
? productData?.data?.uom.name
|
||||
: '')
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label={`Harga / ${isResponseSuccess(productData) ? productData?.data?.uom?.name : 'Produk'} (Rp)`}
|
||||
name='unit_price'
|
||||
value={formik.values.unit_price}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('unit_price')}
|
||||
isError={Boolean(formik.errors.unit_price)}
|
||||
errorMessage={formik.errors.unit_price}
|
||||
placeholder='Masukan Harga Satuan'
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Avg. Bobot (Kg)'
|
||||
name='avg_weight'
|
||||
value={formik.values.avg_weight}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('avg_weight')}
|
||||
isError={Boolean(formik.errors.avg_weight)}
|
||||
errorMessage={formik.errors.avg_weight}
|
||||
placeholder='Masukan Bobot Rata-rata'
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Total Bobot (Kg)'
|
||||
name='total_weight'
|
||||
value={formik.values.total_weight}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('total_weight')}
|
||||
isError={Boolean(formik.errors.total_weight)}
|
||||
errorMessage={formik.errors.total_weight}
|
||||
placeholder='Masukan Total Bobot'
|
||||
/>
|
||||
formik.setValues({
|
||||
...formik.values,
|
||||
marketing_product_id: selected.value as number,
|
||||
marketing_product: SalesProductToFieldValues(so),
|
||||
qty: so.qty,
|
||||
unit_price: so.unit_price,
|
||||
total_price: so.total_price,
|
||||
avg_weight: so.avg_weight,
|
||||
total_weight: so.total_weight,
|
||||
vehicle_number: so.vehicle_number,
|
||||
});
|
||||
}}
|
||||
startAdornment={
|
||||
selectedProduct && (
|
||||
<StatusBadge
|
||||
text={
|
||||
exisitingValues?.find(
|
||||
(item) => item.id === selectedProduct?.value
|
||||
)?.marketing_product?.kandang?.label ?? ''
|
||||
}
|
||||
color='success'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap w-fit font-semibold',
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
isClearable
|
||||
isError={Boolean(formik.errors.marketing_product_id)}
|
||||
errorMessage={formik.errors.marketing_product_id}
|
||||
required
|
||||
/>
|
||||
|
||||
<NumberInput
|
||||
required
|
||||
label='Total Penjualan (Rp)'
|
||||
name='total_price'
|
||||
value={formik.values.total_price}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('total_price')}
|
||||
isError={Boolean(formik.errors.total_price)}
|
||||
errorMessage={formik.errors.total_price}
|
||||
placeholder='Masukan Total Penjualan'
|
||||
/>
|
||||
</div>
|
||||
<PatternInput
|
||||
name='vehicle_number'
|
||||
label='No. Polisi'
|
||||
format='AA #### AAA'
|
||||
mask='_'
|
||||
inputVehicleNumber
|
||||
required
|
||||
type='text'
|
||||
placeholder='B 1234 CDE'
|
||||
value={formatVechicleNumber(formik.values.vehicle_number ?? '')}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
isError={Boolean(formik.errors.vehicle_number)}
|
||||
errorMessage={formik.errors.vehicle_number}
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Kuantitas'
|
||||
name='qty'
|
||||
value={formik.values.qty}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('qty')}
|
||||
isError={Boolean(formik.errors.qty)}
|
||||
errorMessage={formik.errors.qty}
|
||||
placeholder='Masukan Kuantitas'
|
||||
endAdornment={
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-sm text-gray-500'>
|
||||
{isResponseSuccess(productData)
|
||||
? productData?.data?.uom.name
|
||||
: ''}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
bottomLabel={
|
||||
formik.values.marketing_product_id
|
||||
? 'Stok dijual: ' +
|
||||
salesOrders?.find(
|
||||
(item) => item.id === formik.values.marketing_product_id
|
||||
)?.qty +
|
||||
' ' +
|
||||
(isResponseSuccess(productData)
|
||||
? productData?.data?.uom.name
|
||||
: '')
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label={`Harga / ${isResponseSuccess(productData) ? productData?.data?.uom?.name : 'Produk'} (Rp)`}
|
||||
name='unit_price'
|
||||
value={formik.values.unit_price}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('unit_price')}
|
||||
isError={Boolean(formik.errors.unit_price)}
|
||||
errorMessage={formik.errors.unit_price}
|
||||
placeholder='Masukan Harga Satuan'
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Avg. Bobot (Kg)'
|
||||
name='avg_weight'
|
||||
value={formik.values.avg_weight}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('avg_weight')}
|
||||
isError={Boolean(formik.errors.avg_weight)}
|
||||
errorMessage={formik.errors.avg_weight}
|
||||
placeholder='Masukan Bobot Rata-rata'
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Total Bobot (Kg)'
|
||||
name='total_weight'
|
||||
value={formik.values.total_weight}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('total_weight')}
|
||||
isError={Boolean(formik.errors.total_weight)}
|
||||
errorMessage={formik.errors.total_weight}
|
||||
placeholder='Masukan Total Bobot'
|
||||
/>
|
||||
|
||||
<AlertErrorList formErrorList={formErrorList} onClose={close} />
|
||||
<NumberInput
|
||||
required
|
||||
label='Total Penjualan (Rp)'
|
||||
name='total_price'
|
||||
value={formik.values.total_price}
|
||||
onChange={(e) => {
|
||||
formik.handleChange(e);
|
||||
setCurrentInput(e.target.name);
|
||||
}}
|
||||
onBlur={() => handleBlurField('total_price')}
|
||||
isError={Boolean(formik.errors.total_price)}
|
||||
errorMessage={formik.errors.total_price}
|
||||
placeholder='Masukan Total Penjualan'
|
||||
/>
|
||||
|
||||
<div className='flex flex-row justify-end gap-3 mt-4'>
|
||||
<Button type='reset' color='warning'>
|
||||
Reset
|
||||
</Button>
|
||||
<AlertErrorList
|
||||
className={{
|
||||
alert: 'w-full my-4',
|
||||
}}
|
||||
formErrorList={formErrorList}
|
||||
onClose={close}
|
||||
/>
|
||||
|
||||
<div className='h-18' />
|
||||
|
||||
<div className='absolute sm:w-full bottom-0 right-0'>
|
||||
<Button
|
||||
type='submit'
|
||||
isLoading={formik.isSubmitting}
|
||||
disabled={formik.isSubmitting}
|
||||
className='w-full p-3 rounded-lg text-base-100 text-sm font-semibold'
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
@@ -6,27 +6,20 @@ import {
|
||||
SalesOrderProductSchema,
|
||||
} from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||
import { RefObject, useMemo, useState } from 'react';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { OptionType, useSelect } from '@/components/input/SelectInput';
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
import { ProductApi, UomApi, WarehouseApi } from '@/services/api/master-data';
|
||||
import { WarehouseApi } from '@/services/api/master-data';
|
||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||
import NumberInput from '@/components/input/NumberInput';
|
||||
import Button from '@/components/Button';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import {
|
||||
formatCurrency,
|
||||
formatNumber,
|
||||
formatVechicleNumber,
|
||||
} from '@/lib/helper';
|
||||
import { formatNumber, formatVechicleNumber } from '@/lib/helper';
|
||||
import PatternInput from '@/components/input/PatternInput';
|
||||
import Alert from '@/components/Alert';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import useSWR from 'swr';
|
||||
import SelectInputRadio from '@/components/input/SelectInputRadio';
|
||||
|
||||
const roundWeight = (value: number) => Number(value.toFixed(2));
|
||||
const roundPrice = (value: number) => Math.round(value);
|
||||
@@ -282,7 +275,7 @@ const SalesOrderProductForm = ({
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
className='size-full flex flex-col p-4 relative overflow-x-hidden'
|
||||
className='size-full flex flex-col gap-1.5 relative'
|
||||
onSubmit={handleFormSubmit}
|
||||
onReset={handleResetForm}
|
||||
>
|
||||
@@ -311,7 +304,7 @@ const SalesOrderProductForm = ({
|
||||
}
|
||||
errorMessage={formik.errors.vehicle_number}
|
||||
/>
|
||||
<SelectInput
|
||||
<SelectInputRadio
|
||||
required
|
||||
label='Kandang'
|
||||
options={kandangSourceOptions}
|
||||
@@ -327,7 +320,7 @@ const SalesOrderProductForm = ({
|
||||
errorMessage={formik.errors.kandang_id}
|
||||
placeholder='Pilih Kandang'
|
||||
/>
|
||||
<SelectInput
|
||||
<SelectInputRadio
|
||||
required
|
||||
label='Produk'
|
||||
options={productOptionsFiltered}
|
||||
@@ -453,12 +446,12 @@ const SalesOrderProductForm = ({
|
||||
|
||||
<div className='h-18' />
|
||||
|
||||
<div className='absolute p-4 sm:w-[446px] bottom-0 right-0'>
|
||||
<div className='absolute w-full bottom-0 right-0'>
|
||||
<Button
|
||||
type='submit'
|
||||
isLoading={formik.isSubmitting}
|
||||
disabled={formik.isSubmitting}
|
||||
className='w-full rounded-lg'
|
||||
className='w-full p-3 rounded-lg text-base-100'
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import Table from '@/components/Table';
|
||||
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
import * as TanStack from '@tanstack/react-table';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import {
|
||||
cn,
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
formatNumber,
|
||||
formatVechicleNumber,
|
||||
} from '@/lib/helper';
|
||||
import { useRef } from 'react';
|
||||
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
|
||||
import DeliveryOrderExport from '@/components/pages/marketing/pdf/DeliveryOrderExport';
|
||||
import { Marketing } from '@/types/api/marketing/marketing';
|
||||
|
||||
type DeliveryOrderProductTableProps = {
|
||||
data: DeliveryOrderProductFormValues[];
|
||||
formType?: 'add' | 'edit' | 'add_deliver' | 'edit_deliver';
|
||||
onEdit: (id: number) => void;
|
||||
formType?:
|
||||
| 'add'
|
||||
| 'edit'
|
||||
| 'add_deliver'
|
||||
| 'edit_deliver'
|
||||
| 'add_delivery'
|
||||
| 'edit_delivery'
|
||||
| 'detail'
|
||||
| 'rejected'
|
||||
| 'pending'
|
||||
| string
|
||||
| null;
|
||||
marketing?: Marketing;
|
||||
onEdit: (id: number, values: DeliveryOrderProductFormValues) => void;
|
||||
onDelete: (id: number) => void;
|
||||
onAddProductClick: () => void;
|
||||
};
|
||||
@@ -26,201 +32,168 @@ const DeliveryOrderProductTable = ({
|
||||
onEdit,
|
||||
onDelete,
|
||||
onAddProductClick,
|
||||
marketing,
|
||||
}: DeliveryOrderProductTableProps) => {
|
||||
const onEditRef = useRef(onEdit);
|
||||
onEditRef.current = onEdit;
|
||||
const onDeleteRef = useRef(onDelete);
|
||||
onDeleteRef.current = onDelete;
|
||||
|
||||
const canAddData = data.filter((item) => !Boolean(item.qty));
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const cols = [
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.do_number,
|
||||
header: 'No. Pengiriman',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) => (
|
||||
<>
|
||||
{props.row.original.do_number ? props.row.original.do_number : '-'}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.vehicle_number,
|
||||
header: 'No. Polisi',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) =>
|
||||
props.row.original.vehicle_number
|
||||
? formatVechicleNumber(props.row.original.vehicle_number as string)
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
row.marketing_product?.kandang?.label,
|
||||
header: 'Kandang',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
row.marketing_product?.product_warehouse?.label,
|
||||
header: 'Produk',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) =>
|
||||
row.delivery_date
|
||||
? formatDate(row.delivery_date as string, 'DD MMM YYYY')
|
||||
: '-',
|
||||
header: 'Tanggal Delivery',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) =>
|
||||
props.row.original.delivery_date
|
||||
? formatDate(
|
||||
props.row.original.delivery_date as string,
|
||||
'DD MMM YYYY'
|
||||
)
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.unit_price,
|
||||
header: 'Harga Satuan (Rp)',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) =>
|
||||
props.row.original.unit_price
|
||||
? formatCurrency(
|
||||
parseFloat(props.row.original.unit_price as string)
|
||||
)
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.total_weight,
|
||||
header: 'Total Bobot (Kg)',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) =>
|
||||
props.row.original.total_weight
|
||||
? formatNumber(
|
||||
parseFloat(props.row.original.total_weight as string)
|
||||
)
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.qty,
|
||||
header: 'Kuantitas',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) =>
|
||||
props.row.original.qty
|
||||
? formatNumber(parseFloat(props.row.original.qty as string))
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.avg_weight,
|
||||
header: 'Avg. Bobot (Kg)',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) =>
|
||||
props.row.original.avg_weight
|
||||
? formatNumber(parseFloat(props.row.original.avg_weight as string))
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
accessorFn: (row: DeliveryOrderProductFormValues) => row.total_price,
|
||||
header: 'Total Penjualan (Rp)',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) =>
|
||||
props.row.original.total_price
|
||||
? formatCurrency(
|
||||
parseFloat(props.row.original.total_price as string)
|
||||
)
|
||||
: '-',
|
||||
},
|
||||
|
||||
{
|
||||
header: 'Aksi',
|
||||
cell: (
|
||||
props: TanStack.CellContext<DeliveryOrderProductFormValues, unknown>
|
||||
) => (
|
||||
<div className='flex flex-row gap-1 items-center justify-end h-full mt-2'>
|
||||
<>
|
||||
{props.row.original.qty && (
|
||||
<>
|
||||
<Button
|
||||
color='warning'
|
||||
className='px-2 py-1 text-sm'
|
||||
onClick={() =>
|
||||
onEditRef.current(props.row.original.id as number)
|
||||
}
|
||||
type='button'
|
||||
>
|
||||
<Icon icon='mdi:edit' width={16} height={16} /> Edit
|
||||
</Button>
|
||||
<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 && '-'}
|
||||
</>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
if (formType == 'add_deliver') {
|
||||
return cols.filter((col) => col.header != 'No. Pengiriman');
|
||||
}
|
||||
return cols;
|
||||
}, [formType, onEditRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table<DeliveryOrderProductFormValues>
|
||||
data={data}
|
||||
columns={columns}
|
||||
className={{
|
||||
tableWrapperClassName: 'overflow-x-auto min-h-full!',
|
||||
tableClassName: 'font-inter w-full table-auto min-h-full!',
|
||||
headerRowClassName: 'border-b border-b-gray-200',
|
||||
headerColumnClassName:
|
||||
'px-2 py-2 text-xs font-semibold text-gray-500 last:flex last:flex-row last:justify-end first:flex first:flex-row first:justify-start',
|
||||
bodyRowClassName: 'border-b border-b-gray-200',
|
||||
bodyColumnClassName:
|
||||
'px-2 py-2 last:flex last:flex-row last:justify-end',
|
||||
paginationClassName: 'hidden',
|
||||
}}
|
||||
emptyContent={
|
||||
<div
|
||||
className={cn(
|
||||
'w-full h-16 flex flex-col justify-center items-center gap-2'
|
||||
)}
|
||||
>
|
||||
<span className='text-gray-500'>Belum ada data pengiriman</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className='flex flex-row gap-3 mt-3'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
className='justify-start w-fit py-1 text-sm'
|
||||
onClick={onAddProductClick}
|
||||
disabled={!canAddData}
|
||||
>
|
||||
<Icon icon='mdi:plus' width={16} height={16} />
|
||||
Tambah Pengiriman
|
||||
</Button>
|
||||
<div className='size-full flex flex-col relative overflow-x-hidden gap-3'>
|
||||
{data.map((item) => {
|
||||
const doItem = marketing?.delivery_order?.find(
|
||||
(doItem) => doItem.do_number === item.do_number
|
||||
);
|
||||
return (
|
||||
<div
|
||||
className='rounded-lg border border-tools-table-outline border-base-content/5'
|
||||
key={`table-${item.id}`}
|
||||
>
|
||||
<table
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody className='w-full'>
|
||||
<tr className='border-b border-tools-table-outline border-base-content/5'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full mt-2'>
|
||||
<div>Value</div>
|
||||
{(formType === 'add_delivery' ||
|
||||
formType === 'edit_delivery' ||
|
||||
formType === 'detail') && (
|
||||
<div className='flex flex-row gap-1.5 items-center'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
onEditRef.current(item.id as number, item);
|
||||
}}
|
||||
className='p-0 hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:pencil'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
onDeleteRef.current(item.id as number);
|
||||
}}
|
||||
className='p-0 text-error hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:trash'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Tanggal Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.delivery_date ? (
|
||||
formatDate(item.delivery_date, 'DD MMM YYYY')
|
||||
) : (
|
||||
<span className='text-error'>Belum diisi</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
{item.do_number && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Pengiriman</td>
|
||||
<td className='text-sm px-4 py-3'>{item.do_number}</td>
|
||||
</tr>
|
||||
)}
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>No. Polisi</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.vehicle_number}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Gudang</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.marketing_product?.product_warehouse?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Produk</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.marketing_product?.product_warehouse?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Qty</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.qty
|
||||
? `${formatNumber(parseFloat(item.qty as string))} ${item.marketing_product?.uom ?? ''}`
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Avg Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.avg_weight
|
||||
? formatNumber(
|
||||
parseFloat(item.avg_weight as string)
|
||||
) + ' Kg'
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(parseFloat(item.total_weight as string))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(parseFloat(item.unit_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatCurrency(parseFloat(item.total_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
{doItem && (
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
Dokumen Pengiriman
|
||||
</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
<DeliveryOrderExport
|
||||
data={marketing}
|
||||
deliveryOrder={doItem}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
'use client';
|
||||
|
||||
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,
|
||||
formatVechicleNumber,
|
||||
} from '@/lib/helper';
|
||||
import { Icon } from '@iconify/react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import * as TanStack from '@tanstack/react-table';
|
||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||
|
||||
@@ -156,15 +154,51 @@ const SalesOrderProductTable = ({
|
||||
style={{
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
className='border-none'
|
||||
className='border-none w-full'
|
||||
>
|
||||
<tbody>
|
||||
<tbody className='w-full'>
|
||||
<tr className='border-b border-tools-table-outline border-base-content/5'>
|
||||
<th className='text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
<th className='w-1/3 text-start not-first:font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Label
|
||||
</th>
|
||||
<th className='text-start font-medium text-base-content/50 text-sm px-4 py-3'>
|
||||
Value
|
||||
<div className='flex w-full flex-row gap-1 items-center justify-between h-full mt-2'>
|
||||
<div>Value</div>
|
||||
{formType !== 'success' && (
|
||||
<div className='flex flex-row gap-1.5 items-center'>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
onEditRef.current(item.id as number);
|
||||
}}
|
||||
className='p-0 hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:pencil'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='ghost'
|
||||
color='none'
|
||||
onClick={() => {
|
||||
onDeleteRef.current(item.id as number);
|
||||
}}
|
||||
className='p-0 text-error hover:text-base-content'
|
||||
>
|
||||
<Icon
|
||||
icon='heroicons:trash'
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
<>
|
||||
@@ -178,48 +212,47 @@ const SalesOrderProductTable = ({
|
||||
{item.product_warehouse?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Kategori</td>
|
||||
<td className='text-sm px-4 py-3'>Telur</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Produk</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{item.product_warehouse?.label}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Tipe Konversi</td>
|
||||
<td className='text-sm px-4 py-3'>Peti 15Kg</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Peti</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(parseFloat(item.qty as string))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(parseFloat(item.total_weight as string))}
|
||||
{item.total_weight
|
||||
? formatNumber(
|
||||
parseFloat(item.total_weight as string)
|
||||
) + ' Kg'
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Butir Telur</td>
|
||||
<td className='text-sm px-4 py-3'>Avg Bobot</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(parseFloat(item.qty as string))}
|
||||
{item.avg_weight
|
||||
? formatNumber(parseFloat(item.avg_weight as string)) +
|
||||
' Kg'
|
||||
: '-'}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Qty</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{`${formatNumber(parseFloat(item.qty as string))} ${item.uom || ''}`}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Harga Satuan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(parseFloat(item.unit_price as string))}
|
||||
{formatCurrency(parseFloat(item.unit_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className='text-sm px-4 py-3'>Total Penjualan</td>
|
||||
<td className='text-sm px-4 py-3'>
|
||||
{formatNumber(parseFloat(item.total_price as string))}
|
||||
{formatCurrency(parseFloat(item.total_price as string))}
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
@@ -233,7 +266,7 @@ const SalesOrderProductTable = ({
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
className='justify-center w-full rounded-lg text-center text-sm mt-5'
|
||||
className='justify-center p-3 w-full rounded-lg text-center text-sm mt-3'
|
||||
onClick={onAddProductClick}
|
||||
>
|
||||
Tambah Produk
|
||||
|
||||
Reference in New Issue
Block a user