🎨 style: fix Prettier formatting issues

Fixed formatting in 21 files across calendar, contacts, manacore, manadeck,
picture, storage apps and shared-ui package.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Wuesteon 2025-12-04 01:05:10 +01:00
parent 843cf1e678
commit 4900106021
21 changed files with 788 additions and 391 deletions

View file

@ -19,7 +19,15 @@
};
// Views to show in selector
const visibleViews: CalendarViewType[] = ['day', '5day', 'week', '10day', '14day', 'month', 'year'];
const visibleViews: CalendarViewType[] = [
'day',
'5day',
'week',
'10day',
'14day',
'month',
'year',
];
// Format title based on view type
let title = $derived.by(() => {
@ -30,9 +38,17 @@
// Helper to format date range
const formatRange = () => {
if (rangeStart.getMonth() === rangeEnd.getMonth()) {
return format(rangeStart, 'd.', { locale: de }) + ' - ' + format(rangeEnd, 'd. MMMM yyyy', { locale: de });
return (
format(rangeStart, 'd.', { locale: de }) +
' - ' +
format(rangeEnd, 'd. MMMM yyyy', { locale: de })
);
}
return format(rangeStart, 'd. MMM', { locale: de }) + ' - ' + format(rangeEnd, 'd. MMM yyyy', { locale: de });
return (
format(rangeStart, 'd. MMM', { locale: de }) +
' - ' +
format(rangeEnd, 'd. MMM yyyy', { locale: de })
);
};
switch (viewStore.viewType) {
@ -61,14 +77,17 @@
<header class="calendar-header" class:nav-collapsed={$isNavCollapsed}>
<div class="header-left">
<button class="today-btn" onclick={() => viewStore.goToToday()}>
Heute
</button>
<button class="today-btn" onclick={() => viewStore.goToToday()}> Heute </button>
<div class="nav-buttons">
<button class="nav-btn" onclick={() => viewStore.goToPrevious()} aria-label="Zurück">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 19l-7-7 7-7"
/>
</svg>
</button>
<button class="nav-btn" onclick={() => viewStore.goToNext()} aria-label="Weiter">

View file

@ -34,7 +34,9 @@
);
// Calculate visible hours range for positioning
let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0);
let firstVisibleHour = $derived(
settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0
);
let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24);
let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour);
@ -74,12 +76,10 @@
// Split all-day events by display mode
let headerAllDayEvents = $derived(
allDayEvents.filter(e => getEventDisplayMode(e) === 'header')
allDayEvents.filter((e) => getEventDisplayMode(e) === 'header')
);
let blockAllDayEvents = $derived(
allDayEvents.filter(e => getEventDisplayMode(e) === 'block')
);
let blockAllDayEvents = $derived(allDayEvents.filter((e) => getEventDisplayMode(e) === 'block'));
// ============================================================================
// Drag & Drop State
@ -155,7 +155,10 @@
hasMoved = true;
const mouseMinutes = getMinutesFromY(e.clientY);
const newStartMinutes = snapToGrid(mouseMinutes - dragOffsetMinutes);
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(newStartMinutes, lastVisibleHour * 60 - 15));
const clampedMinutes = Math.max(
firstVisibleHour * 60,
Math.min(newStartMinutes, lastVisibleHour * 60 - 15)
);
dragPreviewTop = minutesToPercent(clampedMinutes);
}
@ -168,10 +171,19 @@
const mouseMinutes = getMinutesFromY(e.clientY);
const newStartMinutes = snapToGrid(mouseMinutes - dragOffsetMinutes);
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(newStartMinutes, lastVisibleHour * 60 - 30));
const clampedMinutes = Math.max(
firstVisibleHour * 60,
Math.min(newStartMinutes, lastVisibleHour * 60 - 30)
);
const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime;
const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime;
const start =
typeof draggedEvent.startTime === 'string'
? parseISO(draggedEvent.startTime)
: draggedEvent.startTime;
const end =
typeof draggedEvent.endTime === 'string'
? parseISO(draggedEvent.endTime)
: draggedEvent.endTime;
const duration = differenceInMinutes(end, start);
// Create new start time on same day
@ -262,12 +274,18 @@
let newEnd = new Date(resizeOriginalEnd);
if (resizeEdge === 'top') {
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(snappedMinutes, origEndMinutes - SNAP_MINUTES));
const newStartMinutes = Math.max(
firstVisibleHour * 60,
Math.min(snappedMinutes, origEndMinutes - SNAP_MINUTES)
);
newStart = setHours(new Date(viewStore.currentDate), Math.floor(newStartMinutes / 60));
newStart = setMinutes(newStart, newStartMinutes % 60);
newStart.setSeconds(0, 0);
} else {
const newEndMinutes = Math.min(lastVisibleHour * 60, Math.max(snappedMinutes, origStartMinutes + SNAP_MINUTES));
const newEndMinutes = Math.min(
lastVisibleHour * 60,
Math.max(snappedMinutes, origStartMinutes + SNAP_MINUTES)
);
newEnd = setHours(new Date(viewStore.currentDate), Math.floor(newEndMinutes / 60));
newEnd = setMinutes(newEnd, newEndMinutes % 60);
newEnd.setSeconds(0, 0);
@ -343,7 +361,9 @@
if (isDragging || isResizing || hasMoved) {
e.preventDefault();
e.stopPropagation();
setTimeout(() => { hasMoved = false; }, 100);
setTimeout(() => {
hasMoved = false;
}, 100);
return;
}
goto(`/?event=${event.id}`);
@ -395,11 +415,7 @@
{/each}
</div>
<div
class="day-column"
class:today={isToday(viewStore.currentDate)}
bind:this={dayColumnRef}
>
<div class="day-column" class:today={isToday(viewStore.currentDate)} bind:this={dayColumnRef}>
{#each hours as hour}
<button
class="hour-slot"
@ -430,7 +446,11 @@
class:resizing={isBeingResized}
class:draft={isDraft}
data-event-id={event.id}
style={isBeingDragged ? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};` : isBeingResized ? `top: ${resizePreviewTop}%; height: ${resizePreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};` : getEventStyle(event)}
style={isBeingDragged
? `top: ${dragPreviewTop}%; height: ${dragPreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
: isBeingResized
? `top: ${resizePreviewTop}%; height: ${resizePreviewHeight}%; background-color: ${calendarsStore.getColor(event.calendarId)};`
: getEventStyle(event)}
onpointerdown={(e) => startDrag(event, e)}
onclick={(e) => !isDraft && handleEventClick(event, e)}
role="button"
@ -599,7 +619,9 @@
overflow: hidden;
touch-action: none;
user-select: none;
transition: box-shadow 150ms ease, opacity 150ms ease;
transition:
box-shadow 150ms ease,
opacity 150ms ease;
}
.event-card:hover {
@ -629,7 +651,8 @@
}
@keyframes pulse-outline {
0%, 100% {
0%,
100% {
outline-color: hsl(var(--color-primary));
}
50% {

View file

@ -45,16 +45,18 @@
// Filter weekends if option is active
let calendarDays = $derived(
settingsStore.showOnlyWeekdays ? allCalendarDays.filter((day) => !isWeekend(day)) : allCalendarDays
settingsStore.showOnlyWeekdays
? allCalendarDays.filter((day) => !isWeekend(day))
: allCalendarDays
);
// Week day headers - depends on week start setting
const weekDaysFromMonday = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
const weekDaysFromSunday = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];
let allWeekDays = $derived(settingsStore.weekStartsOn === 1 ? weekDaysFromMonday : weekDaysFromSunday);
let weekDays = $derived(
settingsStore.showOnlyWeekdays ? allWeekDays.slice(0, 5) : allWeekDays
let allWeekDays = $derived(
settingsStore.weekStartsOn === 1 ? weekDaysFromMonday : weekDaysFromSunday
);
let weekDays = $derived(settingsStore.showOnlyWeekdays ? allWeekDays.slice(0, 5) : allWeekDays);
// Number of columns for grid
let columnCount = $derived(settingsStore.showOnlyWeekdays ? 5 : 7);
@ -94,7 +96,7 @@
return {
destroy() {
setDayCellRef(day, null);
}
},
};
}
@ -141,8 +143,14 @@
const targetDay = getDayFromPoint(e.clientX, e.clientY);
if (targetDay) {
const start = typeof draggedEvent.startTime === 'string' ? new Date(draggedEvent.startTime) : draggedEvent.startTime;
const end = typeof draggedEvent.endTime === 'string' ? new Date(draggedEvent.endTime) : draggedEvent.endTime;
const start =
typeof draggedEvent.startTime === 'string'
? new Date(draggedEvent.startTime)
: draggedEvent.startTime;
const end =
typeof draggedEvent.endTime === 'string'
? new Date(draggedEvent.endTime)
: draggedEvent.endTime;
const duration = differenceInMinutes(end, start);
// Keep the same time, change the date
@ -280,14 +288,13 @@
: event.startTime,
'HH:mm'
)}-{format(
typeof event.endTime === 'string'
? new Date(event.endTime)
: event.endTime,
typeof event.endTime === 'string' ? new Date(event.endTime) : event.endTime,
'HH:mm'
)}</span
>
{/if}
<span class="event-title">{event.title || (isDraft ? '(Neuer Termin)' : '')}</span>
<span class="event-title">{event.title || (isDraft ? '(Neuer Termin)' : '')}</span
>
</div>
{/each}
@ -308,7 +315,6 @@
.month-view {
display: flex;
flex-direction: column;
}
.weekday-headers {
@ -408,7 +414,10 @@
text-overflow: ellipsis;
touch-action: none;
user-select: none;
transition: transform 150ms ease, box-shadow 150ms ease, opacity 150ms ease;
transition:
transform 150ms ease,
box-shadow 150ms ease,
opacity 150ms ease;
}
.event-pill:hover {
@ -429,7 +438,8 @@
}
@keyframes pulse-outline {
0%, 100% {
0%,
100% {
outline-color: hsl(var(--color-primary));
}
50% {

View file

@ -32,7 +32,9 @@
// Get date-fns locale based on current app locale
const dateLocales = { de, en: enUS, fr, es, it };
let currentDateLocale = $derived(dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de);
let currentDateLocale = $derived(
dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de
);
// Generate days based on view range, optionally filtering weekends
let allDays = $derived(
@ -55,7 +57,9 @@
);
// Calculate visible hours range for positioning
let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0);
let firstVisibleHour = $derived(
settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0
);
let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24);
let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour);
@ -125,16 +129,16 @@
// Split all-day events by display mode
function getHeaderAllDayEventsForDay(day: Date) {
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'header');
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'header');
}
function getBlockAllDayEventsForDay(day: Date) {
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'block');
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'block');
}
// Check if there are any all-day events to show in header
let hasAnyHeaderAllDayEvents = $derived(
days.some(day => getHeaderAllDayEventsForDay(day).length > 0)
days.some((day) => getHeaderAllDayEventsForDay(day).length > 0)
);
function getEventStyle(event: any) {
@ -162,7 +166,9 @@
if (isDragging || isResizing || hasMoved) {
e.preventDefault();
e.stopPropagation();
setTimeout(() => { hasMoved = false; }, 100);
setTimeout(() => {
hasMoved = false;
}, 100);
return;
}
goto(`/?event=${event.id}`);
@ -248,7 +254,10 @@
const newMinutes = getMinutesFromY(e.clientY) - dragOffsetMinutes;
// Clamp to valid range (firstVisibleHour to lastVisibleHour)
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(lastVisibleHour * 60 - 15, newMinutes));
const clampedMinutes = Math.max(
firstVisibleHour * 60,
Math.min(lastVisibleHour * 60 - 15, newMinutes)
);
// Update preview
dragPreviewTop = minutesToPercent(clampedMinutes);
@ -268,8 +277,14 @@
return;
}
const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime;
const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime;
const start =
typeof draggedEvent.startTime === 'string'
? parseISO(draggedEvent.startTime)
: draggedEvent.startTime;
const end =
typeof draggedEvent.endTime === 'string'
? parseISO(draggedEvent.endTime)
: draggedEvent.endTime;
const duration = differenceInMinutes(end, start);
// Calculate new start time
@ -336,17 +351,24 @@
hasMoved = true;
const currentMinutes = getMinutesFromY(e.clientY);
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalStartMinutes =
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
if (resizeEdge === 'bottom') {
// Resize from bottom - change end time
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
const newEndMinutes = Math.max(
originalStartMinutes + 15,
Math.min(lastVisibleHour * 60, currentMinutes)
);
const newDuration = newEndMinutes - originalStartMinutes;
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
} else {
// Resize from top - change start time
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
const newStartMinutes = Math.max(
firstVisibleHour * 60,
Math.min(originalEndMinutes - 15, currentMinutes)
);
const newDuration = originalEndMinutes - newStartMinutes;
resizePreviewTop = minutesToPercent(newStartMinutes);
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
@ -367,20 +389,27 @@
}
const currentMinutes = getMinutesFromY(e.clientY);
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalStartMinutes =
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
let newStart = resizeOriginalStart;
let newEnd = resizeOriginalEnd;
if (resizeEdge === 'bottom') {
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
const newEndMinutes = Math.max(
originalStartMinutes + 15,
Math.min(lastVisibleHour * 60, currentMinutes)
);
const newHours = Math.floor(newEndMinutes / 60);
const newMins = newEndMinutes % 60;
newEnd = setHours(new Date(resizeOriginalEnd), newHours);
newEnd = setMinutes(newEnd, newMins);
} else {
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
const newStartMinutes = Math.max(
firstVisibleHour * 60,
Math.min(originalEndMinutes - 15, currentMinutes)
);
const newHours = Math.floor(newStartMinutes / 60);
const newMins = newStartMinutes % 60;
newStart = setHours(new Date(resizeOriginalStart), newHours);
@ -434,7 +463,11 @@
});
</script>
<div class="multi-day-view" class:compact={columnClass === 'compact'} class:very-compact={columnClass === 'very-compact'}>
<div
class="multi-day-view"
class:compact={columnClass === 'compact'}
class:very-compact={columnClass === 'very-compact'}
>
<!-- All-day events row (only shown when there are header-mode all-day events) -->
{#if hasAnyHeaderAllDayEvents}
<div class="all-day-row">
@ -461,7 +494,9 @@
<div class="time-gutter"></div>
{#each days as day}
<div class="day-header" class:today={isToday(day)}>
<span class="day-name">{format(day, columnClass === 'very-compact' ? 'EEEEE' : 'EEE', { locale: de })}</span>
<span class="day-name"
>{format(day, columnClass === 'very-compact' ? 'EEEEE' : 'EEE', { locale: de })}</span
>
<span class="day-number" class:today={isToday(day)}>{format(day, 'd')}</span>
</div>
{/each}
@ -553,10 +588,12 @@
{/each}
<!-- Drag preview ghost (for cross-day dragging) -->
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some(e => e.id === draggedEvent.id)}
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some((e) => e.id === draggedEvent.id)}
<div
class="event-card drag-ghost"
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(draggedEvent.calendarId)};"
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(
draggedEvent.calendarId
)};"
>
{#if columnClass !== 'very-compact'}
<span class="event-time">{formatEventTime(draggedEvent.startTime)}</span>
@ -795,7 +832,9 @@
padding: 2px 4px;
border-radius: var(--radius-sm);
overflow: hidden;
transition: box-shadow 0.15s ease, opacity 0.15s ease;
transition:
box-shadow 0.15s ease,
opacity 0.15s ease;
touch-action: none;
user-select: none;
}
@ -826,7 +865,8 @@
}
@keyframes pulse-outline {
0%, 100% {
0%,
100% {
outline-color: hsl(var(--color-primary));
}
50% {

View file

@ -33,7 +33,9 @@
// Get date-fns locale based on current app locale
const dateLocales = { de, en: enUS, fr, es, it };
let currentDateLocale = $derived(dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de);
let currentDateLocale = $derived(
dateLocales[$locale?.substring(0, 2) as keyof typeof dateLocales] || de
);
// Generate days of the week, optionally filtering weekends
let allDays = $derived(
@ -61,7 +63,9 @@
);
// Calculate visible hours range for positioning
let firstVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0);
let firstVisibleHour = $derived(
settingsStore.filterHoursEnabled ? settingsStore.dayStartHour : 0
);
let lastVisibleHour = $derived(settingsStore.filterHoursEnabled ? settingsStore.dayEndHour : 24);
let totalVisibleHours = $derived(lastVisibleHour - firstVisibleHour);
@ -124,16 +128,16 @@
// Split all-day events by display mode
function getHeaderAllDayEventsForDay(day: Date) {
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'header');
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'header');
}
function getBlockAllDayEventsForDay(day: Date) {
return getAllDayEventsForDay(day).filter(e => getEventDisplayMode(e) === 'block');
return getAllDayEventsForDay(day).filter((e) => getEventDisplayMode(e) === 'block');
}
// Check if there are any all-day events to show in header
let hasAnyHeaderAllDayEvents = $derived(
days.some(day => getHeaderAllDayEventsForDay(day).length > 0)
days.some((day) => getHeaderAllDayEventsForDay(day).length > 0)
);
function getEventStyle(event: any) {
@ -162,7 +166,9 @@
e.preventDefault();
e.stopPropagation();
// Reset hasMoved after a short delay to allow for the next clean click
setTimeout(() => { hasMoved = false; }, 100);
setTimeout(() => {
hasMoved = false;
}, 100);
return;
}
goto(`/?event=${event.id}`);
@ -248,7 +254,10 @@
const newMinutes = getMinutesFromY(e.clientY) - dragOffsetMinutes;
// Clamp to valid range (firstVisibleHour to lastVisibleHour)
const clampedMinutes = Math.max(firstVisibleHour * 60, Math.min(lastVisibleHour * 60 - 15, newMinutes));
const clampedMinutes = Math.max(
firstVisibleHour * 60,
Math.min(lastVisibleHour * 60 - 15, newMinutes)
);
// Update preview
dragPreviewTop = minutesToPercent(clampedMinutes);
@ -268,8 +277,14 @@
return;
}
const start = typeof draggedEvent.startTime === 'string' ? parseISO(draggedEvent.startTime) : draggedEvent.startTime;
const end = typeof draggedEvent.endTime === 'string' ? parseISO(draggedEvent.endTime) : draggedEvent.endTime;
const start =
typeof draggedEvent.startTime === 'string'
? parseISO(draggedEvent.startTime)
: draggedEvent.startTime;
const end =
typeof draggedEvent.endTime === 'string'
? parseISO(draggedEvent.endTime)
: draggedEvent.endTime;
const duration = differenceInMinutes(end, start);
// Calculate new start time
@ -336,17 +351,24 @@
hasMoved = true;
const currentMinutes = getMinutesFromY(e.clientY);
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalStartMinutes =
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
if (resizeEdge === 'bottom') {
// Resize from bottom - change end time
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
const newEndMinutes = Math.max(
originalStartMinutes + 15,
Math.min(lastVisibleHour * 60, currentMinutes)
);
const newDuration = newEndMinutes - originalStartMinutes;
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
} else {
// Resize from top - change start time
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
const newStartMinutes = Math.max(
firstVisibleHour * 60,
Math.min(originalEndMinutes - 15, currentMinutes)
);
const newDuration = originalEndMinutes - newStartMinutes;
resizePreviewTop = minutesToPercent(newStartMinutes);
resizePreviewHeight = (newDuration / (totalVisibleHours * 60)) * 100;
@ -367,20 +389,27 @@
}
const currentMinutes = getMinutesFromY(e.clientY);
const originalStartMinutes = resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalStartMinutes =
resizeOriginalStart.getHours() * 60 + resizeOriginalStart.getMinutes();
const originalEndMinutes = resizeOriginalEnd.getHours() * 60 + resizeOriginalEnd.getMinutes();
let newStart = resizeOriginalStart;
let newEnd = resizeOriginalEnd;
if (resizeEdge === 'bottom') {
const newEndMinutes = Math.max(originalStartMinutes + 15, Math.min(lastVisibleHour * 60, currentMinutes));
const newEndMinutes = Math.max(
originalStartMinutes + 15,
Math.min(lastVisibleHour * 60, currentMinutes)
);
const newHours = Math.floor(newEndMinutes / 60);
const newMins = newEndMinutes % 60;
newEnd = setHours(new Date(resizeOriginalEnd), newHours);
newEnd = setMinutes(newEnd, newMins);
} else {
const newStartMinutes = Math.max(firstVisibleHour * 60, Math.min(originalEndMinutes - 15, currentMinutes));
const newStartMinutes = Math.max(
firstVisibleHour * 60,
Math.min(originalEndMinutes - 15, currentMinutes)
);
const newHours = Math.floor(newStartMinutes / 60);
const newMins = newStartMinutes % 60;
newStart = setHours(new Date(resizeOriginalStart), newHours);
@ -563,10 +592,12 @@
{/each}
<!-- Drag preview ghost (for cross-day dragging) -->
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some(e => e.id === draggedEvent.id)}
{#if isDragging && draggedEvent && dragTargetDay && isSameDay(day, dragTargetDay) && !getEventsForDay(day).some((e) => e.id === draggedEvent.id)}
<div
class="event-card drag-ghost"
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(draggedEvent.calendarId)};"
style="top: {dragPreviewTop}%; height: {dragPreviewHeight}%; background-color: {calendarsStore.getColor(
draggedEvent.calendarId
)};"
>
<span class="event-time">{formatEventTime(draggedEvent.startTime)}</span>
<span class="event-title">{draggedEvent.title}</span>
@ -764,7 +795,9 @@
cursor: grab;
z-index: 1;
overflow: hidden;
transition: box-shadow 0.15s ease, opacity 0.15s ease;
transition:
box-shadow 0.15s ease,
opacity 0.15s ease;
touch-action: none;
user-select: none;
}
@ -801,7 +834,8 @@
}
@keyframes pulse-outline {
0%, 100% {
0%,
100% {
outline-color: hsl(var(--color-primary));
}
50% {

View file

@ -104,11 +104,9 @@
// Get calendar info for the event
let calendarName = $derived(
event ? calendarsStore.calendars.find(c => c.id === event!.calendarId)?.name : undefined
);
let calendarColor = $derived(
event ? calendarsStore.getColor(event.calendarId) : '#3b82f6'
event ? calendarsStore.calendars.find((c) => c.id === event!.calendarId)?.name : undefined
);
let calendarColor = $derived(event ? calendarsStore.getColor(event.calendarId) : '#3b82f6');
// Format recurrence rule to human readable text
function formatRecurrence(rule: string): string {
@ -120,9 +118,18 @@
const days = rule.match(/BYDAY=([A-Z,]+)/)?.[1];
if (days) {
const dayMap: Record<string, string> = {
MO: 'Mo', TU: 'Di', WE: 'Mi', TH: 'Do', FR: 'Fr', SA: 'Sa', SU: 'So'
MO: 'Mo',
TU: 'Di',
WE: 'Mi',
TH: 'Do',
FR: 'Fr',
SA: 'Sa',
SU: 'So',
};
const translatedDays = days.split(',').map(d => dayMap[d] || d).join(', ');
const translatedDays = days
.split(',')
.map((d) => dayMap[d] || d)
.join(', ');
return `Wöchentlich (${translatedDays})`;
}
}
@ -153,20 +160,35 @@
{#if !isEditing}
<button class="btn btn-ghost" onclick={() => (isEditing = true)}>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
/>
</svg>
Bearbeiten
</button>
<button class="btn btn-ghost text-destructive" onclick={handleDelete}>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
Löschen
</button>
{/if}
<button class="btn btn-ghost btn-close" onclick={onClose} aria-label="Schließen">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
@ -194,7 +216,12 @@
<div class="detail-row">
<span class="detail-icon">
<svg class="w-5 h-5" 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" />
<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>
</span>
<div class="detail-content">
@ -208,7 +235,12 @@
<div class="detail-row">
<span class="detail-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
</span>
<div class="detail-content">
@ -223,8 +255,18 @@
<div class="detail-row">
<span class="detail-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</span>
<div class="detail-content">
@ -257,15 +299,30 @@
<div class="detail-row">
<span class="detail-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
</span>
<div class="detail-content">
<span class="detail-label">Videokonferenz</span>
<a href={event.metadata.conferenceUrl} target="_blank" rel="noopener noreferrer" class="detail-link">
<a
href={event.metadata.conferenceUrl}
target="_blank"
rel="noopener noreferrer"
class="detail-link"
>
Beitreten
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
@ -277,15 +334,30 @@
<div class="detail-row">
<span class="detail-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"
/>
</svg>
</span>
<div class="detail-content">
<span class="detail-label">Link</span>
<a href={event.metadata.url} target="_blank" rel="noopener noreferrer" class="detail-link">
<a
href={event.metadata.url}
target="_blank"
rel="noopener noreferrer"
class="detail-link"
>
{new URL(event.metadata.url).hostname}
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg>
</a>
</div>
@ -297,7 +369,12 @@
<div class="detail-row">
<span class="detail-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h7"
/>
</svg>
</span>
<div class="detail-content">
@ -312,7 +389,12 @@
<div class="detail-row">
<span class="detail-icon">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
/>
</svg>
</span>
<div class="detail-content">
@ -322,8 +404,17 @@
<div class="attendee">
<span class="attendee-name">{attendee.name || attendee.email}</span>
{#if attendee.status}
<span class="attendee-status" class:accepted={attendee.status === 'accepted'} class:declined={attendee.status === 'declined'} class:tentative={attendee.status === 'tentative'}>
{attendee.status === 'accepted' ? '✓' : attendee.status === 'declined' ? '✗' : '?'}
<span
class="attendee-status"
class:accepted={attendee.status === 'accepted'}
class:declined={attendee.status === 'declined'}
class:tentative={attendee.status === 'tentative'}
>
{attendee.status === 'accepted'
? '✓'
: attendee.status === 'declined'
? '✗'
: '?'}
</span>
{/if}
</div>

View file

@ -1,7 +1,12 @@
<script lang="ts">
import { calendarsStore } from '$lib/stores/calendars.svelte';
import { settingsStore } from '$lib/stores/settings.svelte';
import type { CalendarEvent, CreateEventInput, UpdateEventInput, LocationDetails } from '@calendar/shared';
import type {
CalendarEvent,
CreateEventInput,
UpdateEventInput,
LocationDetails,
} from '@calendar/shared';
import { format, addMinutes, parseISO } from 'date-fns';
interface Props {
@ -96,13 +101,16 @@
// Build location details if any field is filled
const locationDetails: LocationDetails | undefined =
(locationStreet.trim() || locationPostalCode.trim() || locationCity.trim() || locationCountry.trim())
locationStreet.trim() ||
locationPostalCode.trim() ||
locationCity.trim() ||
locationCountry.trim()
? {
street: locationStreet.trim() || undefined,
postalCode: locationPostalCode.trim() || undefined,
city: locationCity.trim() || undefined,
country: locationCountry.trim() || undefined,
}
street: locationStreet.trim() || undefined,
postalCode: locationPostalCode.trim() || undefined,
city: locationCity.trim() || undefined,
country: locationCountry.trim() || undefined,
}
: undefined;
// Build metadata
@ -253,9 +261,15 @@
<button
type="button"
class="flex items-center gap-1 text-sm text-primary hover:text-primary/80 transition-colors self-start"
onclick={() => showLocationDetails = !showLocationDetails}
onclick={() => (showLocationDetails = !showLocationDetails)}
>
<svg class="w-4 h-4 transition-transform" class:rotate-90={showLocationDetails} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg
class="w-4 h-4 transition-transform"
class:rotate-90={showLocationDetails}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
{showLocationDetails ? 'Adressdetails ausblenden' : 'Adressdetails hinzufügen'}
@ -331,7 +345,11 @@
>
Abbrechen
</button>
<button type="submit" class="px-4 py-2 rounded-lg font-medium text-primary-foreground bg-primary hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" disabled={submitting || !title.trim() || !calendarId}>
<button
type="submit"
class="px-4 py-2 rounded-lg font-medium text-primary-foreground bg-primary hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
disabled={submitting || !title.trim() || !calendarId}
>
{mode === 'create' ? 'Erstellen' : 'Speichern'}
</button>
</div>

View file

@ -280,7 +280,10 @@
// Build location details if any field is filled
const locationDetails: LocationDetails | undefined =
locationStreet.trim() || locationPostalCode.trim() || locationCity.trim() || locationCountry.trim()
locationStreet.trim() ||
locationPostalCode.trim() ||
locationCity.trim() ||
locationCountry.trim()
? {
street: locationStreet.trim() || undefined,
postalCode: locationPostalCode.trim() || undefined,
@ -337,235 +340,313 @@
aria-modal="true"
aria-label="Termin erstellen"
>
<form onsubmit={handleSubmit}>
<!-- Header -->
<div class="overlay-header">
<span class="header-title">Neuer Termin</span>
<button type="button" class="close-btn" onclick={onClose} aria-label="Schließen">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<form onsubmit={handleSubmit}>
<!-- Header -->
<div class="overlay-header">
<span class="header-title">Neuer Termin</span>
<button type="button" class="close-btn" onclick={onClose} aria-label="Schließen">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
<!-- Scrollable content -->
<div class="overlay-content">
<!-- Title input -->
<div class="form-group">
<input
type="text"
class="title-input"
value={title}
oninput={handleTitleChange}
bind:this={titleInputRef}
placeholder="Titel hinzufügen"
/>
</div>
<!-- Scrollable content -->
<div class="overlay-content">
<!-- Title input -->
<div class="form-group">
<!-- Time display under title -->
<div class="time-display">
<svg class="icon" 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>
<span>
{format(draftStart(), 'EEEE, d. MMMM yyyy', { locale: de })}
{#if !isAllDay}
· {displayStartTime} {displayEndTime}
{:else}
· Ganztägig
{/if}
</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">
<label class="field-label">Kalender</label>
<select class="field-select" value={calendarId} onchange={handleCalendarChange}>
{#each calendarsStore.calendars as cal}
<option value={cal.id}>{cal.name}</option>
{/each}
</select>
</div>
</div>
<!-- All day toggle -->
<div class="form-row clickable" onclick={handleAllDayToggle}>
<div class="row-icon">
<svg class="icon" 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>
</div>
<div class="row-content toggle-content">
<span>Ganztägig</span>
<input
type="text"
class="title-input"
value={title}
oninput={handleTitleChange}
bind:this={titleInputRef}
placeholder="Titel hinzufügen"
type="checkbox"
checked={isAllDay}
class="toggle-checkbox"
onclick={(e) => e.stopPropagation()}
onchange={handleAllDayToggle}
/>
</div>
</div>
<!-- Time display under title -->
<div class="time-display">
<svg class="icon" 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>
<span>
{format(draftStart(), 'EEEE, d. MMMM yyyy', { locale: de })}
{#if !isAllDay}
· {displayStartTime} {displayEndTime}
{:else}
· Ganztägig
{/if}
</span>
</div>
<!-- Calendar select -->
<div class="form-row">
<div class="row-icon">
<div class="calendar-dot" style="background-color: {calendarsStore.getColor(calendarId)}"></div>
</div>
<!-- All-day display mode -->
{#if isAllDay}
<div class="form-row sub-row">
<div class="row-icon"></div>
<div class="row-content">
<label class="field-label">Kalender</label>
<select class="field-select" value={calendarId} onchange={handleCalendarChange}>
{#each calendarsStore.calendars as cal}
<option value={cal.id}>{cal.name}</option>
{/each}
<label class="field-label">Anzeigeart</label>
<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>
</div>
</div>
{/if}
<!-- All day toggle -->
<div class="form-row clickable" onclick={handleAllDayToggle}>
<div class="row-icon">
<svg class="icon" 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>
</div>
<div class="row-content toggle-content">
<span>Ganztägig</span>
<input type="checkbox" checked={isAllDay} class="toggle-checkbox" onclick={(e) => e.stopPropagation()} onchange={handleAllDayToggle} />
</div>
</div>
<!-- All-day display mode -->
{#if isAllDay}
<div class="form-row sub-row">
<div class="row-icon"></div>
<div class="row-content">
<label class="field-label">Anzeigeart</label>
<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>
</div>
</div>
{/if}
<!-- Start date/time -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" 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>
</div>
<div class="row-content datetime-row">
<div class="datetime-field">
<label class="field-label">Beginn</label>
<input type="date" class="field-input" value={startDateStr} onchange={handleStartDateChange} />
</div>
{#if !isAllDay}
<div class="datetime-field time-field">
<label class="field-label">Uhrzeit</label>
<input type="time" class="field-input" value={startTimeStr} onchange={handleStartTimeChange} />
</div>
{/if}
</div>
</div>
<!-- End date/time -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" 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>
</div>
<div class="row-content datetime-row">
<div class="datetime-field">
<label class="field-label">Ende</label>
<input type="date" class="field-input" value={endDateStr} onchange={handleEndDateChange} />
</div>
{#if !isAllDay}
<div class="datetime-field time-field">
<label class="field-label">Uhrzeit</label>
<input type="time" class="field-input" value={endTimeStr} onchange={handleEndTimeChange} />
</div>
{/if}
</div>
</div>
<!-- Location -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<div class="row-content">
<input
type="text"
class="field-input full"
bind:value={location}
placeholder="Ort hinzufügen"
<!-- Start date/time -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" 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"
/>
<!-- Toggle for address details -->
<button
type="button"
class="address-toggle"
onclick={() => (showLocationDetails = !showLocationDetails)}
>
<svg class="toggle-chevron" class:rotated={showLocationDetails} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
{showLocationDetails ? 'Adressdetails ausblenden' : 'Adressdetails hinzufügen'}
</button>
</div>
</svg>
</div>
<div class="row-content datetime-row">
<div class="datetime-field">
<label class="field-label">Beginn</label>
<input
type="date"
class="field-input"
value={startDateStr}
onchange={handleStartDateChange}
/>
</div>
{#if !isAllDay}
<div class="datetime-field time-field">
<label class="field-label">Uhrzeit</label>
<input
type="time"
class="field-input"
value={startTimeStr}
onchange={handleStartTimeChange}
/>
</div>
{/if}
</div>
</div>
<!-- Location details (expandable) -->
{#if showLocationDetails}
<div class="form-row sub-row">
<div class="row-icon"></div>
<div class="row-content address-details-form">
<div class="address-field">
<label class="field-label">Straße</label>
<!-- End date/time -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" 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>
</div>
<div class="row-content datetime-row">
<div class="datetime-field">
<label class="field-label">Ende</label>
<input
type="date"
class="field-input"
value={endDateStr}
onchange={handleEndDateChange}
/>
</div>
{#if !isAllDay}
<div class="datetime-field time-field">
<label class="field-label">Uhrzeit</label>
<input
type="time"
class="field-input"
value={endTimeStr}
onchange={handleEndTimeChange}
/>
</div>
{/if}
</div>
</div>
<!-- Location -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
</div>
<div class="row-content">
<input
type="text"
class="field-input full"
bind:value={location}
placeholder="Ort hinzufügen"
/>
<!-- Toggle for address details -->
<button
type="button"
class="address-toggle"
onclick={() => (showLocationDetails = !showLocationDetails)}
>
<svg
class="toggle-chevron"
class:rotated={showLocationDetails}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 5l7 7-7 7"
/>
</svg>
{showLocationDetails ? 'Adressdetails ausblenden' : 'Adressdetails hinzufügen'}
</button>
</div>
</div>
<!-- Location details (expandable) -->
{#if showLocationDetails}
<div class="form-row sub-row">
<div class="row-icon"></div>
<div class="row-content address-details-form">
<div class="address-field">
<label class="field-label">Straße</label>
<input
type="text"
class="field-input"
bind:value={locationStreet}
placeholder="Musterstraße 123"
/>
</div>
<div class="address-row">
<div class="address-field postal">
<label class="field-label">PLZ</label>
<input
type="text"
class="field-input"
bind:value={locationStreet}
placeholder="Musterstraße 123"
bind:value={locationPostalCode}
placeholder="12345"
/>
</div>
<div class="address-row">
<div class="address-field postal">
<label class="field-label">PLZ</label>
<input
type="text"
class="field-input"
bind:value={locationPostalCode}
placeholder="12345"
/>
</div>
<div class="address-field city">
<label class="field-label">Stadt</label>
<input
type="text"
class="field-input"
bind:value={locationCity}
placeholder="Musterstadt"
/>
</div>
</div>
<div class="address-field">
<label class="field-label">Land</label>
<div class="address-field city">
<label class="field-label">Stadt</label>
<input
type="text"
class="field-input"
bind:value={locationCountry}
placeholder="Deutschland"
bind:value={locationCity}
placeholder="Musterstadt"
/>
</div>
</div>
</div>
{/if}
<!-- Description -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
</svg>
</div>
<div class="row-content">
<textarea
class="field-textarea"
bind:value={description}
placeholder="Beschreibung hinzufügen"
rows="3"
></textarea>
<div class="address-field">
<label class="field-label">Land</label>
<input
type="text"
class="field-input"
bind:value={locationCountry}
placeholder="Deutschland"
/>
</div>
</div>
</div>
</div>
{/if}
<!-- Actions (sticky footer) -->
<div class="overlay-actions">
<button type="button" class="btn-ghost" onclick={onClose}>
Abbrechen
</button>
<button type="submit" class="btn-primary" disabled={submitting || !title.trim()}>
{submitting ? 'Speichern...' : 'Speichern'}
</button>
<!-- Description -->
<div class="form-row">
<div class="row-icon">
<svg class="icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h7"
/>
</svg>
</div>
<div class="row-content">
<textarea
class="field-textarea"
bind:value={description}
placeholder="Beschreibung hinzufügen"
rows="3"
></textarea>
</div>
</div>
</form>
</div>
<!-- Actions (sticky footer) -->
<div class="overlay-actions">
<button type="button" class="btn-ghost" onclick={onClose}> Abbrechen </button>
<button type="submit" class="btn-primary" disabled={submitting || !title.trim()}>
{submitting ? 'Speichern...' : 'Speichern'}
</button>
</div>
</form>
</div>
<style>
@ -576,7 +657,9 @@
background: hsl(var(--color-surface));
border: 1px solid hsl(var(--color-border));
border-radius: var(--radius-lg);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1);
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.2),
0 4px 16px rgba(0, 0, 0, 0.1);
z-index: 1001;
display: flex;
flex-direction: column;

View file

@ -84,7 +84,9 @@ export const eventsStore = {
// Include draft event if it exists and is on this day
if (includeDraft && draftEvent) {
const draftStart =
typeof draftEvent.startTime === 'string' ? parseISO(draftEvent.startTime) : draftEvent.startTime;
typeof draftEvent.startTime === 'string'
? parseISO(draftEvent.startTime)
: draftEvent.startTime;
if (isSameDay(date, draftStart)) {
result.push(draftEvent);
}

View file

@ -106,7 +106,10 @@ export const viewStore = {
// Load view type from settings or localStorage (for backwards compatibility)
const savedView = localStorage.getItem('calendar-view-type');
if (savedView && ['day', '5day', 'week', '10day', '14day', 'month', 'year', 'agenda'].includes(savedView)) {
if (
savedView &&
['day', '5day', 'week', '10day', '14day', 'month', 'year', 'agenda'].includes(savedView)
) {
viewType = savedView as CalendarViewType;
} else {
// Use default view from settings

View file

@ -225,7 +225,10 @@
class:sidebar-mode={isSidebarMode && !isCollapsed}
class:floating-mode={!isSidebarMode && !isCollapsed}
>
<div class="content-wrapper" class:calendar-expanded={settingsStore.sidebarCollapsed && $page.url.pathname === '/'}>
<div
class="content-wrapper"
class:calendar-expanded={settingsStore.sidebarCollapsed && $page.url.pathname === '/'}
>
{@render children()}
</div>
</main>

View file

@ -107,7 +107,12 @@
title="Sidebar ausblenden"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 19l-7-7 7-7m8 14l-7-7 7-7"
/>
</svg>
</button>
@ -135,16 +140,22 @@
title="Sidebar einblenden"
>
<svg class="w-5 h-5" 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" />
<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>
</button>
<button
class="fab-new-event"
onclick={handleNewEvent}
title="Neuer Termin"
>
<button class="fab-new-event" onclick={handleNewEvent} title="Neuer Termin">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 4v16m8-8H4"
/>
</svg>
</button>
</div>
@ -186,10 +197,7 @@
<!-- Event Detail Modal -->
{#if modalEventId}
<EventDetailModal
eventId={modalEventId}
onClose={handleEventModalClose}
/>
<EventDetailModal eventId={modalEventId} onClose={handleEventModalClose} />
{/if}
</div>

View file

@ -18,7 +18,8 @@
const groups: Map<string, typeof currentEvents> = new Map();
for (const event of currentEvents) {
const start = typeof event.startTime === 'string' ? parseISO(event.startTime) : event.startTime;
const start =
typeof event.startTime === 'string' ? parseISO(event.startTime) : event.startTime;
const dateKey = format(start, 'yyyy-MM-dd');
if (!groups.has(dateKey)) {

View file

@ -5,7 +5,12 @@
import { authStore } from '$lib/stores/auth.svelte';
import { theme } from '$lib/stores/theme';
import { userSettings } from '$lib/stores/user-settings.svelte';
import { settingsStore, type WeekStartDay, type TimeFormat, type AllDayDisplayMode } from '$lib/stores/settings.svelte';
import {
settingsStore,
type WeekStartDay,
type TimeFormat,
type AllDayDisplayMode,
} from '$lib/stores/settings.svelte';
import { calendarsStore } from '$lib/stores/calendars.svelte';
import { toast } from '$lib/stores/toast';
import { setLocale, supportedLocales, type SupportedLocale } from '$lib/i18n';
@ -177,7 +182,11 @@
<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)}>
<button
type="button"
class="btn btn-ghost"
onclick={() => (showNewCalendarForm = false)}
>
Abbrechen
</button>
<button type="submit" class="btn btn-primary" disabled={!newCalendarName.trim()}>
@ -206,7 +215,11 @@
<input type="color" name="color" class="color-input" value={calendar.color} />
</div>
<div class="calendar-form-actions">
<button type="button" class="btn btn-ghost" onclick={() => (editingCalendar = null)}>
<button
type="button"
class="btn btn-ghost"
onclick={() => (editingCalendar = null)}
>
Abbrechen
</button>
<button type="submit" class="btn btn-primary"> Speichern </button>
@ -435,11 +448,14 @@
<input
type="checkbox"
checked={settingsStore.filterHoursEnabled}
onchange={() => settingsStore.set('filterHoursEnabled', !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>
<span class="setting-description"
>Nur bestimmte Stunden in der Tages-/Wochenansicht anzeigen</span
>
</div>
</label>
</div>
@ -448,7 +464,9 @@
<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>
<span class="setting-description"
>Zeitbereich der in der Kalenderansicht angezeigt wird</span
>
</div>
<div class="hour-range-inputs">
<div class="hour-input-group">
@ -526,7 +544,9 @@
>
{#each durationOptions as duration}
<option value={duration}>
{duration >= 60 ? `${duration / 60} Stunde${duration > 60 ? 'n' : ''}` : `${duration} Minuten`}
{duration >= 60
? `${duration / 60} Stunde${duration > 60 ? 'n' : ''}`
: `${duration} Minuten`}
</option>
{/each}
</select>

View file

@ -43,8 +43,7 @@
<div class="flex gap-2">
{#each ['light', 'dark', 'system'] as mode}
<button
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode ===
mode
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode === mode
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => theme.setMode(mode as 'light' | 'dark' | 'system')}

View file

@ -33,12 +33,16 @@
// Navigation position handler
async function handleNavPositionChange(position: NavPosition) {
await userSettings.updateGlobal({ nav: { ...userSettings.globalSettings.nav, desktopPosition: position } });
await userSettings.updateGlobal({
nav: { ...userSettings.globalSettings.nav, desktopPosition: position },
});
}
// Sidebar collapsed handler
async function handleSidebarChange(collapsed: boolean) {
await userSettings.updateGlobal({ nav: { ...userSettings.globalSettings.nav, sidebarCollapsed: collapsed } });
await userSettings.updateGlobal({
nav: { ...userSettings.globalSettings.nav, sidebarCollapsed: collapsed },
});
}
// Theme mode handler
@ -48,7 +52,9 @@
// Color scheme handler
async function handleColorSchemeChange(colorScheme: string) {
await userSettings.updateGlobal({ theme: { ...userSettings.globalSettings.theme, colorScheme } });
await userSettings.updateGlobal({
theme: { ...userSettings.globalSettings.theme, colorScheme },
});
}
// Locale handler
@ -202,11 +208,14 @@
<div class="flex items-center justify-between py-3 border-b border-border">
<div>
<p class="font-medium">Position (Desktop)</p>
<p class="text-sm text-muted-foreground">Position der Navigation auf großen Bildschirmen</p>
<p class="text-sm text-muted-foreground">
Position der Navigation auf großen Bildschirmen
</p>
</div>
<div class="flex gap-2">
<button
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.nav.desktopPosition === 'top'
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.nav.desktopPosition === 'top'
? 'bg-primary text-primary-foreground'
: 'bg-surface-hover hover:bg-surface-hover/80'}"
onclick={() => handleNavPositionChange('top')}
@ -214,7 +223,8 @@
Oben
</button>
<button
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.nav.desktopPosition === 'bottom'
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.nav.desktopPosition === 'bottom'
? 'bg-primary text-primary-foreground'
: 'bg-surface-hover hover:bg-surface-hover/80'}"
onclick={() => handleNavPositionChange('bottom')}
@ -230,13 +240,16 @@
<p class="text-sm text-muted-foreground">Standard-Zustand der Sidebar</p>
</div>
<button
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {userSettings.globalSettings.nav.sidebarCollapsed
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {userSettings
.globalSettings.nav.sidebarCollapsed
? 'bg-primary'
: 'bg-gray-200 dark:bg-gray-700'}"
onclick={() => handleSidebarChange(!userSettings.globalSettings.nav.sidebarCollapsed)}
onclick={() =>
handleSidebarChange(!userSettings.globalSettings.nav.sidebarCollapsed)}
>
<span
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {userSettings.globalSettings.nav.sidebarCollapsed
class="inline-block h-4 w-4 transform rounded-full bg-white transition-transform {userSettings
.globalSettings.nav.sidebarCollapsed
? 'translate-x-6'
: 'translate-x-1'}"
></span>
@ -257,7 +270,8 @@
</div>
<div class="flex gap-2">
<button
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.theme.mode === 'light'
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.theme.mode === 'light'
? 'bg-primary text-primary-foreground'
: 'bg-surface-hover hover:bg-surface-hover/80'}"
onclick={() => handleThemeModeChange('light')}
@ -265,7 +279,8 @@
Hell
</button>
<button
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.theme.mode === 'dark'
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.theme.mode === 'dark'
? 'bg-primary text-primary-foreground'
: 'bg-surface-hover hover:bg-surface-hover/80'}"
onclick={() => handleThemeModeChange('dark')}
@ -273,7 +288,8 @@
Dunkel
</button>
<button
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.theme.mode === 'system'
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.theme.mode === 'system'
? 'bg-primary text-primary-foreground'
: 'bg-surface-hover hover:bg-surface-hover/80'}"
onclick={() => handleThemeModeChange('system')}
@ -289,14 +305,10 @@
<p class="text-sm text-muted-foreground">Akzentfarbe der Benutzeroberfläche</p>
</div>
<div class="flex gap-2">
{#each [
{ id: 'ocean', label: 'Ozean', color: 'bg-blue-500' },
{ id: 'forest', label: 'Wald', color: 'bg-green-500' },
{ id: 'sunset', label: 'Sonnenuntergang', color: 'bg-orange-500' },
{ id: 'lavender', label: 'Lavendel', color: 'bg-purple-500' }
] as scheme}
{#each [{ id: 'ocean', label: 'Ozean', color: 'bg-blue-500' }, { id: 'forest', label: 'Wald', color: 'bg-green-500' }, { id: 'sunset', label: 'Sonnenuntergang', color: 'bg-orange-500' }, { id: 'lavender', label: 'Lavendel', color: 'bg-purple-500' }] as scheme}
<button
class="w-8 h-8 rounded-full transition-all {scheme.color} {userSettings.globalSettings.theme.colorScheme === scheme.id
class="w-8 h-8 rounded-full transition-all {scheme.color} {userSettings
.globalSettings.theme.colorScheme === scheme.id
? 'ring-2 ring-offset-2 ring-primary'
: 'hover:scale-110'}"
onclick={() => handleColorSchemeChange(scheme.id)}
@ -319,15 +331,10 @@
<p class="text-sm text-muted-foreground">Sprache der Benutzeroberfläche</p>
</div>
<div class="flex gap-2">
{#each [
{ id: 'de', label: 'DE' },
{ id: 'en', label: 'EN' },
{ id: 'fr', label: 'FR' },
{ id: 'es', label: 'ES' },
{ id: 'it', label: 'IT' }
] as lang}
{#each [{ id: 'de', label: 'DE' }, { id: 'en', label: 'EN' }, { id: 'fr', label: 'FR' }, { id: 'es', label: 'ES' }, { id: 'it', label: 'IT' }] as lang}
<button
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings.globalSettings.locale === lang.id
class="px-3 py-2 text-sm font-medium rounded-lg transition-colors {userSettings
.globalSettings.locale === lang.id
? 'bg-primary text-primary-foreground'
: 'bg-surface-hover hover:bg-surface-hover/80'}"
onclick={() => handleLocaleChange(lang.id)}
@ -342,7 +349,9 @@
{#if userSettings.syncing}
<div class="mt-4 flex items-center gap-2 text-sm text-muted-foreground">
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
<div
class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"
></div>
<span>Einstellungen werden synchronisiert...</span>
</div>
{/if}

View file

@ -43,8 +43,7 @@
<div class="flex gap-2">
{#each ['light', 'dark', 'system'] as mode}
<button
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode ===
mode
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode === mode
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => theme.setMode(mode as 'light' | 'dark' | 'system')}

View file

@ -43,8 +43,7 @@
<div class="flex gap-2">
{#each ['light', 'dark', 'system'] as mode}
<button
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode ===
mode
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode === mode
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => theme.setMode(mode as 'light' | 'dark' | 'system')}

View file

@ -43,8 +43,7 @@
<div class="flex gap-2">
{#each ['light', 'dark', 'system'] as mode}
<button
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode ===
mode
class="px-4 py-2 text-sm font-medium rounded-lg transition-colors {theme.mode === mode
? 'bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]'
: 'bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 text-[hsl(var(--foreground))]'}"
onclick={() => theme.setMode(mode as 'light' | 'dark' | 'system')}

View file

@ -262,8 +262,7 @@
mic: 'M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z',
calendar:
'M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z',
folder:
'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z',
folder: 'M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z',
archive: 'M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4',
upload: 'M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12',
music:
@ -477,13 +476,20 @@
<!-- Contrast Toggle -->
<button
type="button"
onclick={() => onA11yContrastChange?.(a11yContrast === 'high' ? 'normal' : 'high')}
onclick={() =>
onA11yContrastChange?.(a11yContrast === 'high' ? 'normal' : 'high')}
class="a11y-btn"
class:active={a11yContrast === 'high'}
title="Hoher Kontrast"
aria-pressed={a11yContrast === 'high'}
>
<svg class="a11y-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg
class="a11y-icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 2v20M12 2a10 10 0 0 1 0 20" fill="currentColor" />
</svg>
@ -497,7 +503,13 @@
title="Animationen reduzieren"
aria-pressed={a11yReduceMotion}
>
<svg class="a11y-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<svg
class="a11y-icon"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
{#if a11yReduceMotion}
<rect x="6" y="4" width="4" height="16" rx="1" />
<rect x="14" y="4" width="4" height="16" rx="1" />
@ -705,7 +717,12 @@
<!-- FAB for collapsed state -->
{#if isCollapsed}
<button onclick={expandNav} class="nav-fab glass-pill" class:desktop-bottom={desktopPosition === 'bottom'} title="Expand navigation">
<button
onclick={expandNav}
class="nav-fab glass-pill"
class:desktop-bottom={desktopPosition === 'bottom'}
title="Expand navigation"
>
<svg class="pill-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"

View file

@ -102,7 +102,9 @@
{#if showNavigation}
<!-- Navigation Settings -->
<div class="space-y-4">
<h3 class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider">
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Navigation
</h3>
@ -135,10 +137,14 @@
</div>
</div>
<div class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]">
<div
class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]"
>
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Sidebar eingeklappt</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Standard-Zustand der Sidebar</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Standard-Zustand der Sidebar
</p>
</div>
<button
class="relative inline-flex h-6 w-11 items-center rounded-full transition-colors {userSettings
@ -161,15 +167,21 @@
{#if showTheme}
<!-- Theme Settings -->
<div class="space-y-4 {showNavigation ? 'pt-4 border-t border-[hsl(var(--border))]' : ''}">
<h3 class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider">
<div
class="space-y-4 {showNavigation ? 'pt-4 border-t border-[hsl(var(--border))]' : ''}"
>
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Erscheinungsbild
</h3>
<div class="flex items-center justify-between py-2">
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Farbmodus</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Hell, Dunkel oder automatisch</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Hell, Dunkel oder automatisch
</p>
</div>
<div class="flex gap-1">
{#each ['light', 'dark', 'system'] as mode}
@ -186,10 +198,14 @@
</div>
</div>
<div class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]">
<div
class="flex items-center justify-between py-2 border-t border-[hsl(var(--border))]"
>
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Farbschema</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Akzentfarbe der Benutzeroberfläche</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Akzentfarbe der Benutzeroberfläche
</p>
</div>
<div class="flex gap-2">
{#each colorSchemes as scheme}
@ -214,14 +230,18 @@
? 'pt-4 border-t border-[hsl(var(--border))]'
: ''}"
>
<h3 class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider">
<h3
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
>
Sprache
</h3>
<div class="flex items-center justify-between py-2">
<div>
<p class="font-medium text-[hsl(var(--foreground))]">Anzeigesprache</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">Sprache der Benutzeroberfläche</p>
<p class="text-sm text-[hsl(var(--muted-foreground))]">
Sprache der Benutzeroberfläche
</p>
</div>
<div class="flex gap-1">
{#each languages as lang}