diff --git a/openapi.yaml b/openapi.yaml index 8d8d6dce..c68471f5 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -3375,7 +3375,7 @@ paths: tags: - Departments summary: List departments - description: Retrieve a list of all visible departments + description: Retrieve visible, active departments by default. Superuser department access may filter archived departments with `filters=archived:1`. operationId: listDepartments parameters: - name: id @@ -3386,6 +3386,11 @@ paths: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/PerPageParam' - $ref: '#/components/parameters/SearchParam' + - name: filters + in: query + schema: + type: string + description: Comma-separated field filters. `archived:1` is only honored for users with superuser department access. responses: '200': description: Departments retrieved successfully @@ -15652,6 +15657,8 @@ components: type: integer visible: type: boolean + archived: + type: boolean dimension: type: integer branding: @@ -15724,6 +15731,8 @@ components: type: integer visible: type: boolean + archived: + type: boolean longitude: type: number format: float @@ -15744,6 +15753,8 @@ components: type: string visible: type: boolean + archived: + type: boolean longitude: type: number format: float diff --git a/src/components/displays/pagination/models/SuperUserDashboard/DepartmentsPagination.vue b/src/components/displays/pagination/models/SuperUserDashboard/DepartmentsPagination.vue index 095bf92c..c99149bf 100644 --- a/src/components/displays/pagination/models/SuperUserDashboard/DepartmentsPagination.vue +++ b/src/components/displays/pagination/models/SuperUserDashboard/DepartmentsPagination.vue @@ -1,18 +1,19 @@ \ No newline at end of file +.department-status-filter { + min-width: 14rem; +} + diff --git a/src/components/displays/superuser/tables/departmentsTable.vue b/src/components/displays/superuser/tables/departmentsTable.vue index 3785a580..325f22a5 100644 --- a/src/components/displays/superuser/tables/departmentsTable.vue +++ b/src/components/displays/superuser/tables/departmentsTable.vue @@ -1,11 +1,7 @@ \ No newline at end of file +.archive-switch-cell { + min-width: 4rem; + white-space: nowrap; +} + diff --git a/src/components/session/token/SessionUser/Objects/Departments.vue b/src/components/session/token/SessionUser/Objects/Departments.vue index 15c1f39c..22f509ae 100644 --- a/src/components/session/token/SessionUser/Objects/Departments.vue +++ b/src/components/session/token/SessionUser/Objects/Departments.vue @@ -101,6 +101,14 @@ export const getDepartmentName = async (id) => { required: false } }, + archived: { + label: t('objects.departments.columns.archived'), + type: "boolean", + sortable: true, + creation: { + required: false + } + }, longitude: { label: t('objects.departments.columns.longitude'), type: "number", @@ -177,6 +185,14 @@ export const getDepartmentName = async (id) => { parseInt(dimension) ) }, + archived: async (id, archived) => { + return ObjectsGlobal.set.column( + Departments.meta.endpoint, + id, + "archived", + ObjectsGlobal.parse.boolean(archived) + ) + }, longitude: async (id, longitude) => { return ObjectsGlobal.set.column( Departments.meta.endpoint, @@ -237,4 +253,4 @@ export const getDepartmentName = async (id) => { ); } }; - \ No newline at end of file + diff --git a/src/components/session/token/SessionUser/Objects/ObjectsGlobal.vue b/src/components/session/token/SessionUser/Objects/ObjectsGlobal.vue index bd48711a..75b967d9 100644 --- a/src/components/session/token/SessionUser/Objects/ObjectsGlobal.vue +++ b/src/components/session/token/SessionUser/Objects/ObjectsGlobal.vue @@ -307,7 +307,7 @@ export const ObjectsGlobal = { }, parse: { boolean: (value) => { - return value === 'true' || value === true; + return value === 'true' || value === '1' || value === true || value === 1; }, number: (value) => { return parseInt(value); diff --git a/src/i18n/locales/da.json b/src/i18n/locales/da.json index ca3aef55..58f22be5 100644 --- a/src/i18n/locales/da.json +++ b/src/i18n/locales/da.json @@ -2512,6 +2512,7 @@ }, "departments": { "columns": { + "archived": "Arkiveret", "dimension": "Dimensjon", "economic_department": "E-conomic afdeling", "latitude": "Breddegrad", @@ -2519,6 +2520,11 @@ "slack_webhook": "Slack webhook" }, "description": "Oversigt over afdelinger", + "filters": { + "active": "Aktive afdelinger", + "archived": "Arkiverede afdelinger", + "status": "Status" + }, "multiple": "afdelinger", "single": "afdeling", "title": "Afdelinger" diff --git a/src/i18n/locales/de.json b/src/i18n/locales/de.json index f0fac958..5bba18ef 100644 --- a/src/i18n/locales/de.json +++ b/src/i18n/locales/de.json @@ -2512,6 +2512,7 @@ }, "departments": { "columns": { + "archived": "Archiviert", "dimension": "Abmessung", "economic_department": "E-conomic-Abteilung", "latitude": "Breitengrad", @@ -2519,6 +2520,11 @@ "slack_webhook": "Slack Webhook" }, "description": "?bersicht ?ber Abteilungen", + "filters": { + "active": "Aktive Abteilungen", + "archived": "Archivierte Abteilungen", + "status": "Status" + }, "multiple": "Abteilungen", "single": "Abteilung", "title": "Abteilungen" diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index df8d8c28..02178e5d 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -2512,6 +2512,7 @@ }, "departments": { "columns": { + "archived": "Archived", "dimension": "Dimension", "economic_department": "E-conomic Department", "latitude": "Latitude", @@ -2519,6 +2520,11 @@ "slack_webhook": "Slack Webhook" }, "description": "Overview of departments", + "filters": { + "active": "Active departments", + "archived": "Archived departments", + "status": "Status" + }, "multiple": "departments", "single": "department", "title": "Departments" diff --git a/src/i18n/locales/no.json b/src/i18n/locales/no.json index cf2262f4..8323e2a5 100644 --- a/src/i18n/locales/no.json +++ b/src/i18n/locales/no.json @@ -2512,6 +2512,7 @@ }, "departments": { "columns": { + "archived": "Arkivert", "dimension": "Dimensjon", "economic_department": "E-økonomisk avdeling", "latitude": "Breddegrad", @@ -2519,6 +2520,11 @@ "slack_webhook": "Slakk Webhook" }, "description": "Oversikt over avdelinger", + "filters": { + "active": "Aktive avdelinger", + "archived": "Arkiverte avdelinger", + "status": "Status" + }, "multiple": "avdelinger", "single": "avdeling", "title": "Avdelinger" diff --git a/src/i18n/locales/sv.json b/src/i18n/locales/sv.json index bc38edd2..20a23c0e 100644 --- a/src/i18n/locales/sv.json +++ b/src/i18n/locales/sv.json @@ -2512,6 +2512,7 @@ }, "departments": { "columns": { + "archived": "Arkiverad", "dimension": "Dimension", "economic_department": "E-conomic-avdelning", "latitude": "Latitude", @@ -2519,6 +2520,11 @@ "slack_webhook": "Slack Webhook" }, "description": "översikt över avdelningar", + "filters": { + "active": "Aktiva avdelningar", + "archived": "Arkiverade avdelningar", + "status": "Status" + }, "multiple": "avdelningar", "single": "avdelning", "title": "Avdelningar" diff --git a/src/services/departmentVisibility.js b/src/services/departmentVisibility.js index 869e4ae8..8f2f6244 100644 --- a/src/services/departmentVisibility.js +++ b/src/services/departmentVisibility.js @@ -22,8 +22,17 @@ export const hasValidDepartmentName = (department) => { return isDepartmentLabelValid(department?.name); }; +export const isDepartmentArchived = (department) => { + return ( + department?.archived === true + || department?.archived === 1 + || department?.archived === "1" + || department?.archived === "true" + ); +}; + export const isDepartmentVisible = (department) => { - return !( + return !isDepartmentArchived(department) && !( department?.visible === false || department?.visible === 0 || department?.visible === "0" diff --git a/tests/e2e/admin-department-visibility.spec.ts b/tests/e2e/admin-department-visibility.spec.ts index 83b37e5e..2717a0b0 100644 --- a/tests/e2e/admin-department-visibility.spec.ts +++ b/tests/e2e/admin-department-visibility.spec.ts @@ -9,6 +9,7 @@ const adminPermissions = [ "department_access_2", "department_access_3", "department_access_4", + "department_access_5", ]; const defaultDepartments = [ @@ -16,6 +17,7 @@ const defaultDepartments = [ { id: 2, name: "Hidden South", visible: false }, { id: 3, name: "Legacy East" }, { id: 4, name: "Numeric Hidden", visible: 0 }, + { id: 5, name: "Archived West", visible: true, archived: true }, ]; const json = (body: unknown, status = 200) => ({ @@ -180,6 +182,7 @@ test.describe("Admin department visibility", () => { await expect(departmentControls.getByRole("button", { name: "Legacy East" })).toBeVisible(); await expect(departmentControls.getByRole("button", { name: "Hidden South" })).toHaveCount(0); await expect(departmentControls.getByRole("button", { name: "Numeric Hidden" })).toHaveCount(0); + await expect(departmentControls.getByRole("button", { name: "Archived West" })).toHaveCount(0); if (isDesktopProject(test.info())) { const desktopDepartmentSelect = page.getByTestId("desktop-header-department-select"); @@ -187,6 +190,7 @@ test.describe("Admin department visibility", () => { await expect(desktopDepartmentSelect.locator("option", { hasText: "Legacy East" })).toHaveCount(1); await expect(desktopDepartmentSelect.locator("option", { hasText: "Hidden South" })).toHaveCount(0); await expect(desktopDepartmentSelect.locator("option", { hasText: "Numeric Hidden" })).toHaveCount(0); + await expect(desktopDepartmentSelect.locator("option", { hasText: "Archived West" })).toHaveCount(0); } }); diff --git a/tests/e2e/superuser-departments-archive.spec.ts b/tests/e2e/superuser-departments-archive.spec.ts new file mode 100644 index 00000000..2f8601d2 --- /dev/null +++ b/tests/e2e/superuser-departments-archive.spec.ts @@ -0,0 +1,93 @@ +import { expect, test } from "@playwright/test"; + +import { mockApi, seedAuthenticatedState } from "./support/network.js"; +import { isDesktopProject } from "./support/projects"; + +const json = (body: unknown, status = 200) => ({ + status, + contentType: "application/json", + body: JSON.stringify(body), +}); + +const now = "2026-05-07 10:00:00"; + +const buildDepartment = (department: Record) => ({ + description: "E2E department", + economic_department_id: 0, + created_at: now, + updated_at: now, + dimension: 0, + branding: 0, + latitude: 0, + longitude: 0, + order_priority: 1, + archived: false, + ...department, +}); + +const departmentEnvelope = (departments: Array>) => ({ + data: departments, + meta: { + pagination: { + page: 1, + per_page: 100, + total: departments.length, + }, + }, +}); + +test.describe("Superuser department archive filter", () => { + test("shows active departments by default and archived departments only after selecting archived", async ({ + page, + }, testInfo) => { + test.skip(!isDesktopProject(testInfo), "Desktop only"); + + const activeDepartments = [buildDepartment({ id: 1, name: "Active North", order_priority: 1, archived: false })]; + const archivedDepartments = [buildDepartment({ id: 2, name: "Archived East", order_priority: 1, archived: true })]; + const departmentFiltersSeen: string[] = []; + + await page.setViewportSize({ width: 1280, height: 720 }); + await seedAuthenticatedState(page, "superuser-departments-archive-token"); + await mockApi(page, { + authenticated: true, + permissions: ["superuser", "user", "list_departments", "superuser_fetch_department", "edit_department"], + sessionData: { + group_id: 1, + }, + }); + + await page.route( + /https?:\/\/(?:api\.truckwash\.io(?::\d+)?\/departments|localhost(?::\d+)?\/api\/departments|127\.0\.0\.1(?::\d+)?\/api\/departments)(?:\?.*)?$/i, + async (route) => { + const request = route.request(); + const url = new URL(request.url()); + + if (request.method() === "PUT") { + await route.fulfill(json({ data: { message: "Department updated successfully" } })); + return; + } + + const filters = url.searchParams.get("filters") || ""; + departmentFiltersSeen.push(filters); + const departments = filters.includes("archived:1") ? archivedDepartments : activeDepartments; + + await route.fulfill(json(departmentEnvelope(departments))); + } + ); + + await page.goto("/superuser/departments"); + + await expect(page.getByTestId("pagination-reload-actions")).toBeVisible(); + await expect(page.getByTestId("superuser-departments-table")).toBeVisible(); + await expect(page.getByTestId("superuser-departments-archive-filter")).toHaveValue("0"); + await expect(page.getByTestId("superuser-departments-row-1")).toContainText("Active North"); + await expect(page.getByTestId("superuser-departments-row-2")).toHaveCount(0); + + await page.getByTestId("superuser-departments-archive-filter").selectOption("1"); + + await expect(page.getByTestId("superuser-departments-row-2")).toContainText("Archived East"); + await expect(page.getByTestId("superuser-departments-row-1")).toHaveCount(0); + expect(departmentFiltersSeen.some((filters) => filters.includes("archived:0"))).toBeTruthy(); + expect(departmentFiltersSeen.some((filters) => filters.includes("archived:1"))).toBeTruthy(); + }); +});