Files
pleno-vue/src/components/displays/boxes/ElementTabsBox.vue
T
Jeppe Bundgaard 6a8e19b3e2 Refactor and optimize component logic and tests:
- 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.
2026-03-19 11:25:17 +01:00

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>