develop #21
@@ -1,11 +1,11 @@
|
||||
---
|
||||
# nuzlocke-tracker-36wg
|
||||
title: Make footer stick to bottom of viewport
|
||||
status: in-progress
|
||||
status: completed
|
||||
type: bug
|
||||
priority: normal
|
||||
created_at: 2026-02-13T07:47:48Z
|
||||
updated_at: 2026-02-13T12:51:48Z
|
||||
updated_at: 2026-02-13T12:59:22Z
|
||||
---
|
||||
|
||||
On pages with little content, the footer appears right after the content instead of staying at the bottom of the viewport. The footer should always be at the bottom of the browser window, pushing down when there's enough content but not floating in the middle of the page when content is short (sticky footer pattern).
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
# nuzlocke-tracker-6r4z
|
||||
title: 'Admin table improvements: Routes and Bosses'
|
||||
status: completed
|
||||
type: task
|
||||
priority: normal
|
||||
created_at: 2026-02-13T13:01:55Z
|
||||
updated_at: 2026-02-13T13:06:08Z
|
||||
---
|
||||
|
||||
1. Routes table: add column for 'pinwheel close' and a column for quicklink to encounters
|
||||
2. Bosses table: add column for Position after route column, ideally as an inline dropdown for mass editing
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"name": "nuzlocke-tracker-frontend",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "frontend",
|
||||
"name": "nuzlocke-tracker-frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
|
||||
@@ -43,9 +43,11 @@ import type { CreateBossBattleInput, UpdateBossBattleInput } from '../../types/a
|
||||
|
||||
function SortableRouteRow({
|
||||
route,
|
||||
gameId,
|
||||
onClick,
|
||||
}: {
|
||||
route: GameRoute
|
||||
gameId: number
|
||||
onClick: (r: GameRoute) => void
|
||||
}) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
|
||||
@@ -83,15 +85,31 @@ function SortableRouteRow({
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap w-16">{route.order}</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">{route.name}</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap text-center">
|
||||
{route.pinwheelZone != null ? route.pinwheelZone : '\u2014'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">
|
||||
<Link
|
||||
to={`/admin/games/${gameId}/routes/${route.id}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="text-blue-600 dark:text-blue-400 hover:underline"
|
||||
>
|
||||
Encounters
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
function SortableBossRow({
|
||||
boss,
|
||||
routes,
|
||||
onPositionChange,
|
||||
onClick,
|
||||
}: {
|
||||
boss: BossBattle
|
||||
routes: GameRoute[]
|
||||
onPositionChange: (bossId: number, afterRouteId: number | null) => void
|
||||
onClick: (b: BossBattle) => void
|
||||
}) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } =
|
||||
@@ -137,6 +155,24 @@ function SortableBossRow({
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">{boss.section ?? '\u2014'}</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">{boss.location}</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">
|
||||
<select
|
||||
value={boss.afterRouteId ?? ''}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value === '' ? null : Number(e.target.value)
|
||||
onPositionChange(boss.id, value)
|
||||
}}
|
||||
className="text-sm border border-gray-300 dark:border-gray-600 rounded px-1.5 py-0.5 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 max-w-[180px]"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{routes.map((r) => (
|
||||
<option key={r.id} value={r.id}>
|
||||
{r.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">{boss.levelCap}</td>
|
||||
<td className="px-4 py-3 text-sm whitespace-nowrap">{boss.pokemon.length}</td>
|
||||
</tr>
|
||||
@@ -314,6 +350,12 @@ export function AdminGameDetail() {
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Name
|
||||
</th>
|
||||
<th className="px-4 py-3 text-center text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-24">
|
||||
Pinwheel
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-28">
|
||||
Links
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<DndContext
|
||||
@@ -330,6 +372,7 @@ export function AdminGameDetail() {
|
||||
<SortableRouteRow
|
||||
key={route.id}
|
||||
route={route}
|
||||
gameId={id}
|
||||
onClick={(r) => setEditing(r)}
|
||||
/>
|
||||
))}
|
||||
@@ -443,6 +486,9 @@ export function AdminGameDetail() {
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Location
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||
Position
|
||||
</th>
|
||||
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider w-20">
|
||||
Lv Cap
|
||||
</th>
|
||||
@@ -465,6 +511,13 @@ export function AdminGameDetail() {
|
||||
<SortableBossRow
|
||||
key={boss.id}
|
||||
boss={boss}
|
||||
routes={routes}
|
||||
onPositionChange={(bossId, afterRouteId) =>
|
||||
updateBoss.mutate({
|
||||
bossId,
|
||||
data: { afterRouteId } as UpdateBossBattleInput,
|
||||
})
|
||||
}
|
||||
onClick={(b) => setEditingBoss(b)}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user