feat: Persoenliche Farbe fuer freigegebene Kalender

CalendarShare bekommt color-Spalte. Im Kalender-Menue kann jeder
Benutzer eine eigene Anzeigefarbe fuer einen mit ihm geteilten
Kalender setzen, ohne dass sich dadurch die Farbe beim
Eigentuemer oder anderen Share-Empfaengern aendert.

* Owner: Farbe aendert den Kalender direkt (wie bisher).
* Share-Empfaenger: Farbe landet in CalendarShare.color und wird
  nur fuer ihn ausgeliefert (list_calendars injiziert sie in
  'color', Owner-Farbe bleibt in 'owner_color' als Referenz).

Neuer Endpoint: PUT /calendars/<id>/my-color.
UI-Hinweis: "Nur fuer deine Ansicht - <Owner> behaelt seine Farbe".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-12 13:14:45 +02:00
parent 2170f4a7b1
commit e85338761d
3 changed files with 61 additions and 0 deletions
+28
View File
@@ -146,6 +146,19 @@
<div v-if="selectedCal" class="cal-menu-content">
<p><strong>{{ selectedCal.name }}</strong></p>
<div class="field">
<label>
{{ selectedCal.permission === 'owner' ? 'Farbe' : 'Persoenliche Anzeigefarbe' }}
</label>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<InputText :modelValue="selectedCal.color" @change="onColorChange($event)"
type="color" style="width: 60px; height: 36px" />
<span v-if="selectedCal.permission !== 'owner'" class="color-hint">
Nur fuer deine Ansicht - {{ selectedCal.owner_name }} behaelt seine Farbe
</span>
</div>
</div>
<div v-if="selectedCal.permission === 'owner'" class="field">
<label>Mit Benutzer teilen</label>
<div class="share-row">
@@ -821,6 +834,20 @@ function copyText(text) {
toast.add({ severity: 'info', summary: 'Kopiert', life: 1500 })
}
async function onColorChange(ev) {
if (!selectedCal.value) return
const color = ev.target.value
try {
const res = await apiClient.put(`/calendars/${selectedCal.value.id}/my-color`, { color })
selectedCal.value.color = res.data.color
await loadCalendars()
refreshEvents()
toast.add({ severity: 'success', summary: 'Farbe aktualisiert', life: 2000 })
} catch (err) {
toast.add({ severity: 'error', summary: 'Fehler', detail: err.response?.data?.error, life: 4000 })
}
}
async function deleteCalendar() {
if (!selectedCal.value) return
await apiClient.delete(`/calendars/${selectedCal.value.id}`)
@@ -935,6 +962,7 @@ onUnmounted(() => {
font-size: 0.8rem; word-break: break-all; flex: 1; }
.caldav-clients { font-size: 0.75rem; color: var(--p-text-muted-color); margin-top: 0.5rem; }
.caldav-clients div { margin-bottom: 0.15rem; }
.color-hint { font-size: 0.75rem; color: var(--p-text-muted-color); font-style: italic; }
.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; }