diff --git a/frontend/src/components/Dashboard/ForecastWidget.tsx b/frontend/src/components/Dashboard/ForecastWidget.tsx index 62b8baa..c58fa5a 100644 --- a/frontend/src/components/Dashboard/ForecastWidget.tsx +++ b/frontend/src/components/Dashboard/ForecastWidget.tsx @@ -1,6 +1,80 @@ +import { useLiveValue } from "@/components/Flow/liveStore" +import { cn } from "@/lib/utils" +import { ICON_COLORS, ICONS } from "./icons" import type { WidgetProps } from "./widgets" -/** A forecast — a stub until the forecast widget is written. */ -export function ForecastWidget(_props: WidgetProps) { - return

+/** One column of a forecast, as the `list` message declares it. */ +type ForecastItem = { + label?: string + icon?: string + value?: string | number + color?: string +} + +const config = (widget: WidgetProps["widget"]) => + (widget.config ?? {}) as Record + +/** + * What comes next, as a strip of columns over a `list` message. + * + * The shape is the widget's contract rather than a path per column: every item + * is `{label, icon, value}` with an optional `color`, naming its icon from the + * same vocabulary the icon widget offers, so a flow answering with a forecast + * decides what a step is called and this only has to draw it. Later columns are + * dimmed progressively — a forecast is less certain the further out it reads. + */ +export function ForecastWidget({ widget }: WidgetProps) { + const cfg = config(widget) + const message = cfg.message ? String(cfg.message) : "" + const live = useLiveValue(message || undefined) + if (!message) { + return

Pick a message.

+ } + + const count = Number(cfg.count) + const items: ForecastItem[] = (Array.isArray(live?.value) ? live.value : []) + .filter((item): item is ForecastItem => !!item && typeof item === "object") + .slice(0, Number.isFinite(count) ? count : 5) + + if (items.length === 0) { + return

Nothing forecast.

+ } + + return ( +
+ {items.map((item, index) => { + const Glyph = ICONS[item.icon ?? ""] ?? null + return ( +
+ + {item.label ?? ""} + + {Glyph ? ( + + ) : null} + + {item.value === null || item.value === undefined + ? "" + : String(item.value)} + +
+ ) + })} +
+ ) }