Add conditional boss battle teams (variant teams by condition)
Wire up the existing condition_label column on boss_pokemon to support variant teams throughout the UI. Boss battles can now have multiple team configurations based on conditions (e.g., starter choice in Gen 1). - Add condition_label to BossPokemonInput schema (frontend + backend bulk import) - Rewrite BossTeamEditor with variant tabs (Default + named conditions) - Add variant pill selector to BossDefeatModal team preview - Add BossTeamPreview component to RunEncounters boss cards - Fix MissingGreenlet error in set_boss_team via session.expunge_all() - Fix PokemonSelector state bleed between tabs via composite React key - Add Alembic migration for condition_label column Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import type {
|
||||
EncounterStatus,
|
||||
CreateEncounterInput,
|
||||
BossBattle,
|
||||
BossPokemon,
|
||||
} from '../types'
|
||||
|
||||
const statusStyles: Record<RunStatus, string> = {
|
||||
@@ -145,6 +146,67 @@ function countDistinctZones(group: RouteWithChildren): number {
|
||||
return zones.size
|
||||
}
|
||||
|
||||
function BossTeamPreview({ pokemon }: { pokemon: BossPokemon[] }) {
|
||||
const variantLabels = useMemo(() => {
|
||||
const labels = new Set<string>()
|
||||
for (const bp of pokemon) {
|
||||
if (bp.conditionLabel) labels.add(bp.conditionLabel)
|
||||
}
|
||||
return [...labels].sort()
|
||||
}, [pokemon])
|
||||
|
||||
const hasVariants = variantLabels.length > 0
|
||||
const [selectedVariant, setSelectedVariant] = useState<string | null>(
|
||||
hasVariants ? variantLabels[0] : null,
|
||||
)
|
||||
|
||||
const displayed = useMemo(() => {
|
||||
if (!hasVariants) return pokemon
|
||||
return pokemon.filter(
|
||||
(bp) => bp.conditionLabel === selectedVariant || bp.conditionLabel === null,
|
||||
)
|
||||
}, [pokemon, hasVariants, selectedVariant])
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
{hasVariants && (
|
||||
<div className="flex gap-1 mb-2 flex-wrap">
|
||||
{variantLabels.map((label) => (
|
||||
<button
|
||||
key={label}
|
||||
type="button"
|
||||
onClick={() => setSelectedVariant(label)}
|
||||
className={`px-2 py-0.5 text-xs font-medium rounded-full transition-colors ${
|
||||
selectedVariant === label
|
||||
? 'bg-blue-600 text-white'
|
||||
: 'bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{[...displayed]
|
||||
.sort((a, b) => a.order - b.order)
|
||||
.map((bp) => (
|
||||
<div key={bp.id} className="flex items-center gap-1">
|
||||
{bp.pokemon.spriteUrl ? (
|
||||
<img src={bp.pokemon.spriteUrl} alt={bp.pokemon.name} className="w-20 h-20" />
|
||||
) : (
|
||||
<div className="w-20 h-20 bg-gray-200 dark:bg-gray-700 rounded-full" />
|
||||
)}
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Lvl {bp.level}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface RouteGroupProps {
|
||||
group: RouteWithChildren
|
||||
encounterByRoute: Map<number, EncounterDetail>
|
||||
@@ -1124,22 +1186,7 @@ export function RunEncounters() {
|
||||
</div>
|
||||
{/* Boss pokemon team */}
|
||||
{isBossExpanded && boss.pokemon.length > 0 && (
|
||||
<div className="flex gap-2 mt-2 flex-wrap">
|
||||
{boss.pokemon
|
||||
.sort((a, b) => a.order - b.order)
|
||||
.map((bp) => (
|
||||
<div key={bp.id} className="flex items-center gap-1">
|
||||
{bp.pokemon.spriteUrl ? (
|
||||
<img src={bp.pokemon.spriteUrl} alt={bp.pokemon.name} className="w-20 h-20" />
|
||||
) : (
|
||||
<div className="w-20 h-20 bg-gray-200 dark:bg-gray-700 rounded-full" />
|
||||
)}
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
Lvl {bp.level}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<BossTeamPreview pokemon={boss.pokemon} />
|
||||
)}
|
||||
</div>
|
||||
{sectionAfter && (
|
||||
|
||||
Reference in New Issue
Block a user