Add interactive analytics drill-downs
Some checks are pending
CI / verify (push) Waiting to run

Make demo search, filters, exports, and controls functional while exposing dedicated KPI detail pages with tested breakdowns.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-25 13:14:22 -07:00
parent 135be480e5
commit 49f2c28a46
22 changed files with 1090 additions and 104 deletions

43
tests/analytics.test.ts Normal file
View File

@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { getDashboardData, getMetricDetails } from "@/domain/analytics";
describe("metric drill-down analytics", () => {
const dashboard = getDashboardData();
const details = getMetricDetails();
it("reconciles inventory value with batch totals", () => {
expect(details.inventoryValue.retailValue).toBe(dashboard.metrics.retailValue);
expect(details.inventoryValue.costValue).toBe(
dashboard.inventory.reduce((sum, item) => sum + item.costExposure, 0),
);
expect(details.inventoryValue.potentialMargin).toBe(
details.inventoryValue.retailValue - details.inventoryValue.costValue,
);
expect(details.inventoryValue.byBranch).toHaveLength(4);
});
it("uses complete current and prior revenue periods", () => {
expect(details.revenue.current30d).toBe(dashboard.metrics.revenue30d);
expect(details.revenue.current30d).toBeGreaterThan(0);
expect(details.revenue.prior30d).toBeGreaterThan(0);
expect(Number.isFinite(details.revenue.changePercent)).toBe(true);
expect(details.revenue.byProduct).toHaveLength(8);
});
it("limits headline risk exposure to at-risk and expired batches", () => {
expect(details.risk.affected.every(
(item) => item.status === "at_risk" || item.status === "expired",
)).toBe(true);
expect(details.risk.costValue).toBe(dashboard.metrics.atRiskValue);
expect(details.risk.byStatus.some((row) => row.status === "watch")).toBe(true);
});
it("provides bounded freshness rollups", () => {
expect(details.freshness.averageScore).toBe(dashboard.metrics.averageFreshness);
expect(details.freshness.weightedScore).toBeGreaterThanOrEqual(0);
expect(details.freshness.weightedScore).toBeLessThanOrEqual(100);
expect(details.freshness.freshUnitsPercent).toBeGreaterThanOrEqual(0);
expect(details.freshness.freshUnitsPercent).toBeLessThanOrEqual(100);
expect(details.freshness.byProduct).toHaveLength(8);
});
});

View File

@@ -7,6 +7,22 @@ test("shows inventory intelligence overview", async ({ page }) => {
await expect(page.getByText("Freshness watchlist")).toBeVisible();
});
test("opens every overview metric drill-down", async ({ page }) => {
const metrics = [
{ label: "Inventory value", path: "/metrics/inventory-value", heading: "Inventory value" },
{ label: "Revenue · 30 days", path: "/metrics/revenue", heading: "Revenue · 30 days" },
{ label: "At-risk exposure", path: "/metrics/at-risk", heading: "At-risk exposure" },
{ label: "Average freshness", path: "/metrics/freshness", heading: "Average freshness" },
];
for (const metric of metrics) {
await page.goto("/");
await page.getByRole("link", { name: `View details for ${metric.label}` }).click();
await expect(page).toHaveURL(metric.path);
await expect(page.getByRole("heading", { name: metric.heading })).toBeVisible();
}
});
test("moves from inventory to a product", async ({ page }) => {
await page.goto("/inventory");
await expect(page.getByRole("heading", { name: "Inventory" })).toBeVisible();