Add prev/next and dropdown route navigation to route detail page
Reduces navigation depth when working with route encounters by adding prev/next buttons and a route dropdown selector to AdminRouteDetail. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
# nuzlocke-tracker-ljle
|
# nuzlocke-tracker-ljle
|
||||||
title: Route navigation within game detail
|
title: Route navigation within game detail
|
||||||
status: todo
|
status: completed
|
||||||
type: feature
|
type: feature
|
||||||
|
priority: normal
|
||||||
created_at: 2026-02-08T12:33:26Z
|
created_at: 2026-02-08T12:33:26Z
|
||||||
updated_at: 2026-02-08T12:33:26Z
|
updated_at: 2026-02-08T19:00:07Z
|
||||||
parent: nuzlocke-tracker-iu5b
|
parent: nuzlocke-tracker-iu5b
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { useParams, Link } from 'react-router-dom'
|
import { useParams, Link, useNavigate } from 'react-router-dom'
|
||||||
import { AdminTable, type Column } from '../../components/admin/AdminTable'
|
import { AdminTable, type Column } from '../../components/admin/AdminTable'
|
||||||
import { RouteEncounterFormModal } from '../../components/admin/RouteEncounterFormModal'
|
import { RouteEncounterFormModal } from '../../components/admin/RouteEncounterFormModal'
|
||||||
import { useGame, useRoutePokemon } from '../../hooks/useGames'
|
import { useGame, useRoutePokemon } from '../../hooks/useGames'
|
||||||
@@ -18,6 +18,7 @@ export function AdminRouteDetail() {
|
|||||||
const { gameId, routeId } = useParams<{ gameId: string; routeId: string }>()
|
const { gameId, routeId } = useParams<{ gameId: string; routeId: string }>()
|
||||||
const gId = Number(gameId)
|
const gId = Number(gameId)
|
||||||
const rId = Number(routeId)
|
const rId = Number(routeId)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { data: game } = useGame(gId)
|
const { data: game } = useGame(gId)
|
||||||
const { data: encounters = [], isLoading } = useRoutePokemon(rId, gId)
|
const { data: encounters = [], isLoading } = useRoutePokemon(rId, gId)
|
||||||
@@ -29,7 +30,17 @@ export function AdminRouteDetail() {
|
|||||||
const [showCreate, setShowCreate] = useState(false)
|
const [showCreate, setShowCreate] = useState(false)
|
||||||
const [editing, setEditing] = useState<RouteEncounterDetail | null>(null)
|
const [editing, setEditing] = useState<RouteEncounterDetail | null>(null)
|
||||||
|
|
||||||
const route = game?.routes?.find((r) => r.id === rId)
|
const sortedRoutes = useMemo(
|
||||||
|
() => [...(game?.routes ?? [])].sort((a, b) => a.order - b.order),
|
||||||
|
[game?.routes],
|
||||||
|
)
|
||||||
|
const currentIndex = sortedRoutes.findIndex((r) => r.id === rId)
|
||||||
|
const route = currentIndex >= 0 ? sortedRoutes[currentIndex] : undefined
|
||||||
|
const prevRoute = currentIndex > 0 ? sortedRoutes[currentIndex - 1] : undefined
|
||||||
|
const nextRoute =
|
||||||
|
currentIndex >= 0 && currentIndex < sortedRoutes.length - 1
|
||||||
|
? sortedRoutes[currentIndex + 1]
|
||||||
|
: undefined
|
||||||
|
|
||||||
const columns: Column<RouteEncounterDetail>[] = [
|
const columns: Column<RouteEncounterDetail>[] = [
|
||||||
{
|
{
|
||||||
@@ -65,15 +76,53 @@ export function AdminRouteDetail() {
|
|||||||
{game?.name ?? '...'}
|
{game?.name ?? '...'}
|
||||||
</Link>
|
</Link>
|
||||||
{' / '}
|
{' / '}
|
||||||
<span className="text-gray-900 dark:text-gray-100">
|
<select
|
||||||
{route?.name ?? '...'}
|
className="text-gray-900 dark:text-gray-100 bg-transparent font-medium cursor-pointer hover:underline border-none p-0 text-sm"
|
||||||
</span>
|
value={rId}
|
||||||
|
onChange={(e) => navigate(`/admin/games/${gId}/routes/${e.target.value}`)}
|
||||||
|
>
|
||||||
|
{sortedRoutes.map((r) => (
|
||||||
|
<option key={r.id} value={r.id}>
|
||||||
|
{r.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<h2 className="text-xl font-semibold">
|
<h2 className="text-xl font-semibold">
|
||||||
{route?.name ?? 'Route'} - Pokemon ({encounters.length})
|
{route?.name ?? 'Route'} - Pokemon ({encounters.length})
|
||||||
</h2>
|
</h2>
|
||||||
|
<div className="flex items-center gap-1 ml-2">
|
||||||
|
{prevRoute ? (
|
||||||
|
<Link
|
||||||
|
to={`/admin/games/${gId}/routes/${prevRoute.id}`}
|
||||||
|
className="px-2 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||||
|
title={prevRoute.name}
|
||||||
|
>
|
||||||
|
← Prev
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="px-2 py-1 text-sm rounded border border-gray-200 dark:border-gray-700 text-gray-300 dark:text-gray-600 cursor-not-allowed">
|
||||||
|
← Prev
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{nextRoute ? (
|
||||||
|
<Link
|
||||||
|
to={`/admin/games/${gId}/routes/${nextRoute.id}`}
|
||||||
|
className="px-2 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||||
|
title={nextRoute.name}
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="px-2 py-1 text-sm rounded border border-gray-200 dark:border-gray-700 text-gray-300 dark:text-gray-600 cursor-not-allowed">
|
||||||
|
Next →
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowCreate(true)}
|
onClick={() => setShowCreate(true)}
|
||||||
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
className="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700"
|
||||||
|
|||||||
Reference in New Issue
Block a user