mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 21:41:57 +00:00
Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into dev/restu
This commit is contained in:
@@ -325,7 +325,7 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
};
|
||||
|
||||
const useSelect = <T,>(
|
||||
basePath: string,
|
||||
basePath: string | null,
|
||||
valueKey: keyof T | string,
|
||||
labelKey: keyof T | string,
|
||||
searchKey: string = 'search',
|
||||
@@ -354,7 +354,7 @@ const useSelect = <T,>(
|
||||
[limitKey]: String(limit),
|
||||
}).toString();
|
||||
|
||||
return `${basePath}?${qs}`;
|
||||
return basePath ? `${basePath}?${qs}` : null;
|
||||
};
|
||||
|
||||
const {
|
||||
|
||||
@@ -163,6 +163,7 @@ const ClosingsTable = () => {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
const [selectedLocation, setSelectedLocation] = useState<OptionType | null>(
|
||||
@@ -228,6 +229,7 @@ const ClosingsTable = () => {
|
||||
value={selectedLocation}
|
||||
onChange={locationChangeHandler}
|
||||
onInputChange={setLocationInputValue}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-6',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||
import { isResponseError } from '@/lib/api-helper';
|
||||
import { InventoryAdjustmentApi } from '@/services/api/inventory';
|
||||
import {
|
||||
CreateInventoryAdjustmentPayload,
|
||||
@@ -22,12 +22,18 @@ import {
|
||||
} from '@/services/api/master-data';
|
||||
import Button from '@/components/Button';
|
||||
import { Icon } from '@iconify/react';
|
||||
import SelectInput, { OptionType } from '@/components/input/SelectInput';
|
||||
import SelectInput, {
|
||||
OptionType,
|
||||
useSelect,
|
||||
} from '@/components/input/SelectInput';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import { RadioGroup } from '@/components/input/RadioInput';
|
||||
import TextArea from '@/components/input/TextArea';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { ProductCategory } from '@/types/api/master-data/product-category';
|
||||
import { Product } from '@/types/api/master-data/product';
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
|
||||
interface InventoryAdjustmentFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -44,10 +50,7 @@ const InventoryAdjustmentForm = ({
|
||||
InventoryAdjustmentFormErrorMessage,
|
||||
setInventoryAdjustmentFormErrorMessage,
|
||||
] = useState('');
|
||||
const [selectedProductCategories, setSelectedProductCategories] =
|
||||
useState('');
|
||||
const [disabledProduct, setDisabledProduct] = useState(true);
|
||||
const [optionsProduct, setOptionsProduct] = useState<OptionType[]>([]);
|
||||
const [quantityLabel, setQuantityLabel] = useState('Tambah Stok');
|
||||
|
||||
// Submit Handler
|
||||
@@ -108,45 +111,30 @@ const InventoryAdjustmentForm = ({
|
||||
});
|
||||
|
||||
// Fetch Data
|
||||
const productCategoriesUrl = `${
|
||||
ProductCategoryApi.basePath
|
||||
}?${new URLSearchParams({
|
||||
search: '',
|
||||
}).toString()}`;
|
||||
const { data: productCategories, isLoading: isLoadingProductCategories } =
|
||||
useSWR(productCategoriesUrl, ProductCategoryApi.getAllFetcher);
|
||||
const {
|
||||
setInputValue: setProductCategoryInputValue,
|
||||
options: productCategoryOptions,
|
||||
isLoadingOptions: isLoadingProductCategoryOptions,
|
||||
loadMore: loadMoreProductCategories,
|
||||
} = useSelect<ProductCategory>(ProductCategoryApi.basePath, 'id', 'name');
|
||||
|
||||
const productUrl = `${ProductApi.basePath}?${new URLSearchParams({
|
||||
search: '',
|
||||
product_category_id: selectedProductCategories,
|
||||
}).toString()}`;
|
||||
const { data: products, isLoading: isLoadingProducts } = useSWR(
|
||||
productUrl,
|
||||
ProductApi.getAllFetcher
|
||||
);
|
||||
const {
|
||||
setInputValue: setProductInputValue,
|
||||
options: productOptions,
|
||||
isLoadingOptions: isLoadingProductOptions,
|
||||
loadMore: loadMoreProducts,
|
||||
} = useSelect<Product>(ProductApi.basePath, 'id', 'name', 'search', {
|
||||
product_category_id: formik.values.product_category_id
|
||||
? String(formik.values.product_category_id)
|
||||
: '',
|
||||
});
|
||||
|
||||
const warehouseUrl = `${WarehouseApi.basePath}?${new URLSearchParams({
|
||||
search: '',
|
||||
limit: '100',
|
||||
}).toString()}`;
|
||||
const { data: warehouses, isLoading: isLoadingWarehouses } = useSWR(
|
||||
warehouseUrl,
|
||||
WarehouseApi.getAllFetcher
|
||||
);
|
||||
|
||||
// Map Data to Options
|
||||
const optionsProductCategory = isResponseSuccess(productCategories)
|
||||
? productCategories?.data.map((productCategory) => ({
|
||||
value: productCategory.id,
|
||||
label: productCategory.name,
|
||||
}))
|
||||
: [];
|
||||
const optionsWarehouse = isResponseSuccess(warehouses)
|
||||
? warehouses?.data.map((warehouse) => ({
|
||||
value: warehouse.id,
|
||||
label: warehouse.name,
|
||||
}))
|
||||
: [];
|
||||
const {
|
||||
setInputValue: setWarehouseInputValue,
|
||||
options: warehouseOptions,
|
||||
isLoadingOptions: isLoadingWarehouseOptions,
|
||||
loadMore: loadMoreWarehouses,
|
||||
} = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name');
|
||||
|
||||
// Options Handler
|
||||
const productCategoryChangeHandler = (
|
||||
@@ -157,7 +145,6 @@ const InventoryAdjustmentForm = ({
|
||||
|
||||
formik.setFieldValue('product_category', val);
|
||||
|
||||
setSelectedProductCategories((val as OptionType)?.value as string);
|
||||
const disabled = (val as OptionType)?.value == null;
|
||||
setDisabledProduct(disabled);
|
||||
formik.setFieldValue('product_id', 0);
|
||||
@@ -193,9 +180,6 @@ const InventoryAdjustmentForm = ({
|
||||
// Effect
|
||||
useEffect(() => {
|
||||
if (initialValues?.product_warehouse?.product?.id) {
|
||||
setSelectedProductCategories(
|
||||
String(initialValues.product_warehouse.product.id)
|
||||
);
|
||||
setDisabledProduct(false);
|
||||
formik.setFieldValue(
|
||||
'product_id',
|
||||
@@ -219,25 +203,10 @@ const InventoryAdjustmentForm = ({
|
||||
);
|
||||
formik.setFieldValue('note', initialValues.note);
|
||||
}
|
||||
}, [
|
||||
formik,
|
||||
initialValues,
|
||||
setQuantityLabel,
|
||||
setDisabledProduct,
|
||||
setSelectedProductCategories,
|
||||
]);
|
||||
}, [formik, initialValues, setQuantityLabel, setDisabledProduct]);
|
||||
useEffect(() => {
|
||||
formikSetValues(formikInitialValues as InventoryAdjustmentFormValues);
|
||||
}, [formikSetValues, formikInitialValues]);
|
||||
useEffect(() => {
|
||||
if (isResponseSuccess(products)) {
|
||||
const options = products.data.map((p) => ({
|
||||
value: p.id,
|
||||
label: p.name,
|
||||
}));
|
||||
setOptionsProduct(options);
|
||||
}
|
||||
}, [products]);
|
||||
|
||||
// Utils Function
|
||||
const formatNumber = (value: string) => {
|
||||
@@ -282,9 +251,10 @@ const InventoryAdjustmentForm = ({
|
||||
label='Kategori Produk'
|
||||
value={formik.values.product_category as OptionType}
|
||||
onChange={productCategoryChangeHandler}
|
||||
onInputChange={setSelectedProductCategories}
|
||||
options={optionsProductCategory}
|
||||
isLoading={isLoadingProductCategories}
|
||||
onInputChange={setProductCategoryInputValue}
|
||||
options={productCategoryOptions}
|
||||
onMenuScrollToBottom={loadMoreProductCategories}
|
||||
isLoading={isLoadingProductCategoryOptions}
|
||||
isError={
|
||||
formik.touched.product_category &&
|
||||
Boolean(formik.errors.product_category)
|
||||
@@ -300,8 +270,10 @@ const InventoryAdjustmentForm = ({
|
||||
label='Produk'
|
||||
value={formik.values.product as OptionType}
|
||||
onChange={productChangeHandler}
|
||||
options={optionsProduct}
|
||||
isLoading={isLoadingProducts}
|
||||
onInputChange={setProductInputValue}
|
||||
options={productOptions}
|
||||
onMenuScrollToBottom={loadMoreProducts}
|
||||
isLoading={isLoadingProductOptions}
|
||||
isError={formik.touched.product && Boolean(formik.errors.product)}
|
||||
errorMessage={formik.errors.product as string}
|
||||
isDisabled={type === 'detail' || disabledProduct}
|
||||
@@ -314,8 +286,10 @@ const InventoryAdjustmentForm = ({
|
||||
label='Warehouse'
|
||||
value={formik.values.warehouse as OptionType}
|
||||
onChange={warehouseChangeHandler}
|
||||
options={optionsWarehouse}
|
||||
isLoading={isLoadingWarehouses}
|
||||
onInputChange={setWarehouseInputValue}
|
||||
options={warehouseOptions}
|
||||
onMenuScrollToBottom={loadMoreWarehouses}
|
||||
isLoading={isLoadingWarehouseOptions}
|
||||
isError={
|
||||
formik.touched.warehouse && Boolean(formik.errors.warehouse)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ import Card from '@/components/Card';
|
||||
import { S3_PUBLIC_BASE_URL } from '@/config/constant';
|
||||
import { getUniqueFormikErrors } from '@/lib/formik-helper';
|
||||
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||
import { Warehouse } from '@/types/api/master-data/warehouse';
|
||||
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
|
||||
|
||||
interface MovementFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -49,10 +51,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
|
||||
// ===== STATE MANAGEMENT =====
|
||||
const [movementFormErrorMessage, setMovementFormErrorMessage] = useState('');
|
||||
const [
|
||||
productWarehouseSelectInputValue,
|
||||
setProductWarehouseSelectInputValue,
|
||||
] = useState('');
|
||||
const [selectedProducts, setSelectedProducts] = useState<number[]>([]);
|
||||
const [selectedDeliveries, setSelectedDeliveries] = useState<number[]>([]);
|
||||
const [formErrorList, setFormErrorList] = useState<string[]>([]);
|
||||
@@ -93,10 +91,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
|
||||
// ===== USE SELECT HOOKS =====
|
||||
const {
|
||||
inputValue: warehouseSelectInputValue,
|
||||
setInputValue: setWarehouseSelectInputValue,
|
||||
isLoadingOptions: isLoadingWarehouses,
|
||||
} = useSelect(WarehouseApi.basePath, 'id', 'name', 'search');
|
||||
loadMore: loadMoreWarehouses,
|
||||
rawData: warehouses,
|
||||
} = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
// ===== SELECT INPUT DATA =====
|
||||
const {
|
||||
@@ -107,12 +106,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
category: 'BOP',
|
||||
});
|
||||
|
||||
const warehousesUrl = `${WarehouseApi.basePath}?${new URLSearchParams({ search: warehouseSelectInputValue }).toString()}`;
|
||||
const { data: warehouses } = useSWR(
|
||||
warehousesUrl,
|
||||
WarehouseApi.getAllFetcher
|
||||
);
|
||||
|
||||
// ===== DATA PROCESSING =====
|
||||
const warehouseStockMap = useMemo(() => {
|
||||
if (!isResponseSuccess(allProductWarehouses)) return new Map();
|
||||
@@ -269,25 +262,22 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
});
|
||||
|
||||
// ===== PRODUCT WAREHOUSE FETCHING (after form initialization) =====
|
||||
const getProductWarehousesUrl = useCallback(() => {
|
||||
const productWarehouseParams = new URLSearchParams({
|
||||
search: productWarehouseSelectInputValue,
|
||||
});
|
||||
if (formik.values.source_warehouse_id) {
|
||||
productWarehouseParams.append(
|
||||
'warehouse_id',
|
||||
formik.values.source_warehouse_id.toString()
|
||||
);
|
||||
const {
|
||||
setInputValue: setProductWarehouseSelectInputValue,
|
||||
isLoadingOptions: isLoadingProductWarehouses,
|
||||
loadMore: loadMoreProductWarehouses,
|
||||
rawData: productWarehouses,
|
||||
} = useSelect<ProductWarehouse>(
|
||||
formik.values.source_warehouse_id ? ProductWarehouseApi.basePath : null,
|
||||
'id',
|
||||
'name',
|
||||
'search',
|
||||
{
|
||||
warehouse_id: formik.values.source_warehouse_id
|
||||
? formik.values.source_warehouse_id.toString()
|
||||
: '',
|
||||
}
|
||||
return `${ProductWarehouseApi.basePath}?${productWarehouseParams.toString()}`;
|
||||
}, [formik.values.source_warehouse_id, productWarehouseSelectInputValue]);
|
||||
|
||||
const productWarehousesUrl = getProductWarehousesUrl();
|
||||
const { data: productWarehouses, isLoading: isLoadingProductWarehouses } =
|
||||
useSWR(
|
||||
formik.values.source_warehouse_id ? productWarehousesUrl : null,
|
||||
ProductWarehouseApi.getAllFetcher
|
||||
);
|
||||
);
|
||||
|
||||
const productWarehouseOptions = isResponseSuccess(productWarehouses)
|
||||
? productWarehouses?.data.map((pw) => ({
|
||||
@@ -1006,6 +996,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
}}
|
||||
options={warehouseOptions}
|
||||
onInputChange={setWarehouseSelectInputValue}
|
||||
onMenuScrollToBottom={loadMoreWarehouses}
|
||||
isLoading={isLoadingWarehouses}
|
||||
isError={
|
||||
formik.touched.source_warehouse_id &&
|
||||
@@ -1104,6 +1095,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
options={warehouseOptions}
|
||||
onInputChange={setWarehouseSelectInputValue}
|
||||
isLoading={isLoadingWarehouses}
|
||||
onMenuScrollToBottom={loadMoreWarehouses}
|
||||
isError={
|
||||
formik.touched.destination_warehouse_id &&
|
||||
Boolean(formik.errors.destination_warehouse_id)
|
||||
@@ -1263,6 +1255,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
}}
|
||||
options={productWarehouseOptions}
|
||||
onInputChange={setProductWarehouseSelectInputValue}
|
||||
onMenuScrollToBottom={loadMoreProductWarehouses}
|
||||
isLoading={isLoadingProductWarehouses}
|
||||
isDisabled={
|
||||
type === 'detail' ||
|
||||
|
||||
@@ -215,7 +215,7 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
|
||||
required
|
||||
label='Nama'
|
||||
name='name'
|
||||
placeholder='Masukkan nama lokasi'
|
||||
placeholder='Masukkan nama kandang'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
|
||||
@@ -229,7 +229,7 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
|
||||
required
|
||||
label='Nama'
|
||||
name='name'
|
||||
placeholder='Masukkan nama lokasi'
|
||||
placeholder='Masukkan nama nonstock'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as Yup from 'yup';
|
||||
type ProductFormSchemaType = {
|
||||
name: string;
|
||||
brand: string;
|
||||
sku: string;
|
||||
sku?: string;
|
||||
uom?: {
|
||||
value: number;
|
||||
label: string;
|
||||
@@ -15,10 +15,16 @@ type ProductFormSchemaType = {
|
||||
} | null;
|
||||
product_category_id: number;
|
||||
product_price: number | string;
|
||||
selling_price: number | string;
|
||||
tax: number | string;
|
||||
expiry_period: number | string;
|
||||
supplier_ids: number[];
|
||||
selling_price?: number | string;
|
||||
tax?: number | string;
|
||||
expiry_period?: number | string;
|
||||
suppliers: {
|
||||
supplier: {
|
||||
value: number;
|
||||
label: string;
|
||||
} | null;
|
||||
price: number;
|
||||
}[];
|
||||
flags: string[];
|
||||
};
|
||||
|
||||
@@ -26,7 +32,7 @@ export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
|
||||
Yup.object({
|
||||
name: Yup.string().required('Nama wajib diisi!'),
|
||||
brand: Yup.string().required('Merek wajib diisi!'),
|
||||
sku: Yup.string().required('SKU wajib diisi!'),
|
||||
sku: Yup.string(),
|
||||
|
||||
uom: Yup.object({
|
||||
value: Yup.number()
|
||||
@@ -58,23 +64,34 @@ export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
|
||||
.min(1, 'Harga produk tidak boleh kurang dari 1!'),
|
||||
|
||||
selling_price: Yup.number()
|
||||
.required('Harga jual wajib diisi!')
|
||||
.typeError('Harga jual wajib diisi!')
|
||||
.typeError('Harga hanya boleh angka!')
|
||||
.min(1, 'Harga jual tidak boleh kurang dari 1!'),
|
||||
|
||||
tax: Yup.number()
|
||||
.required('Pajak wajib diisi!')
|
||||
.typeError('Pajak wajib diisi!')
|
||||
.typeError('Pajak hanya boleh angka!')
|
||||
.min(0, 'Pajak tidak boleh kurang dari 0!')
|
||||
.max(100, 'Pajak tidak boleh lebih dari 100%!'),
|
||||
|
||||
expiry_period: Yup.number()
|
||||
.required('Periode kadaluarsa wajib diisi!')
|
||||
.typeError('Periode kadaluarsa wajib diisi!')
|
||||
.typeError('Periode kadaluarsa hanya boleh angka!')
|
||||
.min(1, 'Periode kadaluarsa tidak boleh kurang dari 1 hari!'),
|
||||
|
||||
supplier_ids: Yup.array()
|
||||
.of(Yup.number().required().typeError('Supplier tidak valid!'))
|
||||
suppliers: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
supplier: Yup.object({
|
||||
value: Yup.number()
|
||||
.min(1, 'Supplier wajib dipilih!')
|
||||
.required('Supplier wajib dipilih!')
|
||||
.typeError('Supplier wajib dipilih!'),
|
||||
label: Yup.string().required('Supplier wajib dipilih!'),
|
||||
}).required('Supplier wajib dipilih!'),
|
||||
price: Yup.number()
|
||||
.min(1, 'Harga tidak boleh kurang dari 1!')
|
||||
.required('Harga wajib diisi!')
|
||||
.typeError('Harga wajib diisi!'),
|
||||
})
|
||||
)
|
||||
.min(1, 'Minimal harus ada 1 supplier!')
|
||||
.required('Supplier wajib diisi!'),
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ import { cn } from '@/lib/helper';
|
||||
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
|
||||
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import Card from '@/components/Card';
|
||||
import { removeArrayItemAndSync } from '@/lib/utils/formik';
|
||||
|
||||
interface ProductFormProps {
|
||||
type?: 'add' | 'edit' | 'detail';
|
||||
@@ -101,7 +103,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
selling_price: initialValues?.selling_price ?? '',
|
||||
tax: initialValues?.tax ?? '',
|
||||
expiry_period: initialValues?.expiry_period ?? '',
|
||||
supplier_ids: initialValues?.suppliers?.map((s) => s.id) ?? [],
|
||||
suppliers: initialValues?.suppliers
|
||||
? initialValues.suppliers.map((supplier) => ({
|
||||
supplier: {
|
||||
value: supplier.id,
|
||||
label: supplier.name,
|
||||
},
|
||||
price: supplier.price,
|
||||
}))
|
||||
: [],
|
||||
flags: initialValues?.flags ?? [],
|
||||
}),
|
||||
[initialValues]
|
||||
@@ -120,12 +130,17 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
uom_id: values.uom_id,
|
||||
product_category_id: values.product_category_id,
|
||||
product_price: parseInt(values.product_price.toString()) || 0,
|
||||
selling_price: parseInt(values.selling_price.toString()) || 0,
|
||||
tax: parseInt(values.tax.toString()) || 0,
|
||||
expiry_period: parseInt(values.expiry_period.toString()) || 0,
|
||||
supplier_ids: values.supplier_ids.filter(
|
||||
(id): id is number => typeof id === 'number'
|
||||
),
|
||||
selling_price: values.selling_price
|
||||
? parseInt(values.selling_price.toString()) || 0
|
||||
: undefined,
|
||||
tax: values.tax ? parseInt(values.tax.toString()) || 0 : undefined,
|
||||
expiry_period: values.expiry_period
|
||||
? parseInt(values.expiry_period.toString()) || 0
|
||||
: undefined,
|
||||
suppliers: values.suppliers.map((s) => ({
|
||||
supplier_id: s.supplier?.value as number,
|
||||
price: parseInt(s.price.toString()) || 0,
|
||||
})),
|
||||
flags: values.flags.filter((f): f is string => typeof f === 'string'),
|
||||
};
|
||||
switch (type) {
|
||||
@@ -179,13 +194,29 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
category: 'SAPRONAK',
|
||||
});
|
||||
|
||||
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
const arr = Array.isArray(val) ? val : val ? [val] : [];
|
||||
formik.setFieldTouched('supplier_ids', true);
|
||||
formik.setFieldValue(
|
||||
'supplier_ids',
|
||||
arr.map((v) => (v as OptionType).value)
|
||||
);
|
||||
const filteredSupplierOptions = useMemo(() => {
|
||||
return supplierOptions.filter((opt) => {
|
||||
return !formik.values.suppliers.some(
|
||||
(s) => s.supplier?.value === opt.value
|
||||
);
|
||||
});
|
||||
}, [supplierOptions, formik.values.suppliers]);
|
||||
|
||||
const addSupplierHandler = () => {
|
||||
formik.setFieldValue('suppliers', [
|
||||
...formik.values.suppliers,
|
||||
{
|
||||
supplier_id: '',
|
||||
price: formik.values.product_price,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const deleteSupplierItemHandler = (idx: number) => {
|
||||
const path = 'suppliers';
|
||||
|
||||
// trims values, errors, and touched at idx
|
||||
removeArrayItemAndSync(formik, path, idx);
|
||||
};
|
||||
|
||||
const deleteProductClickHandler = () => {
|
||||
@@ -201,6 +232,19 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
router.push('/master-data/product');
|
||||
};
|
||||
|
||||
const isSupplierRepeaterError = (
|
||||
column: 'supplier' | 'price',
|
||||
supplierIdx: number
|
||||
) => {
|
||||
return (
|
||||
formik.touched.suppliers?.[supplierIdx]?.[column] &&
|
||||
Boolean(
|
||||
formik.errors.suppliers?.[supplierIdx] instanceof Object &&
|
||||
formik.errors.suppliers?.[supplierIdx]?.[column]
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
formikSetValues(formikInitialValues);
|
||||
}, [formikSetValues, formikInitialValues]);
|
||||
@@ -271,7 +315,6 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
<TextInput
|
||||
required
|
||||
label='SKU'
|
||||
name='sku'
|
||||
placeholder='Masukkan SKU...'
|
||||
@@ -344,7 +387,6 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Harga Jual'
|
||||
name='selling_price'
|
||||
placeholder='Masukkan harga jual...'
|
||||
@@ -366,7 +408,6 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
</div>
|
||||
<div className='grid sm:grid-cols-2 gap-4'>
|
||||
<NumberInput
|
||||
required
|
||||
label='Pajak (%)'
|
||||
name='tax'
|
||||
placeholder='Masukkan pajak...'
|
||||
@@ -383,7 +424,6 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
<NumberInput
|
||||
required
|
||||
label='Periode Kadaluarsa (hari)'
|
||||
name='expiry_period'
|
||||
placeholder='Masukkan periode kadaluarsa...'
|
||||
@@ -403,28 +443,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
readOnly={type === 'detail'}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid sm:grid-cols-2 gap-4'>
|
||||
<SelectInput
|
||||
required
|
||||
label='Supplier'
|
||||
placeholder='Pilih supplier...'
|
||||
isMulti
|
||||
value={supplierOptions.filter((opt) =>
|
||||
(formik.values.supplier_ids || []).includes(opt.value)
|
||||
)}
|
||||
onChange={supplierChangeHandler}
|
||||
options={supplierOptions}
|
||||
onInputChange={setSupplierSelectInputValue}
|
||||
onMenuScrollToBottom={loadMoreSuppliers}
|
||||
isLoading={isLoadingSuppliers}
|
||||
isError={
|
||||
formik.touched.supplier_ids &&
|
||||
Boolean(formik.errors.supplier_ids)
|
||||
}
|
||||
errorMessage={formik.errors.supplier_ids as string}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
/>
|
||||
<div className='grid sm:grid-cols-1 gap-4'>
|
||||
<SelectInput
|
||||
required
|
||||
label='Flags'
|
||||
@@ -447,6 +466,126 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
|
||||
isClearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid sm:grid-cols-1 gap-4'>
|
||||
{type !== 'detail' && formik.values.suppliers.length === 0 && (
|
||||
<Button
|
||||
type='button'
|
||||
color='success'
|
||||
onClick={addSupplierHandler}
|
||||
className='w-fit mx-auto'
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} /> Tambah
|
||||
Supplier
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{formik.values.suppliers.length > 0 && (
|
||||
<Card
|
||||
className={{
|
||||
wrapper: 'w-full',
|
||||
body: 'p-4 shadow',
|
||||
}}
|
||||
>
|
||||
<div className='mb-4 text-center'>
|
||||
<h4 className='font-bold text-xl'>Supplier</h4>
|
||||
</div>
|
||||
|
||||
<div className='overflow-x-auto'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='after:content-["*"] after:text-red-500 after:ml-0.5'>
|
||||
Supplier
|
||||
</th>
|
||||
<th className='after:content-["*"] after:text-red-500 after:ml-0.5'>
|
||||
Harga
|
||||
</th>
|
||||
<th>Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{formik.values.suppliers.map((supplier, idx) => (
|
||||
<tr key={idx}>
|
||||
<td className='p-2 w-full max-w-1/2'>
|
||||
<SelectInput
|
||||
placeholder='Pilih Supplier'
|
||||
options={filteredSupplierOptions}
|
||||
onInputChange={setSupplierSelectInputValue}
|
||||
onMenuScrollToBottom={loadMoreSuppliers}
|
||||
isLoading={isLoadingSuppliers}
|
||||
value={formik.values.suppliers[idx].supplier}
|
||||
onChange={(val) => {
|
||||
formik.setFieldValue(
|
||||
`suppliers.${idx}.supplier`,
|
||||
val
|
||||
);
|
||||
}}
|
||||
isError={isSupplierRepeaterError(
|
||||
'supplier',
|
||||
idx
|
||||
)}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'min-w-48 w-full',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td className='p-2 w-full max-w-1/2'>
|
||||
<NumberInput
|
||||
required
|
||||
name={`suppliers.${idx}.price`}
|
||||
placeholder='Masukkan harga...'
|
||||
value={formik.values.suppliers[idx].price}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
decimalScale={2}
|
||||
allowNegative={false}
|
||||
thousandSeparator=','
|
||||
decimalSeparator='.'
|
||||
inputPrefix='Rp '
|
||||
isError={isSupplierRepeaterError('price', idx)}
|
||||
readOnly={type === 'detail'}
|
||||
className={{
|
||||
wrapper: 'min-w-48 w-full',
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
{type !== 'detail' && (
|
||||
<td>
|
||||
<Button
|
||||
type='button'
|
||||
color='error'
|
||||
onClick={() => deleteSupplierItemHandler(idx)}
|
||||
>
|
||||
<Icon
|
||||
icon='material-symbols:delete-outline-rounded'
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
</Button>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div className='w-full flex flex-row justify-center'>
|
||||
<Button
|
||||
type='button'
|
||||
color='success'
|
||||
onClick={addSupplierHandler}
|
||||
>
|
||||
<Icon icon='ic:round-plus' width={24} height={24} />{' '}
|
||||
Tambah Supplier
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-row justify-between gap-2 flex-wrap'>
|
||||
{type !== 'add' && (
|
||||
|
||||
@@ -330,7 +330,7 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
|
||||
required
|
||||
label='Nama'
|
||||
name='name'
|
||||
placeholder='Masukkan nama lokasi'
|
||||
placeholder='Masukkan nama warehouse'
|
||||
value={formik.values.name}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
|
||||
@@ -87,6 +87,7 @@ const DailyMarketingReportContent = () => {
|
||||
setInputValue: setAreaInputValue,
|
||||
options: areaOptions,
|
||||
isLoadingOptions: isLoadingAreaOptions,
|
||||
loadMore: loadMoreAreas,
|
||||
} = useSelect<Area>(AreaApi.basePath, 'id', 'name');
|
||||
|
||||
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -101,6 +102,7 @@ const DailyMarketingReportContent = () => {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -118,6 +120,7 @@ const DailyMarketingReportContent = () => {
|
||||
setInputValue: setWarehouseInputValue,
|
||||
options: warehouseOptions,
|
||||
isLoadingOptions: isLoadingWarehouseOptions,
|
||||
loadMore: loadMoreWarehouses,
|
||||
} = useSelect<Warehouse>(WarehouseApi.basePath, 'id', 'name');
|
||||
|
||||
const warehouseChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -135,6 +138,7 @@ const DailyMarketingReportContent = () => {
|
||||
setInputValue: setCustomerInputValue,
|
||||
options: customerOptions,
|
||||
isLoadingOptions: isLoadingCustomerOptions,
|
||||
loadMore: loadMoreCustomers,
|
||||
} = useSelect<Customer>(CustomerApi.basePath, 'id', 'name');
|
||||
|
||||
const customerChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -298,6 +302,7 @@ const DailyMarketingReportContent = () => {
|
||||
value={selectedArea}
|
||||
onChange={areaChangeHandler}
|
||||
onInputChange={setAreaInputValue}
|
||||
onMenuScrollToBottom={loadMoreAreas}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
|
||||
@@ -312,6 +317,7 @@ const DailyMarketingReportContent = () => {
|
||||
value={selectedLocation}
|
||||
onChange={locationChangeHandler}
|
||||
onInputChange={setLocationInputValue}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
|
||||
@@ -326,6 +332,7 @@ const DailyMarketingReportContent = () => {
|
||||
value={selectedWarehouse}
|
||||
onChange={warehouseChangeHandler}
|
||||
onInputChange={setWarehouseInputValue}
|
||||
onMenuScrollToBottom={loadMoreWarehouses}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
|
||||
@@ -340,6 +347,7 @@ const DailyMarketingReportContent = () => {
|
||||
value={selectedCustomer}
|
||||
onChange={customerChangeHandler}
|
||||
onInputChange={setCustomerInputValue}
|
||||
onMenuScrollToBottom={loadMoreCustomers}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
|
||||
|
||||
@@ -26,6 +26,15 @@ import MenuItem from '@/components/menu/MenuItem';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { generateReportExpensePDF } from './pdf/ReportExpenseExport';
|
||||
import toast from 'react-hot-toast';
|
||||
import {
|
||||
KandangApi,
|
||||
LocationApi,
|
||||
NonstockApi,
|
||||
SupplierApi,
|
||||
} from '@/services/api/master-data';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import { Kandang } from '@/types/api/master-data/kandang';
|
||||
import { Nonstock } from '@/types/api/master-data/nonstock';
|
||||
|
||||
const ReportExpenseTable = () => {
|
||||
// ===== STATE MANAGEMENT =====
|
||||
@@ -64,16 +73,33 @@ const ReportExpenseTable = () => {
|
||||
});
|
||||
|
||||
// ===== SELECT OPTIONS =====
|
||||
const { options: optionsLocation, isLoadingOptions: isLoadingLocation } =
|
||||
useSelect(`/master-data/locations`, 'id', 'name');
|
||||
const { options: optionsSupplier, isLoadingOptions: isLoadingSupplier } =
|
||||
useSelect(`/master-data/suppliers`, 'id', 'name');
|
||||
const { options: optionsKandang, isLoadingOptions: isLoadingKandang } =
|
||||
useSelect(`/master-data/kandangs`, 'id', 'name', '', {
|
||||
location_id: filterState.location_id,
|
||||
});
|
||||
const { options: optionsNonstock, isLoadingOptions: isLoadingNonstock } =
|
||||
useSelect(`/master-data/nonstocks`, 'id', 'name');
|
||||
const {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
|
||||
|
||||
const {
|
||||
setInputValue: setSupplierInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingSupplierOptions,
|
||||
loadMore: loadMoreSuppliers,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||
|
||||
const {
|
||||
setInputValue: setKandangInputValue,
|
||||
options: kandangOptions,
|
||||
isLoadingOptions: isLoadingKandangOptions,
|
||||
loadMore: loadMoreKandangs,
|
||||
} = useSelect<Kandang>(KandangApi.basePath, 'id', 'name');
|
||||
|
||||
const {
|
||||
setInputValue: setNonstockInputValue,
|
||||
options: nonstockOptions,
|
||||
isLoadingOptions: isLoadingNonstockOptions,
|
||||
loadMore: loadMoreNonstocks,
|
||||
} = useSelect<Nonstock>(NonstockApi.basePath, 'id', 'name');
|
||||
|
||||
const categoryOptions = useMemo(
|
||||
() => [
|
||||
@@ -86,31 +112,31 @@ const ReportExpenseTable = () => {
|
||||
// Mendapatkan value option select dari filter state
|
||||
const selectedLocation = useMemo(
|
||||
() =>
|
||||
optionsLocation.find(
|
||||
locationOptions.find(
|
||||
(opt) => String(opt.value) === filterState.location_id
|
||||
) || null,
|
||||
[optionsLocation, filterState.location_id]
|
||||
[locationOptions, filterState.location_id]
|
||||
);
|
||||
const selectedSupplier = useMemo(
|
||||
() =>
|
||||
optionsSupplier.find(
|
||||
supplierOptions.find(
|
||||
(opt) => String(opt.value) === filterState.supplier_id
|
||||
) || null,
|
||||
[optionsSupplier, filterState.supplier_id]
|
||||
[supplierOptions, filterState.supplier_id]
|
||||
);
|
||||
const selectedKandang = useMemo(
|
||||
() =>
|
||||
optionsKandang.find(
|
||||
kandangOptions.find(
|
||||
(opt) => String(opt.value) === filterState.kandang_id
|
||||
) || null,
|
||||
[optionsKandang, filterState.kandang_id]
|
||||
[kandangOptions, filterState.kandang_id]
|
||||
);
|
||||
const selectedNonstock = useMemo(
|
||||
() =>
|
||||
optionsNonstock.find(
|
||||
nonstockOptions.find(
|
||||
(opt) => String(opt.value) === filterState.nonstock_id
|
||||
) || null,
|
||||
[optionsNonstock, filterState.nonstock_id]
|
||||
[nonstockOptions, filterState.nonstock_id]
|
||||
);
|
||||
const selectedCategory = useMemo(
|
||||
() =>
|
||||
@@ -756,38 +782,46 @@ const ReportExpenseTable = () => {
|
||||
<SelectInput
|
||||
isClearable
|
||||
label='Lokasi'
|
||||
options={optionsLocation}
|
||||
isLoading={isLoadingLocation}
|
||||
options={locationOptions}
|
||||
isLoading={isLoadingLocationOptions}
|
||||
placeholder='Lokasi'
|
||||
value={selectedLocation}
|
||||
onChange={locationChangeHandler}
|
||||
onInputChange={setLocationInputValue}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
/>
|
||||
<SelectInput
|
||||
isClearable
|
||||
label='Kandang'
|
||||
options={optionsKandang}
|
||||
isLoading={isLoadingKandang}
|
||||
options={kandangOptions}
|
||||
isLoading={isLoadingKandangOptions}
|
||||
placeholder='Kandang'
|
||||
value={selectedKandang}
|
||||
onChange={kandangChangeHandler}
|
||||
onInputChange={setKandangInputValue}
|
||||
onMenuScrollToBottom={loadMoreKandangs}
|
||||
/>
|
||||
<SelectInput
|
||||
isClearable
|
||||
label='Supplier'
|
||||
options={optionsSupplier}
|
||||
isLoading={isLoadingSupplier}
|
||||
options={supplierOptions}
|
||||
isLoading={isLoadingSupplierOptions}
|
||||
placeholder='Supplier'
|
||||
value={selectedSupplier}
|
||||
onChange={supplierChangeHandler}
|
||||
onInputChange={setSupplierInputValue}
|
||||
onMenuScrollToBottom={loadMoreSuppliers}
|
||||
/>
|
||||
<SelectInput
|
||||
isClearable
|
||||
label='Produk'
|
||||
options={optionsNonstock}
|
||||
isLoading={isLoadingNonstock}
|
||||
options={nonstockOptions}
|
||||
isLoading={isLoadingNonstockOptions}
|
||||
placeholder='Produk'
|
||||
value={selectedNonstock}
|
||||
onChange={nonstockChangeHandler}
|
||||
onInputChange={setNonstockInputValue}
|
||||
onMenuScrollToBottom={loadMoreNonstocks}
|
||||
/>
|
||||
<SelectInput
|
||||
isClearable
|
||||
|
||||
@@ -57,6 +57,7 @@ const CustomerPaymentTab = () => {
|
||||
|
||||
const {
|
||||
options: customerOptions,
|
||||
setInputValue: setCustomerInputValue,
|
||||
isLoadingOptions: isLoadingCustomers,
|
||||
loadMore: loadMoreCustomers,
|
||||
hasMore: hasMoreCustomers,
|
||||
@@ -65,6 +66,7 @@ const CustomerPaymentTab = () => {
|
||||
// TODO: Uncomment when BE is ready
|
||||
const {
|
||||
options: salesOptions,
|
||||
setInputValue: setSalesInputValue,
|
||||
isLoadingOptions: isLoadingSales,
|
||||
loadMore: loadMoreSales,
|
||||
hasMore: hasMoreSales,
|
||||
@@ -653,6 +655,7 @@ const CustomerPaymentTab = () => {
|
||||
Array.isArray(val) ? val : val ? [val] : []
|
||||
);
|
||||
}}
|
||||
onInputChange={setCustomerInputValue}
|
||||
isLoading={isLoadingCustomers}
|
||||
isClearable
|
||||
onMenuScrollToBottom={loadMoreCustomers}
|
||||
@@ -670,6 +673,7 @@ const CustomerPaymentTab = () => {
|
||||
onChange={(val) => {
|
||||
setFilterSales(Array.isArray(val) ? val : val ? [val] : []);
|
||||
}}
|
||||
onInputChange={setSalesInputValue}
|
||||
isLoading={isLoadingSales}
|
||||
isClearable
|
||||
onMenuScrollToBottom={loadMoreSales}
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
} from '@/components/pages/report/finance/filter/DebtSupplierFilter';
|
||||
import { getFilledFormikValuesCount } from '@/lib/formik-helper';
|
||||
import ButtonFilter from '@/components/helper/ButtonFilter';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
|
||||
const DebtSupplierTab = () => {
|
||||
// ===== STATE MANAGEMENT =====
|
||||
@@ -51,10 +52,12 @@ const DebtSupplierTab = () => {
|
||||
|
||||
const filterModal = useModal();
|
||||
|
||||
const { options: supplierOptions, isLoadingOptions: isLoadingSuppliers } =
|
||||
useSelect(SupplierApi.basePath, 'id', 'name', '', {
|
||||
limit: 'limit',
|
||||
});
|
||||
const {
|
||||
setInputValue: setSupplierInputValue,
|
||||
options: supplierOptions,
|
||||
isLoadingOptions: isLoadingSupplierOptions,
|
||||
loadMore: loadMoreSuppliers,
|
||||
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
|
||||
|
||||
const dataTypeOptions = useMemo(
|
||||
() => [
|
||||
@@ -610,7 +613,9 @@ const DebtSupplierTab = () => {
|
||||
Array.isArray(val) ? val : val ? [val] : null
|
||||
);
|
||||
}}
|
||||
isLoading={isLoadingSuppliers}
|
||||
onInputChange={setSupplierInputValue}
|
||||
onMenuScrollToBottom={loadMoreSuppliers}
|
||||
isLoading={isLoadingSupplierOptions}
|
||||
isClearable
|
||||
className={{ wrapper: 'w-full' }}
|
||||
isError={
|
||||
|
||||
@@ -62,6 +62,7 @@ const ProductionResultContent = () => {
|
||||
setInputValue: setAreaInputValue,
|
||||
options: areaOptions,
|
||||
isLoadingOptions: isLoadingAreaOptions,
|
||||
loadMore: loadMoreAreas,
|
||||
} = useSelect<BaseKandang>(AreaApi.basePath, 'id', 'name');
|
||||
|
||||
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||
@@ -78,6 +79,7 @@ const ProductionResultContent = () => {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocationOptions,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect<BaseKandang>(LocationApi.basePath, 'id', 'name', 'search', {
|
||||
area_id: selectedArea ? ((selectedArea as OptionType).value as string) : '',
|
||||
});
|
||||
@@ -94,6 +96,7 @@ const ProductionResultContent = () => {
|
||||
setInputValue: setProjectFlockInputValue,
|
||||
options: projectFlockOptions,
|
||||
isLoadingOptions: isLoadingProjectFlockOptions,
|
||||
loadMore: loadMoreProjectFlocks,
|
||||
} = useSelect<BaseKandang>(
|
||||
ProjectFlockApi.basePath,
|
||||
'id',
|
||||
@@ -120,6 +123,7 @@ const ProductionResultContent = () => {
|
||||
setInputValue: setProjectFlockKandangInputValue,
|
||||
options: projectFlockKandangOptions,
|
||||
isLoadingOptions: isLoadingProjectFlockKandangOptions,
|
||||
loadMore: loadMoreProjectFlockKandangs,
|
||||
} = useSelect<BaseKandang>(
|
||||
ProjectFlockKandangApi.basePath,
|
||||
'id',
|
||||
@@ -235,6 +239,7 @@ const ProductionResultContent = () => {
|
||||
value={selectedArea}
|
||||
onChange={areaChangeHandler}
|
||||
onInputChange={setAreaInputValue}
|
||||
onMenuScrollToBottom={loadMoreAreas}
|
||||
isClearable
|
||||
className={{
|
||||
wrapper: 'col-span-12 sm:col-span-6 lg:col-span-4',
|
||||
@@ -251,6 +256,7 @@ const ProductionResultContent = () => {
|
||||
value={selectedLocation}
|
||||
onChange={locationChangeHandler}
|
||||
onInputChange={setLocationInputValue}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
isClearable
|
||||
isDisabled={!selectedArea}
|
||||
className={{
|
||||
@@ -270,6 +276,7 @@ const ProductionResultContent = () => {
|
||||
value={selectedProjectFlock}
|
||||
onChange={projectFlockChangeHandler}
|
||||
onInputChange={setProjectFlockInputValue}
|
||||
onMenuScrollToBottom={loadMoreProjectFlocks}
|
||||
isClearable
|
||||
isDisabled={!selectedArea || !selectedLocation}
|
||||
className={{
|
||||
@@ -289,6 +296,7 @@ const ProductionResultContent = () => {
|
||||
value={selectedProjectFlockKandang}
|
||||
onChange={projectFlockKandangChangeHandler}
|
||||
onInputChange={setProjectFlockKandangInputValue}
|
||||
onMenuScrollToBottom={loadMoreProjectFlockKandangs}
|
||||
isClearable
|
||||
isDisabled={!selectedProjectFlock}
|
||||
className={{
|
||||
|
||||
@@ -58,18 +58,26 @@ const HppPerKandangTab = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
|
||||
AreaApi.basePath,
|
||||
'id',
|
||||
'name',
|
||||
'search'
|
||||
);
|
||||
const {
|
||||
setInputValue: setAreaInputValue,
|
||||
options: areaOptions,
|
||||
isLoadingOptions: isLoadingAreas,
|
||||
loadMore: loadMoreAreas,
|
||||
} = useSelect(AreaApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
|
||||
useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||
const {
|
||||
setInputValue: setLocationInputValue,
|
||||
options: locationOptions,
|
||||
isLoadingOptions: isLoadingLocations,
|
||||
loadMore: loadMoreLocations,
|
||||
} = useSelect(LocationApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
|
||||
useSelect(KandangApi.basePath, 'id', 'name', 'search');
|
||||
const {
|
||||
setInputValue: setKandangInputValue,
|
||||
options: kandangOptions,
|
||||
isLoadingOptions: isLoadingKandangs,
|
||||
loadMore: loadMoreKandangs,
|
||||
} = useSelect(KandangApi.basePath, 'id', 'name', 'search');
|
||||
|
||||
const showUnrecordedOptions: OptionType[] = [
|
||||
{ value: 'false', label: 'Sembunyikan' },
|
||||
@@ -810,6 +818,8 @@ const HppPerKandangTab = () => {
|
||||
.includes(String(opt.value))
|
||||
)}
|
||||
onChange={areaChangeHandler}
|
||||
onInputChange={setAreaInputValue}
|
||||
onMenuScrollToBottom={loadMoreAreas}
|
||||
isLoading={isLoadingAreas}
|
||||
isClearable
|
||||
/>
|
||||
@@ -824,6 +834,8 @@ const HppPerKandangTab = () => {
|
||||
.includes(String(opt.value))
|
||||
)}
|
||||
onChange={locationChangeHandler}
|
||||
onInputChange={setLocationInputValue}
|
||||
onMenuScrollToBottom={loadMoreLocations}
|
||||
isLoading={isLoadingLocations}
|
||||
isClearable
|
||||
/>
|
||||
@@ -838,6 +850,8 @@ const HppPerKandangTab = () => {
|
||||
.includes(String(opt.value))
|
||||
)}
|
||||
onChange={kandangChangeHandler}
|
||||
onInputChange={setKandangInputValue}
|
||||
onMenuScrollToBottom={loadMoreKandangs}
|
||||
isLoading={isLoadingKandangs}
|
||||
isClearable
|
||||
/>
|
||||
|
||||
@@ -5,22 +5,22 @@ export const ROUTE_PERMISSIONS: Record<string, string[]> = {
|
||||
'/dashboard/': ['lti.dashboard.list'],
|
||||
|
||||
// Daily Checklist
|
||||
// TODO: use real daily checklist permission name
|
||||
// '/daily-checklist/': ['lti.daily_checklist.list'],
|
||||
// '/daily-checklist/dashboard/': ['lti.daily_checklist.list'],
|
||||
// '/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'],
|
||||
// '/daily-checklist/list-daily-checklist/detail/': ['lti.daily_checklist.detail'],
|
||||
// '/daily-checklist/reports/': ['lti.daily_checklist.reports'],
|
||||
// '/daily-checklist/master-data/employee/': ['lti.dashboard.master_data.employee'],
|
||||
// '/daily-checklist/master-data/activity/': ['lti.dashboard.master_data.activity'],
|
||||
'/daily-checklist/dashboard/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/daily-checklist/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/list-daily-checklist/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/list-daily-checklist/detail/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/reports/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/master-data/employee/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/master-data/activity/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/master-data/configuration/': ['lti.dashboard.list'],
|
||||
'/daily-checklist/dashboard/': ['lti.daily_checklist.dashboard.list'],
|
||||
'/daily-checklist/daily-checklist/': ['lti.daily_checklist.create'],
|
||||
'/daily-checklist/list-daily-checklist/': ['lti.daily_checklist.list'],
|
||||
'/daily-checklist/list-daily-checklist/detail/': [
|
||||
'lti.daily_checklist.detail',
|
||||
],
|
||||
'/daily-checklist/reports/': ['lti.daily_checklist.reports'],
|
||||
'/daily-checklist/master-data/employee/': [
|
||||
'lti.daily_checklist.master_data.employee',
|
||||
],
|
||||
'/daily-checklist/master-data/activity/': [
|
||||
'lti.daily_checklist.master_data.activity',
|
||||
],
|
||||
'/daily-checklist/master-data/configuration/': [
|
||||
'lti.daily_checklist.master_data.configuration',
|
||||
],
|
||||
|
||||
// Production
|
||||
// Production - Project Flock
|
||||
|
||||
+12
-9
@@ -1,20 +1,20 @@
|
||||
import { BaseMetadata } from '@/types/api/api-general';
|
||||
import { Uom } from '@/types/api/master-data/uom';
|
||||
import { ProductCategory } from '@/types/api/master-data/product-category';
|
||||
import { Supplier } from '@/types/api/master-data/supplier';
|
||||
import { BaseSupplier, Supplier } from '@/types/api/master-data/supplier';
|
||||
|
||||
export type BaseProduct = {
|
||||
id: number;
|
||||
name: string;
|
||||
brand: string;
|
||||
sku: string;
|
||||
sku?: string;
|
||||
product_price: number;
|
||||
selling_price?: number;
|
||||
tax?: number;
|
||||
expiry_period: number;
|
||||
expiry_period?: number;
|
||||
uom: Uom;
|
||||
product_category: ProductCategory;
|
||||
suppliers: Supplier[];
|
||||
suppliers: (BaseSupplier & { price: number })[];
|
||||
flags: string[];
|
||||
};
|
||||
|
||||
@@ -23,14 +23,17 @@ export type Product = BaseMetadata & BaseProduct;
|
||||
export type CreateProductPayload = {
|
||||
name: string;
|
||||
brand: string;
|
||||
sku: string;
|
||||
sku?: string;
|
||||
uom_id: number;
|
||||
product_category_id: number;
|
||||
product_price: number;
|
||||
selling_price: number;
|
||||
tax: number;
|
||||
expiry_period: number;
|
||||
supplier_ids: number[];
|
||||
selling_price?: number;
|
||||
tax?: number;
|
||||
expiry_period?: number;
|
||||
suppliers: {
|
||||
supplier_id: number;
|
||||
price: number;
|
||||
}[];
|
||||
flags: string[];
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user