CLAUDE: Standardize decision phase naming and fix frontend type mismatches
Frontend alignment with backend WebSocket protocol: **Type System Fixes**: - types/game.ts: Changed DecisionPhase to use 'awaiting_*' convention matching backend - types/game.ts: Fixed PlayOutcome enum values to match backend string values (e.g., 'strikeout' not 'STRIKEOUT') - types/game.ts: Added comprehensive play outcome types (groundball_a/b/c, flyout variants, x_check) **Decision Detection**: - store/game.ts: Updated decision detection to check both decision prompt AND gameState.decision_phase - components: Updated all decision phase checks to use 'awaiting_defensive', 'awaiting_offensive', 'awaiting_stolen_base' **WebSocket Enhancements**: - useWebSocket.ts: Added game_joined event handler with success toast - useWebSocket.ts: Fixed dice roll data - now receives d6_two_a and d6_two_b from server - useWebSocket.ts: Request fresh game state after decision submissions to sync decision_phase **New Constants**: - constants/outcomes.ts: Created centralized PlayOutcome enum with display labels and descriptions **Testing**: - Updated test expectations for new decision phase naming - All component tests passing **Why**: Eliminates confusion from dual naming conventions. Frontend now uses same vocabulary as backend. Fixes runtime type errors from enum value mismatches. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9627a79dce
commit
1373286391
@ -90,8 +90,8 @@ Game state contains minimal `LineupPlayerState` (lineup_id, position). Use `game
|
|||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
|
- **WebSocket Protocol Spec**: `../.claude/WEBSOCKET_PROTOCOL_SPEC.md` - Complete event catalog and workflow
|
||||||
- **Implementation Guide**: `../.claude/implementation/01-infrastructure.md`
|
- **Implementation Guide**: `../.claude/implementation/01-infrastructure.md`
|
||||||
- **WebSocket Protocol**: `../.claude/implementation/websocket-protocol.md`
|
|
||||||
- **Full PRD**: `../prd-web-scorecard-1.1.md`
|
- **Full PRD**: `../prd-web-scorecard-1.1.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@ -98,7 +98,7 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
variant="success"
|
variant="success"
|
||||||
size="lg"
|
size="lg"
|
||||||
:disabled="!isActive || !hasChanges"
|
:disabled="!isActive"
|
||||||
:loading="submitting"
|
:loading="submitting"
|
||||||
full-width
|
full-width
|
||||||
>
|
>
|
||||||
@ -213,7 +213,7 @@ const holdingDisplay = computed(() => {
|
|||||||
}).join(', ')
|
}).join(', ')
|
||||||
})
|
})
|
||||||
|
|
||||||
// Check if setup has changed from initial
|
// Check if setup has changed from initial (for display only)
|
||||||
const hasChanges = computed(() => {
|
const hasChanges = computed(() => {
|
||||||
if (!props.currentSetup) return true
|
if (!props.currentSetup) return true
|
||||||
return (
|
return (
|
||||||
@ -225,13 +225,13 @@ const hasChanges = computed(() => {
|
|||||||
|
|
||||||
const submitButtonText = computed(() => {
|
const submitButtonText = computed(() => {
|
||||||
if (!props.isActive) return 'Wait for Your Turn'
|
if (!props.isActive) return 'Wait for Your Turn'
|
||||||
if (!hasChanges.value) return 'No Changes'
|
if (!hasChanges.value) return 'Submit (Keep Setup)'
|
||||||
return 'Submit Defensive Setup'
|
return 'Submit Defensive Setup'
|
||||||
})
|
})
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!props.isActive || !hasChanges.value) return
|
if (!props.isActive) return
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -68,7 +68,7 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
variant="success"
|
variant="success"
|
||||||
size="lg"
|
size="lg"
|
||||||
:disabled="!isActive || !hasChanges"
|
:disabled="!isActive"
|
||||||
:loading="submitting"
|
:loading="submitting"
|
||||||
full-width
|
full-width
|
||||||
>
|
>
|
||||||
@ -209,7 +209,7 @@ const hasChanges = computed(() => {
|
|||||||
|
|
||||||
const submitButtonText = computed(() => {
|
const submitButtonText = computed(() => {
|
||||||
if (!props.isActive) return 'Wait for Your Turn'
|
if (!props.isActive) return 'Wait for Your Turn'
|
||||||
if (!hasChanges.value) return 'No Changes'
|
if (!hasChanges.value) return 'Submit (Keep Action)'
|
||||||
return 'Submit Offensive Strategy'
|
return 'Submit Offensive Strategy'
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -240,7 +240,7 @@ const getActionButtonClasses = (action: OffensiveDecision['action'], disabled: b
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!props.isActive || !hasChanges.value) return
|
if (!props.isActive) return
|
||||||
|
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -61,6 +61,8 @@
|
|||||||
<ManualOutcomeEntry
|
<ManualOutcomeEntry
|
||||||
:roll-data="pendingRoll"
|
:roll-data="pendingRoll"
|
||||||
:can-submit="canSubmitOutcome"
|
:can-submit="canSubmitOutcome"
|
||||||
|
:outs="outs"
|
||||||
|
:has-runners="hasRunners"
|
||||||
@submit="handleSubmitOutcome"
|
@submit="handleSubmitOutcome"
|
||||||
@cancel="handleCancelOutcome"
|
@cancel="handleCancelOutcome"
|
||||||
/>
|
/>
|
||||||
@ -114,9 +116,14 @@ interface Props {
|
|||||||
pendingRoll: RollData | null
|
pendingRoll: RollData | null
|
||||||
lastPlayResult: PlayResult | null
|
lastPlayResult: PlayResult | null
|
||||||
canSubmitOutcome: boolean
|
canSubmitOutcome: boolean
|
||||||
|
outs?: number
|
||||||
|
hasRunners?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
outs: 0,
|
||||||
|
hasRunners: false,
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
rollDice: []
|
rollDice: []
|
||||||
|
|||||||
@ -107,9 +107,14 @@ import type { PlayOutcome, RollData } from '~/types'
|
|||||||
interface Props {
|
interface Props {
|
||||||
rollData: RollData | null
|
rollData: RollData | null
|
||||||
canSubmit: boolean
|
canSubmit: boolean
|
||||||
|
outs?: number
|
||||||
|
hasRunners?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
outs: 0,
|
||||||
|
hasRunners: false,
|
||||||
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
submit: [{ outcome: PlayOutcome; hitLocation?: string }]
|
submit: [{ outcome: PlayOutcome; hitLocation?: string }]
|
||||||
@ -120,48 +125,25 @@ const emit = defineEmits<{
|
|||||||
const selectedOutcome = ref<PlayOutcome | null>(null)
|
const selectedOutcome = ref<PlayOutcome | null>(null)
|
||||||
const selectedHitLocation = ref<string | null>(null)
|
const selectedHitLocation = ref<string | null>(null)
|
||||||
|
|
||||||
// Outcome categories
|
// Import centralized outcome constants
|
||||||
const outcomeCategories = [
|
import { OUTCOME_CATEGORIES, OUTCOMES_REQUIRING_HIT_LOCATION, HIT_LOCATIONS } from '~/constants/outcomes'
|
||||||
{
|
|
||||||
name: 'Outs',
|
|
||||||
outcomes: ['STRIKEOUT', 'GROUNDOUT', 'FLYOUT', 'LINEOUT', 'POPOUT', 'DOUBLE_PLAY'] as PlayOutcome[],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Hits',
|
|
||||||
outcomes: ['SINGLE_1', 'SINGLE_2', 'SINGLE_UNCAPPED', 'DOUBLE_2', 'DOUBLE_3', 'DOUBLE_UNCAPPED', 'TRIPLE', 'HOMERUN'] as PlayOutcome[],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Walks / HBP',
|
|
||||||
outcomes: ['WALK', 'INTENTIONAL_WALK', 'HIT_BY_PITCH'] as PlayOutcome[],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Special',
|
|
||||||
outcomes: ['ERROR'] as PlayOutcome[],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Interrupts',
|
|
||||||
outcomes: ['STOLEN_BASE', 'CAUGHT_STEALING', 'WILD_PITCH', 'PASSED_BALL', 'BALK', 'PICK_OFF'] as PlayOutcome[],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// Hit location options
|
// Use imported constants
|
||||||
const infieldPositions = ['P', 'C', '1B', '2B', '3B', 'SS']
|
const outcomeCategories = OUTCOME_CATEGORIES
|
||||||
const outfieldPositions = ['LF', 'CF', 'RF']
|
const infieldPositions = HIT_LOCATIONS.infield
|
||||||
|
const outfieldPositions = HIT_LOCATIONS.outfield
|
||||||
// Outcomes that require hit location
|
const outcomesNeedingHitLocation = OUTCOMES_REQUIRING_HIT_LOCATION
|
||||||
// Only outcomes that affect defensive plays require location
|
|
||||||
const outcomesNeedingHitLocation = [
|
|
||||||
'GROUNDOUT', // All groundouts
|
|
||||||
'FLYOUT', // All flyouts
|
|
||||||
'LINEOUT', // All lineouts
|
|
||||||
'SINGLE_UNCAPPED', // Decision tree hits
|
|
||||||
'DOUBLE_UNCAPPED', // Decision tree hits
|
|
||||||
'ERROR', // Defensive plays
|
|
||||||
]
|
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
const needsHitLocation = computed(() => {
|
const needsHitLocation = computed(() => {
|
||||||
return selectedOutcome.value !== null && outcomesNeedingHitLocation.includes(selectedOutcome.value)
|
if (!selectedOutcome.value) return false
|
||||||
|
if (!(outcomesNeedingHitLocation as readonly string[]).includes(selectedOutcome.value)) return false
|
||||||
|
|
||||||
|
// Hit location only matters when there are runners on base AND less than 2 outs
|
||||||
|
// (for fielding choices and runner advancement)
|
||||||
|
const hasRunnersAndCanAdvance = props.hasRunners && props.outs < 2
|
||||||
|
|
||||||
|
return hasRunnersAndCanAdvance
|
||||||
})
|
})
|
||||||
|
|
||||||
const canSubmitForm = computed(() => {
|
const canSubmitForm = computed(() => {
|
||||||
@ -174,7 +156,7 @@ const canSubmitForm = computed(() => {
|
|||||||
const selectOutcome = (outcome: PlayOutcome) => {
|
const selectOutcome = (outcome: PlayOutcome) => {
|
||||||
selectedOutcome.value = outcome
|
selectedOutcome.value = outcome
|
||||||
// Clear hit location if the new outcome doesn't need it
|
// Clear hit location if the new outcome doesn't need it
|
||||||
if (!outcomesNeedingHitLocation.includes(outcome)) {
|
if (!(outcomesNeedingHitLocation as readonly string[]).includes(outcome)) {
|
||||||
selectedHitLocation.value = null
|
selectedHitLocation.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -235,6 +235,11 @@ export function useWebSocket() {
|
|||||||
console.log('[WebSocket] Server confirmed connection for user:', data.user_id)
|
console.log('[WebSocket] Server confirmed connection for user:', data.user_id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socketInstance.on('game_joined', (data: { game_id: string; role: string }) => {
|
||||||
|
console.log('[WebSocket] Successfully joined game:', data.game_id, 'as', data.role)
|
||||||
|
uiStore.showSuccess(`Joined game as ${data.role}`)
|
||||||
|
})
|
||||||
|
|
||||||
socketInstance.on('heartbeat_ack', () => {
|
socketInstance.on('heartbeat_ack', () => {
|
||||||
// Heartbeat acknowledged - connection is healthy
|
// Heartbeat acknowledged - connection is healthy
|
||||||
// No action needed, just prevents timeout
|
// No action needed, just prevents timeout
|
||||||
@ -263,12 +268,6 @@ export function useWebSocket() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
socketInstance.on('play_completed', (play) => {
|
|
||||||
console.log('[WebSocket] Play completed:', play.description)
|
|
||||||
gameStore.addPlayToHistory(play)
|
|
||||||
uiStore.showInfo(play.description, 3000)
|
|
||||||
})
|
|
||||||
|
|
||||||
socketInstance.on('inning_change', (data) => {
|
socketInstance.on('inning_change', (data) => {
|
||||||
console.log(`[WebSocket] Inning change: ${data.half} ${data.inning}`)
|
console.log(`[WebSocket] Inning change: ${data.half} ${data.inning}`)
|
||||||
uiStore.showInfo(`${data.half === 'top' ? 'Top' : 'Bottom'} ${data.inning}`, 3000)
|
uiStore.showInfo(`${data.half === 'top' ? 'Top' : 'Bottom'} ${data.inning}`, 3000)
|
||||||
@ -294,6 +293,14 @@ export function useWebSocket() {
|
|||||||
socketInstance.on('defensive_decision_submitted', (data) => {
|
socketInstance.on('defensive_decision_submitted', (data) => {
|
||||||
console.log('[WebSocket] Defensive decision submitted')
|
console.log('[WebSocket] Defensive decision submitted')
|
||||||
gameStore.clearDecisionPrompt()
|
gameStore.clearDecisionPrompt()
|
||||||
|
|
||||||
|
// Request updated game state to get new decision_phase
|
||||||
|
if (socketInstance && gameStore.gameId) {
|
||||||
|
socketInstance.emit('request_game_state', {
|
||||||
|
game_id: gameStore.gameId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (data.pending_decision) {
|
if (data.pending_decision) {
|
||||||
uiStore.showInfo('Defense set. Waiting for offense...', 3000)
|
uiStore.showInfo('Defense set. Waiting for offense...', 3000)
|
||||||
} else {
|
} else {
|
||||||
@ -304,6 +311,14 @@ export function useWebSocket() {
|
|||||||
socketInstance.on('offensive_decision_submitted', (data) => {
|
socketInstance.on('offensive_decision_submitted', (data) => {
|
||||||
console.log('[WebSocket] Offensive decision submitted')
|
console.log('[WebSocket] Offensive decision submitted')
|
||||||
gameStore.clearDecisionPrompt()
|
gameStore.clearDecisionPrompt()
|
||||||
|
|
||||||
|
// Request updated game state to get new decision_phase
|
||||||
|
if (socketInstance && gameStore.gameId) {
|
||||||
|
socketInstance.emit('request_game_state', {
|
||||||
|
game_id: gameStore.gameId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
uiStore.showSuccess('Offense set. Ready to play!', 3000)
|
uiStore.showSuccess('Offense set. Ready to play!', 3000)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -316,8 +331,8 @@ export function useWebSocket() {
|
|||||||
gameStore.setPendingRoll({
|
gameStore.setPendingRoll({
|
||||||
roll_id: data.roll_id,
|
roll_id: data.roll_id,
|
||||||
d6_one: data.d6_one,
|
d6_one: data.d6_one,
|
||||||
d6_two_a: 0, // Not provided by server
|
d6_two_a: data.d6_two_a,
|
||||||
d6_two_b: 0, // Not provided by server
|
d6_two_b: data.d6_two_b,
|
||||||
d6_two_total: data.d6_two_total,
|
d6_two_total: data.d6_two_total,
|
||||||
chaos_d20: data.chaos_d20,
|
chaos_d20: data.chaos_d20,
|
||||||
resolution_d20: data.resolution_d20,
|
resolution_d20: data.resolution_d20,
|
||||||
|
|||||||
93
frontend-sba/constants/outcomes.ts
Normal file
93
frontend-sba/constants/outcomes.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* Outcome Constants
|
||||||
|
*
|
||||||
|
* Centralized definitions for play outcomes used across the UI.
|
||||||
|
* These match the backend PlayOutcome enum values.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { PlayOutcome } from '~/types/game'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outcome categories for UI display and selection
|
||||||
|
* Grouped by common baseball categories for user-friendly selection
|
||||||
|
*/
|
||||||
|
export const OUTCOME_CATEGORIES = [
|
||||||
|
{
|
||||||
|
name: 'Outs',
|
||||||
|
outcomes: [
|
||||||
|
'strikeout',
|
||||||
|
'groundball_a',
|
||||||
|
'groundball_b',
|
||||||
|
'groundball_c',
|
||||||
|
'flyout_a',
|
||||||
|
'flyout_b',
|
||||||
|
'flyout_c',
|
||||||
|
'lineout',
|
||||||
|
'popout',
|
||||||
|
] as const satisfies readonly PlayOutcome[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Hits',
|
||||||
|
outcomes: [
|
||||||
|
'single_1',
|
||||||
|
'single_2',
|
||||||
|
'single_uncapped',
|
||||||
|
'double_2',
|
||||||
|
'double_3',
|
||||||
|
'double_uncapped',
|
||||||
|
'triple',
|
||||||
|
'homerun',
|
||||||
|
] as const satisfies readonly PlayOutcome[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Walks / HBP',
|
||||||
|
outcomes: [
|
||||||
|
'walk',
|
||||||
|
'intentional_walk',
|
||||||
|
'hbp',
|
||||||
|
] as const satisfies readonly PlayOutcome[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Special',
|
||||||
|
outcomes: [
|
||||||
|
'error',
|
||||||
|
'x_check',
|
||||||
|
] as const satisfies readonly PlayOutcome[],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Interrupts',
|
||||||
|
outcomes: [
|
||||||
|
'stolen_base',
|
||||||
|
'caught_stealing',
|
||||||
|
'wild_pitch',
|
||||||
|
'passed_ball',
|
||||||
|
'balk',
|
||||||
|
'pick_off',
|
||||||
|
] as const satisfies readonly PlayOutcome[],
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Outcomes that require a hit location to be specified
|
||||||
|
* These outcomes involve defensive plays where location matters
|
||||||
|
*/
|
||||||
|
export const OUTCOMES_REQUIRING_HIT_LOCATION = [
|
||||||
|
'groundball_a',
|
||||||
|
'groundball_b',
|
||||||
|
'groundball_c',
|
||||||
|
'flyout_a',
|
||||||
|
'flyout_b',
|
||||||
|
'flyout_c',
|
||||||
|
'lineout',
|
||||||
|
'single_uncapped',
|
||||||
|
'double_uncapped',
|
||||||
|
'error',
|
||||||
|
] as const satisfies readonly PlayOutcome[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hit location options
|
||||||
|
*/
|
||||||
|
export const HIT_LOCATIONS = {
|
||||||
|
infield: ['P', 'C', '1B', '2B', '3B', 'SS'] as const,
|
||||||
|
outfield: ['LF', 'CF', 'RF'] as const,
|
||||||
|
} as const
|
||||||
@ -86,6 +86,8 @@
|
|||||||
:pending-roll="pendingRoll"
|
:pending-roll="pendingRoll"
|
||||||
:last-play-result="lastPlayResult"
|
:last-play-result="lastPlayResult"
|
||||||
:can-submit-outcome="canSubmitOutcome"
|
:can-submit-outcome="canSubmitOutcome"
|
||||||
|
:outs="gameState?.outs ?? 0"
|
||||||
|
:has-runners="!!(gameState?.runners?.first || gameState?.runners?.second || gameState?.runners?.third)"
|
||||||
@roll-dice="handleRollDice"
|
@roll-dice="handleRollDice"
|
||||||
@submit-outcome="handleSubmitOutcome"
|
@submit-outcome="handleSubmitOutcome"
|
||||||
@dismiss-result="handleDismissResult"
|
@dismiss-result="handleDismissResult"
|
||||||
@ -137,6 +139,8 @@
|
|||||||
:pending-roll="pendingRoll"
|
:pending-roll="pendingRoll"
|
||||||
:last-play-result="lastPlayResult"
|
:last-play-result="lastPlayResult"
|
||||||
:can-submit-outcome="canSubmitOutcome"
|
:can-submit-outcome="canSubmitOutcome"
|
||||||
|
:outs="gameState?.outs ?? 0"
|
||||||
|
:has-runners="!!(gameState?.runners?.first || gameState?.runners?.second || gameState?.runners?.third)"
|
||||||
@roll-dice="handleRollDice"
|
@roll-dice="handleRollDice"
|
||||||
@submit-outcome="handleSubmitOutcome"
|
@submit-outcome="handleSubmitOutcome"
|
||||||
@dismiss-result="handleDismissResult"
|
@dismiss-result="handleDismissResult"
|
||||||
@ -303,6 +307,7 @@ const needsDefensiveDecision = computed(() => gameStore.needsDefensiveDecision)
|
|||||||
const needsOffensiveDecision = computed(() => gameStore.needsOffensiveDecision)
|
const needsOffensiveDecision = computed(() => gameStore.needsOffensiveDecision)
|
||||||
const pendingRoll = computed(() => gameStore.pendingRoll)
|
const pendingRoll = computed(() => gameStore.pendingRoll)
|
||||||
const lastPlayResult = computed(() => gameStore.lastPlayResult)
|
const lastPlayResult = computed(() => gameStore.lastPlayResult)
|
||||||
|
const currentDecisionPrompt = computed(() => gameStore.currentDecisionPrompt)
|
||||||
|
|
||||||
// Local UI state
|
// Local UI state
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
@ -353,12 +358,36 @@ const decisionPhase = computed(() => {
|
|||||||
|
|
||||||
// Phase F6: Conditional panel rendering
|
// Phase F6: Conditional panel rendering
|
||||||
const showDecisions = computed(() => {
|
const showDecisions = computed(() => {
|
||||||
return gameState.value?.status === 'active' &&
|
// Don't show decision panels if there's a result pending dismissal
|
||||||
|
if (lastPlayResult.value) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = gameState.value?.status === 'active' &&
|
||||||
isMyTurn.value &&
|
isMyTurn.value &&
|
||||||
(needsDefensiveDecision.value || needsOffensiveDecision.value)
|
(needsDefensiveDecision.value || needsOffensiveDecision.value)
|
||||||
|
|
||||||
|
// Debug logging
|
||||||
|
console.log('[Game Page] Panel visibility check:', {
|
||||||
|
gameStatus: gameState.value?.status,
|
||||||
|
isMyTurn: isMyTurn.value,
|
||||||
|
needsDefensiveDecision: needsDefensiveDecision.value,
|
||||||
|
needsOffensiveDecision: needsOffensiveDecision.value,
|
||||||
|
decision_phase: gameState.value?.decision_phase,
|
||||||
|
showDecisions: result,
|
||||||
|
currentDecisionPrompt: currentDecisionPrompt.value,
|
||||||
|
hasLastPlayResult: !!lastPlayResult.value
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
const showGameplay = computed(() => {
|
const showGameplay = computed(() => {
|
||||||
|
// Show gameplay panel if there's a result to display OR if we're in the resolution phase
|
||||||
|
if (lastPlayResult.value) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
return gameState.value?.status === 'active' &&
|
return gameState.value?.status === 'active' &&
|
||||||
isMyTurn.value &&
|
isMyTurn.value &&
|
||||||
!needsDefensiveDecision.value &&
|
!needsDefensiveDecision.value &&
|
||||||
|
|||||||
@ -107,15 +107,21 @@ export const useGameStore = defineStore('game', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const needsDefensiveDecision = computed(() => {
|
const needsDefensiveDecision = computed(() => {
|
||||||
return currentDecisionPrompt.value?.phase === 'defense'
|
// Standardized naming (2025-01-21): Both backend and frontend now use 'awaiting_defensive'
|
||||||
|
return currentDecisionPrompt.value?.phase === 'awaiting_defensive' ||
|
||||||
|
gameState.value?.decision_phase === 'awaiting_defensive'
|
||||||
})
|
})
|
||||||
|
|
||||||
const needsOffensiveDecision = computed(() => {
|
const needsOffensiveDecision = computed(() => {
|
||||||
return currentDecisionPrompt.value?.phase === 'offensive_approach'
|
// Standardized naming (2025-01-21): Both backend and frontend now use 'awaiting_offensive'
|
||||||
|
return currentDecisionPrompt.value?.phase === 'awaiting_offensive' ||
|
||||||
|
gameState.value?.decision_phase === 'awaiting_offensive'
|
||||||
})
|
})
|
||||||
|
|
||||||
const needsStolenBaseDecision = computed(() => {
|
const needsStolenBaseDecision = computed(() => {
|
||||||
return currentDecisionPrompt.value?.phase === 'stolen_base'
|
// Standardized naming (2025-01-21): Both backend and frontend now use 'awaiting_stolen_base'
|
||||||
|
return currentDecisionPrompt.value?.phase === 'awaiting_stolen_base' ||
|
||||||
|
gameState.value?.decision_phase === 'awaiting_stolen_base'
|
||||||
})
|
})
|
||||||
|
|
||||||
const canRollDice = computed(() => {
|
const canRollDice = computed(() => {
|
||||||
|
|||||||
@ -223,7 +223,7 @@ describe('DefensiveSetup', () => {
|
|||||||
expect(wrapper.vm.submitButtonText).toBe('Wait for Your Turn')
|
expect(wrapper.vm.submitButtonText).toBe('Wait for Your Turn')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows "No Changes" when setup unchanged', () => {
|
it('shows "Submit (Keep Setup)" when setup unchanged', () => {
|
||||||
const currentSetup: DefensiveDecision = {
|
const currentSetup: DefensiveDecision = {
|
||||||
infield_depth: 'normal',
|
infield_depth: 'normal',
|
||||||
outfield_depth: 'normal',
|
outfield_depth: 'normal',
|
||||||
@ -237,7 +237,7 @@ describe('DefensiveSetup', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(wrapper.vm.submitButtonText).toBe('No Changes')
|
expect(wrapper.vm.submitButtonText).toBe('Submit (Keep Setup)')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows "Submit Defensive Setup" when active with changes', () => {
|
it('shows "Submit Defensive Setup" when active with changes', () => {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ const createMockGameState = (overrides?: Partial<GameState>): GameState => ({
|
|||||||
current_batter: null,
|
current_batter: null,
|
||||||
current_pitcher: null,
|
current_pitcher: null,
|
||||||
current_catcher: null,
|
current_catcher: null,
|
||||||
decision_phase: 'defense',
|
decision_phase: 'awaiting_defensive',
|
||||||
...overrides,
|
...overrides,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -34,8 +34,11 @@ export type LeagueId = 'sba' | 'pd'
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Decision phase in the play workflow
|
* Decision phase in the play workflow
|
||||||
|
*
|
||||||
|
* Standardized naming (2025-01-21): Uses backend convention 'awaiting_*'
|
||||||
|
* for clarity about what action is pending.
|
||||||
*/
|
*/
|
||||||
export type DecisionPhase = 'defense' | 'stolen_base' | 'offensive_approach' | 'resolution' | 'complete'
|
export type DecisionPhase = 'awaiting_defensive' | 'awaiting_stolen_base' | 'awaiting_offensive' | 'resolution' | 'complete'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lineup player state - represents a player in the game
|
* Lineup player state - represents a player in the game
|
||||||
@ -156,50 +159,54 @@ export interface RollData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play outcome enumeration (subset of backend PlayOutcome)
|
* Play outcome enumeration (matches backend PlayOutcome enum values)
|
||||||
|
* Note: These are the STRING VALUES, not enum names
|
||||||
*/
|
*/
|
||||||
export type PlayOutcome =
|
export type PlayOutcome =
|
||||||
// Standard outs
|
// Standard outs
|
||||||
| 'STRIKEOUT'
|
| 'strikeout'
|
||||||
| 'GROUNDOUT'
|
| 'groundball_a'
|
||||||
| 'FLYOUT'
|
| 'groundball_b'
|
||||||
| 'LINEOUT'
|
| 'groundball_c'
|
||||||
| 'POPOUT'
|
| 'flyout_a'
|
||||||
| 'DOUBLE_PLAY'
|
| 'flyout_b'
|
||||||
|
| 'flyout_bq'
|
||||||
|
| 'flyout_c'
|
||||||
|
| 'lineout'
|
||||||
|
| 'popout'
|
||||||
|
|
||||||
// Walks and hits by pitch
|
// Walks and hits by pitch
|
||||||
| 'WALK'
|
| 'walk'
|
||||||
| 'INTENTIONAL_WALK'
|
| 'intentional_walk'
|
||||||
| 'HIT_BY_PITCH'
|
| 'hbp'
|
||||||
|
|
||||||
// Hits (capped - specific bases)
|
// Hits (capped - specific bases)
|
||||||
| 'SINGLE_1'
|
| 'single_1'
|
||||||
| 'SINGLE_2'
|
| 'single_2'
|
||||||
| 'DOUBLE_2'
|
| 'single_uncapped'
|
||||||
| 'DOUBLE_3'
|
| 'double_2'
|
||||||
| 'TRIPLE'
|
| 'double_3'
|
||||||
| 'HOMERUN'
|
| 'double_uncapped'
|
||||||
|
| 'triple'
|
||||||
// Uncapped hits (require advancement decisions)
|
| 'homerun'
|
||||||
| 'SINGLE_UNCAPPED'
|
|
||||||
| 'DOUBLE_UNCAPPED'
|
|
||||||
|
|
||||||
// Interrupt plays (baserunning events)
|
// Interrupt plays (baserunning events)
|
||||||
| 'STOLEN_BASE'
|
| 'stolen_base'
|
||||||
| 'CAUGHT_STEALING'
|
| 'caught_stealing'
|
||||||
| 'WILD_PITCH'
|
| 'wild_pitch'
|
||||||
| 'PASSED_BALL'
|
| 'passed_ball'
|
||||||
| 'BALK'
|
| 'balk'
|
||||||
| 'PICK_OFF'
|
| 'pick_off'
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
| 'ERROR'
|
| 'error'
|
||||||
|
| 'x_check'
|
||||||
|
|
||||||
// Ballpark power (PD specific)
|
// Ballpark power (PD specific)
|
||||||
| 'BP_HOMERUN'
|
| 'bp_homerun'
|
||||||
| 'BP_FLYOUT'
|
| 'bp_flyout'
|
||||||
| 'BP_SINGLE'
|
| 'bp_single'
|
||||||
| 'BP_LINEOUT'
|
| 'bp_lineout'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runner advancement during a play
|
* Runner advancement during a play
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user