From 8043916129ec7f89b0f545fd66b2d7b64bbcc49c Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Sun, 18 Jan 2026 23:40:59 +0100 Subject: [PATCH] Implement CSV fetching utility across components for improved data handling This commit introduces a new utility function, fetchCsvText, to streamline the fetching of CSV data across multiple components. The function includes a cache-busting mechanism and retry logic to enhance reliability when retrieving data from the server. This change improves error handling and ensures consistent data retrieval in the Mannschaften overview, detail, and schedule pages, contributing to a more robust application. --- components/MannschaftenUebersicht.vue | 24 +++++++++++++++++------- components/Navigation.vue | 18 +++++++++++++++--- pages/mannschaften/[slug].vue | 22 ++++++++++++++++++---- pages/mannschaften/spielplaene.vue | 23 ++++++++++++++++++++--- 4 files changed, 70 insertions(+), 17 deletions(-) diff --git a/components/MannschaftenUebersicht.vue b/components/MannschaftenUebersicht.vue index 45f3ea9..9b9f055 100644 --- a/components/MannschaftenUebersicht.vue +++ b/components/MannschaftenUebersicht.vue @@ -107,15 +107,25 @@ import { Users } from 'lucide-vue-next' const mannschaften = ref([]) +async function fetchCsvText(url) { + const attempt = async () => { + const withBuster = `${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}` + const res = await fetch(withBuster, { cache: 'no-store' }) + if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`) + return await res.text() + } + + try { + return await attempt() + } catch (_e) { + await new Promise(resolve => setTimeout(resolve, 150)) + return await attempt() + } +} + const loadMannschaften = async () => { try { - const response = await fetch('/data/mannschaften.csv') - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`) - } - - const csv = await response.text() + const csv = await fetchCsvText('/data/mannschaften.csv') // Vereinfachter CSV-Parser const lines = csv.split('\n').filter(line => line.trim() !== '') diff --git a/components/Navigation.vue b/components/Navigation.vue index 6f9d7c6..cbbf493 100644 --- a/components/Navigation.vue +++ b/components/Navigation.vue @@ -923,10 +923,22 @@ const toggleMobileSubmenu = (menu) => { const loadMannschaften = async () => { try { - const response = await fetch('/data/mannschaften.csv') - if (!response.ok) return + const attempt = async () => { + const url = `/data/mannschaften.csv?_t=${Date.now()}` + const response = await fetch(url, { cache: 'no-store' }) + if (!response.ok) return null + return await response.text() + } - const csv = await response.text() + let csv = null + try { + csv = await attempt() + } catch (_e) { + // 1 Retry: hilft bei kurzen Restarts/Proxy-Resets (Firefox: NS_ERROR_NET_PARTIAL_TRANSFER) + await new Promise(resolve => setTimeout(resolve, 150)) + csv = await attempt() + } + if (!csv) return const lines = csv.split('\n').filter(line => line.trim() !== '') if (lines.length < 2) return diff --git a/pages/mannschaften/[slug].vue b/pages/mannschaften/[slug].vue index eb5d6fe..7b2c113 100644 --- a/pages/mannschaften/[slug].vue +++ b/pages/mannschaften/[slug].vue @@ -140,12 +140,26 @@ import { Users } from 'lucide-vue-next' const route = useRoute() const mannschaft = ref(null) +async function fetchCsvText(url) { + const attempt = async () => { + const withBuster = `${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}` + const res = await fetch(withBuster, { cache: 'no-store' }) + if (!res.ok) return null + return await res.text() + } + + try { + return await attempt() + } catch (_e) { + await new Promise(resolve => setTimeout(resolve, 150)) + return await attempt() + } +} + const loadMannschaften = async () => { try { - const response = await fetch('/data/mannschaften.csv') - if (!response.ok) return - - const csv = await response.text() + const csv = await fetchCsvText('/data/mannschaften.csv') + if (!csv) return const lines = csv.split('\n').filter(line => line.trim() !== '') if (lines.length < 2) return diff --git a/pages/mannschaften/spielplaene.vue b/pages/mannschaften/spielplaene.vue index ef5d1e4..2a9c8b7 100644 --- a/pages/mannschaften/spielplaene.vue +++ b/pages/mannschaften/spielplaene.vue @@ -299,6 +299,23 @@ const selectedWettbewerb = ref('punktrunde') const filteredData = ref([]) const mannschaften = ref([]) +async function fetchCsvText(url) { + const attempt = async () => { + const withBuster = `${url}${url.includes('?') ? '&' : '?'}_t=${Date.now()}` + const res = await fetch(withBuster, { cache: 'no-store' }) + if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`) + return await res.text() + } + + try { + return await attempt() + } catch (_e) { + // 1 Retry: hilft bei kurzen Restarts/Proxy-Resets (Firefox: NS_ERROR_NET_PARTIAL_TRANSFER) + await new Promise(resolve => setTimeout(resolve, 150)) + return await attempt() + } +} + const loadData = async () => { isLoading.value = true error.value = null @@ -307,7 +324,7 @@ const loadData = async () => { // Lade Spielplandaten und Mannschaften parallel const [spielplanResponse, mannschaftenResponse] = await Promise.all([ fetch('/api/spielplan'), - fetch('/data/mannschaften.csv') + fetchCsvText('/data/mannschaften.csv') ]) // Spielplandaten verarbeiten @@ -352,8 +369,8 @@ const loadData = async () => { } // Mannschaften aus CMS laden (manuell eingegebene Mannschaften) - if (mannschaftenResponse.ok) { - const csvText = await mannschaftenResponse.text() + if (mannschaftenResponse) { + const csvText = mannschaftenResponse const lines = csvText.split('\n').filter(line => line.trim() !== '') if (lines.length > 1) {