fix: resolve all TypeScript errors and refactor oversized components

- Fix MUI v9 API: PaperProps/InputLabelProps/inputProps → slotProps in 6 components
- Add ExternalMarketResult alias, PropertyUnit import, OPERATIONS workspace config
- Fix TradeOff.description → .concern, ScoreFactor.label → .criterion
- Make schattenmarktRelease.leadTimeMonths optional, fix mock-data enum values
- Fix useMatchDetailData query typing, weightingService missing WeightProfile keys
- Split Pipeline/Compare/MatchDetail/IntelligenceMatchCard into sub-components
- Fix all test fixtures (CreateNeedInput, CreatePropertyInput, TradeOffInput, etc.)
- Add vercel.json for deployment, zero tsc errors

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-26 00:08:35 +02:00
parent 98d2770054
commit 36570c5bdc
52 changed files with 269 additions and 143 deletions
+2 -3
View File
@@ -8,7 +8,6 @@ import { Search, Send, Paperclip, ArrowLeft, Bot, Building2, Kanban } from 'luci
import { mockDemandInquiries } from '../../mock-data/demandInquiries'
import { useInquiryStore } from '../../stores/inquiryStore'
import { usePipelineItems, useMoveStage } from '../../hooks/usePipeline'
import { useToastStore } from '../../stores/toastStore'
import type { InquiryMessage } from '../../domain/inquiry'
import { STAGE_ORDER, STAGE_LABELS, detectKiStage } from './anfragenKiDetection'
import { AnfragenMessageBubble } from '../../components/demand/AnfragenMessageBubble'
@@ -32,7 +31,7 @@ export default function Anfragen() {
const [searchParams] = useSearchParams()
const { data: pipelineItems = [] } = usePipelineItems()
const { mutate: moveStage } = useMoveStage()
const showToast = useToastStore(s => s.showToast)
const preselectedId = searchParams.get('inquiry')
@@ -156,7 +155,7 @@ export default function Anfragen() {
<TextField
size="small" placeholder="Suchen..." fullWidth
value={search} onChange={e => setSearch(e.target.value)}
InputProps={{ startAdornment: <InputAdornment position="start"><Search size={14} color={DS_TEXT.disabled} /></InputAdornment> }}
slotProps={{ input: { startAdornment: <InputAdornment position="start"><Search size={14} color={DS_TEXT.disabled} /></InputAdornment> } }}
sx={{ mb: 1.25 }}
/>
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
-1
View File
@@ -41,7 +41,6 @@ export default function Compare() {
overallWinnerIdx,
bestScoreIdx,
worstConfIdx,
dqScores,
worstDQIdx,
missingCriticalCounts,
maxMissingCritical,
+13 -62
View File
@@ -1,31 +1,19 @@
import { useState } from 'react'
import { InquiryQuickDialog } from '../../components/match-detail/InquiryQuickDialog'
import { Box, Button, Chip, CircularProgress, Paper, Typography } from '@mui/material'
import { AlertTriangle, ArrowLeft, ChevronDown, ChevronUp } from 'lucide-react'
import { AlertTriangle, ArrowLeft } from 'lucide-react'
import { useNavigate, useParams } from 'react-router'
import { useCompareStore } from '../../stores/compareStore'
import { usePipelineStore } from '../../stores/pipelineStore'
import { useInquiryStore } from '../../stores/inquiryStore'
import { AddToPipelineDialog } from '../../components/shortlist'
import { MatchReasonList } from '../../components/match-card/MatchReasonList'
import { PropertyMap } from '../../components/shared'
import { getCityIntelligence } from '../../lib/locationIntelligence'
import {
LocationIntelligencePanel,
ExecutiveSummaryPanel,
NeedAlignmentPanel,
ScoreBreakdownPanel,
TradeoffPanel,
RiskPanel,
MissingInformationPanel,
FutureAvailabilityContextPanel,
NextActionsPanel,
} from '../../components/match-detail'
import { NextActionsPanel } from '../../components/match-detail'
import type { MatchCardReason } from '../../components/match-card/MatchCardViewModel'
import { useMatchDetailData } from '../../hooks/useMatchDetailData'
import { useMatchDetail } from '../../hooks/useMatches'
import { MatchDetailHero } from '../../components/match-detail/MatchDetailHero'
import { MatchDetailPropertySections } from '../../components/match-detail/MatchDetailPropertySections'
import { MatchDetailScoreBreakdown } from '../../components/match-detail/MatchDetailScoreBreakdown'
import { RESULT_TYPE_META, DS_TEXT, DS_SURFACE, DS_BORDER, DS_BG } from '../../lib/ds'
// ── helpers ───────────────────────────────────────────────────────────────────
@@ -49,7 +37,6 @@ export default function MatchDetail() {
const { addToCompare } = useCompareStore()
const { openSavedDialog } = usePipelineStore()
const { openInquiryDialog } = useInquiryStore()
const [showFullAnalysis, setShowFullAnalysis] = useState(false)
const { match, property, need, signal, isLoading, isFuture } = useMatchDetailData(matchId ?? '')
@@ -91,7 +78,7 @@ export default function MatchDetail() {
const handleCompare = () => {
if (match && !isFuture && property) {
addToCompare({
resultType: property.resultType ?? 'VERIFIED_PORTFOLIO',
resultType: (property.resultType ?? 'VERIFIED_PORTFOLIO') as 'VERIFIED_PORTFOLIO' | 'MAISON_WORK',
matchId: match.id, needId: match.needId, matchScore: match.matchScore, match, property,
})
} else if (match && isFuture && signal) {
@@ -210,51 +197,15 @@ export default function MatchDetail() {
})}
/>
{/* ── Full analysis toggle ── */}
<Button
variant="outlined"
onClick={() => setShowFullAnalysis(v => !v)}
fullWidth
endIcon={showFullAnalysis ? <ChevronUp size={15} /> : <ChevronDown size={15} />}
sx={{
borderStyle: 'dashed', color: DS_TEXT.muted, borderColor: DS_BORDER.strong,
textTransform: 'none', fontWeight: 500,
'&:hover': { borderColor: DS_TEXT.muted, bgcolor: DS_BG.subtle },
}}
>
{showFullAnalysis ? 'Weniger anzeigen' : 'Vollständige Analyse anzeigen'}
</Button>
{/* ── Full analysis (hidden by default) ── */}
{showFullAnalysis && (
<>
<ExecutiveSummaryPanel match={match} />
<NeedAlignmentPanel match={match} need={need} property={property} />
{!isFuture && property && <MatchDetailPropertySections property={property} match={match} />}
<LocationIntelligencePanel property={property} />
{!isFuture && property?.images?.[0] && property?.location?.coordinates && (
<Paper sx={{ overflow: 'hidden', p: 0 }}>
<Box sx={{ px: 2.5, pt: 2, pb: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}>Standort</Typography>
<Typography variant="caption" color="text.secondary">
{property.address.street} {property.address.houseNumber}, {property.address.postalCode} {property.address.city}
</Typography>
</Box>
<PropertyMap
lat={property.location.coordinates.lat}
lng={property.location.coordinates.lng}
label={property.title}
height={220}
/>
</Paper>
)}
<TradeoffPanel match={match} />
<RiskPanel match={match} />
<MissingInformationPanel match={match} />
{isFuture && <FutureAvailabilityContextPanel match={match} signal={signal} />}
<ScoreBreakdownPanel match={match} taxCalculatorUrl={taxCalculatorUrl} isFuture={isFuture} signal={signal} />
</>
)}
{/* ── Full analysis toggle + expanded panels ── */}
<MatchDetailScoreBreakdown
match={match}
property={property}
need={need}
signal={signal}
isFuture={isFuture}
taxCalculatorUrl={taxCalculatorUrl}
/>
</Box>
</Box>
)
+1 -1
View File
@@ -2,7 +2,7 @@ import { memo, useState, useEffect, useCallback, useMemo } from 'react'
import { useNavigate } from 'react-router'
import {
Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle,
Divider, FormControl, FormControlLabel, IconButton,
Divider, FormControl, FormControlLabel,
InputLabel, MenuItem, Select as MuiSelect,
Skeleton, Switch, Tab, Tabs, TextField, ToggleButton, ToggleButtonGroup,
Tooltip, Typography,
+1 -1
View File
@@ -53,7 +53,7 @@ export default function SupplyDashboard() {
<Paper sx={{ p: 0, overflow: 'hidden', border: `1px solid ${DS_BORDER.default}` }}>
{/* KPI strip */}
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr 1fr', md: 'repeat(3, 1fr)' }, divideX: true }}>
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr 1fr', md: 'repeat(3, 1fr)' } }}>
{/* Starke Matches */}
<Box sx={{ p: 2.5, borderRight: `1px solid ${DS_BORDER.default}`, bgcolor: strongMatchCount > 0 ? DS_SURFACE.success.bg : 'white' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, mb: 0.5 }}>