Add SelfServeQuestions module with pagination, routing, and admin navigation integration

- Added new `SelfServeQuestionsPagination.vue` and `SelfServeQuestionsTable.vue` components for managing self-service questions with pagination support.
- Introduced routing for self-service questions in the "Self-Serve" module under admin dashboard.
- Updated `NavigationMenuItemsAdmin.vue` to include "Self-Serve Questions" navigation item.
- Integrated filtering and auto-load functionality to support department and lane-specific views.
This commit is contained in:
Jeppe Bundgaard
2026-01-08 11:44:21 +01:00
parent 9c6ffd2297
commit 33ee8c1803
6 changed files with 221 additions and 0 deletions
@@ -0,0 +1,85 @@
<script setup>
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
const { loadList } = usePaginatedListInstance();
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
// Define the props
const props = defineProps({
objects: {
type: Array,
required: true,
},
});
</script>
<template>
<table class="table is-fullwidth is-bordered">
<thead>
<tr>
<th>{{ SessionUser.objects.self_serve_questions.columns.id.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.question.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.description.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.lane.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.product.label }}</th>
<th>{{ SessionUser.objects.self_serve_questions.columns.order_priority.label }}</th>
<th class="has-text-right">{{ SessionUser.objects.global.language.actions }}</th>
</tr>
</thead>
<tbody>
<tr v-for="object in objects" :key="object.id">
<td>{{ object.id }}</td>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="question"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="description"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="lane"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:parse-function="(laneId) => 'Bane ' + laneId"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="product"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:parse-function="(productId) => 'Produkt ' + productId"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<EditableTableColumn
:object="object"
:loadList="loadList"
column="order_priority"
:edit-function="SessionUser.objects.self_serve_questions.showEditObjectFieldForm"
:permission-check-function="SessionUser.canAccessAdmin"
/>
<!-- Actions -->
<td class="has-text-right">
<button class="button is-small is-danger is-light" @click="SessionUser.objects.self_serve_questions.functions.showDeleteObjectForm(object.id, loadList)">
<span class="icon is-small">
<i class="fas fa-trash"></i>
</span>
</button>
</td>
</tr>
</tbody>
</table>
</template>
<style scoped>
</style>
@@ -0,0 +1,88 @@
<script setup>
import SelfServeQuestionsTable from "@/components/displays/department/tables/SelfServeQuestionsTable.vue";
const props = defineProps({
setDepartmentFilter: {
type: [String, Number],
required: false,
default: null,
},
setLaneFilter: {
type: [String, Number],
required: false,
default: null,
},
hideSearch: {
type: Boolean,
required: false,
default: false,
},
autoLoad: {
type: Boolean,
required: false,
default: false,
},
});
import { defineProps } from "vue";
import {
usePaginatedList,
PaginatedListKey
} from "@/components/pagination/paginatedList.vue";
import { provide } from "vue";
const paginatedList = usePaginatedList();
provide(PaginatedListKey, paginatedList);
const {
list,
loadList,
setEndpoint,
setFilter,
setHideSearchField,
} = paginatedList;
import TableLabeledPagination from "@/components/displays/pagination/TableLabeledPagination.vue";
import {SessionUser} from "@/components/session/token/SessionUser.vue";
setEndpoint(SessionUser.objects.self_serve_questions.meta.endpoint, false);
// Hide the search field, if the hideSearch prop is set
if (props.hideSearch) {
setHideSearchField(true);
}
if (props.setDepartmentFilter) {
setFilter(
"department",
props.setDepartmentFilter
);
}
if (props.setLaneFilter) {
setFilter(
"lane",
props.setLaneFilter
);
}
if (props.autoLoad) {
loadList();
}
</script>
<template>
<TableLabeledPagination :label="SessionUser.objects.self_serve_questions.meta.title">
<template #buttons>
<button class="button is-primary is-small" @click="SessionUser.objects.self_serve_questions.showCreateObjectForm(loadList)">
<span class="icon is-small">
<i class="fas fa-plus"></i>
</span>
<span>Tilføj {{ SessionUser.objects.self_serve_questions.meta.labels.single }}</span>
</button>
</template>
<SelfServeQuestionsTable :objects="list" />
</TableLabeledPagination>
</template>
<style scoped>
</style>
@@ -138,6 +138,14 @@ const items = computed<NavigationItemProps[]>(() => [
permissions: ['admin', 'list_department_wash_lanes'],
hidden: !isDepartmentSet.value,
},
// Selvbetjeningsspørgsmål
{
label: firstToUpperCase(SessionUser.objects.self_serve_questions.meta.labels.multiple),
to: `/admin/${department_id.value}/modules/self-serve/questions`,
type: 'department',
permissions: ['admin'],
hidden: !isDepartmentSet.value,
},
// Notifikationer
{
label: firstToUpperCase(SessionUser.objects.notifications.meta.labels.multiple),
+8
View File
@@ -111,6 +111,8 @@ import DepartmentTimeBookingsNew
from "@/views/dashboards/departmentDashboard/modules/time-bookings/book/DepartmentTimeBookingsNew.vue";
import DepartmentWashLanes from "@/views/dashboards/departmentDashboard/modules/wash-lanes/DepartmentWashLanes.vue";
import DepartmentWashLane from "@/views/dashboards/departmentDashboard/modules/wash-lanes/DepartmentWashLane.vue";
import DepartmentSelfServeQuestions
from "@/views/dashboards/departmentDashboard/modules/self-serve/DepartmentSelfServeQuestions.vue";
import ConfigurationLimble from "@/views/dashboards/superUserDashboard/configuration/ConfigurationLimble.vue";
import MyVehicle from "@/views/dashboards/userDashboard/vehicles/MyVehicle.vue";
import XLVaskPage from "@/views/dashboards/superUserDashboard/XLVaskPage.vue";
@@ -405,6 +407,12 @@ export const router = createRouter({
component: DepartmentWashLane,
meta: { middleware: adminMiddleware, departmentSelection: true }
},
{
name: 'selfservequestions',
path: '/admin/:departmentId/modules/self-serve/questions',
component: DepartmentSelfServeQuestions,
meta: { middleware: adminMiddleware, departmentSelection: true }
},
{
name: 'superuser',
path: '/superuser',
@@ -0,0 +1,23 @@
<script setup>
import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrapper.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import DepartmentDashboardPageWrapper from "@/views/dashboards/departmentDashboard/DepartmentDashboardPageWrapper.vue";
import NotFoundFallBackPageWrapper from "@/components/page/wrappers/NotFoundFallBackPageWrapper.vue";
import SelfServeQuestionsPagination from "@/components/displays/pagination/models/DepartmentDashboard/SelfServeQuestionsPagination.vue";
import PageTitle from "@/components/global/PageTitle.vue";
</script>
<template>
<RestrictedPageWrapper :hasPermission="SessionUser.canAccessAdmin()">
<DepartmentDashboardPageWrapper>
<NotFoundFallBackPageWrapper :exists="SessionUser.functions.getDepartmentIdFromUrl() && SessionUser.canAccessDepartment(SessionUser.functions.getDepartmentIdFromUrl())" error="Please select a department, that you have access to. If you believe this is an error, please contact support.">
<PageTitle :title="SessionUser.objects.self_serve_questions.meta.title" :subtitle="SessionUser.objects.self_serve_questions.meta.description" />
<SelfServeQuestionsPagination :setDepartmentFilter="SessionUser.functions.getDepartmentIdFromUrl()" :autoLoad="true" />
</NotFoundFallBackPageWrapper>
</DepartmentDashboardPageWrapper>
</RestrictedPageWrapper>
</template>
<style scoped>
</style>
@@ -6,6 +6,7 @@ import RestrictedPageWrapper from "@/components/page/wrappers/RestrictedPageWrap
import DepartmentDashboardPageWrapper from "@/views/dashboards/departmentDashboard/DepartmentDashboardPageWrapper.vue";
import NotFoundFallBackPageWrapper from "@/components/page/wrappers/NotFoundFallBackPageWrapper.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import SelfServeQuestionsPagination from "@/components/displays/pagination/models/DepartmentDashboard/SelfServeQuestionsPagination.vue";
const router = useRouter();
const laneId = router.currentRoute.value.params.id;
@@ -118,6 +119,14 @@ onMounted(() => {
</div>
</div>
</div>
<div class="columns">
<div class="column is-12">
<div class="box">
<SelfServeQuestionsPagination :set-department-filter="departmentId" :set-lane-filter="laneId" :auto-load="true" />
</div>
</div>
</div>
</div>
<div v-else-if="isLoading" class="has-text-centered section">
<span class="icon is-large">