import type { Row, Table } from '@tanstack/react-table' import { Database } from 'lucide-react' /* Copyright (C) 2023-2026 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import * as React from 'react' import { useTranslation } from 'react-i18next' import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from '@/components/ui/empty' import { Skeleton } from '@/components/ui/skeleton' import { cn } from '@/lib/utils' import { tableHasCompactMeta } from './card-cell-utils' import { CardRowContent } from './card-row-content' /** Helpers passed to a custom {@link DataTableCardGridProps.renderCard}. */ export type DataTableCardHelpers = { /** * Whether the table declares compact card meta (`mobileTitle`/`mobileBadge`). * Provided so custom renderers can match the default layout decision. */ compact: boolean /** * Row selection state captured before entering memoized custom card renderers. */ isSelected: boolean } export interface DataTableCardGridProps { table: Table isLoading?: boolean emptyTitle?: string emptyDescription?: string emptyIcon?: React.ReactNode getRowKey?: (row: Row) => string | number getRowClassName?: (row: Row) => string | undefined /** * Custom card renderer. When omitted, cards render generically from the * column definitions via {@link CardRowContent} (driven by column meta). */ renderCard?: ( row: Row, helpers: DataTableCardHelpers ) => React.ReactNode /** * Responsive grid className override. Defaults to a 1/2/3-column grid. */ gridClassName?: string /** Stable key prefix for skeleton cards. */ skeletonKeyPrefix?: string } const DEFAULT_GRID_CLASSNAME = 'grid grid-cols-1 gap-3 sm:gap-4 md:grid-cols-2 lg:grid-cols-3' function CardGridSkeleton(props: { gridClassName?: string keyPrefix?: string }) { const prefix = props.keyPrefix ?? 'card-skeleton' return (
{[1, 2, 3, 4, 5, 6].map((i) => (
{[1, 2, 3, 4].map((j) => (
))}
))}
) } /** * Desktop card view for table data — a responsive grid of bordered cards. * * Renders the same per-row content as {@link MobileCardList} (via * {@link CardRowContent}) unless a custom `renderCard` is supplied. This keeps * the card view reusable across any table with zero per-feature work while * still allowing a bespoke card design when desired. * * The default generic card omits the `select` column. Custom `renderCard` * implementations can use `helpers.isSelected` to keep selection UI in sync. */ export function DataTableCardGrid(props: DataTableCardGridProps) { const { t } = useTranslation() const resolvedEmptyTitle = props.emptyTitle ?? t('No Data') const resolvedEmptyDescription = props.emptyDescription ?? t('No data available') const visibleColumns = props.table.getVisibleLeafColumns() const compact = React.useMemo( () => tableHasCompactMeta(props.table), // eslint-disable-next-line react-hooks/exhaustive-deps [visibleColumns] ) if (props.isLoading) { return ( ) } const rows = props.table.getRowModel().rows if (!rows || rows.length === 0) { return (
{props.emptyIcon ?? } {resolvedEmptyTitle} {resolvedEmptyDescription}
) } return (
{rows.map((row) => { const key = props.getRowKey ? props.getRowKey(row) : row.id const isSelected = row.getIsSelected() return (
{props.renderCard ? ( props.renderCard(row, { compact, isSelected }) ) : ( )}
) })}
) }