|
5 | 5 | <div class="text-secondary text-xs">Loading data...</div> |
6 | 6 | </div> |
7 | 7 | <div v-else class="flex items-center justify-between"> |
| 8 | + |
8 | 9 | <!-- Crypto Prices --> |
9 | | - <div class="flex items-center space-x-8"> |
10 | | - <div v-if="cryptoPrices.length === 0" class="text-secondary text-xs"> |
11 | | - No price data available |
12 | | - </div> |
13 | | - <template v-else> |
14 | | - <div |
15 | | - v-for="crypto in cryptoPrices" |
16 | | - :key="crypto.symbol" |
17 | | - class="flex items-center space-x-2" |
18 | | - > |
| 10 | + <div class="flex items-center space-x-4"> |
| 11 | + <!-- <template v-for="(crypto, index) in displayCryptoPrices" :key="crypto.symbol"> |
| 12 | + <div class="flex items-center space-x-2"> |
| 13 | + <img |
| 14 | + v-if="crypto.icon" |
| 15 | + :src="crypto.icon" |
| 16 | + :alt="crypto.symbol" |
| 17 | + class="w-8 h-8 object-contain rounded-full border border-gray-300 p-0.5" |
| 18 | + @error="handleImageError" |
| 19 | + /> |
19 | 20 | <span class="text-xs font-bold text-primary uppercase tracking-wider">{{ crypto.symbol }}</span> |
20 | 21 | <span class="text-xs text-secondary">{{ formatPrice(crypto.price) }}</span> |
21 | 22 | </div> |
22 | | - </template> |
| 23 | + <div v-if="index < displayCryptoPrices.length - 1" class="h-4 w-px bg-gray-300"></div> |
| 24 | + </template> --> |
23 | 25 | </div> |
24 | 26 |
|
25 | 27 | <!-- Performance Summary --> |
26 | 28 | <div class="flex items-center space-x-6"> |
27 | | - <div class="flex items-center space-x-2"> |
28 | | - <span class="text-xs text-secondary uppercase tracking-wider">Highest:</span> |
29 | | - <span class="text-xs font-bold text-primary">{{ highest.model }}</span> |
30 | | - <span class="text-xs text-secondary">{{ formatPrice(highest.value) }}</span> |
31 | | - <span class="text-xs font-bold text-primary">+{{ highest.change }}%</span> |
| 29 | + <div class="flex items-center space-x-3"> |
| 30 | + <!-- <span class="text-xs text-secondary uppercase tracking-wider">Highest:</span> --> |
| 31 | + <img |
| 32 | + v-if="highestIcon" |
| 33 | + :src="highestIcon" |
| 34 | + :alt="highest.model" |
| 35 | + class="w-8 h-8 object-contain rounded-full border border-gray-300 p-0.5" |
| 36 | + @error="handleImageError" |
| 37 | + /> |
| 38 | + <span class="text-xs text-mono-text-secondary font-bold">{{ highest.model }}</span> |
| 39 | + <span class="text-xs font-bold text-primary">{{ formatPrice(highest.value) }}</span> |
| 40 | + <span :class="[ |
| 41 | + 'text-xs font-bold', |
| 42 | + highest.change >= 0 ? 'text-green-600' : 'text-red-600' |
| 43 | + ]"> |
| 44 | + {{ formatChange(highest.change) }} |
| 45 | + </span> |
32 | 46 | </div> |
| 47 | + |
| 48 | + <!-- <div class="h-4 w-px bg-gray-300"></div> |
| 49 | + |
33 | 50 | <div class="flex items-center space-x-2"> |
34 | 51 | <span class="text-xs text-secondary uppercase tracking-wider">Lowest:</span> |
| 52 | + <img |
| 53 | + v-if="lowestIcon" |
| 54 | + :src="lowestIcon" |
| 55 | + :alt="lowest.model" |
| 56 | + class="w-8 h-8 object-contain rounded-full border border-gray-300 p-0.5" |
| 57 | + @error="handleImageError" |
| 58 | + /> |
35 | 59 | <span class="text-xs font-bold text-primary">{{ lowest.model }}</span> |
36 | 60 | <span class="text-xs text-secondary">{{ formatPrice(lowest.value) }}</span> |
37 | | - <span class="text-xs font-bold text-primary">{{ lowest.change }}%</span> |
38 | | - </div> |
| 61 | + <span :class="[ |
| 62 | + 'text-xs font-bold', |
| 63 | + lowest.change >= 0 ? 'text-green-600' : 'text-red-600' |
| 64 | + ]"> |
| 65 | + {{ formatChange(lowest.change) }} |
| 66 | + </span> |
| 67 | + </div> --> |
| 68 | + |
39 | 69 | </div> |
40 | 70 | </div> |
41 | 71 | </div> |
|
45 | 75 | <script setup lang="ts"> |
46 | 76 | import type { CryptoPrice, ModelPerformance } from '~/types' |
47 | 77 | import { formatNumber } from '~/composables/useNumberFormat' |
| 78 | +import { getModelIcon, getCoinIcon } from '~/config/assets' |
48 | 79 |
|
49 | 80 | interface Props { |
50 | 81 | cryptoPrices: CryptoPrice[] |
51 | 82 | performance: ModelPerformance |
52 | 83 | loading?: boolean |
53 | 84 | } |
54 | 85 |
|
| 86 | +interface CryptoPriceWithIcon extends CryptoPrice { |
| 87 | + icon: string |
| 88 | +} |
| 89 | +
|
55 | 90 | const props = defineProps<Props>() |
56 | 91 |
|
57 | 92 | const highest = computed(() => props.performance.highest) |
58 | 93 | const lowest = computed(() => props.performance.lowest) |
59 | 94 |
|
| 95 | +// Resolve icon paths from coin symbols returned by API |
| 96 | +const highestIcon = computed(() => { |
| 97 | + if (!highest.value?.icon) return '' |
| 98 | + // API returns coin symbol, resolve to icon path |
| 99 | + return getModelIcon(highest.value.icon) |
| 100 | +}) |
| 101 | +
|
| 102 | +const lowestIcon = computed(() => { |
| 103 | + if (!lowest.value?.icon) return '' |
| 104 | + // API returns coin symbol, resolve to icon path |
| 105 | + return getModelIcon(lowest.value.icon) |
| 106 | +}) |
| 107 | +
|
| 108 | +// Dummy crypto prices for display when no data is available |
| 109 | +const dummyCryptoPrices: CryptoPriceWithIcon[] = [ |
| 110 | + { symbol: 'BTC', name: 'Bitcoin', price: 104577.50, icon: getCoinIcon('BTC') }, |
| 111 | + { symbol: 'ETH', name: 'Ethereum', price: 3505.75, icon: getCoinIcon('ETH') }, |
| 112 | + { symbol: 'SOL', name: 'Solana', price: 158.56, icon: getCoinIcon('SOL') }, |
| 113 | + { symbol: 'BNB', name: 'Binance Coin', price: 952.14, icon: getCoinIcon('BNB') }, |
| 114 | + { symbol: 'DOGE', name: 'Dogecoin', price: 0.1642, icon: getCoinIcon('DOGE') }, |
| 115 | + { symbol: 'XRP', name: 'XRP', price: 2.27, icon: getCoinIcon('XRP') }, |
| 116 | +] |
| 117 | +
|
| 118 | +// Display crypto prices with icons - use dummy data if no real data |
| 119 | +const displayCryptoPrices = computed<CryptoPriceWithIcon[]>(() => { |
| 120 | + if (props.cryptoPrices.length > 0) { |
| 121 | + return props.cryptoPrices.map(crypto => ({ |
| 122 | + ...crypto, |
| 123 | + icon: getModelIcon(crypto.symbol) |
| 124 | + })) |
| 125 | + } |
| 126 | + return dummyCryptoPrices |
| 127 | +}) |
| 128 | +
|
60 | 129 | const formatPrice = (price: number): string => { |
61 | 130 | const formatted = formatNumber(price) |
62 | 131 | return `$${formatted}` |
63 | 132 | } |
| 133 | +
|
| 134 | +const formatChange = (change: number): string => { |
| 135 | + const sign = change >= 0 ? '+' : '' |
| 136 | + return `${sign}${change.toFixed(2)}%` |
| 137 | +} |
| 138 | +
|
| 139 | +const handleImageError = (event: Event) => { |
| 140 | + const img = event.target as HTMLImageElement |
| 141 | + img.style.display = 'none' |
| 142 | +} |
64 | 143 | </script> |
65 | 144 |
|
66 | 145 | <style scoped> |
|
0 commit comments