Resolve frontend Qodana critical and high findings (#176)

Resolve recommended-profile Critical and High findings, update vulnerable dependencies, restore invoice queue E2E authentication setup, and clear the remaining frontend Qodana findings.
This commit is contained in:
Jeppe B
2026-07-17 06:22:51 +02:00
committed by GitHub
parent 6b5ac8a8b3
commit 1729443cc3
516 changed files with 3081 additions and 5867 deletions
+6 -3
View File
@@ -7,6 +7,9 @@ const commonGlobals = {
...globals.browser,
...globals.node,
...globals.es2024,
CanvasImageSource: "readonly",
EventListener: "readonly",
PositionCallback: "readonly",
grecaptcha: "readonly",
};
@@ -101,12 +104,12 @@ export default [
rules: {
"no-console": "off",
"no-debugger": "warn",
"no-empty": "warn",
"no-empty": ["warn", { allowEmptyCatch: true }],
"no-undef": "warn",
"no-unused-vars": ["warn", commonUnusedOptions],
"no-useless-assignment": "warn",
"vue/multi-word-component-names": "off",
"vue/no-mutating-props": "warn",
"vue/no-mutating-props": ["warn", { shallowOnly: true }],
"vue/no-unused-components": "warn",
"vue/no-unused-vars": "warn",
"vue/no-v-html": "off",
@@ -133,7 +136,7 @@ export default [
},
},
rules: {
"no-empty": "warn",
"no-empty": ["warn", { allowEmptyCatch: true }],
},
},
];
+1 -1
View File
@@ -436,7 +436,7 @@
<script>
(function () {
var appPath = window.location.pathname.replace(/^\/[^/]+\/frontend(?=\/|$)/, '') || '/';
var hasStoredSession = false;
var hasStoredSession;
try {
hasStoredSession = Boolean(window.localStorage && window.localStorage.getItem('token'));
} catch (error) {
+1410 -2374
View File
File diff suppressed because it is too large Load Diff
+11 -6
View File
@@ -1,6 +1,7 @@
{
"name": "truckwashdashboardsfrontend",
"version": "0.0.0",
"web-types": "./web-types.json",
"private": true,
"type": "module",
"scripts": {
@@ -71,7 +72,7 @@
},
"dependencies": {
"@azure/msal-browser": "^4.12.0",
"@bubblewrap/cli": "^1.23.0",
"@bubblewrap/cli": "^1.24.1",
"@capacitor/core": "^8.4.1",
"@capacitor/geolocation": "^8.2.0",
"@creativebulma/bulma-badge": "^1.0.1",
@@ -91,7 +92,7 @@
"@xterm/addon-fit": "^0.11.0",
"animate.css": "^4.1.1",
"apexcharts": "^5.10.4",
"axios": "1.13.5",
"axios": "1.18.1",
"buefy": "^3.0.3",
"bulma": "^1.0.2",
"bulma-block-list": "^1.1.0",
@@ -122,7 +123,7 @@
"vue3-apexcharts": "^1.11.1",
"vue3-cookies": "^1.0.6",
"vuex": "^4.1.0",
"xlsx": "^0.18.5",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"xterm": "^5.3.0"
},
"devDependencies": {
@@ -149,9 +150,13 @@
"sass-embedded": "^1.81.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.60.1",
"vite": "7.1.11",
"vite-plugin-pwa": "^1.0.2",
"vite": "8.1.5",
"vite-plugin-pwa": "^1.3.0",
"vite-plugin-vue-devtools": "^7.5.4",
"vitest": "^2.1.9"
"vitest": "^4.1.10",
"vue-eslint-parser": "^10.3.0"
},
"overrides": {
"tar": "7.5.19"
}
}
+28
View File
@@ -10,6 +10,34 @@ include:
- name: Eslint
exclude:
# These flows deliberately funnel synchronous validation failures into the same
# catch blocks that normalize asynchronous API/auth failures for the UI.
- name: ExceptionCaughtLocallyJS
paths:
- scripts/mobile/upload-google-play.mjs
- src/components/displays/buttons/ActionSettingsWheelButton.vue
- src/components/displays/department/pos/orders/OrderAttachmentsActionButton.vue
- src/components/displays/department/tables/SelfServeTaskAttachmentsModal.vue
- src/components/displays/superuser/tables/rolesTable.vue
- src/components/displays/superuser/tables/usersTable.vue
- src/components/session/token/SessionUser/Objects/DepartmentDailyReportComplaints.vue
- src/components/session/token/SessionUser/Objects/ObjectsGlobal.vue
- src/components/session/token/SessionUser/Objects/Subusers.vue
- src/components/shop/POSDepartmentProcess.vue
- src/components/timebookings/displays/CalendarController.vue
- src/components/viewport/page/headers/menu/NavigationMenuGlobalSearch.vue
- src/composables/useSelfServeLogic.js
- src/features/edgeGateways/EdgeGatewayManager.vue
- src/services/PasskeyAuthService.js
- src/services/releaseChannelAvailability.js
- src/views/backoffice/components/LimitedBackofficeEmployeesManager.vue
- src/views/dashboards/departmentDashboard/modules/Pos/DepartmentPosOrder.vue
- src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/components/InvoicingPeriodObjectTree.vue
- src/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportInvoiceQueue.vue
- src/views/dashboards/superUserDashboard/roles/RolePermissionManager.vue
- src/views/dashboards/superUserDashboard/selfserve/components/SelfServeMachineConnectivity.vue
- src/views/dashboards/superUserDashboard/statistics/displays/overview/StatisticsIncomeCard.vue
- src/views/dashboards/userDashboard/profile/displays/Passkeys/PasskeyManagement.vue
- name: All
paths:
- src/i18n/generated
+2 -2
View File
@@ -9,8 +9,8 @@ const activeLocales = ["da", "en", "sv", "de", "no"];
const mode = process.argv.includes("--apply") ? "apply" : "check";
const templatePrefix = "templates.generated.compat";
const placeholderPattern = /\{[A-Za-z_][A-Za-z0-9_]*\}/g;
const exactLinkPattern = /^@(?:\.[\p{L}]+)?:(?:\{'[^']+'\}|[\p{L}\p{N}_.-]+)$/u;
const placeholderPattern = /\{[A-Za-z_][A-Za-z0-9_]*}/g;
const exactLinkPattern = /^@(?:\.[\p{L}]+)?:(?:\x7b'[^']+'\x7d|[\p{L}\p{N}_.-]+)$/u;
const isPlainObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
+2 -11
View File
@@ -7,8 +7,8 @@ const projectRoot = path.resolve(__dirname, "..");
const localesDirectory = path.join(projectRoot, "src", "i18n", "generated");
const activeLocales = ["da", "en", "sv", "de", "no"];
const linkTokenPattern = /@(?:\.[\p{L}]+)?:(?:\{'[^']+'\}|[\p{L}\p{N}_.-]+)/gu;
const placeholderPattern = /\{[A-Za-z_][A-Za-z0-9_]*\}/g;
const linkTokenPattern = /@(?:\.[\p{L}]+)?:(?:\x7b'[^']+'\x7d|[\p{L}\p{N}_.-]+)/gu;
const placeholderPattern = /\{[A-Za-z_][A-Za-z0-9_]*}/g;
const wordTokenPattern = /[\p{L}\p{N}]+/gu;
const linkedMessagePattern = /^@(?<modifier>\.[\p{L}]+)?:(?:\{'(?<literal>[^']+)'\}|(?<path>[\p{L}\p{N}_.-]+))$/u;
@@ -138,15 +138,6 @@ const slugify = (token, usedKeys) => {
return key;
};
const isCapitalizedToken = (token) => {
const lower = token.toLocaleLowerCase();
return token === `${lower.charAt(0).toLocaleUpperCase()}${lower.slice(1)}` && token !== lower;
};
const isUpperToken = (token) => token.length > 1 && token === token.toLocaleUpperCase() && token !== token.toLocaleLowerCase();
const isLowerToken = (token) => token === token.toLocaleLowerCase() && token !== token.toLocaleUpperCase();
const linkedWord = (keyPath, modifier = "") => `@${modifier}:{'${keyPath}'}`;
const applyTextModifier = (value, modifier) => {
+3 -1
View File
@@ -72,7 +72,9 @@ async function fetchJson(baseUrl, assetPath) {
try {
return JSON.parse(result.text());
} catch (error) {
throw new Error(`${assetPath} did not contain valid JSON: ${error instanceof Error ? error.message : error}`);
throw new Error(`${assetPath} did not contain valid JSON: ${error instanceof Error ? error.message : error}`, {
cause: error,
});
}
}
+2 -1
View File
@@ -122,7 +122,8 @@ function normalizePositiveInt(value, fallback) {
}
function stripAnsi(input) {
return input.replace(/\u001b\[[0-9;]*m/g, "");
// eslint-disable-next-line no-control-regex -- ANSI escape sequences intentionally start with ESC.
return input.replace(new RegExp("\\x1b\\[[0-9;]*m", "g"), "");
}
function formatDuration(durationMs) {
-4
View File
@@ -152,10 +152,6 @@ function getArtifactNamespace(suffix) {
return sanitizeNamespace(`${baseNamespace}-${suffix}`);
}
function buildProjectArgs(projects) {
return projects.flatMap((project) => ["--project", project]);
}
async function runPlaywright({ label, commandArgs, artifactSuffix }) {
const finalArgs = ["test", ...commandArgs, ...args.forwardedArgs];
+4 -3
View File
@@ -38,7 +38,7 @@ const SUSPICIOUS_PATTERNS = [
/ÃÂ/g,
/Ã./g,
/Â(?=\S)/g,
/â€(?:[™œžŸ"“”‘’•–—…])/g,
/â€[™œžŸ"“”‘’•–—…]/g,
/’/g,
/“/g,
/”/g,
@@ -49,7 +49,8 @@ const SUSPICIOUS_PATTERNS = [
];
const REGIONAL_CHAR_REGEX = /[æøåÆØÅäöÄÖüÜßéÉèÈáÁàÀóÓúÚíÍñÑçÇ]/g;
const LATIN_EXTENDED_CHAR_REGEX = /[\u00C0-\u024F]/g;
const CONTROL_CHAR_REGEX = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
// eslint-disable-next-line no-control-regex -- these are precisely the invalid control characters this scanner detects.
const CONTROL_CHAR_REGEX = new RegExp("[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\x9f]", "g");
const utf8Decoder = new TextDecoder("utf-8", { fatal: true });
function countMatches(text, regex) {
@@ -227,7 +228,7 @@ async function collectScanFiles(rootDir) {
const files = [];
async function walk(currentDir) {
let entries = [];
let entries;
try {
entries = await fs.readdir(currentDir, { withFileTypes: true });
} catch {
+1 -5
View File
@@ -184,8 +184,6 @@ const generateSquareAround = (matches: colorMatch[], canvasId: string) => {
}
combinedCtx.drawImage(canvas, minX, minY, combinedWidth, combinedHeight, 0, 0, combinedWidth, combinedHeight);
// Get the image data of the combined square
const combinedImageData = combinedCtx.getImageData(0, 0, combinedWidth, combinedHeight);
const combinedData = combinedImageData.data;
// Console.log the base64 representation of the combined square
const base64Combined = combinedCanvas.toDataURL();
console.log(`Base64 representation of combined square:`, base64Combined);
@@ -490,8 +488,6 @@ const findLicensePlate = (object: gridObject) => {
location: { x: (i / 4) % canvas.width, y: Math.floor((i / 4) / canvas.width) }
});
// Check if there's black text around the found color
const x = (i / 4) % canvas.width;
const y = Math.floor((i / 4) / canvas.width);
const textFound = data.slice(i - 100, i + 100).some((value, index) => {
if (index % 4 === 0) { // Check only the red channel
return value < 50; // Assuming black text is below a certain threshold
@@ -589,4 +585,4 @@ const findLicensePlate = (object: gridObject) => {
outline: none;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
}
</style>
</style>
+11 -11
View File
@@ -20,7 +20,7 @@ const props = withDefaults(defineProps<{
defaultExpandAll?: boolean;
expandOnClickNode?: boolean;
lazy?: boolean;
load?: (node: TreeNode) => Promise<TreeNode[]>;
load?: (_node: TreeNode) => Promise<TreeNode[]>;
progressiveBatchSize?: number;
loadMoreLabel?: string;
ariaLabel?: string;
@@ -41,16 +41,16 @@ const props = withDefaults(defineProps<{
});
const emit = defineEmits<{
(event: "update:selected", value: any): void;
(event: "update:expandedKeys", value: any[]): void;
(event: "update:checkedKeys", value: any[]): void;
(event: "select", node: TreeNode, key: any): void;
(event: "check", node: TreeNode, key: any, checkedKeys: any[]): void;
(event: "expand", node: TreeNode, key: any): void;
(event: "collapse", node: TreeNode, key: any): void;
(event: "node-click", node: TreeNode, key: any): void;
(event: "load-start", node: TreeNode, key: any): void;
(event: "load-error", error: unknown, node: TreeNode, key: any): void;
"update:selected": [value: any];
"update:expandedKeys": [value: any[]];
"update:checkedKeys": [value: any[]];
select: [node: TreeNode, key: any];
check: [node: TreeNode, key: any, checkedKeys: any[]];
expand: [node: TreeNode, key: any];
collapse: [node: TreeNode, key: any];
"node-click": [node: TreeNode, key: any];
"load-start": [node: TreeNode, key: any];
"load-error": [error: unknown, node: TreeNode, key: any];
}>();
const resolvedFields = computed(() => ({
+1 -3
View File
@@ -16,9 +16,7 @@ defineProps({
},
});
const redirect = (path) => {
window.location = path;
}
</script>
<template>
+8 -10
View File
@@ -315,11 +315,11 @@ const onFieldChange = async (field, value) => {
console.log(field, value);
}
const getFieldName = (field) => {
const _getFieldName = (field) => {
try {
return SessionUser.objects.forms.meta.labels.fields.names[field.name].label;
}
catch (e) {
catch {
return field.name;
}
}
@@ -364,7 +364,7 @@ const getFieldOptions = (field) => {
console.log(result);
return result;
}
catch (e) {
catch {
return {};
}
}
@@ -376,7 +376,7 @@ const getFieldMetadata = (field) => {
...props.form_field_options[field.name] ?? {}
};
}
catch (e) {
catch {
return {};
}
}
@@ -435,7 +435,6 @@ const getForm = () => {
}
// Parse the types
for (const field_index in fields.value) {
const key = field_index;
const field = fields.value[field_index];
const validator = getValidator(field.validation);
//console.log(field, validator);
@@ -459,7 +458,6 @@ const setDefaultValues = () => {
//console.log('setDefaultValues');
//console.log(fields.value);
for (const field_index in fields.value) {
const key = field_index;
const field = fields.value[field_index];
const validator = getValidator(field.validation);
//console.log(field, validator);
@@ -555,7 +553,7 @@ const isFieldSelect = (field) => {
}
const isFieldMultiple = (field) => {
return field.metadata.allow_multiple === true ?? false;
return field.metadata.allow_multiple === true;
}
const doesMultipleFieldContainValue = (field, index) => {
@@ -614,7 +612,7 @@ const debugGetForm = () => {
<!-- Multiple select -->
<template v-if="isFieldMultiple(field)">
<div class="buttons is-multiline">
<template v-for="(option, index) in getFieldOptions(field)">
<template v-for="(option, index) in getFieldOptions(field)" :key="`${field.id}-${index}`">
<div
class="button"
:class="{
@@ -638,7 +636,7 @@ const debugGetForm = () => {
<template v-else>
<select :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.value)" v-model="fieldValues[field.id]" :disabled="isFieldLocked(field)">
<!-- Loop through the options, while creating the select options -->
<template v-for="(option, index) in getFieldOptions(field)">
<template v-for="(option, index) in getFieldOptions(field)" :key="`${field.id}-${index}`">
<option :value="index">{{ option }}</option>
</template>
</select>
@@ -661,7 +659,7 @@ const debugGetForm = () => {
<!-- Text -->
<input class="input is-link" v-else-if="getValidator(field.validation).type === 'text'" :type="getValidator(field.validation).type" :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.value)" v-model="fieldValues[field.id]" v-bind:disabled="isFieldLocked(field)" v-bind:placeholder="field.metadata.placeholder">
<!-- Email -->
<input class="input is-link" v-else-if="getValidator(field.validation).type === 'email'" :type="getValidator(field.validation).type" :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.value)" v-model="fieldValues[field.id]" :disabled="isFieldLocked(field)" v-bind:placeholder="field.metadata.placeholder" v-if="isFieldDisplayable(field)">
<input class="input is-link" v-else-if="getValidator(field.validation).type === 'email' && isFieldDisplayable(field)" :type="getValidator(field.validation).type" :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.value)" v-model="fieldValues[field.id]" :disabled="isFieldLocked(field)" v-bind:placeholder="field.metadata.placeholder">
<!-- Checkbox -->
<div class="control" v-else-if="getValidator(field.validation).type === 'checkbox'" :type="getValidator(field.validation).type" :required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.checked)">
<input class="switch" :type="getValidator(field.validation).type" v-bind:required="isFieldRequired(field)" :name="field.id" :id="field.id" @change="onFieldChange(field, $event.target.checked)" :checked="isCheckboxChecked(field)">
+1 -1
View File
@@ -1,7 +1,7 @@
<script setup>
import ProductsPagination from "@/components/displays/pagination/models/SuperUserDashboard/ProductsPagination.vue";
import PageTitle from "@/components/global/PageTitle.vue";
import { showCreateProductForm } from "@/components/forms/superUser/createProductForm.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { loadList } from "@/components/pagination/paginatedList.vue";
import { useI18n } from 'vue-i18n';
@@ -14,7 +14,7 @@ const props = defineProps({
tabs: {
type: Array,
required: true,
default: []
default: () => []
},
/**
* The default active tab (slot name)
@@ -129,13 +129,12 @@ if (props.forceActiveTab !== null && props.forceActiveTab !== undefined) {
>
<ul>
<template
v-if="props.tabs"
v-for="(tab, index) in props.tabs"
:key="index"
>
<li
:class="{ 'is-active': tab.slot === activeTab }"
v-if="isTabVisible(tab)"
v-if="props.tabs && isTabVisible(tab)"
>
<a @click="onClickTab(tab.slot)">
<!-- Display the tab icon (if any) -->
+1 -1
View File
@@ -84,7 +84,7 @@ const showAddonPopper = (addon, element) => {
), element);
};
const hideAddonPopper = (addon) => {
const hideAddonPopper = () => {
removePopperIfOpen();
};
+1 -1
View File
@@ -6,7 +6,7 @@ const isSlotEmpty = () => {
return Object.keys(slots).length === 0;
}
const props = defineProps({
const _props = defineProps({
hasBorder: {
type: Boolean,
default: false
@@ -62,7 +62,7 @@ const props = defineProps({
* - toggle (boolean): Emitted when the card is toggled open or closed.
*/
const emit = defineEmits<{
(e: 'toggle', isOpen: boolean): void;
toggle: [isOpen: boolean];
}>();
const isOpen = ref(props.defaultOpen);
@@ -376,9 +376,7 @@ const updateDropdownLayout = async () => {
const menuHeight = Math.ceil(dropdownContent.value.scrollHeight);
lockDropdownPlacement(triggerRect, menuHeight, viewportInsets);
const availableHeight = getDropdownAvailableHeight(triggerRect, viewportInsets);
const nextMaxHeight = availableHeight > 0 && menuHeight > availableHeight ? Math.floor(availableHeight) : null;
dropdownMaxHeight.value = nextMaxHeight;
dropdownMaxHeight.value = availableHeight > 0 && menuHeight > availableHeight ? Math.floor(availableHeight) : null;
await nextTick();
@@ -564,7 +562,7 @@ const showSetCustomerPassword = (userId) => {
icon: "success",
});
})
.catch((error) => {
.catch(() => {
Swal.fire({
title: t("common.error"),
text: t("admin.pos.settings_wheel.error_changing_password"),
@@ -1374,7 +1372,7 @@ const downloadAttachment = async (attachment) => {
try {
const downloadLink = await resolveAttachmentDownloadLink(attachment);
downloadAttachmentFile(downloadLink, attachment);
} catch (error) {
} catch {
Swal.fire({
title: t("common.error"),
text: t("admin.pos.settings_wheel.error_downloading_attachment"),
@@ -119,11 +119,11 @@ const getStyle = () => {
return styles[props.template] ?? styles.default;
}
const getButtonClass = () => {
const _getButtonClass = () => {
return getStyle().button;
}
const getIconClass = () => {
const _getIconClass = () => {
return getStyle().icon.icon;
}
@@ -102,11 +102,11 @@ const getStyle = () => {
return styles[props.template] ?? styles.default;
}
const getButtonClass = () => {
const _getButtonClass = () => {
return getStyle().button;
}
const getIconClass = () => {
const _getIconClass = () => {
return getStyle().icon.icon;
}
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, watch, computed, onMounted, Component } from 'vue';
import { ref, watch, computed, onMounted } from 'vue';
import '@creativebulma/bulma-divider/dist/bulma-divider.min.css';
const props = defineProps({
label: {
@@ -216,24 +216,8 @@ const label_shortened = computed(() => {
<template v-else-if="error">
<i :class="[error_icon_class, error_class]"></i>
</template>
<!-- Not loading and not hovered -->
<template v-else-if="!is_loading && !is_currently_hovered">
<i :class="[icon_class, color_class]"></i>
</template>
<!-- Not loading and hovered -->
<template v-else-if="!is_loading && is_currently_hovered">
<i :class="[icon_class, color_class]"></i>
</template>
<!-- Loading and hovered -->
<template v-else-if="is_loading && is_currently_hovered">
<i class="fas fa-circle-notch fa-spin"></i>
</template>
<!-- Not loading and hovered -->
<template v-else-if="!is_loading && is_currently_hovered">
<i :class="[icon_class, color_class]"></i>
</template>
<!-- Not loading and not hovered -->
<template v-else-if="!is_loading && !is_currently_hovered">
<!-- Default state -->
<template v-else>
<i :class="[icon_class, color_class]"></i>
</template>
</span>
@@ -360,4 +344,4 @@ const label_shortened = computed(() => {
background-color: var(--button-hover-bg-color, #f5f5f5);
color: var(--button-hover-text-color, #363636);
}
</style>
</style>
@@ -373,7 +373,7 @@ const otherDropdownActions = computed<DropdownAction[]>(() => {
onClick: () => applyShortcut(shortcut),
}));
if (visibility.value.showShortCuts !== false && shortcuts.value.length > 0) {
if (visibility.value.showShortCuts && shortcuts.value.length > 0) {
actions.unshift({
id: "other_month",
label: t("global.text.other_month"),
@@ -33,7 +33,7 @@ const props = defineProps({
// Theme can be 'divided' or 'simple'
theme: {
type: String,
validator(value, props) {
validator(value) {
return ['divided', 'simple'].includes(value);
},
default: () => {
@@ -156,8 +156,8 @@ const onClickLogout = () => {
const addNewBooking = async () => {
// Get the dateTime for now, and add 1 hour
const now = new Date();
const startTime = new Date(now.getTime() + 1 * 60 * 60 * 1000); // Add 1 hour
const endTime = new Date(startTime.getTime() + 1 * 60 * 60 * 1000); // Add 1 hour
const startTime = new Date(now.getTime() + 60 * 60 * 1000); // Add 1 hour
const endTime = new Date(startTime.getTime() + 60 * 60 * 1000); // Add 1 hour
try {
const eventDetails = {
subject: "New Booking",
@@ -194,4 +194,4 @@ export {
};
</script>
</script>
@@ -62,7 +62,7 @@ const props = defineProps({
},
// Determines the available time slots.
availableTimeSlots: {
type: Object,
type: Array,
required: false,
default: () => [],
}
@@ -76,7 +76,7 @@ const shownDate = ref<Date>(props.shownDate);
// The current locale
const locale = ref<string>(props.locale);
// The first day of the week
const firstDayOfWeek = ref<number>(props.firstDayOfWeek);
// The minimum date that can be selected
const minDate = ref<Date | null>(props.minDate);
// The maximum date that can be selected
@@ -104,11 +104,7 @@ const navigateToMonthRelative = (modifier: Number): void => {
newDate.setMonth(newDate.getMonth() + Number(modifier));
shownDate.value = newDate;
};
const navigateToMonthAbsolute = (month: Number): void => {
const newDate = new Date(shownDate.value);
newDate.setMonth(Number(month));
shownDate.value = newDate;
};
// Date selection function
const selectDate = (date: Date): void => {
// Check if the date is valid
@@ -130,9 +126,7 @@ const monthName = computed<string>(() => {
return shownDate.value.toLocaleString(locale.value, { month: 'long' });
});
// The year of the shown date
const year = computed<number>(() => {
return shownDate.value.getFullYear();
});
// The days in the shown month
const daysInMonth = computed<number>(() => {
return new Date(shownDate.value.getFullYear(), shownDate.value.getMonth() + 1, 0).getDate();
@@ -142,9 +136,7 @@ const firstDayOfMonth = computed<number>(() => {
return new Date(shownDate.value.getFullYear(), shownDate.value.getMonth(), 1).getDay();
});
// The last day of the shown month (0 = Sunday, 1 = Monday, etc.)
const lastDayOfMonth = computed<number>(() => {
return new Date(shownDate.value.getFullYear(), shownDate.value.getMonth() + daysInMonth.value - 1).getDay();
});
// Function to get the first character of a string
const getFirstCharacter = (str: string): string => {
return str.charAt(0);
@@ -166,8 +158,6 @@ const getFillerDays = (): number[] => {
// and returns that many days from the next month
const getFillerDaysNextMonth = (): number[] => {
const fillerDays: number[] = [];
const nextMonth = new Date(shownDate.value.getFullYear(), shownDate.value.getMonth() + 1, 1);
const daysInNextMonth = new Date(nextMonth.getFullYear(), nextMonth.getMonth() + 1, 0).getDate();
const totalDaysShown = daysInMonth.value + firstDayOfMonth.value;
const daysNeeded = ((7 - (totalDaysShown % 7)) % 7);
// If there's 7 days needed, it means the month ends perfectly
@@ -224,7 +214,7 @@ const needsFillerDaysNextMonth = computed<boolean>(() => {
<thead>
<tr>
<!-- Weekday names -->
<th v-for="(day, index) in 7" :key="index" class="has-text-centered is-narrow px-0">
<th v-for="index in 7" :key="index" class="has-text-centered is-narrow px-0">
{{ getFirstCharacter(new Date(new Date().getFullYear(), 0, index + 1).toLocaleDateString(locale, { weekday: 'short' })) }}
</th>
</tr>
@@ -297,4 +287,4 @@ const needsFillerDaysNextMonth = computed<boolean>(() => {
.row-borderless td, .row-borderless th {
border: none;
}
</style>
</style>
@@ -1,11 +1,11 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
defineProps(['bookings']);
import { ref } from 'vue';
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import { departments, getDepartments} from "@/components/pagination/departmentTabs.vue";
const redirectDepartmentOrderPage = (orderId, departmentId) => {
// Send the user to the order page
@@ -19,36 +19,14 @@ import PosDepartmentStepMobile1 from "@/components/displays/department/pos/steps
import PosDepartmentStepMobile2 from "@/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile2.vue";
import PosDepartmentStepMobile3 from "@/components/displays/department/pos/steps/mobile/PosDepartmentStepMobile3.vue";
import { pos } from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
import VerifiedCustomer from "@/components/viewport/elements/icons/VerifiedCustomer.vue";
import UnknownCustomer from "@/components/viewport/elements/icons/UnknownCustomer.vue";
import BookedCustomer from "@/components/viewport/elements/icons/BookedCustomer.vue";
import "@/components/viewport/elements/icons/VerifiedCustomer.vue";
import "@/components/viewport/elements/icons/UnknownCustomer.vue";
import "@/components/viewport/elements/icons/BookedCustomer.vue";
import PosDepartmentStepMobilePopupRenderer
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobilePopupRenderer.vue";
import { applyPosRouteSearch } from "@/views/dashboards/departmentDashboard/modules/Pos/posRouteState.js";
const router = useRouter();
const debugVehicles = () => {
pos.vehicles.select(
1,
{
reg: "AB123CD",
status: VerifiedCustomer
}
);
pos.vehicles.select(
2,
{
reg: "EF456GH",
status: UnknownCustomer
}
);
pos.vehicles.select(
3,
{
reg: "IJ789KL",
status: BookedCustomer
}
);
};
const routeStateHandlers = {
setOrderId,
@@ -1,12 +1,6 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import { getDepartment, selectScan } from "@/components/shop/POSDepartmentProcess.vue";
import {
isLoaded,
isLoading,
list,
loadList,
metaCurrentPage,
@@ -23,21 +17,12 @@ import {
import {useElementVisibility} from "@vueuse/core";
import {SessionUser} from "@/components/session/token/SessionUser.vue";
import {ref, watch, onMounted, useTemplateRef} from 'vue';
import {popperBox, showPopper, showPopperWithContent, removePopperIfOpen} from "@/components/displays/PopperDefault.vue";
import {popperBox, showPopper, removePopperIfOpen} from "@/components/displays/PopperDefault.vue";
import { Colors } from "@/ThemeConfig.vue";
const activeTab = ref(null);
const panel_tabs = ref([
{
name: t('tables.pos.scanned_plates'),
active: true
},
{
name: t('global.search'),
active: false
}
]);
const scansInfinityScroll = ref(null);
const highestPage = ref(0); // The highest page that has been loaded (This is used to prevent loading the same page multiple times)
@@ -60,17 +45,7 @@ const onChanged = () => {
};
/** Whenever the tab changes, update the items per page, and reset the search */
const changeTab = (tabIndex) => {
// Reset the infinite scroll
scansInfinityScroll.value = null;
// Set the highest page to 0
highestPage.value = 0;
search('', false);
// Set the page to 1
setPage(1);
// Load the list
loadList();
};
/** Whenever the search changes, reset the infinite scroll */
watch(metaSearch, () => {
@@ -93,56 +68,9 @@ watch(list, (newList) => {
}
});
const activePopperPlate = ref({
plate: '',
isLoaded: false
});
const tmp = {
"registration_number": "BA95215",
"status": "Registreret",
"status_date": "2016-04-04T14:12:14.000+02:00",
"type": "Stor personbil",
"use": "Busku00f8rsel",
"first_registration": "2016-04-04+02:00",
"vin": "YV3T2U829G1177642",
"own_weight": 15900,
"cerb_weight": 16500,
"total_weight": 24350,
"axels": 3,
"pulling_axels": 1,
"seats": 54,
"coupling": true,
"trailer_maxweight_nobrakes": 0,
"trailer_maxweight_withbrakes": 2800,
"doors": null,
"make": "VOLVO",
"model": "9700",
"variant": "9700HD (9711R 13,83)",
"model_type": "B6SC",
"model_year": 0,
"color": null,
"chassis_type": "",
"engine_cylinders": 6,
"engine_volume": 10837,
"engine_power": 345,
"fuel_type": "Diesel",
"is_hybrid": false,
"hybrid_type": "mild",
"registration_zipcode": "",
"vehicle_id": 9000000001742548,
"mot_info": {
"type": "PeriodiskSyn",
"date": "2024-02-22",
"result": "Godkendt",
"status": "Aktiv",
"status_date": "2024-02-22",
"mileage": 546000
},
"is_leasing": false,
"leasing_from": null,
"leasing_to": null
};
const decodeMotorAPIString = (string) => {
@@ -441,4 +369,4 @@ setTimeout(() => {
.input.has-placeholder-italic::placeholder {
font-style: italic;
}
</style>
</style>
@@ -4,7 +4,7 @@ import RequiresPermission from "@/components/displays/permissionbased/RequiresPe
const props = defineProps(['notes', 'isAddFormVisible', 'isOldNotesVisible', 'isLabelVisible']);
import { ref } from 'vue';
import { addCustomerNote, isCustomerSelected, showDeleteNoteDialog } from "@/components/shop/POSDepartmentProcess.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
@@ -37,7 +37,7 @@ import { SessionUser } from "@/components/session/token/SessionUser.vue";
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
import {
customer_name,
customer_name as processCustomerName,
customerRequiresReferenceNumber,
fetchAttachments,
customerUsesPONumbers,
@@ -98,7 +98,7 @@ const titleClasses = computed(() => ({
"is-6": isSidebarVariant.value,
"pos-order-items__title": true,
}));
const displayCustomerName = computed(() => props.customer_name || customer_name.value || "");
const displayCustomerName = computed(() => props.customer_name || processCustomerName.value || "");
const showHeaderActions = computed(() => slots.headerActions !== undefined);
const pendingRemovedOrderItemIds = ref([]);
const selectedOrderItem = ref(null);
@@ -294,7 +294,7 @@ if (props.orderId === -1) {
};
registrationNumbersLoading.value = false;
loadedRegistrationNumbersForOrderId.value = null;
customer_name.value = props.customer_name;
processCustomerName.value = props.customer_name;
watch(
() => props.orderId,
(newOrderId) => {
@@ -6,14 +6,6 @@ import {
customer_attributes,
department_id,
loadCustomerAttributes,
hideDiscountsCatalog,
hidePricesCatalog,
setHidePricesCatalog,
setHideDiscountsCatalog,
getHideDiscountsCatalog,
getHidePricesCatalog,
getUserDiscounts,
user_discounts,
} from "@/components/shop/POSDepartmentProcess.vue";
import { createAttribute, deleteAttribute } from "@/components/shop/CustomerAttributes.vue";
import { computed, ref, watch } from "vue";
@@ -242,11 +234,11 @@ const customer_data_has_empty_details = () => {
</ul>
</div>
</div>
<template v-if="panel_tabs[0].active" v-for="detail in details" :key="detail.name">
<template v-for="detail in details" :key="detail.name">
<!-- Details that's not empty (unless isCustomerDetailsExpanded is true) -->
<a
class="panel-block pos-selected-customer__row"
v-if="customer_data[detail.prop] || isCustomerDetailsExpanded"
v-if="panel_tabs[0].active && (customer_data[detail.prop] || isCustomerDetailsExpanded)"
>
<span class="panel-icon pos-selected-customer__icon">
<i :class="detail.icon" aria-hidden="true"></i>
@@ -325,8 +317,8 @@ const customer_data_has_empty_details = () => {
</template>
</RequiresPermission>
<!-- Shortcuts -->
<template v-if="panel_tabs[2].active" v-for="shortcut in shortcuts" :key="shortcut.name">
<RequiresPermission permission="access_super_user">
<template v-for="shortcut in shortcuts" :key="shortcut.name">
<RequiresPermission v-if="panel_tabs[2].active" permission="access_super_user">
<a class="panel-block pos-selected-customer__row">
<span class="panel-icon pos-selected-customer__icon">
<i :class="shortcut.icon" aria-hidden="true"></i>
@@ -1,6 +1,6 @@
<script setup>
import { ref, watch } from 'vue';
import { getUserDiscounts, user_discounts, customer_id } from "@/components/shop/POSDepartmentProcess.vue";
import { getUserDiscounts, customer_id } from "@/components/shop/POSDepartmentProcess.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
// The discounts
@@ -1,6 +1,6 @@
<script setup>
import { ref, watch } from 'vue';
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const props = defineProps({
// Will use the product prop to get the product (To avoid multiple API calls)
@@ -11,7 +11,7 @@ const props = defineProps({
// The Customer discounts
customer_discounts: {
type: Array,
default: []
default: () => []
},
});
@@ -110,7 +110,7 @@ const setFixedProductPrice = (fixedPrice) => {
// Debugging function
const getDiscountDebug = () => {
const _getDiscountDebug = () => {
let line = '#####################'
console.log(line);
console.log("Product: ", props.product);
@@ -132,8 +132,8 @@ const parseCustomerDiscounts = () => {
fixed_product_price.value = null;
const customerDiscounts = Array.isArray(props.customer_discounts) ? props.customer_discounts : [];
const tmp_discount_product = customerDiscounts.find((discount) => discount.product_or_category_id == props.product.id && Number(discount.is_category) === 0)
const tmp_discount_category = customerDiscounts.find((discount) => discount.product_or_category_id == props.product.category && Number(discount.is_category) === 1);
const tmp_discount_product = customerDiscounts.find((discount) => Number(discount.product_or_category_id) === Number(props.product.id) && Number(discount.is_category) === 0)
const tmp_discount_category = customerDiscounts.find((discount) => Number(discount.product_or_category_id) === Number(props.product.category) && Number(discount.is_category) === 1);
const tmp_discount_global = customerDiscounts.find((discount) => discount.id === 999999 && Number(discount.is_category) === 1);
//console.log("Product discount: ", tmp_discount_product);
@@ -5,9 +5,9 @@ import productImage02 from "/src/assets/piktogrammer/02.png";
import productImage03 from "/src/assets/piktogrammer/03.png"; // Replaced by 3
import productImage3 from "/src/assets/piktogrammer/3.png"; // Sættevognstræk
import productImage04 from "/src/assets/piktogrammer/04.png";
import productImage05 from "/src/assets/piktogrammer/05.png"; // Deprecated - use 5
import "/src/assets/piktogrammer/05.png"; // Deprecated - use 5
import productImage5 from "/src/assets/piktogrammer/5.png"; // Forvogn
import productImage06 from "/src/assets/piktogrammer/06.png"; // Deprecated - use 6
import "/src/assets/piktogrammer/06.png"; // Deprecated - use 6
import productImage6 from "/src/assets/piktogrammer/6.png"; // Forvogn med hænger
import productImage07 from "/src/assets/piktogrammer/07.png";
import productImage08 from "/src/assets/piktogrammer/08.png";
@@ -1,5 +1,5 @@
<script setup>
import { computed, onMounted, ref, useSlots, watch } from "vue";
import { computed, onMounted, ref, watch } from "vue";
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
import PosOrderItemsCurrent from "@/components/displays/department/pos/PosOrderItemsCurrent.vue";
import PosSelectedCustomer from "@/components/displays/department/pos/PosSelectedCustomer.vue";
@@ -46,7 +46,7 @@ const props = defineProps({
},
});
const slots = useSlots();
const fetchedSummaryOrder = ref(null);
const departmentTabsLoaded = ref(false);
@@ -4,8 +4,8 @@ import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import ConfigurationInput from "@/components/displays/superuser/configuration/ConfigurationInput.vue";
import "@/components/displays/buttons/EditableTableColumn.vue";
import "@/components/displays/superuser/configuration/ConfigurationInput.vue";
const props = defineProps({
orderItems: {
@@ -56,14 +56,7 @@ const calculateTax = (price) => {
}
return price * (props.taxPercentage / 100);
};
const calculateTotalPrice = (price, quantity) => {
if (!price || !quantity) {
return 0;
}
const totalPrice = price * quantity;
const tax = calculateTax(totalPrice, props.taxPercentage);
return totalPrice + tax;
};
const getOrderItemProductName = (orderItem) => {
return orderItem?.product?.name || t('global.no_data');
@@ -86,7 +79,7 @@ const getOrderItemBasePrice = (orderItem) => {
</tr>
</thead>
<tbody>
<tr v-if="props.isLoading" v-for="n in 5" :key="n">
<tr v-for="n in 5" v-show="props.isLoading" :key="n">
<!-- Skeleton-lines 5 rows -->
<td v-for="n in 5" :key="n">
<div class="skeleton-lines"><div style="width: 100%;"></div></div>
@@ -95,7 +88,7 @@ const getOrderItemBasePrice = (orderItem) => {
<tr
v-for="orderItem in props.orderItems"
:key="orderItem.id"
v-else
v-show="!props.isLoading"
:id="`order-item-${orderItem.id}`"
:class="{ 'order-item-highlight': Number(orderItem.id) === Number(props.highlightOrderItemId) }"
>
@@ -56,10 +56,10 @@ const props = defineProps({
},
});
const emit = defineEmits(["flagStatusChanged", "flagCreated"]);
import { computed, onMounted, ref, watch } from "vue";
import { computed, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { departments, getDepartments, isLoading, getDepartmentName } from "@/components/pagination/departmentTabs.vue";
import { showPopper, removePopperIfOpen, popperBox } from "@/components/displays/PopperDefault.vue";
import { departments, getDepartments, getDepartmentName } from "@/components/pagination/departmentTabs.vue";
import "@/components/displays/PopperDefault.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
const { orderBy, orderDirection, setOrder, loadList } = usePaginatedListInstance();
@@ -69,7 +69,7 @@ import Swal from "sweetalert2";
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
import InvoicingBillingPeriodInvoiceProgressBar from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/displays/InvoicingBillingPeriodInvoiceProgressBar.vue";
import { invoiceQueue } from "@/views/dashboards/superUserDashboard/InvoicingBillingPeriod/imports/InvoicingBillingPeriodImportInvoiceQueue.vue";
import PosDepartmentStepMobileAttachment from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileAttachment.vue";
import "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileAttachment.vue";
import EditableTableColumn from "@/components/displays/buttons/EditableTableColumn.vue";
import ViewportResponsiveWrapper from "@/components/viewport/conditions/elements/ViewportResponsiveWrapper.vue";
import WhiteBoxCard from "@/components/displays/boxes/WhiteBoxCard.vue";
@@ -125,9 +125,7 @@ const isOrderInvoicedWithStripe = (order) => {
return getStripeInvoiceModule(order) !== null;
};
const isOrderInvoiced = (order) => {
return isOrderInvoicedWithEconomic(order) || isOrderInvoicedWithStripe(order);
};
const doesOrderHaveInvoiceCollection = (order) => {
return getInvoiceCollection(order) !== null;
@@ -193,61 +191,9 @@ const getOrderInvoiceStatusBarColor = (order) => {
return color_class;
};
const getOrderInvoiceStatusContent = (order) => {
const content = [];
const economicInvoiceModule = getEconomicInvoiceModule(order);
const stripeInvoiceModule = getStripeInvoiceModule(order);
const invoiceCollection = getInvoiceCollection(order);
if (isOrderInvoiced(order)) {
// If the order is invoiced with e-conomic, show the invoice id
if (isOrderInvoicedWithEconomic(order)) {
isOrderEconomicInvoiceBooked(order)
? content.push("Bogført i e-conomic")
: content.push("Gemt som kladde i e-conomic");
isOrderEconomicInvoiceBooked(order)
? content.push("Faktura ID: <strong>" + (economicInvoiceModule?.invoice_id ?? "-") + "</strong>")
: content.push("Faktura Kladde ID: <strong>" + (economicInvoiceModule?.invoice_draft_id ?? "-") + "</strong>");
}
// If the order is invoiced with stripe, show the invoice id
if (isOrderInvoicedWithStripe(order)) {
isOrderStripeInvoicePaid(order)
? content.push("Fakturaen er <strong>betalt</strong>")
: content.push("Fakturaen er <strong>ikke betalt</strong>");
content.push("Faktura ID: <strong>" + (stripeInvoiceModule?.id ?? "-") + "</strong>");
}
content.push(""); // Add a line break
isOrderInvoicedWithEconomic(order)
? content.push('<span class="has-text-grey">Faktureret med e-conomic</span>')
: content.push('<span class="has-text-grey">Faktureret med stripe</span>');
} else {
// Check if the order has an invoice collection
if (doesOrderHaveInvoiceCollection(order)) {
// Check if the invoice collection is closed
if (isOrderInvoiceCollectionClosed(order)) {
content.push("Faktura samlingen er <strong>lukket</strong>");
// Check if the invoice collection is booked
if (isOrderInvoiceCollectionBooked(order)) {
content.push("Faktura samlingen er <strong>bogført</strong>");
}
}
content.push(""); // Add a line break
content.push("Faktura samling ID: <strong>" + (invoiceCollection?.id ?? "-") + "</strong>");
// Add the Error message if it exists
if (doesOrderHaveErrorMessage(order)) {
content.push('Fejl: <strong class="has-text-danger">' + order.error_message + "</strong>");
}
} else {
content.push(""); // Add a line break
content.push('<strong class="has-text-grey">Transaktionen er ikke faktureret</strong>');
}
}
// Return the list of content as a string, with line breaks between each item
return content.join("<br>");
};
const getOrderInvoiceStatusTitle = (order) => {
return isOrderInvoiced(order) ? "Faktura status" : "Transaktion status";
};
const clickUser = (customer_id) => {
if (!SessionUser.canAccessSuperUser()) {
@@ -394,7 +340,7 @@ const tableHeaders = ref([
{
name: "po",
title: SessionUser.objects.orders.columns.po.label,
visible: (order) => {
visible: () => {
return canSeePoField() || SessionUser.canAccessAdmin();
},
},
@@ -1258,7 +1204,7 @@ const formatCashierName = (order) => {
{{
SessionUser.functions.currency.toLocal(
getOrdersByInvoiceCollection(order.invoice_collection_id)
.reduce((sum, order) => sum + order.total_net_amount, 0)
.reduce((sum, invoiceOrder) => sum + invoiceOrder.total_net_amount, 0)
.toFixed(2)
)
}}
@@ -1492,7 +1438,7 @@ const formatCashierName = (order) => {
column="reg_1"
:parse-function="(value) => value || t('global.no_data')"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permissionCheckFunction="(orderobj) => canEditTransaction(order)"
:permissionCheckFunction="() => canEditTransaction(order)"
/>
</p>
<p v-if="order.reg_2 || order.reg_3" class="pos-order-vehicle-registration-line">
@@ -1504,7 +1450,7 @@ const formatCashierName = (order) => {
column="reg_2"
:parse-function="(value) => value || '-'"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permissionCheckFunction="(bookingobj) => canEditTransaction(order)"
:permissionCheckFunction="() => canEditTransaction(order)"
/>
<span v-if="order.reg_2 && order.reg_3" class="pos-order-vehicle-registration-separator">/</span>
<EditableTableColumn
@@ -1514,7 +1460,7 @@ const formatCashierName = (order) => {
column="reg_3"
:parse-function="(value) => value || '-'"
:edit-function="SessionUser.objects.orders.showEditObjectFieldForm"
:permissionCheckFunction="(bookingobj) => canEditTransaction(order)"
:permissionCheckFunction="() => canEditTransaction(order)"
/>
</p>
</td>
@@ -1885,7 +1831,7 @@ const formatCashierName = (order) => {
>{{
SessionUser.functions.currency.toLocal(
getOrdersByInvoiceCollection(order.invoice_collection_id)
.reduce((sum, order) => sum + order.total_net_amount, 0)
.reduce((sum, invoiceOrder) => sum + invoiceOrder.total_net_amount, 0)
.toFixed(2)
)
}}
@@ -33,11 +33,6 @@ import PosDesktopDuplicateWarning from "@/components/displays/department/pos/ste
import { todayLocalDateOnly } from "@/services/dateOnly.js";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { parsePosRouteSearch } from "@/views/dashboards/departmentDashboard/modules/Pos/posRouteState.js";
import {
getOrderBookingReferenceValue,
getOrderBookingServiceText,
} from "@/components/displays/department/pos/utils/orderBookingDisplay.js";
const { t, locale } = useI18n();
const routeState = parsePosRouteSearch(window.location.search);
@@ -144,7 +139,7 @@ const getDepartmentVersion = () => {
return POS_STEP_1_VERSION;
};
const department_version = ref(getDepartmentVersion());
onMounted(() => {
focusOnReg1();
@@ -232,22 +227,7 @@ watch(
{ immediate: true }
);
const formatBookingDateTime = (booking) => {
const rawValue = booking?.datetime || booking?.created_at || booking?.date || null;
if (!rawValue) {
return t("admin.pos.not_found");
}
const parsedValue = new Date(rawValue);
if (Number.isNaN(parsedValue.getTime())) {
return rawValue;
}
return new Intl.DateTimeFormat(locale.value || undefined, {
dateStyle: "short",
timeStyle: "short",
}).format(parsedValue);
};
const formatDuplicateOrderDate = (value) => {
if (!value) {
@@ -273,34 +253,7 @@ const getDuplicateOrderContent = (order) => {
].join(" • ");
};
const bookingSelectionObjects = computed(() => {
return desktopStep1Context.value.bookingMatches.map((booking) => {
const plateText = [booking?.reg_1, booking?.reg_2].filter(Boolean).join(" / ");
const contentSegments = [
`${t("admin.pos.order_booking_selector.customer_label")}: ${booking?.customer_name || t("admin.pos.not_found")}`,
`${t("admin.pos.order_booking_selector.plates_label")}: ${plateText || t("admin.pos.not_found")}`,
`${t("common.reference")}: ${getOrderBookingReferenceValue(booking) || t("admin.pos.not_found")}`,
`${t("common.services")}: ${getOrderBookingServiceText(booking) || t("admin.pos.not_found")}`,
];
return {
id: booking.id,
label: t("admin.pos.order_booking_selector.option_title", {
id: booking.id,
datetime: formatBookingDateTime(booking),
}),
content: contentSegments.join(" • "),
buttons: [
{
label: t("common.select"),
action: () => handleBookingSelection(booking),
color: "primary",
testId: `pos-desktop-order-booking-use-${booking.id}`,
},
],
};
});
});
const duplicateDetailsObjects = computed(() => {
return duplicateOrders.value.map((order) => ({
@@ -408,7 +361,7 @@ const fetchDuplicateOrdersForContext = async (context) => {
}
};
const ensureDuplicateWarningState = async (context, options = {}) => {
const ensureDuplicateWarningState = async (context) => {
const normalizedContext = normalizeDuplicateContext(context);
if (!normalizedContext.reg1) {
duplicateOrders.value = [];
@@ -230,11 +230,7 @@ const fetchBookingDetails = async (bookings) => {
return false;
}
if (loadingBookingIds.value.includes(bookingId)) {
return false;
}
return true;
return !loadingBookingIds.value.includes(bookingId);
});
if (bookingsToLoad.length === 0) {
@@ -141,7 +141,7 @@ const getLPRFrameVisualFingerprintGetter = (image: LPRFrameInput): (() => string
const rememberParsedImageFingerprint = (image: LPRFrameInput) => {
const quick = getLPRFrameFingerprint(image);
const getContentFingerprint = getLPRFrameContentFingerprint(image);
const entry: ParsedFrameFingerprint = {
lastParsedImage.value = {
content: getContentFingerprint === null ? quick : null,
contentFingerprintPromise: null,
getContentFingerprint,
@@ -151,7 +151,6 @@ const rememberParsedImageFingerprint = (image: LPRFrameInput) => {
visual: getLPRFrameVisualFingerprint(image),
};
lastParsedImage.value = entry;
};
const markLastParsedImageOutcome = (outcome: ParsedFrameFingerprint["outcome"]) => {
@@ -41,7 +41,6 @@ import {
customer_id,
customer_attributes,
customer_attributes_status,
getCustomerEmail,
customer_name,
getAddonRestriction,
getProductRestriction,
@@ -429,7 +428,7 @@ const fetchPrimaryItemProduct = () => {
}
transactionItems.setPrimaryItem(product);
})
.catch((error) => (vehicleSelection.value = true)); // If there's an error, force the user to select a product.
.catch(() => (vehicleSelection.value = true)); // If there's an error, force the user to select a product.
};
const fetchLastOrder = (vehicleIndex: number) => {
@@ -1081,7 +1080,12 @@ const syncCurrentTransactionToOrder = async () => {
const desiredShapes = buildDesiredOrderItemShapes();
const currentShapes = normalizeExistingOrderItemShapes(existingItems);
const shouldForceRecreateForRepricing = desiredShapes.some((shape) => shape.skip_price_override === true);
const comparableDesiredShapes = desiredShapes.map(({ kind, relatedKey, skip_price_override, ...shape }) => shape);
const comparableDesiredShapes = desiredShapes.map(({
kind: _kind,
relatedKey: _relatedKey,
skip_price_override: _skipPriceOverride,
...shape
}) => shape);
if (!shouldForceRecreateForRepricing && JSON.stringify(currentShapes) === JSON.stringify(comparableDesiredShapes)) {
return true;
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue';
import Swal from 'sweetalert2';
const props = defineProps({
results: {
@@ -14,7 +14,7 @@ const props = withDefaults(defineProps<{
});
const emits = defineEmits<{
(e: 'location-updated', coords: { latitude: number | null; longitude: number | null }): void;
"location-updated": [coords: { latitude: number | null; longitude: number | null }];
}>();
const { coords, locatedAt, error, resume, pause } = useGeolocation({
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue';
import SessionUser from "@/components/session/token/SessionUser.vue";
import { popups } from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
@@ -53,16 +53,7 @@ const additionalSelectionLoading = ref(false);
const canSelectAdditionalItems = computed(() => true);
const checked = ref(props.defaultChecked);
// Function to generate a summary from the last order
function generateSummary(order: PosOrder): string {
if (!order || !order.items || order.items.length === 0) {
return "Denne ordre har ingen varer.";
}
const itemNames = order.items.map((item) => item?.product?.name || "Ukendt vare");
const uniqueItems = Array.from(new Set(itemNames));
return uniqueItems.length > 1
? `${uniqueItems.length} varer: ${uniqueItems.slice(0, 2).join(", ")}${uniqueItems.length > 2 ? " og flere" : ""}`
: uniqueItems[0];
}
// Functions to determine the display values
const displayLabel = computed(() => {
@@ -70,9 +61,7 @@ const displayLabel = computed(() => {
? `Vasket d. ${SessionUser.functions.date.toLocal(new Date(props?.lastOrder?.created_at))}`
: props.label;
});
const displaySubtitle = computed(() => {
return props.lastOrder ? generateSummary(props.lastOrder) : props.subtitle;
});
// Emit event on toggle
function onToggle(isOpen: boolean) {
checked.value = isOpen;
@@ -83,7 +72,7 @@ function onToggle(isOpen: boolean) {
}
/** Additional item management logic */
const exampleProducts = ref<PosProduct[]>([
const _exampleProducts = ref<PosProduct[]>([
{
id: 1,
name: "Extra Towel",
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ref } from "vue";
import ControlSelectAmount from "@/components/viewport/elements/controls/ControlSelectAmount.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { Addon } from "../objects/PosAddon.vue";
import PosDepartmentStepMobile2CategoryProduct from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2CategoryProduct.vue";
import { useI18n } from "vue-i18n";
@@ -1,25 +1,17 @@
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { PosOrder } from "../objects/PosOrder.vue";
import { computed } from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import WhiteBoxCard from "@/components/displays/boxes/WhiteBoxCard.vue";
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
import {PosProduct} from "@/components/displays/department/pos/steps/mobile/objects/PosProduct.vue";
import PosDepartmentStepMobile2CategoryProduct
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2CategoryProduct.vue";
import PosDepartmentStepMobile2Addons
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Addons.vue";
import {Addon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue";
import PosDepartmentStepMobileFixedBottomControl
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileFixedBottomControl.vue";
import PosDepartmentStepMobileButtonNextStep
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileButtonNextStep.vue";
import {
transactionItems
} from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
import PosDepartmentStep2MobileVehicleSelection
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep2MobileVehicleSelection.vue";
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2CategoryProduct.vue";
import "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Addons.vue";
import "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileFixedBottomControl.vue";
import "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileButtonNextStep.vue";
import "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep2MobileVehicleSelection.vue";
import "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import { order_id } from "@/components/shop/POSDepartmentProcess.vue";
const props = defineProps({
@@ -43,7 +35,7 @@ const displayLabel = computed(() => {
return props.label;
})
const displaySubtitle = computed(() => {
const _displaySubtitle = computed(() => {
return props.subtitle;
})
@@ -75,4 +67,4 @@ const onClick = () => {
</div>
</template>
<style scoped>
</style>
</style>
@@ -82,8 +82,8 @@ const itemNames = computed(() => {
</div>
</div>
<!-- Product addons -->
<template v-for="addon in item.addons" :key="addon.id" v-if="item.addons && item.addons.length > 0 && isAddonsVisible">
<div class="columns is-mobile is-vcentered is-gapless m-0" v-if="addon?.quantity > 0 && addon?.restricted !== true">
<template v-for="addon in item.addons" :key="addon.id">
<div class="columns is-mobile is-vcentered is-gapless m-0" v-if="item.addons && item.addons.length > 0 && isAddonsVisible && addon?.quantity > 0 && addon?.restricted !== true">
<div class="column"><!-- Product addons -->
<p class="has-text-left"><small>{{addon.quantity}}x {{ addon.name }}</small></p>
</div>
@@ -31,7 +31,7 @@ const props = defineProps({
});
const emit = defineEmits<{
(e: "copy"): void;
copy: [];
}>();
const orderItems = computed(() => (Array.isArray(props.lastOrder?.items) ? props.lastOrder.items : []));
@@ -47,11 +47,11 @@ const props = defineProps({
});
const emit = defineEmits<{
(e: "open-additional-items"): void;
"open-additional-items": [];
}>();
const productName = computed(() => props?.product?.name || "");
const productPrice = computed(() => SessionUser.functions.currency.toLocal(props?.product?.price || 0));
const productImage = computed(() => getPicture(props?.product?.piktogram || props?.product?.id || ""));
const productBadge = computed(() => `${t("common.selected")} ${t("common.vehicle").toLowerCase()}`);
const productChangeHint = computed(() => t("pos.hold_to_change_vehicle"));
@@ -35,7 +35,7 @@ const props = defineProps({
default: "primary",
},
});
const slots = defineSlots();
/** As Addons **/
const mergedAddons = ref<Addon[]>([]);
@@ -81,10 +81,9 @@ watch(
);
const getProductRestrictionForRow = (product: PosProduct) => {
const restriction = getProductRestriction(product, props.restrictionContext === "standaloneAdditionalService"
return getProductRestriction(product, props.restrictionContext === "standaloneAdditionalService"
? { isStandaloneAdditionalService: true }
: {});
return restriction;
};
const getProductRestrictionMessage = (product: PosProduct) => {
@@ -5,10 +5,10 @@ import PosDepartmentStepMobile1RegistrationNumber2
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumber2.vue";
import PosDepartmentStepMobile1RegistrationNumber3
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumber3.vue";
import { reg_1, reg_2, reg_3, loadOrderDetails, department_id } from "@/components/shop/POSDepartmentProcess.vue";
import { reg_1, reg_2, reg_3 } from "@/components/shop/POSDepartmentProcess.vue";
import { pos, popups } from "../objects/PosDepartmentStepMobileFlow.vue";
import { order_id } from "@/components/shop/POSDepartmentProcess.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const layout = {
classes: <string[]>["m-0", "p-0"],
@@ -16,7 +16,7 @@ const layout = {
}
/** Click handler for the registration number component */
const onClick = (field: string, value: string) => {
const onClick = (field: string) => {
// Get the vehicle number (after reg_*)
const vehicle_number = parseInt(field.split('_')[1]);
console.warn("Vehicle number", vehicle_number);
@@ -34,14 +34,7 @@ const onClick = (field: string, value: string) => {
* 'card' => $cardPaymentRequired,
* 'unknown' => !$customer_number && !$isKnown && !$isBooked
*/
const parseVehicleStatus = (status: any) => {
if (status.booked) return 'booked';
if (status.card) return 'card';
if (status.verified) return 'verified';
if (status.unknown) return 'unknown';
if (status.known) return 'known';
return 'unknown';
};
</script>
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { onBeforeUnmount, watch } from 'vue';
import WhiteBoxCard from "@/components/displays/boxes/WhiteBoxCard.vue";
import SessionUser from "@/components/session/token/SessionUser.vue";
import "@/components/session/token/SessionUser.vue";
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
@@ -65,7 +65,7 @@ const props = defineProps({
required: true
},
getPreviewLink: {
type: Function as () => (id: number) => Promise<string>,
type: Function as () => (_id: number) => Promise<string>,
required: true
},
showAttachWashCertificateAction: {
@@ -74,10 +74,10 @@ const props = defineProps({
}
})
const emits = defineEmits<{
(e: 'delete', id: number): void;
(e: 'upload', payload: { filename: string; base64String: string | ArrayBuffer | null }): void;
(e: 'download', id: number): void;
(e: 'attach-wash-certificate'): void;
delete: [id: number];
upload: [payload: { filename: string; base64String: string | ArrayBuffer | null }];
download: [id: number];
"attach-wash-certificate": [];
}>();
const onClickDelete = (id: number) => {
@@ -407,4 +407,3 @@ const onClickAttachWashCertificate = () => {
<style scoped>
</style>
@@ -3,7 +3,7 @@ import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { reference } from "@/components/shop/POSDepartmentProcess.vue";
import {
metadata,
camera,
attachments,
transactionHistory,
@@ -72,7 +72,7 @@ const props = defineProps({
},
});
/** Emits */
const emits = defineEmits([]); // No emits for now
// No emits for now
/** State */
const isAttachmentSelectorVisible = ref(false); // When enabled, the attach and reference buttons are hidden, and the attachment selector is shown
/** Reference */
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { ref } from "vue";
import { reference } from "@/components/shop/POSDepartmentProcess.vue";
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import { metadata, camera, attachments } from "../objects/PosDepartmentStepMobileFlow.vue";
import Swal from "sweetalert2";
import "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import { attachments } from "../objects/PosDepartmentStepMobileFlow.vue";
import "sweetalert2";
import {popups} from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
</script>
@@ -74,4 +74,4 @@ import {popups} from "@/components/displays/department/pos/steps/mobile/objects/
/**border-bottom: 1px solid #ddd; **/
white-space: nowrap;
}
</style>
</style>
@@ -427,7 +427,7 @@ const onClick = async () => {
}
// Proceed to the next step
switch (step.value) {
case 1:
case 1: {
if (!(await step1())) {
return;
}
@@ -453,6 +453,7 @@ const onClick = async () => {
bookingId: metadata.getBookingId?.() ?? null,
});
break;
}
case 2:
if (!(await step1())) {
return;
@@ -9,7 +9,7 @@ const ADD_CUSTOMER_SUBMIT_TEST_ID = "pos-mobile-add-customer-submit";
const { t } = useI18n();
const emit = defineEmits<{
(e: "close"): void;
close: [];
}>();
const {
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { computed, watch, ref } from "vue";
import { watch, ref } from "vue";
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
const emit = defineEmits<{
(e: 'close'): void;
}>();
defineEmits<{ close: [] }>();
const props = pos.popups.get()?.props;
const product = props.product;
const note = ref(product.notes || "");
@@ -4,7 +4,7 @@ import { metadata, popups } from "../objects/PosDepartmentStepMobileFlow.vue";
import { getResolvedMobileSafetySeal } from "../objects/mobileOrderCompletion.js";
import SessionUser from "@/components/session/token/SessionUser.vue";
const emit = defineEmits<{
(e: "close"): void;
close: [];
}>();
const popupProps = computed(() => popups.get()?.props || {});
@@ -2,9 +2,9 @@
import { popups } from "../objects/PosDepartmentStepMobileFlow.vue";
import SuccessCheck from "@/components/viewport/elements/icons/SuccessCheck.vue";
const emit = defineEmits<{
(e: 'close'): void;
}>();
defineEmits<{ close: [] }>();
</script>
<template>
@@ -48,4 +48,4 @@ const emit = defineEmits<{
.custom-subtitle {
font-size: 12px;
}
</style>
</style>
@@ -3,9 +3,8 @@
import PosDepartmentStep1MobileCustomerNotes
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileCustomerNotes.vue";
import { popups } from "../objects/PosDepartmentStepMobileFlow.vue";
const emit = defineEmits<{
(e: 'close'): void;
}>();
defineEmits<{ close: [] }>();
</script>
@@ -16,4 +15,4 @@ const emit = defineEmits<{
</template>
<style scoped>
</style>
</style>
@@ -1,9 +1,9 @@
<script setup lang="ts">
import { popups } from "../objects/PosDepartmentStepMobileFlow.vue";
const emit = defineEmits<{
(e: 'close'): void;
}>();
defineEmits<{ close: [] }>();
</script>
<template>
@@ -42,4 +42,4 @@ const emit = defineEmits<{
.custom-subtitle {
font-size: 12px;
}
</style>
</style>
@@ -1,12 +1,10 @@
<script setup lang="ts">
import { computed, watch, ref } from "vue";
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
import PosDepartmentStep1MobileImage
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileImage.vue";
const emit = defineEmits<{
(e: 'close'): void;
}>();
const props = pos.popups.get()?.props; // props: {base64String: attachment.base64String, filename: attachment.filename}
defineEmits<{ close: [] }>();
// props: {base64String: attachment.base64String, filename: attachment.filename}
</script>
<template>
@@ -16,4 +14,4 @@ const props = pos.popups.get()?.props; // props: {base64String: attachment.base6
</template>
<style scoped>
</style>
</style>
@@ -21,7 +21,7 @@ const { draftTransactionCustomerNumber, hasDraftTransactionCustomer } = useDraft
const { isCardPaymentAvailable } = useStripeReaderAvailability();
const emit = defineEmits<{
(e: "close"): void;
close: [];
}>();
const CUSTOMER_PICKER_MODE_INVOICE = "invoice";
@@ -19,7 +19,7 @@ type BookingRefreshResult = any[] | { bookings?: any[] } | void;
type OrderBookingPopupProps = {
bookings?: any[];
onSelect?: (booking: any) => Promise<void> | void;
onSelect?: (_booking: any) => Promise<void> | void;
onSkip?: () => Promise<void> | void;
onRefreshBookings?: () => Promise<BookingRefreshResult> | BookingRefreshResult;
departmentId?: number | string | null;
@@ -45,10 +45,7 @@ const toPositiveInteger = (value: any) => {
return Number.isInteger(parsedValue) && parsedValue > 0 ? parsedValue : null;
};
const normalizePlateValue = (value: any) =>
String(value ?? "")
.trim()
.toUpperCase();
const formatBookingDateTime = (booking: any) => {
const rawValue = booking?.datetime || booking?.created_at || booking?.date || null;
@@ -190,11 +187,7 @@ const fetchBookingDetails = async (bookingsToInspect: any[]) => {
return false;
}
if (loadingBookingIds.value.includes(bookingId)) {
return false;
}
return true;
return !loadingBookingIds.value.includes(bookingId);
});
if (bookingsToLoad.length === 0) {
@@ -3,9 +3,8 @@
import { popups } from "../objects/PosDepartmentStepMobileFlow.vue";
import PosDepartmentStep1MobileManualInput
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileManualInput.vue";
const emit = defineEmits<{
(e: 'close'): void;
}>();
defineEmits<{ close: [] }>();
const props = popups.get()?.props; // { vehicleNumber: number }
@@ -18,4 +17,4 @@ const props = popups.get()?.props; // { vehicleNumber: number }
</template>
<style scoped>
</style>
</style>
@@ -2,9 +2,8 @@
import PosDepartmentStep1MobileChangeReference
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileChangeReference.vue";
const emit = defineEmits<{
(e: 'close'): void;
}>();
defineEmits<{ close: [] }>();
</script>
@@ -15,4 +14,4 @@ const emit = defineEmits<{
</template>
<style scoped>
</style>
</style>
@@ -813,7 +813,7 @@ const updateTransactionPrices = (updatedProducts: PosProduct[] | null = null) =>
};
// Function to prompt for notes if required (input is a PosProduct)
const promptForNotesIfRequired = (product: PosProduct, callback: (notes: string) => void) => {
const promptForNotesIfRequired = (product: PosProduct, callback: (_notes: string) => void) => {
if (product.requires_note) {
// Open a popup to prompt for notes
popups.select("add_product_note", {
@@ -1480,15 +1480,10 @@ const selectLastVehicleOrder = (vehicleIndex: number) => {
const additionalItems = orderItems
.filter((item) => {
const itemId = Number(item?.id ?? 0);
if (itemId && itemId === Number(primaryOrderItem?.id ?? 0)) {
return false;
}
if (itemId && addonIds.has(itemId)) {
return false;
}
return true;
return !(
itemId &&
(itemId === Number(primaryOrderItem?.id ?? 0) || addonIds.has(itemId))
);
})
.map((item) => {
return {
@@ -1752,7 +1747,7 @@ const buildSnapshot = () => ({
});
// Debounce helper to avoid frequent writes
const debounce = <T extends (...args: any[]) => void>(fn: T, delay = 200) => {
const debounce = <T extends (..._args: any[]) => void>(fn: T, delay = 200) => {
let t: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<T>) => {
if (t) clearTimeout(t);
@@ -29,7 +29,7 @@ export type PopupComponentKey = 'select_customer' | 'select_order_booking' | 'co
export type SelectOrderBookingPopupProps = {
bookings?: any[];
onSelect?: (booking: any) => Promise<void> | void;
onSelect?: (_booking: any) => Promise<void> | void;
onSkip?: () => Promise<void> | void;
onRefreshBookings?: () => Promise<any[] | { bookings?: any[] } | void> | any[] | { bookings?: any[] } | void;
departmentId?: number | null;
@@ -2,7 +2,8 @@
import { ref, onMounted } from "vue";
import {metadata, reference, popups} from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
// Define the close event to emit when the component is closed
const emit = defineEmits(["close"]);
defineEmits(["close"]);
// Define props
const props = defineProps({
// autoFocus - Automatically focus the input field when the component is mounted
@@ -7,7 +7,8 @@ import { useI18n } from 'vue-i18n';
const { t } = useI18n();
// Define the close event to emit when the component is closed
const emit = defineEmits(["close"]);
defineEmits(["close"]);
// Define props
const props = defineProps({
showNotes: {
@@ -49,7 +50,7 @@ const deleteNoteById = async (noteId: number) => {
})
}
// Clear the list of notes
const clearNotes = () => {
const _clearNotes = () => {
notes.value = [];
}
@@ -74,13 +75,13 @@ const renderCashierNames = () => {
})
}
const cashierNames = computed(() => {
const _cashierNames = computed(() => {
return cashierNamesMap.value;
})
const input = ref("");
watch(notes, (newNotes) => {
watch(notes, () => {
// Check if any note is missing a cashier name
renderCashierNames();
}, { deep: true });
@@ -10,9 +10,9 @@ import {
} from "@/components/shop/POSDepartmentProcess.vue";
import PosDepartmentStepMobile1RegistrationNumberInputField from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile1RegistrationNumberInputField.vue";
import BarcodeScanner from "@/components/viewport/elements/icons/BarcodeScanner.vue";
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import RegistrationNumberSearchResult from "@/components/models/pos/step1/RegistrationNumberSearchResult.vue";
import VerifiedCustomer from "@/components/viewport/elements/icons/VerifiedCustomer.vue";
import "@/components/viewport/elements/icons/VerifiedCustomer.vue";
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
import type { PosSearchResult } from "../objects/PosSearchResult.vue";
import SessionUser from "@/components/session/token/SessionUser.vue";
@@ -74,8 +74,7 @@ const getRegistrationSourceRef = (vehicleIndex: number) => {
};
const setPersistedRegistrationValue = (vehicleIndex: number, value: string) => {
const normalizedValue = normalizeRegistrationValue(value);
getRegistrationSourceRef(vehicleIndex).value = normalizedValue;
getRegistrationSourceRef(vehicleIndex).value = normalizeRegistrationValue(value);
};
const syncVehicleRegistration = (vehicleIndex: number, value: string, overrides: Record<string, any> = {}) => {
@@ -16,7 +16,7 @@ import { todayLocalDateOnly } from "@/services/dateOnly.js";
// Define the close event to emit when the component is closed
const emit = defineEmits(["close"]);
/** Display variables */
const showError = ref(true);
const showCompleted = ref(false);
const showPending = ref(true);
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import GenericButton from "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import "@/components/viewport/page/templates/generic/graphics/GenericButton.vue";
import { pos } from "../objects/PosDepartmentStepMobileFlow.vue";
import GenericLabelSearch from "@/components/viewport/page/templates/generic/graphics/GenericLabelSearch.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
@@ -8,8 +8,7 @@ import PosDepartmentStepMobile2Categories
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Categories.vue";
import PosDepartmentStepMobile2Products
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2Products.vue";
import PosDepartmentStepMobile2ProductRecommendations
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2ProductRecommendations.vue";
import "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobile2ProductRecommendations.vue";
import {Addon} from "@/components/displays/department/pos/steps/mobile/objects/PosAddon.vue";
import { getProductRestriction } from "@/components/shop/POSDepartmentProcess.vue";
// Define the close event to emit when the component is closed
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
import "@/components/displays/boxes/WhiteBox.vue";
import PosOrdersSyncColumn from "@/components/displays/department/pos/sync/displays/PosOrdersSyncColumn.vue";
import ColorIndicator from "@/components/displays/buttons/ColorIndicator.vue";
</script>
@@ -1,18 +1,13 @@
<script setup>
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
const props = defineProps({
objects: {
type: Object,
required: true,
},
allowSelectMultiple: {
type: Boolean,
default: false,
},
defineProps({
objects: { type: Object, required: true },
allowSelectMultiple: { type: Boolean, default: false },
});
import {ref, watch} from 'vue';
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import { departments, getDepartments, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import {orderBy, orderDirection, setOrder, loadList} from "@/components/pagination/paginatedList.vue";
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
@@ -20,23 +15,16 @@ import Swal from "sweetalert2";
import ActionSettingsWheelItemLabel from "@/components/displays/buttons/ActionSettingsWheelItemLabel.vue";
const redirectDepartmentOrderPage = (orderId, departmentId) => {
// Send the user to the order page (In a new tab)
// `/admin/${departmentId}/modules/pos/orders/${orderId}`
window.open(`/admin/${departmentId}/modules/pos/orders/${orderId}`, '_blank');
};
const redirectSuperUserInvoiceCollectionPage = (invoiceCollectionId) => {
// Send the user to the invoice collection page (In a new tab)
window.open(`/superuser/invoices/${invoiceCollectionId}`, '_blank');
};
// Get the departments (If the departments are not already loaded)
if (departments.value.length === 0) {
getDepartments();
}
const clickUser = (customer_id) => {
const _clickUser = (customer_id) => {
if (!SessionUser.canAccessSuperUser()) {
return;
}
@@ -80,38 +68,7 @@ const onTableHeaderClick = (column) => {
loadList();
};
const tableHeaders = ref([
{
name: 'id',
title: 'Id',
sortable: true,
},
{
name: 'department',
title: 'Afdeling',
sortable: true,
},
{
name: 'customer_number',
title: SessionUser.objects.global.language.customer,
sortable: true,
},
{
name: 'order_id',
title: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single),
sortable: true,
},
{
name: 'created_at',
title: 'Dato',
sortable: true,
},
{
name: 'actions',
title: '',
sortable: false,
}
]);
const onClickMarkAsNotDuplicate = (object) => {
// Mark the order as not a duplicate
@@ -1,8 +1,4 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
const props = defineProps({
@@ -23,12 +19,12 @@ const props = defineProps({
default: false,
}
});
import {computed, ref, watch} from 'vue';
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import {showPopper, removePopperIfOpen, popperBox} from "@/components/displays/PopperDefault.vue";
import {computed, ref} from 'vue';
import { departments, getDepartments, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import "@/components/displays/PopperDefault.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import {orderBy, orderDirection, setOrder, loadList} from "@/components/pagination/paginatedList.vue";
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
import "@/components/displays/buttons/ActionSettingsWheelItem.vue";
import OrderContentTable from "@/components/displays/superuser/tables/OrderContentTable.vue";
import InvoiceMultipleCollectionsModal from "@/components/displays/modals/InvoiceMultipleCollectionsModal.vue";
import Swal from "sweetalert2";
@@ -79,9 +75,7 @@ const isOrderInvoicedWithStripe = (order) => {
return getStripeInvoiceModule(order) !== null;
};
const isOrderInvoiced = (order) => {
return isOrderInvoicedWithEconomic(order) || isOrderInvoicedWithStripe(order);
};
const doesOrderHaveInvoiceCollection = (order) => {
return getInvoiceCollection(order) !== null;
@@ -148,61 +142,9 @@ const getOrderInvoiceStatusBarColor = (order) => {
return color_class;
};
const getOrderInvoiceStatusContent = (order) => {
const content = [];
const economicInvoiceModule = getEconomicInvoiceModule(order);
const stripeInvoiceModule = getStripeInvoiceModule(order);
const invoiceCollection = getInvoiceCollection(order);
if (isOrderInvoiced(order)) {
// If the order is invoiced with e-conomic, show the invoice id
if (isOrderInvoicedWithEconomic(order)) {
isOrderEconomicInvoiceBooked(order)
? content.push('Bogført i e-conomic')
: content.push('Gemt som kladde i e-conomic');
isOrderEconomicInvoiceBooked(order)
? content.push('Faktura ID: <strong>' + (economicInvoiceModule?.invoice_id ?? '-') + '</strong>')
: content.push('Faktura Kladde ID: <strong>' + (economicInvoiceModule?.invoice_draft_id ?? '-') + '</strong>');
}
// If the order is invoiced with stripe, show the invoice id
if (isOrderInvoicedWithStripe(order)) {
isOrderStripeInvoicePaid(order)
? content.push('Fakturaen er <strong>betalt</strong>')
: content.push('Fakturaen er <strong>ikke betalt</strong>');
content.push('Faktura ID: <strong>' + (stripeInvoiceModule?.id ?? '-') + '</strong>');
}
content.push(''); // Add a line break
isOrderInvoicedWithEconomic(order)
? content.push('<span class="has-text-grey">Faktureret med e-conomic</span>')
: content.push('<span class="has-text-grey">Faktureret med stripe</span>');
} else {
// Check if the order has an invoice collection
if (doesOrderHaveInvoiceCollection(order)) {
// Check if the invoice collection is closed
if (isOrderInvoiceCollectionClosed(order)) {
content.push('Faktura samlingen er <strong>lukket</strong>');
// Check if the invoice collection is booked
if (isOrderInvoiceCollectionBooked(order)) {
content.push('Faktura samlingen er <strong>bogført</strong>');
}
}
content.push(''); // Add a line break
content.push('Faktura samling ID: <strong>' + (invoiceCollection?.id ?? '-') + '</strong>');
// Add the Error message if it exists
if (doesOrderHaveErrorMessage(order)) {
content.push('Fejl: <strong class="has-text-danger">' + order.error_message + '</strong>');
}
} else {
content.push(''); // Add a line break
content.push('<strong class="has-text-grey">Transaktionen er ikke faktureret</strong>');
}
}
// Return the list of content as a string, with line breaks between each item
return content.join('<br>');
};
const getOrderInvoiceStatusTitle = (order) => {
return isOrderInvoiced(order) ? 'Faktura status' : 'Transaktion status';
};
const clickUser = (customer_id) => {
if (!SessionUser.canAccessSuperUser()) {
@@ -248,73 +190,7 @@ const onTableHeaderClick = (column) => {
loadList();
};
const tableHeaders = ref([
{
name: 'id',
title: t('objects.columns.id'),
sortable: true,
},
...(props.isCustomerView) ? [] : [{
name: 'customer_id',
title: t('objects.columns.customer'),
sortable: true,
}],
...(props.isCustomerView) ? [] : [{
name: 'cashier_id',
title: t('objects.orders.columns.cashier'),
sortable: true,
}],
{
name: 'department_id',
title: t('objects.columns.department'),
sortable: true,
},
{
name: 'reg_1',
title: t('objects.bookings.columns.reg_1'),
sortable: true,
},
{
name: 'reg_2',
title: t('objects.orders.columns.reg_2'),
sortable: true,
},
{
name: 'reg_3',
title: t('objects.orders.columns.reg_3'),
sortable: true,
},
{
name: 'reference',
title: t('common.reference'),
sortable: true,
},
{
name: 'notes',
title: t('objects.columns.notes'),
sortable: true,
},
{
name: 'created_at',
title: t('common.date'),
sortable: true,
},
{
name: 'total_net_amount',
title: t('tables.orders.total'),
sortable: false,
},
{
name: 'completed_at',
title: t('objects.columns.completed'),
sortable: true,
},
{
name: 'actions',
title: t('tables.actions'),
sortable: false,
},
]);
const active_dropdown_object_ids = ref([]);
const toggleDropdown = (object) => {
@@ -19,7 +19,7 @@ const props = defineProps({
default: 0,
},
});
import {computed, ref, watch} from 'vue';
import {computed, watch} from 'vue';
import { departments, getDepartments, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
@@ -46,17 +46,14 @@ const redirectDepartmentOrderPage = (orderId, departmentId) => {
window.open(`/admin/${departmentId}/modules/pos/orders/${orderId}`, '_blank');
};
const redirectSuperUserInvoiceCollectionPage = (invoiceCollectionId) => {
// Send the user to the invoice collection page (In a new tab)
window.open(`/superuser/invoices/${invoiceCollectionId}`, '_blank');
};
// Get the departments (If the departments are not already loaded)
if (departments.value.length === 0) {
getDepartments();
}
const clickUser = (customer_id) => {
const _clickUser = (customer_id) => {
if (!SessionUser.canAccessSuperUser()) {
return;
}
@@ -100,43 +97,7 @@ const onTableHeaderClick = (column) => {
loadList();
};
const tableHeaders = ref([
{
name: 'id',
title: 'Id',
sortable: true,
},
{
name: 'department_id',
title: 'Afdeling',
sortable: true,
},
{
name: 'lane',
title: SessionUser.objects.global.language.lane,
sortable: true,
},
{
name: 'customer_number',
title: SessionUser.objects.global.language.customer,
sortable: true,
},
{
name: 'order_id',
title: SessionUser.functions.ucFirst(SessionUser.objects.orders.meta.labels.single),
sortable: true,
},
{
name: 'created_at',
title: 'Dato',
sortable: true,
},
{
name: 'actions',
title: '',
sortable: false,
}
]);
const onClickMarkAsNotDuplicate = (object) => {
// Mark the order as not a duplicate
@@ -887,7 +848,7 @@ const filteredObjects = computed(() => {
<!-- Notes: Generated order -->
<div class="field" v-if="object.notes">
<label class="label">Noter:</label>
<textarea class="textarea" readonly>{{ object.notes }}</textarea>
<textarea class="textarea" readonly :value="object.notes"></textarea>
</div>
<!-- Reference: Generated order -->
<div class="field" v-if="object.reference">
@@ -910,7 +871,7 @@ const filteredObjects = computed(() => {
<!-- Notes: Actual order -->
<div class="field" v-if="duplicate.notes">
<label class="label">Noter:</label>
<textarea class="textarea" readonly>{{ duplicate.notes }}</textarea>
<textarea class="textarea" readonly :value="duplicate.notes"></textarea>
</div>
<!-- Reference: Actual order -->
<div class="field" v-if="duplicate.reference">
@@ -6,13 +6,11 @@ const { t } = useI18n();
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import SelfServeConditionRulesPagination from "@/components/displays/pagination/models/DepartmentDashboard/SelfServeConditionRulesPagination.vue";
const props = defineProps({
condition: {
type: Object,
required: true,
},
defineProps({
condition: { type: Object, required: true },
});
const emit = defineEmits(["closeModal"]);
const closeModal = () => {
@@ -1,21 +1,19 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
import { usePaginatedListInstance } from "@/components/pagination/paginatedList.vue";
const { loadList, setList } = usePaginatedListInstance();
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,
},
defineProps({
objects: { type: Array, required: true },
});
</script>
<template>
@@ -38,11 +38,7 @@ const closeAttachmentsModal = () => {
selectedTaskForAttachments.value = null;
};
const editButtonsFunction = (id, column, value, onAfterSubmit) => {
editingButtonsId.value = id;
currentButtonsSelection.value = normalizeSelfServeTaskButtons(value);
showButtonsEditModal.value = true;
};
const saveButtonsEdit = async () => {
await SessionUser.objects.self_serve_tasks.set.buttons(
@@ -9,12 +9,10 @@ import EditableTableColumn from "@/components/displays/buttons/EditableTableColu
import { SessionUser } from "@/components/session/token/SessionUser.vue";
// Define the props
const props = defineProps({
objects: {
type: Array,
required: true,
},
defineProps({
objects: { type: Array, required: true },
});
</script>
<template>
@@ -1,7 +1,7 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
objects: {
@@ -10,8 +10,8 @@ const props = defineProps({
default: () => []
}
});
import { ref } from 'vue';
import { departments, getDepartments, isLoading, getDepartmentName} from "@/components/pagination/departmentTabs.vue";
import { departments, getDepartments} from "@/components/pagination/departmentTabs.vue";
const redirectDepartmentOrderPage = (orderId, departmentId) => {
// Send the user to the order page
@@ -16,7 +16,7 @@ const props = defineProps({
},
});
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { ref } from 'vue';
import ActionSettingsWheelButton from "@/components/displays/buttons/ActionSettingsWheelButton.vue";
import ActionSettingsWheelItem from "@/components/displays/buttons/ActionSettingsWheelItem.vue";
import RequiresPermission from "@/components/displays/permissionbased/RequiresPermission.vue";
@@ -4,15 +4,13 @@ import { useI18n } from "vue-i18n";
const { t } = useI18n();
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import InvoiceOrdersPagination
from "@/components/displays/pagination/models/SuperUserDashboard/InvoiceOrdersPagination.vue";
import "@/components/displays/pagination/models/SuperUserDashboard/InvoiceOrdersPagination.vue";
import ElementTabsBox from "@/components/displays/boxes/ElementTabsBox.vue";
import CollectedOrderInvoicesOverview
from "@/views/dashboards/superUserDashboard/collectedOrderInvoices/CollectedOrderInvoicesOverview.vue";
import { customer_id, searchAndSelectCustomer, isCustomerSelected } from "@/components/shop/POSDepartmentProcess.vue";
import "@/views/dashboards/superUserDashboard/collectedOrderInvoices/CollectedOrderInvoicesOverview.vue";
import { customer_id as selectedCustomerId, searchAndSelectCustomer, isCustomerSelected } from "@/components/shop/POSDepartmentProcess.vue";
import PosSelectedCustomer from "@/components/displays/department/pos/PosSelectedCustomer.vue";
import CollectedOrderInvoices from "@/views/dashboards/superUserDashboard/CollectedOrderInvoices.vue";
import VehiclesPagination from "@/components/displays/pagination/models/UserDashboard/VehiclesPagination.vue";
import "@/views/dashboards/superUserDashboard/CollectedOrderInvoices.vue";
import "@/components/displays/pagination/models/UserDashboard/VehiclesPagination.vue";
import VehiclesTable from "@/components/displays/user/vehicles/vehiclesTable.vue";
import OrdersTable from "@/components/displays/department/pos/orders/ordersTable.vue";
const props = defineProps({
@@ -64,7 +62,7 @@ const loadUser = async () => {
user.value = response.data.data;
// Select the customer if it is not null
if (user.value.customer_number) {
customer_id.value = user.value.customer_number;
selectedCustomerId.value = user.value.customer_number;
searchAndSelectCustomer(user.value.customer_number);
}
// Load the vehicles for the user
@@ -168,4 +166,4 @@ loadUser();
<style scoped>
</style>
</style>
@@ -63,7 +63,7 @@ const props = defineProps({
});
const emits = defineEmits(['update:isActive', 'selectedObject']);
const inputValue = ref('');
const selectedObject = ref(null);
const onChange = () => {
@@ -1,32 +1,14 @@
<script setup>
import { onBeforeUnmount } from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
const props = defineProps({
queued: {
type: Object,
required: true,
},
succeeded: {
type: Object,
required: true,
},
failed: {
type: Object,
required: true,
},
inProgress: {
type: Object,
required: true,
},
log: {
type: Object,
required: false,
},
jobs: {
type: Object,
required: false,
default: () => ({}),
},
defineProps({
queued: { type: Object, required: true },
succeeded: { type: Object, required: true },
failed: { type: Object, required: true },
inProgress: { type: Object, required: true },
log: { type: Object, required: false },
jobs: { type: Object, required: false, default: () => ({}) },
});
@@ -3,8 +3,8 @@ import { ref } from "vue";
import i18n from "@/i18n";
const t = i18n.global.t;
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import { getTitle, getDataColumns, getDataRows, setDataRows, getSaveFunction, clearProgress, progress, getCompleted, setCompleted, getResponses, setResponses, getParsedDataFormatFunction } from "@/components/forms/other/MassDataInserter.vue";
import { getTitle, getDataRows, setDataRows, getSaveFunction, clearProgress, progress, getCompleted, setCompleted, getResponses, setResponses, getParsedDataFormatFunction } from "@/components/forms/other/MassDataInserter.vue";
import MassDataInsertTable from "@/components/forms/other/elements/MassDataInsertTable.vue";
import MassDataInsertControls from "@/components/forms/other/elements/MassDataInsertControls.vue";
import MassDataInsertButtonSave from "@/components/forms/other/elements/MassDataInsertButtonSave.vue";
@@ -29,18 +29,18 @@ const canRerun = () => {
const onClickRemovePassingItems = () => {
const completedItems = getCompleted();
if (completedItems.length === 0) return;
// Filter out completed items from data rows
const currentRows = getDataRows();
const completedIds = new Set(completedItems.map(item => item.id));
const filteredRows = currentRows.filter(row => !completedIds.has(row.id));
setDataRows(filteredRows);
// Also filter out completed responses
const currentResponses = getResponses();
const filteredResponses = currentResponses.filter(r => !completedIds.has(r.object?.id));
setResponses(filteredResponses);
// Clear completed items
setCompleted([]);
};
@@ -55,7 +55,7 @@ const canRemovePassingItems = () => {
const onClickExport = () => {
const dataRows = getDataRows();
if (dataRows.length === 0) return;
// Create export object with metadata
const exportData = {
exportedAt: new Date().toISOString(),
@@ -71,7 +71,7 @@ const onClickExport = () => {
return cleanRow;
})
};
// Create blob and download
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
@@ -101,7 +101,7 @@ const onClickImport = () => {
const onFileImport = (event) => {
const file = event.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
@@ -121,7 +121,7 @@ const onFileImport = (event) => {
}
};
reader.readAsText(file);
// Reset file input so the same file can be imported again
event.target.value = '';
};
@@ -1,19 +1,17 @@
<script setup>
import { ref } from "vue";
import { SessionUser } from "@/components/session/token/SessionUser.vue";
import InvoiceOrdersPagination
from "@/components/displays/pagination/models/SuperUserDashboard/InvoiceOrdersPagination.vue";
import "@/components/displays/pagination/models/SuperUserDashboard/InvoiceOrdersPagination.vue";
import ElementTabsBox from "@/components/displays/boxes/ElementTabsBox.vue";
import CollectedOrderInvoicesOverview
from "@/views/dashboards/superUserDashboard/collectedOrderInvoices/CollectedOrderInvoicesOverview.vue";
import { customer_id, searchAndSelectCustomer, isCustomerSelected } from "@/components/shop/POSDepartmentProcess.vue";
import PosSelectedCustomer from "@/components/displays/department/pos/PosSelectedCustomer.vue";
import CollectedOrderInvoices from "@/views/dashboards/superUserDashboard/CollectedOrderInvoices.vue";
import VehiclesPagination from "@/components/displays/pagination/models/UserDashboard/VehiclesPagination.vue";
import VehiclesTable from "@/components/displays/user/vehicles/vehiclesTable.vue";
import OrdersTable from "@/components/displays/department/pos/orders/ordersTable.vue";
import InvoicesTable from "@/components/displays/user/invoices/invoicesTable.vue";
import CollectedOrderInvoicesTable from "@/components/displays/superuser/tables/collectedOrderInvoicesTable.vue";
import "@/views/dashboards/superUserDashboard/collectedOrderInvoices/CollectedOrderInvoicesOverview.vue";
import "@/components/shop/POSDepartmentProcess.vue";
import "@/components/displays/department/pos/PosSelectedCustomer.vue";
import "@/views/dashboards/superUserDashboard/CollectedOrderInvoices.vue";
import "@/components/displays/pagination/models/UserDashboard/VehiclesPagination.vue";
import "@/components/displays/user/vehicles/vehiclesTable.vue";
import "@/components/displays/department/pos/orders/ordersTable.vue";
import "@/components/displays/user/invoices/invoicesTable.vue";
import "@/components/displays/superuser/tables/collectedOrderInvoicesTable.vue";
import CollectedOrderInvoicesPagination
from "@/components/displays/pagination/models/SuperUserDashboard/CollectedOrderInvoicesPagination.vue";
import BuefyDateField from "@/components/forms/BuefyDateField.vue";
@@ -49,9 +47,7 @@ const handleKeydown = (event) => {
};
window.addEventListener("keydown", handleKeydown);
const ucFirst = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
const selectInvoiceCollection = (invoiceCollection) => {
props.onInvoiceCollectionSelected(invoiceCollection);
@@ -71,7 +67,7 @@ const get_invoice_collection_object = (invoiceCollection) => {
orders: [], // The orders associated with the invoice collection
}
}
const invoice_collections = ref([
const _invoice_collections = ref([
get_invoice_collection_object({
id: 1,
customer_number: props.customer_id,
@@ -1,5 +1,5 @@
<script setup>
import { metaItemsPerPage, list, metaTotalItems, metaCurrentPage, setList, setMeta, isLoaded, isEmpty, lastUpdated, setLastUpdated, setPage, loadSwitch, isLoading, setEndpoint, paginatedGetRequest, loadList, setMetaItemsPerPage} from "@/components/pagination/paginatedList.vue";
import { metaItemsPerPage, list, metaTotalItems, metaCurrentPage, isLoaded, setPage, isLoading, setEndpoint, loadList, setMetaItemsPerPage} from "@/components/pagination/paginatedList.vue";
import PaginationNavigation from "@/components/displays/pagination/PaginationNavigation.vue";
import LoadButtonWhileAwait from "@/components/request/LoadButtonWhileAwait.vue";
@@ -32,7 +32,7 @@ window.addEventListener('resize', () => {
<!-- If the selected number of items per page is not in the list, add it to the list -->
<option v-if="!perPageOptions.includes(selectedPerPage)" :value="selectedPerPage" :class="{ 'is-skeleton': isLoading }" selected>{{ selectedPerPage }}</option>
<!-- Show the options, with the selected option marked as selected -->
<option v-for="option in perPageOptions" :value="option" :class="{ 'is-skeleton': isLoading }" :selected="option === metaItemsPerPage.value">{{ option }}</option>
<option v-for="option in perPageOptions" :key="option" :value="option" :class="{ 'is-skeleton': isLoading }" :selected="option === metaItemsPerPage.value">{{ option }}</option>
</select>
</div>
</div>
@@ -164,4 +164,3 @@ export default defineComponent({
},
});
</script>
<template></template>
@@ -63,8 +63,8 @@ type FilterType = {
multiple?: boolean;
loading?: boolean;
loadFunction?: () => Promise<void>;
onInput?: (value: string | number | string[] | number[]) => void;
onChange?: (value: string | number | string[] | number[]) => void;
onInput?: (_value: string | number | string[] | number[]) => void;
onChange?: (_value: string | number | string[] | number[]) => void;
};
const setFilterValue = (filterName: string, value: string | number | string[] | number[]) => {
@@ -284,4 +284,4 @@ const renderFilterName = (filter: FilterType) => {
<style scoped>
</style>
</style>
@@ -37,7 +37,7 @@ const toggleActionDropdown = () => {
};
const handleReload = async () => {
if (Boolean(isLoading?.value)) {
if (isLoading?.value) {
return;
}
closeActionDropdown();
@@ -17,12 +17,14 @@ const { t } = useI18n()
<!-- Pagination skeleton while loading -->
<!-- Pagination (IF there's less than 20 pages) -->
<ul class="pagination-list mt-1">
<li v-for="page in totalPages" :key="page" v-if="totalPages <= 15">
<a class="pagination-link"
@click="setPage(page); loadFunction()"
:class="{ 'is-skeleton': isLoading, 'is-current': currentPage === page }"
>{{ page }}</a>
</li>
<template v-if="totalPages <= 15">
<li v-for="page in totalPages" :key="page">
<a class="pagination-link"
@click="setPage(page); loadFunction()"
:class="{ 'is-skeleton': isLoading, 'is-current': currentPage === page }"
>{{ page }}</a>
</li>
</template>
<!-- Pagination (IF there's more than 20 pages) -->
<li v-else>
<!-- Regular pagination -->
@@ -116,4 +118,4 @@ const { t } = useI18n()
.pagination-button {
min-width: 100px;
}
</style>
</style>
@@ -44,10 +44,7 @@ provide(PaginatedListKey, paginatedList);
const {
list,
loadList,
metaTotalItems,
setEndpoint,
setFilter,
setOrder,
} = paginatedList;
const sortedList = computed(() => {
@@ -86,11 +83,7 @@ const sortedList = computed(() => {
}
}
if (conditionFilter && itemCondition !== conditionFilter) {
return false;
}
return true;
return !conditionFilter || itemCondition === conditionFilter;
};
return [...list.value].filter(matchesScope);
@@ -39,9 +39,7 @@ provide(PaginatedListKey, paginatedList);
const {
list,
loadList,
metaTotalItems,
setEndpoint,
setFilter,
setOrder,
setHideSearchField,
} = paginatedList;
@@ -64,11 +62,7 @@ const sortedList = computed(() => {
return false;
}
if (vehicleTypeFilter && itemProduct !== 0 && itemProduct !== vehicleTypeFilter) {
return false;
}
return true;
return !vehicleTypeFilter || itemProduct === 0 || itemProduct === vehicleTypeFilter;
};
return [...list.value]

Some files were not shown because too many files have changed in this diff Show More