Files
nuzlocke-tracker/frontend/src/components/admin/GameFormModal.tsx
Julian Tabel f09b8213fd Add click-to-edit pattern across all admin tables
Replace Actions columns with clickable rows that open edit modals
directly. Delete is now an inline two-step confirm button in the
edit modal footer. Games modal links to routes/bosses detail,
route modal links to encounters, and boss modal has an Edit Team button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 13:44:38 +01:00

134 lines
4.2 KiB
TypeScript

import { type FormEvent, useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { FormModal } from './FormModal'
import type { Game, CreateGameInput, UpdateGameInput } from '../../types'
interface GameFormModalProps {
game?: Game
onSubmit: (data: CreateGameInput | UpdateGameInput) => void
onClose: () => void
isSubmitting?: boolean
onDelete?: () => void
isDeleting?: boolean
detailUrl?: string
}
function slugify(name: string) {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
export function GameFormModal({ game, onSubmit, onClose, isSubmitting, onDelete, isDeleting, detailUrl }: GameFormModalProps) {
const [name, setName] = useState(game?.name ?? '')
const [slug, setSlug] = useState(game?.slug ?? '')
const [generation, setGeneration] = useState(String(game?.generation ?? ''))
const [region, setRegion] = useState(game?.region ?? '')
const [boxArtUrl, setBoxArtUrl] = useState(game?.boxArtUrl ?? '')
const [releaseYear, setReleaseYear] = useState(game?.releaseYear ? String(game.releaseYear) : '')
const [autoSlug, setAutoSlug] = useState(!game)
useEffect(() => {
if (autoSlug) setSlug(slugify(name))
}, [name, autoSlug])
const handleSubmit = (e: FormEvent) => {
e.preventDefault()
onSubmit({
name,
slug,
generation: Number(generation),
region,
boxArtUrl: boxArtUrl || null,
releaseYear: releaseYear ? Number(releaseYear) : null,
})
}
return (
<FormModal
title={game ? 'Edit Game' : 'Add Game'}
onClose={onClose}
onSubmit={handleSubmit}
isSubmitting={isSubmitting}
onDelete={onDelete}
isDeleting={isDeleting}
headerExtra={detailUrl ? (
<Link
to={detailUrl}
className="text-sm text-blue-600 dark:text-blue-400 hover:underline"
>
View Routes & Bosses
</Link>
) : undefined}
>
<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">Slug</label>
<input
type="text"
required
value={slug}
onChange={(e) => {
setSlug(e.target.value)
setAutoSlug(false)
}}
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">Generation</label>
<input
type="number"
required
min={1}
value={generation}
onChange={(e) => setGeneration(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">Region</label>
<input
type="text"
required
value={region}
onChange={(e) => setRegion(e.target.value)}
className="w-full px-3 py-2 border rounded-md dark:bg-gray-700 dark:border-gray-600"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium mb-1">Box Art URL</label>
<input
type="text"
value={boxArtUrl}
onChange={(e) => setBoxArtUrl(e.target.value)}
placeholder="Optional"
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">Release Year</label>
<input
type="number"
value={releaseYear}
onChange={(e) => setReleaseYear(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>
)
}