diff --git a/src/App.tsx b/src/App.tsx
index a1d85a6..c752a1f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -17,6 +17,7 @@ const Shortlists = lazy(() => import('./pages/demand/Shortlists'))
const ReviewQueue = lazy(() => import('./pages/ops/ReviewQueue'))
const AIMonitoring = lazy(() => import('./pages/ops/AIMonitoring'))
const Governance = lazy(() => import('./pages/ops/Governance'))
+const ActivityTimeline = lazy(() => import('./pages/ops/ActivityTimeline'))
function App() {
return (
@@ -43,6 +44,7 @@ function App() {
} />
} />
} />
+ } />
} />
diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx
index 4a7a722..08c53ca 100644
--- a/src/components/layout/AppShell.tsx
+++ b/src/components/layout/AppShell.tsx
@@ -28,9 +28,14 @@ import {
ChevronLeft,
ChevronRight,
Sparkles,
- Bell,
+ Clock,
} from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
+import { OrganizationContextBadge } from './OrganizationContextBadge'
+import { UserMenu } from './UserMenu'
+import { NotificationButton } from './NotificationButton'
+import { RightContextPanel } from './RightContextPanel'
+import { CompareTray } from './CompareTray'
// ---------------------------------------------------------------------------
// Types
@@ -93,6 +98,7 @@ const WORKSPACE_CONFIG: Record = {
{ path: '/ops/review-queue', label: 'Review Queue', icon: ClipboardList },
{ path: '/ops/ai-monitoring', label: 'AI Monitoring', icon: Activity },
{ path: '/ops/governance', label: 'Governance', icon: Shield },
+ { path: '/ops/activity-timeline', label: 'Aktivitäts-Timeline', icon: Clock },
],
},
}
@@ -458,6 +464,7 @@ function TopBar({ activeWorkspace, pathname }: TopBarProps) {
{/* Right side */}
+
-
-
-
-
- AU
-
+
+
)
@@ -521,7 +524,11 @@ export function AppShell() {
+
+
+
+
)
}
diff --git a/src/components/layout/CompareTray.tsx b/src/components/layout/CompareTray.tsx
new file mode 100644
index 0000000..ebd899b
--- /dev/null
+++ b/src/components/layout/CompareTray.tsx
@@ -0,0 +1,73 @@
+import { useEffect } from 'react'
+import { useNavigate } from 'react-router'
+import { Box, Button, Chip, Typography } from '@mui/material'
+import { useCompareStore } from '../../stores/compareStore'
+import { useLayoutStore } from '../../stores/layoutStore'
+
+export function CompareTray() {
+ const { compareTray, removeFromCompare, clearCompare } = useCompareStore()
+ const { setCompareTrayVisible } = useLayoutStore()
+ const navigate = useNavigate()
+
+ useEffect(() => {
+ setCompareTrayVisible(compareTray.length > 0)
+ }, [compareTray.length, setCompareTrayVisible])
+
+ return (
+ 0 ? 'translateY(0)' : 'translateY(100%)',
+ transition: 'transform 0.25s ease',
+ }}
+ >
+
+ Vergleich ({compareTray.length}/3)
+
+
+
+ {compareTray.map((id, i) => (
+ removeFromCompare(id)}
+ sx={{
+ bgcolor: 'rgba(255,255,255,0.15)',
+ color: '#fff',
+ '& .MuiChip-deleteIcon': { color: 'rgba(255,255,255,0.6)' },
+ }}
+ />
+ ))}
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/layout/NotificationButton.tsx b/src/components/layout/NotificationButton.tsx
new file mode 100644
index 0000000..92ffbed
--- /dev/null
+++ b/src/components/layout/NotificationButton.tsx
@@ -0,0 +1,39 @@
+import { useState } from 'react'
+import { Badge, IconButton, Popover, Typography } from '@mui/material'
+import { Bell } from 'lucide-react'
+
+export function NotificationButton() {
+ const [anchorEl, setAnchorEl] = useState(null)
+
+ function handleOpen(e: React.MouseEvent) {
+ setAnchorEl(e.currentTarget)
+ }
+
+ function handleClose() {
+ setAnchorEl(null)
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+ Benachrichtigungen
+
+ Keine neuen Benachrichtigungen
+
+
+ >
+ )
+}
diff --git a/src/components/layout/OrganizationContextBadge.tsx b/src/components/layout/OrganizationContextBadge.tsx
new file mode 100644
index 0000000..0ca238c
--- /dev/null
+++ b/src/components/layout/OrganizationContextBadge.tsx
@@ -0,0 +1,19 @@
+import { Chip } from '@mui/material'
+import { Building2 } from 'lucide-react'
+import { useSessionStore } from '../../stores/sessionStore'
+
+export function OrganizationContextBadge() {
+ const { currentUser } = useSessionStore()
+
+ if (!currentUser?.organizationName) return null
+
+ return (
+ }
+ label={currentUser.organizationName}
+ sx={{ fontSize: '0.7rem', height: 22, color: '#64748b', borderColor: '#e2e8f0' }}
+ />
+ )
+}
diff --git a/src/components/layout/PageHeader.tsx b/src/components/layout/PageHeader.tsx
new file mode 100644
index 0000000..174f722
--- /dev/null
+++ b/src/components/layout/PageHeader.tsx
@@ -0,0 +1,76 @@
+import { Box, Breadcrumbs, Typography } from '@mui/material'
+import type { SxProps, Theme } from '@mui/material'
+import type { ReactNode } from 'react'
+import { NavLink } from 'react-router'
+
+interface BreadcrumbItem {
+ label: string
+ href?: string
+}
+
+interface PageHeaderProps {
+ title: string
+ subtitle?: string
+ breadcrumbs?: BreadcrumbItem[]
+ primaryAction?: ReactNode
+ secondaryActions?: ReactNode
+ badge?: ReactNode
+ sx?: SxProps
+}
+
+export function PageHeader({
+ title,
+ subtitle,
+ breadcrumbs,
+ primaryAction,
+ secondaryActions,
+ badge,
+ sx,
+}: PageHeaderProps) {
+ return (
+
+ {breadcrumbs && breadcrumbs.length > 0 && (
+
+ {breadcrumbs.map((crumb) =>
+ crumb.href ? (
+
+ {crumb.label}
+
+ ) : (
+
+ {crumb.label}
+
+ ),
+ )}
+
+ )}
+
+
+
+
+
+ {title}
+
+ {badge}
+
+ {subtitle && (
+
+ {subtitle}
+
+ )}
+
+
+ {(primaryAction ?? secondaryActions) && (
+
+ {secondaryActions}
+ {primaryAction}
+
+ )}
+
+
+ )
+}
diff --git a/src/components/layout/RightContextPanel.tsx b/src/components/layout/RightContextPanel.tsx
new file mode 100644
index 0000000..cb6d123
--- /dev/null
+++ b/src/components/layout/RightContextPanel.tsx
@@ -0,0 +1,70 @@
+import { Box, Divider, IconButton, Typography } from '@mui/material'
+import { X } from 'lucide-react'
+import { useLayoutStore } from '../../stores/layoutStore'
+import type { RightPanelContentType } from '../../stores/layoutStore'
+
+const PANEL_TITLES: Record = {
+ ai_context: 'KI Kontext',
+ detail_preview: 'Detail Vorschau',
+ compare_preview: 'Vergleich Vorschau',
+ activity_feed: 'Aktivitäts-Feed',
+}
+
+const PANEL_PLACEHOLDERS: Record = {
+ ai_context: 'KI-Kontext wird geladen...',
+ detail_preview: 'Kein Objekt ausgewählt.',
+ compare_preview: 'Vergleichsvorschau nicht verfügbar.',
+ activity_feed: 'Keine Aktivitäten vorhanden.',
+}
+
+export function RightContextPanel() {
+ const { isRightPanelOpen, rightPanelContentType, closeRightPanel } = useLayoutStore()
+
+ const title = rightPanelContentType ? PANEL_TITLES[rightPanelContentType] : ''
+ const placeholder = rightPanelContentType ? PANEL_PLACEHOLDERS[rightPanelContentType] : ''
+
+ return (
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+ {placeholder}
+
+
+
+ )
+}
diff --git a/src/components/layout/UserMenu.tsx b/src/components/layout/UserMenu.tsx
new file mode 100644
index 0000000..cff3b95
--- /dev/null
+++ b/src/components/layout/UserMenu.tsx
@@ -0,0 +1,74 @@
+import { useState } from 'react'
+import { useNavigate } from 'react-router'
+import { Avatar, Divider, IconButton, ListItemIcon, Menu, MenuItem, Typography } from '@mui/material'
+import { LogOut, Settings, User } from 'lucide-react'
+import { useSessionStore } from '../../stores/sessionStore'
+
+function getUserInitials(name: string): string {
+ return name
+ .split(' ')
+ .map((n) => n[0])
+ .join('')
+ .toUpperCase()
+ .slice(0, 2)
+}
+
+export function UserMenu() {
+ const [anchorEl, setAnchorEl] = useState(null)
+ const { currentUser, logout } = useSessionStore()
+ const navigate = useNavigate()
+
+ function handleOpen(e: React.MouseEvent) {
+ setAnchorEl(e.currentTarget)
+ }
+
+ function handleClose() {
+ setAnchorEl(null)
+ }
+
+ function handleLogout() {
+ handleClose()
+ logout()
+ navigate('/')
+ }
+
+ const initials = getUserInitials(currentUser?.name ?? 'U')
+
+ return (
+ <>
+
+
+ {initials}
+
+
+
+
+ >
+ )
+}
diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts
index 391bbd0..90cbbd0 100644
--- a/src/components/layout/index.ts
+++ b/src/components/layout/index.ts
@@ -1 +1,7 @@
export { AppShell } from './AppShell'
+export { PageHeader } from './PageHeader'
+export { RightContextPanel } from './RightContextPanel'
+export { CompareTray } from './CompareTray'
+export { UserMenu } from './UserMenu'
+export { NotificationButton } from './NotificationButton'
+export { OrganizationContextBadge } from './OrganizationContextBadge'
diff --git a/src/pages/ops/ActivityTimeline.tsx b/src/pages/ops/ActivityTimeline.tsx
new file mode 100644
index 0000000..bdcedd8
--- /dev/null
+++ b/src/pages/ops/ActivityTimeline.tsx
@@ -0,0 +1,16 @@
+import { Box, Typography } from '@mui/material'
+import { PageHeader } from '../../components/layout'
+
+export default function ActivityTimeline() {
+ return (
+
+
+
+ Timeline wird implementiert...
+
+
+ )
+}