feat(calendar): add number labels to ViewSwitcher and extended day views

- Replace icons with number labels (1, 3, 5, 7, 10, 14, 30, 60, 90, 365, M, Y, L)
- Add new standard view types: 30day, 60day, 90day, 365day
- Add 3day view as standard option
- Add custom day range input (1-365 days) in context menu
- Update PillTabGroup to show labels when no icon is provided
- Change MultiDayView dayCount prop from union to number type
- Add ultra-compact class for views with 14+ days

🤖 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-15 02:54:10 +01:00
parent 1395291b49
commit 3edb65c2c3
9 changed files with 627 additions and 63 deletions

View file

@ -42,7 +42,7 @@
// Props
interface Props {
dayCount: 5 | 10 | 14;
dayCount: number;
/** Optional date override for carousel navigation (uses viewStore.currentDate if not provided) */
date?: Date;
onQuickCreate?: (date: Date, position: { x: number; y: number }) => void;
@ -105,7 +105,8 @@
let columnClass = $derived.by(() => {
if (days.length <= 5) return 'normal';
if (days.length <= 10) return 'compact';
return 'very-compact';
if (days.length <= 14) return 'very-compact';
return 'ultra-compact';
});
// ========== Drag & Drop State ==========
@ -823,6 +824,7 @@
class="multi-day-view"
class:compact={columnClass === 'compact'}
class:very-compact={columnClass === 'very-compact'}
class:ultra-compact={columnClass === 'ultra-compact'}
>
<!-- Sticky header container -->
<div class="sticky-header">
@ -1527,4 +1529,61 @@
.very-compact .overflow-line:hover {
height: 4px;
}
/* Ultra-compact mode for 14+ days */
.ultra-compact .day-header {
padding: 0.0625rem;
}
.ultra-compact .day-name {
font-size: 0.55rem;
}
.ultra-compact .day-number {
font-size: 0.75rem;
width: 20px;
height: 20px;
}
.ultra-compact .time-label {
font-size: 0.55rem;
padding-right: 0.125rem;
}
.ultra-compact .event-card {
left: 0;
right: 0;
padding: 0 1px;
}
.ultra-compact .event-title {
font-size: 0.5rem;
}
.ultra-compact .all-day-event {
padding: 1px 2px;
font-size: 0.55rem;
}
.ultra-compact .all-day-block-event {
left: 0;
right: 0;
padding: 1px 2px;
}
.ultra-compact .all-day-block-event .event-title {
font-size: 0.5rem;
}
.ultra-compact .resize-handle {
height: 4px;
}
.ultra-compact .overflow-line {
height: 1px;
}
.ultra-compact .overflow-line:hover {
height: 3px;
}
</style>

View file

@ -311,6 +311,14 @@
<MultiDayView dayCount={10} date={prevDate} />
{:else if viewStore.viewType === '14day'}
<MultiDayView dayCount={14} date={prevDate} />
{:else if viewStore.viewType === '30day'}
<MultiDayView dayCount={30} date={prevDate} />
{:else if viewStore.viewType === '60day'}
<MultiDayView dayCount={60} date={prevDate} />
{:else if viewStore.viewType === '90day'}
<MultiDayView dayCount={90} date={prevDate} />
{:else if viewStore.viewType === '365day'}
<MultiDayView dayCount={365} date={prevDate} />
{:else if viewStore.viewType === 'custom'}
<MultiDayView dayCount={settingsStore.customDayCount} date={prevDate} />
{:else if viewStore.viewType === 'month'}
@ -336,6 +344,14 @@
<MultiDayView dayCount={10} {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === '14day'}
<MultiDayView dayCount={14} {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === '30day'}
<MultiDayView dayCount={30} {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === '60day'}
<MultiDayView dayCount={60} {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === '90day'}
<MultiDayView dayCount={90} {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === '365day'}
<MultiDayView dayCount={365} {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === 'custom'}
<MultiDayView dayCount={settingsStore.customDayCount} {onQuickCreate} {onEventClick} />
{:else if viewStore.viewType === 'month'}
@ -363,6 +379,14 @@
<MultiDayView dayCount={10} date={nextDate} />
{:else if viewStore.viewType === '14day'}
<MultiDayView dayCount={14} date={nextDate} />
{:else if viewStore.viewType === '30day'}
<MultiDayView dayCount={30} date={nextDate} />
{:else if viewStore.viewType === '60day'}
<MultiDayView dayCount={60} date={nextDate} />
{:else if viewStore.viewType === '90day'}
<MultiDayView dayCount={90} date={nextDate} />
{:else if viewStore.viewType === '365day'}
<MultiDayView dayCount={365} date={nextDate} />
{:else if viewStore.viewType === 'custom'}
<MultiDayView dayCount={settingsStore.customDayCount} date={nextDate} />
{:else if viewStore.viewType === 'month'}

View file

@ -1,38 +1,85 @@
<script lang="ts">
import { ContextMenu, type ContextMenuItem } from '@manacore/shared-ui';
import { Calendar, ListBullets, GridFour, CalendarBlank } from '@manacore/shared-icons';
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
import { settingsStore } from '$lib/stores/settings.svelte';
import { viewStore } from '$lib/stores/view.svelte';
import type { CalendarViewType } from '@calendar/shared';
// Context menu state
let visible = $state(false);
let x = $state(0);
let y = $state(0);
let menuElement = $state<HTMLElement | null>(null);
let adjustedX = $state(0);
let adjustedY = $state(0);
// Custom day count input state
let customDayInput = $state(String(settingsStore.customDayCount));
// View labels
const viewLabels: Record<CalendarViewType, string> = {
day: 'Tag',
day: 'Tag (1)',
'3day': '3 Tage',
'5day': '5 Tage',
week: 'Woche',
week: 'Woche (7)',
'10day': '10 Tage',
'14day': '14 Tage',
'30day': '30 Tage',
'60day': '60 Tage',
'90day': '90 Tage',
'365day': '365 Tage',
month: 'Monat',
year: 'Jahr',
agenda: 'Agenda',
custom: 'Benutzerdefiniert',
};
// All available views
// All available views (ordered)
const allViews: CalendarViewType[] = [
'day',
'3day',
'5day',
'week',
'10day',
'14day',
'30day',
'60day',
'90day',
'365day',
'month',
'year',
'agenda',
'custom',
];
// Adjust position to keep menu within viewport
$effect(() => {
if (visible && menuElement) {
const rect = menuElement.getBoundingClientRect();
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
// Adjust X if menu would overflow right
if (x + rect.width > viewportWidth - 10) {
adjustedX = x - rect.width;
} else {
adjustedX = x;
}
// Adjust Y if menu would overflow bottom
if (y + rect.height > viewportHeight - 10) {
adjustedY = y - rect.height;
} else {
adjustedY = y;
}
}
});
// Sync custom day input when settings change
$effect(() => {
customDayInput = String(settingsStore.customDayCount);
});
function isViewEnabled(view: CalendarViewType): boolean {
return settingsStore.quickViewPillViews.includes(view);
}
@ -53,42 +100,75 @@
}
}
// Build menu items
let menuItems = $derived.by((): ContextMenuItem[] => {
return allViews.map((view) => ({
id: view,
label: viewLabels[view],
icon: getViewIcon(view),
toggle: true,
checked: isViewEnabled(view),
action: () => toggleView(view),
}));
});
function handleCustomDayInputChange(e: Event) {
const target = e.target as HTMLInputElement;
customDayInput = target.value;
}
// Get appropriate icon for view type
function getViewIcon(view: CalendarViewType) {
switch (view) {
case 'day':
case '5day':
case '10day':
case '14day':
return CalendarBlank;
case 'week':
return Calendar;
case 'month':
case 'year':
return GridFour;
case 'agenda':
return ListBullets;
default:
return Calendar;
function applyCustomDays() {
const value = parseInt(customDayInput, 10);
if (isNaN(value) || value < 1 || value > 365) {
// Reset to current value if invalid
customDayInput = String(settingsStore.customDayCount);
return;
}
// Set custom day count
settingsStore.set('customDayCount', value);
customDayInput = String(value);
// Auto-enable 'custom' view if not already
const current = settingsStore.quickViewPillViews;
if (!current.includes('custom')) {
settingsStore.set('quickViewPillViews', [...current, 'custom']);
}
// Switch to custom view
viewStore.setViewType('custom');
// Close the menu
visible = false;
}
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') {
visible = false;
}
}
function handleClose() {
visible = false;
function handleInputKeyDown(e: KeyboardEvent) {
if (e.key === 'Enter') {
e.preventDefault();
applyCustomDays();
}
// Stop propagation to prevent menu from closing
e.stopPropagation();
}
onMount(() => {
// Close on click outside
const handleClickOutside = (e: MouseEvent) => {
if (menuElement && !menuElement.contains(e.target as Node)) {
visible = false;
}
};
// Close on scroll
const handleScroll = () => {
visible = false;
};
window.addEventListener('click', handleClickOutside);
window.addEventListener('scroll', handleScroll, true);
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('click', handleClickOutside);
window.removeEventListener('scroll', handleScroll, true);
window.removeEventListener('keydown', handleKeyDown);
};
});
// Export show function to be called from parent
export function show(clientX: number, clientY: number) {
x = clientX;
@ -101,4 +181,230 @@
}
</script>
<ContextMenu {visible} {x} {y} items={menuItems} onClose={handleClose} />
{#if visible}
<!-- Backdrop to block clicks on elements behind -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="context-menu-backdrop"
onpointerdown={(e) => {
e.preventDefault();
e.stopPropagation();
visible = false;
}}
onclick={(e) => {
e.preventDefault();
e.stopPropagation();
visible = false;
}}
oncontextmenu={(e) => {
e.preventDefault();
e.stopPropagation();
visible = false;
}}
></div>
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
bind:this={menuElement}
class="context-menu"
style="left: {adjustedX}px; top: {adjustedY}px;"
role="menu"
tabindex="-1"
transition:fly={{ duration: 150, y: -8 }}
onclick={(e) => e.stopPropagation()}
oncontextmenu={(e) => e.preventDefault()}
onkeydown={handleKeyDown}
>
<!-- Standard view toggles -->
{#each allViews as view}
<button class="menu-item has-toggle" onclick={() => toggleView(view)} role="menuitem">
<span class="item-toggle" class:checked={isViewEnabled(view)}>
<span class="toggle-track">
<span class="toggle-thumb"></span>
</span>
</span>
<span class="item-label">{viewLabels[view]}</span>
</button>
{/each}
<!-- Divider -->
<div class="divider"></div>
<!-- Custom day count section -->
<div class="custom-section">
<span class="custom-label">Benutzerdefiniert (1-365)</span>
<div class="custom-input-row">
<input
type="number"
class="custom-input"
min="1"
max="365"
value={customDayInput}
oninput={handleCustomDayInputChange}
onkeydown={handleInputKeyDown}
onclick={(e) => e.stopPropagation()}
/>
<span class="custom-unit">Tage</span>
<button class="custom-apply-btn" onclick={applyCustomDays}> Setzen </button>
</div>
</div>
</div>
{/if}
<style>
.context-menu-backdrop {
position: fixed;
inset: 0;
z-index: 9998;
background: transparent;
pointer-events: auto;
}
.context-menu {
position: fixed;
z-index: 9999;
min-width: 200px;
max-width: 280px;
padding: 0.375rem;
background: var(--color-surface-elevated-3);
border: 1px solid hsl(var(--color-border));
border-radius: var(--radius-lg);
pointer-events: auto;
}
.menu-item {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.5rem 0.625rem;
border: none;
background: transparent;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 0.8125rem;
color: hsl(var(--color-foreground));
text-align: left;
transition: background-color 100ms ease;
}
.menu-item:hover {
background: hsl(var(--color-muted));
}
.item-label {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.divider {
height: 1px;
margin: 0.375rem 0.5rem;
background: hsl(var(--color-border));
}
/* Toggle switch styles */
.item-toggle {
display: flex;
align-items: center;
flex-shrink: 0;
}
.toggle-track {
position: relative;
width: 28px;
height: 16px;
background: hsl(var(--color-muted));
border-radius: 8px;
transition: background-color 150ms ease;
}
.toggle-thumb {
position: absolute;
top: 2px;
left: 2px;
width: 12px;
height: 12px;
background: hsl(var(--color-background));
border-radius: 50%;
transition: transform 150ms ease;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.item-toggle.checked .toggle-track {
background: hsl(var(--color-primary));
}
.item-toggle.checked .toggle-thumb {
transform: translateX(12px);
}
/* Custom section styles */
.custom-section {
padding: 0.5rem 0.625rem;
}
.custom-label {
display: block;
font-size: 0.75rem;
font-weight: 500;
color: hsl(var(--color-muted-foreground));
margin-bottom: 0.5rem;
}
.custom-input-row {
display: flex;
align-items: center;
gap: 0.5rem;
}
.custom-input {
width: 60px;
padding: 0.375rem 0.5rem;
border: 1px solid hsl(var(--color-border));
border-radius: var(--radius-md);
background: hsl(var(--color-background));
color: hsl(var(--color-foreground));
font-size: 0.8125rem;
text-align: center;
}
.custom-input:focus {
outline: none;
border-color: hsl(var(--color-primary));
}
/* Hide number input spinners */
.custom-input::-webkit-outer-spin-button,
.custom-input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.custom-input[type='number'] {
-moz-appearance: textfield;
}
.custom-unit {
font-size: 0.8125rem;
color: hsl(var(--color-muted-foreground));
}
.custom-apply-btn {
margin-left: auto;
padding: 0.375rem 0.75rem;
border: none;
border-radius: var(--radius-md);
background: hsl(var(--color-primary));
color: hsl(var(--color-primary-foreground));
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: opacity 150ms ease;
}
.custom-apply-btn:hover {
opacity: 0.9;
}
</style>

View file

@ -43,6 +43,9 @@ export interface CalendarAppSettings {
dateStripShowWeekNumbers: boolean; // Show week numbers at start of week
dateStripCollapsed: boolean; // Whether DateStrip is minimized to FAB
// TagStrip settings
tagStripCollapsed: boolean; // Whether TagStrip is hidden
// Birthday settings (cross-app integration with Contacts)
showBirthdays: boolean; // Show contact birthdays in calendar
showBirthdayAge: boolean; // Show age in birthday events
@ -52,6 +55,7 @@ export interface CalendarAppSettings {
// Quick View Pill settings
quickViewPillViews: CalendarViewType[]; // Views shown in quick switcher
customDayCount: number; // Custom day count for 'custom' view type (1-365)
// Event defaults
defaultEventDuration: number; // in minutes
@ -82,6 +86,8 @@ const DEFAULT_SETTINGS: CalendarAppSettings = {
dateStripCompact: false,
dateStripShowWeekNumbers: false,
dateStripCollapsed: false,
// TagStrip defaults
tagStripCollapsed: true, // Hidden by default
// Birthday defaults
showBirthdays: true,
showBirthdayAge: true,
@ -89,6 +95,7 @@ const DEFAULT_SETTINGS: CalendarAppSettings = {
sidebarCollapsed: false,
// Quick View Pill defaults
quickViewPillViews: ['week', 'month', 'agenda'],
customDayCount: 30, // Default: 30 days (1 month)
// Event defaults
defaultEventDuration: 60,
defaultReminder: 15,
@ -225,6 +232,10 @@ export const settingsStore = {
get dateStripCollapsed() {
return settings.dateStripCollapsed;
},
// TagStrip settings
get tagStripCollapsed() {
return settings.tagStripCollapsed;
},
// Birthday settings
get showBirthdays() {
return settings.showBirthdays;
@ -244,6 +255,9 @@ export const settingsStore = {
get quickViewPillViews() {
return settings.quickViewPillViews;
},
get customDayCount() {
return settings.customDayCount;
},
get cloudSyncEnabled() {
return cloudSyncEnabled;
},
@ -284,6 +298,15 @@ export const settingsStore = {
syncToCloud();
},
/**
* Toggle TagStrip visibility
*/
toggleTagStrip() {
settings = { ...settings, tagStripCollapsed: !settings.tagStripCollapsed };
saveSettings(settings);
syncToCloud();
},
/**
* Initialize settings from localStorage
*/

View file

@ -38,6 +38,11 @@ const viewRange = $derived.by(() => {
start: startOfDay(currentDate),
end: endOfDay(currentDate),
};
case '3day':
return {
start: startOfDay(currentDate),
end: endOfDay(addDays(currentDate, 2)),
};
case '5day':
return {
start: startOfDay(currentDate),
@ -58,6 +63,33 @@ const viewRange = $derived.by(() => {
start: startOfDay(currentDate),
end: endOfDay(addDays(currentDate, 13)),
};
case '30day':
return {
start: startOfDay(currentDate),
end: endOfDay(addDays(currentDate, 29)),
};
case '60day':
return {
start: startOfDay(currentDate),
end: endOfDay(addDays(currentDate, 59)),
};
case '90day':
return {
start: startOfDay(currentDate),
end: endOfDay(addDays(currentDate, 89)),
};
case '365day':
return {
start: startOfDay(currentDate),
end: endOfDay(addDays(currentDate, 364)),
};
case 'custom': {
const customDays = settingsStore.customDayCount;
return {
start: startOfDay(currentDate),
end: endOfDay(addDays(currentDate, customDays - 1)),
};
}
case 'month':
return {
start: startOfMonth(currentDate),
@ -108,7 +140,22 @@ export const viewStore = {
const savedView = localStorage.getItem('calendar-view-type');
if (
savedView &&
['day', '5day', 'week', '10day', '14day', 'month', 'year', 'agenda'].includes(savedView)
[
'day',
'3day',
'5day',
'week',
'10day',
'14day',
'30day',
'60day',
'90day',
'365day',
'month',
'year',
'agenda',
'custom',
].includes(savedView)
) {
viewType = savedView as CalendarViewType;
} else {
@ -149,6 +196,9 @@ export const viewStore = {
case 'day':
currentDate = subDays(currentDate, 1);
break;
case '3day':
currentDate = subDays(currentDate, 3);
break;
case '5day':
currentDate = subDays(currentDate, 5);
break;
@ -161,6 +211,21 @@ export const viewStore = {
case '14day':
currentDate = subDays(currentDate, 14);
break;
case '30day':
currentDate = subDays(currentDate, 30);
break;
case '60day':
currentDate = subDays(currentDate, 60);
break;
case '90day':
currentDate = subDays(currentDate, 90);
break;
case '365day':
currentDate = subDays(currentDate, 365);
break;
case 'custom':
currentDate = subDays(currentDate, settingsStore.customDayCount);
break;
case 'month':
currentDate = subMonths(currentDate, 1);
break;
@ -181,6 +246,9 @@ export const viewStore = {
case 'day':
currentDate = addDays(currentDate, 1);
break;
case '3day':
currentDate = addDays(currentDate, 3);
break;
case '5day':
currentDate = addDays(currentDate, 5);
break;
@ -193,6 +261,21 @@ export const viewStore = {
case '14day':
currentDate = addDays(currentDate, 14);
break;
case '30day':
currentDate = addDays(currentDate, 30);
break;
case '60day':
currentDate = addDays(currentDate, 60);
break;
case '90day':
currentDate = addDays(currentDate, 90);
break;
case '365day':
currentDate = addDays(currentDate, 365);
break;
case 'custom':
currentDate = addDays(currentDate, settingsStore.customDayCount);
break;
case 'month':
currentDate = addMonths(currentDate, 1);
break;

View file

@ -14,6 +14,7 @@ import {
subMonths,
subYears,
} from 'date-fns';
import { settingsStore } from '$lib/stores/settings.svelte';
/**
* Calculate a date offset based on the current view type
@ -35,6 +36,9 @@ export function getOffsetDate(date: Date, viewType: CalendarViewType, offset: nu
case 'day':
return offset > 0 ? addDays(date, offset) : subDays(date, Math.abs(offset));
case '3day':
return offset > 0 ? addDays(date, offset * 3) : subDays(date, Math.abs(offset) * 3);
case '5day':
return offset > 0 ? addDays(date, offset * 5) : subDays(date, Math.abs(offset) * 5);
@ -47,6 +51,23 @@ export function getOffsetDate(date: Date, viewType: CalendarViewType, offset: nu
case '14day':
return offset > 0 ? addDays(date, offset * 14) : subDays(date, Math.abs(offset) * 14);
case '30day':
return offset > 0 ? addDays(date, offset * 30) : subDays(date, Math.abs(offset) * 30);
case '60day':
return offset > 0 ? addDays(date, offset * 60) : subDays(date, Math.abs(offset) * 60);
case '90day':
return offset > 0 ? addDays(date, offset * 90) : subDays(date, Math.abs(offset) * 90);
case '365day':
return offset > 0 ? addDays(date, offset * 365) : subDays(date, Math.abs(offset) * 365);
case 'custom': {
const days = settingsStore.customDayCount;
return offset > 0 ? addDays(date, offset * days) : subDays(date, Math.abs(offset) * days);
}
case 'month':
return offset > 0 ? addMonths(date, offset) : subMonths(date, Math.abs(offset));

View file

@ -55,6 +55,7 @@
import CalendarToolbarContent from '$lib/components/calendar/CalendarToolbarContent.svelte';
import DateStrip from '$lib/components/calendar/DateStrip.svelte';
import DateStripFab from '$lib/components/calendar/DateStripFab.svelte';
import TagStrip from '$lib/components/calendar/TagStrip.svelte';
import EventContextMenu from '$lib/components/event/EventContextMenu.svelte';
import ViewModePillContextMenu from '$lib/components/calendar/ViewModePillContextMenu.svelte';
import { eventContextMenuStore } from '$lib/stores/eventContextMenu.svelte';
@ -249,14 +250,32 @@
// User email for user dropdown
let userEmail = $derived(authStore.user?.email || 'Menü');
// Toggle TagStrip visibility
function handleTagsToggle() {
settingsStore.toggleTagStrip();
}
// Tags button active state (show as active when TagStrip is visible)
let isTagStripVisible = $derived(!settingsStore.tagStripCollapsed);
// Offset for elements above TagStrip (70px when visible)
let tagStripOffset = $derived(showCalendarToolbar && !settingsStore.tagStripCollapsed ? 70 : 0);
// Base navigation items for Calendar (without Kalender/Aufgaben - handled by tab group)
const baseNavItems: PillNavItem[] = [
{ href: '/tags', label: 'Tags', icon: 'tag' },
// Note: Tags uses onClick to toggle TagStrip visibility instead of navigating
let baseNavItems = $derived<PillNavItem[]>([
{
href: '/tags',
label: 'Tags',
icon: 'tag',
onClick: handleTagsToggle,
active: isTagStripVisible,
},
{ href: '/statistics', label: 'Statistiken', icon: 'bar-chart-3' },
{ href: '/network', label: 'Netzwerk', icon: 'share-2' },
{ href: '/settings', label: 'Einstellungen', icon: 'settings' },
{ href: '/feedback', label: 'Feedback', icon: 'chat' },
];
]);
// Navigation items filtered by visibility settings
const navItems = $derived(
@ -292,6 +311,10 @@
week: '7',
'10day': '10',
'14day': '14',
'30day': '30',
'60day': '60',
'90day': '90',
'365day': '365',
month: 'M',
year: 'Y',
agenda: 'L',
@ -306,6 +329,10 @@
week: 'Wochenansicht',
'10day': '10-Tage-Ansicht',
'14day': '14-Tage-Ansicht',
'30day': '30-Tage-Ansicht',
'60day': '60-Tage-Ansicht',
'90day': '90-Tage-Ansicht',
'365day': '365-Tage-Ansicht',
month: 'Monatsansicht',
year: 'Jahresansicht',
agenda: 'Agenda',
@ -543,18 +570,33 @@
<!-- Date strip (only on main calendar page) -->
{#if showCalendarToolbar}
{#if settingsStore.dateStripCollapsed}
<DateStripFab {isSidebarMode} isToolbarExpanded={!isToolbarCollapsed} {isMobile} />
<DateStripFab
{isSidebarMode}
isToolbarExpanded={!isToolbarCollapsed}
{isMobile}
hasTagStrip={!settingsStore.tagStripCollapsed}
/>
{:else}
<DateStrip {isSidebarMode} isToolbarExpanded={!isToolbarCollapsed} />
<DateStrip
{isSidebarMode}
isToolbarExpanded={!isToolbarCollapsed}
hasTagStrip={!settingsStore.tagStripCollapsed}
/>
{/if}
{/if}
<!-- Tag strip (only on main calendar page, when not collapsed) - directly above PillNav -->
{#if showCalendarToolbar && !settingsStore.tagStripCollapsed}
<TagStrip {isSidebarMode} />
{/if}
<!-- Calendar toolbar (only on main calendar page, not in sidebar mode) -->
{#if showCalendarToolbar && !isSidebarMode}
<CalendarToolbar
{isSidebarMode}
isCollapsed={isToolbarCollapsed}
{isMobile}
bottomOffset={settingsStore.tagStripCollapsed ? '70px' : '140px'}
onModeChange={handleToolbarModeChange}
onCollapsedChange={handleToolbarCollapsedChange}
/>
@ -587,12 +629,12 @@
createText="Erstellen"
appIcon="calendar"
bottomOffset={isMobile
? '70px'
? `${70 + tagStripOffset}px`
: isSidebarMode
? '0px'
? `${tagStripOffset}px`
: showCalendarToolbar && !isToolbarCollapsed
? '140px'
: '70px'}
? `${140 + tagStripOffset}px`
: `${70 + tagStripOffset}px`}
hasFabRight={showCalendarToolbar && !isSidebarMode}
hasFabLeft={!isMobile &&
showCalendarToolbar &&

View file

@ -1,9 +1,28 @@
/**
* Calendar view types
*/
export type CalendarViewType =
| 'day'
| '3day'
| '5day'
| 'week'
| '10day'
| '14day'
| '30day'
| '60day'
| '90day'
| '365day'
| 'month'
| 'year'
| 'agenda'
| 'custom';
/**
* Calendar settings stored in JSONB
*/
export interface CalendarSettings {
/** Default view when opening the calendar */
defaultView?: 'day' | '5day' | 'week' | '10day' | '14day' | 'month' | 'year' | 'agenda';
defaultView?: CalendarViewType;
/** 0 = Sunday, 1 = Monday */
weekStartsOn?: 0 | 1;
/** Show week numbers in calendar views */
@ -57,19 +76,6 @@ export interface UpdateCalendarInput {
settings?: CalendarSettings;
}
/**
* Calendar view types
*/
export type CalendarViewType =
| 'day'
| '5day'
| 'week'
| '10day'
| '14day'
| 'month'
| 'year'
| 'agenda';
/**
* Default calendar colors
*/