From e0d7b3d13dfba47ce8a53e4ee8d7be1648f3c12a Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Sun, 14 Dec 2025 22:22:15 +0100 Subject: [PATCH] feat(calendar): add swipe navigation for calendar views MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement horizontal swipe/trackpad navigation between calendar periods: - Add ViewCarousel component with animated page transitions - Support touch swipe, trackpad scroll, and wheel navigation - Create useSwipeNavigation composable for reusable swipe handling - Add dateNavigation utility for calculating view-specific date offsets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../lib/components/calendar/AgendaView.svelte | 9 +- .../lib/components/calendar/DayView.svelte | 31 +- .../lib/components/calendar/MonthView.svelte | 16 +- .../components/calendar/MultiDayView.svelte | 26 +- .../components/calendar/ViewCarousel.svelte | 278 ++++++++++++++++++ .../lib/components/calendar/WeekView.svelte | 29 +- .../lib/components/calendar/YearView.svelte | 9 +- .../apps/web/src/lib/composables/index.ts | 3 + .../composables/useSwipeNavigation.svelte.ts | 182 ++++++++++++ .../apps/web/src/lib/utils/dateNavigation.ts | 63 ++++ .../apps/web/src/routes/(app)/+page.svelte | 37 +-- 11 files changed, 618 insertions(+), 65 deletions(-) create mode 100644 apps/calendar/apps/web/src/lib/components/calendar/ViewCarousel.svelte create mode 100644 apps/calendar/apps/web/src/lib/composables/useSwipeNavigation.svelte.ts create mode 100644 apps/calendar/apps/web/src/lib/utils/dateNavigation.ts diff --git a/apps/calendar/apps/web/src/lib/components/calendar/AgendaView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/AgendaView.svelte index 6d3acad2f..43a94001c 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/AgendaView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/AgendaView.svelte @@ -10,10 +10,15 @@ import type { CalendarEvent } from '@calendar/shared'; interface Props { + /** Optional date override for carousel navigation (uses viewStore.currentDate if not provided) */ + date?: Date; onEventClick?: (event: CalendarEvent) => void; } - let { onEventClick }: Props = $props(); + let { date, onEventClick }: Props = $props(); + + // Use provided date or fall back to viewStore + let effectiveDate = $derived(date ?? viewStore.currentDate); // Group events by date let groupedEvents = $derived.by(() => { @@ -24,7 +29,7 @@ const visibleCalendarIds = new Set(calendarsStore.visibleCalendars.map((c) => c.id)); // Filter events that start from current date onwards - const startDate = startOfDay(viewStore.currentDate); + const startDate = startOfDay(effectiveDate); const groups: Map = new Map(); diff --git a/apps/calendar/apps/web/src/lib/components/calendar/DayView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/DayView.svelte index 83aef58d6..34f4a64b0 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/DayView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/DayView.svelte @@ -27,12 +27,17 @@ import type { CalendarEvent } from '@calendar/shared'; interface Props { + /** Optional date override for carousel navigation (uses viewStore.currentDate if not provided) */ + date?: Date; onQuickCreate?: (date: Date, position: { x: number; y: number }) => void; onEventClick?: (event: CalendarEvent) => void; onTaskClick?: (task: Task) => void; } - let { onQuickCreate, onEventClick, onTaskClick }: Props = $props(); + let { date, onQuickCreate, onEventClick, onTaskClick }: Props = $props(); + + // Use provided date or fall back to viewStore + let effectiveDate = $derived(date ?? viewStore.currentDate); // Use shared constants const HOUR_HEIGHT = HOUR_HEIGHT_PX; @@ -55,7 +60,7 @@ // Get timed events, filtering out those outside visible range when hour filter is enabled let timedEvents = $derived( getVisibleTimedEvents( - eventsStore.getEventsForDay(viewStore.currentDate), + eventsStore.getEventsForDay(effectiveDate), calendarsStore.visibleCalendars, { filterHoursEnabled: settingsStore.filterHoursEnabled, @@ -71,7 +76,7 @@ return { before: [], after: [] }; } return getVisibleOverflowEvents( - eventsStore.getEventsForDay(viewStore.currentDate), + eventsStore.getEventsForDay(effectiveDate), calendarsStore.visibleCalendars, settingsStore.dayStartHour, settingsStore.dayEndHour @@ -80,7 +85,7 @@ let allDayEvents = $derived( getVisibleAllDayEvents( - eventsStore.getEventsForDay(viewStore.currentDate), + eventsStore.getEventsForDay(effectiveDate), calendarsStore.visibleCalendars ) ); @@ -106,7 +111,7 @@ // Get birthdays for current day (if enabled in settings) let birthdays = $derived.by(() => { if (!settingsStore.showBirthdays) return []; - return birthdaysStore.getBirthdaysForDay(viewStore.currentDate); + return birthdaysStore.getBirthdaysForDay(effectiveDate); }); // ============================================================================ @@ -225,7 +230,7 @@ const duration = differenceInMinutes(end, start); // Create new start time on same day - let newStart = new Date(viewStore.currentDate); + let newStart = new Date(effectiveDate); newStart = setHours(newStart, Math.floor(clampedMinutes / 60)); newStart = setMinutes(newStart, clampedMinutes % 60); newStart.setSeconds(0, 0); @@ -327,7 +332,7 @@ firstVisibleHour * 60, Math.min(adjustedMinutes, origEndMinutes - SNAP_MINUTES) ); - newStart = setHours(new Date(viewStore.currentDate), Math.floor(newStartMinutes / 60)); + newStart = setHours(new Date(effectiveDate), Math.floor(newStartMinutes / 60)); newStart = setMinutes(newStart, newStartMinutes % 60); newStart.setSeconds(0, 0); } else { @@ -335,7 +340,7 @@ lastVisibleHour * 60, Math.max(adjustedMinutes, origStartMinutes + SNAP_MINUTES) ); - newEnd = setHours(new Date(viewStore.currentDate), Math.floor(newEndMinutes / 60)); + newEnd = setHours(new Date(effectiveDate), Math.floor(newEndMinutes / 60)); newEnd = setMinutes(newEnd, newEndMinutes % 60); newEnd.setSeconds(0, 0); } @@ -586,7 +591,7 @@ const endTime = `${endHours.toString().padStart(2, '0')}:${endMins.toString().padStart(2, '0')}`; await todosStore.updateTodo(data.taskId, { - scheduledDate: format(viewStore.currentDate, 'yyyy-MM-dd'), + scheduledDate: format(effectiveDate, 'yyyy-MM-dd'), scheduledStartTime: startTime, scheduledEndTime: endTime, estimatedDuration: duration, @@ -659,7 +664,7 @@ * Get scheduled tasks for current day */ function getScheduledTasks(): Task[] { - return todosStore.getScheduledTasksForDay(viewStore.currentDate); + return todosStore.getScheduledTasksForDay(effectiveDate); } function handleEventClick(event: CalendarEvent, e: MouseEvent) { @@ -683,7 +688,7 @@ // Don't create event if dragging or resizing if (isDragging || isResizing) return; - const startTime = new Date(viewStore.currentDate); + const startTime = new Date(effectiveDate); startTime.setHours(hour, 0, 0, 0); if (onQuickCreate) { @@ -756,7 +761,7 @@
- {#if isToday(viewStore.currentDate)} + {#if isToday(effectiveDate)}
{/if}
diff --git a/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte index 7bfde2249..ebbc02494 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/MonthView.svelte @@ -36,16 +36,21 @@ import type { CalendarEvent } from '@calendar/shared'; interface Props { + /** Optional date override for carousel navigation (uses viewStore.currentDate if not provided) */ + date?: Date; onQuickCreate?: (date: Date, position: { x: number; y: number }) => void; onEventClick?: (event: CalendarEvent) => void; } - let { onQuickCreate, onEventClick }: Props = $props(); + let { date, onQuickCreate, onEventClick }: Props = $props(); + + // Use provided date or fall back to viewStore + let effectiveDate = $derived(date ?? viewStore.currentDate); // Get all days to display in the month grid (including days from prev/next months) let allCalendarDays = $derived.by(() => { - const monthStart = startOfMonth(viewStore.currentDate); - const monthEnd = endOfMonth(viewStore.currentDate); + const monthStart = startOfMonth(effectiveDate); + const monthEnd = endOfMonth(effectiveDate); const calendarStart = startOfWeek(monthStart, { weekStartsOn: settingsStore.weekStartsOn }); const calendarEnd = endOfWeek(monthEnd, { weekStartsOn: settingsStore.weekStartsOn }); @@ -85,7 +90,6 @@ let isDragging = $state(false); let draggedEvent = $state(null); let dragTargetDay = $state(null); - let monthViewRef = $state(null); // Store for day cell refs let dayCellRefs = $state>(new Map()); @@ -276,7 +280,7 @@ } -
+
{#each weekDays as day} @@ -292,7 +296,7 @@ {@const isDropTarget = isDragging && dragTargetDay && isSameDay(day, dragTargetDay)}
void; onEventClick?: (event: CalendarEvent) => void; onTaskClick?: (task: Task) => void; } - let { dayCount, onQuickCreate, onEventClick, onTaskClick }: Props = $props(); + let { dayCount, date, onQuickCreate, onEventClick, onTaskClick }: Props = $props(); + + // Use provided date or fall back to viewStore + let effectiveDate = $derived(date ?? viewStore.currentDate); + + // Calculate view range based on effective date + let effectiveViewRange = $derived.by(() => { + if (date) { + // Calculate range for the provided date based on day count + const end = new Date(date); + end.setDate(end.getDate() + dayCount - 1); + return { + start: date, + end: end, + }; + } + // Use viewStore range when no date override + return viewStore.viewRange; + }); // Get date-fns locale based on current app locale const dateLocales = { de, en: enUS, fr, es, it }; @@ -58,8 +78,8 @@ // Generate days based on view range, optionally filtering weekends let allDays = $derived( eachDayOfInterval({ - start: viewStore.viewRange.start, - end: viewStore.viewRange.end, + start: effectiveViewRange.start, + end: effectiveViewRange.end, }) ); diff --git a/apps/calendar/apps/web/src/lib/components/calendar/ViewCarousel.svelte b/apps/calendar/apps/web/src/lib/components/calendar/ViewCarousel.svelte new file mode 100644 index 000000000..656fee155 --- /dev/null +++ b/apps/calendar/apps/web/src/lib/components/calendar/ViewCarousel.svelte @@ -0,0 +1,278 @@ + + + + + + diff --git a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte index 421291570..ceb0d9d0a 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/WeekView.svelte @@ -32,6 +32,8 @@ setHours, setMinutes, getWeek, + startOfWeek, + endOfWeek, } from 'date-fns'; import { de, enUS, fr, es, it } from 'date-fns/locale'; import { locale, _ } from 'svelte-i18n'; @@ -39,12 +41,31 @@ import type { CalendarEvent } from '@calendar/shared'; interface Props { + /** Optional date override for carousel navigation (uses viewStore.currentDate if not provided) */ + date?: Date; onQuickCreate?: (date: Date, position: { x: number; y: number }) => void; onEventClick?: (event: CalendarEvent) => void; onTaskClick?: (task: Task) => void; } - let { onQuickCreate, onEventClick, onTaskClick }: Props = $props(); + let { date, onQuickCreate, onEventClick, onTaskClick }: Props = $props(); + + // Use provided date or fall back to viewStore + let effectiveDate = $derived(date ?? viewStore.currentDate); + + // Calculate view range based on effective date + let effectiveViewRange = $derived.by(() => { + if (date) { + // Calculate range for the provided date + const weekStartsOn = settingsStore.weekStartsOn; + return { + start: startOfWeek(date, { weekStartsOn }), + end: endOfWeek(date, { weekStartsOn }), + }; + } + // Use viewStore range when no date override + return viewStore.viewRange; + }); // Use shared constants const HOUR_HEIGHT = HOUR_HEIGHT_PX; @@ -59,8 +80,8 @@ // Generate days of the week, optionally filtering weekends let allDays = $derived( eachDayOfInterval({ - start: viewStore.viewRange.start, - end: viewStore.viewRange.end, + start: effectiveViewRange.start, + end: effectiveViewRange.end, }) ); @@ -70,7 +91,7 @@ // Get week number for display let weekNumber = $derived( - getWeek(viewStore.viewRange.start, { weekStartsOn: settingsStore.weekStartsOn }) + getWeek(effectiveViewRange.start, { weekStartsOn: settingsStore.weekStartsOn }) ); // Use composables for hour filtering and time indicator diff --git a/apps/calendar/apps/web/src/lib/components/calendar/YearView.svelte b/apps/calendar/apps/web/src/lib/components/calendar/YearView.svelte index e0c0d5950..ed43e762a 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/YearView.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/YearView.svelte @@ -19,14 +19,19 @@ import type { CalendarViewType, CalendarEvent } from '@calendar/shared'; interface Props { + /** Optional date override for carousel navigation (uses viewStore.currentDate if not provided) */ + date?: Date; onQuickCreate?: (date: Date, position: { x: number; y: number }) => void; onEventClick?: (event: CalendarEvent) => void; } - let { onQuickCreate, onEventClick }: Props = $props(); + let { date, onQuickCreate, onEventClick }: Props = $props(); + + // Use provided date or fall back to viewStore + let effectiveDate = $derived(date ?? viewStore.currentDate); // Derived values - let year = $derived(viewStore.currentDate.getFullYear()); + let year = $derived(effectiveDate.getFullYear()); let months = $derived(Array.from({ length: 12 }, (_, i) => new Date(year, i, 1))); diff --git a/apps/calendar/apps/web/src/lib/composables/index.ts b/apps/calendar/apps/web/src/lib/composables/index.ts index 511e0aaae..6307cdbdd 100644 --- a/apps/calendar/apps/web/src/lib/composables/index.ts +++ b/apps/calendar/apps/web/src/lib/composables/index.ts @@ -26,6 +26,9 @@ export { useCalendarKeyboard, type CancellableOperation } from './useCalendarKey // Birthday popover management export { useBirthdayPopover } from './useBirthdayPopover.svelte'; +// Swipe/scroll navigation for view switching +export { useSwipeNavigation, type SwipeNavigationOptions } from './useSwipeNavigation.svelte'; + // Legacy exports (kept for backwards compatibility, may be removed later) export { useDragDrop, type DragDropConfig, type DragState } from './useDragDrop.svelte'; export { useResize, type ResizeConfig, type ResizeState } from './useResize.svelte'; diff --git a/apps/calendar/apps/web/src/lib/composables/useSwipeNavigation.svelte.ts b/apps/calendar/apps/web/src/lib/composables/useSwipeNavigation.svelte.ts new file mode 100644 index 000000000..84663f430 --- /dev/null +++ b/apps/calendar/apps/web/src/lib/composables/useSwipeNavigation.svelte.ts @@ -0,0 +1,182 @@ +/** + * Swipe Navigation Composable + * Enables horizontal swipe/scroll navigation for calendar views + * + * Supports: + * - Trackpad horizontal scroll (Mac/Windows) + * - Touch swipe (Mobile/Tablet) + * - Mouse horizontal scroll wheel + */ + +import { browser } from '$app/environment'; + +export interface SwipeNavigationOptions { + /** Minimum pixels to trigger navigation (default: 80) */ + threshold?: number; + /** Debounce time in ms for wheel events (default: 150) */ + debounceMs?: number; + /** Disable swipe navigation temporarily */ + disabled?: boolean; +} + +const DEFAULT_THRESHOLD = 80; +const DEFAULT_DEBOUNCE_MS = 150; + +/** + * Creates swipe/scroll navigation for a container element + * + * @param getElement - Function returning the target element + * @param onNext - Callback when swiping left (go to next period) + * @param onPrevious - Callback when swiping right (go to previous period) + * @param options - Configuration options + * + * @example + * ```svelte + * + * + *
...
+ * ``` + */ +export function useSwipeNavigation( + getElement: () => HTMLElement | null, + onNext: () => void, + onPrevious: () => void, + options: SwipeNavigationOptions = {} +) { + if (!browser) return; + + const threshold = options.threshold ?? DEFAULT_THRESHOLD; + const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS; + + // Track accumulated wheel delta for trackpad detection + let accumulatedDelta = 0; + let debounceTimer: ReturnType | null = null; + + // Touch tracking + let touchStartX = 0; + let touchStartY = 0; + let isTouching = false; + + /** + * Handle wheel events (trackpad horizontal scroll) + */ + function handleWheel(e: WheelEvent) { + // Skip if disabled + if (options.disabled) return; + + // Only handle horizontal scrolling (deltaX dominant) + // This distinguishes trackpad gestures from vertical scrolling + if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return; + + // Don't interfere with event dragging + const target = e.target as HTMLElement; + if (target.closest('[data-event-id]') || target.closest('[data-dragging]')) return; + + // Prevent default scroll behavior for horizontal gestures + e.preventDefault(); + + // Accumulate horizontal delta + accumulatedDelta += e.deltaX; + + // Reset accumulator after debounce period + if (debounceTimer) clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + accumulatedDelta = 0; + }, debounceMs); + + // Check if threshold reached + if (accumulatedDelta > threshold) { + onNext(); + accumulatedDelta = 0; + if (debounceTimer) clearTimeout(debounceTimer); + } else if (accumulatedDelta < -threshold) { + onPrevious(); + accumulatedDelta = 0; + if (debounceTimer) clearTimeout(debounceTimer); + } + } + + /** + * Handle touch start + */ + function handleTouchStart(e: TouchEvent) { + // Skip if disabled + if (options.disabled) return; + + // Don't interfere with event dragging + const target = e.target as HTMLElement; + if (target.closest('[data-event-id]') || target.closest('[data-dragging]')) return; + + touchStartX = e.touches[0].clientX; + touchStartY = e.touches[0].clientY; + isTouching = true; + } + + /** + * Handle touch end + */ + function handleTouchEnd(e: TouchEvent) { + // Skip if disabled or wasn't tracking + if (options.disabled || !isTouching) return; + isTouching = false; + + const touchEndX = e.changedTouches[0].clientX; + const touchEndY = e.changedTouches[0].clientY; + + const deltaX = touchEndX - touchStartX; + const deltaY = touchEndY - touchStartY; + + // Only trigger if horizontal movement is dominant and exceeds threshold + if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > threshold) { + if (deltaX > 0) { + // Swiped right → go to previous + onPrevious(); + } else { + // Swiped left → go to next + onNext(); + } + } + } + + /** + * Handle touch cancel + */ + function handleTouchCancel() { + isTouching = false; + } + + // Setup and cleanup with $effect + $effect(() => { + const el = getElement(); + if (!el) return; + + // Add event listeners + el.addEventListener('wheel', handleWheel, { passive: false }); + el.addEventListener('touchstart', handleTouchStart, { passive: true }); + el.addEventListener('touchend', handleTouchEnd, { passive: true }); + el.addEventListener('touchcancel', handleTouchCancel, { passive: true }); + + // Cleanup + return () => { + el.removeEventListener('wheel', handleWheel); + el.removeEventListener('touchstart', handleTouchStart); + el.removeEventListener('touchend', handleTouchEnd); + el.removeEventListener('touchcancel', handleTouchCancel); + + if (debounceTimer) { + clearTimeout(debounceTimer); + } + }; + }); +} diff --git a/apps/calendar/apps/web/src/lib/utils/dateNavigation.ts b/apps/calendar/apps/web/src/lib/utils/dateNavigation.ts new file mode 100644 index 000000000..f7361885c --- /dev/null +++ b/apps/calendar/apps/web/src/lib/utils/dateNavigation.ts @@ -0,0 +1,63 @@ +/** + * Date Navigation Utilities + * Helper functions for calculating date offsets based on view type + */ + +import type { CalendarViewType } from '@calendar/shared'; +import { + addDays, + addWeeks, + addMonths, + addYears, + subDays, + subWeeks, + subMonths, + subYears, +} from 'date-fns'; + +/** + * Calculate a date offset based on the current view type + * + * @param date - The base date + * @param viewType - The current calendar view type + * @param offset - Number of periods to offset (-1 = previous, 1 = next) + * @returns The calculated date + * + * @example + * // Get previous week's date + * getOffsetDate(new Date(), 'week', -1) + * + * // Get next month's date + * getOffsetDate(new Date(), 'month', 1) + */ +export function getOffsetDate(date: Date, viewType: CalendarViewType, offset: number): Date { + switch (viewType) { + case 'day': + return offset > 0 ? addDays(date, offset) : subDays(date, Math.abs(offset)); + + case '5day': + return offset > 0 ? addDays(date, offset * 5) : subDays(date, Math.abs(offset) * 5); + + case 'week': + return offset > 0 ? addWeeks(date, offset) : subWeeks(date, Math.abs(offset)); + + case '10day': + return offset > 0 ? addDays(date, offset * 10) : subDays(date, Math.abs(offset) * 10); + + case '14day': + return offset > 0 ? addDays(date, offset * 14) : subDays(date, Math.abs(offset) * 14); + + case 'month': + return offset > 0 ? addMonths(date, offset) : subMonths(date, Math.abs(offset)); + + case 'year': + return offset > 0 ? addYears(date, offset) : subYears(date, Math.abs(offset)); + + case 'agenda': + // Agenda moves by 7 days + return offset > 0 ? addDays(date, offset * 7) : subDays(date, Math.abs(offset) * 7); + + default: + return offset > 0 ? addWeeks(date, offset) : subWeeks(date, Math.abs(offset)); + } +} diff --git a/apps/calendar/apps/web/src/routes/(app)/+page.svelte b/apps/calendar/apps/web/src/routes/(app)/+page.svelte index e464e1a2d..a1a5c197a 100644 --- a/apps/calendar/apps/web/src/routes/(app)/+page.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/+page.svelte @@ -8,12 +8,7 @@ import { authStore } from '$lib/stores/auth.svelte'; import { settingsStore } from '$lib/stores/settings.svelte'; import { isSidebarMode as sidebarModeStore } from '$lib/stores/navigation'; - import WeekView from '$lib/components/calendar/WeekView.svelte'; - import DayView from '$lib/components/calendar/DayView.svelte'; - import MonthView from '$lib/components/calendar/MonthView.svelte'; - import MultiDayView from '$lib/components/calendar/MultiDayView.svelte'; - import YearView from '$lib/components/calendar/YearView.svelte'; - import AgendaView from '$lib/components/calendar/AgendaView.svelte'; + import ViewCarousel from '$lib/components/calendar/ViewCarousel.svelte'; import TodoSidebarSection from '$lib/components/calendar/TodoSidebarSection.svelte'; import QuickEventOverlay from '$lib/components/event/QuickEventOverlay.svelte'; import { CalendarViewSkeleton } from '$lib/components/skeletons'; @@ -150,36 +145,8 @@
{#if !initialized} - {:else if viewStore.viewType === 'day'} - - {:else if viewStore.viewType === '5day'} - - {:else if viewStore.viewType === 'week'} - - {:else if viewStore.viewType === '10day'} - - {:else if viewStore.viewType === '14day'} - - {:else if viewStore.viewType === 'month'} - - {:else if viewStore.viewType === 'year'} - - {:else if viewStore.viewType === 'agenda'} - {:else} - + {/if}