From d5359691b1ff76d3c52c116346b7128bc9c5493a Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Wed, 27 May 2026 17:22:53 +0000 Subject: [PATCH] feat(dashboard): show actions + convictions + outlook on Videos cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API already returns top_tickers as [{symbol, action, conviction}] but the dashboard's VideoSummary type was string[] — so the rich data was dropped at the type boundary and the cards only showed bare ticker symbols. Changes: - VideoSummary.top_tickers: string[] → VideoTopTicker[] - Videos.tsx card now renders: * outlook pill (bullish/bearish/neutral/mixed, color-coded) * action-colored chip per ticker (buy=green, sell=red, watch=yellow, avoid=rose, hold=slate) with conviction% inline * one-line summary excerpt - Home.tsx (only other top_tickers consumer) updated to use .symbol instead of treating each entry as a string Effect: skim /meet-kevin/videos to see at a glance "this video says buy NVDA 85% + watch SPCX 65%, outlook bullish" without clicking in. --- dashboard/src/pages/meetKevin/Home.tsx | 9 +++-- dashboard/src/pages/meetKevin/Videos.tsx | 47 +++++++++++++++++++++--- dashboard/src/types/meetKevin.ts | 8 +++- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/dashboard/src/pages/meetKevin/Home.tsx b/dashboard/src/pages/meetKevin/Home.tsx index be1daa9..44e8b4c 100644 --- a/dashboard/src/pages/meetKevin/Home.tsx +++ b/dashboard/src/pages/meetKevin/Home.tsx @@ -118,12 +118,13 @@ export default function MeetKevinHome() { {video.outlook} )} - {video.top_tickers.slice(0, 5).map((ticker: string) => ( + {video.top_tickers.slice(0, 5).map((t) => ( - ${ticker} + {t.symbol} ))} diff --git a/dashboard/src/pages/meetKevin/Videos.tsx b/dashboard/src/pages/meetKevin/Videos.tsx index e11a508..45c0341 100644 --- a/dashboard/src/pages/meetKevin/Videos.tsx +++ b/dashboard/src/pages/meetKevin/Videos.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import meetKevinApi from '../../api/meetKevin'; -import type { VideoStatus } from '../../types/meetKevin'; +import type { MarketOutlook, TickerAction, VideoStatus } from '../../types/meetKevin'; const STATUS_OPTIONS: { value: VideoStatus | ''; label: string }[] = [ { value: '', label: 'All' }, @@ -19,6 +19,21 @@ function statusColor(status: VideoStatus): string { return 'text-yellow-300'; } +const ACTION_CHIP: Record = { + buy: 'bg-green-600/30 text-green-300 border-green-500/40', + sell: 'bg-red-600/30 text-red-300 border-red-500/40', + hold: 'bg-slate-600/30 text-slate-300 border-slate-500/40', + watch: 'bg-yellow-600/30 text-yellow-200 border-yellow-500/40', + avoid: 'bg-rose-600/40 text-rose-200 border-rose-500/40', +}; + +const OUTLOOK_PILL: Record = { + bullish: 'bg-green-600/20 text-green-300 border-green-500/40', + bearish: 'bg-red-600/20 text-red-300 border-red-500/40', + neutral: 'bg-slate-600/20 text-slate-300 border-slate-500/40', + mixed: 'bg-purple-600/20 text-purple-300 border-purple-500/40', +}; + export default function MeetKevinVideos() { const [status, setStatus] = useState(''); const [q, setQ] = useState(''); @@ -146,18 +161,38 @@ export default function MeetKevinVideos() {

)} + {video.outlook && ( +
+ + {video.outlook} + +
+ )} + {video.top_tickers.length > 0 && ( -
- {video.top_tickers.map((ticker) => ( +
+ {video.top_tickers.map((t) => ( - {ticker} + {t.symbol} + + {Math.round(t.conviction * 100)} + ))}
)} + + {video.one_line_summary && ( +

+ {video.one_line_summary} +

+ )}
))} diff --git a/dashboard/src/types/meetKevin.ts b/dashboard/src/types/meetKevin.ts index 8a67325..3c04ef4 100644 --- a/dashboard/src/types/meetKevin.ts +++ b/dashboard/src/types/meetKevin.ts @@ -14,6 +14,12 @@ export type VideoStatus = | 'failed' | 'skipped'; +export interface VideoTopTicker { + symbol: string; + action: TickerAction; + conviction: number; +} + export interface VideoSummary { id: number; youtube_video_id: string; @@ -23,7 +29,7 @@ export interface VideoSummary { thumbnail_url: string | null; status: VideoStatus; failure_reason: string | null; - top_tickers: string[]; + top_tickers: VideoTopTicker[]; outlook: MarketOutlook | null; one_line_summary: string | null; }