Files
pleno-vue/src/components/displays/department/pos/PosSelectedCustomer.vue
T

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>