fix: add KPI tile widget type to the dashboard (#26)
CI / build-and-test (pull_request) Successful in 2m24s

Adds a non-chart dashboard widget: a large value with its label, an
optional signed delta versus the previous period, and an optional
sparkline. Four tiles now sit above the charts, 3x1 each.

- KpiTile draws no surface of its own and fills the grid item's, styled
  from the --dialect-* token layer; the delta's up/down colors are new
  tokens rather than literals, so they follow the light/dark toggle.
- Value and delta share one row: stacking them costs a line the tile does
  not have at its default height of one grid cell.
- SparklineChart is a chrome-free line chart built on DialectTheme, so it
  shares the palette and font with the real charts.
- ApexCharts' sparkline.enabled drops the axes on the initial render but
  updateOptions (how the theme toggle recolors a chart) brings the y-axis
  labels back; they are hidden in CSS, which reaches the chart SVG since
  it renders into light DOM.

The registry registration the issue asks for is left out: #21 is blocked,
so there is no widget picker to register with yet. The tiles are wired
into DashboardView the same way the chart widgets are, and their values
come from the translation bundle until the data service (#20) lands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
This commit is contained in:
Pit Friedrich
2026-07-28 19:57:55 +02:00
parent 1b846751dd
commit 21834ec9a0
10 changed files with 399 additions and 10 deletions
@@ -6,6 +6,7 @@ import com.example.components.Card;
import com.example.components.Fa;
import com.example.components.GridStackItem;
import com.example.components.GridStackLayout;
import com.example.components.KpiTile;
import com.example.components.LineChart;
import com.example.components.PieChart;
import com.vaadin.flow.component.Component;
@@ -44,14 +45,30 @@ public class DashboardView extends VerticalLayout implements HasDynamicTitle {
grid.addLayoutChangeListener(e -> status.setText(
getTranslation("gridstack.status", e.getPositions().size())));
// KPI tiles first: the numbers a dashboard is read for, above the charts
// that explain them. They are 3x1 — a quarter row each, one cell high.
grid.add(
new GridStackItem("revenue-trend", 0, 0, 6, 3,
new GridStackItem("kpi-revenue", 0, 0, 3, 1,
kpiTile("kpi.revenueTotal", 12.4,
List.of(30.0, 40.0, 35.0, 50.0, 49.0, 60.0))),
new GridStackItem("kpi-orders", 3, 0, 3, 1,
kpiTile("kpi.openOrders", -3.1,
List.of(52.0, 47.0, 49.0, 44.0, 40.0, 38.0))),
new GridStackItem("kpi-customers", 6, 0, 3, 1,
kpiTile("kpi.newCustomers", 8.0,
List.of(74.0, 81.0, 79.0, 95.0, 104.0, 112.0))),
new GridStackItem("kpi-order-value", 9, 0, 3, 1,
kpiTile("kpi.averageOrderValue", 0.0,
List.of(480.0, 492.0, 478.0, 489.0, 483.0, 486.0))));
grid.add(
new GridStackItem("revenue-trend", 0, 1, 6, 3,
new Card(getTranslation("card.revenueTrend"), lineChart())),
new GridStackItem("revenue-month", 6, 0, 6, 3,
new GridStackItem("revenue-month", 6, 1, 6, 3,
new Card(getTranslation("card.revenueByMonth"), barChart())),
new GridStackItem("revenue-region", 0, 3, 5, 3,
new GridStackItem("revenue-region", 0, 4, 5, 3,
new Card(getTranslation("card.revenueByRegion"), pieChart())),
new GridStackItem("hint", 5, 3, 7, 3,
new GridStackItem("hint", 5, 4, 7, 3,
new Card(getTranslation("card.gridstackHint"),
new Paragraph(getTranslation("gridstack.hint")))));
@@ -88,6 +105,16 @@ public class DashboardView extends VerticalLayout implements HasDynamicTitle {
new Card(title, new Paragraph(getTranslation("gridstack.widgetText")))));
}
/** The displayed value comes from the bundle alongside the label, since it
* carries locale-specific formatting (decimal separator, currency, "Mio.").
* Like the chart literals above, it moves to the data service in #20. */
private KpiTile kpiTile(String labelKey, double delta, List<Double> trend) {
String label = getTranslation(labelKey);
return new KpiTile(label, getTranslation(labelKey + ".value"))
.setDelta(delta)
.setSparkline(label, trend);
}
private Component lineChart() {
LineChart chart = new LineChart();
chart.setData(getTranslation("chart.revenueSeries"),