Document components for code dashboards
Цей контент ще не доступний вашою мовою.
Document components present an answer with a clear reading order, such as an analysis, a proposal, or an estimate, in a React dashboard edited through the workspace pull, edit, and push workflow. They can contain embedded results, live data, or interactive scenarios. They do not freeze data or provide an export format. For the steps to write one, see Write a document dashboard in code.
Most documents need no code: the Document template renders a document written as data, with the
same components. See Document specification. Code is for what the specification
cannot express, and such a dashboard can still render a document with DocumentView.
Imports and scaffold
Section titled “Imports and scaffold”@overvis/hmi-workspace-ui exports the document components below, DocumentChart, and
DocumentChartDataType. @overvis/hmi-workspace-ui/ui exports the UI primitives they compose
with, including:
TableCaption,TableHeader,TableBody,TableFooter,TableRow,TableHead, andTableCell, composed insideDocumentTable.Alert,AlertTitle, andAlertDescription. Alertvariantisdefault,info,warning, ordestructive.Button, withvariantdefault,outline,secondary,ghost,destructive, orlink.Field,FieldTitle,FieldDescription,FieldGroup, andSliderfor scenario controls.Badge. Icons come fromlucide-react.
DashboardRootProps comes from @overvis/hmi-shared/dashboard. DocumentChart needs no
additional chart dependency.
The empty dashboard template creates four source files. index.tsx re-exports the others,
dashboard.tsx holds the default component, config.ts holds dashboardConfigType, and
manifest.ts declares supportedLocales.
Layout components
Section titled “Layout components”| Component | Content and behavior |
|---|---|
DashboardDocument | An article with a maximum reading width, vertical section spacing, and a named container for responsive columns, inside the normal dashboard host. |
DocumentLead | Required title, optional context, and prose children. The host already displays the dashboard catalog name. |
DocumentSection | Required title and arbitrary children, with an associated h2 and consistent section spacing. DocumentProse styles h3 sub-headings. |
DocumentColumns | columns={2} by default or columns={3}. Equal columns stack when the document is narrow, even on a wide browser with the assistant panel open. |
DocumentProse | Paragraphs, lists, emphasis, and links as React children. Provides line length, spacing, and wrapping. Does not parse Markdown or raw HTML. |
DocumentMetric | Required label and value, optional detail. Units or currency are part of the supplied content. |
DocumentFigure | Arbitrary figure children and required caption. Supplies containment and an associated caption; the figure content owns its height. |
DocumentTable | Composes the existing TableHeader, TableBody, TableRow, TableCell, and related components. Wraps descriptions in the first column; other columns retain compact values. Wide tables scroll within their container. |
Layout components accept the underlying HTML element’s props and className. Text outside these
components and DocumentProse gets the document’s body size but no spacing.
Scenario controls
Section titled “Scenario controls”A scenario control is a Field with a FieldTitle that has an id, the control labeled by that
id, and an optional FieldDescription. Option Buttons sit in the same Field. FieldGroup
spaces several fields.
Slider onValueChange receives number | readonly number[]. typeof value === "number" narrows
it; Array.isArray does not narrow a readonly array, so a helper built on it fails the dashboard
type check.
const rateLabel = useId();const [rate, setRate] = useState(60);
<Field> <FieldTitle id={rateLabel}>Labor rate: €{rate}/hour</FieldTitle> <Slider aria-labelledby={rateLabel} min={30} max={120} step={5} value={[rate]} onValueChange={(value) => setRate(typeof value === "number" ? value : (value[0] ?? 30))} /> <FieldDescription>An allowance, not a quoted rate.</FieldDescription></Field>;Charts with supplied data
Section titled “Charts with supplied data”DocumentChart draws kind="bar" for category comparisons and kind="line" for ordered trends.
It has a stable height, theme colors, labeled series, accessible chart navigation, and tooltips. It
displays all supplied rows.
Required props:
data:{ rows: [{ label, values }], series: [{ key, label }] }. Each row’svaluesmaps series keys to numbers ornull. Series order determines legend order and color. Colors cycle after five series.kind:"bar"or"line".label: accessible name describing the chart.formatValue:(value: number) => string, used for the value axis and tooltip.empty: localized React content displayed when no selected series has a value.
DocumentChartDataType is the TypeBox schema and DocumentChartData its derived type. Missing
keys and null represent gaps. Zero remains a real value. Lines do not connect across missing
values. Every series shares one value axis. Long category labels are shortened on the axis and
retained in tooltips. Category positions are equally spaced; the chart is not timestamp-aware. The
history report widgets draw irregular stored
readings.
<DocumentChart kind="bar" label="Weekly consumption" formatValue={(value) => `${value.toLocaleString(locale)} kWh`} empty={<p>No measured consumption in this period.</p>} data={{ series: [{ key: "energy", label: "Consumption" }], rows: [ { label: "Week 1", values: { energy: 120 } }, { label: "Week 2", values: { energy: null } }, { label: "Week 3", values: { energy: 0 } }, ], }}/>Overvis cost calculators
Section titled “Overvis cost calculators”A code calculator reads the price list once with billing.prices and prices its inputs with
estimateCost from @overvis/hmi-shared/billing, the estimator behind billing.estimate. It
updates on every input change without a request, and it never holds prices of its own. A document
written as data prices its inputs with a billing.estimate source instead; see
API procedures.
import { estimateCost } from "@overvis/hmi-shared/billing";import { trpc } from "@overvis/hmi-workspace-ui";
const priceList = trpc.billing.prices.useQuery().data?.current ?? null;const [meters, setMeters] = useState(10);const estimate = useMemo(() => { if (priceList == null) return null; const group = { devices: meters, parametersPerDevice: 5, pollSeconds: 60, endpoints: 1 }; return estimateCost({ deviceGroups: [{ ...group, retentionDays: 365 }] }, priceList, 24);}, [priceList, meters]);The profile and the result are the input and answer of billing.estimate; see
Estimate through the API. Amounts are
micro-euros with VAT. While the price list is null, show that prices are unavailable.