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:
110
frontend/src/pages/admin/AdminGames.tsx
Normal file
110
frontend/src/pages/admin/AdminGames.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { AdminTable, type Column } from '../../components/admin/AdminTable'
|
||||
import { GameFormModal } from '../../components/admin/GameFormModal'
|
||||
import { DeleteConfirmModal } from '../../components/admin/DeleteConfirmModal'
|
||||
import { useGames } from '../../hooks/useGames'
|
||||
import { useCreateGame, useUpdateGame, useDeleteGame } from '../../hooks/useAdmin'
|
||||
import type { Game, CreateGameInput, UpdateGameInput } from '../../types'
|
||||
|
||||
export function AdminGames() {
|
||||
const navigate = useNavigate()
|
||||
const { data: games = [], isLoading } = useGames()
|
||||
const createGame = useCreateGame()
|
||||
const updateGame = useUpdateGame()
|
||||
const deleteGame = useDeleteGame()
|
||||
|
||||
const [showCreate, setShowCreate] = useState(false)
|
||||
const [editing, setEditing] = useState<Game | null>(null)
|
||||
const [deleting, setDeleting] = useState<Game | null>(null)
|
||||
|
||||
const columns: Column<Game>[] = [
|
||||
{ header: 'Name', accessor: (g) => g.name },
|
||||
{ header: 'Slug', accessor: (g) => g.slug },
|
||||
{ header: 'Region', accessor: (g) => g.region },
|
||||
{ header: 'Gen', accessor: (g) => g.generation },
|
||||
{ header: 'Year', accessor: (g) => g.releaseYear ?? '-' },
|
||||
{
|
||||
header: 'Actions',
|
||||
accessor: (g) => (
|
||||
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
|
||||
<button
|
||||
onClick={() => setEditing(g)}
|
||||
className="text-blue-600 hover:text-blue-800 dark:text-blue-400 text-sm"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDeleting(g)}
|
||||
className="text-red-600 hover:text-red-800 dark:text-red-400 text-sm"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-semibold">Games</h2>
|
||||
<button
|
||||
onClick={() => setShowCreate(true)}
|
||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||
>
|
||||
Add Game
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<AdminTable
|
||||
columns={columns}
|
||||
data={games}
|
||||
isLoading={isLoading}
|
||||
emptyMessage="No games yet. Add one to get started."
|
||||
onRowClick={(g) => navigate(`/admin/games/${g.id}`)}
|
||||
keyFn={(g) => g.id}
|
||||
/>
|
||||
|
||||
{showCreate && (
|
||||
<GameFormModal
|
||||
onSubmit={(data) =>
|
||||
createGame.mutate(data as CreateGameInput, {
|
||||
onSuccess: () => setShowCreate(false),
|
||||
})
|
||||
}
|
||||
onClose={() => setShowCreate(false)}
|
||||
isSubmitting={createGame.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{editing && (
|
||||
<GameFormModal
|
||||
game={editing}
|
||||
onSubmit={(data) =>
|
||||
updateGame.mutate(
|
||||
{ id: editing.id, data: data as UpdateGameInput },
|
||||
{ onSuccess: () => setEditing(null) },
|
||||
)
|
||||
}
|
||||
onClose={() => setEditing(null)}
|
||||
isSubmitting={updateGame.isPending}
|
||||
/>
|
||||
)}
|
||||
|
||||
{deleting && (
|
||||
<DeleteConfirmModal
|
||||
title={`Delete ${deleting.name}?`}
|
||||
message="This will permanently delete the game and all its routes. Games with existing runs cannot be deleted."
|
||||
onConfirm={() =>
|
||||
deleteGame.mutate(deleting.id, {
|
||||
onSuccess: () => setDeleting(null),
|
||||
})
|
||||
}
|
||||
onCancel={() => setDeleting(null)}
|
||||
isDeleting={deleteGame.isPending}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user