549 lines
16 KiB
Vue
549 lines
16 KiB
Vue
<script lang="ts">
|
|
import { NavigationItemProps } from "@/components/models/navigation/NavigationItem.vue";
|
|
import { defineComponent, ref, computed, watch } from "vue";
|
|
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
import {
|
|
departments_cache,
|
|
getDepartmentName,
|
|
} from "@/components/session/token/SessionUser/Objects/economicDepartments.vue";
|
|
import i18n from "@/i18n";
|
|
import {
|
|
ensureDraftTransactionCustomerLoaded,
|
|
getDraftTransactionCustomerNumber,
|
|
} from "@/composables/useDraftTransactionCustomer.js";
|
|
import { fetchDepartmentOrderBookingCounts } from "@/components/models/navigation/items/adminBookingCount.js";
|
|
import { fetchDepartmentDraftCount } from "@/components/models/navigation/items/adminDraftCount.js";
|
|
import { hasExplicitBookingCountPermission } from "@/components/models/navigation/items/bookingCountGuards.js";
|
|
import { NAVIGATION_COUNT_REFRESH_EVENT } from "@/components/models/navigation/items/navigationCountEvents.js";
|
|
import { isAccessibleVisibleNamedDepartment } from "@/services/departmentVisibility.js";
|
|
|
|
const t = (key: string) => i18n.global.t(key);
|
|
|
|
const ADMIN_DEPARTMENT_SELECTION_CLASS = "js-admin-department-selection";
|
|
|
|
const routeContext = ref({
|
|
path: window.location.pathname || "",
|
|
params: {} as Record<string, unknown>,
|
|
});
|
|
|
|
const syncAdminNavigationRoute = (route: any) => {
|
|
routeContext.value = {
|
|
path: String(route?.path ?? window.location.pathname ?? ""),
|
|
params: (route?.params ?? {}) as Record<string, unknown>,
|
|
};
|
|
};
|
|
|
|
const department_id = computed(() => {
|
|
const currentRoute = routeContext.value;
|
|
|
|
const fromParams = Number.parseInt(String(currentRoute?.params?.departmentId ?? ""), 10);
|
|
if (Number.isInteger(fromParams) && fromParams > 0) {
|
|
return fromParams;
|
|
}
|
|
|
|
const fromPath = Number.parseInt(String(currentRoute?.path?.match(/^\/admin\/(\d+)(?:\/|$)/)?.[1] ?? ""), 10);
|
|
if (Number.isInteger(fromPath) && fromPath > 0) {
|
|
return fromPath;
|
|
}
|
|
|
|
return undefined;
|
|
});
|
|
const firstToUpperCase = (str: string) => {
|
|
if (!str) return "";
|
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
};
|
|
const department_booking_counts = ref({
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
});
|
|
const department_booking_counts_loading = ref(false);
|
|
const department_draft_count = ref(0);
|
|
const department_draft_count_loading = ref(false);
|
|
const draftTransactionCustomerNumber = computed(() => getDraftTransactionCustomerNumber());
|
|
const canUseAdminNavigationCountsValue = computed(() => canUseAdminNavigationCounts());
|
|
let bookingCountRequestId = 0;
|
|
let draftCountRequestId = 0;
|
|
let bookingCountFetchInFlight = false;
|
|
let draftCountFetchInFlight = false;
|
|
let bookingCountRefreshQueued = false;
|
|
let draftCountRefreshQueued = false;
|
|
let queuedBookingLoadingIndicator = false;
|
|
let queuedDraftLoadingIndicator = false;
|
|
const getDepartmentIdNumber = () => Number.parseInt(String(department_id.value), 10);
|
|
const canUseAdminNavigationCounts = () => SessionUser.canAccessAdmin();
|
|
const canUseAdminBookingNavigationCounts = () =>
|
|
canUseAdminNavigationCounts() && hasExplicitBookingCountPermission(SessionUser);
|
|
const getDepartmentById = (id: number) => {
|
|
return departments_cache.value?.find((department: any) => Number(department?.id) === Number(id)) || null;
|
|
};
|
|
const hasValidDepartmentId = computed(() => {
|
|
const id = getDepartmentIdNumber();
|
|
return Number.isInteger(id) && id > 0;
|
|
});
|
|
|
|
const getBookingsLoadingBadge = () => ({
|
|
type: "info" as const,
|
|
text: "",
|
|
condition: true,
|
|
classes: ["desktop-buefy-badge-loading"],
|
|
testId: "desktop-buefy-nav-bookings-badge-loading",
|
|
tooltip: "Indlæser bookinger",
|
|
tooltipTestId: "desktop-buefy-nav-bookings-badge-loading-tooltip",
|
|
});
|
|
|
|
const getDepartmentBookingBadges = () => {
|
|
if (department_booking_counts_loading.value) {
|
|
return [getBookingsLoadingBadge()];
|
|
}
|
|
|
|
return [
|
|
{
|
|
type: "danger" as const,
|
|
text: department_booking_counts.value.past.toString(),
|
|
condition: department_booking_counts.value.past > 0,
|
|
testId: "desktop-buefy-nav-bookings-badge-overdue",
|
|
tooltip: "Tidligere",
|
|
tooltipTestId: "desktop-buefy-nav-bookings-badge-overdue-tooltip",
|
|
},
|
|
{
|
|
type: "info" as const,
|
|
text: department_booking_counts.value.current.toString(),
|
|
condition: department_booking_counts.value.current > 0,
|
|
testId: "desktop-buefy-nav-bookings-badge",
|
|
tooltip: "I dag",
|
|
tooltipTestId: "desktop-buefy-nav-bookings-badge-tooltip",
|
|
},
|
|
{
|
|
type: "info" as const,
|
|
text: department_booking_counts.value.future.toString(),
|
|
condition: department_booking_counts.value.future > 0,
|
|
classes: ["desktop-buefy-bookings-badge-future"],
|
|
testId: "desktop-buefy-nav-bookings-badge-future",
|
|
tooltip: "Fremtidige",
|
|
tooltipTestId: "desktop-buefy-nav-bookings-badge-future-tooltip",
|
|
},
|
|
];
|
|
};
|
|
|
|
const getDraftsLoadingBadge = () => ({
|
|
type: "info" as const,
|
|
text: "",
|
|
condition: true,
|
|
classes: ["desktop-buefy-badge-loading"],
|
|
testId: "desktop-buefy-nav-drafts-badge-loading",
|
|
tooltip: "Indlæser kladder",
|
|
tooltipTestId: "desktop-buefy-nav-drafts-badge-loading-tooltip",
|
|
});
|
|
|
|
const getDepartmentDraftBadge = () => {
|
|
if (department_draft_count_loading.value) {
|
|
return getDraftsLoadingBadge();
|
|
}
|
|
|
|
return {
|
|
type: "info" as const,
|
|
text: department_draft_count.value.toString(),
|
|
condition: department_draft_count.value > 0,
|
|
testId: "desktop-buefy-nav-drafts-badge",
|
|
tooltip: t("nav.drafts"),
|
|
tooltipTestId: "desktop-buefy-nav-drafts-badge-tooltip",
|
|
};
|
|
};
|
|
|
|
const fetchDepartmentBookingCount = async ({ showLoadingIndicator = false } = {}) => {
|
|
if (!canUseAdminBookingNavigationCounts()) {
|
|
department_booking_counts.value = {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
};
|
|
department_booking_counts_loading.value = false;
|
|
bookingCountFetchInFlight = false;
|
|
bookingCountRefreshQueued = false;
|
|
queuedBookingLoadingIndicator = false;
|
|
return;
|
|
}
|
|
|
|
if (!hasValidDepartmentId.value) {
|
|
department_booking_counts.value = {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
};
|
|
department_booking_counts_loading.value = false;
|
|
bookingCountFetchInFlight = false;
|
|
bookingCountRefreshQueued = false;
|
|
queuedBookingLoadingIndicator = false;
|
|
return;
|
|
}
|
|
|
|
if (bookingCountFetchInFlight) {
|
|
bookingCountRefreshQueued = true;
|
|
queuedBookingLoadingIndicator = queuedBookingLoadingIndicator || showLoadingIndicator;
|
|
return;
|
|
}
|
|
|
|
if (showLoadingIndicator) {
|
|
department_booking_counts_loading.value = true;
|
|
department_booking_counts.value = {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
};
|
|
}
|
|
|
|
bookingCountFetchInFlight = true;
|
|
const requestId = ++bookingCountRequestId;
|
|
const id = getDepartmentIdNumber();
|
|
let didApply = false;
|
|
|
|
try {
|
|
const nextBookingCounts = await fetchDepartmentOrderBookingCounts({
|
|
departmentId: id,
|
|
});
|
|
|
|
if (requestId !== bookingCountRequestId || id !== getDepartmentIdNumber()) {
|
|
return;
|
|
}
|
|
|
|
department_booking_counts.value = nextBookingCounts;
|
|
department_booking_counts_loading.value = false;
|
|
didApply = true;
|
|
} finally {
|
|
bookingCountFetchInFlight = false;
|
|
|
|
const shouldRerun = bookingCountRefreshQueued;
|
|
const shouldShowLoadingIndicator = queuedBookingLoadingIndicator;
|
|
|
|
bookingCountRefreshQueued = false;
|
|
queuedBookingLoadingIndicator = false;
|
|
|
|
if (shouldRerun) {
|
|
void fetchDepartmentBookingCount({
|
|
showLoadingIndicator: shouldShowLoadingIndicator,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!didApply) {
|
|
department_booking_counts_loading.value = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
const fetchCurrentDepartmentDraftCount = async ({ showLoadingIndicator = false } = {}) => {
|
|
if (!hasValidDepartmentId.value || draftTransactionCustomerNumber.value === null) {
|
|
department_draft_count.value = 0;
|
|
department_draft_count_loading.value = false;
|
|
draftCountFetchInFlight = false;
|
|
draftCountRefreshQueued = false;
|
|
queuedDraftLoadingIndicator = false;
|
|
return;
|
|
}
|
|
|
|
if (draftCountFetchInFlight) {
|
|
draftCountRefreshQueued = true;
|
|
queuedDraftLoadingIndicator = queuedDraftLoadingIndicator || showLoadingIndicator;
|
|
return;
|
|
}
|
|
|
|
if (showLoadingIndicator) {
|
|
department_draft_count_loading.value = true;
|
|
department_draft_count.value = 0;
|
|
}
|
|
|
|
draftCountFetchInFlight = true;
|
|
const requestId = ++draftCountRequestId;
|
|
const currentDepartmentId = getDepartmentIdNumber();
|
|
const currentCustomerNumber = draftTransactionCustomerNumber.value;
|
|
let didApply = false;
|
|
|
|
try {
|
|
const nextDraftCount = await fetchDepartmentDraftCount({
|
|
departmentId: currentDepartmentId,
|
|
customerNumber: currentCustomerNumber,
|
|
});
|
|
|
|
if (
|
|
requestId !== draftCountRequestId ||
|
|
currentDepartmentId !== getDepartmentIdNumber() ||
|
|
currentCustomerNumber !== draftTransactionCustomerNumber.value
|
|
) {
|
|
return;
|
|
}
|
|
|
|
department_draft_count.value = nextDraftCount;
|
|
department_draft_count_loading.value = false;
|
|
didApply = true;
|
|
} finally {
|
|
draftCountFetchInFlight = false;
|
|
|
|
const shouldRerun = draftCountRefreshQueued;
|
|
const shouldShowLoadingIndicator = queuedDraftLoadingIndicator;
|
|
|
|
draftCountRefreshQueued = false;
|
|
queuedDraftLoadingIndicator = false;
|
|
|
|
if (shouldRerun) {
|
|
void fetchCurrentDepartmentDraftCount({
|
|
showLoadingIndicator: shouldShowLoadingIndicator,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!didApply) {
|
|
department_draft_count_loading.value = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
const refreshNavigationCounts = ({ showBookingLoadingIndicator = false, showDraftLoadingIndicator = false } = {}) => {
|
|
if (!canUseAdminNavigationCounts()) {
|
|
bookingCountRequestId += 1;
|
|
draftCountRequestId += 1;
|
|
department_booking_counts.value = {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
};
|
|
department_booking_counts_loading.value = false;
|
|
department_draft_count.value = 0;
|
|
department_draft_count_loading.value = false;
|
|
bookingCountFetchInFlight = false;
|
|
draftCountFetchInFlight = false;
|
|
bookingCountRefreshQueued = false;
|
|
draftCountRefreshQueued = false;
|
|
queuedBookingLoadingIndicator = false;
|
|
queuedDraftLoadingIndicator = false;
|
|
return;
|
|
}
|
|
|
|
if (!hasValidDepartmentId.value) {
|
|
department_booking_counts.value = {
|
|
past: 0,
|
|
current: 0,
|
|
future: 0,
|
|
};
|
|
department_booking_counts_loading.value = false;
|
|
department_draft_count.value = 0;
|
|
department_draft_count_loading.value = false;
|
|
bookingCountFetchInFlight = false;
|
|
draftCountFetchInFlight = false;
|
|
bookingCountRefreshQueued = false;
|
|
draftCountRefreshQueued = false;
|
|
queuedBookingLoadingIndicator = false;
|
|
queuedDraftLoadingIndicator = false;
|
|
return;
|
|
}
|
|
|
|
void fetchDepartmentBookingCount({
|
|
showLoadingIndicator: showBookingLoadingIndicator,
|
|
});
|
|
void fetchCurrentDepartmentDraftCount({
|
|
showLoadingIndicator: showDraftLoadingIndicator,
|
|
});
|
|
};
|
|
|
|
const isDepartmentSet = computed(() => {
|
|
return department_id.value !== "default" && parseInt(department_id.value) > 0;
|
|
});
|
|
|
|
watch(
|
|
[department_id, canUseAdminNavigationCountsValue],
|
|
() => {
|
|
refreshNavigationCounts({
|
|
showBookingLoadingIndicator: true,
|
|
showDraftLoadingIndicator: true,
|
|
});
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
watch(draftTransactionCustomerNumber, () => {
|
|
void fetchCurrentDepartmentDraftCount({
|
|
showLoadingIndicator: true,
|
|
});
|
|
});
|
|
|
|
void ensureDraftTransactionCustomerLoaded();
|
|
|
|
if (typeof window !== "undefined") {
|
|
window.addEventListener(NAVIGATION_COUNT_REFRESH_EVENT, refreshNavigationCounts);
|
|
}
|
|
|
|
// Fetch the booking count every 5 seconds when department context is valid
|
|
setInterval(() => {
|
|
if (canUseAdminNavigationCounts() && hasValidDepartmentId.value) {
|
|
refreshNavigationCounts();
|
|
}
|
|
}, 5000);
|
|
|
|
const items = computed<NavigationItemProps[]>(() => [
|
|
{
|
|
label: t("nav.overview"),
|
|
to: `/admin`,
|
|
permissions: ["admin"],
|
|
},
|
|
// Select available department options
|
|
{
|
|
label: t("nav.select_department"),
|
|
to: `/admin/departments`,
|
|
type: "category",
|
|
hidden: isDepartmentSet.value,
|
|
classes: [ADMIN_DEPARTMENT_SELECTION_CLASS],
|
|
permissions: ["admin", "list_departments"],
|
|
children: SessionUser.functions
|
|
.getAccessibleDepartments()
|
|
.map((departmentId: any) => {
|
|
const label = getDepartmentName(departmentId);
|
|
const department = getDepartmentById(departmentId);
|
|
|
|
if (!isAccessibleVisibleNamedDepartment(department, SessionUser.canAccessAssignedDepartment)) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
label,
|
|
to: `/admin/${department.id}`,
|
|
type: "department",
|
|
permissions: ["admin", "view_department"],
|
|
order_priority: department?.order_priority ?? department?.priority_order ?? null,
|
|
priority_order: department?.priority_order ?? department?.order_priority ?? null,
|
|
};
|
|
})
|
|
.filter(Boolean) as NavigationItemProps[],
|
|
},
|
|
// Kassesystem
|
|
{
|
|
label: t("nav.pos_system"),
|
|
type: "category",
|
|
hidden: !isDepartmentSet.value,
|
|
permissions: ["admin", "add_order"],
|
|
children: [
|
|
{
|
|
label: t("nav.create_transaction"),
|
|
to: `/admin/${department_id.value}/modules/pos`,
|
|
type: "department",
|
|
permissions: ["admin", "add_order"],
|
|
},
|
|
{
|
|
label: t("nav.wash_log"),
|
|
to: `/admin/${department_id.value}/modules/pos/orders`,
|
|
type: "department",
|
|
permissions: ["admin", "list_orders"],
|
|
},
|
|
{
|
|
label: t("nav.drafts"),
|
|
to: `/admin/${department_id.value}/modules/pos/drafts`,
|
|
type: "department",
|
|
permissions: ["admin", "list_orders"],
|
|
hidden: draftTransactionCustomerNumber.value === null,
|
|
badge: getDepartmentDraftBadge(),
|
|
},
|
|
],
|
|
},
|
|
// Tidsbookinger
|
|
/**
|
|
{
|
|
label: 'Tidsbookinger',
|
|
type: 'category',
|
|
permissions: ['admin', 'department_timebookings_entries_get'],
|
|
children: [
|
|
{
|
|
label: 'Opret tidsbooking',
|
|
to: `/admin/${department_id.value}/modules/time-bookings/new`,
|
|
type: 'department',
|
|
permissions: ['admin', 'department_timebookings_create'],
|
|
},
|
|
{
|
|
label: 'Tidsbookinger',
|
|
to: `/admin/${department_id.value}/modules/time-bookings`,
|
|
type: 'department',
|
|
permissions: ['admin', 'department_timebookings_entries_get'],
|
|
},
|
|
{
|
|
label: 'Åbningstider',
|
|
to: `/admin/${department_id.value}/modules/time-bookings/opening-hours`,
|
|
type: 'department',
|
|
permissions: ['admin', 'department_timebookings_opening_hours_get'],
|
|
},
|
|
{
|
|
label: 'Vasketyper',
|
|
to: `/admin/${department_id.value}/modules/time-bookings/types`,
|
|
type: 'department',
|
|
permissions: ['admin', 'department_timebookings_types_get'],
|
|
},
|
|
],
|
|
}, */
|
|
// Bookinger
|
|
{
|
|
label: t("nav.bookings"),
|
|
to: `/admin/${department_id.value}/modules/bookings`,
|
|
type: "department",
|
|
permissions: ["admin", "list_bookings"],
|
|
hidden: !isDepartmentSet.value,
|
|
badges: getDepartmentBookingBadges(),
|
|
},
|
|
// Dagsopgørelse
|
|
{
|
|
label: t("nav.daily_report"),
|
|
to: `/admin/${department_id.value}/modules/daily-report`,
|
|
type: "department",
|
|
permissions: ["admin", "list_department_daily_reports"],
|
|
hidden: !isDepartmentSet.value,
|
|
},
|
|
// Selvvask
|
|
{
|
|
label: t("nav.self_wash"),
|
|
to: `/admin/${department_id.value}/modules/wash-lanes`,
|
|
type: "department",
|
|
permissions: ["admin", "list_department_wash_lanes"],
|
|
hidden: !isDepartmentSet.value,
|
|
},
|
|
// Notifikationer
|
|
{
|
|
label: t("nav.notifications"),
|
|
to: `/admin/${department_id.value}/modules/notifications`,
|
|
type: "department",
|
|
permissions: ["admin", "department_notification_sms_get"],
|
|
hidden: !isDepartmentSet.value,
|
|
},
|
|
// Intranet
|
|
/**
|
|
{
|
|
label: 'Intranet',
|
|
to: `/admin/${department_id.value}/modules/intranet`,
|
|
type: 'department',
|
|
permissions: ['admin', 'list_intranet_posts'],
|
|
}*/
|
|
]);
|
|
const isDepartment = computed(() => department_id.value !== "default" && parseInt(department_id.value) > 0);
|
|
|
|
const parsedItems = () => {
|
|
// If the current route is not department-specific, filter out department items
|
|
if (!isDepartment.value) {
|
|
return items.value.filter((item) => item.type !== "department");
|
|
}
|
|
// Otherwise, return all items
|
|
return items.value;
|
|
};
|
|
|
|
export default defineComponent({
|
|
name: "NavigationMenuItemsAdmin",
|
|
setup() {
|
|
return {
|
|
items,
|
|
parsedItems,
|
|
};
|
|
},
|
|
});
|
|
|
|
// Named exports if needed
|
|
export { ADMIN_DEPARTMENT_SELECTION_CLASS, syncAdminNavigationRoute, items, parsedItems };
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Add a template section even if empty -->
|
|
</template>
|