Add admin panel with CRUD endpoints and management UI
Add admin API endpoints for games, routes, pokemon, and route encounters with full CRUD operations including bulk import. Build admin frontend with game/route/pokemon management pages, navigation, and data tables. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
78
frontend/src/components/admin/AdminTable.tsx
Normal file
78
frontend/src/components/admin/AdminTable.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { type ReactNode } from 'react'
|
||||
|
||||
export interface Column<T> {
|
||||
header: string
|
||||
accessor: (row: T) => ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface AdminTableProps<T> {
|
||||
columns: Column<T>[]
|
||||
data: T[]
|
||||
isLoading?: boolean
|
||||
emptyMessage?: string
|
||||
onRowClick?: (row: T) => void
|
||||
keyFn: (row: T) => string | number
|
||||
}
|
||||
|
||||
export function AdminTable<T>({
|
||||
columns,
|
||||
data,
|
||||
isLoading,
|
||||
emptyMessage = 'No data found.',
|
||||
onRowClick,
|
||||
keyFn,
|
||||
}: AdminTableProps<T>) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
Loading...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
|
||||
{emptyMessage}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||
<tr>
|
||||
{columns.map((col) => (
|
||||
<th
|
||||
key={col.header}
|
||||
className={`px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider ${col.className ?? ''}`}
|
||||
>
|
||||
{col.header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700">
|
||||
{data.map((row) => (
|
||||
<tr
|
||||
key={keyFn(row)}
|
||||
onClick={onRowClick ? () => onRowClick(row) : undefined}
|
||||
className={onRowClick ? 'cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800' : ''}
|
||||
>
|
||||
{columns.map((col) => (
|
||||
<td
|
||||
key={col.header}
|
||||
className={`px-4 py-3 text-sm whitespace-nowrap ${col.className ?? ''}`}
|
||||
>
|
||||
{col.accessor(row)}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user