Step 7: Assemble the dashboard
This step shows how the components compose into the final App.jsx that ships with the reference repo. The three components you built each play a role:
LiveChartrenders each metric card in the charts row.SessionButtonsits inside the device's metadata panel, where it also drives a session duration timer and an energy accumulator (seeDeviceCard.jsxin the repo).SampleRateDialogopens from the Set sample rate button in the device card header, and wrapsSampleRateFormin a modal.
Here is dashboard/src/App.jsx:
import { useEffect, useState } from "react";
import { TopBar } from "./components/TopBar.jsx";
import { DeviceCard } from "./components/DeviceCard.jsx";
import { LiveChart } from "./components/LiveChart.jsx";
import { EventsCard, AlertsCard } from "./components/EventsAlerts.jsx";
import { useSimState } from "./hooks/useSimState.js";
const TWEAK_DEFAULTS = {
accentColor: "#157A3E",
deviceName: "SF-DEMO-001",
tempThreshold: 58,
pricePerKwh: 29.5,
};
export default function App() {
const [tweaks] = useState(TWEAK_DEFAULTS);
const { state, devSpikeTemp, acknowledgeAlert } = useSimState({
tempThreshold: tweaks.tempThreshold,
pricePerKwh: tweaks.pricePerKwh,
});
useEffect(() => {
document.documentElement.style.setProperty("--blue", tweaks.accentColor);
}, [tweaks.accentColor]);
return (
<div>
<TopBar />
<main style={{ padding: "20px 24px 32px", maxWidth: 1440, margin: "0 auto" }}>
<h1>Charger 1</h1>
<DeviceCard simState={state} onDevSpike={devSpikeTemp} tweaks={tweaks} />
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 12 }}>
<LiveChart title="Power draw" subtitle="power · mW · 5 min (live)" metric="power" color="#157A3E" unit="mW" />
<LiveChart title="Current draw" subtitle="current · mA · 5 min (live)" metric="current" color="#E09E00" unit="mA" />
<LiveChart title="Bus voltage" subtitle="volt · V · 5 min (live)" metric="volt" color="#1E5FAE" unit="V" />
</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
<EventsCard events={state.events} />
<AlertsCard alerts={state.alerts} onAck={acknowledgeAlert} />
</div>
</main>
</div>
);
}
TopBar, EventsCard, AlertsCard, and useSimState are UI chrome and simulated state that drive the visual demo. They aren't part of the RelayX integration. The components that matter for this tutorial are DeviceCard and LiveChart.
DeviceCard is a layout wrapper that composes the components you built. Inside it:
- The left metadata panel uses
useOnline,useDeviceConfig, and rendersSessionButtonat the bottom. - The right grid uses
useRelayLatestto drive threeArcGaugereadouts (not covered in this tutorial; see the UI Kit reference). - The header has a Set sample rate button that opens
SampleRateDialog, which wrapsSampleRateForm.
See dashboard/src/components/DeviceCard.jsx in the reference repo for the full source.
Test it
With the device running, reload http://localhost:5173. The full dashboard renders:
- A top bar
- A header with "Charger 1" and a styled device card (metadata panel + gauges)
- A Start session button in the metadata panel. Press it and the relay closes, the gauges move, the charts draw new points, and the session duration + energy fields start accumulating. Press Stop session and they freeze.
- A Set sample rate button in the card header. Open the dialog, set
250ms, and all charts tighten their cadence to four times per second. - Three live charts and the simulated events + alerts panels below.
That is the full end-to-end loop. You built the three components that carry the RelayX integration. The rest is styling and layout you can adapt to your own design system.