'use client'; import React, { useEffect, useState, useCallback, useMemo } from 'react'; import { useTheme } from 'next-themes'; import { motion, AnimatePresence } from 'framer-motion'; import { LayoutDashboard, Users, Grid3X3, PenLine, Flower2, ClipboardCheck, Calculator, FileText, BookOpen, Sun, Moon, Globe, LogOut, ChevronRight, Heart, Bell, GraduationCap, Settings, Search, HelpCircle, Command, CornerDownLeft, User as UserIcon, FolderSearch, X, TrendingUp, CalendarCheck, CalendarDays, Calendar as CalendarIconNav, Mail, Shield, Target, Ruler, MessageSquareText, Pencil, Plus, Palette, Leaf, Check, CheckCircle2, AlertTriangle, Info, BookMarked, StickyNote, Award, UserCheck, BarChart3, Trash2, } from 'lucide-react'; import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarInset, SidebarTrigger, SidebarRail, } from '@/components/ui/sidebar'; import { Button } from '@/components/ui/button'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Separator } from '@/components/ui/separator'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from '@/components/ui/breadcrumb'; import { Input } from '@/components/ui/input'; import { Badge } from '@/components/ui/badge'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { ScrollArea } from '@/components/ui/scroll-area'; import { useAppStore, type ViewName } from '@/lib/store'; import { t } from '@/lib/i18n'; import { fetchSchoolYears, fetchStudents, fetchClasses, fetchDBNotifications, markAllNotificationsRead, markSingleNotificationRead, type SchoolYear, type Student, type ClassGroup, type DBNotification, type DBNotificationData } from '@/lib/api'; import OnboardingTour, { isOnboardingCompleted } from '@/components/onboarding-tour'; import KeyboardShortcutsDialog from '@/components/keyboard-shortcuts-dialog'; import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut, } from '@/components/ui/command'; import { toast } from 'sonner'; import { useWebSocket, usePushNotifications, playNotificationSound, getNotificationSoundPref } from '@/lib/websocket'; import { OfflineIndicator, PWAInstallPrompt, useServiceWorker, OfflineSyncManager } from '@/components/offline-indicator'; import DashboardView from './dashboard-view'; import ClassesView from './classes-view'; import CompetencyGridView from './competency-grid-view'; import ProgressEntriesView from './progress-entries-view'; import CompetenceFlowerView from './competence-flower-view'; import AssessmentsView from './assessments-view'; import GradingView from './grading-view'; import ReportsView from './reports-view'; import SettingsView from './settings-view'; import StudentDetailView from './student-detail-view'; import AnalyticsView from './analytics-view'; import MasteryMatrixView from './mastery-matrix-view'; import AttendanceView from './attendance-view'; import LessonPlansView from './lesson-plans-view'; import CalendarView from './calendar-view'; import ParentCommunicationView from './parent-communication-view'; import BehaviorTrackingView from './behavior-tracking-view'; import CurriculumCoverageView from './curriculum-coverage-view'; import RubricLibraryView from './rubric-library-view'; import CommentBankView from './comment-bank-view'; import NotebooksView from './notebooks-view'; import DrawingView from './drawing-view'; type NavItem = { key: ViewName; icon: React.ElementType; labelKey: string }; type NavSection = { id: string; labelKey: string; items: NavItem[] }; const navSections: NavSection[] = [ { id: 'analysis', labelKey: 'polish.nav_analysis', items: [ { key: 'dashboard', icon: LayoutDashboard, labelKey: 'nav.dashboard' }, { key: 'calendar', icon: CalendarIconNav, labelKey: 'nav.calendar' }, { key: 'analytics', icon: TrendingUp, labelKey: 'nav.analytics' }, { key: 'reports', icon: FileText, labelKey: 'nav.reports' }, { key: 'coverage', icon: Target, labelKey: 'nav.coverage' }, ], }, { id: 'teaching', labelKey: 'polish.nav_teaching', items: [ { key: 'classes', icon: Users, labelKey: 'nav.classes' }, { key: 'progress', icon: PenLine, labelKey: 'nav.progress' }, { key: 'flower', icon: Flower2, labelKey: 'nav.flower' }, { key: 'matrix', icon: Grid3X3, labelKey: 'nav.matrix' }, { key: 'assessments', icon: ClipboardCheck, labelKey: 'nav.assessments' }, { key: 'grading', icon: Calculator, labelKey: 'nav.grading' }, { key: 'attendance', icon: CalendarCheck, labelKey: 'nav.attendance' }, { key: 'lesson-plans', icon: CalendarDays, labelKey: 'nav.lesson_plans' }, { key: 'parents', icon: Mail, labelKey: 'nav.parents' }, { key: 'behavior', icon: Shield, labelKey: 'nav.behavior' }, { key: 'rubrics', icon: Ruler, labelKey: 'nav.rubrics' }, { key: 'comments', icon: MessageSquareText, labelKey: 'nav.comments' }, { key: 'notebooks', icon: BookOpen, labelKey: 'nav.notebooks' }, { key: 'drawing', icon: Palette, labelKey: 'nav.drawing' }, ], }, { id: 'setup', labelKey: 'polish.nav_setup', items: [ { key: 'competencies', icon: BookOpen, labelKey: 'nav.competencies' }, { key: 'settings', icon: Settings, labelKey: 'nav.settings' }, ], }, ]; // Student-specific navigation (simplified) const studentNavSections: NavSection[] = [ { id: 'student-main', labelKey: 'student.dashboard_title', items: [ { key: 'dashboard', icon: LayoutDashboard, labelKey: 'nav.dashboard' }, { key: 'notebooks', icon: BookOpen, labelKey: 'nav.student_notebooks' }, { key: 'flower', icon: Flower2, labelKey: 'nav.student_competencies' }, { key: 'grading', icon: Calculator, labelKey: 'nav.student_grades' }, { key: 'attendance', icon: CalendarCheck, labelKey: 'nav.student_attendance' }, { key: 'calendar', icon: CalendarIconNav, labelKey: 'nav.calendar' }, ], }, ]; // Parent-specific navigation (simplified) const parentNavSections: NavSection[] = [ { id: 'parent-main', labelKey: 'parent.dashboard_title', items: [ { key: 'dashboard', icon: LayoutDashboard, labelKey: 'nav.dashboard' }, { key: 'parents', icon: Mail, labelKey: 'parent.my_children' }, { key: 'parents', icon: MessageSquareText, labelKey: 'parent.messages' }, { key: 'calendar', icon: CalendarIconNav, labelKey: 'nav.calendar' }, ], }, ]; function renderView(view: ViewName) { switch (view) { case 'dashboard': return ; case 'classes': return ; case 'competencies': return ; case 'progress': return ; case 'flower': return ; case 'matrix': return ; case 'analytics': return ; case 'assessments': return ; case 'grading': return ; case 'reports': return ; case 'settings': return ; case 'student-detail': return ; case 'attendance': return ; case 'lesson-plans': return ; case 'calendar': return ; case 'parents': return ; case 'behavior': return ; case 'coverage': return ; case 'rubrics': return ; case 'comments': return ; case 'notebooks': return ; case 'drawing': return ; default: return ; } } export default function AppLayout() { const { theme, setTheme } = useTheme(); const currentView = useAppStore((s) => s.currentView); const setCurrentView = useAppStore((s) => s.setCurrentView); const currentUser = useAppStore((s) => s.currentUser); const logout = useAppStore((s) => s.logout); const locale = useAppStore((s) => s.locale); const setLocale = useAppStore((s) => s.setLocale); const storeSchoolYearId = useAppStore((s) => s.schoolYearId); const setSchoolYearId = useAppStore((s) => s.setSchoolYearId); const navigateToStudentDetail = useAppStore((s) => s.navigateToStudentDetail); const setCurrentClass = useAppStore((s) => s.setCurrentClass); const [schoolYears, setSchoolYears] = useState([]); const [selectedYearId, setSelectedYearId] = useState(storeSchoolYearId ?? ''); // Service Worker registration for PWA useServiceWorker(); // WebSocket connection for real-time push notifications const { connected: wsConnected } = useWebSocket(); const { notification: pushNotification } = usePushNotifications(); // Quick search (Cmd+K) state const [searchOpen, setSearchOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [searchStudents, setSearchStudents] = useState([]); const [searchClasses, setSearchClasses] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const [helpOpen, setHelpOpen] = useState(false); const [notifOpen, setNotifOpen] = useState(false); const [notifData, setNotifData] = useState(null); const [notifLoading, setNotifLoading] = useState(false); const [onboardingOpen, setOnboardingOpen] = useState(false); const [shortcutsOpen, setShortcutsOpen] = useState(false); const [recentActions, setRecentActions] = useState<{ key: string; label: string; view?: ViewName; timestamp: number }[]>([]); const initials = currentUser ? `${currentUser.firstName[0]}${currentUser.lastName[0]}`.toUpperCase() : '?'; const displayName = currentUser ? `${currentUser.firstName} ${currentUser.lastName}` : ''; const roleKey = currentUser?.role === 'TEACHER' ? 'role.teacher' : currentUser?.role === 'SCHOOL_ADMIN' ? 'role.school_admin' : currentUser?.role === 'STUDENT' ? 'role.student' : currentUser?.role === 'PARENT' ? 'role.parent' : 'role.super_admin'; const isStudent = currentUser?.role === 'STUDENT'; const isParent = currentUser?.role === 'PARENT'; const activeNavSections = isStudent ? studentNavSections : isParent ? parentNavSections : navSections; const isDemoUser = currentUser?.isDemo === true || (currentUser?.email?.endsWith('@competencetrack.org') && currentUser?.email?.startsWith('demo')); const handleLogout = async () => { await logout(); toast.success(t('auth.logout')); }; const toggleLocale = () => { setLocale(locale === 'de' ? 'en' : 'de'); }; // Track recent actions for command palette const trackAction = useCallback((key: string, label: string, view?: ViewName) => { setRecentActions((prev) => { const filtered = prev.filter((a) => a.key !== key); return [{ key, label, view, timestamp: Date.now() }, ...filtered].slice(0, 5); }); }, []); // View shortcut mapping: Ctrl/Cmd + number -> view const viewShortcuts: Record = { 1: 'dashboard', 2: 'classes', 3: 'progress', 4: 'flower', 5: 'assessments', 6: 'grading', 7: 'reports', 8: 'competencies', 9: 'settings', }; // Global keyboard shortcuts useEffect(() => { function onKey(e: KeyboardEvent) { const isMod = e.metaKey || e.ctrlKey; // Ctrl/Cmd + K: Open command palette if (isMod && !e.shiftKey && e.key.toLowerCase() === 'k') { e.preventDefault(); setSearchOpen((v) => !v); return; } // Ctrl/Cmd + /: Show keyboard shortcuts help if (isMod && e.key.toLowerCase() === '/') { e.preventDefault(); setShortcutsOpen((v) => !v); return; } // Ctrl/Cmd + Shift + N: Add new page to current notebook (when on notebooks view) if (isMod && e.shiftKey && e.key.toLowerCase() === 'n') { e.preventDefault(); if (currentView === 'notebooks') { window.dispatchEvent(new CustomEvent('ct-shortcut', { detail: 'new-page' })); return; } } // Ctrl/Cmd + Shift + A: Toggle archive mode (when on notebooks view) if (isMod && e.shiftKey && e.key.toLowerCase() === 'a') { e.preventDefault(); if (currentView === 'notebooks') { window.dispatchEvent(new CustomEvent('ct-shortcut', { detail: 'archive-toggle' })); return; } } // Ctrl/Cmd + N: Context-dependent new action if (isMod && !e.shiftKey && e.key.toLowerCase() === 'n') { e.preventDefault(); setSearchOpen(false); if (currentView === 'notebooks') { // Open create notebook dialog window.dispatchEvent(new CustomEvent('ct-shortcut', { detail: 'new-notebook' })); } else { // Default: navigate to progress setCurrentView('progress'); trackAction('new-entry', t('shortcuts.new_entry'), 'progress'); toast.info(t('shortcuts.new_entry')); } return; } // Ctrl/Cmd + D: Open drawing canvas if (isMod && e.key.toLowerCase() === 'd') { e.preventDefault(); if (currentView === 'notebooks') { window.dispatchEvent(new CustomEvent('ct-shortcut', { detail: 'drawing' })); } else { setCurrentView('drawing'); trackAction('drawing', t('shortcuts.drawing'), 'drawing'); } return; } // Ctrl/Cmd + E: Export current notebook as PDF if (isMod && e.key.toLowerCase() === 'e') { e.preventDefault(); if (currentView === 'notebooks') { window.dispatchEvent(new CustomEvent('ct-shortcut', { detail: 'export-pdf' })); } return; } // Ctrl/Cmd + .: Close current notebook (go back to library) if (isMod && e.key === '.') { e.preventDefault(); if (currentView === 'notebooks') { window.dispatchEvent(new CustomEvent('ct-shortcut', { detail: 'close-notebook' })); } return; } // Ctrl/Cmd + 1-9: Switch to specific view if (isMod && e.key >= '1' && e.key <= '9') { e.preventDefault(); const view = viewShortcuts[Number(e.key)]; if (view) { setCurrentView(view); trackAction(`view-${view}`, t(`nav.${view}`), view); } return; } // Escape: Close any open dialog if (e.key === 'Escape') { if (searchOpen) { setSearchOpen(false); } else if (shortcutsOpen) { setShortcutsOpen(false); } else if (helpOpen) { setHelpOpen(false); } } } window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [searchOpen, shortcutsOpen, helpOpen, currentView, setCurrentView, trackAction]); // Debounced search useEffect(() => { if (!searchOpen) { setSearchQuery(''); setSearchStudents([]); setSearchClasses([]); return; } if (!searchQuery.trim()) { setSearchStudents([]); setSearchClasses([]); return; } const q = searchQuery.trim(); const handle = setTimeout(async () => { setSearchLoading(true); try { const [students, classes] = await Promise.all([ fetchStudents(currentUser?.schoolId ?? undefined, undefined, q).catch(() => []), fetchClasses(currentUser?.schoolId ?? undefined, undefined).catch(() => []), ]); const lowerQ = q.toLowerCase(); setSearchStudents(students.slice(0, 6)); setSearchClasses(classes.filter((c) => c.name.toLowerCase().includes(lowerQ)).slice(0, 4)); } finally { setSearchLoading(false); } }, 220); return () => clearTimeout(handle); }, [searchQuery, searchOpen, currentUser?.schoolId]); const breadcrumbLabel = useMemo(() => { if (currentView === 'student-detail') return t('nav.student-detail'); return t(`nav.${currentView}`); }, [currentView]); const handleSearchPickStudent = useCallback((s: Student) => { setSearchOpen(false); navigateToStudentDetail(s.id, currentView); }, [navigateToStudentDetail, currentView]); const handleSearchPickClass = useCallback((c: ClassGroup) => { setSearchOpen(false); setCurrentClass(c.id); setCurrentView('classes'); }, [setCurrentClass, setCurrentView]); // Check onboarding status on mount useEffect(() => { if (!isOnboardingCompleted()) { setOnboardingOpen(true); } }, []); // Fetch notifications const loadNotifications = useCallback(async () => { if (!currentUser?.schoolId) return; setNotifLoading(true); try { const data = await fetchDBNotifications(); setNotifData(data); } catch { // ignore } finally { setNotifLoading(false); } }, [currentUser?.schoolId]); useEffect(() => { loadNotifications(); // Refresh notifications every 60 seconds const interval = setInterval(loadNotifications, 60000); return () => clearInterval(interval); }, [loadNotifications]); // Handle push notifications in real-time (after loadNotifications is defined) useEffect(() => { if (!pushNotification) return; // Play notification sound if enabled if (getNotificationSoundPref()) { playNotificationSound(); } // Show toast notification const typeInfo = getNotifTypeInfo(pushNotification.type); toast(typeInfo.label, { description: pushNotification.message, duration: 5000, }); // Refresh notification data to update the bell count loadNotifications(); }, [pushNotification, loadNotifications]); const handleMarkAllRead = useCallback(async () => { if (!notifData || notifData.unreadCount === 0) return; try { await markAllNotificationsRead(); setNotifData((prev) => prev ? { ...prev, notifications: prev.notifications.map((n) => ({ ...n, isRead: true })), unreadCount: 0, } : prev); } catch { // ignore } }, [notifData]); const handleMarkSingleRead = useCallback(async (id: string) => { try { await markSingleNotificationRead(id); setNotifData((prev) => { if (!prev) return prev; const updated = prev.notifications.map((n) => n.id === id ? { ...n, isRead: true } : n ); return { ...prev, notifications: updated, unreadCount: updated.filter((n) => !n.isRead).length, }; }); } catch { // ignore } }, []); const handleNotificationClick = useCallback((n: DBNotification) => { if (!n.isRead) { handleMarkSingleRead(n.id); } setNotifOpen(false); if (n.actionUrl) { setCurrentView(n.actionUrl as ViewName); } }, [handleMarkSingleRead, setCurrentView]); // Get icon and color for notification type const getNotifTypeInfo = (type: string) => { switch (type) { case 'ASSESSMENT_DUE': return { icon: AlertTriangle, bg: 'bg-amber-100/80 dark:bg-amber-900/20', text: 'text-amber-600 dark:text-amber-400', label: t('notifications.type_assessment_due') }; case 'MISSING_OBSERVATION': return { icon: Info, bg: 'bg-teal-100/80 dark:bg-teal-900/20', text: 'text-teal-600 dark:text-teal-400', label: t('notifications.type_missing_observation') }; case 'NOTEBOOK_SHARED': return { icon: BookMarked, bg: 'bg-emerald-100/80 dark:bg-emerald-900/20', text: 'text-emerald-600 dark:text-emerald-400', label: t('notifications.type_notebook_shared') }; case 'BEHAVIOR_ALERT': return { icon: Shield, bg: 'bg-rose-100/80 dark:bg-rose-900/20', text: 'text-rose-600 dark:text-rose-400', label: t('notifications.type_behavior_alert') }; case 'GRADE_COMPUTED': return { icon: Award, bg: 'bg-violet-100/80 dark:bg-violet-900/20', text: 'text-violet-600 dark:text-violet-400', label: t('notifications.type_grade_computed') }; case 'ATTENDANCE_ALERT': return { icon: UserCheck, bg: 'bg-orange-100/80 dark:bg-orange-900/20', text: 'text-orange-600 dark:text-orange-400', label: t('notifications.type_attendance_alert') }; case 'REPORT_READY': return { icon: FileText, bg: 'bg-cyan-100/80 dark:bg-cyan-900/20', text: 'text-cyan-600 dark:text-cyan-400', label: t('notifications.type_report_ready') }; default: return { icon: Bell, bg: 'bg-gray-100/80 dark:bg-gray-900/20', text: 'text-gray-600 dark:text-gray-400', label: t('notifications.type_general') }; } }; // Group notifications by date const groupedNotifications = useMemo(() => { if (!notifData?.notifications) return []; const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const yesterday = new Date(today.getTime() - 86400000); const weekAgo = new Date(today.getTime() - 7 * 86400000); const groups: { label: string; notifications: DBNotification[] }[] = []; const todayItems: DBNotification[] = []; const yesterdayItems: DBNotification[] = []; const weekItems: DBNotification[] = []; const earlierItems: DBNotification[] = []; for (const n of notifData.notifications) { const date = new Date(n.createdAt); if (date >= today) todayItems.push(n); else if (date >= yesterday) yesterdayItems.push(n); else if (date >= weekAgo) weekItems.push(n); else earlierItems.push(n); } if (todayItems.length > 0) groups.push({ label: t('notifications.today'), notifications: todayItems }); if (yesterdayItems.length > 0) groups.push({ label: t('notifications.yesterday'), notifications: yesterdayItems }); if (weekItems.length > 0) groups.push({ label: t('notifications.this_week'), notifications: weekItems }); if (earlierItems.length > 0) groups.push({ label: t('notifications.earlier'), notifications: earlierItems }); return groups; }, [notifData?.notifications]); useEffect(() => { async function loadYears() { if (!currentUser?.schoolId) return; try { const years = await fetchSchoolYears(currentUser.schoolId); setSchoolYears(years); if (years.length > 0 && !selectedYearId) { setSelectedYearId(years[0].id); setSchoolYearId(years[0].id); } } catch { // ignore } } loadYears(); }, [currentUser?.schoolId]); return ( {/* Offline indicator bar */} {/* PWA install prompt */} {/* Offline sync manager */}

CompetenceTrack

{t('app.subtitle')}

{activeNavSections.map((section) => ( {section.id === 'analysis' && } {section.id === 'teaching' && } {section.id === 'setup' && } {section.id === 'student-main' && } {t(section.labelKey)} {section.items.map((item) => ( setCurrentView(item.key)} tooltip={t(item.labelKey)} className={`group relative transition-all duration-200 min-h-[44px] ${ currentView === item.key ? 'bg-gradient-to-r from-emerald-100/90 via-emerald-50/70 to-teal-50/50 dark:from-emerald-900/40 dark:via-emerald-900/25 dark:to-teal-900/15 text-emerald-700 dark:text-emerald-300 font-semibold border-l-3 border-emerald-500 rounded-l-none shadow-sm shadow-emerald-100/40 dark:shadow-emerald-900/20' : 'text-gray-600 dark:text-gray-400 hover:text-emerald-600 dark:hover:text-emerald-400 hover:bg-emerald-50/50 dark:hover:bg-emerald-900/10' }`} > {currentView === item.key && ( )} {t(item.labelKey)} ))} ))} {/* Environmental message */}

{t('sidebar.eco_message')}

{initials}

{displayName} {isDemoUser && ( {t('badge.demo')} )}

{t(roleKey)}

setCurrentView('dashboard')} > {t('polish.breadcrumb_home')} {breadcrumbLabel} {/* Right-aligned action group: Search | Help | School Year | Bell | Lang | Avatar */}
{/* Quick Search (Cmd+K) */} {/* Help button — opens help dialog, long-press/Ctrl+click opens onboarding tour */} {/* School Year Selector */} {schoolYears.length > 0 && ( )} {/* Notification Bell with Popover */} {/* Header */}

{t('notifications.title')}

{notifData && notifData.unreadCount > 0 && ( )}
{/* Content */} {notifLoading && (

Loading…

)} {!notifLoading && notifData && notifData.notifications.length === 0 && (

{t('notifications.empty')}

)} {!notifLoading && groupedNotifications.map((group) => (

{group.label}

{group.notifications.map((n: DBNotification, i: number) => { const typeInfo = getNotifTypeInfo(n.type); const TypeIcon = typeInfo.icon; return ( handleNotificationClick(n)} className={`flex items-start gap-3 px-2 py-2.5 rounded-lg transition-colors cursor-pointer min-h-[44px] hover:bg-emerald-50/50 dark:hover:bg-emerald-900/10 ${!n.isRead ? 'bg-emerald-50/30 dark:bg-emerald-900/5' : 'opacity-70'}`} >

{n.title}

{n.message}

{typeInfo.label}
{!n.isRead && ( )}
); })}
))}
{/* Footer */} {!notifLoading && notifData && notifData.notifications.length > 0 && (
)}
{/* Language toggle (mobile: just flag) */} {/* User avatar */} {initials}
{renderView(currentView)}
CompetenceTrack — {t('footer.copyright')} {t('footer.version')} · {t('footer.oss')} {t('footer.version')}
{/* ─── Command Palette (Cmd+K) ─────────────────────────── */}

{t('polish.no_results')}

{/* Recent actions */} {!searchQuery.trim() && recentActions.length > 0 && ( {recentActions.map((action) => ( { if (action.view) { setCurrentView(action.view); trackAction(action.key, action.label, action.view); } setSearchOpen(false); }} > {action.label} {t('shortcuts.recent_actions')} ))} )} {/* Quick actions (shown when no search query) */} {!searchQuery.trim() && ( { setCurrentView('progress'); trackAction('new-entry', t('shortcuts.new_entry'), 'progress'); setSearchOpen(false); }} > {t('shortcuts.new_entry')} N { setCurrentView('assessments'); trackAction('new-assessment', t('action.create_assessment'), 'assessments'); setSearchOpen(false); }} > {t('action.create_assessment')} 5 { setCurrentView('reports'); trackAction('new-report', t('action.generate_report'), 'reports'); setSearchOpen(false); }} > {t('action.generate_report')} 7 { setShortcutsOpen(true); setSearchOpen(false); }} > {t('shortcuts.show_shortcuts')} / )} {/* Available views (shown when no search query) */} {!searchQuery.trim() && ( {navSections.flatMap((section) => section.items).map((item, idx) => ( { setCurrentView(item.key); trackAction(`view-${item.key}`, t(item.labelKey), item.key); setSearchOpen(false); }} > {t(item.labelKey)} {idx < 9 && ( {idx + 1} )} ))} )} {/* Search results: Students */} {searchStudents.length > 0 && ( {searchStudents.map((s) => ( handleSearchPickStudent(s)} >
{s.firstName[0]}{s.lastName[0]}
{s.firstName} {s.lastName} {s.enrollments?.[0]?.classGroup?.name ?? ''}
))}
)} {/* Search results: Classes */} {searchClasses.length > 0 && ( {searchClasses.map((c) => ( handleSearchPickClass(c)} > {c.name} {t('label.grade')} {c.gradeLevel} ))} )}
{/* ─── Keyboard Shortcuts Dialog ────────────────────── */} {/* ─── Help Modal ─────────────────────────────────────────── */} {t('polish.help')} CompetenceTrack · {t('footer.version')}
{[ { kbd: '⌘K', label: t('shortcuts.command_palette') }, { kbd: '⌘/', label: t('shortcuts.show_shortcuts') }, { kbd: '⌘N', label: t('shortcuts.new_entry') }, { kbd: '⌘1-9', label: t('shortcuts.switch_view') }, { kbd: 'Esc', label: t('shortcuts.close_dialog') }, ].map((row) => (
{row.label} {row.kbd}
))}

{t('app.tagline')}

{/* Restart onboarding tour */}
{/* ─── Onboarding Tour ─────────────────────────────────────── */} setOnboardingOpen(false)} />
); }