// Built-in dashboard widget renderers, one per declarative `kind`. Each takes // the resolved spec + computed WidgetData + the format context (locale/currency) // and renders the body INSIDE a . recharts powers the charts; colors // come from theme CSS vars (dark-mode safe), curves are smooth, axes/legends // compact, tooltips on. import * as React from 'react' import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Line, LineChart, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts' import { cn } from '@asteby/metacore-ui/lib' import type { DashboardWidgetSpec, WidgetData } from '../dashboard-types' import { WidgetCard, DeltaChip, WidgetEmpty } from './widget-card' import { accentClasses, paletteColor, formatWidgetValue, formatAxisTick, formatDelta, CHART_GRID, CHART_MUTED, type WidgetFormatCtx, } from './widget-format' export interface WidgetRenderProps { spec: DashboardWidgetSpec data?: WidgetData locale?: string currency?: string /** i18n: per-widget empty fallback (already translated). */ emptyText: string } const fmtCtx = ( spec: DashboardWidgetSpec, locale?: string, currency?: string, ): WidgetFormatCtx => ({ format: spec.format, locale, currency }) const hasSeries = (d?: WidgetData): d is WidgetData & { series: NonNullable } => Array.isArray(d?.series) && d!.series!.length > 0 // Fixed-height chart body. In the masonry (CSS columns) layout cards take their // natural height, so charts need a definite height for ResponsiveContainer to // resolve. This height + the card chrome gives every chart card a consistent, // taller footprint that balances cleanly against compact stat cards. const CHART_H = 'h-[190px]' function ChartArea({ children }: { children: React.ReactElement }) { return (
{children}
) } // Compact recharts tooltip styled with theme tokens. function ChartTooltip({ ctx }: { ctx: WidgetFormatCtx }) { return ( formatWidgetValue(Number(v), ctx)} /> ) } // --- stat ----------------------------------------------------------------- export function StatWidget(p: WidgetRenderProps) { const ctx = fmtCtx(p.spec, p.locale, p.currency) const value = p.data?.value const delta = p.data?.delta const hasValue = typeof value === 'number' && !Number.isNaN(value) return ( ) : undefined } > {hasValue ? (
{formatWidgetValue(value!, ctx)}
) : (
{p.emptyText}
)}
) } // --- bar ------------------------------------------------------------------ export function BarWidget(p: WidgetRenderProps) { const ctx = fmtCtx(p.spec, p.locale, p.currency) const a = accentClasses(p.spec.accent) return ( {hasSeries(p.data) ? ( formatAxisTick(v, ctx)} /> ) : ( )} ) } // --- line / area (shared) ------------------------------------------------- function TimeSeriesWidget(p: WidgetRenderProps & { variant: 'line' | 'area' }) { const ctx = fmtCtx(p.spec, p.locale, p.currency) const a = accentClasses(p.spec.accent) const gradId = `wg-grad-${p.spec.key}` return ( {hasSeries(p.data) ? ( {p.variant === 'area' ? ( formatAxisTick(v, ctx)} /> ) : ( formatAxisTick(v, ctx)} /> )} ) : ( )} ) } export function LineWidget(p: WidgetRenderProps) { return } export function AreaWidget(p: WidgetRenderProps) { return } // --- pie / donut (shared) ------------------------------------------------- function CircularWidget(p: WidgetRenderProps & { variant: 'pie' | 'donut' }) { const ctx = fmtCtx(p.spec, p.locale, p.currency) return ( {hasSeries(p.data) ? (
{p.data.series.map((_, i) => ( ))}
    {p.data.series.slice(0, 6).map((pt, i) => (
  • {pt.label} {formatWidgetValue(pt.value, ctx)}
  • ))}
) : ( )}
) } export function PieWidget(p: WidgetRenderProps) { return } export function DonutWidget(p: WidgetRenderProps) { return } // --- list (top-N with proportion bars) ------------------------------------ export function ListWidget(p: WidgetRenderProps) { const ctx = fmtCtx(p.spec, p.locale, p.currency) const a = accentClasses(p.spec.accent) const series = p.data?.series ?? [] const max = series.reduce((m, s) => Math.max(m, s.value), 0) || 1 return ( {series.length > 0 ? (
    {series.map((pt) => (
  • {pt.label} {formatWidgetValue(pt.value, ctx)}
  • ))}
) : ( )}
) } // --- progress ------------------------------------------------------------- // A scalar rendered as a proportion bar. When `format:'percent'` the value is // a fraction in [0,1]; otherwise it's shown as the big number with a full bar. export function ProgressWidget(p: WidgetRenderProps) { const ctx = fmtCtx(p.spec, p.locale, p.currency) const a = accentClasses(p.spec.accent) const value = p.data?.value const hasValue = typeof value === 'number' && !Number.isNaN(value) const pct = p.spec.format === 'percent' ? Math.min(100, Math.max(0, (value ?? 0) * 100)) : 100 return ( {hasValue ? (
{formatWidgetValue(value!, ctx)}
) : ( )} ) }