mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-26 00:05:45 +00:00
fix(FE): add sales person, refactor calculate total weight and price, add uom information and implement lazy load select
This commit is contained in:
+99
-35
@@ -18,6 +18,11 @@ import * as Yup from 'yup';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import useSWR from 'swr';
|
||||
import { ProductApi } from '@/services/api/master-data';
|
||||
|
||||
const roundWeight = (value: number) => Number(value.toFixed(2));
|
||||
const roundPrice = (value: number) => Math.round(value);
|
||||
|
||||
const DeliveryOrderProductForm = ({
|
||||
formState,
|
||||
@@ -43,6 +48,17 @@ const DeliveryOrderProductForm = ({
|
||||
);
|
||||
const [currentInput, setCurrentInput] = useState<string>('');
|
||||
|
||||
// ============ Fetch Data ============
|
||||
const { data: productData } = useSWR(
|
||||
selectedProduct?.value
|
||||
? ProductApi.basePath + '/' + selectedProduct?.value
|
||||
: null,
|
||||
() =>
|
||||
selectedProduct?.value
|
||||
? ProductApi.getSingle(Number(selectedProduct?.value))
|
||||
: undefined
|
||||
);
|
||||
|
||||
const salesOrder = salesOrders.find(
|
||||
(item) => item.id === initialValues?.marketing_product_id
|
||||
);
|
||||
@@ -113,22 +129,60 @@ const DeliveryOrderProductForm = ({
|
||||
|
||||
const handleBlurField = (field: string) => {
|
||||
setCurrentInput(field);
|
||||
const { qty, unit_price, total_price, avg_weight, total_weight } =
|
||||
formik.values;
|
||||
|
||||
if (field === 'unit_price' || field === 'total_price' || field === 'qty') {
|
||||
if (qty && unit_price && (field === 'unit_price' || field === 'qty')) {
|
||||
formik.setFieldValue('total_price', Number(qty) * Number(unit_price));
|
||||
} else if (qty && total_price && field === 'total_price') {
|
||||
formik.setFieldValue('unit_price', Number(total_price) / Number(qty));
|
||||
const qty = Number(formik.values.qty || 0);
|
||||
const avgWeight = Number(formik.values.avg_weight || 0);
|
||||
const totalWeight = Number(formik.values.total_weight || 0);
|
||||
const unitPrice = Number(formik.values.unit_price || 0);
|
||||
const totalPrice = Number(formik.values.total_price || 0);
|
||||
|
||||
if (qty <= 0) return;
|
||||
|
||||
switch (field) {
|
||||
// ===== SOURCE FIELDS =====
|
||||
case 'qty': {
|
||||
if (avgWeight > 0) {
|
||||
formik.setFieldValue('total_weight', roundWeight(qty * avgWeight));
|
||||
}
|
||||
|
||||
if (unitPrice > 0) {
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (field === 'avg_weight' || field === 'total_weight' || field === 'qty') {
|
||||
if (qty && avg_weight && (field === 'avg_weight' || field === 'qty')) {
|
||||
formik.setFieldValue('total_weight', Number(qty) * Number(avg_weight));
|
||||
} else if (qty && total_weight && field === 'total_weight') {
|
||||
formik.setFieldValue('avg_weight', Number(total_weight) / Number(qty));
|
||||
case 'avg_weight': {
|
||||
if (avgWeight > 0) {
|
||||
const tw = roundWeight(qty * avgWeight);
|
||||
formik.setFieldValue('total_weight', tw);
|
||||
|
||||
if (unitPrice > 0) {
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unit_price': {
|
||||
if (unitPrice > 0) {
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ===== TOTAL EDITABLE =====
|
||||
case 'total_weight': {
|
||||
if (totalWeight > 0) {
|
||||
formik.setFieldValue('avg_weight', roundWeight(totalWeight / qty));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'total_price': {
|
||||
if (totalPrice > 0) {
|
||||
formik.setFieldValue('unit_price', roundPrice(totalPrice / qty));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -183,7 +237,7 @@ const DeliveryOrderProductForm = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='grid sm:grid-cols-2 gap-4'>
|
||||
<div className='grid sm:grid-cols-3 gap-4'>
|
||||
<SelectInput
|
||||
options={options}
|
||||
label='Produk'
|
||||
@@ -287,7 +341,9 @@ const DeliveryOrderProductForm = ({
|
||||
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'
|
||||
@@ -301,33 +357,28 @@ const DeliveryOrderProductForm = ({
|
||||
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
|
||||
)?.qty +
|
||||
' ' +
|
||||
(isResponseSuccess(productData)
|
||||
? productData?.data?.uom.name
|
||||
: '')
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='divider my-6'></div>
|
||||
<div className='grid sm:grid-cols-2 gap-4'>
|
||||
<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='Harga Satuan (Rp)'
|
||||
@@ -342,7 +393,20 @@ const DeliveryOrderProductForm = ({
|
||||
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)'
|
||||
|
||||
+111
-50
@@ -11,7 +11,7 @@ import SelectInput, {
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
import { WarehouseApi } from '@/services/api/master-data';
|
||||
import { ProductApi, UomApi, 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';
|
||||
@@ -26,6 +26,10 @@ 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';
|
||||
|
||||
const roundWeight = (value: number) => Number(value.toFixed(2));
|
||||
const roundPrice = (value: number) => Math.round(value);
|
||||
|
||||
const SalesOrderProductForm = ({
|
||||
initialValues,
|
||||
@@ -39,6 +43,19 @@ const SalesOrderProductForm = ({
|
||||
}) => {
|
||||
const [formErrorMessage, setFormErrorMessage] = useState('');
|
||||
const [currentInput, setCurrentInput] = useState<string>('');
|
||||
const [selectedProductWarehouse, setSelectedProductWarehouse] =
|
||||
useState<ProductWarehouse | null>(null);
|
||||
|
||||
// ============ Fetch Data ============
|
||||
const { data: productData } = useSWR(
|
||||
selectedProductWarehouse?.product_id
|
||||
? ProductApi.basePath + '/' + selectedProductWarehouse?.product_id
|
||||
: null,
|
||||
() =>
|
||||
selectedProductWarehouse?.product_id
|
||||
? ProductApi.getSingle(selectedProductWarehouse?.product_id)
|
||||
: undefined
|
||||
);
|
||||
|
||||
// ============ Formik ============
|
||||
const formik = useFormik<SalesOrderProductFormValues>({
|
||||
@@ -69,17 +86,21 @@ const SalesOrderProductForm = ({
|
||||
const {
|
||||
options: kandangSourceOptions,
|
||||
isLoadingOptions: isLoadingKandangSourceOptions,
|
||||
setInputValue: setKandangInputValue,
|
||||
loadMore: loadMoreKandang,
|
||||
} = useSelect<Kandang>(WarehouseApi.basePath, 'id', 'name');
|
||||
|
||||
const {
|
||||
options: warehouseSourceOptions,
|
||||
rawData: warehouseSourceRawData,
|
||||
isLoadingOptions: isLoadingWarehouseSourceOptions,
|
||||
setInputValue: setWarehouseInputValue,
|
||||
loadMore: loadMoreWarehouse,
|
||||
} = useSelect<ProductWarehouse>(
|
||||
ProductWarehouseApi.basePath,
|
||||
'id',
|
||||
'product.name',
|
||||
'search',
|
||||
'',
|
||||
{
|
||||
warehouse_id: formik.values.kandang_id?.toString() ?? '',
|
||||
}
|
||||
@@ -112,6 +133,7 @@ const SalesOrderProductForm = ({
|
||||
const productWarehouse = warehouseSourceRawData?.data.find(
|
||||
(item: ProductWarehouse) => item.id === newId
|
||||
);
|
||||
setSelectedProductWarehouse(productWarehouse || null);
|
||||
formik.setFieldValue('qty', productWarehouse?.quantity);
|
||||
handleBlurField('qty');
|
||||
} else {
|
||||
@@ -139,34 +161,60 @@ const SalesOrderProductForm = ({
|
||||
|
||||
const handleBlurField = (field: string) => {
|
||||
setCurrentInput(field);
|
||||
const { qty, unit_price, total_price, avg_weight, total_weight } =
|
||||
formik.values;
|
||||
|
||||
if (field === 'unit_price' || field === 'total_price' || field === 'qty') {
|
||||
if (qty && unit_price && (field === 'unit_price' || field === 'qty')) {
|
||||
formik.setFieldValue(
|
||||
'total_price',
|
||||
(qty as number) * (unit_price as number)
|
||||
);
|
||||
} else if (qty && total_price && field === 'total_price') {
|
||||
formik.setFieldValue(
|
||||
'unit_price',
|
||||
(total_price as number) / (qty as number)
|
||||
);
|
||||
const qty = Number(formik.values.qty || 0);
|
||||
const avgWeight = Number(formik.values.avg_weight || 0);
|
||||
const totalWeight = Number(formik.values.total_weight || 0);
|
||||
const unitPrice = Number(formik.values.unit_price || 0);
|
||||
const totalPrice = Number(formik.values.total_price || 0);
|
||||
|
||||
if (qty <= 0) return;
|
||||
|
||||
switch (field) {
|
||||
// ===== SOURCE FIELDS =====
|
||||
case 'qty': {
|
||||
if (avgWeight > 0) {
|
||||
formik.setFieldValue('total_weight', roundWeight(qty * avgWeight));
|
||||
}
|
||||
|
||||
if (unitPrice > 0) {
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (field === 'avg_weight' || field === 'total_weight' || field === 'qty') {
|
||||
if (qty && avg_weight && (field === 'avg_weight' || field === 'qty')) {
|
||||
formik.setFieldValue(
|
||||
'total_weight',
|
||||
(qty as number) * (avg_weight as number)
|
||||
);
|
||||
} else if (qty && total_weight && field === 'total_weight') {
|
||||
formik.setFieldValue(
|
||||
'avg_weight',
|
||||
(total_weight as number) / (qty as number)
|
||||
);
|
||||
case 'avg_weight': {
|
||||
if (avgWeight > 0) {
|
||||
const tw = roundWeight(qty * avgWeight);
|
||||
formik.setFieldValue('total_weight', tw);
|
||||
|
||||
if (unitPrice > 0) {
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unit_price': {
|
||||
if (unitPrice > 0) {
|
||||
formik.setFieldValue('total_price', roundPrice(qty * unitPrice));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// ===== TOTAL EDITABLE =====
|
||||
case 'total_weight': {
|
||||
if (totalWeight > 0) {
|
||||
formik.setFieldValue('avg_weight', roundWeight(totalWeight / qty));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'total_price': {
|
||||
if (totalPrice > 0) {
|
||||
formik.setFieldValue('unit_price', roundPrice(totalPrice / qty));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -188,7 +236,7 @@ const SalesOrderProductForm = ({
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
<div className='grid sm:grid-cols-2 gap-4 z-200'>
|
||||
<div className='grid sm:grid-cols-3 gap-4 z-200'>
|
||||
<PatternInput
|
||||
name='vehicle_number'
|
||||
label='No. Polisi'
|
||||
@@ -215,6 +263,8 @@ const SalesOrderProductForm = ({
|
||||
value={formik.values.kandang}
|
||||
onChange={kandangChangeHandler}
|
||||
isClearable
|
||||
onInputChange={setKandangInputValue}
|
||||
onMenuScrollToBottom={loadMoreKandang}
|
||||
isError={
|
||||
formik.touched.kandang_id && Boolean(formik.errors.kandang_id)
|
||||
}
|
||||
@@ -228,6 +278,8 @@ const SalesOrderProductForm = ({
|
||||
isLoading={isLoadingWarehouseSourceOptions}
|
||||
value={formik.values.product_warehouse}
|
||||
onChange={warehouseChangeHandler}
|
||||
onInputChange={setWarehouseInputValue}
|
||||
onMenuScrollToBottom={loadMoreWarehouse}
|
||||
isClearable
|
||||
placeholder={
|
||||
formik.values.kandang_id
|
||||
@@ -243,6 +295,9 @@ const SalesOrderProductForm = ({
|
||||
}
|
||||
errorMessage={formik.errors.product_warehouse_id}
|
||||
/>
|
||||
</div>
|
||||
<div className='divider my-6'></div>
|
||||
<div className='grid sm:grid-cols-3 gap-4 z-200'>
|
||||
<NumberInput
|
||||
required
|
||||
label='Kuantitas'
|
||||
@@ -256,6 +311,15 @@ const SalesOrderProductForm = ({
|
||||
isError={formik.touched.qty && 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={
|
||||
isResponseSuccess(warehouseSourceRawData) &&
|
||||
formik.values.product_warehouse_id
|
||||
@@ -264,32 +328,13 @@ const SalesOrderProductForm = ({
|
||||
(item) => item.id === formik.values.product_warehouse_id
|
||||
)?.quantity ?? 0
|
||||
)} ${
|
||||
warehouseSourceRawData?.data?.find(
|
||||
(item) => item.id === formik.values.product_warehouse_id
|
||||
)?.product?.uom?.name ?? ''
|
||||
isResponseSuccess(productData)
|
||||
? productData?.data?.uom.name
|
||||
: ''
|
||||
}`
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='divider my-6'></div>
|
||||
<div className='grid sm:grid-cols-2 gap-4 z-200'>
|
||||
<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={
|
||||
formik.touched.avg_weight && Boolean(formik.errors.avg_weight)
|
||||
}
|
||||
errorMessage={formik.errors.avg_weight}
|
||||
placeholder='Masukan Bobot Rata-rata'
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Harga Satuan (Rp)'
|
||||
@@ -306,6 +351,22 @@ const SalesOrderProductForm = ({
|
||||
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={
|
||||
formik.touched.avg_weight && Boolean(formik.errors.avg_weight)
|
||||
}
|
||||
errorMessage={formik.errors.avg_weight}
|
||||
placeholder='Masukan Bobot Rata-rata'
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Total Bobot (Kg)'
|
||||
|
||||
Reference in New Issue
Block a user