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 type { ReactElement } from 'react' |
| 2 | import { useData, type PanelRef } from '@jaspers-ai/sdk' |
| 3 | import { answers, calendarOutput, readCalendarRows, shiftDay, today, type CallsState } from './calls' |
| 4 | import { cleanError, localTime, longDate, Publish, TickerField } from './shared' |
| 5 | |
| 6 | // The calls held on one day. A row opens its call in this panel and keeps the day, so the way back |
| 7 | // lands on the same calendar. |
| 8 | |
| 9 | export function Calendar({ panel, state, write }: { panel: PanelRef; state: CallsState; write: (next: CallsState) => void }): ReactElement { |
| 10 | const now = today(new Date()) |
| 11 | const day = state.date ?? now |
| 12 | const run = useData('earningscall/calendar', { date: day }) |
| 13 | // Until the new day is in, the run still holds the last day's calls. |
| 14 | const fresh = run.data !== undefined && answers(run.meta, { date: day }) |
| 15 | const rows = fresh ? readCalendarRows(run.data) : [] |
| 16 | const error = run.error ? cleanError(run.error) : null |
| 17 | const go = (date: string | null): void => write({ ...state, date }) |
| 18 | return ( |
| 19 | <div className="ec-root"> |
| 20 | {(fresh || error) && <Publish panel={panel} output={{ ...calendarOutput(day, rows, error) }} text={null} />} |
| 21 | <div className="ec-bar"> |
| 22 | <button type="button" className="ec-button" title="Day before" onClick={() => go(shiftDay(day, -1))}> |
| 23 | ‹ |
| 24 | </button> |
| 25 | <span className="ec-title">{longDate(day)}</span> |
| 26 | <button type="button" className="ec-button" title="Day after" onClick={() => go(shiftDay(day, 1))}> |
| 27 | › |
| 28 | </button> |
| 29 | {day !== now && ( |
| 30 | <button type="button" className="ec-button" onClick={() => go(null)}> |
| 31 | Today |
| 32 | </button> |
| 33 | )} |
| 34 | {run.loading && <span className="ec-muted">loading…</span>} |
| 35 | <span className="ec-grow" /> |
| 36 | <TickerField symbol="" onSymbol={(symbol) => write({ ...state, symbol, exchange: null, year: null, quarter: null })} /> |
| 37 | </div> |
| 38 | <div className="ec-body"> |
| 39 | {error ? ( |
| 40 | <p className="ec-error">{error}</p> |
| 41 | ) : !fresh ? ( |
| 42 | <p className="ec-note">Loading…</p> |
| 43 | ) : rows.length === 0 ? ( |
| 44 | <p className="ec-note">No earnings calls on this day.</p> |
| 45 | ) : ( |
| 46 | <table className="ec-table"> |
| 47 | <thead> |
| 48 | <tr> |
| 49 | <th>Time</th> |
| 50 | <th>Ticker</th> |
| 51 | <th>Company</th> |
| 52 | <th>Call</th> |
| 53 | <th>Transcript</th> |
| 54 | </tr> |
| 55 | </thead> |
| 56 | <tbody> |
| 57 | {rows.map((row) => ( |
| 58 | <tr |
| 59 | key={`${row.exchange}:${row.symbol}:${row.year}:${row.quarter}`} |
| 60 | className="ec-row" |
| 61 | title={`Open the Q${row.quarter} ${row.year} call`} |
| 62 | onClick={() => write({ ...state, symbol: row.symbol, exchange: row.exchange, year: row.year, quarter: row.quarter, date: day })} |
| 63 | > |
| 64 | <td className="ec-num">{localTime(row.date)}</td> |
| 65 | <td>{row.symbol}</td> |
| 66 | <td className="ec-company">{row.company}</td> |
| 67 | <td> |
| 68 | Q{row.quarter} {row.year} |
| 69 | </td> |
| 70 | <td className={row.ready ? undefined : 'ec-muted'}>{row.ready ? 'Ready' : 'Not yet'}</td> |
| 71 | </tr> |
| 72 | ))} |
| 73 | </tbody> |
| 74 | </table> |
| 75 | )} |
| 76 | </div> |
| 77 | </div> |
| 78 | ) |
| 79 | } |