Files
breakpilot-lehrer/studio-v2/components/Sidebar.tsx
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

238 lines
13 KiB
TypeScript

'use client'
import { useState } from 'react'
import { useRouter, usePathname } from 'next/navigation'
import { BPIcon } from '@/components/Logo'
import { useLanguage } from '@/lib/LanguageContext'
import { useTheme } from '@/lib/ThemeContext'
import { useAlerts } from '@/lib/AlertsContext'
import { useAlertsB2B } from '@/lib/AlertsB2BContext'
import { useMessages } from '@/lib/MessagesContext'
import { UserMenu } from '@/components/UserMenu'
interface SidebarProps {
selectedTab?: string
onTabChange?: (tab: string) => void
}
export function Sidebar({ selectedTab = 'dashboard', onTabChange }: SidebarProps) {
const [sidebarHovered, setSidebarHovered] = useState(false)
const { t } = useLanguage()
const { isDark } = useTheme()
const { unreadCount } = useAlerts()
const { unreadCount: b2bUnreadCount } = useAlertsB2B()
const { unreadCount: messagesUnreadCount } = useMessages()
const router = useRouter()
const pathname = usePathname()
const navItems = [
{ id: 'dashboard', labelKey: 'nav_dashboard', href: '/', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
)},
{ id: 'dokumente', labelKey: 'nav_dokumente', href: '/', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
)},
{ id: 'klausuren', labelKey: 'nav_klausuren', href: '/', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
)},
{ id: 'analytics', labelKey: 'nav_analytics', href: '/', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
)},
{ id: 'alerts', labelKey: 'nav_alerts', href: '/alerts', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
), showBadge: true },
{ id: 'alerts-b2b', labelKey: 'nav_alerts_b2b', href: '/alerts-b2b', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
), showB2BBadge: true },
{ id: 'messages', labelKey: 'nav_messages', href: '/messages', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" />
</svg>
), showMessagesBadge: true },
{ id: 'vokabeln', labelKey: 'nav_vokabeln', href: '/vocab-worksheet', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
)},
{ id: 'worksheet-editor', labelKey: 'nav_worksheet_editor', href: '/worksheet-editor', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
)},
{ id: 'worksheet-cleanup', labelKey: 'nav_worksheet_cleanup', href: '/worksheet-cleanup', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
</svg>
)},
{ id: 'korrektur', labelKey: 'nav_korrektur', href: '/korrektur', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
)},
{ id: 'companion', labelKey: 'nav_companion', href: '/companion', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M12 6v6l4 2" />
</svg>
)},
{ id: 'meet', labelKey: 'nav_meet', href: '/meet', icon: (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} 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>
)},
]
const handleNavClick = (item: typeof navItems[0]) => {
// Check if this is an external page (has a specific href)
if (item.href !== '/') {
router.push(item.href)
} else if (item.id === 'vokabeln') {
router.push('/vocab-worksheet')
} else if (item.id === 'meet') {
router.push('/meet')
} else if (item.id === 'alerts') {
router.push('/alerts')
} else {
// For dashboard tabs, either navigate or call the callback
if (pathname !== '/') {
router.push('/')
}
if (onTabChange) {
onTabChange(item.id)
}
}
}
// Determine active item based on pathname or selectedTab
const getActiveItem = () => {
if (pathname === '/companion') return 'companion'
if (pathname === '/meet') return 'meet'
if (pathname === '/vocab-worksheet') return 'vokabeln'
if (pathname === '/worksheet-editor') return 'worksheet-editor'
if (pathname === '/worksheet-cleanup') return 'worksheet-cleanup'
if (pathname === '/magic-help') return 'magic-help'
if (pathname === '/alerts') return 'alerts'
if (pathname === '/alerts-b2b') return 'alerts-b2b'
if (pathname === '/messages') return 'messages'
if (pathname?.startsWith('/korrektur')) return 'korrektur'
return selectedTab
}
const activeItem = getActiveItem()
return (
<aside
className="flex-shrink-0 w-[72px] hover:w-[200px] transition-all duration-300 group"
onMouseEnter={() => setSidebarHovered(true)}
onMouseLeave={() => setSidebarHovered(false)}
>
<div className={`sticky top-4 h-[calc(100vh-32px)] backdrop-blur-2xl rounded-3xl border flex flex-col p-3 overflow-hidden ${
isDark
? 'bg-white/10 border-white/20'
: 'bg-white/70 border-black/10 shadow-xl'
}`}>
{/* Logo - Cupertino Clean */}
<div className="mb-8">
<div className="flex items-center gap-4">
<BPIcon variant="cupertino" size={48} className="flex-shrink-0" />
<div className="opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap">
<span className={`font-semibold text-lg ${isDark ? 'text-white' : 'text-slate-900'}`}>BreakPilot</span>
<span className={`block text-xs ${isDark ? 'text-white/60' : 'text-slate-500'}`}>Studio v2</span>
</div>
</div>
</div>
{/* Navigation */}
<nav className="flex-1 space-y-2">
{navItems.map((item) => (
<button
key={item.id}
onClick={() => handleNavClick(item)}
className={`relative w-full flex items-center gap-4 p-3 rounded-2xl transition-all ${
activeItem === item.id
? isDark
? 'bg-white/20 text-white shadow-lg'
: 'bg-indigo-100 text-indigo-900 shadow-lg'
: item.id === 'alerts' && unreadCount > 0
? isDark
? 'text-amber-400 hover:bg-amber-500/10'
: 'text-amber-600 hover:bg-amber-50'
: item.id === 'alerts-b2b' && b2bUnreadCount > 0
? isDark
? 'text-blue-400 hover:bg-blue-500/10'
: 'text-blue-600 hover:bg-blue-50'
: item.id === 'messages' && messagesUnreadCount > 0
? isDark
? 'text-green-400 hover:bg-green-500/10'
: 'text-green-600 hover:bg-green-50'
: isDark
? 'text-white/60 hover:bg-white/10 hover:text-white'
: 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
}`}
>
<span className="relative flex-shrink-0">
{item.icon}
{item.id === 'alerts' && unreadCount > 0 && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-red-500 rounded-full text-[10px] text-white flex items-center justify-center font-medium">
{unreadCount > 9 ? '9+' : unreadCount}
</span>
)}
{item.id === 'alerts-b2b' && b2bUnreadCount > 0 && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-blue-500 rounded-full text-[10px] text-white flex items-center justify-center font-medium">
{b2bUnreadCount > 9 ? '9+' : b2bUnreadCount}
</span>
)}
{item.id === 'messages' && messagesUnreadCount > 0 && (
<span className="absolute -top-1 -right-1 w-4 h-4 bg-green-500 rounded-full text-[10px] text-white flex items-center justify-center font-medium">
{messagesUnreadCount > 9 ? '9+' : messagesUnreadCount}
</span>
)}
</span>
<span className="font-medium opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap flex items-center gap-2">
{t(item.labelKey)}
{item.id === 'alerts' && unreadCount > 0 && (
<span className="px-1.5 py-0.5 text-[10px] rounded-full bg-amber-500/20 text-amber-500">
{unreadCount}
</span>
)}
{item.id === 'alerts-b2b' && b2bUnreadCount > 0 && (
<span className="px-1.5 py-0.5 text-[10px] rounded-full bg-blue-500/20 text-blue-500">
{b2bUnreadCount}
</span>
)}
{item.id === 'messages' && messagesUnreadCount > 0 && (
<span className="px-1.5 py-0.5 text-[10px] rounded-full bg-green-500/20 text-green-500">
{messagesUnreadCount}
</span>
)}
</span>
</button>
))}
</nav>
{/* User Menu */}
<div className={`pt-4 border-t ${isDark ? 'border-white/10' : 'border-slate-200'}`}>
<UserMenu
userName="Lehrer Max"
userEmail="max@schule.de"
userInitials="LM"
isExpanded={sidebarHovered}
/>
</div>
</div>
</aside>
)
}