feat: make level field optional in boss defeat modal
Remove the level input from the boss defeat modal since the app doesn't track levels elsewhere. Team selection is now just checkboxes without requiring level entry. - Remove level input UI from BossDefeatModal.tsx - Add alembic migration to make boss_result_team.level nullable - Update model and schemas to make level optional (defaults to null) - Conditionally render level in boss result display Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,10 +23,7 @@ function matchVariant(labels: string[], starterName?: string | null): string | n
|
||||
return matches.length === 1 ? (matches[0] ?? null) : null
|
||||
}
|
||||
|
||||
interface TeamSelection {
|
||||
encounterId: number
|
||||
level: number
|
||||
}
|
||||
type TeamSelection = number
|
||||
|
||||
export function BossDefeatModal({
|
||||
boss,
|
||||
@@ -36,26 +33,15 @@ export function BossDefeatModal({
|
||||
isPending,
|
||||
starterName,
|
||||
}: BossDefeatModalProps) {
|
||||
const [selectedTeam, setSelectedTeam] = useState<Map<number, TeamSelection>>(new Map())
|
||||
const [selectedTeam, setSelectedTeam] = useState<Set<TeamSelection>>(new Set())
|
||||
|
||||
const toggleTeamMember = (enc: EncounterDetail) => {
|
||||
const toggleTeamMember = (encounterId: number) => {
|
||||
setSelectedTeam((prev) => {
|
||||
const next = new Map(prev)
|
||||
if (next.has(enc.id)) {
|
||||
next.delete(enc.id)
|
||||
const next = new Set(prev)
|
||||
if (next.has(encounterId)) {
|
||||
next.delete(encounterId)
|
||||
} else {
|
||||
next.set(enc.id, { encounterId: enc.id, level: enc.catchLevel ?? 1 })
|
||||
}
|
||||
return next
|
||||
})
|
||||
}
|
||||
|
||||
const updateLevel = (encounterId: number, level: number) => {
|
||||
setSelectedTeam((prev) => {
|
||||
const next = new Map(prev)
|
||||
const existing = next.get(encounterId)
|
||||
if (existing) {
|
||||
next.set(encounterId, { ...existing, level })
|
||||
next.add(encounterId)
|
||||
}
|
||||
return next
|
||||
})
|
||||
@@ -87,7 +73,9 @@ export function BossDefeatModal({
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
const team: BossResultTeamMemberInput[] = Array.from(selectedTeam.values())
|
||||
const team: BossResultTeamMemberInput[] = Array.from(selectedTeam).map((encounterId) => ({
|
||||
encounterId,
|
||||
}))
|
||||
onSubmit({
|
||||
bossBattleId: boss.id,
|
||||
result: 'won',
|
||||
@@ -134,11 +122,17 @@ export function BossDefeatModal({
|
||||
return (
|
||||
<div key={bp.id} className="flex flex-col items-center">
|
||||
{bp.pokemon.spriteUrl ? (
|
||||
<img src={bp.pokemon.spriteUrl} alt={bp.pokemon.name} className="w-10 h-10" />
|
||||
<img
|
||||
src={bp.pokemon.spriteUrl}
|
||||
alt={bp.pokemon.name}
|
||||
className="w-10 h-10"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-10 h-10 bg-surface-3 rounded-full" />
|
||||
)}
|
||||
<span className="text-xs text-text-tertiary capitalize">{bp.pokemon.name}</span>
|
||||
<span className="text-xs text-text-tertiary capitalize">
|
||||
{bp.pokemon.name}
|
||||
</span>
|
||||
<span className="text-xs font-medium text-text-secondary">Lv.{bp.level}</span>
|
||||
<ConditionBadge condition={bp.conditionLabel} size="xs" />
|
||||
{bp.ability && (
|
||||
@@ -166,7 +160,6 @@ export function BossDefeatModal({
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-48 overflow-y-auto">
|
||||
{aliveEncounters.map((enc) => {
|
||||
const isSelected = selectedTeam.has(enc.id)
|
||||
const selection = selectedTeam.get(enc.id)
|
||||
const displayPokemon = enc.currentPokemon ?? enc.pokemon
|
||||
return (
|
||||
<div
|
||||
@@ -176,12 +169,12 @@ export function BossDefeatModal({
|
||||
? 'border-accent-500 bg-accent-500/10'
|
||||
: 'border-border-default hover:bg-surface-2'
|
||||
}`}
|
||||
onClick={() => toggleTeamMember(enc)}
|
||||
onClick={() => toggleTeamMember(enc.id)}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isSelected}
|
||||
onChange={() => toggleTeamMember(enc)}
|
||||
onChange={() => toggleTeamMember(enc.id)}
|
||||
className="sr-only"
|
||||
/>
|
||||
{displayPokemon.spriteUrl ? (
|
||||
@@ -193,26 +186,9 @@ export function BossDefeatModal({
|
||||
) : (
|
||||
<div className="w-8 h-8 bg-surface-3 rounded-full" />
|
||||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-xs font-medium truncate">
|
||||
{enc.nickname ?? displayPokemon.name}
|
||||
</p>
|
||||
{isSelected && (
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={100}
|
||||
value={selection?.level ?? enc.catchLevel ?? 1}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation()
|
||||
updateLevel(enc.id, Number.parseInt(e.target.value, 10) || 1)
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="w-14 text-xs px-1 py-0.5 mt-1 rounded border border-border-default bg-surface-1"
|
||||
placeholder="Lv"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="flex-1 min-w-0 text-xs font-medium truncate">
|
||||
{enc.nickname ?? displayPokemon.name}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user