EarningsCall Transcripts & Calendar: Shows a company's earnings call transcripts and the daily earnings calendar from EarningsCall.biz in a single panel view.
| 1 | import { useEffect, useState, type ReactElement } from 'react' |
| 2 | import { usePublish, usePublishText, type PanelRef } from '@jaspers-ai/sdk' |
| 3 | |
| 4 | // What the calendar and the call share: the ticker field, the way a failure reads, a publish from a |
| 5 | // branch that has nothing else to render, and dates written for people. |
| 6 | |
| 7 | /** |
| 8 | * Enter opens the ticker typed; a ticker the orchestrator sets shows here. Enter is read off the key, |
| 9 | * not a form: the view's frame is sandboxed without allow-forms, so a form never submits. |
| 10 | */ |
| 11 | export function TickerField({ symbol, onSymbol }: { symbol: string; onSymbol: (symbol: string) => void }): ReactElement { |
| 12 | const [draft, setDraft] = useState(symbol) |
| 13 | useEffect(() => setDraft(symbol), [symbol]) |
| 14 | return ( |
| 15 | <input |
| 16 | className="ec-input ec-input-ticker" |
| 17 | name="symbol" |
| 18 | value={draft} |
| 19 | placeholder="Ticker" |
| 20 | spellCheck={false} |
| 21 | onChange={(event) => setDraft(event.target.value)} |
| 22 | onKeyDown={(event) => { |
| 23 | if (event.key !== 'Enter' || event.nativeEvent.isComposing) return |
| 24 | event.preventDefault() |
| 25 | const next = draft.trim().toUpperCase() |
| 26 | if (next) onSymbol(next) |
| 27 | }} |
| 28 | /> |
| 29 | ) |
| 30 | } |
| 31 | |
| 32 | /** What a branch shows, published: its output, and its text, which only a transcript has. */ |
| 33 | export function Publish({ panel, output, text }: { panel: PanelRef; output: Record<string, unknown>; text: string | null }): null { |
| 34 | usePublish(panel, output) |
| 35 | usePublishText(panel, text) |
| 36 | return null |
| 37 | } |
| 38 | |
| 39 | /** The message as the server wrote it, without the wrapping the IPC adds. */ |
| 40 | export function cleanError(message: string): string { |
| 41 | return message.replace(/^Error invoking remote method '[^']*': (?:Error: )?/, '') |
| 42 | } |
| 43 | |
| 44 | /** 'Wed, Jul 29, 2026' for a day or for the day part of a timestamp. */ |
| 45 | export function longDate(value: string | null): string { |
| 46 | const match = value ? /^(\d{4})-(\d{2})-(\d{2})/.exec(value) : null |
| 47 | if (!match) return '' |
| 48 | const day = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))) |
| 49 | return day.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric', timeZone: 'UTC' }) |
| 50 | } |
| 51 | |
| 52 | /** A call's start in the viewer's own zone, like '5:30 PM'. */ |
| 53 | export function localTime(value: string | null): string { |
| 54 | const time = value ? Date.parse(value) : NaN |
| 55 | return Number.isNaN(time) ? '—' : new Date(time).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }) |
| 56 | } |