Add permission checks and refine UI elements
Introduce `RequiresPermission` component to enforce role-based access control throughout the application. Refine UI elements with sharper input styles and ensure focused accessibility improvements (e.g., auto-focus on specific fields). Enhance vehicle data parsing and popper usability, while maintaining robust handling of asynchronous operations.
This commit is contained in:
@@ -87,6 +87,19 @@ export const ButtonColors = {
|
||||
},
|
||||
};
|
||||
|
||||
export const InputColors = {
|
||||
default: {
|
||||
/** The background color of the input */
|
||||
backgroundColor: "#f9f9f9",
|
||||
/** The text color of the input */
|
||||
textColor: GlobalColors.textColor,
|
||||
/** The hover color of the input */
|
||||
hoverColor: GlobalColors.hoverColor,
|
||||
/** The active color of the input */
|
||||
activeColor: GlobalColors.activeColor,
|
||||
},
|
||||
};
|
||||
|
||||
export const Colors = {
|
||||
/** The colors of the application */
|
||||
global: GlobalColors,
|
||||
@@ -96,5 +109,7 @@ export const Colors = {
|
||||
headers: HeaderColors,
|
||||
/** The colors of the buttons */
|
||||
buttons: ButtonColors,
|
||||
/** The colors of the inputs */
|
||||
inputs: InputColors,
|
||||
};
|
||||
</script>
|
||||
+6
-1
@@ -1 +1,6 @@
|
||||
@import './base.css';
|
||||
.input.has-sharp-edges {
|
||||
border-radius: 0;
|
||||
}
|
||||
textarea.has-sharp-edges {
|
||||
border-radius: 0;
|
||||
}
|
||||
@@ -51,8 +51,12 @@ export const removePopperIfOpen = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export const popperBox = (title, content) => {
|
||||
return `<div class="">
|
||||
export const popperBox = (title, content, data_key = null) => {
|
||||
// If the data key is not set, we want to use a random key
|
||||
if (!data_key) {
|
||||
data_key = Math.random().toString(36).substring(7);
|
||||
}
|
||||
return `<div class="popper-box" data-key="${data_key}">
|
||||
<article class="message">
|
||||
<div class="message-body">
|
||||
<strong>${title}</strong><br>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
import {ref, watch, onMounted} from 'vue';
|
||||
import {popperBox, showPopper, showPopperWithContent, removePopperIfOpen} from "@/components/displays/PopperDefault.vue";
|
||||
import { Colors } from "@/ThemeConfig.vue";
|
||||
|
||||
const activeTab = ref(null);
|
||||
|
||||
@@ -161,7 +162,7 @@ const generatePopperPlate = (scan, motorapi_data) => {
|
||||
use: decodeMotorAPIString(motorapi_data.use)
|
||||
}
|
||||
return `
|
||||
<div>
|
||||
<div class="numberplate-popper" data-plate="${scan.plate}">
|
||||
<p><strong>Mærke:</strong> ${data.make}</p>
|
||||
<p><strong>Model:</strong> ${data.model}</p>
|
||||
<p><strong>Variant:</strong> ${data.variant}</p>
|
||||
@@ -173,6 +174,14 @@ const generatePopperPlate = (scan, motorapi_data) => {
|
||||
|
||||
const isPlatePopperLoading = ref(false);
|
||||
|
||||
const shownNumberPlatePoppers = ref([]);
|
||||
|
||||
const removePlatePoppersIfOpen = () => {
|
||||
shownNumberPlatePoppers.value.forEach((popperId) => {
|
||||
removePopperIfOpen(popperId);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const showPopperPlate = (scan, element) => {
|
||||
// If the plate is loading, then return
|
||||
@@ -188,10 +197,13 @@ const showPopperPlate = (scan, element) => {
|
||||
SessionUser.superUser.modules.motorapi.functions.lookup(scan.plate).then((response) => {
|
||||
const motorapi_data = response.data.data;
|
||||
let content = generatePopperPlate(scan, motorapi_data);
|
||||
const uniqueId = "plate-popper-" + scan.plate;
|
||||
showPopper(popperBox(
|
||||
'Nummerplade: ' + scan.plate,
|
||||
content
|
||||
content,
|
||||
uniqueId
|
||||
), element);
|
||||
shownNumberPlatePoppers.value.push(uniqueId);
|
||||
isPlatePopperLoading.value = false;
|
||||
}).catch((error) => {
|
||||
isPlatePopperLoading.value = false;
|
||||
@@ -225,26 +237,41 @@ const getPlateScannerName = (id) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<article class="panel is-dark" id="scans-panel">
|
||||
<p class="panel-heading">Nummerplader</p>
|
||||
<p class="panel-tabs">
|
||||
<a v-for="(tab, index) in panel_tabs" :key="index" :class="{'is-active': tab.active}" @click="panel_tabs.forEach(tab => tab.active = false); tab.active = true ; changeTab(index)">
|
||||
{{ tab.name }}
|
||||
</a>
|
||||
<div @mouseleave="removePlatePoppersIfOpen()">
|
||||
<article class="" id="scans-panel">
|
||||
<p
|
||||
class="is-size-4 title"
|
||||
style="font-weight: 400;"
|
||||
>
|
||||
Nummerplader
|
||||
</p>
|
||||
<div class="panel-block" v-if="panel_tabs[1].active">
|
||||
<p class="control">
|
||||
<input @input="search($event.target.value)" class="input mt-2" type="text" placeholder="Søg efter nummerplade" v-model="metaSearch" />
|
||||
<p
|
||||
class="is-size-6 subtitle mb-1"
|
||||
>
|
||||
Seneste scanninger
|
||||
</p>
|
||||
|
||||
<p class="control mb-2">
|
||||
<input
|
||||
@input="search($event.target.value)"
|
||||
class="input mt-2 has-sharp-edges has-placeholder-italic"
|
||||
type="text"
|
||||
placeholder="Søg efter nummerplade"
|
||||
style="border-color: transparent;"
|
||||
:style="{
|
||||
'background-color': Colors.inputs.default.backgroundColor,
|
||||
'color': Colors.inputs.default.textColor
|
||||
}"
|
||||
v-model="metaSearch"
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
<div id="scans-list" class="scans-list">
|
||||
<a class="panel-block plate-scan-entry" v-for="scan in scansInfinityScroll" :key="scan.id" @click="selectScan(scan)"
|
||||
@mouseenter="showPopperPlate(scan, $event.target)"
|
||||
@mouseleave="removePopperIfOpen()">
|
||||
<span class="panel-icon">
|
||||
@mouseleave="removePlatePoppersIfOpen()">
|
||||
<!--<span class="panel-icon">
|
||||
<i class="fas fa-car" aria-hidden="true"></i>
|
||||
</span>
|
||||
</span> -->
|
||||
<span style="width: 50%;">{{ scan.plate }}</span>
|
||||
<span class="has-text-centered"
|
||||
v-if="plate_scanners.length > 0"
|
||||
@@ -290,4 +317,10 @@ const getPlateScannerName = (id) => {
|
||||
.scans-list::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
}
|
||||
.input.has-sharp-edges {
|
||||
border-radius: 0;
|
||||
}
|
||||
.input.has-placeholder-italic::placeholder {
|
||||
font-style: italic;
|
||||
}
|
||||
</style>
|
||||
@@ -1,32 +1,46 @@
|
||||
<script setup>
|
||||
import RequiresPermission from "@/components/displays/permissionbased/RequiresPermission.vue";
|
||||
|
||||
defineProps(['notes', 'isAddFormVisible', 'isOldNotesVisible']);
|
||||
import { ref } from 'vue';
|
||||
import { addCustomerNote, isCustomerSelected, showDeleteNoteDialog } from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
|
||||
const newNote = ref('');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<RequiresPermission permission="list_customer_notes">
|
||||
<div class="field">
|
||||
<label class="label" v-if="notes.length > 0 || isAddFormVisible">Kundebemærkninger</label>
|
||||
<div class="control mt-3" v-if="isOldNotesVisible">
|
||||
<div class="message" v-for="note in notes" :key="note.id">
|
||||
<div class="message-header">
|
||||
<p>{{ note.created_at }}</p>
|
||||
<RequiresPermission permission="delete_customer_note">
|
||||
<button class="delete" @click="showDeleteNoteDialog(note.id)"></button>
|
||||
</RequiresPermission>
|
||||
</div>
|
||||
<div class="message-body">
|
||||
{{ note.note }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<RequiresPermission permission="add_customer_note">
|
||||
<div class="control mt-3" v-if="isAddFormVisible">
|
||||
<textarea class="textarea" v-model="newNote" />
|
||||
<button class="button is-dark is-fullwidth mt-3" @click="addCustomerNote(newNote)" v-if="isCustomerSelected()">Tilføj bemærkning</button>
|
||||
<button class="button is-dark is-fullwidth mt-3" @click="addCustomerNote" v-else disabled>Tilføj bemærkning</button>
|
||||
<textarea class="textarea has-sharp-edges" v-model="newNote" />
|
||||
<button
|
||||
class="button is-dark is-fullwidth mt-3"
|
||||
@click="addCustomerNote(newNote)"
|
||||
v-if="isCustomerSelected() && newNote.length > 0"
|
||||
>
|
||||
Tilføj bemærkning
|
||||
</button>
|
||||
</div>
|
||||
</RequiresPermission>
|
||||
</div>
|
||||
</RequiresPermission>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -5,19 +5,22 @@ import {ref, watch} from 'vue';
|
||||
import 'bulma-switch/dist/css/bulma-switch.min.css';
|
||||
import {SessionUser} from "@/components/session/token/SessionUser.vue";
|
||||
import {popperBox, popper, removePopperIfOpen, showPopperWithContent, showPopper} from "@/components/displays/PopperDefault.vue";
|
||||
import RequiresPermission from "@/components/displays/permissionbased/RequiresPermission.vue";
|
||||
|
||||
const panel_tabs = ref([
|
||||
{
|
||||
name: 'Detaljer',
|
||||
active: true
|
||||
active: true,
|
||||
permission: 'list_customer_details'
|
||||
},
|
||||
{
|
||||
name: 'Regler',
|
||||
active: false
|
||||
active: false,
|
||||
permission: 'list_customer_attributes'
|
||||
},
|
||||
{
|
||||
name: 'Genveje',
|
||||
active: false
|
||||
active: false,
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -139,13 +142,30 @@ loadCustomerAttributes();
|
||||
|
||||
<template>
|
||||
<div v-if="customer_name">
|
||||
<article class="panel is-dark">
|
||||
<p class="panel-heading">{{ customer_name }}</p>
|
||||
<p class="panel-tabs">
|
||||
<a v-for="(tab, index) in panel_tabs" :key="index" :class="{'is-active': tab.active}" @click="panel_tabs.forEach(tab => tab.active = false); tab.active = true">
|
||||
<article class="">
|
||||
<div class="panel-block">
|
||||
<div class="columns is-vcentered">
|
||||
<div class="column">
|
||||
<span class="is-size-6 title">{{ customer_name }}</span>
|
||||
</div>
|
||||
<div class="column is-narrow">
|
||||
<div class="tabs is-right">
|
||||
<ul>
|
||||
<li v-for="(tab, index) in panel_tabs" :key="index" :class="{'is-active': tab.active}" @click="panel_tabs.forEach(tab => tab.active = false); tab.active = true">
|
||||
<RequiresPermission :permission="tab.permission" v-if="tab.permission">
|
||||
<a>
|
||||
{{ tab.name }}
|
||||
</a>
|
||||
</p>
|
||||
</RequiresPermission>
|
||||
<a v-else>
|
||||
{{ tab.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="panel-block" v-if="panel_tabs[0].active" v-for="detail in details" :key="detail.name">
|
||||
<span class="panel-icon">
|
||||
<i :class="detail.icon" aria-hidden="true"></i>
|
||||
@@ -153,6 +173,7 @@ loadCustomerAttributes();
|
||||
<span style="width: 50%;">{{ detail.name }}</span><span class="has-text-right" style="width: 50%;">{{ customer_data[detail.prop] }}</span>
|
||||
</a>
|
||||
<!-- Attributes -->
|
||||
<RequiresPermission permission="list_customer_attributes">
|
||||
<div class="panel-block" v-if="panel_tabs[1].active" v-for="attribute in attributes" :key="attribute.name">
|
||||
<span class="panel-icon">
|
||||
<i :class="attribute.icon" aria-hidden="true"></i>
|
||||
@@ -188,14 +209,17 @@ loadCustomerAttributes();
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</RequiresPermission>
|
||||
<!-- Shortcuts -->
|
||||
<template v-if="panel_tabs[2].active" v-for="shortcut in shortcuts" :key="shortcut.name">
|
||||
<a class="panel-block" v-if="shortcut.visible">
|
||||
<RequiresPermission permission="access_super_user">
|
||||
<a class="panel-block">
|
||||
<span class="panel-icon">
|
||||
<i :class="shortcut.icon" aria-hidden="true"></i>
|
||||
</span>
|
||||
<span @click="shortcut.action">{{ shortcut.name }}</span>
|
||||
</a>
|
||||
</RequiresPermission>
|
||||
</template>
|
||||
</article>
|
||||
<div>
|
||||
|
||||
@@ -11,9 +11,15 @@ import { clearCache } from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import WhiteBox from "@/components/displays/boxes/WhiteBox.vue";
|
||||
import ButtonsBox from "@/components/displays/boxes/ButtonsBox.vue";
|
||||
import { isCustomerSelected } from "@/components/shop/POSDepartmentProcess.vue";
|
||||
import {onMounted} from "vue";
|
||||
|
||||
// Clear cache
|
||||
clearCache();
|
||||
|
||||
// Select the "pos_select_customer_input" input field, and focus on it on page load
|
||||
onMounted(() => {
|
||||
document.getElementById("pos_select_customer_input").focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<script setup>
|
||||
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
||||
import {useSlots, defineProps} from 'vue';
|
||||
import {IS_DEV} from "@/main.js";
|
||||
const props = defineProps(['permission']);
|
||||
const slots = useSlots();
|
||||
|
||||
const isSlotEmpty = () => {
|
||||
return Object.keys(slots).length === 0;
|
||||
}
|
||||
|
||||
// Show an error if the permission prop is not provided
|
||||
if (!props.permission) {
|
||||
console.error('The permission prop is required for the requiresPermission component');
|
||||
}
|
||||
|
||||
// Check if the user has access to the permission
|
||||
|
||||
const checkAccess = () => {
|
||||
// If the global debug flag is set to true, show the missing permission warning in the console
|
||||
if (IS_DEV) {
|
||||
if (!SessionUser.hasPermission(props.permission)) {
|
||||
console.warn(`The user does not have the permission ${props.permission}`);
|
||||
}
|
||||
else {
|
||||
console.log(`The user has the permission ${props.permission}`);
|
||||
}
|
||||
}
|
||||
return SessionUser.hasPermission(props.permission);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="checkAccess()">
|
||||
<slot name="default"></slot>
|
||||
</div>
|
||||
<div v-else-if="slots.fail">
|
||||
<slot name="fail"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -68,7 +68,18 @@ const keyDownNextTabIndexButton = (event, tabIndex) => {
|
||||
<div class="field" :class="{'is-hidden': isCustomerSelected()}">
|
||||
<label class="label">Customer Search (ID / Name)</label>
|
||||
<div class="control" :class="{'is-loading': isSearching}">
|
||||
<CustomerSearchField :v-model="customer_id" class="input" type="text" :disabled="isCustomerSelected()" @keydown="arrowKeyHandler" @focusout="selectedDropdownItem = -1; lostfocus()" @focusin="showSelector = true" :tabindex="isCustomerSelected() ? -1 : 0" />
|
||||
<CustomerSearchField
|
||||
:v-model="customer_id"
|
||||
class="input has-sharp-edges"
|
||||
type="text"
|
||||
:disabled="isCustomerSelected()"
|
||||
@keydown="arrowKeyHandler"
|
||||
@focusout="selectedDropdownItem = -1; lostfocus()"
|
||||
@focusin="showSelector = true"
|
||||
:tabindex="isCustomerSelected() ? -1 : 0"
|
||||
autocomplete="off"
|
||||
id="pos_select_customer_input"
|
||||
/>
|
||||
</div>
|
||||
<div class="dropdown" :class="{'is-active': showSelector && searchCustomerResults.length > 0}">
|
||||
<div class="dropdown-menu">
|
||||
@@ -86,7 +97,7 @@ const keyDownNextTabIndexButton = (event, tabIndex) => {
|
||||
</div>
|
||||
<div class="field has-addons-right has-addons mt-1" :class="{'is-hidden': !isCustomerSelected()}">
|
||||
<p class="control is-expanded">
|
||||
<input class="input" type="text" v-model="customer_name" disabled />
|
||||
<input class="input has-sharp-edges" type="text" v-model="customer_name" disabled />
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-danger" @click="selectCustomer(null)" :tabindex="isCustomerSelected() ? 1 : -1">
|
||||
@@ -98,12 +109,27 @@ const keyDownNextTabIndexButton = (event, tabIndex) => {
|
||||
<div class="field">
|
||||
<label class="label">Reference nr.</label>
|
||||
<div class="control">
|
||||
<input class="input" type="text" v-model="reference" id="reference" tabindex="2" @keydown="keyDownNextInput($event, 'reg_1')" />
|
||||
<input
|
||||
class="input has-sharp-edges"
|
||||
type="text"
|
||||
v-model="reference"
|
||||
id="reference"
|
||||
tabindex="2"
|
||||
@keydown="keyDownNextInput($event, 'reg_1')"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Reg 1</label>
|
||||
<LicensePlateInput :tab-index="3" @keydown="keyDownNextInput($event, 'reg_2')" input-id="reg_1" v-model:inputModel="reg_1" :customer_number="parseInt(customer_id) || 0" />
|
||||
<LicensePlateInput
|
||||
:tab-index="3"
|
||||
@keydown="keyDownNextInput($event, 'reg_2')"
|
||||
input-id="reg_1"
|
||||
v-model:inputModel="reg_1"
|
||||
:customer_number="parseInt(customer_id) || 0"
|
||||
class="has-sharp-edges"
|
||||
/>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Reg 2</label>
|
||||
@@ -117,8 +143,11 @@ const keyDownNextTabIndexButton = (event, tabIndex) => {
|
||||
<LicensePlateInput :tab-index="5" @keydown="keyDownNextTabIndexButton($event, 6)" input-id="reg_3" v-model:inputModel="reg_3" :customer_number="parseInt(customer_id) || 0" />
|
||||
</div>
|
||||
</div>
|
||||
<PosNotes :notes="notes" :is-loading="false" :isAddFormVisible="SessionUser.adminUser" :isOldNotesVisible="false" class="mt-3"/>
|
||||
<PosNotes :notes="notes" :is-loading="false" :isAddFormVisible="SessionUser.adminUser" :isOldNotesVisible="false" class="mt-3" v-if="isCustomerSelected()" />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.input.has-sharp-edges {
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -210,7 +210,16 @@ const isDropdownItemSelected = (registrationNumber) => {
|
||||
<div
|
||||
class="control has-icons-right"
|
||||
:class="{'is-loading': isLoading}">
|
||||
<input class="input reg-input" type="text" @focusout="runParserFocusOut" @input="runParserInput" :class="{'is-danger': !isValueValid() && !isEmpty()}" :id="props.inputId" :tabindex="props.tabIndex" autocomplete="off" @keydown="pressedKey($event)" />
|
||||
<input class="input reg-input has-sharp-edges"
|
||||
type="text"
|
||||
@focusout="runParserFocusOut"
|
||||
@input="runParserInput"
|
||||
:class="{'is-danger': !isValueValid() && !isEmpty()}"
|
||||
:id="props.inputId"
|
||||
:tabindex="props.tabIndex"
|
||||
autocomplete="off"
|
||||
@keydown="pressedKey($event)"
|
||||
/>
|
||||
<span class="icon is-small is-right" v-if="isValueValid()">
|
||||
<i class="fas fa-check"></i>
|
||||
</span>
|
||||
@@ -225,17 +234,20 @@ const isDropdownItemSelected = (registrationNumber) => {
|
||||
{{ getError('license_plate_input_' + uniqueFieldId) }}
|
||||
</p>
|
||||
<!-- Vehicle data -->
|
||||
<div class="vehicle-data" v-if="isValueValid() && !isEmpty() && !isLoading && vehicleData.registration_number">
|
||||
<p class="vehicle-data-item">Mærke: <strong>{{ vehicleData.make }}</strong></p>
|
||||
<p class="vehicle-data-item">Model: <strong>{{ vehicleData.model }}</strong></p>
|
||||
<p class="vehicle-data-item">Variant: <strong>{{ vehicleData.variant }}</strong></p>
|
||||
<p class="vehicle-data-item">Type: <strong>{{ vehicleData.type }}</strong></p>
|
||||
<!-- Pretty print the vehicle data -->
|
||||
<!--<pre>{{ vehicleData }}</pre> -->
|
||||
</div>
|
||||
<p class="help is-info" v-if="isLoading">
|
||||
Henter køretøjsdata...
|
||||
</p>
|
||||
<p class="help is-info" v-if="isValueValid() && !isEmpty() && !isLoading && vehicleData.registration_number">
|
||||
<!-- Plate details -->
|
||||
{{ vehicleData.make }} - {{ vehicleData.model }} - {{ vehicleData.variant }} - {{ vehicleData.type }} - {{ SessionUser.adminUser.plates.parse_motorapi_string(vehicleData.use) }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- Search results -->
|
||||
<div class="dropdown" :class="{'is-active': customerRegistrationNumbers.length > 0 && !isLoading && !isEmpty() && isUserFocused()}">
|
||||
<div
|
||||
class="dropdown"
|
||||
:class="{'is-active': customerRegistrationNumbers.length > 0 && !isLoading && !isEmpty() && isUserFocused()}"
|
||||
v-if="customerRegistrationNumbers.length > 0 && !isLoading && !isEmpty() && isUserFocused()"
|
||||
>
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-content">
|
||||
<a class="dropdown-item" v-for="registrationNumber in customerRegistrationNumbers" :key="registrationNumber.registration_number" @click="setInputValue(registrationNumber.registration_number)" :class="{'is-active': isDropdownItemSelected(registrationNumber)}">
|
||||
@@ -248,5 +260,7 @@ const isDropdownItemSelected = (registrationNumber) => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.input.has-sharp-edges {
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -26,5 +26,22 @@ export const plates = {
|
||||
console.error(error);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Parse the motorapi string
|
||||
* This is used to decode the Unicode characters like u00f8 to ø
|
||||
* @param string The string to parse
|
||||
* @returns {*}
|
||||
*/
|
||||
parse_motorapi_string: (string) => {
|
||||
{
|
||||
// Check if the string contains u00, if so add a backslash
|
||||
if (string.includes('u00')) {
|
||||
string = string.replace(/u00/g, '\\u00');
|
||||
}
|
||||
// unicode decode
|
||||
return string.replace(/\\u[\dA-F]{4}/gi,
|
||||
(match) => String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16)));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -19,11 +19,19 @@ export const getAttributes = (customer_number) => {
|
||||
});
|
||||
};
|
||||
|
||||
const isAttributesLoading = ref(false);
|
||||
|
||||
export const createAttribute = (customer_number, attribute) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
// If the loading state is true, return null
|
||||
if (isAttributesLoading.value) {
|
||||
return null;
|
||||
}
|
||||
// Set the loading state to true
|
||||
isAttributesLoading.value = true;
|
||||
return axios.post(API_URL + '/customer/attributes', {
|
||||
customer_number,
|
||||
attribute
|
||||
@@ -31,7 +39,15 @@ export const createAttribute = (customer_number, attribute) => {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}).then(
|
||||
() => {
|
||||
isAttributesLoading.value = false;
|
||||
}
|
||||
).catch(
|
||||
() => {
|
||||
isAttributesLoading.value = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteAttribute = (customer_number, attribute) => {
|
||||
@@ -39,10 +55,24 @@ export const deleteAttribute = (customer_number, attribute) => {
|
||||
if (!token) {
|
||||
return null;
|
||||
}
|
||||
// If the loading state is true, return null
|
||||
if (isAttributesLoading.value) {
|
||||
return null;
|
||||
}
|
||||
// Set the loading state to true
|
||||
isAttributesLoading.value = true;
|
||||
return axios.delete(API_URL + '/customer/attributes?customer_number=' + customer_number + '&attribute=' + attribute, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
}).then(
|
||||
() => {
|
||||
isAttributesLoading.value = false;
|
||||
}
|
||||
).catch(
|
||||
() => {
|
||||
isAttributesLoading.value = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
</script>
|
||||
@@ -10,6 +10,7 @@ import App from './App.vue'
|
||||
import store from './store/user.vue'
|
||||
import {router} from "@/router.js";
|
||||
import {Colors} from "./ThemeConfig.vue";
|
||||
import '@/assets/main.css';
|
||||
import applyMiddleware from '@/middleware/index.js';
|
||||
|
||||
applyMiddleware(router);
|
||||
|
||||
Reference in New Issue
Block a user