CLAUDE: Add tabbed Recent/Scoring views to PlayByPlay component

Enhanced PlayByPlay with a tabbed interface:
- "Recent" tab shows plays from current half inning only
- "Scoring" tab shows all plays where runs were scored
- Badge counts on each tab show number of matching plays
- Tab-aware empty states with contextual messaging
- Footer shows total game plays count

Removed unused showFilters prop and showAllPlays toggle.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-11-28 12:18:56 -06:00
parent af598b3dee
commit 57121b62bd

View File

@ -8,15 +8,45 @@
</svg>
Play-by-Play
</h2>
</div>
<!-- Filter Toggle (Mobile) -->
<!-- Tabs -->
<div class="flex border-b border-gray-200 dark:border-gray-700 mb-4">
<button
v-if="showFilters"
class="lg:hidden text-xs px-3 py-1 rounded-full transition-colors"
:class="showAllPlays ? 'bg-gray-200 text-gray-700' : 'bg-primary/10 text-primary font-medium'"
@click="showAllPlays = !showAllPlays"
class="flex-1 py-2 px-4 text-sm font-medium border-b-2 transition-colors"
:class="activeTab === 'recent'
? 'text-primary border-primary'
: 'text-gray-500 dark:text-gray-400 border-transparent hover:text-gray-700 dark:hover:text-gray-300'"
@click="activeTab = 'recent'"
>
{{ showAllPlays ? 'Show Recent' : 'Show All' }}
Recent
<span
v-if="recentPlays.length > 0"
class="ml-1.5 px-1.5 py-0.5 text-xs rounded-full"
:class="activeTab === 'recent'
? 'bg-primary/20 text-primary'
: 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400'"
>
{{ recentPlays.length }}
</span>
</button>
<button
class="flex-1 py-2 px-4 text-sm font-medium border-b-2 transition-colors"
:class="activeTab === 'scoring'
? 'text-green-600 border-green-600'
: 'text-gray-500 dark:text-gray-400 border-transparent hover:text-gray-700 dark:hover:text-gray-300'"
@click="activeTab = 'scoring'"
>
Scoring
<span
v-if="scoringPlays.length > 0"
class="ml-1.5 px-1.5 py-0.5 text-xs rounded-full"
:class="activeTab === 'scoring'
? 'bg-green-100 dark:bg-green-900/30 text-green-600'
: 'bg-gray-200 dark:bg-gray-700 text-gray-600 dark:text-gray-400'"
>
{{ scoringPlays.length }}
</span>
</button>
</div>
@ -28,16 +58,23 @@
>
<!-- Empty State -->
<div
v-if="!plays || plays.length === 0"
v-if="displayedPlays.length === 0"
class="text-center py-12 px-4"
>
<div class="w-16 h-16 mx-auto mb-4 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<svg v-if="activeTab === 'recent'" xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<svg v-else xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 10l7-7m0 0l7 7m-7-7v18" />
</svg>
</div>
<p class="text-gray-500 dark:text-gray-400 font-medium">No plays yet</p>
<p class="text-sm text-gray-400 dark:text-gray-500 mt-1">The game will start soon...</p>
<p class="text-gray-500 dark:text-gray-400 font-medium">
{{ activeTab === 'recent' ? 'No plays this half inning' : 'No runs scored yet' }}
</p>
<p class="text-sm text-gray-400 dark:text-gray-500 mt-1">
{{ activeTab === 'recent' ? 'Plays will appear here as they happen' : 'Scoring plays will appear here' }}
</p>
</div>
<!-- Play Items -->
@ -141,17 +178,14 @@
</TransitionGroup>
</div>
<!-- Load More (if showing recent) -->
<!-- Total play count footer -->
<div
v-if="!showAllPlays && plays && plays.length > limit"
class="text-center mt-4"
v-if="plays && plays.length > 0"
class="text-center mt-4 pt-3 border-t border-gray-200 dark:border-gray-700"
>
<button
class="text-sm text-primary hover:text-blue-700 font-medium transition"
@click="showAllPlays = true"
>
View All {{ plays.length }} Plays
</button>
<span class="text-xs text-gray-400 dark:text-gray-500">
{{ plays.length }} total {{ plays.length === 1 ? 'play' : 'plays' }} this game
</span>
</div>
</div>
</template>
@ -167,7 +201,6 @@ interface Props {
compact?: boolean
scrollable?: boolean
maxHeight?: number
showFilters?: boolean
}
const props = withDefaults(defineProps<Props>(), {
@ -175,15 +208,19 @@ const props = withDefaults(defineProps<Props>(), {
limit: 10,
compact: false,
scrollable: true,
maxHeight: 0,
showFilters: true
maxHeight: 0
})
// Store for player name lookup
// Store for player name lookup and current game state
const gameStore = useGameStore()
// State
const showAllPlays = ref(false)
type TabType = 'recent' | 'scoring'
const activeTab = ref<TabType>('recent')
// Current game state for filtering recent plays
const currentInning = computed(() => gameStore.currentInning)
const currentHalf = computed(() => gameStore.currentHalf)
// Helper functions for player names
const getPlayerName = (lineupId: number | undefined): string | null => {
@ -233,15 +270,30 @@ const hasRunnerMovements = (play: PlayResult): boolean => {
return play.runners_advanced && play.runners_advanced.length > 0
}
// Computed
const displayedPlays = computed(() => {
// Computed - Filtered play lists
const recentPlays = computed(() => {
if (!props.plays || props.plays.length === 0) return []
// Sort by play number descending (most recent first)
const sorted = [...props.plays].sort((a, b) => b.play_number - a.play_number)
// Filter plays from current half inning
return props.plays.filter(play =>
play.inning === currentInning.value && play.half === currentHalf.value
).sort((a, b) => b.play_number - a.play_number)
})
// Return limited or all
return showAllPlays.value ? sorted : sorted.slice(0, props.limit)
const scoringPlays = computed(() => {
if (!props.plays || props.plays.length === 0) return []
// Filter plays where runs were scored
return props.plays.filter(play => play.runs_scored > 0)
.sort((a, b) => b.play_number - a.play_number)
})
const displayedPlays = computed(() => {
if (activeTab.value === 'recent') {
return recentPlays.value
} else {
return scoringPlays.value
}
})
// Methods