fix: pull dashboard widget data from a ChartDataService (#20)
CI / build-and-test (pull_request) Successful in 2m32s
CI / build-and-test (pull_request) Successful in 2m32s
Chart and KPI numbers were literals in DashboardView, duplicated between the line and bar chart, and only existed at construction time. Add a thin data layer as the seam for later refresh/filter work: - ChartSeries / KpiData records, immutable, carrying translation keys rather than display text so the data layer stays free of a UI locale. - ChartDataService interface plus an in-memory implementation returning the previous hardcoded numbers, so nothing changes visually. - DashboardView injects the service, lays out the KPI tiles from its list (grid ids come from the data, so saved layouts still match), and resolves the keys against the bundle. Also fixes DashboardChartPlaywrightTest, red on main since the KPI tiles landed: it patched the first apex-chart, which is now a sparkline with no xaxis.categories, so the axis-chart patch was a structural change and legitimately redrew the SVG. It now targets an axis chart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124BiJikbhbiEsNfJxdWM69
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.example.views;
|
||||
|
||||
import com.example.components.ApexChart;
|
||||
import com.example.components.AxisChart;
|
||||
import com.example.components.BarChart;
|
||||
import com.example.components.Card;
|
||||
import com.example.components.Fa;
|
||||
@@ -9,6 +10,9 @@ import com.example.components.GridStackLayout;
|
||||
import com.example.components.KpiTile;
|
||||
import com.example.components.LineChart;
|
||||
import com.example.components.PieChart;
|
||||
import com.example.data.ChartDataService;
|
||||
import com.example.data.ChartSeries;
|
||||
import com.example.data.KpiData;
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
@@ -27,17 +31,26 @@ import java.util.List;
|
||||
* layout is persisted per browser, plus controls to add widgets and reset the
|
||||
* layout at runtime. Widgets are removed by their own close button (see
|
||||
* {@link GridStackItem#setClosable(boolean)}), not from the toolbar.
|
||||
* <p>
|
||||
* All widget numbers come from {@link ChartDataService}; this view only decides
|
||||
* where a widget sits and resolves the data's translation keys against the
|
||||
* bundle.
|
||||
*/
|
||||
@Route("")
|
||||
public class DashboardView extends VerticalLayout implements HasDynamicTitle {
|
||||
|
||||
private static final String STORAGE_KEY = "dashboard";
|
||||
|
||||
/** A KPI tile is a quarter row wide, so the n-th one starts at 3n. */
|
||||
private static final int KPI_WIDTH = 3;
|
||||
|
||||
private final ChartDataService dataService;
|
||||
private final GridStackLayout grid = new GridStackLayout();
|
||||
private final Span status = new Span();
|
||||
private int extraWidgetCount;
|
||||
|
||||
public DashboardView() {
|
||||
public DashboardView(ChartDataService dataService) {
|
||||
this.dataService = dataService;
|
||||
addClassName("dialect-content");
|
||||
|
||||
grid.setWidthFull();
|
||||
@@ -46,20 +59,15 @@ public class DashboardView extends VerticalLayout implements HasDynamicTitle {
|
||||
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("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))));
|
||||
// that explain them. They are 3x1 — a quarter row each, one cell high,
|
||||
// laid out left to right in the order the service returns them. The
|
||||
// grid id is the KPI's own id, so it survives reordering.
|
||||
List<KpiData> kpis = dataService.kpis();
|
||||
for (int i = 0; i < kpis.size(); i++) {
|
||||
KpiData kpi = kpis.get(i);
|
||||
grid.add(new GridStackItem(kpi.id(), i * KPI_WIDTH, 0, KPI_WIDTH, 1,
|
||||
kpiTile(kpi)));
|
||||
}
|
||||
|
||||
grid.add(
|
||||
new GridStackItem("revenue-trend", 0, 1, 6, 3,
|
||||
@@ -106,38 +114,37 @@ public class DashboardView extends VerticalLayout implements HasDynamicTitle {
|
||||
}
|
||||
|
||||
/** 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);
|
||||
* carries locale-specific formatting (decimal separator, currency, "Mio.");
|
||||
* the service supplies the key, not the formatted text. */
|
||||
private KpiTile kpiTile(KpiData kpi) {
|
||||
String label = getTranslation(kpi.labelKey());
|
||||
return new KpiTile(label, getTranslation(kpi.valueKey()))
|
||||
.setDelta(kpi.deltaPercent())
|
||||
.setSparkline(label, kpi.trend());
|
||||
}
|
||||
|
||||
private Component lineChart() {
|
||||
LineChart chart = new LineChart();
|
||||
chart.setData(getTranslation("chart.revenueSeries"),
|
||||
List.of(30.0, 40.0, 35.0, 50.0, 49.0, 60.0), months());
|
||||
chart.addPointClickListener(e -> Notification.show(getTranslation(
|
||||
"chart.pointClick", e.getSeriesIndex(), e.getDataPointIndex())));
|
||||
return sizeFull(chart);
|
||||
return axisChart(new LineChart());
|
||||
}
|
||||
|
||||
private Component barChart() {
|
||||
BarChart chart = new BarChart();
|
||||
chart.setData(getTranslation("chart.revenueSeries"),
|
||||
List.of(30.0, 40.0, 35.0, 50.0, 49.0, 60.0), months());
|
||||
return axisChart(new BarChart());
|
||||
}
|
||||
|
||||
private Component axisChart(AxisChart chart) {
|
||||
ChartSeries series = dataService.revenueByMonth();
|
||||
chart.setData(getTranslation(series.nameKey()), series.values(),
|
||||
translate(series.categoryKeys()));
|
||||
chart.addPointClickListener(e -> Notification.show(getTranslation(
|
||||
"chart.pointClick", e.getSeriesIndex(), e.getDataPointIndex())));
|
||||
return sizeFull(chart);
|
||||
}
|
||||
|
||||
private Component pieChart() {
|
||||
ChartSeries series = dataService.revenueByRegion();
|
||||
PieChart chart = new PieChart();
|
||||
chart.setData(List.of(30.0, 40.0, 35.0, 50.0),
|
||||
List.of(getTranslation("region.north"), getTranslation("region.south"),
|
||||
getTranslation("region.east"), getTranslation("region.west")));
|
||||
// A pie has no series name — its categories are the slice labels.
|
||||
chart.setData(series.values(), translate(series.categoryKeys()));
|
||||
chart.addPointClickListener(e -> Notification.show(
|
||||
getTranslation("chart.sliceClick", e.getDataPointIndex())));
|
||||
return sizeFull(chart);
|
||||
@@ -152,11 +159,10 @@ public class DashboardView extends VerticalLayout implements HasDynamicTitle {
|
||||
return chart;
|
||||
}
|
||||
|
||||
private List<String> months() {
|
||||
return List.of(
|
||||
getTranslation("month.jan"), getTranslation("month.feb"),
|
||||
getTranslation("month.mar"), getTranslation("month.apr"),
|
||||
getTranslation("month.may"), getTranslation("month.jun"));
|
||||
/** Data sets carry translation keys, not display text — see
|
||||
* {@link ChartSeries}. */
|
||||
private List<String> translate(List<String> keys) {
|
||||
return keys.stream().map(this::getTranslation).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user