refactor(FE): Refactor product flags to support single flag and

sub-flags
This commit is contained in:
rstubryan
2026-02-27 14:57:45 +07:00
parent 16f2f2bc06
commit a4378ebd04
4 changed files with 81 additions and 24 deletions
@@ -180,11 +180,17 @@ const ProductsTable = () => {
props.row.original.suppliers?.map((s) => s.name).join(', ') || '-',
},
{
accessorKey: 'flags',
header: 'Flags',
accessorKey: 'flag',
header: 'Flag',
cell: (props) =>
props.row.original.flags?.length
? props.row.original.flags.join(', ')
props.row.original.flag ? props.row.original.flag : '-',
},
{
accessorKey: 'subs_flags',
header: 'Sub Flags',
cell: (props) =>
props.row.original.sub_flags?.length
? props.row.original.sub_flags.join(', ')
: '-',
},
{
@@ -25,7 +25,8 @@ type ProductFormSchemaType = {
} | null;
price: number;
}[];
flags: string[];
flag: string;
sub_flags?: string[];
};
export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
@@ -94,10 +95,12 @@ export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
)
.required('Supplier wajib diisi!'),
flags: Yup.array()
.of(Yup.string().required())
.min(1, 'Minimal harus ada 1 flag!')
.required('Flag wajib diisi!'),
flag: Yup.string()
.min(1, 'Flag wajib diisi!')
.required('Flag wajib diisi!')
.typeError('Flag wajib diisi!'),
sub_flags: Yup.array().of(Yup.string().required()),
});
export const UpdateProductFormSchema = ProductFormSchema;
@@ -36,8 +36,10 @@ import {
ProductApi,
} from '@/services/api/master-data';
import { cn } from '@/lib/helper';
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import { PRODUCT_FLAG_MAPPING } from '@/config/constant';
import { Supplier } from '@/types/api/master-data/supplier';
import Card from '@/components/Card';
import { removeArrayItemAndSync } from '@/lib/utils/formik';
@@ -110,7 +112,8 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
price: supplier.price,
}))
: [],
flags: initialValues?.flags ?? [],
flag: initialValues?.flag ?? '',
sub_flags: initialValues?.sub_flags ?? [],
}),
[initialValues]
);
@@ -139,7 +142,8 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
supplier_id: s.supplier?.value as number,
price: parseInt(s.price.toString()) || 0,
})),
flags: values.flags.filter((f): f is string => typeof f === 'string'),
flag: values.flag,
sub_flags: values.sub_flags,
};
switch (type) {
case 'add':
@@ -200,6 +204,28 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
});
}, [supplierOptions, formik.values.suppliers]);
const selectedFlagMapping = useMemo(() => {
return PRODUCT_FLAG_MAPPING.options.find(
(opt) => opt.flag.value === formik.values.flag
);
}, [formik.values.flag]);
const subFlagOptions = useMemo<OptionType[]>(() => {
return (selectedFlagMapping?.sub_flags as unknown as OptionType[]) ?? [];
}, [selectedFlagMapping]);
const selectedSubFlagValues = useMemo<OptionType[]>(() => {
return (
(selectedFlagMapping?.sub_flags.filter((subFlag) =>
formik.values.sub_flags?.includes(subFlag.value)
) as unknown as OptionType[]) ?? []
);
}, [selectedFlagMapping, formik.values.sub_flags]);
const isSubFlagRequired = useMemo(() => {
return selectedFlagMapping?.allow_without_sub_flag === false;
}, [selectedFlagMapping]);
const addSupplierHandler = () => {
formik.setFieldValue('suppliers', [
...formik.values.suppliers,
@@ -213,7 +239,6 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
const deleteSupplierItemHandler = (idx: number) => {
const path = 'suppliers';
// trims values, errors, and touched at idx
removeArrayItemAndSync(formik, path, idx);
};
@@ -428,26 +453,45 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'}
/>
</div>
<div className='grid sm:grid-cols-1 gap-4'>
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
<SelectInput
required
label='Flags'
placeholder='Pilih flags...'
value={(
PRODUCT_FLAG_MAPPING.flags as unknown as OptionType[]
).find((opt) => opt.value === formik.values.flag)}
onChange={(val) => {
const selectedFlag = (val as OptionType)?.value ?? '';
formik.setFieldValue('flag', selectedFlag);
formik.setFieldValue('sub_flags', []);
}}
options={PRODUCT_FLAG_MAPPING.flags as unknown as OptionType[]}
isError={formik.touched.flag && Boolean(formik.errors.flag)}
errorMessage={formik.errors.flag as string}
isDisabled={type === 'detail'}
isClearable
/>
<SelectInput
label='Sub Flags'
placeholder='Pilih sub flags...'
isMulti
value={PRODUCT_FLAG_OPTIONS.filter((opt) =>
(formik.values.flags || []).includes(opt.value)
)}
required={isSubFlagRequired}
value={selectedSubFlagValues}
onChange={(val) => {
const arr = Array.isArray(val) ? val : val ? [val] : [];
formik.setFieldValue(
'flags',
'sub_flags',
arr.map((v) => (v as OptionType).value)
);
}}
options={PRODUCT_FLAG_OPTIONS}
isError={formik.touched.flags && Boolean(formik.errors.flags)}
errorMessage={formik.errors.flags as string}
isDisabled={type === 'detail'}
options={subFlagOptions}
isError={
formik.touched.sub_flags && Boolean(formik.errors.sub_flags)
}
errorMessage={formik.errors.sub_flags as string}
isDisabled={type === 'detail' || !formik.values.flag}
isClearable
/>
</div>
+6 -2
View File
@@ -15,7 +15,10 @@ export type BaseProduct = {
uom: Uom;
product_category: ProductCategory;
suppliers: (BaseSupplier & { price: number })[];
flags: string[];
flag: string;
sub_flag?: string;
sub_flags?: string[];
flags?: string[];
};
export type Product = BaseMetadata & BaseProduct;
@@ -34,7 +37,8 @@ export type CreateProductPayload = {
supplier_id: number;
price: number;
}[];
flags: string[];
flag: string;
sub_flags?: string[];
};
export type UpdateProductPayload = CreateProductPayload;