Files
property-match/src/components/anfragencenter/ReportObjectFieldSelector.tsx
T
Benjamin Sutter 36570c5bdc 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>
2026-05-26 00:08:35 +02:00

159 lines
6.3 KiB
TypeScript

import { Accordion, AccordionDetails, AccordionSummary, Box, Checkbox, FormControlLabel, Typography, Button } from '@mui/material'
import { ChevronDown } from 'lucide-react'
import type { ReportObjectFieldKey, ReportObjectFieldSelection } from '../../domain/inquiryReport'
interface Props {
propertyTitle: string
value: ReportObjectFieldSelection
onChange: (v: ReportObjectFieldSelection) => void
}
type FieldGroup = {
label: string
fields: { key: ReportObjectFieldKey; label: string }[]
}
const FIELD_GROUPS: FieldGroup[] = [
{
label: 'Basisdaten',
fields: [
{ key: 'areaSqm', label: 'Fläche' },
{ key: 'rentPricePerSqm', label: 'Mietpreis' },
{ key: 'availabilityDate', label: 'Verfügbar ab' },
{ key: 'propertyNumber', label: 'Objektnummer' },
{ key: 'assetType', label: 'Asset Type' },
{ key: 'description', label: 'Beschreibung' },
],
},
{
label: 'Miet-/Vertragsdaten',
fields: [
{ key: 'leaseTerm', label: 'Mietlaufzeit' },
{ key: 'breakoutOption', label: 'Breakout-Option' },
{ key: 'breakoutOptionDate', label: 'Breakout-Option Zeitpunkt' },
{ key: 'currentTenant', label: 'Aktueller Mieter' },
{ key: 'floor', label: 'Stockwerk' },
{ key: 'parking', label: 'Parkplätze' },
{ key: 'fitOut', label: 'Ausbaugrad' },
{ key: 'isBarrierFree', label: 'Barrierefrei' },
],
},
{
label: 'Technische Hard Facts',
fields: [
{ key: 'ceilingHeightM', label: 'Raumhöhe' },
{ key: 'floorLoad', label: 'Bodenlast' },
{ key: 'loadingDocksCount', label: 'Anlieferung' },
{ key: 'goodsLift', label: 'Warenaufzug' },
{ key: 'passengerLift', label: 'Personenaufzug' },
{ key: 'powerSupplyKva', label: 'Stromanschluss (kVA)' },
{ key: 'hasServerRoom', label: 'Serverraum' },
{ key: 'internet', label: 'Internet / Konnektivität' },
{ key: 'deliveryAccess', label: 'Zufahrt / Logistik' },
],
},
{
label: 'Soft Factors',
fields: [
{ key: 'prestigeScore', label: 'Prestige' },
{ key: 'visibilityScore', label: 'Sichtbarkeit' },
{ key: 'footfallScore', label: 'Passantenfrequenz' },
{ key: 'commuterAccessScore', label: 'Pendlererreichbarkeit' },
{ key: 'talentAccessScore', label: 'Talent Access' },
{ key: 'esgScore', label: 'ESG / Nachhaltigkeit' },
{ key: 'flexibilityScore', label: 'Flexibilität' },
{ key: 'expansionPotentialScore', label: 'Expansionsmöglichkeit' },
{ key: 'taxEnvironmentScore', label: 'Steuerumfeld' },
{ key: 'publicTransportScore', label: 'ÖV-Anbindung' },
{ key: 'microLocation', label: 'Mikrostandort' },
{ key: 'competitionEnvironment', label: 'Konkurrenzumfeld' },
{ key: 'infrastructure', label: 'Infrastruktur' },
],
},
{
label: 'Marktinformationen',
fields: [
{ key: 'marketSignals', label: 'Marktsignale' },
{ key: 'negotiationHints', label: 'Verhandlungshinweise' },
{ key: 'missingData', label: 'Datenlücken / offene Punkte' },
{ key: 'dataQuality', label: 'Datenqualität' },
{ key: 'units', label: 'Stockwerkstruktur' },
],
},
]
export function ReportObjectFieldSelector({ propertyTitle, value, onChange }: Props) {
const isSelected = (key: ReportObjectFieldKey) =>
value.selectedOptionalFields.includes(key)
const toggle = (key: ReportObjectFieldKey) => {
const next = isSelected(key)
? value.selectedOptionalFields.filter(k => k !== key)
: [...value.selectedOptionalFields, key]
onChange({ ...value, selectedOptionalFields: next })
}
const selectAll = (keys: ReportObjectFieldKey[]) => {
const next = Array.from(new Set([...value.selectedOptionalFields, ...keys]))
onChange({ ...value, selectedOptionalFields: next })
}
const deselectAll = (keys: ReportObjectFieldKey[]) => {
onChange({ ...value, selectedOptionalFields: value.selectedOptionalFields.filter(k => !keys.includes(k)) })
}
return (
<Box>
<Typography variant="body2" sx={{ fontWeight: 600, color: '#0f172a', mb: 1 }}>
Felder für: {propertyTitle}
</Typography>
<Typography variant="caption" sx={{ color: '#64748b', display: 'block', mb: 1.5 }}>
Pflichtfelder (immer enthalten): Titel, Standort, Karte, Fotos
</Typography>
{FIELD_GROUPS.map(group => {
const groupKeys = group.fields.map(f => f.key)
return (
<Accordion key={group.label} disableGutters elevation={0} sx={{ border: '1px solid #e2e8f0', mb: 0.5, '&:before': { display: 'none' } }}>
<AccordionSummary expandIcon={<ChevronDown size={16} />} sx={{ minHeight: 40, '& .MuiAccordionSummary-content': { my: 0.5 } }}>
<Typography variant="body2" sx={{ fontWeight: 600, fontSize: '0.8125rem' }}>
{group.label}
</Typography>
<Typography variant="caption" sx={{ ml: 1, color: '#64748b', alignSelf: 'center' }}>
({groupKeys.filter(k => value.selectedOptionalFields.includes(k)).length}/{groupKeys.length})
</Typography>
</AccordionSummary>
<AccordionDetails sx={{ pt: 0, pb: 1 }}>
<Box sx={{ display: 'flex', gap: 1, mb: 1 }}>
<Button size="small" onClick={() => selectAll(groupKeys)} sx={{ textTransform: 'none', fontSize: '0.7rem', p: '2px 8px', minWidth: 0 }}>
Alle
</Button>
<Button size="small" onClick={() => deselectAll(groupKeys)} sx={{ textTransform: 'none', fontSize: '0.7rem', p: '2px 8px', minWidth: 0 }}>
Keine
</Button>
</Box>
<Box sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 0 }}>
{group.fields.map(f => (
<FormControlLabel
key={f.key}
control={
<Checkbox
size="small"
checked={isSelected(f.key)}
onChange={() => toggle(f.key)}
sx={{ py: 0.25 }}
/>
}
label={<Typography variant="caption" sx={{ fontSize: '0.75rem' }}>{f.label}</Typography>}
sx={{ m: 0 }}
/>
))}
</Box>
</AccordionDetails>
</Accordion>
)
})}
</Box>
)
}