489 lines
14 KiB
Vue
489 lines
14 KiB
Vue
<script setup>
|
|
import {
|
|
customer_name,
|
|
customer_id,
|
|
customer_data,
|
|
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";
|
|
import "bulma-switch/dist/css/bulma-switch.min.css";
|
|
import "bulma-block-list/src/block-list.scss";
|
|
import { SessionUser } from "@/components/session/token/SessionUser.vue";
|
|
import { useI18n } from "vue-i18n";
|
|
import RequiresPermission from "@/components/displays/permissionbased/RequiresPermission.vue";
|
|
import CustomerDiscountsDepartmentDisplay from "@/components/displays/department/pos/displays/CustomerDiscountsDepartmentDisplay.vue";
|
|
import ExpandableContentBox from "@/components/displays/boxes/ExpandableContentBox.vue";
|
|
import { getCustomerRuleDefinitions } from "@/features/customer/customerRuleRegistry.js";
|
|
import CustomerRuleTooltip from "@/features/customer/CustomerRuleTooltip.vue";
|
|
|
|
const { t } = useI18n();
|
|
const emit = defineEmits(["update:activeTab"]);
|
|
|
|
const panel_tabs = ref([
|
|
{
|
|
key: "details",
|
|
name: "Detaljer",
|
|
active: true,
|
|
permission: "get_user",
|
|
},
|
|
{
|
|
key: "rules",
|
|
name: "Regler",
|
|
active: false,
|
|
permission: "list_customer_attributes",
|
|
},
|
|
{
|
|
key: "shortcuts",
|
|
name: "Genveje",
|
|
active: false,
|
|
},
|
|
{
|
|
key: "discounts",
|
|
name: "Rabatter",
|
|
active: false,
|
|
permission: "get_custom_prices_other",
|
|
},
|
|
]);
|
|
|
|
const details = ref([
|
|
{
|
|
name: "Kunde nummer",
|
|
prop: "customerNumber",
|
|
icon: "fas fa-user",
|
|
},
|
|
{
|
|
name: "Navn",
|
|
prop: "name",
|
|
icon: "fas fa-user",
|
|
},
|
|
{
|
|
name: "Adresse",
|
|
prop: "address",
|
|
icon: "fas fa-map-marker-alt",
|
|
},
|
|
{
|
|
name: "Postnummer",
|
|
prop: "zip",
|
|
icon: "fas fa-map-marker-alt",
|
|
},
|
|
{
|
|
name: "Kunde by",
|
|
prop: "city",
|
|
icon: "fas fa-map-marker-alt",
|
|
},
|
|
{
|
|
name: "Telefon",
|
|
prop: "mobilePhone",
|
|
icon: "fas fa-phone",
|
|
},
|
|
{
|
|
name: "Email",
|
|
prop: "email",
|
|
icon: "fas fa-envelope",
|
|
},
|
|
{
|
|
name: "CVR",
|
|
prop: "corporateIdentificationNumber",
|
|
icon: "fas fa-id-card",
|
|
},
|
|
]);
|
|
|
|
const attributes = computed(() =>
|
|
getCustomerRuleDefinitions().map((attribute) => ({
|
|
...attribute,
|
|
name: t(attribute.labelKey),
|
|
prop: attribute.attribute,
|
|
description: t(attribute.descriptionKey),
|
|
}))
|
|
);
|
|
|
|
const shortcuts = ref([
|
|
{
|
|
name: "Administrer kunde",
|
|
icon: "fas fa-user-edit",
|
|
// Open link in new tab
|
|
action: () => {
|
|
SessionUser.adminUser.customers.fromCustomerNumber
|
|
.getUserId(customer_id.value)
|
|
.then((response) => window.open("/superuser/users/" + response.data.data.user_id, "_blank"));
|
|
},
|
|
visible: SessionUser.canAccessSuperUser(),
|
|
},
|
|
{
|
|
name: "Vaskeabonnement & Specialaftaler",
|
|
icon: "fas fa-user-edit",
|
|
// Open link in new tab
|
|
action: () => {
|
|
SessionUser.adminUser.customers.fromCustomerNumber
|
|
.getUserId(customer_id.value)
|
|
.then((response) => window.open("/superuser/users/" + response.data.data.user_id + "/other", "_blank"));
|
|
},
|
|
visible: SessionUser.canAccessSuperUser(),
|
|
},
|
|
]);
|
|
|
|
const props = defineProps({
|
|
variant: {
|
|
type: String,
|
|
default: "default",
|
|
validator: (value) => ["default", "order-detail", "sidebar"].includes(value),
|
|
},
|
|
});
|
|
|
|
const rootClasses = computed(() => ({
|
|
"pos-selected-customer": true,
|
|
[`pos-selected-customer--${props.variant}`]: true,
|
|
}));
|
|
const isSidebarVariant = computed(() => props.variant === "sidebar");
|
|
const isRulesTabActive = computed(() => panel_tabs.value[1]?.active === true);
|
|
const activeTabKey = computed(() => panel_tabs.value.find((tab) => tab.active)?.key ?? "details");
|
|
|
|
const setActiveTab = (selectedIndex) => {
|
|
panel_tabs.value.forEach((tab, index) => {
|
|
tab.active = index === selectedIndex;
|
|
});
|
|
};
|
|
|
|
const refreshCustomerAttributesForRules = async (selectedCustomerNumber = customer_id.value) => {
|
|
if (!selectedCustomerNumber) {
|
|
return customer_attributes.value;
|
|
}
|
|
|
|
return loadCustomerAttributes(selectedCustomerNumber);
|
|
};
|
|
|
|
/** Check if an attribute is set in the customer attributes */
|
|
const hasAttribute = (prop) => {
|
|
return customer_attributes.value.some((attribute) => attribute.attribute === prop);
|
|
};
|
|
|
|
watch(
|
|
activeTabKey,
|
|
(tabKey) => {
|
|
emit("update:activeTab", tabKey);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
watch(
|
|
isRulesTabActive,
|
|
(isActive) => {
|
|
if (!isActive) {
|
|
return;
|
|
}
|
|
void refreshCustomerAttributesForRules();
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
watch(
|
|
() => customer_id.value,
|
|
(nextCustomerId, previousCustomerId) => {
|
|
if (!nextCustomerId || nextCustomerId === previousCustomerId) {
|
|
return;
|
|
}
|
|
void refreshCustomerAttributesForRules(nextCustomerId);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
const isCustomerDetailsExpanded = ref(false);
|
|
|
|
const customer_data_has_empty_details = () => {
|
|
return details.value.some(
|
|
(detail) => customer_data.value[detail.prop] === null || customer_data.value[detail.prop] === ""
|
|
);
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="customer_name" :class="rootClasses">
|
|
<article class="pos-selected-customer__panel">
|
|
<div class="panel-block pos-selected-customer__header">
|
|
<div class="pos-selected-customer__title-wrap">
|
|
<span class="title is-6 pos-selected-customer__title">{{ customer_name }}</span>
|
|
</div>
|
|
<div
|
|
class="tabs is-small is-centered pos-selected-customer__tabs"
|
|
:class="{ 'pos-sidebar-panel-tabs': isSidebarVariant }"
|
|
:data-testid="isSidebarVariant ? 'pos-customer-sidebar-tabs' : undefined"
|
|
>
|
|
<ul>
|
|
<li
|
|
v-for="(tab, index) in panel_tabs"
|
|
:key="tab.key"
|
|
:class="{ 'is-active': tab.active }"
|
|
:data-testid="`pos-customer-tab-${tab.key}`"
|
|
@click="setActiveTab(index)"
|
|
>
|
|
<RequiresPermission :permission="tab.permission" v-if="tab.permission">
|
|
<a>
|
|
{{ tab.name }}
|
|
</a>
|
|
</RequiresPermission>
|
|
<a v-else>
|
|
{{ tab.name }}
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<template v-if="panel_tabs[0].active" 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"
|
|
>
|
|
<span class="panel-icon pos-selected-customer__icon">
|
|
<i :class="detail.icon" aria-hidden="true"></i>
|
|
</span>
|
|
<span class="pos-selected-customer__label">{{ detail.name }}</span>
|
|
<span class="pos-selected-customer__value">{{ customer_data[detail.prop] }}</span>
|
|
</a>
|
|
</template>
|
|
<!-- Details that's empty, (If there are any) -->
|
|
<ExpandableContentBox
|
|
v-if="panel_tabs[0].active && customer_data_has_empty_details()"
|
|
@update:expanded="isCustomerDetailsExpanded = !isCustomerDetailsExpanded"
|
|
v-bind:expanded="isCustomerDetailsExpanded"
|
|
/>
|
|
<!-- Attributes -->
|
|
<RequiresPermission permission="list_customer_attributes">
|
|
<template v-if="panel_tabs[1].active">
|
|
<div
|
|
v-for="attribute in attributes"
|
|
:key="attribute.attribute"
|
|
class="panel-block pos-selected-customer__row"
|
|
data-testid="pos-customer-rule-row"
|
|
:data-rule-attribute="attribute.attribute"
|
|
>
|
|
<span class="panel-icon pos-selected-customer__icon">
|
|
<i :class="attribute.icon" aria-hidden="true"></i>
|
|
</span>
|
|
<CustomerRuleTooltip
|
|
:attribute="attribute.attribute"
|
|
:active="hasAttribute(attribute.prop)"
|
|
:customer-number="customer_id"
|
|
:department-id="department_id"
|
|
:test-id="`pos-customer-rule-tooltip-${attribute.attribute}`"
|
|
>
|
|
<span class="pos-selected-customer__label">{{ attribute.name }}</span>
|
|
</CustomerRuleTooltip>
|
|
<span class="pos-selected-customer__value">
|
|
<span
|
|
v-if="hasAttribute(attribute.prop)"
|
|
@click="deleteAttribute(customer_id, attribute.prop).then(() => loadCustomerAttributes())"
|
|
>
|
|
<span class="field">
|
|
<input
|
|
:id="attribute.prop"
|
|
type="checkbox"
|
|
:name="attribute.prop"
|
|
class="switch"
|
|
checked="checked"
|
|
:disabled="!SessionUser.hasPermission('delete_customer_attribute')"
|
|
/>
|
|
<label :for="attribute.prop"></label>
|
|
</span>
|
|
</span>
|
|
<span v-else @click="createAttribute(customer_id, attribute.prop).then(() => loadCustomerAttributes())">
|
|
<span class="field">
|
|
<input
|
|
:id="attribute.prop"
|
|
type="checkbox"
|
|
:name="attribute.prop"
|
|
class="switch"
|
|
:disabled="!SessionUser.hasPermission('add_customer_attribute')"
|
|
/>
|
|
<label :for="attribute.prop"></label>
|
|
</span>
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div
|
|
v-if="$slots['rules-footer']"
|
|
class="pos-selected-customer__rules-footer"
|
|
data-testid="pos-customer-rules-footer"
|
|
>
|
|
<slot name="rules-footer"></slot>
|
|
</div>
|
|
</template>
|
|
</RequiresPermission>
|
|
<!-- Shortcuts -->
|
|
<template v-if="panel_tabs[2].active" v-for="shortcut in shortcuts" :key="shortcut.name">
|
|
<RequiresPermission 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>
|
|
</span>
|
|
<span class="pos-selected-customer__shortcut" @click="shortcut.action">{{ shortcut.name }}</span>
|
|
</a>
|
|
</RequiresPermission>
|
|
</template>
|
|
<!-- Discounts -->
|
|
<RequiresPermission permission="get_custom_prices_other">
|
|
<div class="panel-block pos-selected-customer__discounts" v-if="panel_tabs[3].active">
|
|
<CustomerDiscountsDepartmentDisplay />
|
|
</div>
|
|
</RequiresPermission>
|
|
</article>
|
|
<div></div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.pos-selected-customer {
|
|
height: auto;
|
|
min-height: 0;
|
|
}
|
|
|
|
.pos-selected-customer__panel {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0;
|
|
min-width: 0;
|
|
}
|
|
|
|
.pos-selected-customer__header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 0.75rem;
|
|
padding: 1rem 1rem 0.8rem;
|
|
border-bottom: 1px solid #e6edf7;
|
|
}
|
|
|
|
.pos-selected-customer__title-wrap {
|
|
min-width: 0;
|
|
}
|
|
|
|
.pos-selected-customer__title {
|
|
margin: 0;
|
|
font-size: 1.2rem;
|
|
line-height: 1.2;
|
|
color: #26384c;
|
|
}
|
|
|
|
.pos-selected-customer:not(.pos-selected-customer--sidebar) .pos-selected-customer__tabs {
|
|
margin: 0 0 0.35rem;
|
|
}
|
|
|
|
.pos-selected-customer:not(.pos-selected-customer--sidebar) .pos-selected-customer__tabs ul {
|
|
justify-content: flex-start;
|
|
margin: 0;
|
|
gap: 0.35rem;
|
|
flex-wrap: wrap;
|
|
border-bottom: none;
|
|
}
|
|
|
|
.pos-selected-customer:not(.pos-selected-customer--sidebar) .pos-selected-customer__tabs li a {
|
|
border: none;
|
|
border-radius: 999px;
|
|
min-height: 2.1rem;
|
|
padding: 0.45rem 0.85rem;
|
|
color: #5a6980;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.pos-selected-customer:not(.pos-selected-customer--sidebar) .pos-selected-customer__tabs li.is-active a {
|
|
background: #eef4ff;
|
|
color: #21446d;
|
|
}
|
|
|
|
.pos-selected-customer__row {
|
|
display: grid;
|
|
grid-template-columns: 1rem minmax(0, 1fr) auto;
|
|
align-items: start;
|
|
gap: 0.75rem;
|
|
padding: 0.85rem 1rem;
|
|
border-bottom: 1px solid #eef3f8;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.pos-selected-customer__icon {
|
|
margin-right: 0;
|
|
color: #62748a;
|
|
width: 1rem;
|
|
align-self: center;
|
|
}
|
|
|
|
.pos-selected-customer__label {
|
|
min-width: 0;
|
|
color: #4b5a6d;
|
|
font-weight: 600;
|
|
align-self: center;
|
|
}
|
|
|
|
.pos-selected-customer__value {
|
|
min-width: 0;
|
|
max-width: 14rem;
|
|
justify-self: end;
|
|
text-align: right;
|
|
color: #243446;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.pos-selected-customer__shortcut {
|
|
color: #21446d;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.pos-selected-customer__discounts {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.pos-selected-customer__rules-footer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
padding: 1rem;
|
|
border-top: 1px solid #eef3f8;
|
|
}
|
|
|
|
.pos-selected-customer--sidebar .pos-selected-customer__title,
|
|
.pos-selected-customer--order-detail .pos-selected-customer__title {
|
|
font-size: 1.05rem;
|
|
}
|
|
|
|
.pos-selected-customer--sidebar .pos-selected-customer__header,
|
|
.pos-selected-customer--order-detail .pos-selected-customer__header {
|
|
padding: 0.95rem 1rem 0.75rem;
|
|
}
|
|
|
|
.pos-selected-customer :deep(.column.is-12-desktop) {
|
|
padding: 0.35rem 1rem 0.5rem;
|
|
}
|
|
|
|
.pos-selected-customer :deep(.has-text-centered) {
|
|
margin: 0;
|
|
}
|
|
|
|
.pos-selected-customer :deep(.field) {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
@media screen and (max-width: 768px) {
|
|
.pos-selected-customer__row {
|
|
grid-template-columns: 1rem minmax(0, 1fr);
|
|
}
|
|
|
|
.pos-selected-customer__value {
|
|
grid-column: 2;
|
|
justify-self: start;
|
|
text-align: left;
|
|
max-width: none;
|
|
}
|
|
}
|
|
</style>
|