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 (
);
}
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"}
))}
);
}