'use client'; import { fetchLatestCommodityPrices, fetchLatestPrices } from '@/actions/prices.js'; import type { getLatestPrices } from '@/generated/prisma/sql.js'; import { deserialize } from '@/lib/superjson.js'; import { cn } from '@/lib/utils.js'; import { useEffect, useState } from 'react'; interface TickerItem { label: string; price: string; change: number | null; unit: string; } function formatPrice(price: number, unit: string): string { if (unit === '$/MWh') { return `$${price.toFixed(2)}`; } return `$${price.toFixed(2)}`; } function TickerItemDisplay({ item }: { item: TickerItem }) { const changeColor = item.change === null ? 'text-muted-foreground' : item.change >= 0 ? 'text-emerald-400' : 'text-red-400'; const changeSymbol = item.change === null ? '' : item.change >= 0 ? '\u25B2' : '\u25BC'; return ( {item.label} {item.price} {item.unit} {item.change !== null && ( {changeSymbol} {Math.abs(item.change).toFixed(1)}% )} | ); } export function TickerTape() { const [items, setItems] = useState([]); useEffect(() => { async function loadPrices() { const [priceResult, commodityResult] = await Promise.all([fetchLatestPrices(), fetchLatestCommodityPrices()]); const tickerItems: TickerItem[] = []; if (priceResult.ok) { const prices = deserialize(priceResult.data); for (const p of prices) { tickerItems.push({ label: p.region_code, price: formatPrice(p.price_mwh, '$/MWh'), change: null, unit: '$/MWh', }); } } if (commodityResult.ok) { const commodities = deserialize< Array<{ commodity: string; price: number; unit: string; timestamp: Date; source: string; }> >(commodityResult.data); const commodityLabels: Record = { natural_gas: 'Nat Gas', wti_crude: 'WTI Crude', coal: 'Coal', }; for (const c of commodities) { tickerItems.push({ label: commodityLabels[c.commodity] ?? c.commodity, price: formatPrice(c.price, c.unit), change: null, unit: c.unit, }); } } setItems(tickerItems); } void loadPrices(); const interval = setInterval(() => void loadPrices(), 60_000); return () => clearInterval(interval); }, []); if (items.length === 0) { return null; } return ( {/* Duplicate items for seamless looping */} {items.map((item, i) => ( ))} {items.map((item, i) => ( ))} ); }