Benzinga Market Data: Fetches Benzinga news, analyst ratings, earnings, guidance, dividends, splits, IPO, and economic calendars via API, with a news list view.
| 1 | import { useBridge, useData, usePublish, type PanelRef } from '@jaspers-ai/sdk' |
| 2 | import './styles.css' |
| 3 | |
| 4 | interface Story { |
| 5 | id: number |
| 6 | created: string |
| 7 | title: string |
| 8 | teaser: string |
| 9 | url: string |
| 10 | tickers: string |
| 11 | [field: string]: unknown |
| 12 | } |
| 13 | |
| 14 | interface State { |
| 15 | tickers?: string[] |
| 16 | channels?: string[] |
| 17 | pageSize?: number |
| 18 | } |
| 19 | |
| 20 | export function NewsView({ panel }: { panel: PanelRef }) { |
| 21 | const state = useData(`workspaces/${panel.workspaceId}/panels/${panel.id}/state`) as State | undefined |
| 22 | if (state === undefined) return <p className="bz-note">Loading…</p> |
| 23 | return <List panel={panel} tickers={state.tickers ?? []} channels={state.channels ?? []} pageSize={state.pageSize ?? 30} /> |
| 24 | } |
| 25 | |
| 26 | function when(iso: string): string { |
| 27 | const date = new Date(iso) |
| 28 | if (Number.isNaN(date.getTime())) return iso |
| 29 | const today = new Date().toDateString() === date.toDateString() |
| 30 | return today |
| 31 | ? date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) |
| 32 | : date.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) |
| 33 | } |
| 34 | |
| 35 | function List({ panel, tickers, channels, pageSize }: { panel: PanelRef; tickers: string[]; channels: string[]; pageSize: number }) { |
| 36 | const args: Record<string, unknown> = { pageSize, displayOutput: 'abstract' } |
| 37 | if (tickers.length) args.tickers = tickers.join(',') |
| 38 | if (channels.length) args.channels = channels.join(',') |
| 39 | const { data, error } = useData('benzinga/news', args) |
| 40 | const bridge = useBridge() |
| 41 | const rows = (data ?? []) as Story[] |
| 42 | usePublish(panel, { |
| 43 | tickers, |
| 44 | count: rows.length, |
| 45 | headlines: rows.slice(0, 10).map((r) => ({ created: r.created, title: r.title })), |
| 46 | }) |
| 47 | |
| 48 | const heading = `Benzinga news${tickers.length ? ` · ${tickers.join(', ')}` : ''}` |
| 49 | return ( |
| 50 | <div className="bz"> |
| 51 | <h3 className="bz-title">{heading}</h3> |
| 52 | {error ? ( |
| 53 | <p className="bz-note bz-error">{error}</p> |
| 54 | ) : !data ? ( |
| 55 | <p className="bz-note">Loading…</p> |
| 56 | ) : rows.length === 0 ? ( |
| 57 | <p className="bz-note">No stories in the last 7 days.</p> |
| 58 | ) : ( |
| 59 | <ul className="bz-list"> |
| 60 | {rows.map((row) => ( |
| 61 | <li key={row.id} className="bz-item"> |
| 62 | <div className="bz-meta"> |
| 63 | <span className="bz-time">{when(row.created)}</span> |
| 64 | {row.tickers && <span className="bz-tickers">{row.tickers.split(',').slice(0, 6).join(' ')}</span>} |
| 65 | </div> |
| 66 | <a |
| 67 | className="bz-headline" |
| 68 | href={row.url} |
| 69 | onClick={(event) => { |
| 70 | event.preventDefault() |
| 71 | if (row.url) bridge.openLink(row.url) |
| 72 | }} |
| 73 | > |
| 74 | {row.title} |
| 75 | </a> |
| 76 | {row.teaser && <p className="bz-teaser">{row.teaser}</p>} |
| 77 | </li> |
| 78 | ))} |
| 79 | </ul> |
| 80 | )} |
| 81 | </div> |
| 82 | ) |
| 83 | } |