strat-gameplay-webapp/frontend-sba/pages/demo-decisions.vue
Cal Corum 4e7ea9e514 CLAUDE: Remove alignment field from frontend - complete Session 1 cleanup
Removed all references to the defensive alignment field across frontend codebase
after backend removal in Session 1. The alignment field was determined to be unused
and was removed from DefensiveDecision model.

Changes:
- types/websocket.ts: Removed alignment from DefensiveDecisionRequest interface
- composables/useGameActions.ts: Removed alignment from submit handler
- pages/demo-decisions.vue: Updated demo state and summary text (alignment → depths)
- pages/games/[id].vue: Updated decision history text for both defensive and offensive
  * Defensive: Now shows "infield depth, outfield depth" instead of "alignment, infield"
  * Offensive: Updated to use new action field with proper labels (swing_away, hit_and_run, etc.)
- Test files (3): Updated all test cases to remove alignment references
  * tests/unit/composables/useGameActions.spec.ts
  * tests/unit/store/game-decisions.spec.ts
  * tests/unit/components/Decisions/DefensiveSetup.spec.ts

Also updated offensive decision handling to match Session 2 changes (approach/hit_and_run/bunt_attempt → action field).

Total: 7 files modified, all alignment references removed
Verified: Zero remaining alignment references in .ts/.vue/.js files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 15:34:59 -06:00

438 lines
16 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="min-h-screen bg-gradient-to-br from-gray-50 to-blue-50 dark:from-gray-900 dark:to-gray-800 py-8">
<div class="container mx-auto px-4 max-w-6xl">
<!-- Header -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-2">
Phase F3 Decision Components Demo
</h1>
<p class="text-gray-600 dark:text-gray-400">
Interactive preview of all decision input components
</p>
</div>
<!-- Demo Controls -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6 mb-8">
<h2 class="text-xl font-bold text-gray-900 dark:text-white mb-4">Demo Controls</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Active State
</label>
<ToggleSwitch
v-model="demoControls.isActive"
label="Components Active"
size="md"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Runners on Base
</label>
<ToggleSwitch
v-model="demoControls.hasRunners"
label="Runners Present"
size="md"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Decision Phase
</label>
<select
v-model="demoControls.phase"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
>
<option value="idle">Idle</option>
<option value="defensive">Defensive</option>
<option value="offensive">Offensive</option>
</select>
</div>
</div>
</div>
<!-- Tab Navigation -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg mb-8 overflow-hidden">
<div class="flex border-b border-gray-200 dark:border-gray-700">
<button
v-for="tab in tabs"
:key="tab.id"
@click="activeTab = tab.id"
:class="[
'flex-1 px-6 py-4 text-sm font-medium transition-colors',
activeTab === tab.id
? 'bg-primary text-white border-b-2 border-primary'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-gray-700'
]"
>
{{ tab.icon }} {{ tab.label }}
</button>
</div>
</div>
<!-- Tab Content -->
<div class="space-y-8">
<!-- UI Components Tab -->
<div v-if="activeTab === 'ui'">
<div class="space-y-8">
<!-- ActionButton Demo -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">ActionButton</h3>
<div class="space-y-6">
<!-- Variants -->
<div>
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Variants</h4>
<div class="flex flex-wrap gap-3">
<ActionButton variant="primary" @click="showToast('Primary clicked!')">
Primary
</ActionButton>
<ActionButton variant="secondary" @click="showToast('Secondary clicked!')">
Secondary
</ActionButton>
<ActionButton variant="success" @click="showToast('Success clicked!')">
Success
</ActionButton>
<ActionButton variant="danger" @click="showToast('Danger clicked!')">
Danger
</ActionButton>
<ActionButton variant="warning" @click="showToast('Warning clicked!')">
Warning
</ActionButton>
</div>
</div>
<!-- Sizes -->
<div>
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Sizes</h4>
<div class="flex flex-wrap items-center gap-3">
<ActionButton size="sm">Small</ActionButton>
<ActionButton size="md">Medium</ActionButton>
<ActionButton size="lg">Large</ActionButton>
</div>
</div>
<!-- States -->
<div>
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">States</h4>
<div class="flex flex-wrap gap-3">
<ActionButton :loading="true">Loading</ActionButton>
<ActionButton :disabled="true">Disabled</ActionButton>
<ActionButton full-width>Full Width</ActionButton>
</div>
</div>
</div>
</div>
<!-- ButtonGroup Demo -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">ButtonGroup</h3>
<div class="space-y-6">
<!-- Horizontal -->
<div>
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Horizontal</h4>
<ButtonGroup
v-model="demoState.selectedOption"
:options="[
{ value: 'opt1', label: 'Option 1', icon: '🎯' },
{ value: 'opt2', label: 'Option 2', icon: '⚡' },
{ value: 'opt3', label: 'Option 3', icon: '🚀' },
]"
/>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
Selected: {{ demoState.selectedOption }}
</p>
</div>
<!-- Vertical -->
<div>
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-3">Vertical</h4>
<ButtonGroup
v-model="demoState.selectedVertical"
:options="[
{ value: 'a', label: 'Option A' },
{ value: 'b', label: 'Option B' },
{ value: 'c', label: 'Option C' },
]"
vertical
/>
</div>
</div>
</div>
<!-- ToggleSwitch Demo -->
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">ToggleSwitch</h3>
<div class="space-y-4">
<ToggleSwitch
v-model="demoState.toggle1"
label="Enable feature 1"
size="sm"
/>
<ToggleSwitch
v-model="demoState.toggle2"
label="Enable feature 2 (medium)"
size="md"
/>
<ToggleSwitch
v-model="demoState.toggle3"
label="Enable feature 3 (large)"
size="lg"
/>
<ToggleSwitch
v-model="demoState.toggle4"
label="Disabled toggle"
:disabled="true"
/>
</div>
</div>
</div>
</div>
<!-- Defensive Setup Tab -->
<div v-if="activeTab === 'defensive'">
<DefensiveSetup
game-id="demo-game-123"
:is-active="demoControls.isActive"
:current-setup="demoState.defensiveSetup"
@submit="handleDefensiveSubmit"
/>
</div>
<!-- Stolen Base Tab -->
<div v-if="activeTab === 'steals'">
<StolenBaseInputs
:runners="demoRunners"
:is-active="demoControls.isActive"
:current-attempts="demoState.stealAttempts"
@submit="handleStealSubmit"
@cancel="handleStealCancel"
/>
</div>
<!-- Offensive Approach Tab -->
<div v-if="activeTab === 'offensive'">
<OffensiveApproach
game-id="demo-game-123"
:is-active="demoControls.isActive"
:current-decision="demoState.offensiveDecision"
:has-runners-on-base="demoControls.hasRunners"
:runner-on-first="!!demoRunners.first"
:runner-on-second="!!demoRunners.second"
:runner-on-third="!!demoRunners.third"
:outs="0"
@submit="handleOffensiveSubmit"
/>
</div>
<!-- Decision Panel Tab -->
<div v-if="activeTab === 'panel'">
<DecisionPanel
game-id="demo-game-123"
current-team="home"
:is-my-turn="demoControls.isActive"
:phase="demoControls.phase"
:runners="demoRunners"
:current-defensive-setup="demoState.defensiveSetup"
:current-offensive-decision="demoState.offensiveDecision"
:current-steal-attempts="demoState.stealAttempts"
:decision-history="demoState.decisionHistory"
@defensive-submit="handleDefensiveSubmit"
@offensive-submit="handleOffensiveSubmit"
@steal-attempts-submit="handleStealSubmit"
/>
</div>
</div>
<!-- Toast Notification -->
<Transition
enter-active-class="transition ease-out duration-300"
enter-from-class="opacity-0 translate-y-4"
enter-to-class="opacity-100 translate-y-0"
leave-active-class="transition ease-in duration-200"
leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 translate-y-4"
>
<div
v-if="toastMessage"
class="fixed bottom-8 right-8 bg-green-600 text-white px-6 py-4 rounded-lg shadow-2xl z-50 max-w-md"
>
<div class="flex items-center gap-3">
<span class="text-2xl">✅</span>
<div>
<p class="font-semibold">{{ toastMessage }}</p>
</div>
</div>
</div>
</Transition>
<!-- Footer with Demo Links -->
<div class="mt-12 border-t border-gray-200 dark:border-gray-700 pt-8">
<div class="bg-white dark:bg-gray-800 rounded-xl shadow-lg p-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4">
📚 Other Demo Pages
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<NuxtLink
to="/demo"
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
>
<div class="text-2xl mb-2">🎮</div>
<div class="font-semibold text-gray-900 dark:text-white">Game State Demo</div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Live game state visualization
</div>
</NuxtLink>
<NuxtLink
to="/demo-gameplay"
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
>
<div class="text-2xl mb-2">🎲</div>
<div class="font-semibold text-gray-900 dark:text-white">Gameplay Demo</div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Dice rolling & manual outcomes
</div>
</NuxtLink>
<NuxtLink
to="/demo-substitutions"
class="p-4 rounded-lg border-2 border-gray-300 dark:border-gray-600 hover:border-blue-500 dark:hover:border-blue-400 transition-colors"
>
<div class="text-2xl mb-2">🔄</div>
<div class="font-semibold text-gray-900 dark:text-white">Substitutions Demo</div>
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
Player substitution workflow
</div>
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import type { DefensiveDecision, OffensiveDecision } from '~/types/game'
import ActionButton from '~/components/UI/ActionButton.vue'
import ButtonGroup from '~/components/UI/ButtonGroup.vue'
import ToggleSwitch from '~/components/UI/ToggleSwitch.vue'
import DefensiveSetup from '~/components/Decisions/DefensiveSetup.vue'
import StolenBaseInputs from '~/components/Decisions/StolenBaseInputs.vue'
import OffensiveApproach from '~/components/Decisions/OffensiveApproach.vue'
import DecisionPanel from '~/components/Decisions/DecisionPanel.vue'
// Tab navigation
const activeTab = ref('ui')
const tabs = [
{ id: 'ui', label: 'UI Components', icon: '🎨' },
{ id: 'defensive', label: 'Defensive Setup', icon: '🛡' },
{ id: 'steals', label: 'Stolen Bases', icon: '🏃' },
{ id: 'offensive', label: 'Offensive Approach', icon: '' },
{ id: 'panel', label: 'Decision Panel', icon: '🎯' },
]
// Demo controls
const demoControls = ref({
isActive: true,
hasRunners: true,
phase: 'offensive' as 'idle' | 'defensive' | 'offensive',
})
// Demo state
const demoState = ref({
selectedOption: 'opt1',
selectedVertical: 'a',
toggle1: false,
toggle2: true,
toggle3: false,
toggle4: true,
defensiveSetup: {
infield_depth: 'normal',
outfield_depth: 'normal',
hold_runners: [],
} as DefensiveDecision,
offensiveDecision: {
action: 'swing_away',
} as Omit<OffensiveDecision, 'steal_attempts'>,
stealAttempts: [] as number[],
decisionHistory: [
{
type: 'Defensive' as const,
summary: 'normal infield, normal outfield',
timestamp: '10:45:23',
},
{
type: 'Offensive' as const,
summary: 'Swing Away',
timestamp: '10:45:18',
},
],
})
// Demo runners
const demoRunners = computed(() => {
if (!demoControls.value.hasRunners) {
return { first: null, second: null, third: null }
}
return {
first: 101,
second: 102,
third: 103,
}
})
// Toast notification
const toastMessage = ref('')
const showToast = (message: string) => {
toastMessage.value = message
setTimeout(() => {
toastMessage.value = ''
}, 3000)
}
// Event handlers
const handleDefensiveSubmit = (decision: DefensiveDecision) => {
demoState.value.defensiveSetup = decision
const summary = `${decision.infield_depth} infield, ${decision.outfield_depth} outfield`
demoState.value.decisionHistory.unshift({
type: 'Defensive',
summary,
timestamp: new Date().toLocaleTimeString(),
})
showToast(`Defensive setup submitted: ${summary}`)
}
const handleOffensiveSubmit = (decision: Omit<OffensiveDecision, 'steal_attempts'>) => {
demoState.value.offensiveDecision = decision
const actionLabels: Record<string, string> = {
swing_away: 'Swing Away',
steal: 'Steal',
check_jump: 'Check Jump',
hit_and_run: 'Hit and Run',
sac_bunt: 'Sacrifice Bunt',
squeeze_bunt: 'Squeeze Bunt',
}
const summary = actionLabels[decision.action] || decision.action
demoState.value.decisionHistory.unshift({
type: 'Offensive',
summary,
timestamp: new Date().toLocaleTimeString(),
})
showToast(`Offensive action selected: ${summary}`)
}
const handleStealSubmit = (attempts: number[]) => {
demoState.value.stealAttempts = attempts
const bases = attempts.map(a => {
if (a === 2) return '2nd'
if (a === 3) return '3rd'
if (a === 4) return 'Home'
return a
}).join(', ')
showToast(`Steal attempts: ${bases || 'None'}`)
}
const handleStealCancel = () => {
showToast('Steal attempts canceled')
}
</script>