forked from syntaxbullet/aurorabot
Adds a React admin panel (panel/) with Discord OAuth2 login, live dashboard via WebSocket, and settings/management pages. Includes Docker build support, Vite proxy config for dev, game_settings migration, and open-redirect protection on auth callback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
74 lines
1.8 KiB
TypeScript
74 lines
1.8 KiB
TypeScript
import type { ReactNode } from "react";
|
|
|
|
export interface Column<T> {
|
|
key: string;
|
|
header: string;
|
|
render?: (row: T) => ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
interface DataTableProps<T> {
|
|
columns: Column<T>[];
|
|
data: T[];
|
|
keyField: string;
|
|
loading?: boolean;
|
|
onRowClick?: (row: T) => void;
|
|
emptyMessage?: string;
|
|
}
|
|
|
|
export default function DataTable<T extends Record<string, unknown>>({
|
|
columns,
|
|
data,
|
|
keyField,
|
|
loading,
|
|
onRowClick,
|
|
emptyMessage = "No data found",
|
|
}: DataTableProps<T>) {
|
|
if (loading) {
|
|
return (
|
|
<div className="flex justify-center p-8">
|
|
<span className="loading loading-spinner loading-lg" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="overflow-x-auto">
|
|
<table className="table table-zebra w-full">
|
|
<thead>
|
|
<tr>
|
|
{columns.map((col) => (
|
|
<th key={col.key} className={col.className}>
|
|
{col.header}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{data.length === 0 ? (
|
|
<tr>
|
|
<td colSpan={columns.length} className="text-center py-8 text-base-content/50">
|
|
{emptyMessage}
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
data.map((row) => (
|
|
<tr
|
|
key={String(row[keyField])}
|
|
className={onRowClick ? "cursor-pointer hover" : ""}
|
|
onClick={() => onRowClick?.(row)}
|
|
>
|
|
{columns.map((col) => (
|
|
<td key={col.key} className={col.className}>
|
|
{col.render ? col.render(row) : String(row[col.key] ?? "")}
|
|
</td>
|
|
))}
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
);
|
|
}
|