feat: event eco

This commit is contained in:
OpenSquared
2026-07-21 15:59:53 +02:00
parent 5f23141992
commit 24e5e4b03b
7 changed files with 129 additions and 50 deletions

View File

@@ -51,12 +51,6 @@ const IMPACT_DOT: Record<string, string> = {
high: 'bg-red-500', medium: 'bg-yellow-400', low: 'bg-slate-400',
}
const isTodayISO = (dateStr: string) => {
const t = new Date()
const utc = `${t.getUTCFullYear()}-${String(t.getUTCMonth() + 1).padStart(2, '0')}-${String(t.getUTCDate()).padStart(2, '0')}`
return dateStr === utc
}
const formatDateShort = (dateStr: string) => {
const d = new Date(dateStr + 'T00:00:00Z')
return d.toLocaleDateString('fr-FR', { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' })
@@ -123,10 +117,29 @@ function QuoteRow({ q }: { q: Quote }) {
)
}
function EcoEventRow({ ev, showActual }: { ev: any; showActual: boolean }) {
return (
<div className="flex items-center gap-1.5 text-[10px]">
<span className="shrink-0">{CURRENCY_FLAGS[ev.currency] ?? ev.currency}</span>
<span className={clsx('w-1.5 h-1.5 rounded-full shrink-0', IMPACT_DOT[ev.impact] ?? 'bg-slate-400')} />
<span className="text-white truncate flex-1 line-clamp-1">{ev.event_name}</span>
{showActual && ev.actual_value ? (
<span className="text-[9px] font-mono shrink-0">
<span className="text-white font-semibold">{ev.actual_value}</span>
{ev.forecast_value && <span className="text-slate-600"> /{ev.forecast_value}</span>}
</span>
) : ev.forecast_value ? (
<span className="text-[9px] font-mono text-slate-600 shrink-0">F {ev.forecast_value}</span>
) : null}
{ev.event_time && <span className="text-slate-700 text-[9px] shrink-0 font-mono">{ev.event_time}</span>}
</div>
)
}
export default function Dashboard() {
const { data: riskScore, isLoading: riskLoading } = useGeoRiskScore()
const { data: allQuotes } = useAllQuotes()
const { data: ecoCalendarData } = useEcoCalendar({ period: 'recent', limit: 30 })
const { data: ecoCalendarData } = useEcoCalendar({ period: 'recent', limit: 100 })
const { data: portfolio } = usePortfolioSummary()
const { data: lastScoresData } = useLastScores()
const { data: allPatternsData } = useAllPatterns()
@@ -259,20 +272,32 @@ export default function Dashboard() {
})
}, [watchlistQuotesData])
// Upcoming FF economic events, chronological, grouped by date (fills available height, no fixed cap)
const upcomingEventsGrouped = useMemo(() => {
// FF economic events — today (with actual/forecast as they release) + rest of the
// current week (Mon-Sun), grouped by date. Past events beyond today are dropped —
// the point of this card is "am I up to date", not a historical log.
const { todayEvents, restOfWeekGrouped } = useMemo(() => {
const events: any[] = (ecoCalendarData as any)?.events ?? []
const todayISO = new Date().toISOString().slice(0, 10)
const sorted = [...events]
.filter((ev: any) => ev.event_date >= todayISO)
const now = new Date()
const todayISO = now.toISOString().slice(0, 10)
const dow = now.getUTCDay() // 0=Sun..6=Sat
const sunday = new Date(now)
sunday.setUTCDate(now.getUTCDate() + (dow === 0 ? 0 : 7 - dow))
const weekEndISO = sunday.toISOString().slice(0, 10)
const today = events
.filter((ev: any) => ev.event_date === todayISO)
.sort((a: any, b: any) => (a.event_time ?? '').localeCompare(b.event_time ?? ''))
const restOfWeek = events
.filter((ev: any) => ev.event_date > todayISO && ev.event_date <= weekEndISO)
.sort((a: any, b: any) => `${a.event_date}${a.event_time ?? ''}`.localeCompare(`${b.event_date}${b.event_time ?? ''}`))
const groups: { date: string; events: any[] }[] = []
for (const ev of sorted) {
for (const ev of restOfWeek) {
const last = groups[groups.length - 1]
if (!last || last.date !== ev.event_date) groups.push({ date: ev.event_date, events: [ev] })
else last.events.push(ev)
}
return groups
return { todayEvents: today, restOfWeekGrouped: groups }
}, [ecoCalendarData])
return (
@@ -542,31 +567,33 @@ export default function Dashboard() {
)
})()}
{/* Economic Events — real Forex Factory feed, grouped by date */}
{/* Economic Events — real Forex Factory feed: today (actual as it releases) + rest of week */}
<div className="card col-span-1 flex flex-col overflow-hidden" style={row1Height ? { height: row1Height } : undefined}>
<div className="section-title flex items-center gap-1 shrink-0"><Clock className="w-3 h-3" /> Economic Events</div>
{upcomingEventsGrouped.length > 0 ? (
{(todayEvents.length > 0 || restOfWeekGrouped.length > 0) ? (
<div className="mt-2 space-y-2 overflow-y-auto flex-1 min-h-0">
{upcomingEventsGrouped.map(group => (
{todayEvents.length > 0 && (
<div>
<div className="flex items-center gap-1.5 text-[9px] font-semibold text-slate-300 bg-dark-700/50 rounded px-1.5 py-0.5 mb-1">
Aujourd'hui <span className="badge-blue text-[8px] px-1 py-0">TODAY</span>
</div>
<div className="space-y-1 pl-0.5">
{todayEvents.map((ev: any, i: number) => <EcoEventRow key={i} ev={ev} showActual />)}
</div>
</div>
)}
{restOfWeekGrouped.map(group => (
<div key={group.date}>
<div className="flex items-center gap-1.5 text-[9px] font-semibold text-slate-300 bg-dark-700/50 rounded px-1.5 py-0.5 mb-1">
{formatDateShort(group.date)}
{isTodayISO(group.date) && <span className="badge-blue text-[8px] px-1 py-0">TODAY</span>}
</div>
<div className="space-y-1 pl-0.5">
{group.events.map((ev: any, i: number) => (
<div key={i} className="flex items-center gap-1.5 text-[10px]">
<span className="shrink-0">{CURRENCY_FLAGS[ev.currency] ?? ev.currency}</span>
<span className={clsx('w-1.5 h-1.5 rounded-full shrink-0', IMPACT_DOT[ev.impact] ?? 'bg-slate-400')} />
<span className="text-white truncate flex-1 line-clamp-1">{ev.event_name}</span>
{ev.event_time && <span className="text-slate-700 text-[9px] shrink-0 font-mono">{ev.event_time}</span>}
</div>
))}
{group.events.map((ev: any, i: number) => <EcoEventRow key={i} ev={ev} showActual={false} />)}
</div>
</div>
))}
</div>
) : <div className="text-slate-600 text-xs mt-2">No upcoming events</div>}
) : <div className="text-slate-600 text-xs mt-2">No events this week</div>}
</div>
</div>