Split Filters logic

This commit is contained in:
Mikan
2025-06-03 00:35:10 +03:00
parent b4ace0d368
commit 672a884ae9
6 changed files with 237 additions and 180 deletions

View File

@@ -182,7 +182,6 @@ export function useApiDocumentation(
throw new Error(`$ref not found in response schema for ${pathKey}`);
}
console.log(schemaRef.match(/#\/definitions\/(\w+)/))
// Получить название определения
const defNameMatch = schemaRef.match(/#\/definitions\/(\w+)/);

View File

@@ -1,190 +1,22 @@
import {useState, useMemo, useEffect} from 'react'
import { FieldInfo } from '../types/common'
import {VisibilityState} from "../types/filters.ts";
interface FilteredTableProps {
schema: FieldInfo[]
data: object[]
showFilters?: boolean
filteredData: object[]
visibleColumns: VisibilityState
}
/**
* Компонент динамической фильтрованной таблицы.
* Позволяет фильтровать данные по различным условиям, в зависимости от типа поля.
*/
export function FilteredTable({ schema, data, showFilters = false }: FilteredTableProps) {
const [filters, setFilters] = useState<Record<string, unknown>>({})
const [visibleColumns, setVisibleColumns] = useState<Record<string, boolean>>({})
// Изначальный массив данных для фильтрации
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<string, boolean> = {}
for (const field of schema) {
newVisibleColumns = {...newVisibleColumns, [field.name]:true}
}
setVisibleColumns(newVisibleColumns)
}, [schema]);
console.log(visibleColumns)
export function FilteredTable({ schema, filteredData, visibleColumns}: FilteredTableProps) {
return (
<div className="filtered-table">
{showFilters && (
<div className="filters-block">
{schema.map(field => (
<div key={field.name} className="filter-item">
<label>
<input
type="checkbox"
checked={visibleColumns[field.name]}
onChange={() => handleColumnToggle(field.name)}
/>
{field.title}
</label>
{field.type === 'string' && (
<div className="string-filters">
<input
type="text"
placeholder="Поиск"
onChange={(e) => handleFilterChange(field.name, {searchText: e.target.value})}
/>
<label>
<input
type="checkbox"
checked={filters[field.name]?.exactMatch || false}
onChange={() => handleFilterChange(field.name, {exactMatch: !filters[field.name]?.exactMatch})}
/> Строгое совпадение
</label>
<div>
<label>
<input
type="checkbox"
checked={filters[field.name]?.notEmpty || false}
onChange={(e) =>
handleFilterChange(field.name, {
notEmpty: e.target.checked,
empty: false
})
}
/>Не пустые
</label>
<label>
<input
type="checkbox"
checked={filters[field.name]?.empty || false}
onChange={(e) =>
handleFilterChange(field.name, {
empty: e.target.checked,
notEmpty: false
})
}
/>Пустые
</label>
</div>
</div>
)}
{field.type === 'date' && (
<div className="date-filters">
<input
type="date"
onChange={(e) => handleFilterChange(field.name, { fromDate: e.target.value })}
/>
<input
type="date"
onChange={(e) => handleFilterChange(field.name, { toDate: e.target.value })}
/>
</div>
)}
{field.type === 'number' && (
<div className="number-filters">
<input
type="number"
placeholder="От"
onChange={(e) =>
handleFilterChange(field.name, { gte: e.target.value !== '' ? Number(e.target.value) : undefined })
}
/>
<input
type="number"
placeholder="До"
onChange={(e) =>
handleFilterChange(field.name, { lte: e.target.value !== '' ? Number(e.target.value) : undefined })
}
/>
</div>
)}
</div>
))}
</div>
)}
<table className="filtered-table-main" style={{ overflowX: 'auto', display: 'block' }}>
<thead>
<tr>
@@ -200,13 +32,13 @@ export function FilteredTable({ schema, data, showFilters = false }: FilteredTab
</thead>
<tbody>
{filteredData.length > 0 ? (
filteredData.map((item, i) => (
<tr key={item['id'] || i}>
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] || "-"}
{item[field.name] ?? "-"}
</td>
)
)}

107
src/components/Filters.tsx Normal file
View File

@@ -0,0 +1,107 @@
import {FieldInfo} from "../types/common.ts";
import {FiltersState, VisibilityState} from "../types/filters.ts";
interface FiltersProps{
schema: FieldInfo[];
visibleColumns: VisibilityState;
handleColumnToggle(name: string): void;
handleFilterChange(name: string, filterPart: unknown):void;
filters: FiltersState;
isShow?: boolean;
}
export function Filters({schema, visibleColumns, handleColumnToggle, handleFilterChange, filters, isShow=false}:FiltersProps ){
if (!isShow){
return null;
}
return (
<div className="filters-block">
{schema.map(field => (
<div key={field.name} className="filter-item">
<label>
<input
type="checkbox"
checked={visibleColumns[field.name]}
onChange={() => handleColumnToggle(field.name)}
/>
{field.title}
</label>
{field.type === 'string' && (
<div className="string-filters">
<input
type="text"
placeholder="Поиск"
onChange={(e) => handleFilterChange(field.name, {searchText: e.target.value})}
/>
<label>
<input
type="checkbox"
checked={filters[field.name]?.exactMatch || false}
onChange={() => handleFilterChange(field.name, {exactMatch: !filters[field.name]?.exactMatch})}
/> Строгое совпадение
</label>
<div>
<label>
<input
type="checkbox"
checked={filters[field.name]?.notEmpty || false}
onChange={(e) =>
handleFilterChange(field.name, {
notEmpty: e.target.checked,
empty: false
})
}
/> Не пустые
</label>
<label>
<input
type="checkbox"
checked={filters[field.name]?.empty || false}
onChange={(e) =>
handleFilterChange(field.name, {
empty: e.target.checked,
notEmpty: false
})
}
/> Пустые
</label>
</div>
</div>
)}
{field.type === 'date' && (
<div className="date-filters">
<input
type="date"
onChange={(e) => handleFilterChange(field.name, {fromDate: e.target.value})}
/>
<input
type="date"
onChange={(e) => handleFilterChange(field.name, {toDate: e.target.value})}
/>
</div>
)}
{field.type === 'number' && (
<div className="number-filters">
<input
type="number"
placeholder="От"
onChange={(e) =>
handleFilterChange(field.name, {gte: e.target.value !== '' ? Number(e.target.value) : undefined})
}
/>
<input
type="number"
placeholder="До"
onChange={(e) =>
handleFilterChange(field.name, {lte: e.target.value !== '' ? Number(e.target.value) : undefined})
}
/>
</div>
)}
</div>
))}
</div>
);
}

View File

@@ -0,0 +1,104 @@
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
}
}

View File

@@ -4,6 +4,8 @@ import { FieldInfo } from "../../types/common.ts";
import {FilteredTable} from "../../components/FilteredTable.tsx";
import {useState} from "react";
import {LoadingData} from "../../components/LoadingData.tsx";
import {useTableFilters} from "../../hooks/useTableFilters.ts";
import {Filters} from "../../components/Filters.tsx";
export function DataPage() {
const { applicationName, endpointName } = useParams<{ applicationName: string; endpointName: string }>();
@@ -13,7 +15,13 @@ export function DataPage() {
const [filterShow, setFilterShow] = useState(false);
const {
filters,
handleFilterChange,
visibleColumns,
handleColumnToggle,
filteredData
} = useTableFilters(schema, dataPage.items)
// Функция для экспорта данных в Excel (пока пустая)
const handleExport = () => {
@@ -22,7 +30,7 @@ export function DataPage() {
// Функция для добавления новых данных (пока пустая)
const handleAdd = () => {
// Логика для добавления данных
alert("Недостаточно прав!")
};
// Функция для фильтрации данных (пока пустая)
@@ -43,10 +51,14 @@ export function DataPage() {
<button className="filters-button" onClick={handleFilters}>Фильтры</button>
</div>
<LoadingData data={dataPage.items}>
<Filters schema={schema as FieldInfo[]} visibleColumns={visibleColumns} handleColumnToggle={handleColumnToggle} handleFilterChange={handleFilterChange} filters={filters} isShow={filterShow}/>
<FilteredTable
schema={schema as FieldInfo[]}
data={dataPage.items as object[]}
showFilters={filterShow}
visibleColumns={visibleColumns}
filteredData={filteredData as object[]}
/>
</LoadingData>
</div>

3
src/types/filters.ts Normal file
View File

@@ -0,0 +1,3 @@
export type FiltersState = Record<string, unknown>
export type VisibilityState = Record<string, boolean>