311 lines
7.1 KiB
Vue
311 lines
7.1 KiB
Vue
<template>
|
|
<div class="workout-card" @click="$emit('click', workout)">
|
|
<div class="workout-card-header">
|
|
<div class="workout-badges">
|
|
<div class="workout-type" :class="`type-${workout.type}`">
|
|
{{ formatType(workout.type) }}
|
|
</div>
|
|
<div v-if="workout.difficulty" class="workout-difficulty" :class="`difficulty-${workout.difficulty}`">
|
|
{{ formatDifficulty(workout.difficulty) }}
|
|
</div>
|
|
</div>
|
|
<FavoriteButton
|
|
v-if="showFavorite"
|
|
:is-favorited="isFavorited"
|
|
@toggle="$emit('toggle-favorite', workout.id)"
|
|
@click.stop
|
|
/>
|
|
</div>
|
|
|
|
<h3 class="workout-name">{{ workout.name }}</h3>
|
|
<p class="workout-description">{{ truncateDescription(workout.description) }}</p>
|
|
|
|
<div class="workout-stats">
|
|
<div class="stat">
|
|
<svg viewBox="0 0 24 24" fill="none">
|
|
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
|
<path d="M12 6V12L16 14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
</svg>
|
|
<span>{{ workout.duration_minutes }} min</span>
|
|
</div>
|
|
<div class="stat">
|
|
<svg viewBox="0 0 24 24" fill="none">
|
|
<path d="M13 2L3 14H12L11 22L21 10H12L13 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
<span>IF {{ formatIF(workout.intensity_factor) }}</span>
|
|
</div>
|
|
<div class="stat">
|
|
<svg viewBox="0 0 24 24" fill="none">
|
|
<path d="M22 12H18L15 21L9 3L6 12H2" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
<span>TSS {{ workout.tss || '—' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="workout-footer">
|
|
<StarRating :rating="workout.average_rating || 0" :readonly="true" size="small" />
|
|
<span class="rating-count" v-if="workout.rating_count">({{ workout.rating_count }})</span>
|
|
<div class="use-count" v-if="workout.use_count">
|
|
<svg viewBox="0 0 24 24" fill="none">
|
|
<path d="M17 21V19C17 17.9391 16.5786 16.9217 15.8284 16.1716C15.0783 15.4214 14.0609 15 13 15H5C3.93913 15 2.92172 15.4214 2.17157 16.1716C1.42143 16.9217 1 17.9391 1 19V21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<circle cx="9" cy="7" r="4" stroke="currentColor" stroke-width="2"/>
|
|
</svg>
|
|
<span>{{ workout.use_count }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="workout.is_system" class="system-badge">System</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { defineProps, defineEmits } from 'vue'
|
|
import StarRating from './StarRating.vue'
|
|
import FavoriteButton from './FavoriteButton.vue'
|
|
|
|
defineProps({
|
|
workout: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
isFavorited: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
showFavorite: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
})
|
|
|
|
defineEmits(['click', 'toggle-favorite'])
|
|
|
|
const typeLabels = {
|
|
endurance: 'Endurance',
|
|
tempo: 'Tempo',
|
|
threshold: 'Threshold',
|
|
vo2max: 'VO2 Max',
|
|
sprint: 'Sprint',
|
|
recovery: 'Recovery',
|
|
climbing: 'Climbing',
|
|
interval: 'Interval',
|
|
freeride: 'Free Ride',
|
|
race: 'Race'
|
|
}
|
|
|
|
const difficultyLabels = {
|
|
beginner: 'Beginner',
|
|
intermediate: 'Intermediate',
|
|
advanced: 'Advanced',
|
|
expert: 'Expert'
|
|
}
|
|
|
|
function formatType(type) {
|
|
return typeLabels[type] || type
|
|
}
|
|
|
|
function formatDifficulty(difficulty) {
|
|
return difficultyLabels[difficulty] || difficulty
|
|
}
|
|
|
|
function formatIF(value) {
|
|
if (!value) return '—'
|
|
return value.toFixed(2)
|
|
}
|
|
|
|
function truncateDescription(text) {
|
|
if (!text) return ''
|
|
return text.length > 100 ? text.substring(0, 100) + '...' : text
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.workout-card {
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-lg);
|
|
padding: var(--spacing-lg);
|
|
cursor: pointer;
|
|
transition: all var(--transition-base);
|
|
position: relative;
|
|
}
|
|
|
|
.workout-card:hover {
|
|
border-color: var(--color-primary);
|
|
box-shadow: var(--shadow-md);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.workout-card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: var(--spacing-sm);
|
|
}
|
|
|
|
.workout-badges {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.workout-type,
|
|
.workout-difficulty {
|
|
display: inline-block;
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
border-radius: var(--radius-sm);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: var(--font-weight-semibold);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.type-endurance {
|
|
background: rgba(46, 204, 113, 0.15);
|
|
color: #27ae60;
|
|
}
|
|
|
|
.type-tempo {
|
|
background: rgba(52, 152, 219, 0.15);
|
|
color: #2980b9;
|
|
}
|
|
|
|
.type-threshold {
|
|
background: rgba(241, 196, 15, 0.15);
|
|
color: #f39c12;
|
|
}
|
|
|
|
.type-vo2max {
|
|
background: rgba(231, 76, 60, 0.15);
|
|
color: #c0392b;
|
|
}
|
|
|
|
.type-sprint {
|
|
background: rgba(155, 89, 182, 0.15);
|
|
color: #8e44ad;
|
|
}
|
|
|
|
.type-recovery {
|
|
background: rgba(149, 165, 166, 0.15);
|
|
color: #7f8c8d;
|
|
}
|
|
|
|
.type-climbing {
|
|
background: rgba(230, 126, 34, 0.15);
|
|
color: #d35400;
|
|
}
|
|
|
|
.type-interval {
|
|
background: rgba(26, 188, 156, 0.15);
|
|
color: #16a085;
|
|
}
|
|
|
|
.type-freeride {
|
|
background: rgba(52, 73, 94, 0.15);
|
|
color: #2c3e50;
|
|
}
|
|
|
|
.type-race {
|
|
background: rgba(192, 57, 43, 0.15);
|
|
color: #c0392b;
|
|
}
|
|
|
|
.difficulty-beginner {
|
|
background: rgba(46, 204, 113, 0.1);
|
|
color: #27ae60;
|
|
}
|
|
|
|
.difficulty-intermediate {
|
|
background: rgba(241, 196, 15, 0.1);
|
|
color: #f39c12;
|
|
}
|
|
|
|
.difficulty-advanced {
|
|
background: rgba(230, 126, 34, 0.1);
|
|
color: #d35400;
|
|
}
|
|
|
|
.difficulty-expert {
|
|
background: rgba(231, 76, 60, 0.1);
|
|
color: #c0392b;
|
|
}
|
|
|
|
.workout-name {
|
|
margin: 0 0 var(--spacing-xs);
|
|
font-size: var(--font-size-lg);
|
|
font-weight: var(--font-weight-semibold);
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.workout-description {
|
|
margin: 0 0 var(--spacing-md);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-secondary);
|
|
line-height: 1.5;
|
|
min-height: 42px;
|
|
}
|
|
|
|
.workout-stats {
|
|
display: flex;
|
|
gap: var(--spacing-lg);
|
|
margin-bottom: var(--spacing-md);
|
|
padding-bottom: var(--spacing-md);
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
.stat {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.stat svg {
|
|
width: 16px;
|
|
height: 16px;
|
|
}
|
|
|
|
.workout-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.rating-count {
|
|
font-size: var(--font-size-xs);
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.use-count {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
margin-left: auto;
|
|
font-size: var(--font-size-xs);
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.use-count svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
|
|
.system-badge {
|
|
position: absolute;
|
|
top: var(--spacing-md);
|
|
right: var(--spacing-md);
|
|
padding: 2px var(--spacing-xs);
|
|
background: var(--color-primary);
|
|
color: white;
|
|
font-size: var(--font-size-xs);
|
|
font-weight: var(--font-weight-medium);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.workout-card-header + .system-badge {
|
|
top: var(--spacing-lg);
|
|
right: 48px;
|
|
}
|
|
</style>
|