refine: sharper corners, minimal status indicator, no metric boxes
- theme: borderRadius 8→4 (cascades to all MUI components) - PropertyIntelligenceCard: status pill → plain dot+text (no bg/border) - OCCUPIED status: green → neutral gray (rented = expected, no signal needed) - metric cells: remove box decoration, whitespace-only separation - metric labels: lowercase, no uppercase/letter-spacing - status placed inline next to location, saving vertical space Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,36 +11,12 @@ interface Props {
|
|||||||
inquiryCount?: number
|
inquiryCount?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// Occupancy status — the property manager's primary lens
|
// Occupancy status — colour only for actionable exceptions; occupied = expected, no signal needed
|
||||||
const STATUS_CONFIG = {
|
const STATUS_CONFIG = {
|
||||||
AVAILABLE_NOW: {
|
AVAILABLE_NOW: { dot: '#b91c1c', text: '#b91c1c', label: 'Verfügbar' },
|
||||||
dot: '#b91c1c',
|
AVAILABLE_SOON: { dot: '#a16207', text: '#a16207', label: 'Bald frei' },
|
||||||
bg: '#fef2f2',
|
OCCUPIED: { dot: '#9a9a9a', text: '#9a9a9a', label: 'Vermietet' },
|
||||||
border:'#fecaca',
|
FUTURE_SIGNAL: { dot: '#5b3f8a', text: '#5b3f8a', label: 'Geplante Verf.' },
|
||||||
text: '#b91c1c',
|
|
||||||
label: 'Verfügbar',
|
|
||||||
},
|
|
||||||
AVAILABLE_SOON: {
|
|
||||||
dot: '#a16207',
|
|
||||||
bg: '#fffbeb',
|
|
||||||
border:'#fde68a',
|
|
||||||
text: '#a16207',
|
|
||||||
label: 'Bald frei',
|
|
||||||
},
|
|
||||||
OCCUPIED: {
|
|
||||||
dot: '#1d6342',
|
|
||||||
bg: '#f0fdf4',
|
|
||||||
border:'#bbf7d0',
|
|
||||||
text: '#1d6342',
|
|
||||||
label: 'Vermietet',
|
|
||||||
},
|
|
||||||
FUTURE_SIGNAL: {
|
|
||||||
dot: '#5b3f8a',
|
|
||||||
bg: '#faf7ff',
|
|
||||||
border:'#e9d5ff',
|
|
||||||
text: '#5b3f8a',
|
|
||||||
label: 'Geplante Verfügbarkeit',
|
|
||||||
},
|
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const FIT_OUT_META: Record<string, { label: string; tip: string }> = {
|
const FIT_OUT_META: Record<string, { label: string; tip: string }> = {
|
||||||
@@ -67,7 +43,7 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
|
|||||||
<Box
|
<Box
|
||||||
onClick={() => onSelect(p.id)}
|
onClick={() => onSelect(p.id)}
|
||||||
sx={{
|
sx={{
|
||||||
borderRadius: 2,
|
borderRadius: 1,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
border: `1px solid ${DS_BORDER.default}`,
|
border: `1px solid ${DS_BORDER.default}`,
|
||||||
bgcolor: '#ffffff',
|
bgcolor: '#ffffff',
|
||||||
@@ -94,7 +70,7 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Single neutral asset-type label */}
|
{/* Single neutral asset-type label */}
|
||||||
<Box sx={{ position: 'absolute', top: 10, left: 10, bgcolor: 'rgba(0,0,0,0.50)', borderRadius: '5px', px: 0.75, py: 0.25 }}>
|
<Box sx={{ position: 'absolute', top: 10, left: 10, bgcolor: 'rgba(0,0,0,0.50)', borderRadius: '3px', px: 0.75, py: 0.25 }}>
|
||||||
<Typography sx={{ fontSize: '0.65rem', fontWeight: 600, color: 'white', lineHeight: 1.3 }}>
|
<Typography sx={{ fontSize: '0.65rem', fontWeight: 600, color: 'white', lineHeight: 1.3 }}>
|
||||||
{getAssetTypeLabel(p.assetType)}
|
{getAssetTypeLabel(p.assetType)}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -104,62 +80,50 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
|
|||||||
{/* ── Body ── */}
|
{/* ── Body ── */}
|
||||||
<Box sx={{ px: 1.75, pt: 1.5, pb: 1.75, display: 'flex', flexDirection: 'column', gap: 1.125, flex: 1 }}>
|
<Box sx={{ px: 1.75, pt: 1.5, pb: 1.75, display: 'flex', flexDirection: 'column', gap: 1.125, flex: 1 }}>
|
||||||
|
|
||||||
{/* ── Occupancy status — primary decision indicator ── */}
|
{/* Title + location + status inline */}
|
||||||
<Box sx={{
|
|
||||||
display: 'inline-flex', alignItems: 'center', gap: 0.75,
|
|
||||||
bgcolor: status.bg, border: `1px solid ${status.border}`,
|
|
||||||
borderRadius: 1.5, px: 1, py: 0.5, alignSelf: 'flex-start',
|
|
||||||
}}>
|
|
||||||
<Box sx={{ width: 7, height: 7, borderRadius: '50%', bgcolor: status.dot, flexShrink: 0 }} />
|
|
||||||
<Typography sx={{ fontSize: '0.75rem', fontWeight: 600, color: status.text, lineHeight: 1 }}>
|
|
||||||
{status.label}
|
|
||||||
{p.availabilityStatus === 'AVAILABLE_SOON' && availDate ? ` · ab ${availDate}` : ''}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Title + location */}
|
|
||||||
<Box>
|
<Box>
|
||||||
<Typography sx={{ fontWeight: 600, fontSize: '0.9375rem', color: DS_TEXT.primary, lineHeight: 1.3 }} noWrap>
|
<Typography sx={{ fontWeight: 600, fontSize: '0.9375rem', color: DS_TEXT.primary, lineHeight: 1.3 }} noWrap>
|
||||||
{p.title}
|
{p.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4, mt: 0.3 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mt: 0.4 }}>
|
||||||
<MapPin size={11} color="#9a9a9a" />
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.4 }}>
|
||||||
<Typography sx={{ fontSize: '0.75rem', color: DS_TEXT.muted }}>
|
<MapPin size={11} color="#9a9a9a" />
|
||||||
{p.location.city}{p.location.district ? ` · ${p.location.district}` : ''}
|
<Typography sx={{ fontSize: '0.75rem', color: DS_TEXT.muted }}>
|
||||||
</Typography>
|
{p.location.city}{p.location.district ? ` · ${p.location.district}` : ''}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
{/* Status — plain dot + text, no pill container */}
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
|
<Box sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: status.dot, flexShrink: 0 }} />
|
||||||
|
<Typography sx={{ fontSize: '0.72rem', fontWeight: 500, color: status.text, lineHeight: 1 }}>
|
||||||
|
{status.label}
|
||||||
|
{p.availabilityStatus === 'AVAILABLE_SOON' && availDate ? ` · ab ${availDate}` : ''}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 3 key metrics: Fläche · Miete/m² · Anfragen */}
|
{/* 3 key metrics — no boxes, whitespace only */}
|
||||||
<Box sx={{ display: 'flex', gap: 0.875 }}>
|
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||||
<Box sx={{ flex: 1, bgcolor: '#f9f8f6', borderRadius: 1.5, px: 1, py: 0.875, textAlign: 'center', border: `1px solid ${DS_BORDER.muted}` }}>
|
<Box>
|
||||||
<Typography sx={{ fontSize: '0.58rem', color: DS_TEXT.muted, textTransform: 'uppercase', letterSpacing: '0.04em', mb: 0.25 }}>
|
<Typography sx={{ fontSize: '0.68rem', color: DS_TEXT.muted, mb: 0.25 }}>Fläche</Typography>
|
||||||
Fläche
|
<Typography sx={{ fontWeight: 600, fontSize: '0.875rem', color: DS_TEXT.primary }}>
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ fontWeight: 700, fontSize: '0.875rem', color: DS_TEXT.primary }}>
|
|
||||||
{p.areaSqm.toLocaleString('de-CH')} m²
|
{p.areaSqm.toLocaleString('de-CH')} m²
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ flex: 1, bgcolor: '#f9f8f6', borderRadius: 1.5, px: 1, py: 0.875, textAlign: 'center', border: `1px solid ${DS_BORDER.muted}` }}>
|
<Box>
|
||||||
<Typography sx={{ fontSize: '0.58rem', color: DS_TEXT.muted, textTransform: 'uppercase', letterSpacing: '0.04em', mb: 0.25 }}>
|
<Typography sx={{ fontSize: '0.68rem', color: DS_TEXT.muted, mb: 0.25 }}>Miete</Typography>
|
||||||
Miete
|
<Typography sx={{ fontWeight: 600, fontSize: '0.875rem', color: DS_TEXT.primary }}>
|
||||||
</Typography>
|
{p.rentPricePerSqm}
|
||||||
<Typography sx={{ fontWeight: 700, fontSize: '0.875rem', color: DS_TEXT.primary }}>
|
<Typography component="span" sx={{ fontSize: '0.62rem', fontWeight: 400, color: DS_TEXT.muted }}> CHF/m²/J</Typography>
|
||||||
{p.rentPricePerSqm} <Typography component="span" sx={{ fontSize: '0.65rem', fontWeight: 400, color: DS_TEXT.muted }}>CHF/m²/J</Typography>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Tooltip title={hasInquiries ? `${inquiryCount} aktive Anfragen` : 'Noch keine Anfragen'} placement="top" arrow>
|
<Tooltip title={hasInquiries ? `${inquiryCount} aktive Anfragen` : 'Noch keine Anfragen'} placement="top" arrow>
|
||||||
<Box sx={{
|
<Box>
|
||||||
flex: 1, borderRadius: 1.5, px: 1, py: 0.875, textAlign: 'center',
|
<Typography sx={{ fontSize: '0.68rem', color: DS_TEXT.muted, mb: 0.25 }}>Anfragen</Typography>
|
||||||
bgcolor: hasInquiries ? '#fffbeb' : '#f9f8f6',
|
<Typography sx={{ fontWeight: 600, fontSize: '0.875rem', color: hasInquiries ? '#a16207' : DS_TEXT.muted }}>
|
||||||
border: `1px solid ${hasInquiries ? '#fde68a' : DS_BORDER.muted}`,
|
|
||||||
}}>
|
|
||||||
<Typography sx={{ fontSize: '0.58rem', color: hasInquiries ? '#a16207' : DS_TEXT.muted, textTransform: 'uppercase', letterSpacing: '0.04em', mb: 0.25 }}>
|
|
||||||
Anfragen
|
|
||||||
</Typography>
|
|
||||||
<Typography sx={{ fontWeight: 700, fontSize: '0.875rem', color: hasInquiries ? '#a16207' : DS_TEXT.muted }}>
|
|
||||||
{inquiryCount ?? 0}
|
{inquiryCount ?? 0}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -173,20 +137,20 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
|
|||||||
return (
|
return (
|
||||||
<Tooltip title={meta.tip} placement="top" arrow>
|
<Tooltip title={meta.tip} placement="top" arrow>
|
||||||
<Chip label={meta.label} size="small"
|
<Chip label={meta.label} size="small"
|
||||||
sx={{ height: 22, fontSize: '0.7rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}`, cursor: 'help' }} />
|
sx={{ height: 20, fontSize: '0.68rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}`, cursor: 'help' }} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
})()}
|
})()}
|
||||||
{p.contractDurationMonths && (
|
{p.contractDurationMonths && (
|
||||||
<Chip label={`${p.contractDurationMonths}M Vertrag`} size="small"
|
<Chip label={`${p.contractDurationMonths}M Vertrag`} size="small"
|
||||||
sx={{ height: 22, fontSize: '0.7rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}` }} />
|
sx={{ height: 20, fontSize: '0.68rem', bgcolor: '#f4f3f0', color: DS_TEXT.secondary, border: `1px solid ${DS_BORDER.default}` }} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Data completeness — tells manager if listing is visible to matching */}
|
{/* Data completeness */}
|
||||||
<Box sx={{ mt: 'auto' }}>
|
<Box sx={{ mt: 'auto' }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 0.5 }}>
|
||||||
<Typography sx={{ fontSize: '0.62rem', color: DS_TEXT.disabled, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
<Typography sx={{ fontSize: '0.62rem', color: DS_TEXT.disabled, letterSpacing: '0.03em' }}>
|
||||||
Datenvollständigkeit
|
Datenvollständigkeit
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography sx={{ fontSize: '0.75rem', fontWeight: 700, color: p.confidenceScore >= 0.75 ? '#b8975a' : DS_TEXT.muted }}>
|
<Typography sx={{ fontSize: '0.75rem', fontWeight: 700, color: p.confidenceScore >= 0.75 ? '#b8975a' : DS_TEXT.muted }}>
|
||||||
@@ -194,8 +158,8 @@ export const PropertyIntelligenceCard = memo(function PropertyIntelligenceCard({
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<LinearProgress variant="determinate" value={confPct}
|
<LinearProgress variant="determinate" value={confPct}
|
||||||
sx={{ height: 3, borderRadius: 2, bgcolor: '#f4f3f0',
|
sx={{ height: 3, borderRadius: 1, bgcolor: '#f4f3f0',
|
||||||
'& .MuiLinearProgress-bar': { bgcolor: '#152642', borderRadius: 2 } }} />
|
'& .MuiLinearProgress-bar': { bgcolor: '#152642', borderRadius: 1 } }} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
+3
-3
@@ -45,7 +45,7 @@ export const theme = createTheme({
|
|||||||
overline: { fontSize: '0.6875rem', fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' },
|
overline: { fontSize: '0.6875rem', fontWeight: 600, letterSpacing: '0.08em', textTransform: 'uppercase' },
|
||||||
button: { fontSize: '0.8125rem', fontWeight: 600, textTransform: 'none' },
|
button: { fontSize: '0.8125rem', fontWeight: 600, textTransform: 'none' },
|
||||||
},
|
},
|
||||||
shape: { borderRadius: 8 },
|
shape: { borderRadius: 4 },
|
||||||
shadows: [
|
shadows: [
|
||||||
'none',
|
'none',
|
||||||
'0 1px 2px rgba(0,0,0,0.05)',
|
'0 1px 2px rgba(0,0,0,0.05)',
|
||||||
@@ -78,7 +78,7 @@ export const theme = createTheme({
|
|||||||
MuiButton: {
|
MuiButton: {
|
||||||
defaultProps: { disableElevation: true },
|
defaultProps: { disableElevation: true },
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: { borderRadius: 6, padding: '6px 16px' },
|
root: { borderRadius: 4, padding: '6px 16px' },
|
||||||
contained: {
|
contained: {
|
||||||
'&.MuiButton-containedPrimary': {
|
'&.MuiButton-containedPrimary': {
|
||||||
background: '#152642',
|
background: '#152642',
|
||||||
@@ -89,7 +89,7 @@ export const theme = createTheme({
|
|||||||
},
|
},
|
||||||
MuiChip: {
|
MuiChip: {
|
||||||
styleOverrides: {
|
styleOverrides: {
|
||||||
root: { borderRadius: 6, fontSize: '0.75rem', fontWeight: 500, height: 22 },
|
root: { borderRadius: 4, fontSize: '0.75rem', fontWeight: 500, height: 22 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MuiTableCell: {
|
MuiTableCell: {
|
||||||
|
|||||||
Reference in New Issue
Block a user