feat: Verwaltungszugang refactor — lease data, 3-tab detail, market signals

- Dashboard: remove QuickActionPanel, StrongMatchOverview, FutureSignalWidget,
  DataQualityWidget, header buttons, Marktchancen nav; KpiGrid → 4 cards
- Property domain: 9 new fields (leaseTerm, leaseStartDate/End, breakoutOption,
  currentTenant, importedFrom, importedAt, lastUpdatedAt)
- Mock data: annual CHF/m²/Jahr prices (×12), Swiss images, tenant/lease data
  for all 10 VERIFIED_PORTFOLIO properties; need budgets updated to annual
- PropertyTable: swap columns — add Aktueller Mieter, Mietlaufzeit,
  Breakoutoption, Breakoutoption Zeitpunkt; remove Verfügbarkeit, Konfidenz, Quelle
- PropertyDetailView: 3 tabs (Übersicht, Matchability, Marktsignale) with
  inline edit mode, NeedMatchCard list, PropertyMarketSignalsTab
- New: marketReport domain + mock data + service, NeedMatchCard,
  PropertyMarketSignalsTab with 4 sections + PDF button
- matchService: getNeedMatchesForProperty(propertyId, {minScore})

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-19 15:43:52 +02:00
parent 52a2693cad
commit 920ce8eb09
17 changed files with 1036 additions and 476 deletions
+6 -101
View File
@@ -1,4 +1,4 @@
import { Box, Alert, Button, Typography } from '@mui/material'
import { Box, Button, Typography } from '@mui/material'
import { useNavigate } from 'react-router'
import { useSupplyDashboard } from '../../hooks/useSupplyDashboard'
import { useSessionStore } from '../../stores/sessionStore'
@@ -6,21 +6,10 @@ import { UserRole } from '../../domain/enums'
import {
DashboardHeader,
KpiGrid,
StrongMatchOverview,
DataQualityWidget,
FutureSignalWidget,
ReviewTaskWidget,
QuickActionPanel,
DashboardSkeleton,
} from '../../components/supply'
function WidgetError({ label }: { label: string }) {
return (
<Alert severity="error" sx={{ height: '100%' }}>
{label} konnte nicht geladen werden.
</Alert>
)
}
export default function SupplyDashboard() {
const { data, isLoading, isError, refetch } = useSupplyDashboard()
@@ -30,11 +19,6 @@ export default function SupplyDashboard() {
const role = currentUser?.role ?? UserRole.DEMAND_USER
const isReviewer = role === UserRole.REVIEWER
const isOwnerViewer = role === UserRole.OWNER_VIEWER
const canSeeMatches = role !== UserRole.OWNER_VIEWER && role !== UserRole.DEMAND_USER
const canSeeOperations =
role === UserRole.SUPER_ADMIN ||
role === UserRole.ORGANIZATION_ADMIN ||
role === UserRole.REVIEWER
if (isLoading) return <DashboardSkeleton />
@@ -98,91 +82,12 @@ export default function SupplyDashboard() {
<KpiGrid data={data} />
)}
{/* REVIEWER: Review Queue first, then Matches */}
{isReviewer && (
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3 }}>
{data.reviewTasks !== null ? (
<ReviewTaskWidget
tasks={data.reviewTasks}
onNavigate={() => navigate('/ops/review-queue')}
/>
) : (
<WidgetError label="Review Tasks" />
)}
{canSeeMatches &&
(data.strongMatches !== null ? (
<StrongMatchOverview
matches={data.strongMatches}
onNavigate={() => navigate('/supply/match-center')}
/>
) : (
<WidgetError label="Starke Matches" />
))}
</Box>
{isReviewer && data.reviewTasks !== null && (
<ReviewTaskWidget
tasks={data.reviewTasks}
onNavigate={() => navigate('/ops/review-queue')}
/>
)}
{/* Default: Matches + Data Quality */}
{!isReviewer && canSeeMatches && (
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3 }}>
{data.strongMatches !== null ? (
<StrongMatchOverview
matches={data.strongMatches}
onNavigate={() => navigate('/supply/match-center')}
/>
) : (
<WidgetError label="Starke Matches" />
)}
{data.dataQuality !== null ? (
<DataQualityWidget
summary={data.dataQuality}
onNavigate={() => navigate('/supply/data-quality')}
/>
) : (
<WidgetError label="Datenqualität" />
)}
</Box>
)}
{canSeeMatches && data.strongMatches?.length === 0 && (
<Alert
severity="info"
action={
<Button size="small" onClick={() => navigate('/demand/ai-search')}>
Bedarfsprofil erstellen
</Button>
}
>
Noch keine Matches vorhanden. Erstellen Sie ein Bedarfsprofil, um passende Objekte zu
finden.
</Alert>
)}
{!isOwnerViewer && (
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 3 }}>
{data.futureSignals !== null ? (
<FutureSignalWidget summary={data.futureSignals} />
) : (
<WidgetError label="Marktsignale" />
)}
{canSeeOperations && !isReviewer ? (
data.reviewTasks !== null ? (
<ReviewTaskWidget
tasks={data.reviewTasks}
onNavigate={() => navigate('/ops/review-queue')}
/>
) : (
<WidgetError label="Review Tasks" />
)
) : !canSeeOperations && data.dataQuality !== null ? (
<DataQualityWidget
summary={data.dataQuality}
onNavigate={() => navigate('/supply/data-quality')}
/>
) : null}
</Box>
)}
<QuickActionPanel />
</Box>
)
}