Add customer complaints page for superusers with pagination, filtering, and e2e test coverage:
- Implemented `CustomerComplaints.vue` and `CustomerComplaintsPagination.vue` for managing complaints with department filters, search functionality, and sorting. - Added `CustomerComplaintsTable.vue` for displaying complaint details with edit and delete actions. - Introduced `DepartmentDailyReportComplaints` module for managing CRUD operations and validation. - Added e2e tests for complaint workflows, including pagination, filtering, and customer interaction validation (`superuser-customer-complaints.spec.ts`). - Included validation logic and error handling for phone call gate configurations (`DepartmentGates.vue`). - Enhanced department gate e2e tests with validation scenarios (`superuser-department-gates.spec.ts`).
This commit is contained in:
@@ -93,6 +93,8 @@ const degradedSnapshot = {
|
||||
probe_supported: true,
|
||||
status: "ok",
|
||||
status_reason: "OpenAI API connectivity confirmed.",
|
||||
status_reason_key: "http_ok",
|
||||
status_reason_params: { label: "OpenAI API" },
|
||||
checked_at: "2026-04-08T08:45:00.000Z",
|
||||
},
|
||||
{
|
||||
@@ -102,6 +104,8 @@ const degradedSnapshot = {
|
||||
probe_supported: true,
|
||||
status: "degraded",
|
||||
status_reason: "Bird API returned HTTP 503.",
|
||||
status_reason_key: "http_status",
|
||||
status_reason_params: { label: "Bird API", httpStatus: 503 },
|
||||
checked_at: "2026-04-08T08:45:00.000Z",
|
||||
},
|
||||
],
|
||||
@@ -115,6 +119,8 @@ const degradedSnapshot = {
|
||||
principal_id: 101,
|
||||
display_name: "Acme Logistics",
|
||||
context_label: "Customer 1001",
|
||||
context_label_key: "customer_number",
|
||||
context_label_params: { number: 1001 },
|
||||
customer_number_context: 1001,
|
||||
device_type: "desktop",
|
||||
user_agent: "Mozilla/5.0",
|
||||
@@ -126,6 +132,13 @@ const degradedSnapshot = {
|
||||
],
|
||||
},
|
||||
warnings: ["Redis is unavailable; module probe caching is bypassed."],
|
||||
warning_entries: [
|
||||
{
|
||||
key: "redis_cache_bypass",
|
||||
params: {},
|
||||
message: "Redis is unavailable; module probe caching is bypassed.",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const databaseCompatibilitySnapshot = {
|
||||
@@ -164,6 +177,51 @@ const databaseCompatibilitySnapshot = {
|
||||
warnings: [],
|
||||
};
|
||||
|
||||
const longReasonSnapshot = {
|
||||
...degradedSnapshot,
|
||||
modules: [
|
||||
{
|
||||
key: "shelly",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
probe_supported: true,
|
||||
status: "configured",
|
||||
status_reason: "Shelly cloud credentials are configured, but no known device id is available for a safe read-only probe.",
|
||||
status_reason_key: "shelly_no_device_id",
|
||||
status_reason_params: {},
|
||||
checked_at: "2026-04-08T08:45:00.000Z",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const longTitleSnapshot = {
|
||||
...degradedSnapshot,
|
||||
modules: [
|
||||
{
|
||||
key: "licenseplaterecognizer",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
probe_supported: true,
|
||||
status: "ok",
|
||||
status_reason: "License Plate Recognizer connectivity confirmed.",
|
||||
status_reason_key: "http_ok",
|
||||
status_reason_params: { label: "License Plate Recognizer" },
|
||||
checked_at: "2026-04-08T08:45:00.000Z",
|
||||
},
|
||||
{
|
||||
key: "virkdata",
|
||||
enabled: true,
|
||||
configured: true,
|
||||
probe_supported: false,
|
||||
status: "configured",
|
||||
status_reason: "Configuration is present, but no safe read-only probe is available.",
|
||||
status_reason_key: "safe_probe_unavailable",
|
||||
status_reason_params: {},
|
||||
checked_at: "2026-04-08T08:45:00.000Z",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
test.describe("Superuser system status smoke", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await mockApi(page, {
|
||||
@@ -173,7 +231,7 @@ test.describe("Superuser system status smoke", () => {
|
||||
await primeSuperuserSession(page);
|
||||
});
|
||||
|
||||
test("@smoke superuser status dashboard renders degraded infrastructure and recent sessions", async ({ page }) => {
|
||||
test("@smoke superuser status dashboard renders Danish translations for degraded infrastructure and recent sessions", async ({ page }) => {
|
||||
await installSystemStatusMock(page, degradedSnapshot);
|
||||
|
||||
await page.goto("/superuser");
|
||||
@@ -182,8 +240,15 @@ test.describe("Superuser system status smoke", () => {
|
||||
await expect(page.getByTestId("system-status-dashboard")).toBeVisible();
|
||||
await expect(page.getByTestId("status-card-database")).toContainText("truckwash");
|
||||
await expect(page.locator("body")).toContainText("Acme Logistics");
|
||||
await expect(page.locator("body")).toContainText("Kunde 1001");
|
||||
await expect(page.locator("body")).toContainText("Bruger");
|
||||
await expect(page.locator("body")).toContainText("Computer");
|
||||
await expect(page.locator("body")).toContainText("/superuser/vehicles");
|
||||
await expect(page.locator("body")).toContainText("Redis is unavailable; module probe caching is bypassed.");
|
||||
await expect(page.locator("body")).toContainText("Forbindelse til OpenAI API bekræftet.");
|
||||
await expect(page.locator("body")).toContainText("Redis er utilgængelig; cache til modulprober omgås.");
|
||||
await expect(page.locator("body")).not.toContainText("OpenAI API connectivity confirmed.");
|
||||
await expect(page.locator("body")).not.toContainText("Redis is unavailable; module probe caching is bypassed.");
|
||||
await expect(page.locator("body")).not.toContainText("Customer 1001");
|
||||
await expect(page.locator('a[href="/superuser/configuration/openai"]').last()).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -197,4 +262,74 @@ test.describe("Superuser system status smoke", () => {
|
||||
await expect(page.getByTestId("database-status-card")).toContainText("truckwash");
|
||||
await expect(page.getByTestId("database-status-card")).toContainText("8.0.36");
|
||||
});
|
||||
|
||||
test("@smoke superuser module cards keep long Danish status reasons below the header row", async ({ page }) => {
|
||||
await installSystemStatusMock(page, longReasonSnapshot);
|
||||
|
||||
await page.goto("/superuser");
|
||||
|
||||
const card = page.getByTestId("module-card-shelly");
|
||||
const title = card.locator(".module-card__title");
|
||||
const pill = card.locator(".status-pill");
|
||||
const reason = page.getByTestId("module-reason-shelly");
|
||||
|
||||
await expect(card).toBeVisible();
|
||||
await expect(reason).toContainText("der findes ikke et kendt device-id");
|
||||
|
||||
const [titleBox, pillBox, reasonBox] = await Promise.all([
|
||||
title.boundingBox(),
|
||||
pill.boundingBox(),
|
||||
reason.boundingBox(),
|
||||
]);
|
||||
|
||||
expect(titleBox).not.toBeNull();
|
||||
expect(pillBox).not.toBeNull();
|
||||
expect(reasonBox).not.toBeNull();
|
||||
|
||||
const headerBottom = Math.max(
|
||||
titleBox.y + titleBox.height,
|
||||
pillBox.y + pillBox.height
|
||||
);
|
||||
|
||||
expect(reasonBox.y).toBeGreaterThanOrEqual(headerBottom - 1);
|
||||
});
|
||||
|
||||
test("@smoke superuser module headers keep long titles and status pills inside their own cards", async ({ page }) => {
|
||||
await installSystemStatusMock(page, longTitleSnapshot);
|
||||
|
||||
await page.goto("/superuser");
|
||||
|
||||
const card = page.getByTestId("module-card-licenseplaterecognizer");
|
||||
const title = card.locator(".module-card__title");
|
||||
const pill = card.locator(".status-pill");
|
||||
|
||||
await expect(card).toBeVisible();
|
||||
await expect(title).toContainText("License Plate Recognizer");
|
||||
|
||||
const [cardBox, titleBox, pillBox] = await Promise.all([
|
||||
card.boundingBox(),
|
||||
title.boundingBox(),
|
||||
pill.boundingBox(),
|
||||
]);
|
||||
|
||||
expect(cardBox).not.toBeNull();
|
||||
expect(titleBox).not.toBeNull();
|
||||
expect(pillBox).not.toBeNull();
|
||||
|
||||
expect(titleBox.x).toBeGreaterThanOrEqual(cardBox.x - 1);
|
||||
expect(titleBox.x + titleBox.width).toBeLessThanOrEqual(cardBox.x + cardBox.width + 1);
|
||||
expect(pillBox.x).toBeGreaterThanOrEqual(cardBox.x - 1);
|
||||
expect(pillBox.x + pillBox.width).toBeLessThanOrEqual(cardBox.x + cardBox.width + 1);
|
||||
|
||||
const overlapWidth = Math.max(
|
||||
0,
|
||||
Math.min(titleBox.x + titleBox.width, pillBox.x + pillBox.width) - Math.max(titleBox.x, pillBox.x)
|
||||
);
|
||||
const overlapHeight = Math.max(
|
||||
0,
|
||||
Math.min(titleBox.y + titleBox.height, pillBox.y + pillBox.height) - Math.max(titleBox.y, pillBox.y)
|
||||
);
|
||||
|
||||
expect(overlapWidth * overlapHeight).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user