diff --git a/src/components/FilteredTable.tsx b/src/components/FilteredTable.tsx new file mode 100644 index 0000000..1ea0429 --- /dev/null +++ b/src/components/FilteredTable.tsx @@ -0,0 +1,226 @@ +import {useState, useMemo, useEffect} from 'react' +import { FieldInfo } from '../types/common' + +interface FilteredTableProps { + schema: FieldInfo[] + data: object[] + showFilters?: boolean +} + +/** + * Компонент динамической фильтрованной таблицы. + * Позволяет фильтровать данные по различным условиям, в зависимости от типа поля. + */ +export function FilteredTable({ schema, data, showFilters = false }: FilteredTableProps) { + const [filters, setFilters] = useState>({}) + const [visibleColumns, setVisibleColumns] = useState>({}) + + // Изначальный массив данных для фильтрации + const initialData = useMemo(() => data, [data]) + + // Массив отфильтрованных данных + const filteredData = useMemo(() => { + 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.type === 'date') { + const dateValue = new Date(value).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]) + + // Обработчик изменения фильтров + const handleFilterChange = (name: string, filterPart: unknown) => { + setFilters(prev => ({ + ...prev, + [name]: { + ...prev[name], + ...filterPart + } + })) + } + + // Обработчик переключения видимости колонок + const handleColumnToggle = (name: string) => { + setVisibleColumns(prev => ({ + ...prev, + [name]: !prev[name] + })) + } + + useEffect(() => { + + let newVisibleColumns:Record = {} + + for (const field of schema) { + newVisibleColumns = {...newVisibleColumns, [field.name]:true} + + } + setVisibleColumns(newVisibleColumns) + }, [schema]); + + console.log(visibleColumns) + + return ( +
+ {showFilters && ( +
+ {schema.map(field => ( +
+ + {field.type === 'string' && ( +
+ handleFilterChange(field.name, {searchText: e.target.value})} + /> + + +
+ + +
+
+ )} + {field.type === 'date' && ( +
+ handleFilterChange(field.name, { fromDate: e.target.value })} + /> + handleFilterChange(field.name, { toDate: e.target.value })} + /> +
+ )} + {field.type === 'number' && ( +
+ + handleFilterChange(field.name, { gte: e.target.value !== '' ? Number(e.target.value) : undefined }) + } + /> + + handleFilterChange(field.name, { lte: e.target.value !== '' ? Number(e.target.value) : undefined }) + } + /> +
+ )} +
+ ))} +
+ )} + + + + {schema.map( + (field) => + visibleColumns[field.name] !== false && ( + + ) + )} + + + + {filteredData.length > 0 ? ( + filteredData.map((item, i) => ( + + {schema.map( + (field) => + visibleColumns[field.name] !== false && ( + + ) + )} + + )) + ) : ( + + + + )} + +
+ {field.title} +
+ {item[field.name]} +
+ Нет данных для отображения +
+
+ ) +} \ No newline at end of file diff --git a/src/pages/dashboards/DataPage.tsx b/src/pages/dashboards/DataPage.tsx index 26c203a..0757b07 100644 --- a/src/pages/dashboards/DataPage.tsx +++ b/src/pages/dashboards/DataPage.tsx @@ -1,16 +1,20 @@ import { useParams } from 'react-router-dom'; import { useApiDocumentation, useDataPage } from "../../API/hooks.ts"; import { FieldInfo } from "../../types/common.ts"; +import {FilteredTable} from "../../components/FilteredTable.tsx"; +import {useState} from "react"; +import {LoadingData} from "../../components/LoadingData.tsx"; export function DataPage() { const { applicationName, endpointName } = useParams<{ applicationName: string; endpointName: string }>(); - // Получаем схему документации API const { schema, loading: loadingSchema } = useApiDocumentation(applicationName || "", endpointName || ""); - - // Получаем данные с помощью пагинации const dataPage = useDataPage(applicationName || "", endpointName || "", {}, !loadingSchema); + const [filterShow, setFilterShow] = useState(false); + + + // Функция для экспорта данных в Excel (пока пустая) const handleExport = () => { // Логика для экспорта данных @@ -23,7 +27,7 @@ export function DataPage() { // Функция для фильтрации данных (пока пустая) const handleFilters = () => { - // Логика для фильтрации данных + setFilterShow(!filterShow) }; // Если данные или схема загружаются, отображаем загрузку @@ -38,38 +42,13 @@ export function DataPage() { -
- - - - {schema?.map((field: FieldInfo) => ( - - ))} - - - - {dataPage.items && dataPage.items.length > 0 ? ( - dataPage.items.map((item: unknown) => ( - - {schema?.map((field: FieldInfo) => ( - - ))} - - )) - ) : ( - - - - )} - -
- {field.title} -
- {item[field.name] || "-"} -
- Нет данных для отображения -
-
+ + + ); } \ No newline at end of file