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>
134 lines
4.2 KiB
TypeScript
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>
|
|
)
|
|
}
|