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:
parent
af598b3dee
commit
57121b62bd
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user