> function TableFieldSelect({ formField, value, onChange }: { formField: FormFieldData; value: any; onChange: (value: string) => void; }) { const options = formField.options && !Array.isArray(formField.options) ? formField.options : {}; const tableId = options.tableId; const displayColumn = options.displayColumn || ''; const { data: tableData, isLoading, error } = useQuery<{ table: { rows: Array<{ id: number; values: string[] }>; co lumns: Array<{ name: string }> } }>({ queryKey: ['/api/directories', tableId], enabled: !!tableId, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, retry: false, }); const [searchTerm, setSearchTerm] = useState(''); const [isOpen, setIsOpen] = useState(false); const inputRef = useRef(null); if (!tableId) { return
Таблица не настроена
; } if (isLoading) { return ; } if (error) { return
Нет доступа к таблице
; } const table = tableData?.table; if (!table || !table.rows?.length) { return
Таблица пуста
; } // Находим индекс столбца для отображения, с fallback на первый столбец let displayColumnIndex = table.columns?.findIndex(c => c.name === displayColumn); if (displayColumnIndex === undefined || displayColumnIndex < 0) { displayColumnIndex = 0; } const getDisplayValue = (rowId: string | number) => { const row = table.rows?.find(r => r.id.toString() === rowId?.toString()); return row?.values[displayColumnIndex!] || row?.values[0] || ''; }; const selectedRow = table.rows?.find(r => r.id.toString() === value?.toString()); const selectedDisplay = selectedRow ? (selectedRow.values[displayColumnIndex!] || selectedRow.values[0] || '') : '' ; const sortedRows = useMemo(() => { return [...table.rows].sort((a, b) => { const av = a.values[displayColumnIndex!] || a.values[0] || ''; const bv = b.values[displayColumnIndex!] || b.values[0] || ''; return av.localeCompare(bv, 'ru'); }); }, [table.rows, displayColumnIndex]); const filtered = useMemo(() => { if (!searchTerm.trim()) return sortedRows; const term = searchTerm.toLowerCase(); return sortedRows.filter(r => { const display = r.values[displayColumnIndex!] || r.values[0] || ''; return display.toLowerCase().includes(term); }); }, [sortedRows, searchTerm, displayColumnIndex]); const handleSelect = (rowId: string) => { onChange(rowId); setSearchTerm(''); setIsOpen(false); }; return (
{ setSearchTerm(e.target.value); setIsOpen(true); }} onFocus={() => { setSearchTerm(''); setIsOpen(true); }} data-testid={`select-table-${formField.code}`} />
e.preventDefault()} onInteractOutside={(e) => { if (inputRef.current && inputRef.current.contains(e.target as Node)) { e.preventDefault(); } }} >
  • { e.preventDefault(); handleSelect(''); }} > —
  • {filtered.map(row => (
  • { e.preventDefault(); handleSelect(row.id.toString()); }} >
    {row.values[displayColumnIndex!] || row.values[0]} ))} {filtered.length === 0 && (
  • Нет совпадений
  • )} ); } // Компонент выбора связанной задачи из другой формы function TaskFieldSelect({ formField, value, onChange }: {