import React from 'react'; import { IconArrowUp, IconArrowDown, IconEdit, IconTrash, IconCopy, IconCheck, IconX } from '@tabler/icons-react'; import TableSkeleton, { TableEmpty } from './TableSkeleton.jsx'; import EmptyState from './EmptyState.jsx'; import Pagination from './Pagination.jsx'; import Tooltip from './Tooltip.jsx'; /** * Универсальный компонент таблицы данных * * @param {Object} props * @param {Array} props.columns - Массив описаний колонок * - { key: string, title: string, sortable?: boolean, icon?: Component, render?: (value, item) => ReactNode } * @param {Array} props.items - Массив данных для отображения * @param {string} props.itemKey - Ключ уникального идентификатора элемента * @param {boolean} props.loading - Состояние загрузки * @param {string} props.sortField - Текущее поле сортировки * @param {string} props.sortOrder - Направление сортировки ('asc' | 'desc') * @param {function} props.onSort - Обработчик изменения сортировки * @param {Set} props.selectedItems - Набор выбранных элементов * @param {function} props.onSelectItem - Обработчик выбора элемента * @param {function} props.onSelectAll - Обработчик выбора всех * @param {function} props.onDeselectAll - Обработчик снятия выбора * @param {function} props.onEdit - Обработчик редактирования (item) => void * @param {function} props.onDelete - Обработчик удаления (item) => void * @param {function} props.onCopy - Обработчик копирования (item) => void * @param {Object} props.inlineEdit - Настройки инлайн-редактирования * - { editingKey: string, editingValue: string, onSave: () => void, onCancel: () => void, onChange: (value) => void, renderEditor?: () => ReactNode } * @param {Object} props.pagination - Настройки пагинации * - { currentPage, totalPages, totalItems, pageSize, onPageChange } * @param {Object} props.emptyState - Настройки пустого состояния * - { title, description, action, secondaryAction } * @param {Array} props.actions - Дополнительные действия в строке * - [{ icon: Component, label: string, onClick: (item) => void, variant?: string }] * @param {boolean} props.selectable - Включить чекбоксы выбора (default: true) * @param {number} props.skeletonRows - Количество строк скелетона (default: 10) * @param {boolean} props.striped - Чередование фона строк (Tabler UX, default: true) * @param {boolean} props.compact - Уменьшенные отступы ячеек (default: false) * @param {boolean} props.flat - Таблица без карточной подложки: только горизонтальные разделители (default: false) */ function DataTable({ columns = [], items = [], itemKey = 'id', loading = false, sortField, sortOrder = 'asc', onSort, selectedItems = new Set(), onSelectItem, onSelectAll, onDeselectAll, onEdit, onDelete, onCopy, inlineEdit, pagination, emptyState, actions = [], selectable = true, skeletonRows = 10, striped = true, compact = false, flat = false, }) { const hasActions = onEdit || onDelete || onCopy || actions.length > 0; const isEditing = (item) => inlineEdit && item[itemKey] === inlineEdit.editingKey; // Рендер заголовка колонки (col.title — подпись, col.tooltip — подсказка для UX) const renderColumnHeader = (col) => { const isSortable = col.sortable !== false && onSort; const isActive = sortField === col.key; const content = (
{col.icon && } {col.title} {isSortable && isActive && ( {sortOrder === 'asc' ? : } )}
); const thProps = { key: col.key, scope: 'col', style: col.width ? { width: col.width } : undefined, }; if (isSortable) { return ( onSort(col.key)} > {col.tooltip ? {content} : content} ); } return ( {col.tooltip ? {content} : content} ); }; // Рендер ячейки const renderCell = (col, item) => { const value = item[col.key]; if (col.render) { return col.render(value, item); } return value; }; // Рендер действий const renderActions = (item) => { if (isEditing(item)) { return ( <> {inlineEdit.renderEditor && inlineEdit.renderEditor(item)} ); } return ( <> {onEdit && ( )} {onCopy && ( )} {actions.map((action, idx) => ( ))} {onDelete && ( )} ); }; // Загрузка if (loading) { return ; } // Пустое состояние if (items.length === 0) { return ( {emptyState ? ( ) : ( )} ); } const allSelected = items.length > 0 && items.every(i => selectedItems.has(i[itemKey])); const tableClass = [ 'table table-vcenter table-nowrap mb-0', flat ? 'table-flat' : 'card-table', !flat && striped && 'table-striped', compact && 'table-sm', ].filter(Boolean).join(' '); return ( <>
{selectable && ( )} {columns.map(renderColumnHeader)} {hasActions && } {items.map((item, idx) => { const id = item[itemKey]; const isSelected = selectedItems.has(id); const editing = isEditing(item); return ( {selectable && ( )} {columns.map((col) => ( ))} {hasActions && ( )} ); })}
e.target.checked ? onSelectAll?.() : onDeselectAll?.()} title="Выбрать все на странице" /> Действия
onSelectItem?.(id)} onClick={(e) => e.stopPropagation()} /> {renderCell(col, item)} {renderActions(item)}
{pagination && ( )} ); } export default DataTable;