diff --git a/package-lock.json b/package-lock.json index d5d876c..fac1abe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,9 @@ "version": "0.1.0", "dependencies": { "@tailwindcss/postcss": "^4.1.17", + "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.11", + "@tanstack/react-table": "^8.21.3", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "^16.0.7", @@ -2176,6 +2178,22 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.19.4.tgz", + "integrity": "sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==", + "license": "MIT", + "dependencies": { + "remove-accents": "0.5.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/query-core": { "version": "5.90.11", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.11.tgz", @@ -2202,6 +2220,39 @@ "react": "^18 || ^19" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -6665,6 +6716,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==", + "license": "MIT" + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", diff --git a/package.json b/package.json index 26f4079..aefcc87 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,9 @@ }, "dependencies": { "@tailwindcss/postcss": "^4.1.17", + "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.11", + "@tanstack/react-table": "^8.21.3", "clsx": "^2.1.1", "jose": "^6.1.2", "next": "^16.0.7", diff --git a/src/app/components/tanstakTable.tsx b/src/app/components/tanstakTable.tsx new file mode 100644 index 0000000..70f265a --- /dev/null +++ b/src/app/components/tanstakTable.tsx @@ -0,0 +1,302 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import { + useReactTable, + getCoreRowModel, + getSortedRowModel, + getPaginationRowModel, + getFilteredRowModel, + ColumnDef, + SortingState, + ColumnFiltersState, + FilterFn, +} from '@tanstack/react-table'; +import { rankItem } from '@tanstack/match-sorter-utils'; + +// Типы данных +type Person = { + id: number; + name: string; + age: number; + email: string; + department: string; + salary: number; +}; + +// Кастомная функция фильтрации +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + const itemRank = rankItem(row.getValue(columnId), value); + addMeta({ itemRank }); + return itemRank.passed; +}; + +// Определяем тип для фильтров +declare module '@tanstack/react-table' { + interface FilterFns { + fuzzy: FilterFn; + } +} + +export default function DataTable() { + // Данные + const [data] = useState([ + { id: 1, name: 'Иван Петров', age: 28, email: 'ivan@example.com', department: 'IT', salary: 80000 }, + { id: 2, name: 'Анна Сидорова', age: 32, email: 'anna@example.com', department: 'HR', salary: 65000 }, + { id: 3, name: 'Сергей Иванов', age: 45, email: 'sergey@example.com', department: 'Финансы', salary: 95000 }, + { id: 4, name: 'Мария Кузнецова', age: 26, email: 'maria@example.com', department: 'Маркетинг', salary: 60000 }, + { id: 5, name: 'Алексей Смирнов', age: 38, email: 'alex@example.com', department: 'IT', salary: 85000 }, + { id: 6, name: 'Ольга Васнецова', age: 29, email: 'olga@example.com', department: 'HR', salary: 62000 }, + { id: 7, name: 'Дмитрий Орлов', age: 41, email: 'dmitry@example.com', department: 'Финансы', salary: 92000 }, + { id: 8, name: 'Екатерина Новикова', age: 35, email: 'ekaterina@example.com', department: 'Маркетинг', salary: 70000 }, + ]); + + // Состояния + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [globalFilter, setGlobalFilter] = useState(''); + const [rowSelection, setRowSelection] = useState({}); + + // Определение колонок + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ), + enableSorting: false, + enableColumnFilter: false, + }, + { + accessorKey: 'id', + header: 'ID', + cell: (info) => info.getValue(), + }, + { + accessorKey: 'name', + header: 'Имя', + cell: (info) => info.getValue(), + filterFn: fuzzyFilter, + }, + { + accessorKey: 'age', + header: 'Возраст', + cell: (info) => info.getValue(), + }, + { + accessorKey: 'email', + header: 'Email', + cell: (info) => info.getValue(), + }, + { + accessorKey: 'department', + header: 'Отдел', + cell: (info) => info.getValue(), + }, + { + accessorKey: 'salary', + header: 'Зарплата', + cell: (info) => `$${Number(info.getValue()).toLocaleString()}`, + }, + ], + [] + ); + + // Создание таблицы + const table = useReactTable({ + data, + columns, + filterFns: { + fuzzy: fuzzyFilter, + }, + state: { + sorting, + columnFilters, + globalFilter, + rowSelection, + }, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onGlobalFilterChange: setGlobalFilter, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getFilteredRowModel: getFilteredRowModel(), + globalFilterFn: fuzzyFilter, // Используем саму функцию, а не строку + }); + + return ( +
+

Таблица сотрудников

+ + {/* Глобальный поиск */} +
+ setGlobalFilter(e.target.value)} + className="px-4 py-2 border rounded-md w-full max-w-md" + /> +
+ + {/* Фильтры по колонкам */} +
+ {table.getAllColumns() + .filter(column => column.getCanFilter() && column.id !== 'select') + .map(column => ( +
+ + {column.id === 'department' ? ( + + ) : column.id === 'age' ? ( + column.setFilterValue(e.target.value ? parseInt(e.target.value) : undefined)} + className="px-3 py-1 border rounded w-32" + /> + ) : ( + column.setFilterValue(e.target.value)} + className="px-3 py-1 border rounded" + /> + )} +
+ ))} +
+ + {/* Таблица */} +
+ + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + + {table.getRowModel().rows.map(row => ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ))} + +
+
+ {header.isPlaceholder + ? null + : typeof header.column.columnDef.header === 'string' + ? header.column.columnDef.header + : header.column.id} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+
+ {typeof cell.column.columnDef.cell === 'function' + ? cell.column.columnDef.cell(cell.getContext()) + : cell.getValue() as string} +
+
+ + {/* Пагинация */} +
+
+ + + + +
+ +
+ + Страница{' '} + + {table.getState().pagination.pageIndex + 1} из {table.getPageCount()} + + + + + Всего строк: {data.length} | Отфильтровано: {table.getFilteredRowModel().rows.length} + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/ui/dashboard/files-table.tsx b/src/app/ui/dashboard/files-table.tsx index de6ba47..6c96b70 100644 --- a/src/app/ui/dashboard/files-table.tsx +++ b/src/app/ui/dashboard/files-table.tsx @@ -1,7 +1,8 @@ +import DataTable from '@/app/components/tanstakTable'; export default function FilesTable() { return (
- table +
) } \ No newline at end of file