'use client'; import * as React from 'react'; import { ArrowDownIcon, ArrowUpIcon, ChevronDownIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; import { useIsDesktop } from '@/hooks/use-media-query'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@/components/ui/table'; import { Card } from '@/components/ui/card'; import { SkeletonTableRow, SkeletonCard } from '@/components/ui/skeleton'; import { EmptyState } from '@/components/ui/empty-state'; import { DataPagination } from '@/components/ui/data-pagination'; export interface DataTableSort { columnId: string; direction: 'asc' | 'desc'; } export interface DataTableColumn { id: string; header: string; cell: (row: T) => React.ReactNode; sortable?: boolean; /** Campo em destaque no card mobile (limite recomendado: 1–2 por tabela). */ primary?: boolean; /** Não mostrado no card mobile (ex: coluna técnica só relevante em tela larga). */ hideOnMobileCard?: boolean; className?: string; } interface DataTableProps { columns: DataTableColumn[]; data: T[]; keyField: (row: T) => string; loading?: boolean; sort?: DataTableSort; onSortChange?: (sort: DataTableSort) => void; emptyTitle?: string; emptyDescription?: string; emptyAction?: React.ReactNode; rowActions?: (row: T) => React.ReactNode; onRowClick?: (row: T) => void; pagination?: { page: number; limit: number; total: number; onPageChange: (page: number) => void; }; className?: string; } function SortButton({ column, sort, onSortChange, }: { column: DataTableColumn; sort?: DataTableSort; onSortChange: (sort: DataTableSort) => void; }) { const active = sort?.columnId === column.id; const nextDirection = active && sort?.direction === 'asc' ? 'desc' : 'asc'; return ( ); } function MobileRowCard({ row, columns, rowActions, onRowClick, }: { row: T; columns: DataTableColumn[]; rowActions?: (row: T) => React.ReactNode; onRowClick?: (row: T) => void; }) { const [expanded, setExpanded] = React.useState(false); const primaryColumns = columns.filter((c) => c.primary); const secondaryColumns = columns.filter((c) => !c.primary && !c.hideOnMobileCard); return ( onRowClick(row) : undefined} role={onRowClick ? 'button' : undefined} tabIndex={onRowClick ? 0 : undefined} onKeyDown={ onRowClick ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onRowClick(row); } } : undefined } > {primaryColumns.length > 0 && (
{primaryColumns.map((column) => (
{column.cell(row)}
))}
)} {secondaryColumns.length > 0 && ( <> {expanded && (
{secondaryColumns.map((column) => (
{column.header}
{column.cell(row)}
))}
)} )} {rowActions && (
e.stopPropagation()} > {rowActions(row)}
)}
); } /** * Componente central do design system (Etapa 12). Abaixo de `md` renderiza * como lista de Cards empilhados em vez de fazer scroll horizontal na * tabela — cada `column.primary` vira um campo em destaque no card, o * resto fica atrás de "Ver mais". */ function DataTable({ columns, data, keyField, loading, sort, onSortChange, emptyTitle = 'Nenhum resultado encontrado', emptyDescription, emptyAction, rowActions, onRowClick, pagination, className, }: DataTableProps) { const isDesktop = useIsDesktop(); if (loading) { return (
{isDesktop ? (
{Array.from({ length: 5 }).map((_, i) => ( ))}
) : (
{Array.from({ length: 3 }).map((_, i) => ( ))}
)}
); } if (data.length === 0) { return (
); } return (
{isDesktop ? ( // `overflow-x-auto` (não `overflow-hidden`): com muitas colunas perto // do breakpoint `md`, a tabela pode ficar mais larga que o // container — sem scroll horizontal, a(s) última(s) coluna(s) // simplesmente sumiam cortadas em vez de ficarem acessíveis // (achado da Etapa 15, testando `/master/tenants` em 768px).
{columns.map((column) => ( {column.sortable && onSortChange ? ( ) : ( column.header )} ))} {rowActions && ( Ações )} {data.map((row) => ( onRowClick(row) : undefined} tabIndex={onRowClick ? 0 : undefined} onKeyDown={ onRowClick ? (e) => { if (e.key === 'Enter') onRowClick(row); } : undefined } > {columns.map((column) => ( {column.cell(row)} ))} {rowActions && ( e.stopPropagation()}>
{rowActions(row)}
)}
))}
) : (
{data.map((row) => ( ))}
)} {pagination && }
); } export { DataTable };