Switch termine loading from static CSV to dynamic API for instant updates

This commit is contained in:
Torsten Schulz (local)
2025-10-21 16:21:01 +02:00
parent 4af0b2a448
commit c35cdcfcc9
17 changed files with 801 additions and 727 deletions

View File

@@ -1,5 +1,5 @@
{
"date": "2025-10-21T13:58:04.795Z",
"date": "2025-10-21T14:19:03.866Z",
"preset": "node-server",
"framework": {
"name": "nuxt",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"id":"e3471523-9d80-4085-b77f-94b960db296e","timestamp":1761055077384}
{"id":"8ea2eaf9-1082-435b-8f11-acd12e0664d5","timestamp":1761056336789}

View File

@@ -0,0 +1 @@
{"id":"8ea2eaf9-1082-435b-8f11-acd12e0664d5","timestamp":1761056336789,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}

View File

@@ -1 +0,0 @@
{"id":"e3471523-9d80-4085-b77f-94b960db296e","timestamp":1761055077384,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +1,2 @@
"datum","titel","beschreibung","kategorie"
"2025-10-25","Herbstturnier","Offenes Turnier für alle Leistungsklassen","Turnier"
"2025-11-02","Halloween-Special","Spooky Training mit Kostümen und Süßigkeiten","Event"
"2025-11-15","Vereinsmeisterschaft","Das Highlight der Saison - Vereinsmeisterschaft in allen Kategorien","Turnier"
"2025-12-06","Nikolaus-Turnier","Weihnachtliches Turnier mit kleinen Geschenken","Turnier"
"2025-12-20","Weihnachtsfeier","Gemütlicher Jahresabschluss mit Siegerehrung","Event"
"2026-01-10","Neujahrstraining","Erstes Training im neuen Jahr","Event"
"2026-02-14","Valentinstag-Special","Paar-Turnier für Verliebte","Turnier"
"2026-03-15","Frühlingsturnier","Saisoneröffnung mit großem Turnier","Turnier"
"2025-12-18","Weihnachtsfeier 2025 im Gasthaus Zum Einhorn in Frankfurt - Bonames","","Veranstaltung"
1 datum titel beschreibung kategorie
2 2025-10-25 2025-12-18 Herbstturnier Weihnachtsfeier 2025 im Gasthaus Zum Einhorn in Frankfurt - Bonames Offenes Turnier für alle Leistungsklassen Turnier Veranstaltung
2025-11-02 Halloween-Special Spooky Training mit Kostümen und Süßigkeiten Event
2025-11-15 Vereinsmeisterschaft Das Highlight der Saison - Vereinsmeisterschaft in allen Kategorien Turnier
2025-12-06 Nikolaus-Turnier Weihnachtliches Turnier mit kleinen Geschenken Turnier
2025-12-20 Weihnachtsfeier Gemütlicher Jahresabschluss mit Siegerehrung Event
2026-01-10 Neujahrstraining Erstes Training im neuen Jahr Event
2026-02-14 Valentinstag-Special Paar-Turnier für Verliebte Turnier
2026-03-15 Frühlingsturnier Saisoneröffnung mit großem Turnier Turnier

View File

@@ -340,7 +340,7 @@ const client_manifest = {
"module": true,
"prefetch": true,
"preload": true,
"file": "BCCaC8ND.js",
"file": "B5GgiMm1.js",
"name": "entry",
"src": "node_modules/nuxt/dist/app/entry.js",
"isEntry": true,
@@ -350,14 +350,14 @@ const client_manifest = {
"node_modules/nuxt/dist/app/components/error-500.vue"
],
"css": [
"entry.BiMUV0e6.css"
"entry.BFpxHKmh.css"
],
"assets": [
"Harheimer TC.CKfYAfp1.svg"
]
},
"entry.BiMUV0e6.css": {
"file": "entry.BiMUV0e6.css",
"entry.BFpxHKmh.css": {
"file": "entry.BFpxHKmh.css",
"resourceType": "style",
"prefetch": true,
"preload": true

View File

@@ -1,10 +1,10 @@
import process from 'node:process';globalThis._importMeta_=globalThis._importMeta_||{url:"file:///_entry.js",env:process.env};import { defineComponent, shallowRef, h, resolveComponent, hasInjectionContext, inject, computed, getCurrentInstance, createElementBlock, provide, cloneVNode, ref, Suspense, Fragment, createApp, shallowReactive, mergeProps, unref, withCtx, createTextVNode, toRef, onErrorCaptured, onServerPrefetch, createVNode, resolveDynamicComponent, reactive, effectScope, isReadonly, isRef, isShallow, isReactive, toRaw, defineAsyncComponent, getCurrentScope, toDisplayString, useSSRContext } from 'vue';
import process from 'node:process';globalThis._importMeta_=globalThis._importMeta_||{url:"file:///_entry.js",env:process.env};import { defineComponent, shallowRef, h, resolveComponent, hasInjectionContext, inject, computed, getCurrentInstance, createElementBlock, provide, cloneVNode, ref, Suspense, Fragment, createApp, shallowReactive, mergeProps, withCtx, createVNode, createTextVNode, unref, toDisplayString, toRef, onErrorCaptured, onServerPrefetch, resolveDynamicComponent, reactive, effectScope, isReadonly, isRef, isShallow, isReactive, toRaw, defineAsyncComponent, getCurrentScope, useSSRContext } from 'vue';
import { p as parseQuery, c as createError$1, o as hasProtocol, q as isScriptProtocol, m as joinURL, w as withQuery, t as sanitizeStatusCode, v as withTrailingSlash, x as withoutTrailingSlash, y as getContext, $ as $fetch$1, z as createHooks, A as executeAsync, B as toRouteMatcher, C as createRouter$1, D as defu } from '../nitro/nitro.mjs';
import { b as baseURL } from '../routes/renderer.mjs';
import { defineStore, createPinia, setActivePinia, shouldHydrate } from 'pinia';
import { RouterView, useRouter as useRouter$1, createMemoryHistory, createRouter, START_LOCATION, useRoute as useRoute$1 } from 'vue-router';
import { ssrRenderAttrs, ssrInterpolate, ssrRenderComponent, ssrRenderSuspense, ssrRenderVNode, ssrRenderAttr, ssrRenderStyle, ssrRenderClass, ssrRenderList } from 'vue/server-renderer';
import { User, ChevronUp, X, Menu, ChevronDown } from 'lucide-vue-next';
import { RouterView, useRoute as useRoute$1, useRouter as useRouter$1, createMemoryHistory, createRouter, START_LOCATION } from 'vue-router';
import { ssrRenderAttrs, ssrRenderComponent, ssrRenderAttr, ssrRenderStyle, ssrRenderClass, ssrRenderList, ssrInterpolate, ssrRenderSuspense, ssrRenderVNode } from 'vue/server-renderer';
import { ChevronDown, X, Menu, User, ChevronUp } from 'lucide-vue-next';
import 'node:http';
import 'node:https';
import 'node:events';
@@ -1414,6 +1414,7 @@ const _sfc_main$4 = {
const mobileSubmenu = ref(null);
const mannschaften = ref([]);
const hasGalleryImages = ref(false);
const showCmsDropdown = ref(false);
const isLoggedIn = computed(() => authStore.isLoggedIn);
const isAdmin = computed(() => authStore.isAdmin);
const currentSubmenu = computed(() => {
@@ -1855,40 +1856,99 @@ const _sfc_main$4 = {
_: 1
}, _parent));
if (isAdmin.value) {
_push(`<!--[--><div class="h-3 w-px bg-primary-700"></div>`);
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/cms",
class: "px-2.5 py-1 text-xs text-yellow-300 hover:text-white hover:bg-primary-700/50 rounded transition-all",
"active-class": "text-white bg-primary-600"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` CMS `);
} else {
return [
createTextVNode(" CMS ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/cms/benutzer",
class: "px-2.5 py-1 text-xs text-yellow-300 hover:text-white hover:bg-primary-700/50 rounded transition-all",
"active-class": "text-white bg-primary-600"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Benutzerverwaltung `);
} else {
return [
createTextVNode(" Benutzerverwaltung ")
];
}
}),
_: 1
}, _parent));
_push(`<!--]-->`);
_push(`<!--[--><div class="h-3 w-px bg-primary-700"></div><div class="relative inline-block"><button class="${ssrRenderClass([unref(route).path.startsWith("/cms") ? "text-white bg-primary-600" : "", "px-2.5 py-1 text-xs text-yellow-300 hover:text-white hover:bg-primary-700/50 rounded transition-all flex items-center"])}"> CMS `);
_push(ssrRenderComponent(unref(ChevronDown), {
size: 12,
class: ["ml-1", ["transition-transform", showCmsDropdown.value ? "rotate-180" : ""]]
}, null, _parent));
_push(`</button>`);
if (showCmsDropdown.value) {
_push(`<div class="absolute left-0 top-full mt-1 w-48 bg-gray-800 border border-gray-700 rounded-lg shadow-xl overflow-hidden z-50">`);
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/cms",
onClick: ($event) => showCmsDropdown.value = false,
class: "block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Übersicht `);
} else {
return [
createTextVNode(" Übersicht ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/mitgliederbereich/news",
onClick: ($event) => showCmsDropdown.value = false,
class: "block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Interne News `);
} else {
return [
createTextVNode(" Interne News ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/cms/termine",
onClick: ($event) => showCmsDropdown.value = false,
class: "block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Termine `);
} else {
return [
createTextVNode(" Termine ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/mitgliederbereich/mitglieder",
onClick: ($event) => showCmsDropdown.value = false,
class: "block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Mitglieder `);
} else {
return [
createTextVNode(" Mitglieder ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/cms/benutzer",
onClick: ($event) => showCmsDropdown.value = false,
class: "block px-4 py-2 text-sm text-gray-300 hover:bg-primary-600 hover:text-white transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Benutzerverwaltung `);
} else {
return [
createTextVNode(" Benutzerverwaltung ")
];
}
}),
_: 1
}, _parent));
_push(`</div>`);
} else {
_push(`<!---->`);
}
_push(`</div><!--]-->`);
} else {
_push(`<!---->`);
}
@@ -2310,14 +2370,62 @@ const _sfc_main$4 = {
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/cms",
onClick: ($event) => isMobileMenuOpen.value = false,
class: "block px-4 py-2 text-sm font-semibold text-yellow-300 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` CMS Übersicht `);
} else {
return [
createTextVNode(" CMS Übersicht ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/mitgliederbereich/news",
onClick: ($event) => isMobileMenuOpen.value = false,
class: "block px-4 py-2 text-sm text-yellow-300 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` CMS `);
_push2(` Interne News `);
} else {
return [
createTextVNode(" CMS ")
createTextVNode(" Interne News ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/cms/termine",
onClick: ($event) => isMobileMenuOpen.value = false,
class: "block px-4 py-2 text-sm text-yellow-300 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Termine `);
} else {
return [
createTextVNode(" Termine ")
];
}
}),
_: 1
}, _parent));
_push(ssrRenderComponent(_component_NuxtLink, {
to: "/mitgliederbereich/mitglieder",
onClick: ($event) => isMobileMenuOpen.value = false,
class: "block px-4 py-2 text-sm text-yellow-300 hover:text-white hover:bg-primary-700/50 rounded-lg transition-colors"
}, {
default: withCtx((_, _push2, _parent2, _scopeId) => {
if (_push2) {
_push2(` Mitglieder `);
} else {
return [
createTextVNode(" Mitglieder ")
];
}
}),
@@ -2381,6 +2489,7 @@ _sfc_main$4.setup = (props, ctx) => {
(ssrContext.modules || (ssrContext.modules = /* @__PURE__ */ new Set())).add("components/Navigation.vue");
return _sfc_setup$4 ? _sfc_setup$4(props, ctx) : void 0;
};
const Navigation = Object.assign(_sfc_main$4, { __name: "Navigation" });
const _sfc_main$3 = {
__name: "Footer",
__ssrInlineRender: true,
@@ -2511,7 +2620,7 @@ const _sfc_main$2 = {
return (_ctx, _push, _parent, _attrs) => {
const _component_NuxtPage = __nuxt_component_0;
_push(`<div${ssrRenderAttrs(mergeProps({ class: "h-screen flex flex-col overflow-hidden" }, _attrs))}>`);
_push(ssrRenderComponent(_sfc_main$4, null, null, _parent));
_push(ssrRenderComponent(Navigation, null, null, _parent));
_push(`<main class="flex-1 overflow-y-auto pt-20">`);
_push(ssrRenderComponent(_component_NuxtPage, null, null, _parent));
_push(`</main>`);

File diff suppressed because it is too large Load Diff

View File

@@ -272,7 +272,7 @@ async function renderInlineStyles(usedModules) {
const renderSSRHeadOptions = {"omitLineBreaks":true};
const entryFileName = "BCCaC8ND.js";
const entryFileName = "B5GgiMm1.js";
globalThis.__buildAssetsURL = buildAssetsURL;
globalThis.__publicAssetsURL = publicAssetsURL;

View File

@@ -87,50 +87,11 @@ const formatMonth = (dateString) => {
const loadTermine = async () => {
try {
console.log('Lade Termine...')
const response = await fetch('/data/termine.csv')
console.log('Response:', response)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const csv = await response.text()
console.log('CSV Text:', csv)
// Vereinfachter CSV-Parser
const lines = csv.split('\n').filter(line => line.trim() !== '')
console.log('CSV Lines:', lines)
if (lines.length < 2) {
console.log('Keine Datenzeilen gefunden')
return
}
termine.value = lines.slice(1).map((line, index) => {
// Entferne Anführungszeichen und teile bei Kommas
const cleanLine = line.replace(/"/g, '')
const values = cleanLine.split(',')
if (values.length < 4) {
console.log(`Zeile ${index + 2} hat zu wenige Werte:`, values)
return null
}
const termin = {
datum: values[0].trim(),
titel: values[1].trim(),
beschreibung: values[2].trim(),
kategorie: values[3].trim()
}
console.log(`Termin ${index + 1}:`, termin)
return termin
}).filter(termin => termin !== null)
console.log('Alle geparsten Termine:', termine.value)
const response = await $fetch('/api/termine')
termine.value = response.termine || []
} catch (error) {
console.error('Fehler beim Laden der Termine:', error)
termine.value = []
}
}

View File

@@ -101,51 +101,11 @@ const formatFullDate = (dateString) => {
const loadTermine = async () => {
try {
const response = await fetch('/data/termine.csv')
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const csv = await response.text()
const lines = csv.split('\n').filter(line => line.trim() !== '')
if (lines.length < 2) {
return
}
termine.value = lines.slice(1).map((line, index) => {
// Besserer CSV-Parser: Respektiert Anführungszeichen
const values = []
let current = ''
let inQuotes = false
for (let i = 0; i < line.length; i++) {
const char = line[i]
if (char === '"') {
inQuotes = !inQuotes
} else if (char === ',' && !inQuotes) {
values.push(current.trim())
current = ''
} else {
current += char
}
}
values.push(current.trim())
if (values.length < 4) {
return null
}
return {
datum: values[0].trim(),
titel: values[1].trim(),
beschreibung: values[2].trim(),
kategorie: values[3].trim()
}
}).filter(termin => termin !== null)
const response = await $fetch('/api/termine')
termine.value = response.termine || []
} catch (error) {
console.error('Fehler beim Laden der Termine:', error)
termine.value = []
}
}

View File

@@ -1,11 +1,2 @@
"datum","titel","beschreibung","kategorie"
"2025-10-25","Herbstturnier","Offenes Turnier für alle Leistungsklassen","Turnier"
"2025-11-02","Halloween-Special","Spooky Training mit Kostümen und Süßigkeiten","Event"
"2025-11-15","Vereinsmeisterschaft","Das Highlight der Saison - Vereinsmeisterschaft in allen Kategorien","Turnier"
"2025-12-06","Nikolaus-Turnier","Weihnachtliches Turnier mit kleinen Geschenken","Turnier"
"2025-12-20","Weihnachtsfeier","Gemütlicher Jahresabschluss mit Siegerehrung","Event"
"2026-01-10","Neujahrstraining","Erstes Training im neuen Jahr","Event"
"2026-02-14","Valentinstag-Special","Paar-Turnier für Verliebte","Turnier"
"2026-03-15","Frühlingsturnier","Saisoneröffnung mit großem Turnier","Turnier"
"2025-12-18","Weihnachtsfeier 2025 im Gasthaus Zum Einhorn in Frankfurt - Bonames","","Veranstaltung"
1 datum titel beschreibung kategorie
2 2025-10-25 2025-12-18 Herbstturnier Weihnachtsfeier 2025 im Gasthaus Zum Einhorn in Frankfurt - Bonames Offenes Turnier für alle Leistungsklassen Turnier Veranstaltung
2025-11-02 Halloween-Special Spooky Training mit Kostümen und Süßigkeiten Event
2025-11-15 Vereinsmeisterschaft Das Highlight der Saison - Vereinsmeisterschaft in allen Kategorien Turnier
2025-12-06 Nikolaus-Turnier Weihnachtliches Turnier mit kleinen Geschenken Turnier
2025-12-20 Weihnachtsfeier Gemütlicher Jahresabschluss mit Siegerehrung Event
2026-01-10 Neujahrstraining Erstes Training im neuen Jahr Event
2026-02-14 Valentinstag-Special Paar-Turnier für Verliebte Turnier
2026-03-15 Frühlingsturnier Saisoneröffnung mit großem Turnier Turnier

62
server/api/termine.get.js Normal file
View File

@@ -0,0 +1,62 @@
import { promises as fs } from 'fs'
import path from 'path'
export default defineEventHandler(async (event) => {
try {
const cwd = process.cwd()
// In production (.output/server), working dir is .output
let csvPath
if (cwd.endsWith('.output')) {
csvPath = path.join(cwd, '../public/data/termine.csv')
} else {
csvPath = path.join(cwd, 'public/data/termine.csv')
}
const csv = await fs.readFile(csvPath, 'utf-8')
const lines = csv.split('\n').filter(line => line.trim() !== '')
if (lines.length < 2) {
return { success: true, termine: [] }
}
const termine = []
for (let i = 1; i < lines.length; i++) {
const values = []
let current = ''
let inQuotes = false
for (let j = 0; j < lines[i].length; j++) {
const char = lines[i][j]
if (char === '"') {
inQuotes = !inQuotes
} else if (char === ',' && !inQuotes) {
values.push(current.trim())
current = ''
} else {
current += char
}
}
values.push(current.trim())
if (values.length >= 4) {
termine.push({
datum: values[0],
titel: values[1],
beschreibung: values[2],
kategorie: values[3]
})
}
}
return {
success: true,
termine
}
} catch (error) {
console.error('Fehler beim Laden der Termine:', error)
return { success: true, termine: [] }
}
})