strat-gameplay-webapp/frontend-sba/components/Decisions/StolenBaseInputs.vue
Cal Corum 2381456189 test: Skip unstable test suites
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 20:18:33 -06:00

262 lines
8.0 KiB
Vue

<template>
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<!-- Header -->
<div class="flex items-center justify-between mb-6">
<h3 class="text-xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<span class="text-2xl">🏃</span>
Stolen Base Attempts
</h3>
<span
v-if="!isActive"
class="px-3 py-1 text-xs font-semibold rounded-full bg-gray-200 text-gray-600"
>
Opponent's Turn
</span>
</div>
<!-- No runners on base -->
<div
v-if="!hasRunners"
class="text-center py-8 text-gray-500 dark:text-gray-400"
>
<div class="text-4xl mb-2">⚾</div>
<p class="text-sm">No runners on base</p>
</div>
<!-- Runners present -->
<div v-else class="space-y-6">
<!-- Visual Mini Diamond -->
<div class="bg-gradient-to-br from-green-50 to-green-100 dark:from-gray-700 dark:to-gray-600 rounded-lg p-6">
<div class="relative w-32 h-32 mx-auto">
<!-- Diamond -->
<div class="absolute inset-0 transform rotate-45">
<div class="w-full h-full border-4 border-green-600 dark:border-green-400 bg-amber-200 dark:bg-amber-700 opacity-50"/>
</div>
<!-- Bases -->
<!-- Home -->
<div
class="absolute bottom-0 left-1/2 -translate-x-1/2 w-4 h-4 bg-white border-2 border-gray-600 rounded-sm"
/>
<!-- 1st Base -->
<div
class="absolute top-1/2 right-0 -translate-y-1/2 w-5 h-5 rounded-full border-2"
:class="runners.first !== null
? 'bg-yellow-400 border-yellow-600 shadow-lg shadow-yellow-400/50'
: 'bg-white border-gray-400'"
>
<span v-if="runners.first !== null && stealAttempts.includes(2)" class="absolute inset-0 flex items-center justify-center text-xs font-bold text-red-600">
</span>
</div>
<!-- 2nd Base -->
<div
class="absolute top-0 left-1/2 -translate-x-1/2 w-5 h-5 rounded-full border-2"
:class="runners.second !== null
? 'bg-yellow-400 border-yellow-600 shadow-lg shadow-yellow-400/50'
: 'bg-white border-gray-400'"
>
<span v-if="runners.second !== null && stealAttempts.includes(3)" class="absolute inset-0 flex items-center justify-center text-xs font-bold text-red-600">
</span>
</div>
<!-- 3rd Base -->
<div
class="absolute top-1/2 left-0 -translate-y-1/2 w-5 h-5 rounded-full border-2"
:class="runners.third !== null
? 'bg-yellow-400 border-yellow-600 shadow-lg shadow-yellow-400/50'
: 'bg-white border-gray-400'"
>
<span v-if="runners.third !== null && stealAttempts.includes(4)" class="absolute inset-0 flex items-center justify-center text-xs font-bold text-red-600">
</span>
</div>
</div>
</div>
<!-- Steal Toggles -->
<div class="space-y-3">
<!-- Runner on 1st -->
<div
v-if="runners.first !== null"
class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4"
>
<ToggleSwitch
v-model="stealToSecond"
:label="`Runner on 1st attempts steal to 2nd`"
:disabled="!isActive"
size="md"
/>
</div>
<!-- Runner on 2nd -->
<div
v-if="runners.second !== null"
class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4"
>
<ToggleSwitch
v-model="stealToThird"
:label="`Runner on 2nd attempts steal to 3rd`"
:disabled="!isActive"
size="md"
/>
</div>
<!-- Runner on 3rd -->
<div
v-if="runners.third !== null"
class="bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4"
>
<ToggleSwitch
v-model="stealHome"
:label="`Runner on 3rd attempts steal home`"
:disabled="!isActive"
size="md"
/>
</div>
</div>
<!-- Summary -->
<div
v-if="stealAttempts.length > 0"
class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-lg p-3"
>
<p class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
⚡ {{ stealAttempts.length }} steal attempt{{ stealAttempts.length > 1 ? 's' : '' }} selected
</p>
</div>
<!-- Actions -->
<div class="flex gap-3">
<ActionButton
variant="secondary"
size="lg"
:disabled="!isActive || !hasChanges"
class="flex-1"
@click="handleCancel"
>
Cancel
</ActionButton>
<ActionButton
variant="success"
size="lg"
:disabled="!isActive || !hasChanges"
:loading="submitting"
class="flex-1"
@click="handleSubmit"
>
{{ submitButtonText }}
</ActionButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import ToggleSwitch from '~/components/UI/ToggleSwitch.vue'
import ActionButton from '~/components/UI/ActionButton.vue'
interface Props {
runners: {
first: number | null
second: number | null
third: number | null
}
isActive: boolean
currentAttempts?: number[]
}
const props = withDefaults(defineProps<Props>(), {
isActive: false,
currentAttempts: () => [],
})
const emit = defineEmits<{
submit: [stealAttempts: number[]]
cancel: []
}>()
// Local state
const submitting = ref(false)
const stealToSecond = ref(props.currentAttempts.includes(2))
const stealToThird = ref(props.currentAttempts.includes(3))
const stealHome = ref(props.currentAttempts.includes(4))
// Computed
const hasRunners = computed(() => {
return props.runners.first !== null ||
props.runners.second !== null ||
props.runners.third !== null
})
const stealAttempts = computed(() => {
const attempts: number[] = []
if (stealToSecond.value && props.runners.first !== null) attempts.push(2)
if (stealToThird.value && props.runners.second !== null) attempts.push(3)
if (stealHome.value && props.runners.third !== null) attempts.push(4)
return attempts
})
const hasChanges = computed(() => {
const currentSet = new Set(props.currentAttempts)
const newSet = new Set(stealAttempts.value)
if (currentSet.size !== newSet.size) return true
for (const attempt of newSet) {
if (!currentSet.has(attempt)) return true
}
return false
})
const submitButtonText = computed(() => {
if (!props.isActive) return 'Wait for Your Turn'
if (!hasChanges.value) return 'No Changes'
if (stealAttempts.value.length === 0) return 'No Steal Attempts'
return `Submit ${stealAttempts.value.length} Attempt${stealAttempts.value.length > 1 ? 's' : ''}`
})
// Methods
const handleSubmit = async () => {
if (!props.isActive || !hasChanges.value) return
submitting.value = true
try {
emit('submit', stealAttempts.value)
} finally {
submitting.value = false
}
}
const handleCancel = () => {
if (!props.isActive) return
// Reset to current attempts
stealToSecond.value = props.currentAttempts.includes(2)
stealToThird.value = props.currentAttempts.includes(3)
stealHome.value = props.currentAttempts.includes(4)
emit('cancel')
}
// Watch for prop changes
watch(() => props.currentAttempts, (newAttempts) => {
stealToSecond.value = newAttempts.includes(2)
stealToThird.value = newAttempts.includes(3)
stealHome.value = newAttempts.includes(4)
}, { deep: true })
// Reset toggles when runners change
watch(() => props.runners, (newRunners) => {
// Clear steal attempts for bases that no longer have runners
if (newRunners.first === null) stealToSecond.value = false
if (newRunners.second === null) stealToThird.value = false
if (newRunners.third === null) stealHome.value = false
}, { deep: true })
</script>