feat(calendar): replace calendar dropdown with horizontal pills

- Replace select dropdown with horizontal scrolling pill buttons in QuickEventOverlay
- Each pill shows colored dot + calendar name for quick visual selection
- Add setAsDefault method to calendars store
- Improve settings page calendar editing with SettingsSection/SettingsCard components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Till-JS 2025-12-14 15:19:22 +01:00
parent 5190b1449a
commit b720f64d2f
3 changed files with 683 additions and 347 deletions

View file

@ -361,15 +361,6 @@
});
}
// Update draft when calendar changes
function handleCalendarChange(e: Event) {
const target = e.target as HTMLSelectElement;
calendarId = target.value;
if (!isEditMode) {
eventsStore.updateDraftEvent({ calendarId: target.value });
}
}
// Update draft when all-day changes
function handleAllDayToggle() {
isAllDay = !isAllDay;
@ -711,26 +702,31 @@
</span>
</div>
<!-- Calendar select -->
<div class="form-row">
<div class="row-icon">
<div
class="calendar-dot"
style="background-color: {calendarsStore.getColor(calendarId)}"
></div>
</div>
<div class="row-content">
<span class="field-label">Kalender</span>
{#if calendarsStore.calendars.length > 0}
<select class="field-select" value={calendarId} onchange={handleCalendarChange}>
{#each calendarsStore.calendars as cal}
<option value={cal.id}>{cal.name}</option>
{/each}
</select>
{:else}
<span class="field-placeholder">Standardkalender wird erstellt</span>
{/if}
</div>
<!-- Calendar pills -->
<div class="calendar-pills-container">
{#if calendarsStore.calendars.length > 0}
<div class="calendar-pills-scroll">
{#each calendarsStore.calendars as cal}
<button
type="button"
class="calendar-pill"
class:active={calendarId === cal.id}
onclick={() => {
calendarId = cal.id;
if (!isEditMode) {
eventsStore.updateDraftEvent({ calendarId: cal.id });
}
}}
>
<span class="calendar-pill-dot" style="background-color: {cal.color || '#3b82f6'}"
></span>
<span class="calendar-pill-name">{cal.name}</span>
</button>
{/each}
</div>
{:else}
<span class="field-placeholder">Standardkalender wird erstellt</span>
{/if}
</div>
<!-- People (compact) -->
@ -1225,6 +1221,63 @@
border-radius: 50%;
}
/* Calendar pills */
.calendar-pills-container {
padding: 0.5rem 0;
border-bottom: 1px solid hsl(var(--color-border));
}
.calendar-pills-scroll {
display: flex;
gap: 0.5rem;
overflow-x: auto;
scrollbar-width: none;
-ms-overflow-style: none;
padding: 0 1.25rem 2px;
}
.calendar-pills-scroll::-webkit-scrollbar {
display: none;
}
.calendar-pill {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.75rem;
border: 1px solid hsl(var(--color-border));
border-radius: 9999px;
background: transparent;
color: hsl(var(--color-muted-foreground));
font-size: 0.8125rem;
font-weight: 500;
white-space: nowrap;
cursor: pointer;
transition: all 150ms;
flex-shrink: 0;
}
.calendar-pill:hover {
background: hsl(var(--color-muted) / 0.3);
color: hsl(var(--color-foreground));
}
.calendar-pill.active {
background: hsl(var(--color-primary) / 0.1);
border-color: hsl(var(--color-primary) / 0.3);
color: hsl(var(--color-primary));
}
.calendar-pill-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.calendar-pill-name {
}
.row-content {
flex: 1;
min-width: 0;

View file

@ -115,6 +115,23 @@ export const calendarsStore = {
return this.updateCalendar(id, { isVisible: !calendar.isVisible });
},
/**
* Set a calendar as the default
*/
async setAsDefault(id: string) {
const result = await api.updateCalendar(id, { isDefault: true });
if (result.data) {
// Update local state: set this one as default, remove default from others
calendars = getCalendarsArray().map((c) => ({
...c,
isDefault: c.id === id,
}));
}
return result;
},
/**
* Get calendar by ID
*/

View file

@ -7,15 +7,32 @@
import type { TimeFormat, AllDayDisplayMode } from '$lib/stores/settings.svelte';
import { calendarsStore } from '$lib/stores/calendars.svelte';
import { toast } from '$lib/stores/toast.svelte';
import { GlobalSettingsSection } from '@manacore/shared-ui';
import { GlobalSettingsSection, SettingsSection, SettingsCard } from '@manacore/shared-ui';
import type { CalendarViewType, Calendar } from '@calendar/shared';
// Calendar management state
let editingCalendar = $state<Calendar | null>(null);
let editName = $state('');
let editColor = $state('');
let editIsDefault = $state(false);
let showNewCalendarForm = $state(false);
let newCalendarName = $state('');
let newCalendarColor = $state('#3b82f6');
function startEditing(calendar: Calendar) {
editingCalendar = calendar;
editName = calendar.name;
editColor = calendar.color || '#3b82f6';
editIsDefault = calendar.isDefault || false;
}
function cancelEditing() {
editingCalendar = null;
editName = '';
editColor = '';
editIsDefault = false;
}
onMount(async () => {
if (!authStore.isAuthenticated) {
goto('/login');
@ -59,8 +76,23 @@
toast.success('Kalender gelöscht');
}
async function handleUpdateCalendar(calendar: Calendar, name: string, color: string) {
const result = await calendarsStore.updateCalendar(calendar.id, { name, color });
async function handleUpdateCalendar() {
if (!editingCalendar || !editName.trim()) return;
// If setting as default and it wasn't before, use setAsDefault
if (editIsDefault && !editingCalendar.isDefault) {
const defaultResult = await calendarsStore.setAsDefault(editingCalendar.id);
if (defaultResult?.error) {
toast.error(`Fehler: ${defaultResult.error.message}`);
return;
}
}
// Update name and color
const result = await calendarsStore.updateCalendar(editingCalendar.id, {
name: editName.trim(),
color: editColor,
});
if (result.error) {
toast.error(`Fehler: ${result.error.message}`);
@ -68,7 +100,7 @@
}
toast.success('Kalender aktualisiert');
editingCalendar = null;
cancelEditing();
}
function handleViewChange(view: CalendarViewType) {
@ -124,335 +156,422 @@
</header>
<!-- Meine Kalender -->
<section class="settings-section card">
<div class="calendars-header">
<h2>Meine Kalender</h2>
<button class="btn btn-primary btn-sm" onclick={() => (showNewCalendarForm = true)}>
Neuer Kalender
</button>
</div>
<SettingsSection title="Meine Kalender">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
/>
</svg>
{/snippet}
<SettingsCard>
<div class="p-5">
<div class="calendars-toolbar">
<button class="btn btn-primary btn-sm" onclick={() => (showNewCalendarForm = true)}>
Neuer Kalender
</button>
</div>
{#if showNewCalendarForm}
<div class="new-calendar-form">
<form
onsubmit={(e) => {
e.preventDefault();
handleCreateCalendar();
}}
>
<div class="calendar-form-row">
<input
type="text"
class="input"
placeholder="Kalender Name"
bind:value={newCalendarName}
/>
<input type="color" class="color-input" bind:value={newCalendarColor} />
</div>
<div class="calendar-form-actions">
<button
type="button"
class="btn btn-ghost"
onclick={() => (showNewCalendarForm = false)}
>
Abbrechen
</button>
<button type="submit" class="btn btn-primary" disabled={!newCalendarName.trim()}>
Erstellen
</button>
</div>
</form>
</div>
{/if}
<div class="calendar-list">
{#each calendarsStore.calendars as calendar}
<div class="calendar-card">
{#if editingCalendar?.id === calendar.id}
{#if showNewCalendarForm}
<div class="new-calendar-form">
<form
onsubmit={(e) => {
e.preventDefault();
const form = e.target as HTMLFormElement;
const name = (form.elements.namedItem('name') as HTMLInputElement).value;
const color = (form.elements.namedItem('color') as HTMLInputElement).value;
handleUpdateCalendar(calendar, name, color);
handleCreateCalendar();
}}
>
<div class="calendar-form-row">
<input type="text" name="name" class="input" value={calendar.name} />
<input type="color" name="color" class="color-input" value={calendar.color} />
<input
type="text"
class="input"
placeholder="Kalender Name"
bind:value={newCalendarName}
/>
<input type="color" class="color-input" bind:value={newCalendarColor} />
</div>
<div class="calendar-form-actions">
<button
type="button"
class="btn btn-ghost"
onclick={() => (editingCalendar = null)}
onclick={() => (showNewCalendarForm = false)}
>
Abbrechen
</button>
<button type="submit" class="btn btn-primary"> Speichern </button>
<button type="submit" class="btn btn-primary" disabled={!newCalendarName.trim()}>
Erstellen
</button>
</div>
</form>
{:else}
<div class="calendar-info">
<span class="color-dot" style="background-color: {calendar.color}"></span>
<span class="calendar-name">{calendar.name}</span>
{#if calendar.isDefault}
<span class="badge">Standard</span>
{/if}
</div>
<div class="calendar-actions">
<button class="btn btn-ghost btn-sm" onclick={() => (editingCalendar = calendar)}>
Bearbeiten
</button>
{#if !calendar.isDefault}
<button
class="btn btn-ghost btn-sm text-destructive"
onclick={() => handleDeleteCalendar(calendar)}
</div>
{/if}
<div class="calendar-list">
{#each calendarsStore.calendars as calendar}
{#if editingCalendar?.id === calendar.id}
<div class="calendar-edit-form">
<form
onsubmit={(e) => {
e.preventDefault();
handleUpdateCalendar();
}}
>
Löschen
</button>
{/if}
<div class="edit-form-row">
<div class="edit-form-group edit-form-group--name">
<label for="edit-name" class="edit-label">Name</label>
<input
type="text"
id="edit-name"
class="edit-input"
placeholder="Kalender Name"
bind:value={editName}
/>
</div>
<div class="edit-form-group edit-form-group--color">
<label for="edit-color" class="edit-label">Farbe</label>
<div class="edit-color-wrapper">
<input
type="color"
id="edit-color"
class="edit-color-input"
bind:value={editColor}
/>
<span class="edit-color-value">{editColor}</span>
</div>
</div>
</div>
<label class="edit-checkbox">
<input
type="checkbox"
bind:checked={editIsDefault}
disabled={editingCalendar.isDefault}
/>
<span class="edit-checkbox-text">
Als Standardkalender verwenden
{#if editingCalendar.isDefault}
<span class="edit-checkbox-hint">(aktueller Standard)</span>
{/if}
</span>
</label>
<div class="edit-form-actions">
<button type="button" class="btn btn-ghost" onclick={cancelEditing}>
Abbrechen
</button>
<button type="submit" class="btn btn-primary" disabled={!editName.trim()}>
Speichern
</button>
</div>
</form>
</div>
{:else}
<div class="calendar-card">
<div class="calendar-info">
<span class="color-dot" style="background-color: {calendar.color}"></span>
<span class="calendar-name">{calendar.name}</span>
{#if calendar.isDefault}
<span class="badge badge-primary">Standard</span>
{/if}
</div>
<div class="calendar-actions">
<button class="btn btn-ghost btn-sm" onclick={() => startEditing(calendar)}>
Bearbeiten
</button>
{#if !calendar.isDefault}
<button
class="btn btn-ghost btn-sm text-destructive"
onclick={() => handleDeleteCalendar(calendar)}
>
Löschen
</button>
{/if}
</div>
</div>
{/if}
{/each}
{#if calendarsStore.calendars.length === 0}
<div class="empty-state">
<p>Keine Kalender vorhanden</p>
</div>
{/if}
</div>
{/each}
{#if calendarsStore.calendars.length === 0}
<div class="empty-state">
<p>Keine Kalender vorhanden</p>
</div>
{/if}
</div>
</section>
</div>
</SettingsCard>
</SettingsSection>
<!-- Global App Settings (synced across all apps) -->
<section class="settings-section">
<GlobalSettingsSection
{userSettings}
appId="calendar"
title="App-Einstellungen"
description="Diese Einstellungen werden mit allen Mana Apps synchronisiert"
/>
</section>
<GlobalSettingsSection
{userSettings}
appId="calendar"
title="App-Einstellungen"
description="Diese Einstellungen werden mit allen Mana Apps synchronisiert"
/>
<!-- Kalender-Ansicht -->
<section class="settings-section card">
<h2>Kalender-Ansicht</h2>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Standard-Ansicht</span>
<span class="setting-description">Ansicht beim Öffnen des Kalenders</span>
</div>
<select
class="select-input"
value={settingsStore.defaultView}
onchange={(e) => handleViewChange(e.currentTarget.value as CalendarViewType)}
>
{#each Object.entries(viewLabels) as [value, label]}
<option {value}>{label}</option>
{/each}
</select>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Zeitformat</span>
<span class="setting-description">Anzeige der Uhrzeiten</span>
</div>
<div class="button-group">
<button
class="group-button"
class:active={settingsStore.timeFormat === '24h'}
onclick={() => handleTimeFormatChange('24h')}
>
24h (14:00)
</button>
<button
class="group-button"
class:active={settingsStore.timeFormat === '12h'}
onclick={() => handleTimeFormatChange('12h')}
>
12h (2:00 PM)
</button>
</div>
</div>
<div class="setting-item">
<label class="toggle-setting">
<input
type="checkbox"
checked={settingsStore.showOnlyWeekdays}
onchange={() => settingsStore.set('showOnlyWeekdays', !settingsStore.showOnlyWeekdays)}
<SettingsSection title="Kalender-Ansicht">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
<div class="toggle-info">
<span class="setting-label">Nur Werktage anzeigen</span>
<span class="setting-description">Wochenenden in der Kalenderansicht ausblenden</span>
</div>
</label>
</div>
<div class="setting-item">
<label class="toggle-setting">
<input
type="checkbox"
checked={settingsStore.showWeekNumbers}
onchange={() => settingsStore.set('showWeekNumbers', !settingsStore.showWeekNumbers)}
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/>
<div class="toggle-info">
<span class="setting-label">Wochennummern anzeigen</span>
<span class="setting-description">Kalenderwoche (KW) in der Ansicht anzeigen</span>
</div>
</label>
</div>
<div class="setting-item">
<label class="toggle-setting">
<input
type="checkbox"
checked={settingsStore.filterHoursEnabled}
onchange={() =>
settingsStore.set('filterHoursEnabled', !settingsStore.filterHoursEnabled)}
/>
<div class="toggle-info">
<span class="setting-label">Stunden filtern</span>
<span class="setting-description"
>Nur bestimmte Stunden in der Tages-/Wochenansicht anzeigen</span
>
</div>
</label>
</div>
{#if settingsStore.filterHoursEnabled}
<div class="setting-item hour-range-setting">
<div class="setting-info">
<span class="setting-label">Sichtbare Stunden</span>
<span class="setting-description"
>Zeitbereich der in der Kalenderansicht angezeigt wird</span
>
</div>
<div class="hour-range-inputs">
<div class="hour-input-group">
<label for="start-hour">Von</label>
<select
id="start-hour"
class="select-input hour-select"
value={settingsStore.dayStartHour}
onchange={(e) => settingsStore.set('dayStartHour', Number(e.currentTarget.value))}
>
{#each Array.from({ length: 24 }, (_, i) => i) as hour}
{#if hour < settingsStore.dayEndHour}
<option value={hour}>{hour.toString().padStart(2, '0')}:00</option>
{/if}
{/each}
</select>
</svg>
{/snippet}
<SettingsCard>
<div class="p-5 space-y-4">
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Standard-Ansicht</span>
<span class="setting-description">Ansicht beim Öffnen des Kalenders</span>
</div>
<span class="hour-separator"></span>
<div class="hour-input-group">
<label for="end-hour">Bis</label>
<select
id="end-hour"
class="select-input hour-select"
value={settingsStore.dayEndHour}
onchange={(e) => settingsStore.set('dayEndHour', Number(e.currentTarget.value))}
<select
class="select-input"
value={settingsStore.defaultView}
onchange={(e) => handleViewChange(e.currentTarget.value as CalendarViewType)}
>
{#each Object.entries(viewLabels) as [value, label]}
<option {value}>{label}</option>
{/each}
</select>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Zeitformat</span>
<span class="setting-description">Anzeige der Uhrzeiten</span>
</div>
<div class="button-group">
<button
class="group-button"
class:active={settingsStore.timeFormat === '24h'}
onclick={() => handleTimeFormatChange('24h')}
>
{#each Array.from({ length: 24 }, (_, i) => i + 1) as hour}
{#if hour > settingsStore.dayStartHour}
<option value={hour}>{hour.toString().padStart(2, '0')}:00</option>
{/if}
{/each}
</select>
24h (14:00)
</button>
<button
class="group-button"
class:active={settingsStore.timeFormat === '12h'}
onclick={() => handleTimeFormatChange('12h')}
>
12h (2:00 PM)
</button>
</div>
</div>
<div class="setting-item">
<label class="toggle-setting">
<input
type="checkbox"
checked={settingsStore.showOnlyWeekdays}
onchange={() =>
settingsStore.set('showOnlyWeekdays', !settingsStore.showOnlyWeekdays)}
/>
<div class="toggle-info">
<span class="setting-label">Nur Werktage anzeigen</span>
<span class="setting-description">Wochenenden in der Kalenderansicht ausblenden</span>
</div>
</label>
</div>
<div class="setting-item">
<label class="toggle-setting">
<input
type="checkbox"
checked={settingsStore.showWeekNumbers}
onchange={() => settingsStore.set('showWeekNumbers', !settingsStore.showWeekNumbers)}
/>
<div class="toggle-info">
<span class="setting-label">Wochennummern anzeigen</span>
<span class="setting-description">Kalenderwoche (KW) in der Ansicht anzeigen</span>
</div>
</label>
</div>
<div class="setting-item">
<label class="toggle-setting">
<input
type="checkbox"
checked={settingsStore.filterHoursEnabled}
onchange={() =>
settingsStore.set('filterHoursEnabled', !settingsStore.filterHoursEnabled)}
/>
<div class="toggle-info">
<span class="setting-label">Stunden filtern</span>
<span class="setting-description"
>Nur bestimmte Stunden in der Tages-/Wochenansicht anzeigen</span
>
</div>
</label>
</div>
{#if settingsStore.filterHoursEnabled}
<div class="setting-item hour-range-setting">
<div class="setting-info">
<span class="setting-label">Sichtbare Stunden</span>
<span class="setting-description"
>Zeitbereich der in der Kalenderansicht angezeigt wird</span
>
</div>
<div class="hour-range-inputs">
<div class="hour-input-group">
<label for="start-hour">Von</label>
<select
id="start-hour"
class="select-input hour-select"
value={settingsStore.dayStartHour}
onchange={(e) => settingsStore.set('dayStartHour', Number(e.currentTarget.value))}
>
{#each Array.from({ length: 24 }, (_, i) => i) as hour}
{#if hour < settingsStore.dayEndHour}
<option value={hour}>{hour.toString().padStart(2, '0')}:00</option>
{/if}
{/each}
</select>
</div>
<span class="hour-separator"></span>
<div class="hour-input-group">
<label for="end-hour">Bis</label>
<select
id="end-hour"
class="select-input hour-select"
value={settingsStore.dayEndHour}
onchange={(e) => settingsStore.set('dayEndHour', Number(e.currentTarget.value))}
>
{#each Array.from({ length: 24 }, (_, i) => i + 1) as hour}
{#if hour > settingsStore.dayStartHour}
<option value={hour}>{hour.toString().padStart(2, '0')}:00</option>
{/if}
{/each}
</select>
</div>
</div>
</div>
{/if}
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Ganztägige Termine</span>
<span class="setting-description">Wie sollen ganztägige Termine angezeigt werden?</span>
</div>
<div class="button-group">
<button
class="group-button"
class:active={settingsStore.allDayDisplayMode === 'header'}
onclick={() => settingsStore.set('allDayDisplayMode', 'header' as AllDayDisplayMode)}
>
In Kopfzeile
</button>
<button
class="group-button"
class:active={settingsStore.allDayDisplayMode === 'block'}
onclick={() => settingsStore.set('allDayDisplayMode', 'block' as AllDayDisplayMode)}
>
Als Tagesblock
</button>
</div>
</div>
</div>
{/if}
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Ganztägige Termine</span>
<span class="setting-description">Wie sollen ganztägige Termine angezeigt werden?</span>
</div>
<div class="button-group">
<button
class="group-button"
class:active={settingsStore.allDayDisplayMode === 'header'}
onclick={() => settingsStore.set('allDayDisplayMode', 'header' as AllDayDisplayMode)}
>
In Kopfzeile
</button>
<button
class="group-button"
class:active={settingsStore.allDayDisplayMode === 'block'}
onclick={() => settingsStore.set('allDayDisplayMode', 'block' as AllDayDisplayMode)}
>
Als Tagesblock
</button>
</div>
</div>
</section>
</SettingsCard>
</SettingsSection>
<!-- Termin-Einstellungen -->
<section class="settings-section card">
<h2>Termine</h2>
<SettingsSection title="Termine">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{/snippet}
<SettingsCard>
<div class="p-5 space-y-4">
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Standard-Dauer</span>
<span class="setting-description">Voreingestellte Dauer für neue Termine</span>
</div>
<select
class="select-input"
value={settingsStore.defaultEventDuration}
onchange={(e) => handleEventDurationChange(Number(e.currentTarget.value))}
>
{#each durationOptions as duration}
<option value={duration}>
{duration >= 60
? `${duration / 60} Stunde${duration > 60 ? 'n' : ''}`
: `${duration} Minuten`}
</option>
{/each}
</select>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Standard-Dauer</span>
<span class="setting-description">Voreingestellte Dauer für neue Termine</span>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Standard-Erinnerung</span>
<span class="setting-description">Voreingestellte Erinnerung für neue Termine</span>
</div>
<select
class="select-input"
value={settingsStore.defaultReminder}
onchange={(e) => handleReminderChange(Number(e.currentTarget.value))}
>
{#each reminderOptions as option}
<option value={option.value}>{option.label}</option>
{/each}
</select>
</div>
</div>
<select
class="select-input"
value={settingsStore.defaultEventDuration}
onchange={(e) => handleEventDurationChange(Number(e.currentTarget.value))}
>
{#each durationOptions as duration}
<option value={duration}>
{duration >= 60
? `${duration / 60} Stunde${duration > 60 ? 'n' : ''}`
: `${duration} Minuten`}
</option>
{/each}
</select>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">Standard-Erinnerung</span>
<span class="setting-description">Voreingestellte Erinnerung für neue Termine</span>
</div>
<select
class="select-input"
value={settingsStore.defaultReminder}
onchange={(e) => handleReminderChange(Number(e.currentTarget.value))}
>
{#each reminderOptions as option}
<option value={option.value}>{option.label}</option>
{/each}
</select>
</div>
</section>
</SettingsCard>
</SettingsSection>
<!-- Konto -->
<section class="settings-section card">
<h2>Konto</h2>
<SettingsSection title="Konto">
{#snippet icon()}
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
/>
</svg>
{/snippet}
<SettingsCard>
<div class="p-5 space-y-4">
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">E-Mail</span>
<span class="setting-value">{authStore.user?.email || '-'}</span>
</div>
</div>
<div class="setting-item">
<div class="setting-info">
<span class="setting-label">E-Mail</span>
<span class="setting-value">{authStore.user?.email || '-'}</span>
<div class="setting-item">
<button
class="btn btn-ghost text-destructive"
onclick={() => authStore.signOut().then(() => goto('/login'))}
>
Abmelden
</button>
</div>
</div>
</div>
<div class="setting-item">
<button
class="btn btn-ghost text-destructive"
onclick={() => authStore.signOut().then(() => goto('/login'))}
>
Abmelden
</button>
</div>
</section>
</SettingsCard>
</SettingsSection>
</div>
<style>
@ -472,16 +591,10 @@
margin: 0;
}
.settings-section {
margin-bottom: 1.5rem;
}
.settings-section h2 {
font-size: 1rem;
font-weight: 600;
margin: 0 0 1rem 0;
padding-bottom: 0.75rem;
border-bottom: 1px solid hsl(var(--color-border));
.calendars-toolbar {
display: flex;
justify-content: flex-end;
margin-bottom: 1rem;
}
.setting-item {
@ -634,21 +747,6 @@
}
/* Calendar management styles */
.calendars-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid hsl(var(--color-border));
}
.calendars-header h2 {
margin: 0;
padding: 0;
border: none;
}
.new-calendar-form {
margin-bottom: 1rem;
padding: 1rem;
@ -696,6 +794,168 @@
border-radius: var(--radius-md);
}
/* Edit form styles */
.calendar-edit-form {
padding: 1.25rem;
background: hsl(var(--color-card));
border: 1px solid hsl(var(--color-border));
border-radius: var(--radius-lg);
box-shadow: 0 2px 8px hsl(var(--color-foreground) / 0.08);
}
.edit-form-row {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.edit-form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.edit-form-group--name {
flex: 1;
}
.edit-form-group--color {
flex-shrink: 0;
}
.edit-label {
font-size: 0.8125rem;
font-weight: 600;
color: hsl(var(--color-foreground));
text-transform: uppercase;
letter-spacing: 0.025em;
}
.edit-input {
width: 100%;
padding: 0.625rem 0.875rem;
font-size: 0.9375rem;
color: hsl(var(--color-foreground));
background: hsl(var(--color-background));
border: 2px solid hsl(var(--color-border));
border-radius: var(--radius-md);
outline: none;
transition:
border-color 150ms ease,
box-shadow 150ms ease;
}
.edit-input:hover {
border-color: hsl(var(--color-muted-foreground) / 0.5);
}
.edit-input:focus {
border-color: hsl(var(--color-primary));
box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.15);
}
.edit-input::placeholder {
color: hsl(var(--color-muted-foreground) / 0.7);
}
.edit-color-wrapper {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.375rem 0.625rem 0.375rem 0.375rem;
background: hsl(var(--color-background));
border: 2px solid hsl(var(--color-border));
border-radius: var(--radius-md);
transition: border-color 150ms ease;
}
.edit-color-wrapper:hover {
border-color: hsl(var(--color-muted-foreground) / 0.5);
}
.edit-color-wrapper:focus-within {
border-color: hsl(var(--color-primary));
box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.15);
}
.edit-color-input {
width: 32px;
height: 32px;
padding: 0;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
background: transparent;
}
.edit-color-input::-webkit-color-swatch-wrapper {
padding: 0;
}
.edit-color-input::-webkit-color-swatch {
border: none;
border-radius: var(--radius-sm);
}
.edit-color-input::-moz-color-swatch {
border: none;
border-radius: var(--radius-sm);
}
.edit-color-value {
font-size: 0.8125rem;
font-family: monospace;
color: hsl(var(--color-muted-foreground));
text-transform: uppercase;
}
.edit-checkbox {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem;
margin-bottom: 1rem;
background: hsl(var(--color-muted) / 0.3);
border-radius: var(--radius-md);
cursor: pointer;
transition: background 150ms ease;
}
.edit-checkbox:hover {
background: hsl(var(--color-muted) / 0.5);
}
.edit-checkbox input[type='checkbox'] {
width: 1.125rem;
height: 1.125rem;
accent-color: hsl(var(--color-primary));
cursor: pointer;
}
.edit-checkbox input[type='checkbox']:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.edit-checkbox-text {
font-size: 0.875rem;
color: hsl(var(--color-foreground));
}
.edit-checkbox-hint {
color: hsl(var(--color-muted-foreground));
font-style: italic;
margin-left: 0.25rem;
}
.edit-form-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
padding-top: 1rem;
border-top: 1px solid hsl(var(--color-border));
}
.calendar-info {
display: flex;
align-items: center;
@ -720,6 +980,12 @@
color: hsl(var(--color-muted-foreground));
}
.badge-primary {
background: hsl(var(--color-primary) / 0.15);
color: hsl(var(--color-primary));
font-weight: 500;
}
.calendar-actions {
display: flex;
gap: 0.5rem;