124 lines
3.7 KiB
TypeScript
124 lines
3.7 KiB
TypeScript
import React from 'react';
|
|
import { Skeleton } from './Skeleton';
|
|
import { cn } from '../utils/cn';
|
|
|
|
export interface Column<T> {
|
|
key: keyof T;
|
|
header: string;
|
|
render?: (value: any, row: T, index: number) => React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
export interface TableProps<T> {
|
|
columns: Column<T>[];
|
|
data: T[];
|
|
loading?: boolean;
|
|
emptyMessage?: string;
|
|
rowKey?: keyof T | ((row: T) => string);
|
|
}
|
|
|
|
export function Table<T extends Record<string, any>>({
|
|
columns,
|
|
data,
|
|
loading = false,
|
|
emptyMessage = 'No Results',
|
|
rowKey,
|
|
}: TableProps<T>) {
|
|
const getRowKey = (row: T, index: number): string => {
|
|
if (typeof rowKey === 'function') {
|
|
return rowKey(row);
|
|
}
|
|
if (rowKey) {
|
|
return String(row[rowKey] ?? index);
|
|
}
|
|
return `row-${index}`;
|
|
};
|
|
|
|
// Skeleton table component for loading state
|
|
function SkeletonTable({ rows = 5, columns = 4, className = '' }: { rows?: number; columns?: number; className?: string }) {
|
|
return (
|
|
<div className={cn('bg-white dark:bg-gray-700 rounded-lg overflow-hidden', className)}>
|
|
<table className="min-w-full">
|
|
<thead>
|
|
<tr className="border-b dark:border-gray-600">
|
|
{Array.from({ length: columns }).map((_, i) => (
|
|
<th key={i} className="p-3">
|
|
<Skeleton variant="text" className="w-3/4 h-5" />
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{Array.from({ length: rows }).map((_, rowIndex) => (
|
|
<tr key={rowIndex} className="border-b dark:border-gray-600 last:border-0">
|
|
{Array.from({ length: columns }).map((_, colIndex) => (
|
|
<td key={colIndex} className="p-3">
|
|
<Skeleton variant="text" className={colIndex === columns - 1 ? 'w-1/2' : 'w-full'} />
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<SkeletonTable rows={5} columns={columns.length} />
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-x-auto">
|
|
<div className="inline-block min-w-full overflow-hidden rounded shadow">
|
|
<table className="min-w-full bg-white dark:bg-gray-700">
|
|
<thead>
|
|
<tr className="border-b dark:border-gray-600">
|
|
{columns.map((column) => (
|
|
<th
|
|
key={String(column.key)}
|
|
className={`text-left p-3 text-gray-500 dark:text-white ${column.className || ''}`}
|
|
>
|
|
{column.header}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{data.length === 0 ? (
|
|
<tr>
|
|
<td
|
|
colSpan={columns.length}
|
|
className="text-center p-3 text-gray-700 dark:text-gray-300"
|
|
>
|
|
{emptyMessage}
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
data.map((row, index) => (
|
|
<tr
|
|
key={getRowKey(row, index)}
|
|
className="border-b dark:border-gray-600"
|
|
>
|
|
{columns.map((column) => (
|
|
<td
|
|
key={`${getRowKey(row, index)}-${String(column.key)}`}
|
|
className={`p-3 text-gray-700 dark:text-gray-300 ${column.className || ''}`}
|
|
>
|
|
{column.render
|
|
? column.render(row[column.key], row, index)
|
|
: row[column.key]}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|