Files
pleno-vue/tests/e2e/admin-daily-report.spec.ts
T

782 lines
32 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { mockApi, seedAuthenticatedState } from "./support/network.js";
import { isCompactProject } from "./support/projects";
const adminPermissions = [
"admin",
"list_department_daily_reports",
"list_bookings",
"create_department_daily_report_complaints",
"department_access_1",
"department_access_2",
];
const json = (body: unknown, status = 200) => ({
status,
contentType: "application/json",
body: JSON.stringify(body),
});
const formatDisplayedDate = (isoDate: string) => {
const [year, month, day] = isoDate.split("-");
return `${day}/${month}/${year}`;
};
const buildOverviewPayload = ({
complaintsState = "ready",
complaintsValue = 2,
complaintsMessage = null,
metrics = {},
products = [
{
product_id: 24,
slug: "spot-free-lastbil",
title: "Spot Free (Lastbil)",
state: "ready",
value: 3,
out_of: 8,
target_percentage: null,
target_department_id: null,
},
{
product_id: 25,
slug: "faelg-flex",
title: "Fælg flex pr. enhed",
state: "ready",
value: 2,
out_of: 8,
target_percentage: null,
target_department_id: null,
},
{
product_id: 27,
slug: "extraordinary-10-min",
title: "Ekstraordinær pr. 10 min inkl. kemi",
state: "ready",
value: 1,
out_of: 8,
target_percentage: null,
target_department_id: null,
},
{
product_id: 26,
slug: "hoejglans",
title: "Højglans - Voksforsegling pr. enhed",
state: "ready",
value: 4,
out_of: 8,
target_percentage: null,
target_department_id: null,
},
{
product_id: 21,
slug: "undervognsskyl",
title: "Undervognsskyl pr. enhed",
state: "ready",
value: 2,
out_of: 8,
target_percentage: null,
target_department_id: null,
},
{
product_id: 22,
slug: "double-duty-kemi",
title: "Tillæg for Specialsæbe - DD",
state: "ready",
value: 5,
out_of: 8,
target_percentage: null,
target_department_id: null,
},
],
...overrides
}: Record<string, unknown> = {}) => ({
data: {
department_ids: [1],
date: "2026-03-23",
date_to: "2026-03-23",
metrics: {
bookings: { state: "ready", value: 4, out_of: 6, message: null },
complaints: {
state: complaintsState,
value: complaintsState === "ready" ? complaintsValue : null,
out_of: null,
message: complaintsMessage,
},
night_washes: { state: "ready", value: 1, out_of: null, message: null },
revenue: { state: "ready", value: 2400, out_of: null, message: null },
washes: { state: "ready", value: 8, out_of: null, message: null },
products_sold: { state: "ready", value: 19, out_of: null, message: null },
transactions: { state: "ready", value: 11, out_of: null, message: null },
water_usage: { state: "ready", value: 32, out_of: null, message: null },
overtime: { state: "ready", value: 1.5, out_of: null, message: null },
...metrics,
},
products,
...overrides,
},
});
async function mockDailyReportDependencies(
page,
input:
| {
overviewHandler: (url: URL) => Promise<ReturnType<typeof json>> | ReturnType<typeof json>;
weatherHandler?: (url: URL) => Promise<ReturnType<typeof json>> | ReturnType<typeof json>;
complaintHandler?: (
body: Record<string, unknown>
) => Promise<ReturnType<typeof json>> | ReturnType<typeof json>;
complaintCustomerLookupHandler?: (url: URL) => Promise<ReturnType<typeof json>> | ReturnType<typeof json>;
productTargetHandler?: (
body: Record<string, unknown>
) => Promise<ReturnType<typeof json>> | ReturnType<typeof json>;
permissions?: string[];
}
| ((url: URL) => Promise<ReturnType<typeof json>> | ReturnType<typeof json>)
) {
const options = typeof input === "function" ? { overviewHandler: input } : input;
const weatherHandler = options.weatherHandler || (async () => json({ data: [] }));
const complaintHandler =
options.complaintHandler ||
(async (body) =>
json({
data: {
id: 99,
department_id: body.department_id,
customer_number: body.customer_number ?? null,
wash_date: body.wash_date ?? null,
category: body.category ?? null,
description: body.description,
created_by: 1,
created_at: "2026-04-08 10:00:00",
},
}));
const complaintCustomerLookupHandler = options.complaintCustomerLookupHandler || (async () => json({ data: [] }));
const productTargetHandler =
options.productTargetHandler ||
(async (body) =>
json({
data: {
department_id: body.department_id,
product_id: body.product_id,
target_percentage: body.target_percentage ?? null,
},
}));
await seedAuthenticatedState(page);
await mockApi(page, {
authenticated: true,
permissions: options.permissions || adminPermissions,
});
await page.route(/\/departments(\?.*)?$/i, async (route) => {
await route.fulfill(
json({
data: [
{ id: 1, name: "North" },
{ id: 2, name: "South" },
],
})
);
});
await page.route(/\/departments\/weather(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const response = await weatherHandler(url);
await route.fulfill(response);
});
await page.route(/\/departments\/daily-reports\/get(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const date = url.searchParams.get("date") || "2026-03-23";
await route.fulfill(
json({
data: {
id: 10,
department_id: 1,
water_usage: 32,
notes: "",
filled_by: 1,
created_at: `${date} 00:00:00`,
},
})
);
});
await page.route(/\/departments\/daily-reports\/complaints$/i, async (route) => {
const body = route.request().postDataJSON() as Record<string, unknown>;
const response = await complaintHandler(body);
await route.fulfill(response);
});
await page.route(/\/departments\/daily-reports\/complaints\/customers(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
const response = await complaintCustomerLookupHandler(url);
await route.fulfill(response);
});
await page.route(/\/departments\/daily-reports\/product-targets$/i, async (route) => {
const body = route.request().postDataJSON() as Record<string, unknown>;
const response = await productTargetHandler(body);
await route.fulfill(response);
});
await page.route(/\/departments\/daily-reports(?:\/overview)?(\?.*)?$/i, async (route) => {
const url = new URL(route.request().url());
if (url.pathname.endsWith("/departments/daily-reports/overview")) {
const response = await options.overviewHandler(url);
await route.fulfill(response);
return;
}
await route.fulfill(json({ data: [] }));
});
}
async function expandDepartmentFiltersOnMobile(page, testInfo) {
if (!isCompactProject(testInfo)) {
return;
}
if (await page.getByTestId("daily-report-department-controls").count()) {
return;
}
await page.getByTestId("daily-report-mobile-departments-toggle").click();
await expect(page.getByTestId("daily-report-department-controls")).toBeVisible();
}
test.describe("Admin daily report", () => {
test("renders every daily report tile from the overview payload", async ({ page }) => {
await mockDailyReportDependencies(page, async () => json(buildOverviewPayload()));
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect(page.locator(".card[data-testid^='daily-report-tile-']")).toHaveCount(17);
await expect(page.getByTestId("daily-report-tile-bookings")).toContainText("4");
await expect(page.getByTestId("daily-report-tile-complaints")).toContainText("2");
await expect(page.getByTestId("daily-report-complaints-add-button")).toBeVisible();
await expect(page.getByTestId("daily-report-tile-night-washes")).toContainText("1");
await expect(page.getByTestId("daily-report-tile-overtime")).toContainText("1,5");
await expect(page.getByTestId("daily-report-tile-spot-free-lastbil")).toContainText("Spot Free (Lastbil)");
await expect(page.getByTestId("daily-report-tile-spot-free-lastbil")).toContainText("3");
await expect(page.getByTestId("daily-report-tile-faelg-flex")).toContainText("Fælg flex pr. enhed");
await expect(page.getByTestId("daily-report-tile-extraordinary-10-min")).toContainText(
"Ekstraordinær pr. 10 min inkl. kemi"
);
await expect(page.getByTestId("daily-report-tile-hoejglans")).toContainText("Højglans - Voksforsegling pr. enhed");
await expect(page.getByTestId("daily-report-tile-undervognsskyl")).toContainText("Undervognsskyl pr. enhed");
await expect(page.getByTestId("daily-report-tile-double-duty-kemi")).toContainText("Tillæg for Specialsæbe - DD");
});
test("lets permitted users set a target from an optional product percentage", async ({ page }) => {
let targetPercentage: number | null = null;
const targetRequests: Array<Record<string, unknown>> = [];
await mockDailyReportDependencies(page, {
permissions: [...adminPermissions, "set_department_daily_report_product_targets"],
overviewHandler: async () =>
json(
buildOverviewPayload({
products: [
{
product_id: 24,
slug: "spot-free-lastbil",
title: "Spot Free (Lastbil)",
state: "ready",
value: 3,
out_of: 8,
target_percentage: targetPercentage,
target_department_id: targetPercentage === null ? null : 1,
},
],
})
),
productTargetHandler: async (body) => {
targetRequests.push(body);
targetPercentage = body.target_percentage === null ? null : Number(body.target_percentage);
return json({
data: {
department_id: body.department_id,
product_id: body.product_id,
target_percentage: targetPercentage,
},
});
},
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
const productTile = page.getByTestId("daily-report-tile-spot-free-lastbil");
const percentageButton = page.getByTestId("daily-report-tile-spot-free-lastbil-percentage-button");
await expect(percentageButton).toBeVisible();
await expect(percentageButton).toHaveClass(/is-outlined/);
await expect(page.locator('[data-testid="daily-report-tile-spot-free-lastbil-target-button"]')).toHaveCount(0);
await percentageButton.click();
await page.getByTestId("daily-report-tile-spot-free-lastbil-target-input").fill("55.5");
await page.getByTestId("daily-report-tile-spot-free-lastbil-target-save").click();
await expect.poll(() => targetRequests.length).toBe(1);
expect(targetRequests[0]).toMatchObject({
department_id: 1,
product_id: 24,
target_percentage: 55.5,
});
await expect(page.getByTestId("daily-report-tile-spot-free-lastbil-target-button")).toContainText("55.5%");
await expect(productTile.locator(".daily-report-count__target-background-icon")).toHaveCount(1);
});
test("does not open the product target editor without permission", async ({ page }) => {
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload()),
permissions: adminPermissions,
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await page.getByTestId("daily-report-tile-spot-free-lastbil-percentage-button").click();
await expect(page.locator('[data-testid="daily-report-tile-spot-free-lastbil-target-input"]')).toHaveCount(0);
await expect(page.locator('[data-testid="daily-report-tile-spot-free-lastbil-target-button"]')).toHaveCount(0);
});
test("renders rounded-up weather hours and keeps unknown productivity gray", async ({ page }) => {
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload()),
weatherHandler: async () =>
json({
data: [
{
date: "2026-03-23",
time: "06:00",
current: false,
weather: "rain",
washes: 0,
hours: 0.5,
status: "unhealthy",
},
{
date: "2026-03-23",
time: "19:00",
current: false,
weather: "clear",
washes: 20,
hours: 19.0,
status: "degraded",
},
{
date: "2026-03-23",
time: "20:00",
current: false,
weather: "clear",
washes: 10,
hours: 20.19,
status: "unhealthy",
},
{
date: "2026-03-24",
time: "08:00",
current: false,
weather: "mostly_clear",
washes: 2,
hours: 0,
status: "unknown",
},
],
}),
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-24");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect(page.getByTestId("department-weather-hours-2026-03-23-06:00")).toHaveText("1");
await expect(page.getByTestId("department-weather-hours-2026-03-23-19:00")).toHaveText("19");
await expect(page.getByTestId("department-weather-hours-2026-03-23-20:00")).toHaveText("21");
await expect(page.getByTestId("department-weather-productivity-2026-03-23-06:00")).toHaveClass(/red/);
await expect(page.getByTestId("department-weather-productivity-2026-03-24-08:00")).toHaveClass(/gray/);
await page.getByTestId("department-weather-summary-toggle").click();
await expect(page.getByTestId("department-weather-hours-2026-03-23-00:00")).toHaveText("40");
await expect(page.getByTestId("department-weather-hours-2026-03-24-00:00")).toHaveText("0");
await expect(page.getByTestId("department-weather-productivity-2026-03-23-00:00")).toHaveClass(/red/);
await expect(page.getByTestId("department-weather-productivity-2026-03-24-00:00")).toHaveClass(/gray/);
});
test("hides future empty weather hours and keeps day summaries based on occurred hours only", async ({ page }) => {
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload()),
weatherHandler: async () =>
json({
data: [
{
date: "2026-03-23",
time: "12:00",
current: false,
weather: "clear",
washes: 3,
hours: 1,
status: "healthy",
},
{
date: "2026-03-24",
time: "13:00",
current: true,
weather: "rain",
washes: 1,
hours: 0.25,
status: "healthy",
},
{
date: "2026-03-24",
time: "14:00",
current: false,
weather: "rain",
washes: 0,
hours: 0,
status: "unknown",
},
],
}),
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-24");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect(page.getByTestId("department-weather-hours-2026-03-23-12:00")).toHaveText("1");
await expect(page.getByTestId("department-weather-hours-2026-03-24-13:00")).toHaveText("1");
await expect(page.getByTestId("department-weather-hours-2026-03-24-14:00")).toHaveCount(0);
await page.getByTestId("department-weather-summary-toggle").click();
await expect(page.getByTestId("department-weather-hours-2026-03-23-00:00")).toHaveText("1");
await expect(page.getByTestId("department-weather-hours-2026-03-24-00:00")).toHaveText("1");
});
test("opens and closes the complaints modal, requires wash date/category on multi-day ranges, clears a selected customer, submits without customer number, and refreshes the count", async ({
page,
}) => {
const complaintRequests: Array<Record<string, unknown>> = [];
let complaintCount = 2;
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload({ complaintsValue: complaintCount })),
complaintHandler: async (body) => {
complaintRequests.push(body);
complaintCount = 3;
return json({
data: {
id: 501,
department_id: body.department_id,
customer_number: null,
wash_date: body.wash_date,
category: body.category,
description: body.description,
created_by: 1,
created_at: "2026-04-08 10:00:00",
},
});
},
complaintCustomerLookupHandler: async () =>
json({
data: [{ customer_number: 12345, customer_name: "Acme Transport" }],
}),
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-24");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect(page.getByTestId("daily-report-tile-complaints")).toContainText("2");
const washDateInput = page.getByTestId("daily-report-complaints-wash-date-input");
await page.getByTestId("daily-report-complaints-add-button").click();
await expect(page.getByTestId("daily-report-complaints-modal")).toBeVisible();
await expect(page.getByTestId("daily-report-complaints-department-select")).toHaveValue("1");
await expect(washDateInput).toHaveValue("");
await expect(page.getByTestId("daily-report-complaints-date-note")).toContainText("konkrete vask");
await page.getByTestId("daily-report-complaints-cancel").click();
await expect(page.getByTestId("daily-report-complaints-modal")).toHaveCount(0);
await page.getByTestId("daily-report-complaints-add-button").click();
await page.getByTestId("daily-report-complaints-customer-search-input").fill("ac");
await expect(page.getByTestId("daily-report-complaints-customer-option-12345")).toBeVisible();
await expect(page.getByTestId("daily-report-complaints-description-textarea")).toBeVisible();
await page.getByTestId("daily-report-complaints-customer-option-12345").click();
await expect(page.getByTestId("daily-report-complaints-customer-selected")).toHaveValue("Acme Transport - 12345");
await page.getByTestId("daily-report-complaints-customer-clear").click();
await expect(page.getByTestId("daily-report-complaints-customer-search-input")).toBeVisible();
await page.getByTestId("daily-report-complaints-submit").click();
await expect(page.getByTestId("daily-report-complaints-validation-error")).toContainText("Dato for vask");
await washDateInput.fill("24/03/2026");
await washDateInput.press("Tab");
await expect(washDateInput).toHaveValue("24/03/2026");
await page.getByTestId("daily-report-complaints-submit").click();
await expect(page.getByTestId("daily-report-complaints-validation-error")).toContainText("Kategori");
await page.getByTestId("daily-report-complaints-category-select").selectOption("service");
await page.getByTestId("daily-report-complaints-description-textarea").fill("Delayed service response");
await page.getByTestId("daily-report-complaints-submit").click();
await expect.poll(() => complaintRequests.length).toBe(1);
expect(complaintRequests[0]).toEqual({
department_id: 1,
wash_date: "2026-03-24",
category: "service",
description: "Delayed service response",
});
await expect(page.getByTestId("daily-report-tile-complaints")).toContainText("3");
await expect(page.getByTestId("daily-report-complaints-modal")).toHaveCount(0);
});
test("submits complaints with an optional customer selected from search", async ({ page }) => {
const complaintRequests: Array<Record<string, unknown>> = [];
const customerLookupQueries: string[] = [];
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload({ complaintsValue: 2 })),
complaintHandler: async (body) => {
complaintRequests.push(body);
return json({
data: {
id: 502,
department_id: body.department_id,
customer_number: body.customer_number,
wash_date: body.wash_date,
category: body.category,
description: body.description,
created_by: 1,
created_at: "2026-04-08 10:10:00",
},
});
},
complaintCustomerLookupHandler: async (url) => {
customerLookupQueries.push(url.searchParams.get("search") || "");
return json({
data: [{ customer_number: 12345, customer_name: "Acme Transport" }],
});
},
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
const washDateInput = page.getByTestId("daily-report-complaints-wash-date-input");
await page.getByTestId("daily-report-complaints-add-button").click();
await expect(washDateInput).toHaveValue(formatDisplayedDate("2026-03-23"));
await expect(page.locator('[data-testid="daily-report-complaints-date-note"]')).toHaveCount(0);
await page.getByTestId("daily-report-complaints-customer-search-input").fill("ac");
await expect(page.getByTestId("daily-report-complaints-customer-option-12345")).toBeVisible();
await page.getByTestId("daily-report-complaints-customer-option-12345").click();
await expect(page.getByTestId("daily-report-complaints-customer-selected")).toHaveValue("Acme Transport - 12345");
await page.getByTestId("daily-report-complaints-category-select").selectOption("wash_quality");
await page.getByTestId("daily-report-complaints-description-textarea").fill("Customer-linked complaint");
await page.getByTestId("daily-report-complaints-submit").click();
await expect.poll(() => complaintRequests.length).toBe(1);
expect(customerLookupQueries).toContain("ac");
expect(complaintRequests[0]).toEqual({
department_id: 1,
customer_number: 12345,
wash_date: "2026-03-23",
category: "wash_quality",
description: "Customer-linked complaint",
});
});
test("blocks complaint submission when search text is typed without selecting a customer", async ({ page }) => {
const complaintRequests: Array<Record<string, unknown>> = [];
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload({ complaintsValue: 2 })),
complaintHandler: async (body) => {
complaintRequests.push(body);
return json({ data: body });
},
complaintCustomerLookupHandler: async () => json({ data: [] }),
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await page.getByTestId("daily-report-complaints-add-button").click();
await page.getByTestId("daily-report-complaints-customer-search-input").fill("12345");
await expect(page.getByTestId("daily-report-complaints-customer-no-results")).toBeVisible();
await page.getByTestId("daily-report-complaints-category-select").selectOption("service");
await page.getByTestId("daily-report-complaints-description-textarea").fill("Customer lookup not selected");
await page.getByTestId("daily-report-complaints-submit").click();
await expect(page.getByTestId("daily-report-complaints-validation-error")).toContainText("Vælg en kunde");
expect(complaintRequests).toHaveLength(0);
});
test("keeps the complaints modal open and shows an API error when submission fails", async ({ page }) => {
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload({ complaintsValue: 2 })),
complaintHandler: async () => json({ message: "Customer not found" }, 400),
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await page.getByTestId("daily-report-complaints-add-button").click();
await page.getByTestId("daily-report-complaints-category-select").selectOption("service");
await page.getByTestId("daily-report-complaints-description-textarea").fill("Broken mirror");
await page.getByTestId("daily-report-complaints-submit").click();
await expect(page.getByTestId("daily-report-complaints-modal")).toBeVisible();
await expect(page.getByTestId("daily-report-complaints-error")).toContainText("Customer not found");
});
test("hides the complaints plus action without create permission", async ({ page }) => {
await mockDailyReportDependencies(page, {
overviewHandler: async () => json(buildOverviewPayload({ complaintsValue: 2 })),
permissions: adminPermissions.filter((permission) => permission !== "create_department_daily_report_complaints"),
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect(page.locator('[data-testid="daily-report-complaints-add-button"]')).toHaveCount(0);
});
test("falls back to local optional product names when the overview payload omits titles", async ({ page }) => {
await mockDailyReportDependencies(page, async () =>
json(
buildOverviewPayload({
products: [
{ product_id: 24, slug: "spot-free-lastbil", title: "", state: "ready", value: 3, out_of: 8 },
{ product_id: 25, slug: "faelg-flex", title: null, state: "ready", value: 2, out_of: 8 },
{ product_id: 27, slug: "extraordinary-10-min", state: "ready", value: 1, out_of: 8 },
{ product_id: 26, slug: "hoejglans", title: " ", state: "ready", value: 4, out_of: 8 },
],
})
)
);
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect(page.getByTestId("daily-report-tile-spot-free-lastbil")).toContainText("Spot Free (Lastbil)");
await expect(page.getByTestId("daily-report-tile-faelg-flex")).toContainText("Fælg flex pr. enhed");
await expect(page.getByTestId("daily-report-tile-extraordinary-10-min")).toContainText(
"Ekstraordinær pr. 10 min inkl. kemi"
);
await expect(page.getByTestId("daily-report-tile-hoejglans")).toContainText("Højglans - Voksforsegling pr. enhed");
await expect(page.getByTestId("daily-report-tile-undervognsskyl")).toContainText("Undervognsskyl pr. enhed");
await expect(page.getByTestId("daily-report-tile-double-duty-kemi")).toContainText("Tillæg for Specialsæbe - DD");
});
test("updates departments and date query params with one overview request per UI action", async ({
page,
}, testInfo) => {
const overviewRequests: URL[] = [];
await mockDailyReportDependencies(page, async (url) => {
overviewRequests.push(url);
return json(
buildOverviewPayload({
department_ids: (url.searchParams.get("department_ids") || "1").split(",").map((value) => Number(value)),
date_to: url.searchParams.get("date_to") || "2026-03-23",
})
);
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
await expect.poll(() => overviewRequests.length).toBe(1);
await expandDepartmentFiltersOnMobile(page, testInfo);
await page.getByTestId("daily-report-department-2").click();
await expect.poll(() => overviewRequests.length).toBe(2);
expect(overviewRequests.at(-1)?.searchParams.get("department_ids")).toBe("1,2");
await expect(page).toHaveURL(/departmentIds=1%2C2|departmentIds=1,2/);
const endDateInput = page.getByTestId("daily-report-date-controls").locator("input[type='date']:visible").nth(1);
await endDateInput.fill("2026-03-24");
await endDateInput.blur();
await expect.poll(() => overviewRequests.length).toBe(3);
expect(overviewRequests.at(-1)?.searchParams.get("date_to")).toBe("2026-03-24");
await expect(page).toHaveURL(/dateTo=2026-03-24/);
});
test("ignores stale overview responses and keeps unavailable overtime fallback visible", async ({ page }) => {
const overviewRequests: URL[] = [];
await mockDailyReportDependencies(page, async (url) => {
overviewRequests.push(url);
const dateTo = url.searchParams.get("date_to") || "2026-03-23";
if (dateTo === "2026-03-23") {
await new Promise((resolve) => setTimeout(resolve, 400));
return json(
buildOverviewPayload({
metrics: {
bookings: { state: "ready", value: 1, out_of: 2, message: null },
complaints: {
state: "unavailable",
value: null,
out_of: null,
message: "Kundeklager er ikke tilgængelig endnu.",
},
night_washes: { state: "ready", value: 0, out_of: null, message: null },
revenue: { state: "ready", value: 100, out_of: null, message: null },
washes: { state: "ready", value: 1, out_of: null, message: null },
products_sold: { state: "ready", value: 2, out_of: null, message: null },
transactions: { state: "ready", value: 1, out_of: null, message: null },
water_usage: { state: "ready", value: 3, out_of: null, message: null },
overtime: { state: "ready", value: 0.25, out_of: null, message: null },
},
})
);
}
return json(
buildOverviewPayload({
date_to: "2026-03-24",
metrics: {
bookings: { state: "ready", value: 6, out_of: 9, message: null },
complaints: {
state: "unavailable",
value: null,
out_of: null,
message: "Kundeklager er ikke tilgængelig endnu.",
},
night_washes: { state: "ready", value: 3, out_of: null, message: null },
revenue: { state: "ready", value: 3200, out_of: null, message: null },
washes: { state: "ready", value: 10, out_of: null, message: null },
products_sold: { state: "ready", value: 20, out_of: null, message: null },
transactions: { state: "ready", value: 9, out_of: null, message: null },
water_usage: { state: "ready", value: 40, out_of: null, message: null },
overtime: {
state: "unavailable",
value: null,
out_of: null,
message: "Overarbejde kræver Workfeed-kobling.",
},
},
})
);
});
await page.goto("/admin/1/modules/daily-report?departmentIds=1&dateFrom=2026-03-23&dateTo=2026-03-23");
await expect(page.getByTestId("daily-report-page")).toBeVisible();
const endDateInput = page.getByTestId("daily-report-date-controls").locator("input[type='date']:visible").nth(1);
await endDateInput.fill("2026-03-24");
await endDateInput.blur();
await expect.poll(() => overviewRequests.length).toBe(2);
expect(overviewRequests.at(-1)?.searchParams.get("date_to")).toBe("2026-03-24");
await expect(page).toHaveURL(/dateTo=2026-03-24/);
await expect(page.getByTestId("daily-report-tile-transactions")).toContainText("9", { timeout: 10000 });
await expect(page.getByTestId("daily-report-tile-night-washes")).toContainText("3");
await expect(page.getByTestId("daily-report-tile-overtime")).toContainText("Ikke tilgængelig");
await expect(page.getByTestId("daily-report-tile-overtime")).toContainText("Workfeed");
});
});