feat: add homepage components and API for settings and spielplan options
- Introduced new Vue components for homepage teasers: HomeLinksTeaser, HomeSpielplanTeamWidget, HomeTrainingTeaser, and HomeVereinsmeisterschaftenTeaser. - Created XML layout for tablet app window dump. - Implemented API endpoints for fetching and updating homepage settings. - Added API for retrieving spielplan options, including team extraction logic.
This commit is contained in:
@@ -33,7 +33,10 @@
|
||||
<!-- Tab Content -->
|
||||
<div>
|
||||
<CmsTermine v-if="activeTab === 'termine'" />
|
||||
<CmsMannschaften ref="cmsMannschaftenRef" v-if="activeTab === 'mannschaften'" />
|
||||
<CmsMannschaften
|
||||
v-if="activeTab === 'mannschaften'"
|
||||
ref="cmsMannschaftenRef"
|
||||
/>
|
||||
<CmsSpielplaene v-if="activeTab === 'spielplaene'" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
Verfügbare Elemente
|
||||
</h2>
|
||||
<p class="text-sm text-gray-600">
|
||||
Ziehen Sie die Elemente per Drag & Drop oder verwenden Sie die Pfeil-Buttons, um die Reihenfolge zu ändern.
|
||||
Legen Sie Reihenfolge, Sichtbarkeit und Marker fest (ohne Marker, cookie, eingeloggt).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -110,13 +110,33 @@
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Marker -->
|
||||
<div class="flex items-center">
|
||||
<label class="text-sm text-gray-700 mr-2">Marker</label>
|
||||
<select
|
||||
v-model="section.marker"
|
||||
class="px-2 py-1 border border-gray-300 rounded-lg text-sm bg-white"
|
||||
:disabled="isSaving"
|
||||
>
|
||||
<option value="">
|
||||
keiner
|
||||
</option>
|
||||
<option value="cookie">
|
||||
cookie
|
||||
</option>
|
||||
<option value="eingeloggt">
|
||||
eingeloggt
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Box -->
|
||||
<div class="mt-6 p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||
<p class="text-sm text-blue-800">
|
||||
<strong>Hinweis:</strong> Deaktivierte Elemente werden auf der Startseite nicht angezeigt, bleiben aber in der Konfiguration erhalten.
|
||||
<strong>Hinweis:</strong> Marker steuern die Sichtbarkeit auf der Web-Startseite: cookie zeigt das Element bei vorhandenen Cookies, eingeloggt nur für angemeldete Nutzer.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -166,6 +186,30 @@ const availableSections = {
|
||||
kontakt: {
|
||||
label: 'Kontakt-Boxen',
|
||||
description: 'Mitglied werden & Kontakt aufnehmen'
|
||||
},
|
||||
training: {
|
||||
label: 'Training-Teaser',
|
||||
description: 'Direktzugang zu Training, Trainern und Anfängerbereich'
|
||||
},
|
||||
links: {
|
||||
label: 'Links-Teaser',
|
||||
description: 'Direktzugang zu den nützlichen Vereinslinks'
|
||||
},
|
||||
vereinsmeisterschaften: {
|
||||
label: 'Vereinsmeisterschaften-Teaser',
|
||||
description: 'Direktzugang zu Meisterschaftsergebnissen'
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeMarker(marker) {
|
||||
return marker === 'cookie' || marker === 'eingeloggt' ? marker : ''
|
||||
}
|
||||
|
||||
function normalizeSection(section) {
|
||||
return {
|
||||
id: section?.id,
|
||||
enabled: section?.enabled !== false,
|
||||
marker: normalizeMarker(section?.marker)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,17 +229,23 @@ const loadConfig = async () => {
|
||||
|
||||
// Standard-Reihenfolge, falls nicht vorhanden
|
||||
const defaultSections = [
|
||||
{ id: 'banner', enabled: true },
|
||||
{ id: 'termine', enabled: true },
|
||||
{ id: 'spiele', enabled: true },
|
||||
{ id: 'aktuelles', enabled: true },
|
||||
{ id: 'kontakt', enabled: true }
|
||||
{ id: 'banner', enabled: true, marker: '' },
|
||||
{ id: 'termine', enabled: true, marker: '' },
|
||||
{ id: 'spiele', enabled: true, marker: '' },
|
||||
{ id: 'aktuelles', enabled: true, marker: '' },
|
||||
{ id: 'kontakt', enabled: true, marker: '' },
|
||||
{ id: 'training', enabled: false, marker: '' },
|
||||
{ id: 'links', enabled: false, marker: '' },
|
||||
{ id: 'vereinsmeisterschaften', enabled: false, marker: '' }
|
||||
]
|
||||
|
||||
if (config.homepage && config.homepage.sections && Array.isArray(config.homepage.sections)) {
|
||||
// Validiere und merge: Nur bekannte IDs verwenden, fehlende hinzufügen
|
||||
const knownIds = new Set(config.homepage.sections.map(s => s.id))
|
||||
const merged = [...config.homepage.sections]
|
||||
const normalized = config.homepage.sections
|
||||
.filter(s => s?.id)
|
||||
.map(normalizeSection)
|
||||
const knownIds = new Set(normalized.map(s => s.id))
|
||||
const merged = [...normalized]
|
||||
|
||||
// Füge fehlende Standard-Elemente hinzu
|
||||
for (const defaultSection of defaultSections) {
|
||||
@@ -204,9 +254,9 @@ const loadConfig = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
sections.value = merged
|
||||
sections.value = merged.map(normalizeSection)
|
||||
} else {
|
||||
sections.value = [...defaultSections]
|
||||
sections.value = defaultSections.map(normalizeSection)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Konfiguration:', error)
|
||||
@@ -242,7 +292,7 @@ const saveConfig = async () => {
|
||||
if (!config.homepage) {
|
||||
config.homepage = {}
|
||||
}
|
||||
config.homepage.sections = sections.value
|
||||
config.homepage.sections = sections.value.map(normalizeSection)
|
||||
|
||||
// Speichere Config
|
||||
await $fetch('/api/config', {
|
||||
|
||||
545
pages/index.vue
545
pages/index.vue
@@ -1,55 +1,562 @@
|
||||
<template>
|
||||
<div class="min-h-full">
|
||||
<component
|
||||
:is="getComponentForSection(section.id)"
|
||||
<div
|
||||
v-if="canCustomizeHome"
|
||||
class="fixed right-4 bottom-14 z-[60]"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="w-9 h-9 rounded-full bg-white border border-gray-200 shadow-sm hover:shadow-md hover:bg-gray-50 flex items-center justify-center text-gray-700"
|
||||
:title="editorOpen ? 'Startseiteneditor schließen' : 'Startseiteneditor öffnen'"
|
||||
@click="editorOpen ? closeEditor() : openEditor()"
|
||||
>
|
||||
<X
|
||||
v-if="editorOpen"
|
||||
:size="15"
|
||||
/>
|
||||
<SlidersHorizontal
|
||||
v-else
|
||||
:size="15"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="editorOpen"
|
||||
class="fixed right-4 bottom-28 z-[60] w-[min(92vw,30rem)] bg-white border border-gray-200 rounded-xl shadow-xl p-4"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-3 mb-3">
|
||||
<h2 class="text-base font-semibold text-gray-900">
|
||||
Startseiteneditor
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-gray-500 mb-3">
|
||||
{{ isLoggedIn ? 'Einstellungen werden serverseitig für deinen Nutzer gespeichert.' : 'Einstellungen werden nur im Browser-Cookie gespeichert.' }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-if="editorSections.length === 0"
|
||||
class="text-sm text-gray-600"
|
||||
>
|
||||
Keine Elemente zur Konfiguration gefunden.
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="space-y-2 max-h-[50vh] overflow-auto pr-1"
|
||||
>
|
||||
<div
|
||||
v-for="(section, index) in editorSections"
|
||||
:key="section.key"
|
||||
class="p-3 border border-gray-200 rounded-lg bg-gray-50"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-medium text-gray-900 truncate">
|
||||
{{ getSectionLabel(section) }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 truncate">
|
||||
{{ section.id }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
class="px-2 py-1 text-xs rounded border border-gray-300 bg-white disabled:opacity-50"
|
||||
:disabled="index === 0 || isSavingSettings"
|
||||
@click="moveEditorSectionUp(index)"
|
||||
>
|
||||
Hoch
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="px-2 py-1 text-xs rounded border border-gray-300 bg-white disabled:opacity-50"
|
||||
:disabled="index === editorSections.length - 1 || isSavingSettings"
|
||||
@click="moveEditorSectionDown(index)"
|
||||
>
|
||||
Runter
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700">
|
||||
<input
|
||||
v-model="section.enabled"
|
||||
type="checkbox"
|
||||
:disabled="isSavingSettings"
|
||||
>
|
||||
Anzeigen
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="section.id === 'spielplan_team'"
|
||||
class="mt-3 grid grid-cols-1 gap-2"
|
||||
>
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Saison</label>
|
||||
<select
|
||||
:value="section.config?.season || ''"
|
||||
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded bg-white"
|
||||
:disabled="isSavingSettings || widgetOptionsLoading"
|
||||
@change="onWidgetSeasonChanged(section, $event.target.value)"
|
||||
>
|
||||
<option
|
||||
v-for="season in spielplanSeasons"
|
||||
:key="season.slug"
|
||||
:value="season.slug"
|
||||
>
|
||||
{{ season.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Mannschaft</label>
|
||||
<select
|
||||
:value="teamKeyFromConfig(section.config)"
|
||||
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded bg-white"
|
||||
:disabled="isSavingSettings || widgetOptionsLoading"
|
||||
@change="onWidgetTeamChanged(section, $event.target.value)"
|
||||
>
|
||||
<option
|
||||
v-for="team in getTeamsForSeason(section.config?.season)"
|
||||
:key="team.key"
|
||||
:value="team.key"
|
||||
>
|
||||
{{ team.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-4 border-t border-gray-200">
|
||||
<p class="text-sm font-semibold text-gray-900 mb-2">
|
||||
Widget hinzufügen
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mb-3">
|
||||
Spielplan-Widget für eine konkrete Mannschaft und Saison.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Saison</label>
|
||||
<select
|
||||
v-model="newWidgetSeason"
|
||||
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded bg-white"
|
||||
:disabled="widgetOptionsLoading"
|
||||
@change="onNewWidgetSeasonChanged"
|
||||
>
|
||||
<option
|
||||
v-for="season in spielplanSeasons"
|
||||
:key="season.slug"
|
||||
:value="season.slug"
|
||||
>
|
||||
{{ season.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-xs text-gray-500">Mannschaft</label>
|
||||
<select
|
||||
v-model="newWidgetTeamKey"
|
||||
class="mt-1 w-full px-2 py-1.5 text-sm border border-gray-300 rounded bg-white"
|
||||
:disabled="widgetOptionsLoading"
|
||||
>
|
||||
<option
|
||||
v-for="team in newWidgetTeams"
|
||||
:key="team.key"
|
||||
:value="team.key"
|
||||
>
|
||||
{{ team.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mt-3 px-3 py-2 text-sm rounded-lg border border-primary-300 text-primary-700 hover:bg-primary-50 disabled:opacity-50"
|
||||
:disabled="!canAddSpielplanWidget || isSavingSettings"
|
||||
@click="addSpielplanWidget"
|
||||
>
|
||||
Spielplan-Widget hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 text-sm rounded-lg bg-primary-600 hover:bg-primary-700 text-white disabled:opacity-50"
|
||||
:disabled="isSavingSettings || editorSections.length === 0"
|
||||
@click="saveEditor"
|
||||
>
|
||||
{{ isSavingSettings ? 'Speichert...' : 'Speichern' }}
|
||||
</button>
|
||||
<p
|
||||
v-if="editorMessage"
|
||||
class="text-sm"
|
||||
:class="editorMessageType === 'error' ? 'text-red-700' : 'text-green-700'"
|
||||
>
|
||||
{{ editorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template
|
||||
v-for="section in enabledSections"
|
||||
:key="section.id"
|
||||
/>
|
||||
:key="section.key"
|
||||
>
|
||||
<HomeSpielplanTeamWidget
|
||||
v-if="section.id === 'spielplan_team'"
|
||||
:season="section.config?.season"
|
||||
:team-name="section.config?.teamName"
|
||||
:team-age-group="section.config?.teamAgeGroup"
|
||||
/>
|
||||
<component
|
||||
:is="getComponentForSection(section.id)"
|
||||
v-else
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { SlidersHorizontal, X } from 'lucide-vue-next'
|
||||
import Hero from '~/components/Hero.vue'
|
||||
import HomeTermine from '~/components/HomeTermine.vue'
|
||||
import Spielplan from '~/components/Spielplan.vue'
|
||||
import PublicNews from '~/components/PublicNews.vue'
|
||||
import HomeActions from '~/components/HomeActions.vue'
|
||||
import HomeTrainingTeaser from '~/components/HomeTrainingTeaser.vue'
|
||||
import HomeLinksTeaser from '~/components/HomeLinksTeaser.vue'
|
||||
import HomeVereinsmeisterschaftenTeaser from '~/components/HomeVereinsmeisterschaftenTeaser.vue'
|
||||
import HomeSpielplanTeamWidget from '~/components/HomeSpielplanTeamWidget.vue'
|
||||
|
||||
const { data: config } = await useFetch('/api/config')
|
||||
const { data: authStatus } = await useFetch('/api/auth/status')
|
||||
const { data: homepageSettings, refresh: refreshHomepageSettings } = await useFetch('/api/homepage/settings')
|
||||
|
||||
// Standard-Reihenfolge, falls Config nicht vorhanden
|
||||
const defaultSections = [
|
||||
const editorOpen = ref(false)
|
||||
const editorSections = ref([])
|
||||
const isSavingSettings = ref(false)
|
||||
const editorMessage = ref('')
|
||||
const editorMessageType = ref('success')
|
||||
const widgetOptionsLoading = ref(false)
|
||||
const spielplanSeasons = ref([])
|
||||
const teamOptionsBySeason = ref({})
|
||||
const newWidgetSeason = ref('')
|
||||
const newWidgetTeamKey = ref('')
|
||||
|
||||
const baseSectionDefinitions = [
|
||||
{ id: 'banner', enabled: true },
|
||||
{ id: 'termine', enabled: true },
|
||||
{ id: 'spiele', enabled: true },
|
||||
{ id: 'aktuelles', enabled: true },
|
||||
{ id: 'kontakt', enabled: true }
|
||||
{ id: 'kontakt', enabled: true },
|
||||
{ id: 'training', enabled: false },
|
||||
{ id: 'links', enabled: false },
|
||||
{ id: 'vereinsmeisterschaften', enabled: false }
|
||||
]
|
||||
const baseSectionIds = new Set(baseSectionDefinitions.map(section => section.id))
|
||||
|
||||
// Lade Sections aus Config oder verwende Standard
|
||||
const sections = computed(() => {
|
||||
if (config.value?.homepage?.sections && Array.isArray(config.value.homepage.sections)) {
|
||||
return config.value.homepage.sections
|
||||
function createEntryKey(id) {
|
||||
return `${id}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
function normalizeConfig(config) {
|
||||
if (!config || typeof config !== 'object') return undefined
|
||||
const normalized = {
|
||||
season: config.season ? String(config.season) : '',
|
||||
teamName: config.teamName ? String(config.teamName) : '',
|
||||
teamAgeGroup: config.teamAgeGroup ? String(config.teamAgeGroup) : ''
|
||||
}
|
||||
return defaultSections
|
||||
if (!normalized.season && !normalized.teamName && !normalized.teamAgeGroup) {
|
||||
return undefined
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
function normalizeEntry(entry, index, fallbackId = '') {
|
||||
const id = String(entry?.id || fallbackId || '').trim()
|
||||
if (!id) return null
|
||||
return {
|
||||
key: entry?.key ? String(entry.key) : `${id}-${index}`,
|
||||
id,
|
||||
enabled: entry?.enabled !== false,
|
||||
config: normalizeConfig(entry?.config)
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeSectionList(rawSections) {
|
||||
const incoming = Array.isArray(rawSections) ? rawSections : []
|
||||
const sanitized = incoming
|
||||
.map((section, index) => normalizeEntry(section, index))
|
||||
.filter(Boolean)
|
||||
|
||||
if (sanitized.length === 0) {
|
||||
return baseSectionDefinitions.map((section, index) => normalizeEntry(
|
||||
{ ...section, key: `base-${section.id}` },
|
||||
index,
|
||||
section.id
|
||||
))
|
||||
}
|
||||
|
||||
const knownIds = new Set(sanitized.map(section => section.id))
|
||||
const merged = [...sanitized]
|
||||
for (const defaultSection of baseSectionDefinitions) {
|
||||
if (!knownIds.has(defaultSection.id)) {
|
||||
merged.push(normalizeEntry(
|
||||
{ ...defaultSection, key: `base-${defaultSection.id}` },
|
||||
merged.length,
|
||||
defaultSection.id
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
const sections = computed(() => normalizeSectionList(config.value?.homepage?.sections))
|
||||
const personalizedSections = computed(() => {
|
||||
const raw = homepageSettings.value?.sections
|
||||
const list = Array.isArray(raw) ? raw : []
|
||||
return list.map((section, index) => normalizeEntry(section, index)).filter(Boolean)
|
||||
})
|
||||
|
||||
// Filtere nur aktivierte Sections
|
||||
const enabledSections = computed(() => {
|
||||
return sections.value.filter(section => section.enabled !== false)
|
||||
})
|
||||
const isLoggedIn = computed(() => authStatus.value?.isLoggedIn === true)
|
||||
const canCustomizeHome = computed(() => sections.value.length > 0)
|
||||
|
||||
function applyPersonalization(baseSections, settingsSections) {
|
||||
if (!settingsSections.length) return baseSections
|
||||
|
||||
const presentBaseIds = new Set(
|
||||
settingsSections.filter(section => baseSectionIds.has(section.id)).map(section => section.id)
|
||||
)
|
||||
const missingBaseSections = baseSections.filter(section => !presentBaseIds.has(section.id))
|
||||
return [...settingsSections, ...missingBaseSections]
|
||||
}
|
||||
|
||||
const resolvedSections = computed(() => applyPersonalization([...sections.value], personalizedSections.value))
|
||||
const enabledSections = computed(() => resolvedSections.value.filter(section => section.enabled !== false))
|
||||
|
||||
// Mapping von Section-ID zu Komponente
|
||||
const componentMap = {
|
||||
banner: Hero,
|
||||
termine: HomeTermine,
|
||||
spiele: Spielplan,
|
||||
aktuelles: PublicNews,
|
||||
kontakt: HomeActions
|
||||
kontakt: HomeActions,
|
||||
training: HomeTrainingTeaser,
|
||||
links: HomeLinksTeaser,
|
||||
vereinsmeisterschaften: HomeVereinsmeisterschaftenTeaser
|
||||
}
|
||||
|
||||
function getComponentForSection(sectionId) {
|
||||
return componentMap[sectionId] || null
|
||||
}
|
||||
|
||||
function getSectionLabel(section) {
|
||||
if (section.id === 'spielplan_team') {
|
||||
if (!section.config?.teamName) return 'Spielplan-Widget'
|
||||
return `Spielplan: ${section.config.teamName}`
|
||||
}
|
||||
|
||||
const labels = {
|
||||
banner: 'Banner (Willkommen)',
|
||||
termine: 'Kommende Termine',
|
||||
spiele: 'Nächste Spiele',
|
||||
aktuelles: 'Aktuelles',
|
||||
kontakt: 'Kontakt-Boxen',
|
||||
training: 'Training-Teaser',
|
||||
links: 'Links-Teaser',
|
||||
vereinsmeisterschaften: 'Vereinsmeisterschaften-Teaser'
|
||||
}
|
||||
return labels[section.id] || section.id
|
||||
}
|
||||
|
||||
function getTeamsForSeason(seasonSlug) {
|
||||
if (!seasonSlug) return []
|
||||
return teamOptionsBySeason.value[seasonSlug] || []
|
||||
}
|
||||
|
||||
function teamKeyFromConfig(config) {
|
||||
if (!config?.teamName) return ''
|
||||
return `${config.teamName}||${config.teamAgeGroup || ''}`
|
||||
}
|
||||
|
||||
function applyTeamToSectionConfig(section, teamKey) {
|
||||
const season = section.config?.season || ''
|
||||
const teams = getTeamsForSeason(season)
|
||||
const team = teams.find(item => item.key === teamKey)
|
||||
if (!team) return
|
||||
section.config = {
|
||||
...section.config,
|
||||
season,
|
||||
teamName: team.teamName,
|
||||
teamAgeGroup: team.teamAgeGroup || ''
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureTeamOptions(seasonSlug) {
|
||||
if (!seasonSlug || teamOptionsBySeason.value[seasonSlug]) return
|
||||
const result = await $fetch('/api/homepage/spielplan-options', {
|
||||
query: { season: seasonSlug }
|
||||
})
|
||||
teamOptionsBySeason.value = {
|
||||
...teamOptionsBySeason.value,
|
||||
[seasonSlug]: result?.teams || []
|
||||
}
|
||||
if (!spielplanSeasons.value.length && Array.isArray(result?.seasons)) {
|
||||
spielplanSeasons.value = result.seasons
|
||||
}
|
||||
}
|
||||
|
||||
async function loadWidgetOptions() {
|
||||
if (widgetOptionsLoading.value) return
|
||||
widgetOptionsLoading.value = true
|
||||
try {
|
||||
const result = await $fetch('/api/homepage/spielplan-options')
|
||||
spielplanSeasons.value = Array.isArray(result?.seasons) ? result.seasons : []
|
||||
|
||||
const selectedSeason = result?.selectedSeason || spielplanSeasons.value[0]?.slug || ''
|
||||
if (selectedSeason) {
|
||||
teamOptionsBySeason.value = {
|
||||
...teamOptionsBySeason.value,
|
||||
[selectedSeason]: result?.teams || []
|
||||
}
|
||||
}
|
||||
|
||||
if (!newWidgetSeason.value) {
|
||||
newWidgetSeason.value = selectedSeason
|
||||
}
|
||||
if (newWidgetSeason.value) {
|
||||
await ensureTeamOptions(newWidgetSeason.value)
|
||||
const teams = getTeamsForSeason(newWidgetSeason.value)
|
||||
if (teams.length && !newWidgetTeamKey.value) {
|
||||
newWidgetTeamKey.value = teams[0].key
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
widgetOptionsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openEditor() {
|
||||
editorMessage.value = ''
|
||||
editorSections.value = resolvedSections.value.map(section => ({
|
||||
key: section.key,
|
||||
id: section.id,
|
||||
enabled: section.enabled !== false,
|
||||
config: normalizeConfig(section.config)
|
||||
}))
|
||||
|
||||
await loadWidgetOptions()
|
||||
|
||||
for (const section of editorSections.value.filter(item => item.id === 'spielplan_team')) {
|
||||
const fallbackSeason = section.config?.season || newWidgetSeason.value || spielplanSeasons.value[0]?.slug || ''
|
||||
if (!section.config) section.config = {}
|
||||
section.config.season = fallbackSeason
|
||||
await ensureTeamOptions(fallbackSeason)
|
||||
const currentTeamKey = teamKeyFromConfig(section.config)
|
||||
const availableTeams = getTeamsForSeason(fallbackSeason)
|
||||
if (availableTeams.length && !availableTeams.find(item => item.key === currentTeamKey)) {
|
||||
applyTeamToSectionConfig(section, availableTeams[0].key)
|
||||
}
|
||||
}
|
||||
|
||||
editorOpen.value = true
|
||||
}
|
||||
|
||||
function closeEditor() {
|
||||
editorOpen.value = false
|
||||
editorMessage.value = ''
|
||||
}
|
||||
|
||||
function moveEditorSectionUp(index) {
|
||||
if (index <= 0) return
|
||||
const item = editorSections.value[index]
|
||||
editorSections.value.splice(index, 1)
|
||||
editorSections.value.splice(index - 1, 0, item)
|
||||
}
|
||||
|
||||
function moveEditorSectionDown(index) {
|
||||
if (index >= editorSections.value.length - 1) return
|
||||
const item = editorSections.value[index]
|
||||
editorSections.value.splice(index, 1)
|
||||
editorSections.value.splice(index + 1, 0, item)
|
||||
}
|
||||
|
||||
async function onWidgetSeasonChanged(section, seasonSlug) {
|
||||
if (!section.config) section.config = {}
|
||||
section.config.season = seasonSlug
|
||||
await ensureTeamOptions(seasonSlug)
|
||||
const teams = getTeamsForSeason(seasonSlug)
|
||||
const currentTeamKey = teamKeyFromConfig(section.config)
|
||||
if (teams.length && !teams.find(item => item.key === currentTeamKey)) {
|
||||
applyTeamToSectionConfig(section, teams[0].key)
|
||||
}
|
||||
}
|
||||
|
||||
function onWidgetTeamChanged(section, teamKey) {
|
||||
applyTeamToSectionConfig(section, teamKey)
|
||||
}
|
||||
|
||||
const newWidgetTeams = computed(() => getTeamsForSeason(newWidgetSeason.value))
|
||||
const canAddSpielplanWidget = computed(() => !!newWidgetSeason.value && !!newWidgetTeamKey.value)
|
||||
|
||||
async function onNewWidgetSeasonChanged() {
|
||||
await ensureTeamOptions(newWidgetSeason.value)
|
||||
const teams = getTeamsForSeason(newWidgetSeason.value)
|
||||
newWidgetTeamKey.value = teams[0]?.key || ''
|
||||
}
|
||||
|
||||
function addSpielplanWidget() {
|
||||
const teams = getTeamsForSeason(newWidgetSeason.value)
|
||||
const selectedTeam = teams.find(team => team.key === newWidgetTeamKey.value)
|
||||
if (!selectedTeam) return
|
||||
|
||||
editorSections.value.push({
|
||||
key: createEntryKey('spielplan_team'),
|
||||
id: 'spielplan_team',
|
||||
enabled: true,
|
||||
config: {
|
||||
season: newWidgetSeason.value,
|
||||
teamName: selectedTeam.teamName,
|
||||
teamAgeGroup: selectedTeam.teamAgeGroup || ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function saveEditor() {
|
||||
isSavingSettings.value = true
|
||||
editorMessage.value = ''
|
||||
|
||||
try {
|
||||
await $fetch('/api/homepage/settings', {
|
||||
method: 'PUT',
|
||||
body: {
|
||||
sections: editorSections.value.map((section, index) => ({
|
||||
key: section.key || `${section.id}-${index}`,
|
||||
id: section.id,
|
||||
enabled: section.enabled !== false,
|
||||
config: normalizeConfig(section.config)
|
||||
}))
|
||||
}
|
||||
})
|
||||
|
||||
await refreshHomepageSettings()
|
||||
editorMessageType.value = 'success'
|
||||
editorMessage.value = 'Startseiten-Einstellungen gespeichert.'
|
||||
} catch (error) {
|
||||
editorMessageType.value = 'error'
|
||||
editorMessage.value = error?.data?.message || 'Speichern fehlgeschlagen.'
|
||||
} finally {
|
||||
isSavingSettings.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user