- 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.
169 lines
4.7 KiB
TypeScript
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';
|