Przejdź do głównej zawartości

Dashboard UI and forms

Ta treść nie jest jeszcze dostępna w Twoim języku.

The components and helpers that @overvis/hmi-workspace-ui offers dashboards, and the form scaffold that Setup and dashboards share. For the steps to use them, see Build a form that saves through the API, Write parameter values from a dashboard, and Develop a dashboard locally.

The package root @overvis/hmi-workspace-ui exports the form scaffold: useAppForm, FormRoot, mutationFormValidators, focusFirstInvalidField, field helpers, mapTrpcToFormErrors, and apiErrorMessage. Setup and the host apps build their API forms on it.

const form = useAppForm({
defaultValues: {/* … */},
validators: mutationFormValidators({
schema: someSharedFormSchema, // optional
submit: async (value) => {
await someMutation.mutateAsync(/* … */);
return null;
},
// Optional; the default is mapTrpcToFormErrors.
// mapError: (error, value) => mapTrpcToFormErrors(error, value, { fieldMap }),
}),
onSubmitInvalid: focusFirstInvalidField,
onSubmit: () => {
/* runs after a successful submit */
},
});
return (
<form.AppForm>
<form.FormRoot>
{/* form.AppField → field.TextField */}
<form.SubmitButton label="…" pendingLabel="…" />
</form.FormRoot>
</form.AppForm>
);
  • useAppForm wraps @tanstack/react-form with the field components and SubmitButton.
  • FormRoot always mounts FormErrorBanner. It blurs the focused control before submit, so Enter includes values that commit on blur.
  • mutationFormValidators validates the submitted value against the TypeBox schema, then calls submit, and maps a thrown API error to { fields?, form? } with mapError. onSubmit runs only after submit succeeds.
  • mapTrpcToFormErrors puts input_invalid issues and a domain error’s details.field on the matching field (first path segment or leaf), and anything else on the banner. fieldMap maps API input names to form field names when they differ. mapError runs again whenever the message is read; onError runs once per failure.
  • apiErrorMessage(error) is the message for API error code X: the catalog message error_X.
  • focusFirstInvalidField focuses the first invalid field after a failed validation.
  • Setup form values are Static<> of the shared form schemas, built from the API input schemas. The shared …FormToInput functions, such as endpointCreateFormToInput, parameterFormToInput, and reportPresetFormToInput, turn submitted values into the API input or into { ok: false, field, problem }. formInputProblemErrors(result, message) turns that problem into a field error.

@overvis/hmi-workspace-ui/ui exports shadcn-style primitives (Tailwind 4) and cn. The live-data components come from the package root. Icons come from lucide-react. The primitives follow shadcn accessibility patterns, and the workspace shell around them is mobile-first. Which packages a dashboard can import is listed in the host contract.

Export areaExamples
ActionsButton, ButtonGroup, Toggle, ToggleGroup, Command, ContextMenu
InputsInput, InputGroup, InputOTP, Combobox, RadioGroup, Calendar, DateTimePicker, Questionnaire
OverlaysDialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, DropdownMenu, Tooltip
NavigationBreadcrumb, NavigationMenu, Menubar, Sidebar, Pagination
LayoutCard, Accordion, AspectRatio, Carousel, Resizable, ScrollArea, Separator, Tabs
FeedbackNoticeCard, Alert, Badge, Empty, Marker, Skeleton, Spinner, Sonner toasts
Data and messagesTable, composed DataTable, Chart, Sparkline, GaugeBar, Gauge, Item, Attachment, Message, MessageScroller
ChartsChartContainer, ChartTooltip, ChartLegend, BarChart, LineChart, AreaChart, ComposedChart, Bar, Line, XAxis
Live dataParameterLiveControls, ParameterValueDisplay, ParameterBooleanBadge, ParameterDiagnostics, useParameterValueHistory
Iconslucide-react components imported directly by the dashboard

NoticeCard is the problem and status card of the host and the shared components. severity is error, warning, or info. It takes a short title, a plain description, technical facts in details, and one primaryAction or onRetry (with retrying) ahead of secondaryActions. size="compact" fits panels and narrow sections. Alert is the callout of printable documents. The workspace-ui reference lists every prop.

Button with variant="outline" uses an opaque theme background and matching text color, so it remains readable over graphics and images. Disabled buttons block activation without passing pointer clicks through to the content behind them.

Sparkline fits the timestamps it receives by default. With a window of kind: "live", sampleCount, sampleIntervalMs, and gapThresholdMs, it scrolls with its own clock. With kind: "fixed" and to, the caller controls the clock. The component keeps the latest reading in each slot and disconnects the line when the sampling gap exceeds the threshold. range keeps the vertical scale fixed, and referenceValue draws a horizontal setpoint or limit line.

GaugeBar draws a reading on a horizontal bar whose rounded ends keep their shape at any width. Positions are in percent of the width: bands such as zones, a fill from the start, ticks for limits with labels above, and a marker for one reading or the span of several, with a label below. The bar is hidden from assistive technologies and carries no text alternative.

Gauge shows one live value on a half-circle from 0 to max, with an optional marker tick such as the period’s peak. gaugeScaleMax picks max. It takes texts already formatted for the locale. CounterBars from the main entry charts a counter’s increase per hour or day of a period.

@overvis/hmi-workspace-ui/ui provides the shadcn chart parts and these Recharts parts with their prop types: BarChart, LineChart, AreaChart, ComposedChart, Bar, Line, Area, XAxis, YAxis, CartesianGrid, ReferenceLine, and Rectangle. Their props follow the recharts version pinned by @overvis/hmi-workspace-ui. The host does not serve recharts to dashboards, so a direct recharts import puts a separate copy of Recharts into the dashboard bundle.

import {
Bar,
BarChart,
CartesianGrid,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
XAxis,
type ChartConfig,
} from "@overvis/hmi-workspace-ui/ui";
const config = {
consumed: { label: "Consumed, kWh", color: "var(--chart-1)" },
} satisfies ChartConfig;
export function EnergyChart({ rows }: { rows: { month: string; consumed: number }[] }) {
return (
<ChartContainer config={config} className="max-h-72">
<BarChart data={rows} accessibilityLayer>
<CartesianGrid vertical={false} />
<XAxis dataKey="month" tickLine={false} axisLine={false} />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="consumed" fill="var(--color-consumed)" radius={4} />
</BarChart>
</ChartContainer>
);
}

ChartContainer turns each config key into a --color-<key> variable for the chart’s marks and supplies the tooltip and legend labels. A Bar shape function that renders Rectangle with the per-bar props styles individual bars. The document components and ParameterTrendChart cover common charts without composing Recharts parts.

Host chrome (ThemeProvider, LanguageSwitcher, ModeToggle, BrandLink) belongs to the ./host entry and is not available to dashboards.

Dashboards are styled with Tailwind classes in their source files. hmi build compiles the classes the dashboard uses into its index.css and scopes them to the dashboard. Classes that appear only in test modules (*.test.*, *.spec.*, or files under __tests__/) are left out. The host stylesheet already defines the classes of the shared components, so one class can have a rule in both stylesheets. Cascade layers decide between them:

Dashboard classLayer
No variant: grid-cols-2, w-full, text-muted-foregroundBelow the host utilities
Variant: hover:, data-[state=on]:, sm:, @min-[30rem]:, …Above the host utilities
Variant that workspace-ui also uses, such as hover:bg-mutedThe host’s rule for the class

A plain class and a variant of the same property therefore work as in any Tailwind project: grid-cols-2 @min-[30rem]:grid-cols-3, w-full sm:w-auto, and text-muted-foreground hover:text-foreground switch at the breakpoint or state. The same holds for classes passed to a shared component through className. Shared components keep their own hover, focus, pressed, and invalid styles when the dashboard uses the same classes. Important classes (p-0! sm:p-4!) all stay in the lower layer: !important reverses the layer order, so they keep Tailwind’s order among themselves and still beat the host’s.

A layer outranks source order and specificity. Two cases can still resolve differently than Tailwind orders them, depending on which of the classes workspace-ui uses:

  • A shorthand and its longhand on one element, such as p-4 px-2. py-4 px-2, which sets each side once, has no such conflict.
  • Two variants for the same property that are active together, such as a hover: and a data-[state=on]: background on a hovered, pressed item. Exclusive variants, such as not-data-[state=on]:hover:bg-accent data-[state=on]:bg-primary, have no such conflict.

Dialogs, sheets, drawers, and menus render outside the dashboard, so the dashboard’s own classes do not reach them. DASHBOARD_SCOPE_PROPS (from @overvis/hmi-shared/dashboard) spread on the overlay’s content, such as <DropdownMenuContent {...DASHBOARD_SCOPE_PROPS}>, apply the dashboard’s classes to that element and everything inside it.

ParameterWriteReviewDialog sends the reviewed changes in one writeValues request and returns the keys of the failed changes. disabled turns off the confirmation while the changes can no longer be written, such as after write access or current device values are lost while it is open; a write already sent completes.

ParameterWriteSequenceDialog writes ordered steps, one at a time, and stops at the first step that is not acknowledged. Sequences on one page run one after another. DeviceCommandButton wraps it for a command button. Their disabled option blocks starting the sequence; one already running finishes. DeviceSettingsSection applies the same rules to a section of device settings and writes ordered steps, such as an unlock before the changes, when given a write plan; see device settings. Its validate holds the rules between settings, such as a minimum above a maximum: the messages show on the rows once something is edited and block the confirmation. Device dashboards share generic editor and command copy (Apply, no changes, no permission, invalid number, range, write failed, send command) through the device_preset_* keys of the host catalog; see device dashboard components.

The step list shows each parameter name and value; an integer value with a label shows the label. secret adds a typed value, such as a device password: parameterId is the parameter it is written to, label and optional description name the field, maxLength limits it, and validate returns an error message or null. The dialog shows a masked field and writes the typed text to parameterId in a step before the others, masked in the list. With optional, an empty field skips that step without calling validate. initialValue prefills the field, and onSent receives the text when the sequence starts (empty when the step was skipped). stepLabel names the step where steps are listed by name. The secret is written with writeOnly, so it is never read back, and the dialog drops the text when it closes.

steps can also be a function. It is called when the dialog opens, for the list, and again when the sequence starts, so a value such as the current time is the one written.

Write validation failures carry reasons such as write_value_below_min. The runtime codes and reasons are listed in Live value procedures.

ParameterControlButton sends a configured command or setpoint, ParameterValueEditor edits a value or a date-time, and ParameterWriteSequenceDialog writes an ordered group of steps. They use the host’s workspace and live-value providers. They take translated messages from the dashboard. disabled shows a control without letting it write, such as in a preview or edit mode.

The dialogs check permissions and parameter writability before they write. Errors found before the first write can be corrected and retried; once a write starts, another attempt needs the dialog opened again. The dialog stays open until the sequence finishes or stops, then names the step that failed and the device’s error when one was reported. onSettled receives the same result, so a form can mark the fields of the failed step. A step is one write, or { items } sent together in one request. secretParameterIds masks the values of parameters such as passwords in the list of steps. Leaving the page stops the steps not yet sent but cannot undo a write already sent.

The value editor uses the same typed-value preparation and boolean or enum choices as Setup. It submits the selected wire value even when translated option labels are identical. Setup’s inline editor also supports numeric and raw-hex display modes and pasted ISO timestamps; strict date-time dialogs accept the displayed local format.

executeParameterWriteSequence from @overvis/hmi-shared/parameter accepts steps (API write items, or { items } written in one request) and an injected write function. It awaits acknowledgements and never retries. It checks no permissions and asks for no confirmation. Its result reports acknowledged, or the 1-based step that stopped with rejected (and the first reported error), unknown (no acknowledgement, so the step may have been applied), or cancelled. rejected and a returned unknown carry the step’s per-item results. appDateTimeToISOString converts strict YYYY-MM-DD HH:mm:ss input in the browser time zone to the wire timestamp.

evaluateParameterCondition returns null for unavailable, stale, diagnostic, or nonnumeric readings. equalConfigurationValue compares JSON configuration using canonical JSON, ignoring object key order and omitted undefined properties. ParameterDeadline displays a date parameter, marks known overdue values, and opens the confirmed date-time editor. PumpStatusIcon is a compact decorative pump pictogram for use beside a textual status.

DashboardConfigurationRequired is the standard missing-binding screen for dashboards that start without parameter ids. It lists the missing keys with an example configuration and registers no live demand; it offers Open dashboard settings only to members who can open Setup. onRetry of ParameterDiagnostics is optional. @overvis/hmi-workspace-ui/dashboard-bindings finds what it lists without loading the dashboard kit. Binding groups are TypeBox objects of parameter ids: missingDashboardBindingGroups(groups, config.parameters) names the fields each group lacks, exampleDashboardBindings(groups) fills every field with a numbered placeholder id for the screen’s example configuration, and dashboardBindingFields(group) lists a group’s fields in declaration order. missingDashboardBindings(requirements) checks groups whose required fields vary, such as by device model.

useDashboardConfigurationMutation fetches the stored dashboard, validates its configuration against the supplied TypeBox schema, applies a typed transform, preserves dashboard metadata, and refreshes the dashboard queries after the update. The update is based on the fetched dashboard: if the dashboard changed before it is saved, it fails with setup_conflict or dashboard_configuration_conflict instead of overwriting that change. DashboardConfigurationRenameDialog is the shared rename form for labels stored in that configuration. ParameterPickerField picks a parameter for a UUID field on useAppForm, and ParameterPicker does the same for a value outside a form. The field shows the parameter’s name, mnemonic, branch path, and value with units, read once; it opens a search over the parameter tree that matches names in every language, mnemonics, branch paths, and addresses. Both store the parameter UUID, or an empty string when allowEmpty lets the user clear it. preferred lists matching parameters first among search results.

useDashboardQuickReport(workspaceId) creates a temporary history report, opens it, and reports skipped parameters or a failure in a toast. useQuickReport creates the temporary report, reports skipped parameters and failures through caller-supplied localized callbacks, and passes the resulting report preset ID to caller-owned navigation. Counters among the parameters (energy, heat, water, gas) passed as counterParameterIds show consumption per time bucket instead of averages. cadence: "1d" shows daily buckets over the last 31 days instead of hourly ones over the last day.

SvgDiagram loads a vector drawing from an ordinary .svg file and updates the elements marked with data-svg-target values. An asset imported through new URL("./assets/diagram.svg", import.meta.url).href is copied beside the bundle by the dashboard build. The asset must resolve on the same origin as the dashboard host.

SvgDiagram loads and sanitizes the asset, scopes its internal IDs per mounted instance, applies prepared text, state, and action bindings, and fits the asset into the provided container. It does not add pan, zoom, or other viewport interaction, and it does not know parameter UUIDs: bindings carry display strings and state classes that the dashboard prepares. Missing targets are shown as an authoring diagnostic while the rest of the diagram remains usable.

The sanitizer removes scripts, foreignObject, event attributes, external links, CSS imports, and external CSS URLs. Fragment references in attributes, inline styles, and embedded <style> blocks are rewritten to the instance-scoped IDs, so gradients, masks, clip paths, markers, and repeated diagram instances can share one source asset.

A text binding replaces the children of the targeted <text> or <tspan>. Class, visibility, accessible-label, and action bindings leave the target’s children intact. Every action target needs an ariaLabel; SvgDiagram adds button semantics and Enter and Space activation. Classes supplied by bindings override matching declarations from the asset’s embedded stylesheet, so the SVG can define fallback appearance for live targets.

SvgDiagram defaults to min-h-80. className overrides that minimum; for example, aspect-video min-h-0 keeps a 16:9 diagram compact on phones while it fills the desktop column.

DashboardLocationMap from @overvis/hmi-workspace-ui shows named locations on a Google Map. It handles browser credentials, clustering, bounds fitting, and marker-anchored popups. It takes locations with stable IDs, coordinates, marker artwork, popup headers, and content, and translated empty and unavailable messages. Site states, navigation, and popup layouts come from the dashboard. Updating a location’s coordinates under the same ID refreshes both clustering and map bounds. An empty location list shows the empty message even when Maps is unavailable. The fleet and site blocks FleetOverview and FleetMap combine it with filters and status.

A location has an optional tone: critical, warning, or normal (the default). A cluster marker shows its member count and takes the color of its worst member: destructive for critical, amber for warning, and primary otherwise. clusterLabel(count, tone) names cluster markers for screen readers; without it the name is the count. A location’s optional title names its marker instead of name, for example “Station 3 – Alarm”, so the map does not rely on color alone.

DashboardLocationPicker places one position in a setup form: clicking the map or dragging the marker calls onChange with coordinates rounded to six decimals. A changed value moves the marker and pans the map when it is out of view. Optional context sites appear as muted, non-interactive markers. It takes a translated label for the map region and an unavailable message.

When Maps credentials or the SDK fail, including a key that Google rejects for the current site, the map and the picker show the unavailable message; the rest of the dashboard keeps working. A translated retryLabel adds a button that requests credentials again and remounts the map.

useMapsBrowserConfig loads the browser key and map ID, useMapsReverseGeocode returns popup addresses, and readLatLng reads click and marker positions. Maps load in the cloud app and in the local host at http://localhost:8000; http://127.0.0.1:8000 and LAN IP origins do not load the map. @vis.gl/react-google-maps is not in the host import map, so a map dashboard bundles it. CLI workspace scaffolds include the Maps renderer, @googlemaps/markerclusterer, and Google Maps types, and pull refreshes these managed dependencies for existing workspaces.

The public map template shows a Google Map of named sites. Its sites live in configuration. Each site has a UUID id, a name (1–60), and position as static { lat, lng } or a geo parameterId, optional alarm and running parameter ids, and up to eight values. The configuration also has optional center, zoom (1–20), allowAdd, and allowDelete (both default false). Until a successful live value exists, the pin is loading. After that it is unreachable if every bound parameter has a settled error, alarm if the alarm flag is true, operating if running is true, and standby otherwise. Live GNSS pins stay independent markers. Only static { lat, lng } sites cluster.

DashboardGraphics shows diagrams that users can correct in the browser. It shares a versioned JSON format with assistant authoring and supports parameter bindings, reusable symbols, and workspace images. See editable dashboard graphics for geometry, storage, and conflict-aware saving.