Merge branch 'fix/adjustment-transfer-stock-form' into 'development'

[FIX/FE] Adjustment Transfer Stock Form and Adjustment Select Option

See merge request mbugroup/lti-web-client!278
This commit is contained in:
Rivaldi A N S
2026-01-29 04:30:47 +00:00
5 changed files with 352 additions and 250 deletions
+265 -105
View File
@@ -54,6 +54,9 @@ interface SelectInputBaseProps<T = OptionType> {
wrapper?: string; wrapper?: string;
label?: string; label?: string;
select?: string; select?: string;
inputPrefix?: string;
inputSuffix?: string;
inputPrefixSuffixWrapper?: string;
}; };
isError?: boolean; isError?: boolean;
errorMessage?: string; errorMessage?: string;
@@ -62,6 +65,8 @@ interface SelectInputBaseProps<T = OptionType> {
delay?: number; delay?: number;
onInputChange?: (search: string) => void; onInputChange?: (search: string) => void;
startAdornment?: ReactNode; startAdornment?: ReactNode;
inputPrefix?: ReactNode;
inputSuffix?: ReactNode;
menuPortalTarget?: HTMLElement | null; menuPortalTarget?: HTMLElement | null;
closeMenuOnSelect?: boolean; closeMenuOnSelect?: boolean;
hideSelectedOptions?: boolean; hideSelectedOptions?: boolean;
@@ -84,7 +89,7 @@ const CustomControl = <
>( >(
props: ControlProps<Option, IsMulti, Group> props: ControlProps<Option, IsMulti, Group>
) => { ) => {
const { children } = props; const { children, innerProps } = props;
const customProps = props.selectProps as unknown as { const customProps = props.selectProps as unknown as {
shouldShowAdornment?: boolean; shouldShowAdornment?: boolean;
@@ -96,7 +101,7 @@ const CustomControl = <
return ( return (
<ReactSelectComponents.Control {...props}> <ReactSelectComponents.Control {...props}>
<div className='flex-1 p-3! py-1.5 gap-1 flex items-center'> <div className='flex-1 pl-3 gap-1 flex items-center' {...innerProps}>
{shouldShowAdornment && startAdornment} {shouldShowAdornment && startAdornment}
{children} {children}
</div> </div>
@@ -153,6 +158,8 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
createables = false, createables = false,
onInputChange, onInputChange,
startAdornment, startAdornment,
inputPrefix,
inputSuffix,
menuPortalTarget, menuPortalTarget,
closeMenuOnSelect, closeMenuOnSelect,
hideSelectedOptions, hideSelectedOptions,
@@ -227,111 +234,264 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
</span> </span>
)} )}
<SelectComponent<T, boolean, GroupBase<T>> {inputPrefix || inputSuffix ? (
instanceId='select' <div
value={value ?? (isMulti ? [] : null)} className={cn(
onChange={onChange ? handleChange : undefined} 'relative flex text-sm',
options={options} className?.inputPrefixSuffixWrapper
menuIsOpen={openMenu} )}
inputValue={internalInputValue} >
onInputChange={internalInputChangeHandler} {inputPrefix && (
onMenuClose={() => setInternalInputValue('')} <div
isMulti={isMulti} className={cn(
isDisabled={isDisabled || readOnly} 'inline-flex items-center px-3 border border-r-0 border-base-content/10 rounded-l-lg transition-all duration-200',
isLoading={isLoading} {
isClearable={isClearable} 'bg-gray-100 border-base-content/10': !isDisabled,
isRtl={isRtl} 'bg-gray-50 border-base-content/10': isDisabled,
isSearchable={isSearchable} 'border-error': isError,
placeholder={placeholder} },
closeMenuOnSelect={closeMenuOnSelect} className?.inputPrefix
hideSelectedOptions={hideSelectedOptions} )}
className={cn('w-full', className?.select)} >
classNames={{ {inputPrefix}
...(!startAdornment && { </div>
)}
<SelectComponent<T, boolean, GroupBase<T>>
instanceId='select'
value={value ?? (isMulti ? [] : null)}
onChange={onChange ? handleChange : undefined}
options={options}
menuIsOpen={openMenu}
inputValue={internalInputValue}
onInputChange={internalInputChangeHandler}
onMenuClose={() => setInternalInputValue('')}
isMulti={isMulti}
isDisabled={isDisabled || readOnly}
isLoading={isLoading}
isClearable={isClearable}
isRtl={isRtl}
isSearchable={isSearchable}
placeholder={placeholder}
closeMenuOnSelect={closeMenuOnSelect}
hideSelectedOptions={hideSelectedOptions}
className={cn('w-full flex-1', className?.select)}
classNames={{
control: ({ isFocused, isDisabled }) =>
cn('w-full border bg-white transition-shadow', 'rounded-lg!', {
'cursor-pointer!': !readOnly && !isDisabled,
'border-red-500! ring-2 ring-red-200': isError,
'border-indigo-500 ring-2 ring-indigo-200':
isFocused && !startAdornment,
'border-base-content/10!': !isError && !isFocused,
'bg-gray-100 text-gray-400 cursor-not-allowed':
isDisabled && !readOnly,
'bg-transparent! cursor-not-allowed!': readOnly,
'rounded-l-none!': inputPrefix && !startAdornment,
'rounded-r-none!': inputSuffix && !startAdornment,
}),
valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'),
placeholder: () =>
cn({
'text-gray-400 text-sm leading-tight': !isError,
'text-red-300!': isError,
}),
singleValue: () =>
cn({
'm-0! text-gray-900 text-sm leading-tight': !isError,
'text-error!': isError,
'text-gray-900!': readOnly,
}),
input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'),
indicatorsContainer: () =>
cn('flex items-center gap-1 pr-3 py-2'),
dropdownIndicator: ({ isFocused }) =>
cn('p-0! rounded hover:bg-gray-100', {
'text-gray-900': isFocused,
'text-gray-500': !isFocused,
'text-error!': isError,
}),
clearIndicator: () => cn('p-0! rounded hover:bg-gray-100'),
menu: () =>
cn(
'border border-base-content/5 rounded-xl! bg-base-100 shadow-lg! my-1.5!'
),
menuList: () => cn('p-0! max-h-60 overflow-auto'),
option: ({ isFocused, isSelected }) =>
cn('px-3 py-2 rounded-md cursor-pointer!', {
'bg-indigo-600 text-white': isFocused,
'bg-blue-500!': isSelected,
'text-gray-700': !isFocused && !isSelected,
}),
multiValue: ({ getValue, index }) => {
const selectedValues = getValue() as T[];
return cn(
'bg-base-200! rounded-lg! py-[3px] px-2.5 m-0! flex items-center gap-1! w-fit gap-2!',
selectedValues[index]?.className
);
},
multiValueRemove: () => cn('p-0! w-3 h-3'),
multiValueLabel: ({ getValue, index }) => {
const selectedValues = getValue() as T[];
return cn(
'p-0! text-base-content! text-xs!',
selectedValues[index]?.labelClassName
);
},
}}
components={{
...components,
...(optionComponent ? { Option: optionComponent } : {}),
MenuList: CustomMenuList,
}}
{...(startAdornment && {
shouldShowAdornment,
startAdornment,
})}
menuPortalTarget={
typeof document !== 'undefined'
? (menuPortalTarget ?? document.body)
: undefined
}
styles={{
menuPortal: (base) => ({ ...base, zIndex: 9999 }),
multiValue(base) {
return {
...base,
borderRadius: '8px',
};
},
}}
onMenuScrollToBottom={onMenuScrollToBottom}
/>
{inputSuffix && (
<div
className={cn(
'inline-flex items-center px-3 border border-l-0 border-base-content/10 rounded-r-lg transition-all duration-200',
{
'bg-gray-100 border-base-content/10': !isDisabled,
'bg-gray-50 border-base-content/10': isDisabled,
'border-error': isError,
},
className?.inputSuffix
)}
>
{inputSuffix}
</div>
)}
</div>
) : (
<SelectComponent<T, boolean, GroupBase<T>>
instanceId='select'
value={value ?? (isMulti ? [] : null)}
onChange={onChange ? handleChange : undefined}
options={options}
menuIsOpen={openMenu}
inputValue={internalInputValue}
onInputChange={internalInputChangeHandler}
onMenuClose={() => setInternalInputValue('')}
isMulti={isMulti}
isDisabled={isDisabled || readOnly}
isLoading={isLoading}
isClearable={isClearable}
isRtl={isRtl}
isSearchable={isSearchable}
placeholder={placeholder}
closeMenuOnSelect={closeMenuOnSelect}
hideSelectedOptions={hideSelectedOptions}
className={cn('w-full', className?.select)}
classNames={{
control: ({ isFocused, isDisabled }) => control: ({ isFocused, isDisabled }) =>
cn('w-full rounded-lg! border bg-white transition-shadow', { cn(
'cursor-pointer!': !readOnly && !isDisabled, 'w-full border bg-white transition-shadow',
'border-red-500! ring-2 ring-red-200': isError, // Gunakan rounded-lg untuk semua kasus
'border-indigo-500 ring-2 ring-indigo-200': isFocused, 'rounded-lg!',
'border-base-content/10!': !isError && !isFocused, {
'bg-gray-100 text-gray-400 cursor-not-allowed': 'cursor-pointer!': !readOnly && !isDisabled,
isDisabled && !readOnly, 'border-red-500! ring-2 ring-red-200': isError,
'bg-transparent! cursor-not-allowed!': readOnly, 'border-indigo-500 ring-2 ring-indigo-200':
}), isFocused && !startAdornment,
'border-base-content/10!': !isError && !isFocused,
'bg-gray-100 text-gray-400 cursor-not-allowed':
isDisabled && !readOnly,
'bg-transparent! cursor-not-allowed!': readOnly,
}
),
valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'), valueContainer: () => cn('flex-1 px-3! pr-2! py-2.5! gap-1'),
}), placeholder: () =>
placeholder: () => cn({
cn({ 'text-gray-400 text-sm leading-tight': !isError,
'text-gray-400 text-sm leading-tight': !isError, 'text-red-300!': isError,
'text-red-300!': isError, }),
}), singleValue: () =>
singleValue: () => cn({
cn({ 'm-0! text-gray-900 text-sm leading-tight': !isError,
'm-0! text-gray-900 text-sm leading-tight': !isError, 'text-error!': isError,
'text-error!': isError, 'text-gray-900!': readOnly,
'text-gray-900!': readOnly, }),
}), input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'),
input: () => cn('text-gray-900 m-0! p-0! text-sm leading-tight'), indicatorsContainer: () => cn('flex items-center gap-1 pr-3 py-2'),
indicatorsContainer: () => cn('flex items-center gap-1 pr-3 py-2'), dropdownIndicator: ({ isFocused }) =>
dropdownIndicator: ({ isFocused }) => cn('p-0! rounded hover:bg-gray-100', {
cn('p-0! rounded hover:bg-gray-100', { 'text-gray-900': isFocused,
'text-gray-900': isFocused, 'text-gray-500': !isFocused,
'text-gray-500': !isFocused, 'text-error!': isError,
'text-error!': isError, }),
}), clearIndicator: () => cn('p-0! rounded hover:bg-gray-100'),
clearIndicator: () => cn('p-0! rounded hover:bg-gray-100'), menu: () =>
menu: () => cn(
cn( 'border border-base-content/5 rounded-xl! bg-base-100 shadow-lg! my-1.5!'
'border border-base-content/5 rounded-xl! bg-base-100 shadow-lg! my-1.5!' ),
), menuList: () => cn('p-0! max-h-60 overflow-auto'),
menuList: () => cn('p-0! max-h-60 overflow-auto'), option: ({ isFocused, isSelected }) =>
option: ({ isFocused, isSelected }) => cn('px-3 py-2 rounded-md cursor-pointer!', {
cn('px-3 py-2 rounded-md cursor-pointer!', { 'bg-indigo-600 text-white': isFocused,
'bg-indigo-600 text-white': isFocused, 'bg-blue-500!': isSelected,
'bg-blue-500!': isSelected, 'text-gray-700': !isFocused && !isSelected,
'text-gray-700': !isFocused && !isSelected, }),
}), multiValue: ({ getValue, index }) => {
multiValue: ({ getValue, index }) => { const selectedValues = getValue() as T[];
const selectedValues = getValue() as T[]; return cn(
return cn( 'bg-base-200! rounded-lg! py-[3px] px-2.5 m-0! flex items-center gap-1! w-fit gap-2!',
'bg-base-200! rounded-lg! py-[3px] px-2.5 m-0! flex items-center gap-1! w-fit gap-2!', selectedValues[index]?.className
selectedValues[index]?.className );
); },
}, multiValueRemove: () => cn('p-0! w-3 h-3'),
multiValueRemove: () => cn('p-0! w-3 h-3'), multiValueLabel: ({ getValue, index }) => {
multiValueLabel: ({ getValue, index }) => { const selectedValues = getValue() as T[];
const selectedValues = getValue() as T[]; return cn(
return cn( 'p-0! text-base-content! text-xs!',
'p-0! text-base-content! text-xs!', selectedValues[index]?.labelClassName
selectedValues[index]?.labelClassName );
); },
}, }}
}} components={{
components={{ ...components,
...components, ...(optionComponent ? { Option: optionComponent } : {}),
...(optionComponent ? { Option: optionComponent } : {}), MenuList: CustomMenuList,
MenuList: CustomMenuList, }}
}} {...(startAdornment && {
{...(startAdornment && { shouldShowAdornment,
shouldShowAdornment, startAdornment,
startAdornment, })}
})} menuPortalTarget={
menuPortalTarget={ typeof document !== 'undefined'
typeof document !== 'undefined' ? (menuPortalTarget ?? document.body)
? (menuPortalTarget ?? document.body) : undefined
: undefined }
} styles={{
styles={{ menuPortal: (base) => ({ ...base, zIndex: 9999 }),
menuPortal: (base) => ({ ...base, zIndex: 9999 }), multiValue(base) {
multiValue(base) { return {
return { ...base,
...base, borderRadius: '8px',
borderRadius: '8px', };
}; },
}, }}
}} onMenuScrollToBottom={onMenuScrollToBottom}
onMenuScrollToBottom={onMenuScrollToBottom} />
/> )}
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>} {isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
{!isError && bottomLabel && ( {!isError && bottomLabel && (
@@ -27,12 +27,12 @@ type MovementFormSchemaType = {
product_qty: number | string; product_qty: number | string;
}[]; }[];
deliveries: { deliveries: {
delivery_cost?: number | string; delivery_cost?: number | string | null;
delivery_cost_per_item?: number | string; delivery_cost_per_item?: number | string | null;
document?: File | MovementDocument | null; document?: File | MovementDocument | null;
document_path?: string | null; document_path?: string | null;
driver_name: string; driver_name?: string | null;
vehicle_plate: string; vehicle_plate?: string | null;
supplier?: { supplier?: {
value: number; value: number;
label: string; label: string;
@@ -59,12 +59,12 @@ export type ProductSchema = {
}; };
export type DeliverySchema = { export type DeliverySchema = {
delivery_cost?: number | string; delivery_cost?: number | string | null;
delivery_cost_per_item?: number | string; delivery_cost_per_item?: number | string | null;
document?: File | MovementDocument | null; document?: File | MovementDocument | null;
document_path?: string | null; document_path?: string | null;
driver_name: string; driver_name?: string | null;
vehicle_plate: string; vehicle_plate?: string | null;
supplier?: { supplier?: {
value: number; value: number;
label: string; label: string;
@@ -120,32 +120,26 @@ const DeliveryDocumentSchema = Yup.mixed<File | MovementDocument>()
const DeliveryObjectSchema: Yup.ObjectSchema<DeliverySchema> = Yup.object({ const DeliveryObjectSchema: Yup.ObjectSchema<DeliverySchema> = Yup.object({
delivery_cost: Yup.number() delivery_cost: Yup.number()
.transform((value) => (isNaN(value) || value === 0 ? undefined : value)) .transform((value) =>
isNaN(value) || value === '' || value === null ? undefined : value
)
.optional()
.nullable()
.min(1, 'Biaya minimal 1!') .min(1, 'Biaya minimal 1!')
.typeError('Biaya harus berupa angka!') .typeError('Biaya harus berupa angka!'),
.test('one-of-cost-fields', 'Wajib diisi salah satu!', function (value) {
const { delivery_cost_per_item } = this.parent;
return (
(value !== undefined && value > 0) ||
(delivery_cost_per_item !== undefined && delivery_cost_per_item > 0)
);
}),
delivery_cost_per_item: Yup.number() delivery_cost_per_item: Yup.number()
.transform((value) => (isNaN(value) || value === 0 ? undefined : value)) .transform((value) =>
isNaN(value) || value === '' || value === null ? undefined : value
)
.optional()
.nullable()
.min(1, 'Biaya per item minimal 1!') .min(1, 'Biaya per item minimal 1!')
.typeError('Biaya per item harus berupa angka!') .typeError('Biaya per item harus berupa angka!'),
.test('one-of-cost-fields', 'Wajib diisi salah satu!', function (value) {
const { delivery_cost } = this.parent;
return (
(value !== undefined && value > 0) ||
(delivery_cost !== undefined && delivery_cost > 0)
);
}),
document_path: Yup.string().nullable().optional(), document_path: Yup.string().nullable().optional(),
document_index: Yup.number().optional(), document_index: Yup.number().optional(),
document: DeliveryDocumentSchema, document: DeliveryDocumentSchema,
driver_name: Yup.string().required('Nama sopir wajib diisi!'), driver_name: Yup.string().optional().nullable(),
vehicle_plate: Yup.string().required('Plat nomor wajib diisi!'), vehicle_plate: Yup.string().optional().nullable(),
supplier: Yup.object({ supplier: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
@@ -279,12 +273,12 @@ export const getMovementFormInitialValues = (
}) ?? [], }) ?? [],
})) ?? [ })) ?? [
{ {
delivery_cost: undefined, delivery_cost: null,
delivery_cost_per_item: undefined, delivery_cost_per_item: null,
document: null, document: null,
document_path: null, document_path: null,
driver_name: '', driver_name: null,
vehicle_plate: '', vehicle_plate: null,
supplier: null, supplier: null,
supplier_id: 0, supplier_id: 0,
products: [ products: [
@@ -228,19 +228,49 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
} }
} }
return { const deliveryObj: {
delivery_cost: parseInt((d.delivery_cost || '').toString()) || 0, products: Array<{ product_id: number; product_qty: number }>;
delivery_cost_per_item: delivery_cost?: number;
parseInt((d.delivery_cost_per_item || '').toString()) || 0, delivery_cost_per_item?: number;
document_index: documentIndex, document_index?: number;
driver_name: d.driver_name, driver_name?: string;
vehicle_plate: d.vehicle_plate, vehicle_plate?: string;
supplier_id: d.supplier_id, supplier_id?: number;
} = {
products: d.products.map((p) => ({ products: d.products.map((p) => ({
product_id: p.product_id, product_id: p.product_id,
product_qty: parseInt(p.product_qty.toString()) || 0, product_qty: parseInt(p.product_qty.toString()) || 0,
})), })),
}; };
const deliveryCost = parseInt((d.delivery_cost || '').toString()) || 0;
if (deliveryCost > 0) {
deliveryObj.delivery_cost = deliveryCost;
}
const deliveryCostPerItem =
parseInt((d.delivery_cost_per_item || '').toString()) || 0;
if (deliveryCostPerItem > 0) {
deliveryObj.delivery_cost_per_item = deliveryCostPerItem;
}
if (documentIndex >= 0) {
deliveryObj.document_index = documentIndex;
}
if (d.driver_name) {
deliveryObj.driver_name = d.driver_name;
}
if (d.vehicle_plate) {
deliveryObj.vehicle_plate = d.vehicle_plate;
}
if (d.supplier_id) {
deliveryObj.supplier_id = d.supplier_id;
}
return deliveryObj;
}); });
const payload: CreateMovementPayload = { const payload: CreateMovementPayload = {
@@ -844,32 +874,15 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
(warehouseId: number) => { (warehouseId: number) => {
const stockInfo = warehouseStockMap.get(warehouseId); const stockInfo = warehouseStockMap.get(warehouseId);
if (!stockInfo) { if (!stockInfo) {
return ( return <span className='text-xs'>Kosong</span>;
<Badge
variant='ghost'
color='neutral'
size='sm'
className={{ badge: 'whitespace-nowrap font-semibold' }}
>
Kosong
</Badge>
);
} }
const { productCount } = stockInfo; const { productCount } = stockInfo;
let color: 'neutral' | 'success' | 'warning' = 'neutral';
if (productCount === 0) color = 'warning';
else if (productCount > 0) color = 'success';
return ( return (
<Badge <span className='text-xs whitespace-nowrap'>
variant='soft'
color={color}
size='sm'
className={{ badge: 'whitespace-nowrap font-semibold' }}
>
Tersedia {productCount} produk Tersedia {productCount} produk
</Badge> </span>
); );
}, },
[warehouseStockMap] [warehouseStockMap]
@@ -1330,7 +1343,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
errorMessage={formik.errors.source_warehouse_id as string} errorMessage={formik.errors.source_warehouse_id as string}
isDisabled={type === 'detail'} isDisabled={type === 'detail'}
isClearable isClearable
startAdornment={ inputPrefix={
formik.values.source_warehouse_id formik.values.source_warehouse_id
? getWarehouseStockAdornment( ? getWarehouseStockAdornment(
formik.values.source_warehouse_id formik.values.source_warehouse_id
@@ -1388,7 +1401,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
errorMessage={formik.errors.destination_warehouse_id as string} errorMessage={formik.errors.destination_warehouse_id as string}
isDisabled={type === 'detail'} isDisabled={type === 'detail'}
isClearable isClearable
startAdornment={ inputPrefix={
formik.values.destination_warehouse_id formik.values.destination_warehouse_id
? getWarehouseStockAdornment( ? getWarehouseStockAdornment(
formik.values.destination_warehouse_id formik.values.destination_warehouse_id
@@ -1647,43 +1660,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
</span> </span>
</th> </th>
<th>Supplier</th> <th>Supplier</th>
<th> <th>Plat Nomor</th>
Plat Nomor
<span
className='tooltip tooltip-error tooltip-bottom z-9999'
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>Dokumen</th> <th>Dokumen</th>
<th> <th>Biaya Pengiriman (Rp.)</th>
Biaya Pengiriman (Rp.) <th>Biaya Per Item (Rp.)</th>
<span <th>Nama Sopir</th>
className='tooltip tooltip-error tooltip-bottom z-9999'
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Biaya Per Item (Rp.)
<span
className='tooltip tooltip-error tooltip-bottom z-9999'
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Nama Sopir
<span
className='tooltip tooltip-error tooltip-bottom z-9999'
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
{type !== 'detail' && <th>Aksi</th>} {type !== 'detail' && <th>Aksi</th>}
</tr> </tr>
</thead> </thead>
@@ -1780,10 +1761,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
</td> </td>
<td> <td>
<TextInput <TextInput
required
name={`deliveries.${idx}.vehicle_plate`} name={`deliveries.${idx}.vehicle_plate`}
placeholder='Masukkan plat nomor...' placeholder='Masukkan plat nomor...'
value={delivery.vehicle_plate} value={delivery.vehicle_plate ?? ''}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
{...isRepeaterInputError( {...isRepeaterInputError(
@@ -1871,10 +1851,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
</td> </td>
<td> <td>
<NumberInput <NumberInput
required
name={`deliveries.${idx}.delivery_cost`} name={`deliveries.${idx}.delivery_cost`}
placeholder='Masukkan biaya pengiriman...' placeholder='Masukkan biaya pengiriman...'
value={delivery.delivery_cost || ''} value={delivery.delivery_cost ?? ''}
onChange={handleDeliveryCostChangeWrapper(idx)} onChange={handleDeliveryCostChangeWrapper(idx)}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
decimalScale={0} decimalScale={0}
@@ -1895,10 +1874,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
</td> </td>
<td> <td>
<NumberInput <NumberInput
required
name={`deliveries.${idx}.delivery_cost_per_item`} name={`deliveries.${idx}.delivery_cost_per_item`}
placeholder='Masukkan biaya per item...' placeholder='Masukkan biaya per item...'
value={delivery.delivery_cost_per_item || ''} value={delivery.delivery_cost_per_item ?? ''}
onChange={handleDeliveryCostPerItemChangeWrapper(idx)} onChange={handleDeliveryCostPerItemChangeWrapper(idx)}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
decimalScale={0} decimalScale={0}
@@ -1919,10 +1897,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
</td> </td>
<td> <td>
<TextInput <TextInput
required
name={`deliveries.${idx}.driver_name`} name={`deliveries.${idx}.driver_name`}
placeholder='Masukkan nama sopir...' placeholder='Masukkan nama sopir...'
value={delivery.driver_name} value={delivery.driver_name ?? ''}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
{...isRepeaterInputError( {...isRepeaterInputError(
@@ -1125,16 +1125,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
} }
return ( return (
<Badge <span className={'whitespace-nowrap text-xs'}>
variant='soft'
color={color}
size='sm'
className={{
badge: 'whitespace-nowrap font-semibold text-xs px-2',
}}
>
Periode {projectFlockKandangLookup.project_flock?.period} Periode {projectFlockKandangLookup.project_flock?.period}
</Badge> </span>
); );
}, [recordedProjectFlockKandangIds, projectFlockKandangLookup]); }, [recordedProjectFlockKandangIds, projectFlockKandangLookup]);
@@ -1150,33 +1143,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const hasOvkFlag = productWarehouse.product.flags?.includes('OVK'); const hasOvkFlag = productWarehouse.product.flags?.includes('OVK');
if (hasPakanFlag) { if (hasPakanFlag) {
return ( return <span className={'whitespace-nowrap text-xs'}>PAKAN</span>;
<Badge
variant='soft'
color='info'
size='sm'
className={{
badge: 'whitespace-nowrap font-semibold text-xs px-2',
}}
>
PAKAN
</Badge>
);
} }
if (hasOvkFlag) { if (hasOvkFlag) {
return ( return <span className={'whitespace-nowrap text-xs'}>OVK</span>;
<Badge
variant='soft'
color='secondary'
size='sm'
className={{
badge: 'whitespace-nowrap font-semibold text-xs px-2',
}}
>
OVK
</Badge>
);
} }
return null; return null;
@@ -1826,7 +1797,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
Boolean(formik.errors.kandang_id) Boolean(formik.errors.kandang_id)
} }
errorMessage={formik.errors.kandang_id as string} errorMessage={formik.errors.kandang_id as string}
startAdornment={ inputPrefix={
projectFlockKandangLookup || projectFlockKandangDetail projectFlockKandangLookup || projectFlockKandangDetail
? getProjectFlockBadgeAdornment() ? getProjectFlockBadgeAdornment()
: undefined : undefined
@@ -2458,7 +2429,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
!formik.values.project_flock_kandang_id !formik.values.project_flock_kandang_id
} }
isClearable={type !== 'detail'} isClearable={type !== 'detail'}
startAdornment={ inputPrefix={
stock.product_warehouse_id stock.product_warehouse_id
? getProductFlagBadgeAdornment( ? getProductFlagBadgeAdornment(
stock.product_warehouse_id stock.product_warehouse_id
+4 -4
View File
@@ -68,11 +68,11 @@ export type CreateMovementPayloadData = {
product_qty: number; product_qty: number;
}[]; }[];
deliveries: { deliveries: {
delivery_cost: number; delivery_cost?: number;
delivery_cost_per_item: number; delivery_cost_per_item?: number;
document_index?: number; document_index?: number;
driver_name: string; driver_name?: string;
vehicle_plate: string; vehicle_plate?: string;
supplier_id?: number | null; supplier_id?: number | null;
products: { products: {
product_id: number; product_id: number;