Update Hero component to dynamically display years since founding; enhance TermineVorschau component with improved date and time formatting, and add Uhrzeit column in the CMS for better event management. Refactor API to handle new fields and improve data handling in CSV exports.
This commit is contained in:
@@ -20,7 +20,7 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="text-xl sm:text-2xl text-gray-700 mb-8 max-w-3xl mx-auto animate-fade-in-delay-1">
|
<p class="text-xl sm:text-2xl text-gray-700 mb-8 max-w-3xl mx-auto animate-fade-in-delay-1">
|
||||||
Tradition trifft Moderne - Ihr Tischtennisverein in Frankfurt-Harheim seit über 45 Jahren
|
Tradition trifft Moderne - Ihr Tischtennisverein in Frankfurt-Harheim seit {{ yearsSinceFounding }} Jahren
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -29,6 +29,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
const foundingYear = 1954
|
||||||
|
const yearsSinceFounding = new Date().getFullYear() - foundingYear
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -8,9 +8,16 @@
|
|||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<div class="w-10 h-10 bg-primary-600 rounded-lg flex flex-col items-center justify-center text-white text-xs font-bold">
|
<div class="w-16 h-16 bg-primary-600 rounded-lg flex flex-col items-center justify-center text-white text-[10px] font-bold leading-tight py-1.5 px-1.5">
|
||||||
<span>{{ formatDay(termin.datum) }}</span>
|
<span class="text-lg leading-none">{{ formatDay(termin.datum) }}</span>
|
||||||
<span>{{ formatMonth(termin.datum) }}</span>
|
<span class="leading-none mt-0.5">{{ formatMonth(termin.datum) }}</span>
|
||||||
|
<span class="text-[10px] leading-none opacity-95 mt-0.5">{{ formatYear(termin.datum) }}</span>
|
||||||
|
<span
|
||||||
|
v-if="termin.uhrzeit"
|
||||||
|
class="text-[10px] leading-none mt-1 px-2 py-0.5 rounded-md bg-white/40 ring-1 ring-white/50 backdrop-blur-[1.5px] whitespace-nowrap"
|
||||||
|
>
|
||||||
|
{{ formatOnlyTime(termin.uhrzeit) }} Uhr
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold text-gray-900">{{ termin.titel }}</h3>
|
<h3 class="font-semibold text-gray-900">{{ termin.titel }}</h3>
|
||||||
@@ -46,12 +53,23 @@ const naechsteTermine = computed(() => {
|
|||||||
|
|
||||||
const kommende = termine.value
|
const kommende = termine.value
|
||||||
.filter(t => {
|
.filter(t => {
|
||||||
const terminDatum = new Date(t.datum)
|
// Datum + optionale Uhrzeit kombinieren
|
||||||
|
let terminDatum
|
||||||
|
if (t.uhrzeit && /^\d{2}:\d{2}$/.test(t.uhrzeit)) {
|
||||||
|
const iso = `${t.datum}T${t.uhrzeit}:00`
|
||||||
|
terminDatum = new Date(iso)
|
||||||
|
} else {
|
||||||
|
terminDatum = new Date(t.datum)
|
||||||
|
}
|
||||||
const istKommend = terminDatum >= heute
|
const istKommend = terminDatum >= heute
|
||||||
console.log(`Termin ${t.titel} (${t.datum}): ${istKommend ? 'KOMMEND' : 'VERSTRICHEN'}`)
|
console.log(`Termin ${t.titel} (${t.datum}): ${istKommend ? 'KOMMEND' : 'VERSTRICHEN'}`)
|
||||||
return istKommend
|
return istKommend
|
||||||
})
|
})
|
||||||
.sort((a, b) => new Date(a.datum) - new Date(b.datum))
|
.sort((a, b) => {
|
||||||
|
const da = new Date(`${a.datum}${a.uhrzeit ? 'T' + a.uhrzeit + ':00' : ''}`)
|
||||||
|
const db = new Date(`${b.datum}${b.uhrzeit ? 'T' + b.uhrzeit + ':00' : ''}`)
|
||||||
|
return da - db
|
||||||
|
})
|
||||||
|
|
||||||
console.log('Kommende Termine:', kommende)
|
console.log('Kommende Termine:', kommende)
|
||||||
return kommende
|
return kommende
|
||||||
@@ -68,6 +86,16 @@ const formatMonth = (dateString) => {
|
|||||||
return monate[date.getMonth()]
|
return monate[date.getMonth()]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatOnlyTime = (uhrzeit) => {
|
||||||
|
if (!uhrzeit) return ''
|
||||||
|
return /^\d{2}:\d{2}$/.test(uhrzeit) ? uhrzeit : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatYear = (dateString) => {
|
||||||
|
const date = new Date(dateString)
|
||||||
|
return date.getFullYear()
|
||||||
|
}
|
||||||
|
|
||||||
const loadTermine = async () => {
|
const loadTermine = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await $fetch('/api/termine')
|
const response = await $fetch('/api/termine')
|
||||||
|
|||||||
@@ -536,23 +536,43 @@ const saveConfig = async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
successMessage.value = 'Konfiguration erfolgreich gespeichert!'
|
successMessage.value = 'Konfiguration erfolgreich gespeichert!'
|
||||||
|
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Konfiguration erfolgreich gespeichert!') } catch (e) {}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
successMessage.value = ''
|
successMessage.value = ''
|
||||||
}, 3000)
|
}, 3000)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error.data?.message || 'Fehler beim Speichern der Konfiguration.'
|
errorMessage.value = error.data?.message || 'Fehler beim Speichern der Konfiguration.'
|
||||||
|
try { window.showErrorModal && window.showErrorModal('Fehler', errorMessage.value) } catch (e) {}
|
||||||
} finally {
|
} finally {
|
||||||
isSaving.value = false
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addTrainingTime = () => {
|
const addTrainingTime = () => {
|
||||||
|
// Finde die höchste vorhandene Gruppennummer
|
||||||
|
let maxGruppeNummer = 0
|
||||||
|
config.value.training.zeiten.forEach(zeit => {
|
||||||
|
if (zeit.gruppe) {
|
||||||
|
// Prüfe, ob das gruppe-Feld eine Nummer enthält (z.B. "Gruppe 1", "1", "Gruppe 3")
|
||||||
|
const match = zeit.gruppe.match(/(\d+)/)
|
||||||
|
if (match) {
|
||||||
|
const nummer = parseInt(match[1], 10)
|
||||||
|
if (nummer > maxGruppeNummer) {
|
||||||
|
maxGruppeNummer = nummer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Setze die nächste Gruppennummer
|
||||||
|
const naechsteGruppeNummer = maxGruppeNummer + 1
|
||||||
|
|
||||||
config.value.training.zeiten.push({
|
config.value.training.zeiten.push({
|
||||||
id: Date.now().toString(),
|
id: Date.now().toString(),
|
||||||
tag: 'Montag',
|
tag: 'Montag',
|
||||||
von: '19:00',
|
von: '19:00',
|
||||||
bis: '22:00',
|
bis: '22:00',
|
||||||
gruppe: ''
|
gruppe: `Gruppe ${naechsteGruppeNummer}`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,12 @@ async function save() {
|
|||||||
const html = editor.value?.innerHTML || ''
|
const html = editor.value?.innerHTML || ''
|
||||||
const current = await $fetch('/api/config')
|
const current = await $fetch('/api/config')
|
||||||
const updated = { ...current, seiten: { ...(current.seiten || {}), geschichte: html } }
|
const updated = { ...current, seiten: { ...(current.seiten || {}), geschichte: html } }
|
||||||
await $fetch('/api/config', { method: 'PUT', body: updated })
|
try {
|
||||||
|
await $fetch('/api/config', { method: 'PUT', body: updated })
|
||||||
|
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Inhalt erfolgreich gespeichert!') } catch (e) {}
|
||||||
|
} catch (error) {
|
||||||
|
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (e) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function format(cmd) {
|
function format(cmd) {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
<thead class="bg-gray-50">
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Datum</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Datum</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Uhrzeit</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Titel</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Titel</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Beschreibung</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Beschreibung</th>
|
||||||
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kategorie</th>
|
<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Kategorie</th>
|
||||||
@@ -36,10 +37,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="bg-white divide-y divide-gray-200">
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
<tr v-for="termin in termine" :key="termin.id" class="hover:bg-gray-50">
|
<tr v-for="termin in termine" :key="`${termin.datum}-${termin.uhrzeit || ''}-${termin.titel}`" class="hover:bg-gray-50">
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
||||||
{{ formatDate(termin.datum) }}
|
{{ formatDate(termin.datum) }}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
|
||||||
|
{{ termin.uhrzeit || '-' }}
|
||||||
|
</td>
|
||||||
<td class="px-4 py-3 text-sm font-medium text-gray-900">
|
<td class="px-4 py-3 text-sm font-medium text-gray-900">
|
||||||
{{ termin.titel }}
|
{{ termin.titel }}
|
||||||
</td>
|
</td>
|
||||||
@@ -60,7 +64,14 @@
|
|||||||
{{ termin.kategorie }}
|
{{ termin.kategorie }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
|
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium space-x-3">
|
||||||
|
<button
|
||||||
|
@click="openEditModal(termin)"
|
||||||
|
class="text-gray-600 hover:text-gray-900"
|
||||||
|
title="Bearbeiten"
|
||||||
|
>
|
||||||
|
<Pencil :size="18" />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="confirmDelete(termin)"
|
@click="confirmDelete(termin)"
|
||||||
class="text-red-600 hover:text-red-900"
|
class="text-red-600 hover:text-red-900"
|
||||||
@@ -87,11 +98,11 @@
|
|||||||
>
|
>
|
||||||
<div class="bg-white rounded-xl shadow-2xl max-w-2xl w-full p-8">
|
<div class="bg-white rounded-xl shadow-2xl max-w-2xl w-full p-8">
|
||||||
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
<h2 class="text-2xl font-display font-bold text-gray-900 mb-6">
|
||||||
Termin hinzufügen
|
{{ isEditing ? 'Termin bearbeiten' : 'Termin hinzufügen' }}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<form @submit.prevent="saveTermin" class="space-y-4">
|
<form @submit.prevent="saveTermin" class="space-y-4">
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2">Datum *</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">Datum *</label>
|
||||||
<input
|
<input
|
||||||
@@ -102,6 +113,15 @@
|
|||||||
:disabled="isSaving"
|
:disabled="isSaving"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">Uhrzeit</label>
|
||||||
|
<input
|
||||||
|
v-model="formData.uhrzeit"
|
||||||
|
type="time"
|
||||||
|
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500"
|
||||||
|
:disabled="isSaving"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-2">Kategorie *</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">Kategorie *</label>
|
||||||
@@ -173,7 +193,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { Calendar, Plus, Trash2, Loader2, AlertCircle } from 'lucide-vue-next'
|
import { Calendar, Plus, Trash2, Loader2, AlertCircle, Pencil } from 'lucide-vue-next'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
@@ -182,6 +202,8 @@ const isSaving = ref(false)
|
|||||||
const termine = ref([])
|
const termine = ref([])
|
||||||
const showModal = ref(false)
|
const showModal = ref(false)
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const originalTermin = ref(null)
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
datum: '',
|
datum: '',
|
||||||
@@ -207,15 +229,20 @@ const openAddModal = () => {
|
|||||||
datum: '',
|
datum: '',
|
||||||
titel: '',
|
titel: '',
|
||||||
beschreibung: '',
|
beschreibung: '',
|
||||||
kategorie: 'Sonstiges'
|
kategorie: 'Sonstiges',
|
||||||
|
uhrzeit: ''
|
||||||
}
|
}
|
||||||
showModal.value = true
|
showModal.value = true
|
||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
isEditing.value = false
|
||||||
|
originalTermin.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
showModal.value = false
|
showModal.value = false
|
||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
isEditing.value = false
|
||||||
|
originalTermin.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveTermin = async () => {
|
const saveTermin = async () => {
|
||||||
@@ -223,6 +250,17 @@ const saveTermin = async () => {
|
|||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isEditing.value && originalTermin.value) {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
datum: originalTermin.value.datum,
|
||||||
|
uhrzeit: originalTermin.value.uhrzeit || '',
|
||||||
|
titel: originalTermin.value.titel,
|
||||||
|
beschreibung: originalTermin.value.beschreibung || '',
|
||||||
|
kategorie: originalTermin.value.kategorie || 'Sonstiges'
|
||||||
|
})
|
||||||
|
await $fetch(`/api/termine-manage?${params.toString()}`, { method: 'DELETE' })
|
||||||
|
}
|
||||||
|
|
||||||
await $fetch('/api/termine-manage', {
|
await $fetch('/api/termine-manage', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData.value
|
body: formData.value
|
||||||
@@ -237,11 +275,26 @@ const saveTermin = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openEditModal = (termin) => {
|
||||||
|
formData.value = {
|
||||||
|
datum: termin.datum || '',
|
||||||
|
uhrzeit: termin.uhrzeit || '',
|
||||||
|
titel: termin.titel || '',
|
||||||
|
beschreibung: termin.beschreibung || '',
|
||||||
|
kategorie: termin.kategorie || 'Sonstiges'
|
||||||
|
}
|
||||||
|
originalTermin.value = { ...termin }
|
||||||
|
isEditing.value = true
|
||||||
|
showModal.value = true
|
||||||
|
errorMessage.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
const confirmDelete = async (termin) => {
|
const confirmDelete = async (termin) => {
|
||||||
window.showConfirmModal('Termin löschen', `Möchten Sie den Termin "${termin.titel}" wirklich löschen?`, async () => {
|
window.showConfirmModal('Termin löschen', `Möchten Sie den Termin "${termin.titel}" wirklich löschen?`, async () => {
|
||||||
try {
|
try {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
datum: termin.datum,
|
datum: termin.datum,
|
||||||
|
uhrzeit: termin.uhrzeit || '',
|
||||||
titel: termin.titel,
|
titel: termin.titel,
|
||||||
beschreibung: termin.beschreibung || '',
|
beschreibung: termin.beschreibung || '',
|
||||||
kategorie: termin.kategorie || 'Sonstiges'
|
kategorie: termin.kategorie || 'Sonstiges'
|
||||||
|
|||||||
@@ -105,7 +105,12 @@ async function save() {
|
|||||||
const html = editor.value?.innerHTML || ''
|
const html = editor.value?.innerHTML || ''
|
||||||
const current = await $fetch('/api/config')
|
const current = await $fetch('/api/config')
|
||||||
const updated = { ...current, seiten: { ...(current.seiten || {}), ttRegeln: html } }
|
const updated = { ...current, seiten: { ...(current.seiten || {}), ttRegeln: html } }
|
||||||
await $fetch('/api/config', { method: 'PUT', body: updated })
|
try {
|
||||||
|
await $fetch('/api/config', { method: 'PUT', body: updated })
|
||||||
|
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Regeln erfolgreich gespeichert!') } catch (e) {}
|
||||||
|
} catch (error) {
|
||||||
|
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (e) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function format(cmd) {
|
function format(cmd) {
|
||||||
|
|||||||
@@ -67,7 +67,12 @@ async function save() {
|
|||||||
const html = editor.value?.innerHTML || ''
|
const html = editor.value?.innerHTML || ''
|
||||||
const current = await $fetch('/api/config')
|
const current = await $fetch('/api/config')
|
||||||
const updated = { ...current, seiten: { ...(current.seiten || {}), ueberUns: html } }
|
const updated = { ...current, seiten: { ...(current.seiten || {}), ueberUns: html } }
|
||||||
await $fetch('/api/config', { method: 'PUT', body: updated })
|
try {
|
||||||
|
await $fetch('/api/config', { method: 'PUT', body: updated })
|
||||||
|
try { window.showSuccessModal && window.showSuccessModal('Erfolg', 'Inhalt erfolgreich gespeichert!') } catch (e) {}
|
||||||
|
} catch (error) {
|
||||||
|
try { window.showErrorModal && window.showErrorModal('Fehler', error?.data?.message || 'Speichern fehlgeschlagen') } catch (e) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function format(cmd) {
|
function format(cmd) {
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
"datum","titel","beschreibung","kategorie"
|
"datum","uhrzeit","titel","beschreibung","kategorie"
|
||||||
"2025-12-18","Weihnachtsfeier 2025 im Gasthaus Zum Einhorn in Frankfurt - Bonames","","Veranstaltung"
|
"2025-12-18","19:00","Weihnachtsfeier 2025 im Gasthaus Zum Einhorn in Frankfurt - Bonames","","Veranstaltung"
|
||||||
|
|||||||
|
File diff suppressed because it is too large
Load Diff
@@ -32,7 +32,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const query = getQuery(event)
|
const query = getQuery(event)
|
||||||
const { datum, titel, beschreibung, kategorie } = query
|
const { datum, uhrzeit, titel, beschreibung, kategorie } = query
|
||||||
|
|
||||||
if (!datum || !titel) {
|
if (!datum || !titel) {
|
||||||
throw createError({
|
throw createError({
|
||||||
@@ -43,6 +43,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
await deleteTermin({
|
await deleteTermin({
|
||||||
datum,
|
datum,
|
||||||
|
uhrzeit: uhrzeit || '',
|
||||||
titel,
|
titel,
|
||||||
beschreibung: beschreibung || '',
|
beschreibung: beschreibung || '',
|
||||||
kategorie: kategorie || 'Sonstiges'
|
kategorie: kategorie || 'Sonstiges'
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const body = await readBody(event)
|
const body = await readBody(event)
|
||||||
const { datum, titel, beschreibung, kategorie } = body
|
const { datum, uhrzeit, titel, beschreibung, kategorie } = body
|
||||||
|
|
||||||
if (!datum || !titel) {
|
if (!datum || !titel) {
|
||||||
throw createError({
|
throw createError({
|
||||||
@@ -43,6 +43,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
|
|
||||||
await saveTermin({
|
await saveTermin({
|
||||||
datum,
|
datum,
|
||||||
|
uhrzeit: uhrzeit || '',
|
||||||
titel,
|
titel,
|
||||||
beschreibung: beschreibung || '',
|
beschreibung: beschreibung || '',
|
||||||
kategorie: kategorie || 'Sonstiges'
|
kategorie: kategorie || 'Sonstiges'
|
||||||
|
|||||||
@@ -43,9 +43,10 @@ export default defineEventHandler(async (event) => {
|
|||||||
if (values.length >= 4) {
|
if (values.length >= 4) {
|
||||||
termine.push({
|
termine.push({
|
||||||
datum: values[0],
|
datum: values[0],
|
||||||
titel: values[1],
|
uhrzeit: values[1] || '',
|
||||||
beschreibung: values[2],
|
titel: values[2] || '',
|
||||||
kategorie: values[3]
|
beschreibung: values[3] || '',
|
||||||
|
kategorie: values[4] || 'Sonstiges'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,7 +98,7 @@
|
|||||||
"strasse": "Reginastr. 46",
|
"strasse": "Reginastr. 46",
|
||||||
"plz": "60437",
|
"plz": "60437",
|
||||||
"ort": "Frankfurt",
|
"ort": "Frankfurt",
|
||||||
"telefon": "069 509637",
|
"telefon": "06101 122",
|
||||||
"email": "j.dichmann@gmx.de"
|
"email": "j.dichmann@gmx.de"
|
||||||
},
|
},
|
||||||
"stellvertreter": {
|
"stellvertreter": {
|
||||||
|
|||||||
@@ -110,5 +110,12 @@
|
|||||||
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJlbWFpbCI6ImFkbWluQGhhcmhlaW1lcnRjLmRlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzYxMjI1NDI3LCJleHAiOjE3NjE4MzAyMjd9.MANlBkbOU95y-6yd51m0-hoa941A0uHutVwzl481k9I",
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJlbWFpbCI6ImFkbWluQGhhcmhlaW1lcnRjLmRlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzYxMjI1NDI3LCJleHAiOjE3NjE4MzAyMjd9.MANlBkbOU95y-6yd51m0-hoa941A0uHutVwzl481k9I",
|
||||||
"createdAt": "2025-10-23T13:17:07.206Z",
|
"createdAt": "2025-10-23T13:17:07.206Z",
|
||||||
"expiresAt": "2025-10-30T13:17:07.206Z"
|
"expiresAt": "2025-10-30T13:17:07.206Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1762333423617",
|
||||||
|
"userId": "1",
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEiLCJlbWFpbCI6ImFkbWluQGhhcmhlaW1lcnRjLmRlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzYyMzMzNDIzLCJleHAiOjE3NjI5MzgyMjN9.V-L5ethO0VFSOPT2qbsQF2zQYQZSlese1rL5sIFaHbY",
|
||||||
|
"createdAt": "2025-11-05T09:03:43.617Z",
|
||||||
|
"expiresAt": "2025-11-12T09:03:43.617Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -8,6 +8,6 @@
|
|||||||
"phone": "",
|
"phone": "",
|
||||||
"active": true,
|
"active": true,
|
||||||
"created": "2025-10-21T00:00:00.000Z",
|
"created": "2025-10-21T00:00:00.000Z",
|
||||||
"lastLogin": "2025-10-23T13:17:07.206Z"
|
"lastLogin": "2025-11-05T09:03:43.617Z"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -50,9 +50,10 @@ export async function readTermine() {
|
|||||||
termine.push({
|
termine.push({
|
||||||
id: randomUUID(), // Generate ID on-the-fly for editing
|
id: randomUUID(), // Generate ID on-the-fly for editing
|
||||||
datum: values[0],
|
datum: values[0],
|
||||||
titel: values[1],
|
uhrzeit: values[1] || '',
|
||||||
beschreibung: values[2],
|
titel: values[2] || '',
|
||||||
kategorie: values[3]
|
beschreibung: values[3] || '',
|
||||||
|
kategorie: values[4] || 'Sonstiges'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,21 +71,23 @@ export async function readTermine() {
|
|||||||
// Write array of objects to CSV
|
// Write array of objects to CSV
|
||||||
export async function writeTermine(termine) {
|
export async function writeTermine(termine) {
|
||||||
try {
|
try {
|
||||||
let csv = '"datum","titel","beschreibung","kategorie"\n'
|
let csv = '"datum","uhrzeit","titel","beschreibung","kategorie"\n'
|
||||||
|
|
||||||
for (const termin of termine) {
|
for (const termin of termine) {
|
||||||
const datum = termin.datum || ''
|
const datum = termin.datum || ''
|
||||||
|
const uhrzeit = termin.uhrzeit || ''
|
||||||
const titel = termin.titel || ''
|
const titel = termin.titel || ''
|
||||||
const beschreibung = termin.beschreibung || ''
|
const beschreibung = termin.beschreibung || ''
|
||||||
const kategorie = termin.kategorie || ''
|
const kategorie = termin.kategorie || ''
|
||||||
|
|
||||||
// Escape quotes in values
|
// Escape quotes in values
|
||||||
const escapedDatum = datum.replace(/"/g, '""')
|
const escapedDatum = datum.replace(/"/g, '""')
|
||||||
|
const escapedUhrzeit = uhrzeit.replace(/"/g, '""')
|
||||||
const escapedTitel = titel.replace(/"/g, '""')
|
const escapedTitel = titel.replace(/"/g, '""')
|
||||||
const escapedBeschreibung = beschreibung.replace(/"/g, '""')
|
const escapedBeschreibung = beschreibung.replace(/"/g, '""')
|
||||||
const escapedKategorie = kategorie.replace(/"/g, '""')
|
const escapedKategorie = kategorie.replace(/"/g, '""')
|
||||||
|
|
||||||
csv += `"${escapedDatum}","${escapedTitel}","${escapedBeschreibung}","${escapedKategorie}"\n`
|
csv += `"${escapedDatum}","${escapedUhrzeit}","${escapedTitel}","${escapedBeschreibung}","${escapedKategorie}"\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.writeFile(TERMINE_FILE, csv, 'utf-8')
|
await fs.writeFile(TERMINE_FILE, csv, 'utf-8')
|
||||||
@@ -102,6 +105,7 @@ export async function saveTermin(terminData) {
|
|||||||
// Always add as new (CSV doesn't have persistent IDs)
|
// Always add as new (CSV doesn't have persistent IDs)
|
||||||
const newTermin = {
|
const newTermin = {
|
||||||
datum: terminData.datum,
|
datum: terminData.datum,
|
||||||
|
uhrzeit: terminData.uhrzeit || '',
|
||||||
titel: terminData.titel,
|
titel: terminData.titel,
|
||||||
beschreibung: terminData.beschreibung || '',
|
beschreibung: terminData.beschreibung || '',
|
||||||
kategorie: terminData.kategorie || 'Sonstiges'
|
kategorie: terminData.kategorie || 'Sonstiges'
|
||||||
@@ -122,6 +126,7 @@ export async function deleteTermin(terminData) {
|
|||||||
|
|
||||||
termine = termine.filter(t =>
|
termine = termine.filter(t =>
|
||||||
!(t.datum === terminData.datum &&
|
!(t.datum === terminData.datum &&
|
||||||
|
(t.uhrzeit || '') === (terminData.uhrzeit || '') &&
|
||||||
t.titel === terminData.titel &&
|
t.titel === terminData.titel &&
|
||||||
t.beschreibung === terminData.beschreibung &&
|
t.beschreibung === terminData.beschreibung &&
|
||||||
t.kategorie === terminData.kategorie)
|
t.kategorie === terminData.kategorie)
|
||||||
|
|||||||
Reference in New Issue
Block a user