feat: Kalender-Termine zeigen Icons + Start-Ende-Uhrzeit

* 📅-Icon bei ganztaegigen Terminen
* 🔁-Icon bei wiederkehrenden Terminen
* Anzeige "09:00-10:30" statt nur "09:00" in Woche/Tag-Ansicht
* Mouseover-Tooltip mit allen Termin-Infos inklusive Ort und
  Beschreibung

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker 2026-04-12 12:38:44 +02:00
parent c5284f57e0
commit ddd8f57e69
1 changed files with 37 additions and 1 deletions

View File

@ -21,7 +21,16 @@
</aside> </aside>
<div class="calendar-main"> <div class="calendar-main">
<FullCalendar ref="fcRef" :options="calendarOptions" /> <FullCalendar ref="fcRef" :options="calendarOptions">
<template #eventContent="arg">
<div class="fc-event-content-inner" :title="eventTooltip(arg.event)">
<span v-if="arg.event.extendedProps.all_day" class="fc-icon">📅</span>
<span v-else-if="arg.timeText" class="fc-time">{{ formatEventTime(arg.event) }}</span>
<span v-if="arg.event.extendedProps.recurrence_rule" class="fc-icon" title="Wiederholung">🔁</span>
<span class="fc-title">{{ arg.event.title }}</span>
</div>
</template>
</FullCalendar>
</div> </div>
</div> </div>
@ -293,8 +302,31 @@ const calendarOptions = computed(() => ({
select: onDateSelect, select: onDateSelect,
eventDrop: onEventDrop, eventDrop: onEventDrop,
eventResize: onEventDrop, eventResize: onEventDrop,
displayEventEnd: true,
eventTimeFormat: { hour: '2-digit', minute: '2-digit', hour12: false },
})) }))
function formatEventTime(ev) {
if (!ev.start) return ''
const fmt = d => d.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })
if (!ev.end) return fmt(ev.start)
return `${fmt(ev.start)}${fmt(ev.end)}`
}
function eventTooltip(ev) {
const p = ev.extendedProps
const parts = [ev.title]
if (p.all_day) parts.push('Ganztaegig')
else if (ev.start) {
const fmt = d => d.toLocaleString('de-DE', { hour: '2-digit', minute: '2-digit' })
parts.push(ev.end ? `${fmt(ev.start)}${fmt(ev.end)}` : fmt(ev.start))
}
if (p.recurrence_rule) parts.push('Wiederholung')
if (p.location) parts.push('Ort: ' + p.location)
if (p.description) parts.push(p.description)
return parts.join(' • ')
}
async function fetchEvents(info, successCallback, failureCallback) { async function fetchEvents(info, successCallback, failureCallback) {
try { try {
const all = [] const all = []
@ -656,4 +688,8 @@ onMounted(async () => {
.ical-url { font-size: 0.8rem; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; } .ical-url { font-size: 0.8rem; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.ical-url code { background: var(--p-surface-100); padding: 0.25rem 0.5rem; border-radius: 4px; word-break: break-all; } .ical-url code { background: var(--p-surface-100); padding: 0.25rem 0.5rem; border-radius: 4px; word-break: break-all; }
.hint-badge { font-size: 0.75rem; color: var(--p-primary-700); display: inline-flex; gap: 0.25rem; align-items: center; } .hint-badge { font-size: 0.75rem; color: var(--p-primary-700); display: inline-flex; gap: 0.25rem; align-items: center; }
.fc-event-content-inner { display: flex; align-items: center; gap: 0.2rem; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; padding: 0 2px; }
.fc-event-content-inner .fc-icon { flex-shrink: 0; font-size: 0.85em; }
.fc-event-content-inner .fc-time { flex-shrink: 0; font-weight: 600; font-size: 0.8em; opacity: 0.9; }
.fc-event-content-inner .fc-title { overflow: hidden; text-overflow: ellipsis; }
</style> </style>