refactor(calendar): improve agenda and event components

- Replace native select with FilterDropdown in AgendaFilters
- Add view options to DateStripContextMenu
- Improve EventForm layout and styling
- Enhance QuickEventOverlay with better time handling

🤖 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 21:30:24 +01:00
parent cc28cc739e
commit 48edd85591
4 changed files with 76 additions and 57 deletions

View file

@ -1,5 +1,6 @@
<script lang="ts">
import { Calendar, CheckSquare, Filter } from 'lucide-svelte';
import { FilterDropdown, type FilterDropdownOption } from '@manacore/shared-ui';
interface Props {
showEvents: boolean;
@ -19,10 +20,10 @@
onRangeChange,
}: Props = $props();
const rangeOptions = [
{ value: '7' as const, label: '7 Tage' },
{ value: '30' as const, label: '30 Tage' },
{ value: 'all' as const, label: 'Alle' },
const rangeOptions: FilterDropdownOption[] = [
{ value: '7', label: '7 Tage' },
{ value: '30', label: '30 Tage' },
{ value: 'all', label: 'Alle' },
];
</script>
@ -53,15 +54,13 @@
<div class="filter-group">
<div class="range-selector">
<Filter size={14} />
<select
<FilterDropdown
options={rangeOptions}
value={timeRange}
onchange={(e) =>
onRangeChange?.((e.target as HTMLSelectElement).value as '7' | '30' | 'all')}
>
{#each rangeOptions as option}
<option value={option.value}>{option.label}</option>
{/each}
</select>
onChange={(v) => onRangeChange?.(v as '7' | '30' | 'all')}
placeholder="Zeitraum"
embedded={true}
/>
</div>
</div>
</div>
@ -122,21 +121,6 @@
color: hsl(var(--color-muted-foreground));
}
.range-selector select {
padding: 0.375rem 0.75rem;
border-radius: var(--radius-md);
border: 1px solid hsl(var(--color-border));
background: hsl(var(--color-surface));
color: hsl(var(--color-foreground));
font-size: 0.8125rem;
cursor: pointer;
}
.range-selector select:focus {
outline: none;
border-color: hsl(var(--color-primary));
}
@media (max-width: 480px) {
.agenda-filters {
flex-direction: column;

View file

@ -1,6 +1,6 @@
<script lang="ts">
import { ContextMenu, type ContextMenuItem } from '@manacore/shared-ui';
import { Moon, Calendar, Eye, Columns, ArrowsIn } from '@manacore/shared-icons';
import { Moon, Calendar, Eye, Columns, ArrowsIn, ArrowsOut } from '@manacore/shared-icons';
import { settingsStore } from '$lib/stores/settings.svelte';
// Context menu state
@ -77,6 +77,17 @@
checked: settingsStore.dateStripCompact,
action: () => toggleSetting('dateStripCompact'),
},
{
id: 'divider-3',
label: '',
type: 'divider',
},
{
id: 'minimize',
label: settingsStore.dateStripCollapsed ? 'Erweitern' : 'Minimieren',
icon: settingsStore.dateStripCollapsed ? ArrowsOut : ArrowsIn,
action: () => toggleSetting('dateStripCollapsed'),
},
];
});

View file

@ -3,7 +3,12 @@
import { calendarsStore } from '$lib/stores/calendars.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import { eventTagsStore } from '$lib/stores/event-tags.svelte';
import { TagSelector, type Tag } from '@manacore/shared-ui';
import {
TagSelector,
FilterDropdown,
type Tag,
type FilterDropdownOption,
} from '@manacore/shared-ui';
import AttendeeSelector from './AttendeeSelector.svelte';
import ResponsiblePersonSelector from './ResponsiblePersonSelector.svelte';
import type {
@ -79,6 +84,18 @@
// Derived available tags for TagSelector
let availableTags = $derived(eventTagsStore.tags.map(eventTagToTag));
// Calendar options for FilterDropdown
let calendarOptions = $derived<FilterDropdownOption[]>(
calendarsStore.calendars.map((cal) => ({ value: cal.id, label: cal.name }))
);
// All-day display mode options
const displayModeOptions: FilterDropdownOption[] = [
{ value: 'default', label: 'Standard (aus Einstellungen)' },
{ value: 'header', label: 'In Kopfzeile' },
{ value: 'block', label: 'Als Tagesblock' },
];
// Auto-expand location details if any field is filled
$effect(() => {
if (event?.metadata?.locationDetails) {
@ -228,17 +245,14 @@
</div>
<div class="flex flex-col gap-2">
<label for="calendar" class="text-sm font-medium text-foreground">Kalender</label>
<span class="text-sm font-medium text-foreground">Kalender</span>
{#if calendarsStore.calendars.length > 0}
<select
id="calendar"
class="w-full px-3 py-2 border-2 border-border rounded-lg bg-background text-foreground focus:outline-none focus:border-primary transition-colors"
bind:value={calendarId}
>
{#each calendarsStore.calendars as cal}
<option value={cal.id}>{cal.name}</option>
{/each}
</select>
<FilterDropdown
options={calendarOptions}
value={calendarId}
onChange={(v) => (calendarId = typeof v === 'string' ? v : '')}
placeholder="Kalender wählen"
/>
{:else}
<p class="text-sm text-muted-foreground italic">Standardkalender wird automatisch erstellt</p>
{/if}
@ -253,16 +267,13 @@
{#if isAllDay}
<div class="flex flex-col gap-2">
<label for="displayMode" class="text-sm font-medium text-foreground">Anzeigeart</label>
<select
id="displayMode"
class="w-full px-3 py-2 border-2 border-border rounded-lg bg-background text-foreground focus:outline-none focus:border-primary transition-colors"
bind:value={allDayDisplayMode}
>
<option value="default">Standard (aus Einstellungen)</option>
<option value="header">In Kopfzeile</option>
<option value="block">Als Tagesblock</option>
</select>
<span class="text-sm font-medium text-foreground">Anzeigeart</span>
<FilterDropdown
options={displayModeOptions}
value={allDayDisplayMode}
onChange={(v) => (allDayDisplayMode = (v as 'default' | 'header' | 'block') || 'default')}
placeholder="Anzeigeart wählen"
/>
</div>
{/if}

View file

@ -11,7 +11,13 @@
EventAttendee,
} from '@calendar/shared';
import type { ContactSummary, ContactOrManual, ManualContactEntry } from '@manacore/shared-types';
import { ContactSelector, ContactAvatar, ConfirmationPopover } from '@manacore/shared-ui';
import {
ContactSelector,
ContactAvatar,
ConfirmationPopover,
FilterDropdown,
type FilterDropdownOption,
} from '@manacore/shared-ui';
import { Users } from 'lucide-svelte';
import { format, addMinutes } from 'date-fns';
import { de } from 'date-fns/locale';
@ -208,6 +214,13 @@
let showPeopleSelector = $state(false);
let contactsAvailable = $state<boolean | null>(null);
// All-day display mode options
const displayModeOptions: FilterDropdownOption[] = [
{ value: 'default', label: 'Standard (aus Einstellungen)' },
{ value: 'header', label: 'In Kopfzeile' },
{ value: 'block', label: 'Als Tagesblock' },
];
// Check contacts availability
$effect(() => {
contactsStore.checkAvailability().then((available) => {
@ -849,11 +862,13 @@
<div class="row-icon"></div>
<div class="row-content">
<span class="field-label">Anzeigeart</span>
<select class="field-select" bind:value={allDayDisplayMode}>
<option value="default">Standard (aus Einstellungen)</option>
<option value="header">In Kopfzeile</option>
<option value="block">Als Tagesblock</option>
</select>
<FilterDropdown
options={displayModeOptions}
value={allDayDisplayMode}
onChange={(v) =>
(allDayDisplayMode = (v as 'default' | 'header' | 'block') || 'default')}
placeholder="Anzeigeart wählen"
/>
</div>
</div>
{/if}
@ -1306,7 +1321,6 @@
margin-bottom: 0.25rem;
}
.field-select,
.field-input {
width: 100%;
padding: 0.5rem 0.625rem;
@ -1317,7 +1331,6 @@
font-size: 0.875rem;
}
.field-select:focus,
.field-input:focus {
outline: none;
border-color: hsl(var(--color-primary));