From a6f202f659385a092c20af8b630be57c8e6eb532 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sat, 27 Jun 2026 01:42:39 +0700 Subject: [PATCH] =?UTF-8?q?feat(web):=20ReUI=20DataGrid=20=E2=80=94=20Colu?= =?UTF-8?q?mn=20Icons,=20=D0=BE=D0=B1=D0=BE=D0=BB=D0=BE=D1=87=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D1=85=D0=B5=D0=BB=D0=BF=D0=B5=D1=80=D1=8B=20=D1=8F?= =?UTF-8?q?=D1=87=D0=B5=D0=B5=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataGridColumnHeader в columnDefFromDataTable, bordered-контейнер, русская пагинация, хелперы dataGridCellStack/WithIcon/WithFlag. Co-authored-by: Cursor --- apps/web/src/components/data-grid-card.tsx | 103 ++++++++++++++------ apps/web/src/components/data-grid-cells.tsx | 42 ++++++++ apps/web/src/components/data-table-card.tsx | 5 + 3 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 apps/web/src/components/data-grid-cells.tsx diff --git a/apps/web/src/components/data-grid-card.tsx b/apps/web/src/components/data-grid-card.tsx index 43466a3..e7a69f3 100644 --- a/apps/web/src/components/data-grid-card.tsx +++ b/apps/web/src/components/data-grid-card.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState, type ReactNode } from 'react' +import { useState, type ReactNode } from 'react' import { useReactTable, getCoreRowModel, @@ -18,7 +18,22 @@ import { DataGridTable } from '@/components/reui/data-grid/data-grid-table' import { DataGridTableVirtual } from '@/components/reui/data-grid/data-grid-table-virtual' import { DataGridScrollArea } from '@/components/reui/data-grid/data-grid-scroll-area' import { DataGridPagination } from '@/components/reui/data-grid/data-grid-pagination' +import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header' import { EmptyState } from './empty-state' +import type { DataTableColumn } from './data-table-card' + +const PAGINATION_LABELS = { + rowsPerPageLabel: 'Строк на странице', + info: '{from}–{to} из {count}', + previousPageLabel: 'Предыдущая страница', + nextPageLabel: 'Следующая страница', +} as const + +function resolveHeaderTitle(header: ReactNode, headerTitle?: string): string { + if (headerTitle) return headerTitle + if (typeof header === 'string') return header + return '' +} export interface DataGridCardProps { title?: ReactNode @@ -32,9 +47,9 @@ export interface DataGridCardProps { emptyDescription?: string emptyAction?: ReactNode onRowClick?: (row: TData) => void - /** Включить пагинацию. По умолчанию true для >25 строк. */ + /** Включить пагинацию. По умолчанию true. */ pagination?: boolean - /** Размер страницы. По умолчанию 25. */ + /** Размер страницы. По умолчанию 10. */ pageSize?: number /** Footer-контент (итоги). */ footerContent?: ReactNode @@ -51,6 +66,14 @@ export interface DataGridCardProps { className?: string } +function DataGridPaginationBar() { + return ( +
+ +
+ ) +} + export function DataGridCard({ title, description, @@ -63,7 +86,7 @@ export function DataGridCard({ emptyAction, onRowClick, pagination, - pageSize = 25, + pageSize = 10, footerContent, dense = false, pinLastColumn = false, @@ -76,16 +99,11 @@ export function DataGridCard({ const lastColId = pinLastColumn ? columns[columns.length - 1]?.id ?? '' : '' - const columnsWithIds = useMemo(() => { - if (rowId) return columns - return columns - }, [columns, rowId]) - - const showPagination = pagination ?? data.length > pageSize + const showPagination = pagination ?? true const table = useReactTable({ data, - columns: columnsWithIds, + columns, state: { sorting }, onSortingChange: setSorting, getCoreRowModel: getCoreRowModel(), @@ -131,8 +149,8 @@ export function DataGridCard({ {actions ?
{actions}
: null} )} - - + + ({ }} > {virtualization ? ( - - - + <> + + + + {showPagination ? : null} + ) : ( <> - {showPagination ? : null} + {showPagination ? : null} )} @@ -170,22 +191,42 @@ export function DataGridCard({ ) } -/** Хелпер для конвертации старых DataTableColumn → ColumnDef. */ +/** Хелпер для конвертации DataTableColumn → ColumnDef с DataGridColumnHeader. */ export function columnDefFromDataTable( - cols: { - key: string - header: ReactNode - cell: (row: T, index: number) => ReactNode - className?: string - headerClassName?: string - }[], + cols: DataTableColumn[], ): ColumnDef[] { - return cols.map((c) => ({ - id: c.key, - header: () => c.header, - cell: ({ row }) => c.cell(row.original, row.index), - meta: { className: c.className, headerClassName: c.headerClassName }, - })) + return cols.map((c) => { + const title = resolveHeaderTitle(c.header, c.headerTitle) + const Icon = c.icon + const sortable = c.sortable ?? Boolean(Icon) + + return { + id: c.key, + ...(sortable + ? { + accessorFn: c.sortValue + ? (row: T) => c.sortValue!(row) + : (row: T) => (row as Record)[c.key] as string | number, + } + : {}), + header: Icon + ? ({ column }) => ( + } + /> + ) + : () => c.header, + cell: ({ row }) => c.cell(row.original, row.index), + enableSorting: sortable, + meta: { + headerTitle: title || undefined, + cellClassName: c.className, + headerClassName: c.headerClassName, + }, + } + }) } /** re-export flexRender для удобства использования в колонках. */ diff --git a/apps/web/src/components/data-grid-cells.tsx b/apps/web/src/components/data-grid-cells.tsx new file mode 100644 index 0000000..c4f7272 --- /dev/null +++ b/apps/web/src/components/data-grid-cells.tsx @@ -0,0 +1,42 @@ +import type { ReactNode } from 'react' + +import { cn } from '@cfdm/ui/lib/utils' + +export function dataGridCellStack( + primary: ReactNode, + secondary?: ReactNode, + className?: string, +) { + return ( +
+ {primary} + {secondary ? {secondary} : null} +
+ ) +} + +export function dataGridCellWithIcon( + icon: ReactNode, + children: ReactNode, + className?: string, +) { + return ( +
+ {icon} + {children} +
+ ) +} + +export function dataGridCellWithFlag( + flag: ReactNode, + primary: ReactNode, + secondary?: ReactNode, +) { + return ( +
+ {flag} + {secondary ? dataGridCellStack(primary, secondary) : {primary}} +
+ ) +} diff --git a/apps/web/src/components/data-table-card.tsx b/apps/web/src/components/data-table-card.tsx index 07e26e4..3a6462b 100644 --- a/apps/web/src/components/data-table-card.tsx +++ b/apps/web/src/components/data-table-card.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from 'react' +import type { LucideIcon } from 'lucide-react' import { Table, TableBody, @@ -14,6 +15,10 @@ export interface DataTableColumn { key: string header: ReactNode cell: (row: T, index: number) => ReactNode + icon?: LucideIcon + sortable?: boolean + sortValue?: (row: T) => string | number + headerTitle?: string className?: string headerClassName?: string }