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:
83
frontend/src/components/admin/PokemonFormModal.tsx
Normal file
83
frontend/src/components/admin/PokemonFormModal.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { type FormEvent, useState } from 'react'
|
||||
import { FormModal } from './FormModal'
|
||||
import type { Pokemon, CreatePokemonInput, UpdatePokemonInput } from '../../types'
|
||||
|
||||
interface PokemonFormModalProps {
|
||||
pokemon?: Pokemon
|
||||
onSubmit: (data: CreatePokemonInput | UpdatePokemonInput) => void
|
||||
onClose: () => void
|
||||
isSubmitting?: boolean
|
||||
}
|
||||
|
||||
export function PokemonFormModal({ pokemon, onSubmit, onClose, isSubmitting }: PokemonFormModalProps) {
|
||||
const [nationalDex, setNationalDex] = useState(String(pokemon?.nationalDex ?? ''))
|
||||
const [name, setName] = useState(pokemon?.name ?? '')
|
||||
const [types, setTypes] = useState(pokemon?.types.join(', ') ?? '')
|
||||
const [spriteUrl, setSpriteUrl] = useState(pokemon?.spriteUrl ?? '')
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
const typesList = types
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean)
|
||||
onSubmit({
|
||||
nationalDex: Number(nationalDex),
|
||||
name,
|
||||
types: typesList,
|
||||
spriteUrl: spriteUrl || null,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<FormModal
|
||||
title={pokemon ? 'Edit Pokemon' : 'Add Pokemon'}
|
||||
onClose={onClose}
|
||||
onSubmit={handleSubmit}
|
||||
isSubmitting={isSubmitting}
|
||||
>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">National Dex #</label>
|
||||
<input
|
||||
type="number"
|
||||
required
|
||||
min={1}
|
||||
value={nationalDex}
|
||||
onChange={(e) => setNationalDex(e.target.value)}
|
||||
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Types (comma-separated)</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
value={types}
|
||||
onChange={(e) => setTypes(e.target.value)}
|
||||
placeholder="Fire, Flying"
|
||||
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Sprite URL</label>
|
||||
<input
|
||||
type="text"
|
||||
value={spriteUrl}
|
||||
onChange={(e) => setSpriteUrl(e.target.value)}
|
||||
placeholder="Optional"
|
||||
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
|
||||
/>
|
||||
</div>
|
||||
</FormModal>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user