diff --git a/src/components/pages/master-data/kandang/form/KandangForm.tsx b/src/components/pages/master-data/kandang/form/KandangForm.tsx index acced3c5..22ad91f8 100644 --- a/src/components/pages/master-data/kandang/form/KandangForm.tsx +++ b/src/components/pages/master-data/kandang/form/KandangForm.tsx @@ -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} diff --git a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx index cd2c361b..4622a6a3 100644 --- a/src/components/pages/master-data/nonstock/form/NonstockForm.tsx +++ b/src/components/pages/master-data/nonstock/form/NonstockForm.tsx @@ -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} diff --git a/src/components/pages/master-data/product/form/ProductForm.schema.ts b/src/components/pages/master-data/product/form/ProductForm.schema.ts index 9dcf713e..b85d5d4c 100644 --- a/src/components/pages/master-data/product/form/ProductForm.schema.ts +++ b/src/components/pages/master-data/product/form/ProductForm.schema.ts @@ -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 = 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 = .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!'), diff --git a/src/components/pages/master-data/product/form/ProductForm.tsx b/src/components/pages/master-data/product/form/ProductForm.tsx index 2fc3b267..7e893f67 100644 --- a/src/components/pages/master-data/product/form/ProductForm.tsx +++ b/src/components/pages/master-data/product/form/ProductForm.tsx @@ -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'} /> { readOnly={type === 'detail'} /> {
{ readOnly={type === 'detail'} /> { readOnly={type === 'detail'} />
-
- - (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 - /> +
{ isClearable />
+ +
+ {type !== 'detail' && formik.values.suppliers.length === 0 && ( + + )} + + {formik.values.suppliers.length > 0 && ( + +
+

Supplier

+
+ +
+ + + + + + + + + + + {formik.values.suppliers.map((supplier, idx) => ( + + + + {type !== 'detail' && ( + + )} + + ))} + +
+ Supplier + + Harga + Aksi
+ { + formik.setFieldValue( + `suppliers.${idx}.supplier`, + val + ); + }} + isError={isSupplierRepeaterError( + 'supplier', + idx + )} + isClearable + className={{ + wrapper: 'min-w-48 w-full', + }} + /> + + + + +
+
+ +
+ +
+
+ )} +
{type !== 'add' && ( diff --git a/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx b/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx index cab9f750..a6a53e3f 100644 --- a/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx +++ b/src/components/pages/master-data/warehouse/form/WarehouseForm.tsx @@ -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} diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts index 9a0c9d2e..165bc8ee 100644 --- a/src/config/route-permission.ts +++ b/src/config/route-permission.ts @@ -5,22 +5,22 @@ export const ROUTE_PERMISSIONS: Record = { '/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 diff --git a/src/types/api/master-data/product.d.ts b/src/types/api/master-data/product.d.ts index e82f857e..7fd2c7c1 100644 --- a/src/types/api/master-data/product.d.ts +++ b/src/types/api/master-data/product.d.ts @@ -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[]; };