Jimin Huang commited on
Commit
e6fb472
·
1 Parent(s): ca4fd3d

Change settings

Browse files
Files changed (1) hide show
  1. src/views/LiveView.vue +129 -43
src/views/LiveView.vue CHANGED
@@ -1,16 +1,88 @@
1
- <!-- src/views/LiveView.vue -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  <script setup>
3
  import { ref, computed, onMounted } from 'vue'
4
- import CompareChartE from '../components/CompareChartE.vue'
5
  import AssetTabs from '../components/AssetTabs.vue'
6
- import { dataService } from '../lib/dataService.js'
 
7
 
8
- const ASSETS = ['BTC','ETH','SOL','BNB','DOGE','XRP']
9
- const asset = ref(ASSETS[0])
 
10
  const agents = ref([])
11
 
12
- function score(row){ return typeof row.balance === 'number' ? row.balance : -Infinity }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
14
  const winners = computed(() => {
15
  const rows = (agents.value || []).filter(r => r.asset === asset.value)
16
  const byAgent = new Map()
@@ -22,6 +94,7 @@ const winners = computed(() => {
22
  return [...byAgent.values()]
23
  })
24
 
 
25
  const winnersForChart = computed(() =>
26
  winners.value.map(w => ({
27
  agent_name: w.agent_name,
@@ -32,46 +105,59 @@ const winnersForChart = computed(() =>
32
  }))
33
  )
34
 
 
35
  const winnersSorted = computed(() => [...winners.value].sort((a,b) => score(b) - score(a)))
36
-
37
- const fmtUSD = n => (n ?? 0).toLocaleString(undefined,{ style:'currency', currency:'USD', maximumFractionDigits:2 })
38
-
39
- onMounted(async () => {
40
- try {
41
- // run the service load if needed (this populates tableRows + caches decisions)
42
- if (!dataService.loaded) await dataService.load(false)
43
- } catch (e) {
44
- console.error('LiveView: dataService.load failed', e)
45
- }
46
- // copy rows to local reactive state
47
- agents.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
48
- console.log('LiveView: loaded rows =', agents.value.length)
49
- })
50
  </script>
51
 
52
- <template>
53
- <div class="p-4 space-y-4">
54
- <AssetTabs v-model="asset" />
55
 
56
- <CompareChartE v-if="winnersForChart.length" :selected="winnersForChart" :visible="true" />
57
- <div v-else class="p-4 text-sm text-gray-500 border rounded-lg">
58
- No data for {{ asset }} yet. Make sure Supabase has decisions/runs for this asset and that
59
- <code>dataService.load()</code> completes. Check console for “LiveView: loaded rows” count.
60
- </div>
 
 
 
 
 
 
 
 
 
 
 
61
 
 
 
 
 
 
 
 
 
62
 
63
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
64
- <div v-for="row in winnersSorted" :key="row.agent_name+'|'+row.asset+'|'+row.model"
65
- class="p-4 rounded-2xl border shadow-sm flex justify-between items-center">
66
- <div class="min-w-0">
67
- <div class="font-semibold truncate">{{ row.agent_name }}</div>
68
- <div class="text-xs opacity-70 truncate">{{ row.model }}</div>
69
- </div>
70
- <div class="text-right">
71
- <div class="font-bold text-xl leading-6">{{ fmtUSD(row.balance) }}</div>
72
- <div class="text-xs opacity-70">EOD {{ (row.end_date || row.last_nav_ts) ? new Date(row.end_date || row.last_nav_ts).toLocaleDateString() : '-' }}</div>
73
- </div>
74
- </div>
75
- </div>
76
- </div>
77
- </template>
 
 
 
 
 
 
 
 
 
1
+ <template>
2
+ <div class="live-view px-4 py-3 space-y-3">
3
+ <!-- Asset tabs (logos, no prices) -->
4
+ <AssetTabs v-model="asset" />
5
+
6
+ <!-- $ / % mode switch -->
7
+ <div class="flex items-center gap-2">
8
+ <button
9
+ class="switch-btn"
10
+ :class="mode==='usd' ? 'switch-btn--active' : ''"
11
+ @click="mode='usd'">
12
+ $
13
+ </button>
14
+ <button
15
+ class="switch-btn"
16
+ :class="mode==='pct' ? 'switch-btn--active' : ''"
17
+ @click="mode='pct'">
18
+ %
19
+ </button>
20
+ </div>
21
+
22
+ <!-- Chart -->
23
+ <CompareChartE
24
+ v-if="winnersForChart.length"
25
+ :selected="winnersForChart"
26
+ :visible="true"
27
+ :mode="mode"
28
+ />
29
+ <div v-else class="empty">
30
+ No data for <strong>{{ asset }}</strong> yet. Check Supabase runs or try another asset.
31
+ </div>
32
+
33
+ <!-- Winner cards (best balance per agent) -->
34
+ <div v-if="winnersSorted.length" class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
35
+ <div
36
+ v-for="row in winnersSorted"
37
+ :key="row.agent_name+'|'+row.asset+'|'+row.model"
38
+ class="card">
39
+ <div class="min-w-0">
40
+ <div class="card-title truncate">{{ row.agent_name }}</div>
41
+ <div class="card-sub truncate">{{ row.model }}</div>
42
+ </div>
43
+ <div class="text-right">
44
+ <div class="card-balance">{{ fmtUSD(row.balance) }}</div>
45
+ <div class="card-sub">
46
+ EOD {{ row.end_date ? new Date(row.end_date).toLocaleDateString() : (row.last_nav_ts ? new Date(row.last_nav_ts).toLocaleDateString() : '-') }}
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </div>
52
+ </template>
53
+
54
  <script setup>
55
  import { ref, computed, onMounted } from 'vue'
 
56
  import AssetTabs from '../components/AssetTabs.vue'
57
+ import CompareChartE from '../components/CompareChartE.vue'
58
+ import { dataService } from '../lib/dataService'
59
 
60
+ // ---- state ----
61
+ const mode = ref('usd') // 'usd' | 'pct'
62
+ const asset = ref('BTC')
63
  const agents = ref([])
64
 
65
+ // ---- load once, copy rows from dataService ----
66
+ onMounted(async () => {
67
+ try {
68
+ if (!dataService.loaded && !dataService.loading) {
69
+ await dataService.load(false) // uses existing pipeline; populates tableRows/decisions cache
70
+ }
71
+ } catch (e) {
72
+ console.error('LiveView: dataService.load failed', e)
73
+ }
74
+ agents.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
75
+ // if first asset isn’t present, AssetTabs will nudge selection to the first available
76
+ })
77
+
78
+ // ---- helpers ----
79
+ function score(row) {
80
+ return typeof row.balance === 'number' ? row.balance : -Infinity
81
+ }
82
+ const fmtUSD = (n) =>
83
+ (n ?? 0).toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
84
 
85
+ // Best model per agent for the selected asset (by balance)
86
  const winners = computed(() => {
87
  const rows = (agents.value || []).filter(r => r.asset === asset.value)
88
  const byAgent = new Map()
 
94
  return [...byAgent.values()]
95
  })
96
 
97
+ // Series selections for the chart (reuse compare logic in CompareChartE)
98
  const winnersForChart = computed(() =>
99
  winners.value.map(w => ({
100
  agent_name: w.agent_name,
 
105
  }))
106
  )
107
 
108
+ // Bottom cards sorted by balance
109
  const winnersSorted = computed(() => [...winners.value].sort((a,b) => score(b) - score(a)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  </script>
111
 
112
+ <style scoped>
113
+ .live-view { max-width: 1400px; margin: 0 auto; }
 
114
 
115
+ /* toggle buttons */
116
+ .switch-btn {
117
+ padding: 6px 10px;
118
+ border: 1px solid #1f2937; /* gray-800 */
119
+ border-radius: 8px;
120
+ background: #fff;
121
+ font-weight: 700;
122
+ line-height: 1;
123
+ transition: background .12s ease, color .12s ease, transform .06s ease;
124
+ }
125
+ .switch-btn:hover { transform: translateY(-1px); }
126
+ .switch-btn--active {
127
+ background: #111827; /* gray-900 */
128
+ color: #fff;
129
+ border-color: #111827;
130
+ }
131
 
132
+ /* empty state */
133
+ .empty {
134
+ padding: 12px 14px;
135
+ border: 1px dashed #d1d5db; /* gray-300 */
136
+ border-radius: 12px;
137
+ color: #6b7280; /* gray-500 */
138
+ font-size: 0.9rem;
139
+ }
140
 
141
+ /* winner cards */
142
+ .card {
143
+ display: flex;
144
+ justify-content: space-between;
145
+ align-items: center;
146
+ gap: 12px;
147
+ padding: 14px 16px;
148
+ border: 1px solid #e5e7eb; /* gray-200 */
149
+ border-radius: 16px;
150
+ box-shadow: 0 1px 2px rgba(0,0,0,0.04);
151
+ background: #fff;
152
+ }
153
+ .card-title { font-weight: 600; }
154
+ .card-sub { font-size: 12px; opacity: 0.65; }
155
+ .card-balance { font-weight: 800; font-size: 18px; line-height: 1.1; }
156
+
157
+ /* small util */
158
+ .truncate {
159
+ overflow: hidden;
160
+ text-overflow: ellipsis;
161
+ white-space: nowrap;
162
+ }
163
+ </style>