mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-web-client into fix/dashboard
This commit is contained in:
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user