Files
mareike/app/Views/Components/TabbedPage.vue
T
2026-05-23 18:08:27 +02:00

157 lines
3.4 KiB
Vue

<script setup>
import { ref, shallowRef, onMounted } from "vue"
const props = defineProps({
tabs: {
type: Array,
required: true,
},
subTabIndex: {
type: Number,
required: false,
default: 0
}
})
const activeTab = ref(null)
const tabData = ref({})
const tabComponent = shallowRef(null)
const loading = ref(false)
const error = ref(null)
async function selectTab(index) {
const tab = props.tabs[index]
activeTab.value = index
tabComponent.value = null
error.value = null
loading.value = true
try {
const csrfToken = document
.querySelector('meta[name="csrf-token"]')
?.getAttribute('content')
const response = await fetch(tab.endpoint, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': csrfToken,
'X-Requested-With': 'XMLHttpRequest',
},
credentials: 'same-origin',
})
if (!response.ok) throw new Error("Fehler: " + response.status)
const json = await response.json()
tabData.value[index] = json
tabComponent.value = tab.component
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
onMounted(() => {
if (props.tabs.length > 0) {
selectTab(props.subTabIndex)
}
})
</script>
<template>
<div class="tabs">
<div class="tab-header">
<button
v-for="(tab, index) in tabs"
:key="index"
:class="['tab-button', { active: activeTab === index }]"
@click="selectTab(index)"
>
{{ tab.title }}
</button>
</div>
<div class="tab-content" id="tab-content">
<div v-if="loading"> Lädt...</div>
<div v-else-if="error"> {{ error }}</div>
<component
v-else-if="tabComponent"
:is="tabComponent"
:data="tabData[activeTab]"
:deep_jump_id="tabs[activeTab].deep_jump_id"
:deep_jump_id_sub="tabs[activeTab].deep_jump_id_sub"
/>
</div>
</div>
</template>
<style scoped>
.tabs {
border: 1px solid #ddd;
border-radius: 8px;
}
.tab-header {
display: flex;
border-bottom: 1px solid #ddd;
flex-wrap: wrap;
gap: 2px;
background: #f8f8f8;
}
.tab-button {
padding: 0.5rem 1rem;
cursor: pointer;
border: none;
background: #f8f8f8;
white-space: nowrap;
font-size: 0.95rem;
}
.tab-button.active {
font-weight: bold;
background: white;
border-bottom: 2px solid #0073aa;
}
.tab-content {
padding: 1rem;
width: 100%;
overflow-x: auto;
}
/* ─── Tablet ─── */
@media (max-width: 1023px) {
.tab-button {
font-size: 0.85rem;
padding: 0.5rem 0.75rem;
}
}
/* ─── Smartphone ─── */
@media (max-width: 639px) {
.tab-header {
flex-direction: column;
flex-wrap: nowrap;
}
.tab-button {
width: 100%;
text-align: left;
padding: 0.65rem 1rem;
border-bottom: 1px solid #e5e7eb;
font-size: 0.9rem;
}
.tab-button.active {
border-bottom: none;
border-left: 4px solid #0073aa;
background: #ffffff;
}
.tab-content {
padding: 0.75rem;
}
}
</style>