Add archived department filtering feature for superusers, including E2E tests, UI updates, and OpenAPI enhancements. Extend department table with archived toggle switch and update translations.
This commit is contained in:
+12
-1
@@ -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
|
||||
|
||||
+66
-28
@@ -1,18 +1,19 @@
|
||||
<script setup>
|
||||
import { computed, provide, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
import DepartmentsTable from "@/components/displays/superuser/tables/departmentsTable.vue";
|
||||
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
|
||||
import PaginationDisplayGeneralSearchReload from "@/components/displays/pagination/PaginationDisplayGeneralSearchReload.vue";
|
||||
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
|
||||
import { PaginatedListKey, usePaginatedList } from "@/components/pagination/paginatedList.vue";
|
||||
|
||||
let props = defineProps(["setCustomerFilter", "hideSearch", "autoLoad"]);
|
||||
|
||||
import { useRouter } from "vue-router";
|
||||
import {
|
||||
usePaginatedList,
|
||||
PaginatedListKey
|
||||
} from "@/components/pagination/paginatedList.vue";
|
||||
import { provide, computed } from "vue";
|
||||
const props = defineProps(["setCustomerFilter", "hideSearch", "autoLoad"]);
|
||||
const { t } = useI18n();
|
||||
const paginatedList = usePaginatedList();
|
||||
provide(PaginatedListKey, paginatedList);
|
||||
|
||||
const {
|
||||
isLoaded,
|
||||
isLoading,
|
||||
list,
|
||||
loadList,
|
||||
@@ -22,30 +23,27 @@ const {
|
||||
setEndpoint,
|
||||
setMetaItemsPerPage,
|
||||
setPage,
|
||||
metaSearch,
|
||||
search,
|
||||
setFilter,
|
||||
setOrder,
|
||||
hideSearchField,
|
||||
setHideSearchField,
|
||||
} = paginatedList;
|
||||
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
|
||||
|
||||
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
|
||||
import PaginationDisplay from "@/components/displays/pagination/PaginationDisplay.vue";
|
||||
import OrdersTable from "@/components/displays/department/pos/orders/ordersTable.vue";
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter();
|
||||
setEndpoint("/departments", false);
|
||||
setOrder('order_priority', 'asc');
|
||||
setOrder("order_priority", "asc");
|
||||
|
||||
const archivedFilter = ref("0");
|
||||
|
||||
const sortedList = computed(() => {
|
||||
return [...list.value].sort((a, b) => (a.order_priority || 0) - (b.order_priority || 0));
|
||||
});
|
||||
|
||||
// Hide the search field, if the hideSearch prop is set
|
||||
const handleArchivedFilterChange = () => {
|
||||
setFilter("archived", archivedFilter.value);
|
||||
};
|
||||
|
||||
setFilter("archived", archivedFilter.value, false);
|
||||
|
||||
if (props.hideSearch) {
|
||||
setHideSearchField(true);
|
||||
}
|
||||
@@ -53,17 +51,57 @@ if (props.hideSearch) {
|
||||
if (props.autoLoad) {
|
||||
loadList();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input @input="search($event.target.value)" class="input" type="text" :placeholder="$t('global.search_department')" v-if="!hideSearchField"/>
|
||||
<PaginationDisplay :metaItemsPerPage="metaItemsPerPage" :loadFunction="loadList" :isLoading="isLoading" :setMetaItemsPerPage="setMetaItemsPerPage"/>
|
||||
<PaginationDisplayGeneralSearchReload v-if="!hideSearchField" />
|
||||
<PaginationDisplay
|
||||
:metaItemsPerPage="metaItemsPerPage"
|
||||
:loadFunction="loadList"
|
||||
:isLoading="isLoading"
|
||||
:setMetaItemsPerPage="setMetaItemsPerPage"
|
||||
>
|
||||
<template #paginationColumns>
|
||||
<div class="column is-narrow my-3 department-status-filter">
|
||||
<label class="label is-small" for="department-archive-filter">{{
|
||||
t("objects.departments.filters.status")
|
||||
}}</label>
|
||||
<div class="control">
|
||||
<div class="select">
|
||||
<select
|
||||
id="department-archive-filter"
|
||||
v-model="archivedFilter"
|
||||
data-testid="superuser-departments-archive-filter"
|
||||
@change="handleArchivedFilterChange"
|
||||
>
|
||||
<option value="0">{{ t("objects.departments.filters.active") }}</option>
|
||||
<option value="1">{{ t("objects.departments.filters.archived") }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</PaginationDisplay>
|
||||
<div v-if="hideSearchField" class="mb-3">
|
||||
<button class="button is-dark" :class="{ 'is-loading': isLoading }" :disabled="isLoading" @click="loadList">
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-sync-alt" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span>{{ t("pagination.reload") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<DepartmentsTable :objects="sortedList" />
|
||||
<PaginationNavigation :currentPage="metaCurrentPage" :totalPages="Math.ceil(metaTotalItems / metaItemsPerPage)" :loadFunction="loadList" :setPage="setPage" :isLoading="isLoading" />
|
||||
<LoadButtonWhileAwait class="is-dark" :isLoading="isLoading" :loadFunction="loadList" icon="fas fa-sync-alt">{{ t('pagination.reload') }}</LoadButtonWhileAwait>
|
||||
<PaginationNavigation
|
||||
:currentPage="metaCurrentPage"
|
||||
:totalPages="Math.ceil(metaTotalItems / metaItemsPerPage)"
|
||||
:loadFunction="loadList"
|
||||
:setPage="setPage"
|
||||
:isLoading="isLoading"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
.department-status-filter {
|
||||
min-width: 14rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps(['objects']);
|
||||
import { ref } from 'vue';
|
||||
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
|
||||
const props = defineProps(["objects"]);
|
||||
import { ref } from "vue";
|
||||
import { departments, getDepartments, isLoading, getDepartmentName } from "@/components/pagination/departmentTabs.vue";
|
||||
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
|
||||
const { loadList, loadSwitch, metaCurrentPage, metaItemsPerPage, setList } = usePaginatedListInstance();
|
||||
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
|
||||
@@ -20,8 +16,8 @@ const dragOverIndex = ref(null);
|
||||
|
||||
const onDragStart = (event, index) => {
|
||||
draggingIndex.value = index;
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('text/plain', index);
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", index);
|
||||
};
|
||||
|
||||
const onDragOver = (event, index) => {
|
||||
@@ -78,7 +74,7 @@ const parseCustomerName = (user) => {
|
||||
} else {
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
};
|
||||
import { showEditDepartmentForm } from "@/components/forms/superUser/editDepartmentForm.vue";
|
||||
|
||||
import { showEditUserForm } from "@/components/forms/superUser/editUserForm.vue";
|
||||
@@ -86,102 +82,169 @@ import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
|
||||
const redirect = (path) => {
|
||||
window.location = path;
|
||||
}
|
||||
};
|
||||
|
||||
const archiveSwitchLoadingIds = ref(new Set());
|
||||
|
||||
const isDepartmentArchived = (department) => {
|
||||
return (
|
||||
department?.archived === true ||
|
||||
department?.archived === 1 ||
|
||||
department?.archived === "1" ||
|
||||
department?.archived === "true"
|
||||
);
|
||||
};
|
||||
|
||||
const isArchiveSwitchLoading = (departmentId) => {
|
||||
return archiveSwitchLoadingIds.value.has(departmentId);
|
||||
};
|
||||
|
||||
const setArchiveSwitchLoading = (departmentId, loading) => {
|
||||
const loadingIds = new Set(archiveSwitchLoadingIds.value);
|
||||
if (loading) {
|
||||
loadingIds.add(departmentId);
|
||||
} else {
|
||||
loadingIds.delete(departmentId);
|
||||
}
|
||||
archiveSwitchLoadingIds.value = loadingIds;
|
||||
};
|
||||
|
||||
const toggleArchived = async (department) => {
|
||||
const nextArchived = !isDepartmentArchived(department);
|
||||
setArchiveSwitchLoading(department.id, true);
|
||||
|
||||
try {
|
||||
await SessionUser.objects.departments.set.archived(department.id, nextArchived);
|
||||
department.archived = nextArchived;
|
||||
loadList();
|
||||
} finally {
|
||||
setArchiveSwitchLoading(department.id, false);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<table class="table is-fullwidth">
|
||||
<thead>
|
||||
<table class="table is-fullwidth" data-testid="superuser-departments-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-if="SessionUser.canAccessAdmin()" style="width: 40px;"></th>
|
||||
<th v-if="SessionUser.canAccessAdmin()" style="width: 40px"></th>
|
||||
<th>{{ SessionUser.objects.departments.columns.order_priority.label }}</th>
|
||||
<th>{{ SessionUser.objects.departments.columns.id.label }}</th>
|
||||
<th>{{ SessionUser.objects.departments.columns.name.label }}</th>
|
||||
<th>{{ SessionUser.objects.departments.columns.description.label }}</th>
|
||||
<th>{{ SessionUser.objects.departments.columns.economic_department_id.label }}</th>
|
||||
<th>{{ SessionUser.objects.departments.columns.archived.label }}</th>
|
||||
<th>{{ SessionUser.objects.departments.columns.latitude.label }}</th>
|
||||
<th>{{ SessionUser.objects.departments.columns.longitude.label }}</th>
|
||||
<th>{{ $t('tables.actions') }}</th>
|
||||
<th>{{ $t("tables.actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(department, index) in objects" :key="department.id"
|
||||
:class="{'is-dragging': draggingIndex === index, 'drag-over': dragOverIndex === index}"
|
||||
@dragover="onDragOver($event, index)"
|
||||
@dragleave="dragOverIndex = null"
|
||||
@drop="onDrop($event, index)"
|
||||
@dragend="onDragEnd">
|
||||
<td v-if="SessionUser.canAccessAdmin()"
|
||||
class="has-text-centered"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart($event, index)"
|
||||
style="cursor: move;">
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(department, index) in objects"
|
||||
:key="department.id"
|
||||
:data-testid="`superuser-departments-row-${department.id}`"
|
||||
:class="{ 'is-dragging': draggingIndex === index, 'drag-over': dragOverIndex === index }"
|
||||
@dragover="onDragOver($event, index)"
|
||||
@dragleave="dragOverIndex = null"
|
||||
@drop="onDrop($event, index)"
|
||||
@dragend="onDragEnd"
|
||||
>
|
||||
<td
|
||||
v-if="SessionUser.canAccessAdmin()"
|
||||
class="has-text-centered"
|
||||
draggable="true"
|
||||
@dragstart="onDragStart($event, index)"
|
||||
style="cursor: move"
|
||||
>
|
||||
<span class="icon is-small">
|
||||
<i class="fas fa-grip-lines"></i>
|
||||
</span>
|
||||
</td>
|
||||
<EditableTableColumn
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="order_priority"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="order_priority"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
/>
|
||||
<td>{{ department.id }}</td>
|
||||
<EditableTableColumn
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="name"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="name"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
/>
|
||||
<EditableTableColumn
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="description"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="description"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
/>
|
||||
<EditableTableColumn
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="economic_department_id"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
:parse-function="SessionUser.objects.economic_departments.functions.getEconomicDepartmentName"
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="economic_department_id"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
:parse-function="SessionUser.objects.economic_departments.functions.getEconomicDepartmentName"
|
||||
/>
|
||||
<td class="archive-switch-cell">
|
||||
<input
|
||||
:id="`department-archived-${department.id}`"
|
||||
class="switch is-rounded is-small"
|
||||
type="checkbox"
|
||||
:checked="isDepartmentArchived(department)"
|
||||
:disabled="isArchiveSwitchLoading(department.id)"
|
||||
:data-testid="`department-archive-toggle-${department.id}`"
|
||||
:aria-label="`${SessionUser.objects.departments.columns.archived.label}: ${department.name}`"
|
||||
@change="toggleArchived(department)"
|
||||
/>
|
||||
<label :for="`department-archived-${department.id}`"></label>
|
||||
</td>
|
||||
<EditableTableColumn
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="latitude"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
/>
|
||||
<EditableTableColumn
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="latitude"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
/>
|
||||
<EditableTableColumn
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="longitude"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
:object="department"
|
||||
:loadList="loadList"
|
||||
column="longitude"
|
||||
:edit-function="SessionUser.objects.departments.showEditObjectFieldForm"
|
||||
/>
|
||||
<td>
|
||||
<div class="buttons">
|
||||
<button class="button is-small" @click="showEditDepartmentForm(department.id, department.name, department.description, department.economic_department_id)">
|
||||
<button
|
||||
class="button is-small"
|
||||
@click="
|
||||
showEditDepartmentForm(
|
||||
department.id,
|
||||
department.name,
|
||||
department.description,
|
||||
department.economic_department_id
|
||||
)
|
||||
"
|
||||
>
|
||||
<span class="icon">
|
||||
<i class="fas fa-edit"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button class="button is-small" @click="redirect('/admin/' + department.id)">
|
||||
<!-- External link icon -->
|
||||
<span class="icon">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</span>
|
||||
<span class="icon">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</span>
|
||||
</button>
|
||||
<button class="button is-small is-dark" @click="redirect('/superuser/departments/' + department.id)">
|
||||
<span class="icon">
|
||||
<i class="fas fa-cog"></i>
|
||||
</span>
|
||||
<span class="icon">
|
||||
<i class="fas fa-cog"></i>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -191,4 +254,8 @@ const redirect = (path) => {
|
||||
.drag-over td {
|
||||
border-top: 3px solid #3273dc !important;
|
||||
}
|
||||
</style>
|
||||
.archive-switch-cell {
|
||||
min-width: 4rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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) => {
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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<string, unknown>) => ({
|
||||
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<Record<string, unknown>>) => ({
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user