Add section field to boss battles for run progression dividers

Adds a nullable `section` column to boss battles (e.g. "Main Story",
"Endgame") with dividers rendered in the run view between sections.
Admin UI gets a Section column in the table and a text input in the form.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-08 14:55:26 +01:00
parent a01d01c565
commit a4f814e66e
9 changed files with 199 additions and 24 deletions

View File

@@ -459,6 +459,20 @@ export function RunEncounters() {
return nextBoss.levelCap
}, [nextBoss, sortedBosses])
// Pre-compute which bosses get a section divider rendered AFTER them
// (when the next boss in order has a different section)
const sectionDividerAfterBoss = useMemo(() => {
const map = new Map<number, string>()
for (let i = 0; i < sortedBosses.length - 1; i++) {
const current = sortedBosses[i]
const next = sortedBosses[i + 1]
if (next.section != null && current.section !== next.section) {
map.set(current.id, next.section)
}
}
return map
}, [sortedBosses])
// Map afterRouteId → BossBattle[] for interleaving
const bossesAfterRoute = useMemo(() => {
const map = new Map<number, BossBattle[]>()
@@ -1023,6 +1037,7 @@ export function RunEncounters() {
{/* Boss battle cards after this route */}
{bossesHere.map((boss) => {
const isDefeated = defeatedBossIds.has(boss.id)
const sectionAfter = sectionDividerAfterBoss.get(boss.id)
const bossTypeLabel: Record<string, string> = {
gym_leader: 'Gym Leader',
elite_four: 'Elite Four',
@@ -1051,12 +1066,12 @@ export function RunEncounters() {
}
return (
<div
key={`boss-${boss.id}`}
className={`my-2 rounded-lg border-2 ${bossTypeColors[boss.bossType] ?? bossTypeColors.other} ${
isDefeated ? 'bg-green-50/50 dark:bg-green-900/10' : 'bg-white dark:bg-gray-800'
} px-4 py-3`}
>
<div key={`boss-${boss.id}`}>
<div
className={`my-2 rounded-lg border-2 ${bossTypeColors[boss.bossType] ?? bossTypeColors.other} ${
isDefeated ? 'bg-green-50/50 dark:bg-green-900/10' : 'bg-white dark:bg-gray-800'
} px-4 py-3`}
>
<div
className="flex items-start justify-between cursor-pointer select-none"
onClick={toggleBoss}
@@ -1122,6 +1137,14 @@ export function RunEncounters() {
))}
</div>
)}
</div>
{sectionAfter && (
<div className="flex items-center gap-3 my-4">
<div className="flex-1 h-px bg-gray-300 dark:bg-gray-600" />
<span className="text-sm font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">{sectionAfter}</span>
<div className="flex-1 h-px bg-gray-300 dark:bg-gray-600" />
</div>
)}
</div>
)
})}