департмент
This commit is contained in:
@@ -328,6 +328,7 @@ export const RoutesScreen: React.FC<IRoutesScreenProps> = ({ api, dictionary, on
|
||||
clearLabel="Без отдела (маршрут по умолчанию)"
|
||||
placeholder="Без отдела (маршрут по умолчанию)"
|
||||
searchPlaceholder="Поиск по названию отдела..."
|
||||
emptyMessage="У выбранной компании нет отделов в справочнике"
|
||||
onChange={departmentId => setForm({ ...form, departmentId })}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
@@ -20,6 +20,8 @@ export interface ISearchableSelectProps {
|
||||
/** Показывать пункт «очистить» — для необязательных полей. */
|
||||
allowClear?: boolean;
|
||||
clearLabel?: string;
|
||||
/** Текст, когда список опций пуст (не результат поиска, а вообще нечего выбирать). */
|
||||
emptyMessage?: string;
|
||||
}
|
||||
|
||||
export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
|
||||
@@ -31,25 +33,35 @@ export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
|
||||
disabled,
|
||||
loading,
|
||||
allowClear,
|
||||
clearLabel = 'Не выбрано'
|
||||
clearLabel = 'Не выбрано',
|
||||
emptyMessage = 'Ничего не найдено'
|
||||
}) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [query, setQuery] = React.useState('');
|
||||
const rootRef = React.useRef<HTMLDivElement>(null);
|
||||
const searchRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
const selected = options.filter(o => o.value === value)[0];
|
||||
// Сохранённое значение может отсутствовать в справочнике (запись удалили в 1С
|
||||
// или она не проходит текущий фильтр). Показать в этом случае плейсхолдер —
|
||||
// значит соврать, будто поле пустое, и затереть значение при сохранении.
|
||||
// Поэтому подмешиваем недостающую опцию явным образом.
|
||||
const effectiveOptions = React.useMemo(() => {
|
||||
if (!value || options.some(o => o.value === value)) return options;
|
||||
return [{ value, label: value, sublabel: 'Нет в справочнике' }, ...options];
|
||||
}, [options, value]);
|
||||
|
||||
const selected = effectiveOptions.filter(o => o.value === value)[0];
|
||||
|
||||
// Поиск на клиенте: справочник целиком уже загружен, отделов ~700 —
|
||||
// фильтровать в памяти дешевле, чем ходить на сервер на каждый символ.
|
||||
const filtered = React.useMemo(() => {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) return options;
|
||||
return options.filter(o =>
|
||||
if (!q) return effectiveOptions;
|
||||
return effectiveOptions.filter(o =>
|
||||
o.label.toLowerCase().indexOf(q) !== -1 ||
|
||||
(o.sublabel ? o.sublabel.toLowerCase().indexOf(q) !== -1 : false)
|
||||
);
|
||||
}, [options, query]);
|
||||
}, [effectiveOptions, query]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -110,7 +122,11 @@ export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
/>
|
||||
<div className={styles.list}>
|
||||
{filtered.length === 0 && <div className={styles.empty}>Ничего не найдено</div>}
|
||||
{filtered.length === 0 && (
|
||||
<div className={styles.empty}>
|
||||
{effectiveOptions.length === 0 ? emptyMessage : 'Ничего не найдено'}
|
||||
</div>
|
||||
)}
|
||||
{filtered.map(opt => (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user