diff --git a/apps/calendar/apps/web/src/lib/api/events.ts b/apps/calendar/apps/web/src/lib/api/events.ts index 0a0b04d5a..ad35c3040 100644 --- a/apps/calendar/apps/web/src/lib/api/events.ts +++ b/apps/calendar/apps/web/src/lib/api/events.ts @@ -9,6 +9,7 @@ export interface QueryEventsParams { startDate: string; endDate: string; calendarIds?: string[]; + search?: string; } export async function getEvents(params: QueryEventsParams) { @@ -19,9 +20,25 @@ export async function getEvents(params: QueryEventsParams) { if (params.calendarIds?.length) { searchParams.set('calendarIds', params.calendarIds.join(',')); } + if (params.search) { + searchParams.set('search', params.search); + } return fetchApi(`/events?${searchParams.toString()}`); } +export async function searchEvents(query: string, limit: number = 10) { + // Search events within the next year + const now = new Date(); + const oneYearFromNow = new Date(); + oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1); + + return getEvents({ + startDate: now.toISOString(), + endDate: oneYearFromNow.toISOString(), + search: query, + }); +} + export async function getEvent(id: string) { const result = await fetchApi<{ event: CalendarEvent }>(`/events/${id}`); if (result.error || !result.data) { diff --git a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte index 942230a73..79b0f4509 100644 --- a/apps/calendar/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/calendar/apps/web/src/routes/(app)/+layout.svelte @@ -3,8 +3,13 @@ import { page } from '$app/stores'; import { onMount } from 'svelte'; import { locale } from 'svelte-i18n'; - import { PillNavigation } from '@manacore/shared-ui'; - import type { PillNavItem, PillDropdownItem } from '@manacore/shared-ui'; + import { PillNavigation, CommandBar } from '@manacore/shared-ui'; + import type { + PillNavItem, + PillDropdownItem, + CommandBarItem, + QuickAction, + } from '@manacore/shared-ui'; import { theme } from '$lib/stores/theme'; import { authStore } from '$lib/stores/auth.svelte'; import { userSettings } from '$lib/stores/user-settings.svelte'; @@ -19,12 +24,49 @@ import { getLanguageDropdownItems, getCurrentLanguageLabel } from '@manacore/shared-i18n'; import { getPillAppItems } from '@manacore/shared-branding'; import { setLocale, supportedLocales } from '$lib/i18n'; + import { searchEvents } from '$lib/api/events'; + import { format } from 'date-fns'; + import { de } from 'date-fns/locale'; // App switcher items const appItems = getPillAppItems('calendar'); let { children } = $props(); + // CommandBar state + let commandBarOpen = $state(false); + + // CommandBar quick actions (no search for calendar yet) + const commandBarQuickActions: QuickAction[] = [ + { id: 'new', label: 'Neuen Termin erstellen', icon: 'plus', href: '/event/new', shortcut: 'N' }, + { + id: 'today', + label: 'Zu Heute springen', + icon: 'calendar', + onclick: () => viewStore.goToToday(), + }, + { id: 'agenda', label: 'Agenda anzeigen', icon: 'list', href: '/agenda' }, + { id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' }, + ]; + + // CommandBar search - search events + async function handleCommandBarSearch(query: string): Promise { + if (!query.trim()) return []; + + const result = await searchEvents(query); + if (result.error || !result.data) return []; + + return result.data.slice(0, 10).map((event) => ({ + id: event.id, + title: event.title, + subtitle: format(new Date(event.startTime), 'dd. MMM yyyy, HH:mm', { locale: de }), + })); + } + + function handleCommandBarSelect(item: CommandBarItem) { + goto(`/event/${item.id}`); + } + let isSidebarMode = $state(false); let isCollapsed = $state(false); @@ -79,6 +121,13 @@ function handleKeydown(event: KeyboardEvent) { const target = event.target as HTMLElement; + // Cmd/Ctrl+K to open command bar (works even in inputs) + if ((event.ctrlKey || event.metaKey) && event.key === 'k') { + event.preventDefault(); + commandBarOpen = true; + return; + } + if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { return; } @@ -209,6 +258,18 @@ {@render children()} + + + (commandBarOpen = false)} + onSearch={handleCommandBarSearch} + onSelect={handleCommandBarSelect} + quickActions={commandBarQuickActions} + placeholder="Termin suchen..." + emptyText="Keine Termine gefunden" + searchingText="Suche..." + /> diff --git a/packages/shared-ui/src/command-bar/index.ts b/packages/shared-ui/src/command-bar/index.ts new file mode 100644 index 000000000..28928f717 --- /dev/null +++ b/packages/shared-ui/src/command-bar/index.ts @@ -0,0 +1,2 @@ +export { default as CommandBar } from './CommandBar.svelte'; +export type { CommandBarItem, QuickAction } from './CommandBar.svelte'; diff --git a/packages/shared-ui/src/index.ts b/packages/shared-ui/src/index.ts index b735e8594..c2e07e415 100644 --- a/packages/shared-ui/src/index.ts +++ b/packages/shared-ui/src/index.ts @@ -71,5 +71,9 @@ export { GlobalSettingsSection, } from './settings'; +// Command Bar +export { CommandBar } from './command-bar'; +export type { CommandBarItem, QuickAction } from './command-bar'; + // Pages export { default as AppsPage } from './pages/AppsPage.svelte';