Фронтенд для сервиса #5

Merged
gorbenko.pavel merged 29 commits from trynode22 into main 2026-07-30 06:11:24 +00:00
2 changed files with 23 additions and 6 deletions
Showing only changes of commit ab5793b1a9 - Show all commits
@@ -328,6 +328,7 @@ export const RoutesScreen: React.FC<IRoutesScreenProps> = ({ api, dictionary, on
clearLabel="Без отдела (маршрут по умолчанию)" clearLabel="Без отдела (маршрут по умолчанию)"
placeholder="Без отдела (маршрут по умолчанию)" placeholder="Без отдела (маршрут по умолчанию)"
searchPlaceholder="Поиск по названию отдела..." searchPlaceholder="Поиск по названию отдела..."
emptyMessage="У выбранной компании нет отделов в справочнике"
onChange={departmentId => setForm({ ...form, departmentId })} onChange={departmentId => setForm({ ...form, departmentId })}
/> />
</Field> </Field>
@@ -20,6 +20,8 @@ export interface ISearchableSelectProps {
/** Показывать пункт «очистить» — для необязательных полей. */ /** Показывать пункт «очистить» — для необязательных полей. */
allowClear?: boolean; allowClear?: boolean;
clearLabel?: string; clearLabel?: string;
/** Текст, когда список опций пуст (не результат поиска, а вообще нечего выбирать). */
emptyMessage?: string;
} }
export const SearchableSelect: React.FC<ISearchableSelectProps> = ({ export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
@@ -31,25 +33,35 @@ export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
disabled, disabled,
loading, loading,
allowClear, allowClear,
clearLabel = 'Не выбрано' clearLabel = 'Не выбрано',
emptyMessage = 'Ничего не найдено'
}) => { }) => {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const [query, setQuery] = React.useState(''); const [query, setQuery] = React.useState('');
const rootRef = React.useRef<HTMLDivElement>(null); const rootRef = React.useRef<HTMLDivElement>(null);
const searchRef = React.useRef<HTMLInputElement>(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 — // Поиск на клиенте: справочник целиком уже загружен, отделов ~700 —
// фильтровать в памяти дешевле, чем ходить на сервер на каждый символ. // фильтровать в памяти дешевле, чем ходить на сервер на каждый символ.
const filtered = React.useMemo(() => { const filtered = React.useMemo(() => {
const q = query.trim().toLowerCase(); const q = query.trim().toLowerCase();
if (!q) return options; if (!q) return effectiveOptions;
return options.filter(o => return effectiveOptions.filter(o =>
o.label.toLowerCase().indexOf(q) !== -1 || o.label.toLowerCase().indexOf(q) !== -1 ||
(o.sublabel ? o.sublabel.toLowerCase().indexOf(q) !== -1 : false) (o.sublabel ? o.sublabel.toLowerCase().indexOf(q) !== -1 : false)
); );
}, [options, query]); }, [effectiveOptions, query]);
React.useEffect(() => { React.useEffect(() => {
if (!open) return; if (!open) return;
@@ -110,7 +122,11 @@ export const SearchableSelect: React.FC<ISearchableSelectProps> = ({
onChange={e => setQuery(e.target.value)} onChange={e => setQuery(e.target.value)}
/> />
<div className={styles.list}> <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 => ( {filtered.map(opt => (
<button <button
type="button" type="button"