feat: event eco
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user