Files
trackgo-fe/packages/ui/src/components/Form/fields/async-select.field.tsx
T
shancheas 67ae5b6c11 refactor: improve code formatting and consistency across multiple files
- Standardized import statements and removed unnecessary line breaks for better readability in various components.
- Enhanced error handling and logging in the useElectronPrinter hook.
- Updated sample data formatting in AgGridShowcase for improved clarity.
- Refactored JSX elements for consistent indentation and structure in LandingSample, AuthPage, and EventsPage components.
- Consolidated and simplified conditional rendering logic in several components.

These changes aim to enhance code maintainability and readability throughout the project.
2026-08-25 17:50:48 +07:00

169 lines
4.7 KiB
TypeScript

import React from 'react';
import { useController, type FieldPath, type FieldValues, type UseControllerProps } from 'react-hook-form';
import type { SelectProps, MultiSelectProps } from '@mantine/core';
import { AsyncSelect } from '../custom/selects/AsyncSelect';
import type { AsyncSelectBaseProps, LoadOptionsFn } from '../custom/selects/types';
import { useTranslatedError } from '../useTranslatedError';
// ---------------------------------------------------------------------------
// FieldAsyncSelect — RHF-connected Async Select
// ---------------------------------------------------------------------------
//
// Thin RHF wrapper around the standalone AsyncSelect engine.
// Adds useController binding + i18n error translation.
//
// Data Mapping Contract:
// Single: RHF stores T | null → maps to Mantine string | null
// Multi: RHF stores T[] → maps to Mantine string[]
//
// Inversion of Control: accepts `loadOptions` callback instead of
// hardcoded API endpoint. The component is transport-agnostic.
// ---------------------------------------------------------------------------
/** Mantine props we manage ourselves */
type ManagedSelectProps =
| 'data'
| 'value'
| 'defaultValue'
| 'onChange'
| 'onBlur'
| 'error'
| 'filter'
| 'name'
| 'onSelect';
type ManagedMultiSelectProps =
| 'data'
| 'value'
| 'defaultValue'
| 'onChange'
| 'onBlur'
| 'error'
| 'filter'
| 'name'
| 'onSelect';
/** Async-specific props (IoC pattern) */
interface AsyncExtraProps<T> {
/** Async callback to load options — (search, page, prevOptions) => Promise */
loadOptions: LoadOptionsFn<T>;
/** Pre-loaded objects for edit forms (injected into dropdown regardless of fetch state) */
defaultOptions?: T[];
/** Search debounce delay in ms (default: 300) */
debounceMs?: number;
}
/** Single-select async RHF props */
export type FieldAsyncSelectSingleProps<
T extends Record<string, any>,
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = AsyncSelectBaseProps<T> &
AsyncExtraProps<T> &
UseControllerProps<TFieldValues, TName> &
Omit<SelectProps, ManagedSelectProps> & {
multiple?: false;
};
/** Multi-select async RHF props */
export type FieldAsyncSelectMultiProps<
T extends Record<string, any>,
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = AsyncSelectBaseProps<T> &
AsyncExtraProps<T> &
UseControllerProps<TFieldValues, TName> &
Omit<MultiSelectProps, ManagedMultiSelectProps> & {
multiple: true;
};
export type FieldAsyncSelectProps<
T extends Record<string, any>,
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = FieldAsyncSelectSingleProps<T, TFieldValues, TName> | FieldAsyncSelectMultiProps<T, TFieldValues, TName>;
// ---------------------------------------------------------------------------
// Component Implementation
// ---------------------------------------------------------------------------
function FieldAsyncSelectInner<
T extends Record<string, any>,
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>(props: FieldAsyncSelectProps<T, TFieldValues, TName>) {
const {
// RHF controller props
name,
control,
rules,
shouldUnregister,
defaultValue,
disabled,
// Object/Async engine props
valueKey,
labelKey,
renderLabel,
multiple,
filterOption,
onSelect: onSelectCallback,
loadOptions,
defaultOptions,
debounceMs,
// Remaining Mantine props
...mantineProps
} = props;
const {
field,
fieldState: { error },
} = useController<TFieldValues, TName>({
name,
control,
rules,
shouldUnregister,
defaultValue,
disabled,
});
const translatedError = useTranslatedError(error?.message);
const handleChange = (value: any) => {
field.onChange(value);
onSelectCallback?.(value);
};
const engineProps = {
valueKey,
labelKey,
renderLabel,
filterOption,
loadOptions,
defaultOptions,
debounceMs,
onBlur: field.onBlur,
error: translatedError,
disabled: field.disabled,
};
if (multiple) {
return (
<AsyncSelect<T>
multiple
{...engineProps}
value={field.value ?? []}
onChange={handleChange}
{...(mantineProps as any)}
/>
);
}
return (
<AsyncSelect<T> {...engineProps} value={field.value ?? null} onChange={handleChange} {...(mantineProps as any)} />
);
}
export const FieldAsyncSelect = React.memo(FieldAsyncSelectInner) as typeof FieldAsyncSelectInner;
(FieldAsyncSelect as any).displayName = 'FieldAsyncSelect';