Jimin Huang commited on
Commit
7e33fb5
·
1 Parent(s): 7240d16

Change settings

Browse files
src/components/CompareChartE.vue CHANGED
@@ -361,7 +361,7 @@ export default defineComponent({
361
 
362
  this.option = {
363
  animation: true,
364
- grid: { left: 56, right: 240, top: 16, bottom: 60 },
365
  tooltip: {
366
  trigger: 'axis',
367
  axisPointer: { type: 'line' },
@@ -373,7 +373,7 @@ export default defineComponent({
373
  : v.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
374
  }
375
  },
376
- legend: { type: 'scroll', bottom: 8, icon: 'roundRect', itemGap: 10, data: legend },
377
  xAxis: { type: 'time' },
378
  yAxis: this.mode === 'pct'
379
  ? {
 
361
 
362
  this.option = {
363
  animation: true,
364
+ grid: { left: 64, right: 200, top: 8, bottom: 52 },
365
  tooltip: {
366
  trigger: 'axis',
367
  axisPointer: { type: 'line' },
 
373
  : v.toLocaleString(undefined, { style: 'currency', currency: 'USD', maximumFractionDigits: 2 })
374
  }
375
  },
376
+ legend: { show: false },
377
  xAxis: { type: 'time' },
378
  yAxis: this.mode === 'pct'
379
  ? {
src/views/LiveView.vue CHANGED
@@ -1,53 +1,65 @@
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
 
@@ -66,13 +78,12 @@ const agents = ref([])
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 ----
@@ -94,7 +105,7 @@ const winners = computed(() => {
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,
@@ -110,54 +121,109 @@ const winnersSorted = computed(() => [...winners.value].sort((a,b) => score(b) -
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>
 
1
  <template>
2
+ <div class="live">
3
+ <!-- Toolbar: assets + mode -->
4
+ <header class="toolbar">
5
+ <div class="toolbar__left">
6
+ <AssetTabs v-model="asset" />
7
+ </div>
8
+ <div class="toolbar__right">
9
+ <div class="mode">
10
+ <button
11
+ class="mode__btn"
12
+ :class="{ 'is-active': mode==='usd' }"
13
+ @click="mode='usd'">
14
+ $
15
+ </button>
16
+ <button
17
+ class="mode__btn"
18
+ :class="{ 'is-active': mode==='pct' }"
19
+ @click="mode='pct'">
20
+ %
21
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  </div>
23
+ </div>
24
+ </header>
25
+
26
+ <!-- Chart panel -->
27
+ <section class="panel panel--chart">
28
+ <CompareChartE
29
+ v-if="winnersForChart.length"
30
+ :selected="winnersForChart"
31
+ :visible="true"
32
+ :mode="mode"
33
+ />
34
+ <div v-else class="empty">
35
+ No data for <strong>{{ asset }}</strong> yet. Check Supabase runs or try another asset.
36
+ </div>
37
+ </section>
38
+
39
+ <!-- Winner cards -->
40
+ <section v-if="winnersSorted.length" class="panel panel--cards">
41
+ <div class="cards">
42
+ <div
43
+ v-for="row in winnersSorted"
44
+ :key="row.agent_name+'|'+row.asset+'|'+row.model"
45
+ class="card">
46
+ <div class="card__left">
47
+ <div class="card__title" :title="row.agent_name">{{ row.agent_name }}</div>
48
+ <div class="card__sub" :title="row.model">{{ row.model }}</div>
49
+ </div>
50
+ <div class="card__right">
51
+ <div class="card__balance">{{ fmtUSD(row.balance) }}</div>
52
+ <div class="card__sub">
53
+ EOD {{
54
+ row.end_date
55
+ ? new Date(row.end_date).toLocaleDateString()
56
+ : (row.last_nav_ts ? new Date(row.last_nav_ts).toLocaleDateString() : '-')
57
+ }}
58
+ </div>
59
  </div>
60
  </div>
61
  </div>
62
+ </section>
63
  </div>
64
  </template>
65
 
 
78
  onMounted(async () => {
79
  try {
80
  if (!dataService.loaded && !dataService.loading) {
81
+ await dataService.load(false)
82
  }
83
  } catch (e) {
84
  console.error('LiveView: dataService.load failed', e)
85
  }
86
  agents.value = Array.isArray(dataService.tableRows) ? dataService.tableRows : []
 
87
  })
88
 
89
  // ---- helpers ----
 
105
  return [...byAgent.values()]
106
  })
107
 
108
+ // Series selections for the chart
109
  const winnersForChart = computed(() =>
110
  winners.value.map(w => ({
111
  agent_name: w.agent_name,
 
121
  </script>
122
 
123
  <style scoped>
124
+ /* page container */
125
+ .live {
126
+ max-width: 1280px;
127
+ margin: 0 auto;
128
+ padding: 12px 20px 28px;
129
+ }
130
 
131
+ /* sticky toolbar */
132
+ .toolbar {
133
+ position: sticky;
134
+ top: 0;
135
+ z-index: 10;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: space-between;
139
+ gap: 16px;
140
+ padding: 8px 0 10px;
141
+ background: #fff; /* keeps tabs visible while scrolling */
142
+ }
143
+ .toolbar__left { min-width: 0; }
144
+ .toolbar__right { display: flex; align-items: center; gap: 10px; }
145
+
146
+ /* $ / % toggle */
147
+ .mode { display: inline-flex; gap: 8px; }
148
+ .mode__btn {
149
+ height: 32px;
150
+ min-width: 40px;
151
+ padding: 0 12px;
152
+ border-radius: 10px;
153
+ border: 1px solid #D6DAE1;
154
  background: #fff;
155
  font-weight: 700;
156
+ color: #0F172A;
157
+ transition: all .12s ease;
158
  }
159
+ .mode__btn:hover { transform: translateY(-1px); }
160
+ .mode__btn.is-active {
161
+ background: #0F172A;
162
  color: #fff;
163
+ border-color: #0F172A;
164
  }
165
 
166
+ /* panels */
167
+ .panel {
168
+ background: #fff;
169
+ border: 1px solid #EDF0F4;
170
+ border-radius: 14px;
171
+ }
172
+ .panel--chart { padding: 10px 10px 2px; }
173
+ .panel--cards { padding: 12px; }
174
+
175
  /* empty state */
176
  .empty {
177
+ padding: 14px;
178
+ border: 1px dashed #D6DAE1;
179
  border-radius: 12px;
180
+ color: #6B7280;
181
+ font-size: .92rem;
182
+ }
183
+
184
+ /* cards grid */
185
+ .cards {
186
+ display: grid;
187
+ gap: 12px;
188
+ grid-template-columns: repeat(1, minmax(0, 1fr));
189
  }
190
+ @media (min-width: 640px) { .cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
191
+ @media (min-width: 1024px) { .cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
192
+ @media (min-width: 1280px) { .cards { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
193
 
194
+ /* card */
195
  .card {
196
+ display: grid;
197
+ grid-template-columns: 1fr auto;
198
  align-items: center;
199
+ gap: 10px 12px;
200
  padding: 14px 16px;
201
+ border: 1px solid #EEF1F6;
202
+ border-radius: 14px;
 
203
  background: #fff;
204
+ box-shadow: 0 1px 2px rgba(0,0,0,.04);
205
+ }
206
+ .card__title {
207
+ font-weight: 700;
208
+ color: #0F172A;
209
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
210
+ }
211
+ .card__sub {
212
+ font-size: 12px;
213
+ color: #5B6476;
214
+ opacity: .8;
215
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
216
  }
217
+ .card__balance {
218
+ font-weight: 800;
219
+ font-size: 18px;
220
+ line-height: 1.1;
221
+ color: #0F172A;
222
+ text-align: right;
 
 
 
223
  }
224
+ .card__left { min-width: 0; }
225
+ .card__right { display: grid; gap: 4px; justify-items: end; }
226
+
227
+ /* small utility */
228
+ strong { font-weight: 700; }
229
  </style>