🎮 Add tournament detail component for displaying Challonge tournament information

This commit is contained in:
2026-01-29 05:34:02 +00:00
parent 66ad148ccc
commit 5dafcfa232

View File

@@ -0,0 +1,217 @@
<!--
TournamentDetail Component
Displays detailed tournament information in an expandable section.
Features:
- Expandable/collapsible details view
- JSON display of full tournament data
- Loading state for detail fetching
- Error handling
- Formatted JSON output
Props:
- tournamentDetails: Object containing full tournament data
- loading: Boolean for loading state
- error: String for error message
- isExpanded: Boolean to control visibility
Usage:
<TournamentDetail
:tournament-details="details"
:loading="loading"
:error="error"
:is-expanded="true"
/>
-->
<template>
<div v-if="isExpanded" class="tournament-detail">
<!-- Loading State -->
<div v-if="loading" class="detail-status loading">
<div class="spinner"></div>
<span>Loading tournament details...</span>
</div>
<!-- Error State -->
<div v-else-if="error" class="detail-status error">
<span class="error-icon"></span>
<span>{{ error }}</span>
</div>
<!-- Details Content -->
<div v-else-if="tournamentDetails" class="detail-content">
<div class="detail-header">
<h4>Full Tournament Details</h4>
</div>
<pre class="detail-json">{{ formattedDetails }}</pre>
</div>
<!-- Empty State (details expanded but no data) -->
<div v-else class="detail-status empty">
<span>No details available</span>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue';
/**
* Props for TournamentDetail component
*/
const props = defineProps({
tournamentDetails: {
type: Object,
default: null
},
loading: {
type: Boolean,
default: false
},
error: {
type: String,
default: null
},
isExpanded: {
type: Boolean,
default: false
}
});
/**
* Format tournament details as pretty-printed JSON
*/
const formattedDetails = computed(() => {
if (!props.tournamentDetails) return '';
return JSON.stringify(props.tournamentDetails, null, 2);
});
</script>
<style scoped>
.tournament-detail {
margin-top: 1rem;
border-top: 2px solid #e0e0e0;
padding-top: 1rem;
animation: slideDown 0.2s ease-out;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.detail-status {
padding: 1rem;
border-radius: 6px;
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.9rem;
}
.detail-status.loading {
background: #e3f2fd;
color: #1976d2;
}
.detail-status.error {
background: #ffebee;
color: #c62828;
}
.detail-status.empty {
background: #f5f5f5;
color: #666;
justify-content: center;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid #1976d2;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.error-icon {
font-size: 1.25rem;
}
.detail-content {
background: #f8f9fa;
border-radius: 6px;
overflow: hidden;
}
.detail-header {
background: #667eea;
color: white;
padding: 0.75rem 1rem;
margin: 0;
}
.detail-header h4 {
margin: 0;
font-size: 1rem;
font-weight: 600;
}
.detail-json {
margin: 0;
padding: 1rem;
background: #282c34;
color: #abb2bf;
font-family: 'Fira Code', 'Monaco', 'Menlo', monospace;
font-size: 0.85rem;
line-height: 1.5;
overflow-x: auto;
max-height: 600px;
overflow-y: auto;
}
/* JSON Syntax Highlighting (approximate) */
.detail-json {
/* Numbers */
background: linear-gradient(to bottom, #282c34 0%, #282c34 100%);
}
/* Scrollbar styling for detail-json */
.detail-json::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.detail-json::-webkit-scrollbar-track {
background: #1e2127;
}
.detail-json::-webkit-scrollbar-thumb {
background: #4b5362;
border-radius: 4px;
}
.detail-json::-webkit-scrollbar-thumb:hover {
background: #5c6370;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.detail-json {
font-size: 0.75rem;
max-height: 400px;
}
}
</style>