mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'development' of gitlab.com:mbugroup/lti-web-client into fix/adjustment-uniformity-ui
This commit is contained in:
@@ -54,6 +54,9 @@ interface SelectInputBaseProps<T = OptionType> {
|
||||
wrapper?: string;
|
||||
label?: string;
|
||||
select?: string;
|
||||
inputPrefix?: string;
|
||||
inputSuffix?: string;
|
||||
inputPrefixSuffixWrapper?: string;
|
||||
};
|
||||
isError?: boolean;
|
||||
errorMessage?: string;
|
||||
@@ -62,6 +65,8 @@ interface SelectInputBaseProps<T = OptionType> {
|
||||
delay?: number;
|
||||
onInputChange?: (search: string) => void;
|
||||
startAdornment?: ReactNode;
|
||||
inputPrefix?: ReactNode;
|
||||
inputSuffix?: ReactNode;
|
||||
menuPortalTarget?: HTMLElement | null;
|
||||
closeMenuOnSelect?: boolean;
|
||||
hideSelectedOptions?: boolean;
|
||||
@@ -84,7 +89,7 @@ const CustomControl = <
|
||||
>(
|
||||
props: ControlProps<Option, IsMulti, Group>
|
||||
) => {
|
||||
const { children } = props;
|
||||
const { children, innerProps } = props;
|
||||
|
||||
const customProps = props.selectProps as unknown as {
|
||||
shouldShowAdornment?: boolean;
|
||||
@@ -96,7 +101,7 @@ const CustomControl = <
|
||||
|
||||
return (
|
||||
<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}
|
||||
{children}
|
||||
</div>
|
||||
@@ -153,6 +158,8 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
createables = false,
|
||||
onInputChange,
|
||||
startAdornment,
|
||||
inputPrefix,
|
||||
inputSuffix,
|
||||
menuPortalTarget,
|
||||
closeMenuOnSelect,
|
||||
hideSelectedOptions,
|
||||
@@ -227,111 +234,264 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
||||
</span>
|
||||
)}
|
||||
|
||||
<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={{
|
||||
...(!startAdornment && {
|
||||
{inputPrefix || inputSuffix ? (
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex text-sm',
|
||||
className?.inputPrefixSuffixWrapper
|
||||
)}
|
||||
>
|
||||
{inputPrefix && (
|
||||
<div
|
||||
className={cn(
|
||||
'inline-flex items-center px-3 border border-r-0 border-base-content/10 rounded-l-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?.inputPrefix
|
||||
)}
|
||||
>
|
||||
{inputPrefix}
|
||||
</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 }) =>
|
||||
cn('w-full rounded-lg! border bg-white transition-shadow', {
|
||||
'cursor-pointer!': !readOnly && !isDisabled,
|
||||
'border-red-500! ring-2 ring-red-200': isError,
|
||||
'border-indigo-500 ring-2 ring-indigo-200': isFocused,
|
||||
'border-base-content/10!': !isError && !isFocused,
|
||||
'bg-gray-100 text-gray-400 cursor-not-allowed':
|
||||
isDisabled && !readOnly,
|
||||
'bg-transparent! cursor-not-allowed!': readOnly,
|
||||
}),
|
||||
cn(
|
||||
'w-full border bg-white transition-shadow',
|
||||
// Gunakan rounded-lg untuk semua kasus
|
||||
'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,
|
||||
}
|
||||
),
|
||||
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}
|
||||
/>
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
|
||||
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||
{!isError && bottomLabel && (
|
||||
|
||||
@@ -27,12 +27,12 @@ type MovementFormSchemaType = {
|
||||
product_qty: number | string;
|
||||
}[];
|
||||
deliveries: {
|
||||
delivery_cost?: number | string;
|
||||
delivery_cost_per_item?: number | string;
|
||||
delivery_cost?: number | string | null;
|
||||
delivery_cost_per_item?: number | string | null;
|
||||
document?: File | MovementDocument | null;
|
||||
document_path?: string | null;
|
||||
driver_name: string;
|
||||
vehicle_plate: string;
|
||||
driver_name?: string | null;
|
||||
vehicle_plate?: string | null;
|
||||
supplier?: {
|
||||
value: number;
|
||||
label: string;
|
||||
@@ -59,12 +59,12 @@ export type ProductSchema = {
|
||||
};
|
||||
|
||||
export type DeliverySchema = {
|
||||
delivery_cost?: number | string;
|
||||
delivery_cost_per_item?: number | string;
|
||||
delivery_cost?: number | string | null;
|
||||
delivery_cost_per_item?: number | string | null;
|
||||
document?: File | MovementDocument | null;
|
||||
document_path?: string | null;
|
||||
driver_name: string;
|
||||
vehicle_plate: string;
|
||||
driver_name?: string | null;
|
||||
vehicle_plate?: string | null;
|
||||
supplier?: {
|
||||
value: number;
|
||||
label: string;
|
||||
@@ -120,32 +120,26 @@ const DeliveryDocumentSchema = Yup.mixed<File | MovementDocument>()
|
||||
|
||||
const DeliveryObjectSchema: Yup.ObjectSchema<DeliverySchema> = Yup.object({
|
||||
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!')
|
||||
.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)
|
||||
);
|
||||
}),
|
||||
.typeError('Biaya harus berupa angka!'),
|
||||
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!')
|
||||
.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)
|
||||
);
|
||||
}),
|
||||
.typeError('Biaya per item harus berupa angka!'),
|
||||
document_path: Yup.string().nullable().optional(),
|
||||
document_index: Yup.number().optional(),
|
||||
document: DeliveryDocumentSchema,
|
||||
driver_name: Yup.string().required('Nama sopir wajib diisi!'),
|
||||
vehicle_plate: Yup.string().required('Plat nomor wajib diisi!'),
|
||||
driver_name: Yup.string().optional().nullable(),
|
||||
vehicle_plate: Yup.string().optional().nullable(),
|
||||
supplier: Yup.object({
|
||||
value: Yup.number().min(1).required(),
|
||||
label: Yup.string().required(),
|
||||
@@ -279,12 +273,12 @@ export const getMovementFormInitialValues = (
|
||||
}) ?? [],
|
||||
})) ?? [
|
||||
{
|
||||
delivery_cost: undefined,
|
||||
delivery_cost_per_item: undefined,
|
||||
delivery_cost: null,
|
||||
delivery_cost_per_item: null,
|
||||
document: null,
|
||||
document_path: null,
|
||||
driver_name: '',
|
||||
vehicle_plate: '',
|
||||
driver_name: null,
|
||||
vehicle_plate: null,
|
||||
supplier: null,
|
||||
supplier_id: 0,
|
||||
products: [
|
||||
|
||||
@@ -228,19 +228,49 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
delivery_cost: parseInt((d.delivery_cost || '').toString()) || 0,
|
||||
delivery_cost_per_item:
|
||||
parseInt((d.delivery_cost_per_item || '').toString()) || 0,
|
||||
document_index: documentIndex,
|
||||
driver_name: d.driver_name,
|
||||
vehicle_plate: d.vehicle_plate,
|
||||
supplier_id: d.supplier_id,
|
||||
const deliveryObj: {
|
||||
products: Array<{ product_id: number; product_qty: number }>;
|
||||
delivery_cost?: number;
|
||||
delivery_cost_per_item?: number;
|
||||
document_index?: number;
|
||||
driver_name?: string;
|
||||
vehicle_plate?: string;
|
||||
supplier_id?: number;
|
||||
} = {
|
||||
products: d.products.map((p) => ({
|
||||
product_id: p.product_id,
|
||||
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 = {
|
||||
@@ -844,32 +874,15 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
(warehouseId: number) => {
|
||||
const stockInfo = warehouseStockMap.get(warehouseId);
|
||||
if (!stockInfo) {
|
||||
return (
|
||||
<Badge
|
||||
variant='ghost'
|
||||
color='neutral'
|
||||
size='sm'
|
||||
className={{ badge: 'whitespace-nowrap font-semibold' }}
|
||||
>
|
||||
Kosong
|
||||
</Badge>
|
||||
);
|
||||
return <span className='text-xs'>Kosong</span>;
|
||||
}
|
||||
|
||||
const { productCount } = stockInfo;
|
||||
let color: 'neutral' | 'success' | 'warning' = 'neutral';
|
||||
if (productCount === 0) color = 'warning';
|
||||
else if (productCount > 0) color = 'success';
|
||||
|
||||
return (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color={color}
|
||||
size='sm'
|
||||
className={{ badge: 'whitespace-nowrap font-semibold' }}
|
||||
>
|
||||
<span className='text-xs whitespace-nowrap'>
|
||||
Tersedia {productCount} produk
|
||||
</Badge>
|
||||
</span>
|
||||
);
|
||||
},
|
||||
[warehouseStockMap]
|
||||
@@ -1330,7 +1343,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
errorMessage={formik.errors.source_warehouse_id as string}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
startAdornment={
|
||||
inputPrefix={
|
||||
formik.values.source_warehouse_id
|
||||
? getWarehouseStockAdornment(
|
||||
formik.values.source_warehouse_id
|
||||
@@ -1388,7 +1401,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
errorMessage={formik.errors.destination_warehouse_id as string}
|
||||
isDisabled={type === 'detail'}
|
||||
isClearable
|
||||
startAdornment={
|
||||
inputPrefix={
|
||||
formik.values.destination_warehouse_id
|
||||
? getWarehouseStockAdornment(
|
||||
formik.values.destination_warehouse_id
|
||||
@@ -1647,43 +1660,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
</span>
|
||||
</th>
|
||||
<th>Supplier</th>
|
||||
<th>
|
||||
Plat Nomor
|
||||
<span
|
||||
className='tooltip tooltip-error tooltip-bottom z-9999'
|
||||
data-tip='required'
|
||||
>
|
||||
<span className='text-error'>*</span>
|
||||
</span>
|
||||
</th>
|
||||
<th>Plat Nomor</th>
|
||||
<th>Dokumen</th>
|
||||
<th>
|
||||
Biaya Pengiriman (Rp.)
|
||||
<span
|
||||
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>
|
||||
<th>Biaya Pengiriman (Rp.)</th>
|
||||
<th>Biaya Per Item (Rp.)</th>
|
||||
<th>Nama Sopir</th>
|
||||
{type !== 'detail' && <th>Aksi</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -1780,10 +1761,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
name={`deliveries.${idx}.vehicle_plate`}
|
||||
placeholder='Masukkan plat nomor...'
|
||||
value={delivery.vehicle_plate}
|
||||
value={delivery.vehicle_plate ?? ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
{...isRepeaterInputError(
|
||||
@@ -1871,10 +1851,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
</td>
|
||||
<td>
|
||||
<NumberInput
|
||||
required
|
||||
name={`deliveries.${idx}.delivery_cost`}
|
||||
placeholder='Masukkan biaya pengiriman...'
|
||||
value={delivery.delivery_cost || ''}
|
||||
value={delivery.delivery_cost ?? ''}
|
||||
onChange={handleDeliveryCostChangeWrapper(idx)}
|
||||
onBlur={formik.handleBlur}
|
||||
decimalScale={0}
|
||||
@@ -1895,10 +1874,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
</td>
|
||||
<td>
|
||||
<NumberInput
|
||||
required
|
||||
name={`deliveries.${idx}.delivery_cost_per_item`}
|
||||
placeholder='Masukkan biaya per item...'
|
||||
value={delivery.delivery_cost_per_item || ''}
|
||||
value={delivery.delivery_cost_per_item ?? ''}
|
||||
onChange={handleDeliveryCostPerItemChangeWrapper(idx)}
|
||||
onBlur={formik.handleBlur}
|
||||
decimalScale={0}
|
||||
@@ -1919,10 +1897,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
||||
</td>
|
||||
<td>
|
||||
<TextInput
|
||||
required
|
||||
name={`deliveries.${idx}.driver_name`}
|
||||
placeholder='Masukkan nama sopir...'
|
||||
value={delivery.driver_name}
|
||||
value={delivery.driver_name ?? ''}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
{...isRepeaterInputError(
|
||||
|
||||
@@ -1125,16 +1125,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color={color}
|
||||
size='sm'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||
}}
|
||||
>
|
||||
<span className={'whitespace-nowrap text-xs'}>
|
||||
Periode {projectFlockKandangLookup.project_flock?.period}
|
||||
</Badge>
|
||||
</span>
|
||||
);
|
||||
}, [recordedProjectFlockKandangIds, projectFlockKandangLookup]);
|
||||
|
||||
@@ -1150,33 +1143,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
const hasOvkFlag = productWarehouse.product.flags?.includes('OVK');
|
||||
|
||||
if (hasPakanFlag) {
|
||||
return (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='info'
|
||||
size='sm'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||
}}
|
||||
>
|
||||
PAKAN
|
||||
</Badge>
|
||||
);
|
||||
return <span className={'whitespace-nowrap text-xs'}>PAKAN</span>;
|
||||
}
|
||||
|
||||
if (hasOvkFlag) {
|
||||
return (
|
||||
<Badge
|
||||
variant='soft'
|
||||
color='secondary'
|
||||
size='sm'
|
||||
className={{
|
||||
badge: 'whitespace-nowrap font-semibold text-xs px-2',
|
||||
}}
|
||||
>
|
||||
OVK
|
||||
</Badge>
|
||||
);
|
||||
return <span className={'whitespace-nowrap text-xs'}>OVK</span>;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -1826,7 +1797,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
Boolean(formik.errors.kandang_id)
|
||||
}
|
||||
errorMessage={formik.errors.kandang_id as string}
|
||||
startAdornment={
|
||||
inputPrefix={
|
||||
projectFlockKandangLookup || projectFlockKandangDetail
|
||||
? getProjectFlockBadgeAdornment()
|
||||
: undefined
|
||||
@@ -2458,7 +2429,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
||||
!formik.values.project_flock_kandang_id
|
||||
}
|
||||
isClearable={type !== 'detail'}
|
||||
startAdornment={
|
||||
inputPrefix={
|
||||
stock.product_warehouse_id
|
||||
? getProductFlagBadgeAdornment(
|
||||
stock.product_warehouse_id
|
||||
|
||||
+4
-4
@@ -68,11 +68,11 @@ export type CreateMovementPayloadData = {
|
||||
product_qty: number;
|
||||
}[];
|
||||
deliveries: {
|
||||
delivery_cost: number;
|
||||
delivery_cost_per_item: number;
|
||||
delivery_cost?: number;
|
||||
delivery_cost_per_item?: number;
|
||||
document_index?: number;
|
||||
driver_name: string;
|
||||
vehicle_plate: string;
|
||||
driver_name?: string;
|
||||
vehicle_plate?: string;
|
||||
supplier_id?: number | null;
|
||||
products: {
|
||||
product_id: number;
|
||||
|
||||
Reference in New Issue
Block a user