- Consolidate route search handling in `PosDepartmentMVP.vue` using `applyPosRouteSearch` for improved maintainability. - Refactor tab visibility logic in `ElementTabsBox.vue` with reusable `isHidden` utility, removing redundant code. - Enhance `SessionUser.vue` and `superUserObject.vue` modules with lazy bindings for improved initialization and performance. - Update `NavigationMenuItemsAdmin.vue` to validate department IDs with `hasValidDepartmentId` and watchers for reactive booking count updates. - Improve error handling logic in `HandleGlobalError.vue` and `loadOrderItems` for robustness against invalid inputs. - Add and update regression tests for session and superuser initialization to ensure compliance with binding contracts. - Refactor POS step components to streamline order-related logic and support immediate reactivity.
168 lines
4.3 KiB
Vue
168 lines
4.3 KiB
Vue
<script setup>
|
|
import { onMounted, ref, watch } from 'vue';
|
|
|
|
const props = defineProps({
|
|
/**
|
|
* The tabs to display
|
|
* @example
|
|
* [
|
|
* { name: 'Tab 1', slot: 'tab1', icon: 'fas fa-home' }
|
|
* { name: 'Tab 2', slot: 'tab2', icon: 'fas fa-user', hidden: true }
|
|
* { name: 'Tab 3', slot: 'tab3', hidden: () => { return false; } }
|
|
* ]
|
|
*/
|
|
tabs: {
|
|
type: Array,
|
|
required: true,
|
|
default: []
|
|
},
|
|
/**
|
|
* The default active tab (slot name)
|
|
* @example 'tab1'
|
|
*/
|
|
defaultActiveTab: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
/**
|
|
* Allow the tabs to be compact when there is only one tab
|
|
* @default false
|
|
*/
|
|
allowCompactWhenOneTab: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
/**
|
|
* Forcefully set the active tab
|
|
* @default null
|
|
* @type String
|
|
*/
|
|
forceActiveTab: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
});
|
|
|
|
const slots = defineSlots();
|
|
const emit = defineEmits(['update:activeTab']);
|
|
const visibleTabs = ref([]);
|
|
/**
|
|
* The active tab
|
|
* The is by default the prop defaultActiveTab, but can be changed by the user and won't be emitted, as it is not a prop.
|
|
*/
|
|
const activeTab = ref(props.defaultActiveTab);
|
|
|
|
const isHidden = (tab) => {
|
|
if (typeof tab.hidden === 'function') {
|
|
return tab.hidden();
|
|
}
|
|
return !!tab.hidden;
|
|
};
|
|
/**
|
|
* Update the visible tabs
|
|
* This checks if the tab is hidden or not, and returns the visible tabs
|
|
*/
|
|
const updateVisibleTabs = () => {
|
|
visibleTabs.value = props.tabs.filter((tab) => !isHidden(tab));
|
|
// If the active tab is not visible, set the active tab to the first visible tab
|
|
if (!visibleTabs.value.find((tab) => tab.slot === activeTab.value)) {
|
|
activeTab.value = visibleTabs.value[0]?.slot ?? null;
|
|
}
|
|
};
|
|
// Update the visible tabs every time the slot changes (If the tabs is a reactive object)
|
|
if (typeof props.tabs === 'object' && props.tabs.value !== undefined) {
|
|
watch(props.tabs, () => {
|
|
updateVisibleTabs();
|
|
});
|
|
}
|
|
|
|
// Update the visible tabs when the component is mounted
|
|
onMounted(() => {
|
|
updateVisibleTabs();
|
|
});
|
|
|
|
// Update the visible tabs when the current active tab changes "Hidden" status
|
|
watch(activeTab, () => {
|
|
updateVisibleTabs();
|
|
});
|
|
|
|
const isTabVisible = (tab) => {
|
|
const tmp_visible = !isHidden(tab);
|
|
// If the tab is the current active tab, and the tab is not visible, set the active tab to the first visible tab
|
|
if (tab.slot === activeTab.value && !tmp_visible) {
|
|
activeTab.value = visibleTabs.value[0]?.slot ?? null;
|
|
}
|
|
return tmp_visible;
|
|
};
|
|
|
|
const onClickTab = (tab) => {
|
|
// If the tab is not visible, do nothing
|
|
if (!isTabVisible(tab)) {
|
|
return;
|
|
}
|
|
// Set the active tab to the clicked tab
|
|
if (props.forceActiveTab === null || props.forceActiveTab === undefined) {
|
|
activeTab.value = tab;
|
|
}
|
|
// Emit the active tab
|
|
emit('update:activeTab', tab);
|
|
};
|
|
|
|
// Watch the forceActiveTab prop and set the active tab to the forceActiveTab value
|
|
watch(() => props.forceActiveTab, (newValue) => {
|
|
if (newValue !== null && newValue !== undefined) {
|
|
activeTab.value = newValue;
|
|
}
|
|
});
|
|
|
|
if (props.forceActiveTab !== null && props.forceActiveTab !== undefined) {
|
|
activeTab.value = props.forceActiveTab;
|
|
}
|
|
|
|
</script>
|
|
<template>
|
|
<div>
|
|
<!-- Display the tabs, if there are more than one tab (and the compact mode is not allowed) -->
|
|
<div
|
|
class="tabs is-centered is-fullwidth"
|
|
v-if="props.allowCompactWhenOneTab"
|
|
>
|
|
<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)"
|
|
>
|
|
<a @click="onClickTab(tab.slot)">
|
|
<!-- Display the tab icon (if any) -->
|
|
<span v-if="tab.icon" class="icon is-small">
|
|
<i :class="tab.icon"></i>
|
|
</span>
|
|
<!-- Display the tab name -->
|
|
<span>
|
|
{{ tab.name }}
|
|
</span>
|
|
</a>
|
|
</li>
|
|
</template>
|
|
</ul>
|
|
</div>
|
|
<!-- Display the content of the active tab -->
|
|
<div>
|
|
<template v-for="(tab, index) in props.tabs" :key="index">
|
|
<template v-if="slots[tab.slot] && tab.slot === activeTab">
|
|
<slot :name="tab.slot"></slot>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
|
|
</style>
|