refactor: split AppShell, match-detail panels, extract useCompareData

AppShell.tsx: 627→116 lines
- appShellConfig.ts: NavItem/WorkspaceConfig types, WORKSPACE_CONFIG, nav helpers
- AppShellSidebar.tsx: Sidebar component with visual constants
- AppShellTopBar.tsx: TopBar component

Match-detail panels:
- ScoreBreakdownPanel: 377→303 lines (scoreBreakdownConstants.ts + CriterionRow.tsx extracted)
- LocationIntelligencePanel: 383→333 lines (SoftFactorBar.tsx extracted)
- FutureAvailabilityContextPanel: 386→351 lines (futureAvailabilityConstants.tsx extracted)

Compare.tsx: 485→441 lines
- useCompareData hook: all queries and derived state extracted to hooks/useCompareData.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-24 01:00:38 +02:00
parent ae82d0e6a0
commit 0bacd188d6
13 changed files with 792 additions and 741 deletions
+92
View File
@@ -0,0 +1,92 @@
import { Box, Typography, Chip, Button, IconButton } from '@mui/material'
import { Sparkles, Menu } from 'lucide-react'
import { WorkspaceType } from '../../domain/enums'
import { useAssistantStore } from '../../stores/assistantStore'
import { OrganizationContextBadge } from './OrganizationContextBadge'
import { NotificationButton } from './NotificationButton'
import { UserMenu } from './UserMenu'
import { WORKSPACE_CONFIG, getPageNameFromPath } from './appShellConfig'
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface TopBarProps {
activeWorkspace: WorkspaceType
pathname: string
onMenuClick?: () => void
isMobile?: boolean
}
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
export function TopBar({ activeWorkspace, pathname, onMenuClick, isMobile }: TopBarProps) {
const config = WORKSPACE_CONFIG[activeWorkspace]
const pageName = getPageNameFromPath(pathname)
const openAssistant = useAssistantStore(s => s.open)
return (
<Box
component="header"
sx={{
height: 56,
flexShrink: 0,
backgroundColor: '#ffffff',
borderBottom: '1px solid #e2e8f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
px: { xs: 1.5, sm: 3 },
}}
>
{/* Left side */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{isMobile && (
<IconButton size="small" onClick={onMenuClick} sx={{ mr: 0.5 }}>
<Menu size={20} />
</IconButton>
)}
<Chip
label={config.label}
size="small"
sx={{
backgroundColor: config.chipColor,
color: '#ffffff',
fontWeight: 600,
fontSize: '0.7rem',
height: 22,
}}
/>
<Typography
variant="body1"
sx={{ fontWeight: 500, color: '#1e293b', fontSize: { xs: '0.85rem', sm: '0.9375rem' } }}
>
{pageName}
</Typography>
</Box>
{/* Right side */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: { xs: 0.5, sm: 1 } }}>
<Box sx={{ display: { xs: 'none', sm: 'flex' } }}>
<OrganizationContextBadge />
</Box>
<Button
variant="outlined"
size="small"
startIcon={<Sparkles size={14} />}
onClick={openAssistant}
sx={{ textTransform: 'none', fontSize: '0.8125rem', display: { xs: 'none', md: 'flex' } }}
>
AI Assistent
</Button>
<IconButton size="small" onClick={openAssistant} sx={{ display: { xs: 'flex', md: 'none' } }}>
<Sparkles size={18} />
</IconButton>
<NotificationButton />
<UserMenu />
</Box>
</Box>
)
}