- Rewrite DefensiveSetup as inline segmented controls (~120px vs ~340px) - Fix bug: read game state from useGameStore() instead of never-passed prop - Remove turn indicator banner from DecisionPanel (replaced by green glow ring) - Emoji fix: shield -> baseball glove - Confirm button matches Roll Dice style (full-width, prominent) - Infield options only show IF In/Corners when runner on 3rd - Outfield row only renders in walk-off scenarios - Hold pills only render for occupied bases - 20 tests rewritten with Pinia store integration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
175 lines
5.0 KiB
Vue
175 lines
5.0 KiB
Vue
<template>
|
|
<div class="space-y-4">
|
|
<!-- Decision Phase Content -->
|
|
<div v-if="phase !== 'idle'" class="space-y-4">
|
|
<!-- Defensive Phase -->
|
|
<template v-if="phase === 'defensive'">
|
|
<DefensiveSetup
|
|
:game-id="gameId"
|
|
:is-active="isMyTurn"
|
|
:current-setup="currentDefensiveSetup"
|
|
@submit="handleDefensiveSubmit"
|
|
/>
|
|
</template>
|
|
|
|
<!-- Offensive Phase -->
|
|
<template v-if="phase === 'offensive'">
|
|
<!-- Offensive Approach -->
|
|
<OffensiveApproach
|
|
:game-id="gameId"
|
|
:is-active="isMyTurn"
|
|
:current-decision="currentOffensiveDecision"
|
|
:has-runners-on-base="hasRunnersOnBase"
|
|
@submit="handleOffensiveSubmit"
|
|
/>
|
|
|
|
<!-- Stolen Base Attempts (if runners on base) -->
|
|
<StolenBaseInputs
|
|
v-if="hasRunnersOnBase"
|
|
:runners="runners"
|
|
:is-active="isMyTurn"
|
|
:current-attempts="currentStealAttempts"
|
|
@submit="handleStealAttemptsSubmit"
|
|
@cancel="handleStealAttemptsCancel"
|
|
/>
|
|
</template>
|
|
|
|
<!-- Decision History (Collapsible) -->
|
|
<div
|
|
v-if="decisionHistory.length > 0"
|
|
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg overflow-hidden"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="w-full px-6 py-3 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors"
|
|
@click="historyExpanded = !historyExpanded"
|
|
>
|
|
<span class="font-semibold text-gray-900 dark:text-white">
|
|
Recent Decisions
|
|
</span>
|
|
<span class="text-gray-500 dark:text-gray-400">
|
|
{{ historyExpanded ? '▼' : '▶' }}
|
|
</span>
|
|
</button>
|
|
|
|
<div
|
|
v-show="historyExpanded"
|
|
class="border-t border-gray-200 dark:border-gray-700 p-4 space-y-2"
|
|
>
|
|
<div
|
|
v-for="(decision, index) in recentDecisions"
|
|
:key="index"
|
|
class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-3 text-sm"
|
|
>
|
|
<div class="flex items-start justify-between gap-2">
|
|
<div class="flex-1">
|
|
<div class="font-medium text-gray-900 dark:text-white">
|
|
{{ decision.type }} Decision
|
|
</div>
|
|
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
|
{{ decision.summary }}
|
|
</div>
|
|
</div>
|
|
<div class="text-xs text-gray-400 dark:text-gray-500">
|
|
{{ decision.timestamp }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Idle State -->
|
|
<div
|
|
v-else
|
|
class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-8 text-center"
|
|
>
|
|
<div class="text-6xl mb-4">⚾</div>
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
|
Waiting for Play
|
|
</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
No decisions required at this time
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
import type { DefensiveDecision, OffensiveDecision } from '~/types/game'
|
|
import DefensiveSetup from './DefensiveSetup.vue'
|
|
import OffensiveApproach from './OffensiveApproach.vue'
|
|
import StolenBaseInputs from './StolenBaseInputs.vue'
|
|
|
|
interface DecisionHistoryItem {
|
|
type: 'Defensive' | 'Offensive'
|
|
summary: string
|
|
timestamp: string
|
|
}
|
|
|
|
interface Props {
|
|
gameId: string
|
|
currentTeam: 'home' | 'away'
|
|
isMyTurn: boolean
|
|
phase: 'defensive' | 'offensive' | 'idle'
|
|
runners?: {
|
|
first: number | null
|
|
second: number | null
|
|
third: number | null
|
|
}
|
|
currentDefensiveSetup?: DefensiveDecision
|
|
currentOffensiveDecision?: Omit<OffensiveDecision, 'steal_attempts'>
|
|
currentStealAttempts?: number[]
|
|
decisionHistory?: DecisionHistoryItem[]
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
phase: 'idle',
|
|
runners: () => ({
|
|
first: null,
|
|
second: null,
|
|
third: null,
|
|
}),
|
|
currentStealAttempts: () => [],
|
|
decisionHistory: () => [],
|
|
})
|
|
|
|
const emit = defineEmits<{
|
|
defensiveSubmit: [decision: DefensiveDecision]
|
|
offensiveSubmit: [decision: Omit<OffensiveDecision, 'steal_attempts'>]
|
|
stealAttemptsSubmit: [attempts: number[]]
|
|
}>()
|
|
|
|
// Local state
|
|
const historyExpanded = ref(false)
|
|
|
|
// Computed
|
|
const hasRunnersOnBase = computed(() => {
|
|
return props.runners.first !== null ||
|
|
props.runners.second !== null ||
|
|
props.runners.third !== null
|
|
})
|
|
|
|
const recentDecisions = computed(() => {
|
|
return props.decisionHistory.slice(0, 3)
|
|
})
|
|
|
|
// Event handlers
|
|
const handleDefensiveSubmit = (decision: DefensiveDecision) => {
|
|
emit('defensiveSubmit', decision)
|
|
}
|
|
|
|
const handleOffensiveSubmit = (decision: Omit<OffensiveDecision, 'steal_attempts'>) => {
|
|
emit('offensiveSubmit', decision)
|
|
}
|
|
|
|
const handleStealAttemptsSubmit = (attempts: number[]) => {
|
|
emit('stealAttemptsSubmit', attempts)
|
|
}
|
|
|
|
const handleStealAttemptsCancel = () => {
|
|
// Reset handled by parent component
|
|
}
|
|
</script>
|