2025-06-03 01:48:46 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useMemo, useCallback } from 'react'
|
|
|
|
|
|
import { FieldInfo } from '../types/common'
|
|
|
|
|
|
import {FiltersState, VisibilityState} from "../types/filters.ts";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Хук для управления фильтрами и видимостью колонок таблицы.
|
|
|
|
|
|
* @param schema Схема полей таблицы.
|
|
|
|
|
|
* @param initialData
|
|
|
|
|
|
* @returns Объекты состояния фильтров и видимости, а также функции для их изменения.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function useTableFilters(schema: FieldInfo[]| null, initialData: unknown[] | null) {
|
|
|
|
|
|
const [filters, setFilters] = useState<FiltersState>({})
|
|
|
|
|
|
const [visibleColumns, setVisibleColumns] = useState<VisibilityState>({})
|
|
|
|
|
|
|
|
|
|
|
|
// Инициализация видимости колонок
|
|
|
|
|
|
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]
|
|
|
|
|
|
}))
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
// Логика фильтрации данных, вынесенная из компонента
|
|
|
|
|
|
const filteredData = useMemo(() => {
|
|
|
|
|
|
if (!initialData || !schema){
|
|
|
|
|
|
return ;
|
|
|
|
|
|
}
|
|
|
|
|
|
return initialData.filter(item => {
|
|
|
|
|
|
return 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
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}, [initialData, filters, schema])
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
filters,
|
|
|
|
|
|
setFilters,
|
|
|
|
|
|
visibleColumns,
|
|
|
|
|
|
setVisibleColumns,
|
|
|
|
|
|
handleFilterChange,
|
|
|
|
|
|
handleColumnToggle,
|
|
|
|
|
|
filteredData
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function FilteredTable({ schema, filteredData, visibleColumns}: FilteredTableProps) {
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="filtered-table">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<table className="filtered-table-main" style={{ overflowX: 'auto', display: 'block' }}>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
{schema.map(
|
|
|
|
|
|
(field) =>
|
|
|
|
|
|
visibleColumns[field.name] && (
|
|
|
|
|
|
<th key={field.name} className="column-header">
|
|
|
|
|
|
{field.title}
|
|
|
|
|
|
</th>
|
|
|
|
|
|
)
|
|
|
|
|
|
)}
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
{filteredData.length > 0 ? (
|
|
|
|
|
|
filteredData.map((item) => (
|
|
|
|
|
|
<tr key={(item as {id: number}).id}>
|
|
|
|
|
|
{schema.map(
|
|
|
|
|
|
(field) =>
|
|
|
|
|
|
visibleColumns[field.name] && (
|
|
|
|
|
|
<td key={field.name} className="data-cell">
|
|
|
|
|
|
{item[field.name] ?? "-"}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
)
|
|
|
|
|
|
)}
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
))
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<td colSpan={schema.length} className="no-data">
|
|
|
|
|
|
Нет данных для отображения
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
|
const {
|
|
|
|
|
|
filters,
|
|
|
|
|
|
handleFilterChange,
|
|
|
|
|
|
visibleColumns,
|
|
|
|
|
|
handleColumnToggle,
|
|
|
|
|
|
filteredData
|
|
|
|
|
|
} = useTableFilters(schema, dataPage.items)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleExport = async () => {
|
|
|
|
|
|
if (!filteredData || filteredData.length === 0) {
|
|
|
|
|
|
alert('Нет данных для экспорта')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const blob = new Blob([buffer], {
|
|
|
|
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
|
|
|
|
})
|
|
|
|
|
|
saveAs(blob, "Отчёт.")
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
в handleExport сохранять csv
|