import type { EventDTO } from "@dci/core"; import { ArrowRight, ExternalLink } from "lucide-react"; import Link from "next/link"; import { AiMark, ConfidenceBadge, EventBadge, SignificanceBadge } from "@/components/ui/badge"; import { SourceBadge } from "@/components/ui/source-badge"; import { Tooltip } from "@/components/ui/popover"; import { cn } from "@/components/ui/cn"; import { CountryFlag } from "@/components/ui/country-flag"; import { RelativeTime } from "@/components/ui/relative-time"; import { EmptyState } from "@/components/ui/states"; import { dayKey, fmtDayHeading, fmtValue } from "@/lib/format"; import { ENTITY_TYPE_LABEL, SIGNIFICANCE_LABEL, SOURCE_KIND_LABEL, significanceBand } from "@/lib/labels"; import { entityHref } from "@/lib/routes"; function valueHint(e: EventDTO): "mw" | "date" | "usd" | undefined { switch (e.eventType) { case "capacity_changed": case "planned_capacity_changed": return "mw"; case "opening_date_changed": return "date"; case "investment_announced": return "usd"; default: return undefined; } } /** "120 MW → 180 MW" */ export function ValueChange({ e, className }: { e: EventDTO; className?: string }) { const hint = valueHint(e); const hasOld = e.oldValue !== null && e.oldValue !== undefined && e.oldValue !== ""; const hasNew = e.newValue !== null && e.newValue !== undefined && e.newValue !== ""; if (!hasOld && !hasNew) return null; return ( {hasOld && {fmtValue(e.oldValue, hint)}} {hasOld && hasNew && } {hasNew && {fmtValue(e.newValue, hint)}} ); } export function SignificanceMark({ value, band, className }: { value: number; band?: EventDTO["significanceBand"]; className?: string }) { const b = significanceBand(value, band); const level = b === "major" ? 3 : b === "medium" ? 2 : 1; return ( {[0, 1, 2].map((i) => ( ))} ); } export function EventRow({ e, dense, highlight }: { e: EventDTO; dense?: boolean; highlight?: boolean }) { const href = e.entity ? entityHref(e.entityType, e.entity.slug, e.entity.name) : null; return (
  • {significanceBand(e.significance, e.significanceBand) === "major" && } {e.isAi && } {e.countryIso2 && } {ENTITY_TYPE_LABEL[e.entityType] ?? e.entityType}
    {href && e.entity ? ( // stretched link: the whole row is the tap target for the entity (secondary links sit above it via z-index) {e.entity.name} ) : ( {e.title} )} {href && e.entity && e.title && e.title !== e.entity.name && — {e.title}}
    {!dense && e.summary &&

    {e.summary}

    }
    {e.operator && ( {e.operator.name} )} {e.sourceName} {(e.evidenceCount ?? 0) > 1 && }
  • ); } /** "also reported by 4 sources" with the other documents on hover (deduplicated cluster). */ function AlsoReported({ e }: { e: EventDTO }) { const n = (e.evidenceCount ?? 1) - 1; const others = e.otherSources ?? []; const label = `also reported by ${n} ${n === 1 ? "source" : "sources"}`; if (!others.length) return {label}; return ( {others.slice(0, 6).map((o, i) => (
  • {o.sourceName} · {SOURCE_KIND_LABEL[o.sourceKind]}
  • ))} {others.length > 6 &&
  • +{others.length - 6} more
  • } } > {label}
    ); } /** Flat list (home module) or grouped by day (live feed). */ export function EventList({ events, groupByDay, dense, highlightIds, emptyTitle = "No changes detected yet", emptyDescription = "New facilities, capacity and status changes will appear here as connectors run." }: { events: EventDTO[]; groupByDay?: boolean; dense?: boolean; highlightIds?: Set; emptyTitle?: string; emptyDescription?: string }) { if (!events.length) return ; if (!groupByDay) { return (
      {events.map((e) => ( ))}
    ); } const groups = new Map(); for (const e of events) { const k = dayKey(e.detectedAt); if (!groups.has(k)) groups.set(k, []); groups.get(k)!.push(e); } return (
    {Array.from(groups.entries()).map(([day, list]) => (

    {fmtDayHeading(day)}

    {day} · {list.length} {list.length === 1 ? "change" : "changes"}
      {list.map((e) => ( ))}
    ))}
    ); }