`components/useTableFilters.tsx` ```ts // добавьте новую функцию для экспорта CSV export function useTableFilters(schema: FieldInfo[]| null, initialData: unknown[] | null) { const [filters, setFilters] = useState({}) const [visibleColumns, setVisibleColumns] = useState({}) // Инициализация видимости колонок useMemo(() => { if (!schema){ return; } const initialVisibility: VisibilityState = {} schema.forEach(field => { initialVisibility[field.name] = true }) setVisibleColumns(initialVisibility) }, [schema]) // Обновление фильтров const handleFilterChange = useCallback((name: string, filterPart: unknown) => { setFilters(prev => ({ ...prev, [name]: { ...prev[name], ...filterPart } })) }, []) // Переключатель видимости колонки const handleColumnToggle = useCallback((name: string) => { setVisibleColumns(prev => ({ ...prev, [name]: !prev[name] })) }, []) // Функция экспорта данных в CSV const handleExportCSV = useCallback(() => { if (!initialData || !schema || initialData.length === 0) { alert('Нет данных для экспорта') return } const visibleFields = schema.filter(field => visibleColumns[field.name]) const header = visibleFields.map(field => `"${field.title.replace(/"/g, '""')}"`).join(';') const rows = initialData .filter(item => schema.every(field => { const value = item[field.name] const filter = filters[field.name] if (!filter) return true if (field.type === 'string') { if (filter.searchText) { if (typeof value !== 'string') return false if (!value.toLowerCase().includes(filter.searchText.toLowerCase())) return false } if (filter.exactMatch && filter.searchText) { return value.toLowerCase() === filter.searchText.toLowerCase() } if (filter.empty !== undefined) { const isEmpty = value === '' || value === null || value === undefined if (filter.empty && !isEmpty) return false if (filter.notEmpty && isEmpty) return false } } else if (field.format === 'date') { const dateValue = new Date(value as string).getTime() if (filter.fromDate) { const fromTime = new Date(filter.fromDate).getTime() if (dateValue < fromTime) return false } if (filter.toDate) { const toTime = new Date(filter.toDate).getTime() if (dateValue > toTime) return false } } else if (field.type === 'number') { const numValue = Number(value) if (filter.gte !== undefined && numValue < filter.gte) return false if (filter.lte !== undefined && numValue > filter.lte) return false } return true })) .map(item => { const row = visibleFields.map(field => { const val = item[field.name] // Обработка null/undefined const strVal = val !== undefined && val !== null ? String(val) : '' // Экранирование кавычек return `"${strVal.replace(/"/g, '""')}"` }) return row.join(';') }) const csvContent = [header, ...rows].join('\n') // Создаем ссылку для скачивания const blob = new Blob([csvContent], { type: 'text/csv' }) const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = 'export.csv' document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) }, [initialData, schema, filters, visibleColumns]) return { filters, setFilters, visibleColumns, setVisibleColumns, handleFilterChange, handleColumnToggle, filteredData, handleExportCSV // добавьте сюда } } ``` `Ваш файл, где вызывается handleExport` (примеры использования) ```ts const { filters, handleFilterChange, visibleColumns, handleColumnToggle, filteredData, handleExportCSV // новая функция } = useTableFilters(schema, dataPage.items) const handleExport = async () => { handleExportCSV() } ```