feat: design token system — DS_TEXT/DS_SURFACE/DS_BORDER + 237 hex migrations
Token infrastructure (src/lib/ds.ts): - DS_TEXT: 16 semantic text color tokens (primary/secondary/muted/success/ warning/error/info/brand/signal + dark variants successDark/warningDark/ signalDark/infoDark for text on tinted surfaces) - DS_BG: page/surface/subtle/muted background tokens - DS_BORDER: default/muted/strong border tokens - DS_SURFACE: 10 bg+border surface pairs (success/warning/error/info/indigo/ purple/orange/blue/neutral/slate) - DS_MATCH_TIER: score-tier surface aliases keyed by strong/moderate/weak - DS_PRE_MARKET / DS_MARKET_SIGNAL: named aliases for futureCard tokens - DS_SHADOW: card/panel/dialog elevation tokens - BADGE_COLORS: 14 semantic presets for GenericBadge (confidenceHigh, riskMedium, preMarket, marketSignal, verified, gold, silver, bronze…) GenericBadge (src/components/shared/GenericBadge.tsx): - New semanticVariant prop (keyof BADGE_COLORS) — preferred over raw hex - color prop becomes optional (fallback), type-documented as escape hatch Priority file migrations — 237 hex literals replaced across 10 files: ScoreBreakdownPanel.tsx −22 PipelineDetailPanel.tsx −22 FutureAvailabilityCard.tsx −26 AICompareSummary.tsx −27 LocationIntelligencePanel −39 AssistantPromptSuggestions −18 UnitStructurePanel.tsx −25 BerichtDialog.tsx −24 PreMarketPanel.tsx −24 PropertyActivityLogPanel −10 ESLint (eslint.config.js): - Updated rule message to reference new tokens (DS_TEXT, DS_SURFACE, etc.) - Added 'stroke' to monitored property names - Remains 'warn' for gradual migration; use check:tokens for CI gate CI script (scripts/check-tokens.js + npm run check:tokens): - Counts hex patterns in components/ + pages/ - Fails if count > THRESHOLD (ratchet: 1958 baseline, lower per sprint) - Reports top 15 offenders for prioritizing next migration batch Results: ESLint targeted sx-prop violations: 1752 → 1030 (−41%) 0 TypeScript errors, 154 tests green Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { Box, Button, CircularProgress, Dialog, LinearProgress, Typography } from '@mui/material'
|
||||
import { CheckCircle, Download, FileText } from 'lucide-react'
|
||||
import type { MarketReport } from '../../domain/marketReport'
|
||||
import { DS_TEXT, DS_BG, DS_BORDER, DS_SURFACE } from '../../lib/ds'
|
||||
|
||||
const SECTION_COLORS: Record<string, string> = {
|
||||
development: '#1e3a5f',
|
||||
@@ -80,9 +81,9 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
PaperProps={{ sx: { height: '88vh', display: 'flex', flexDirection: 'column', overflow: 'hidden' } }}
|
||||
>
|
||||
{/* Header */}
|
||||
<Box sx={{ px: 3, py: 1.5, borderBottom: '1px solid #e2e8f0', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
|
||||
<Box sx={{ px: 3, py: 1.5, borderBottom: `1px solid ${DS_BORDER.default}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
|
||||
<Box>
|
||||
<Typography variant="caption" sx={{ color: '#64748b', fontSize: '0.7rem', textTransform: 'uppercase', fontWeight: 600, letterSpacing: 0.5 }}>
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.muted, fontSize: '0.7rem', textTransform: 'uppercase', fontWeight: 600, letterSpacing: 0.5 }}>
|
||||
Bericht erstellen
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ fontWeight: 700, color: '#0f172a', fontSize: '0.95rem' }}>
|
||||
@@ -91,7 +92,7 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
</Box>
|
||||
{ready && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||
<CheckCircle size={14} color="#1a7a4a" />
|
||||
<CheckCircle size={14} color={DS_TEXT.success} />
|
||||
<Typography variant="caption" sx={{ color: '#166534', fontWeight: 600 }}>Bereit</Typography>
|
||||
</Box>
|
||||
)}
|
||||
@@ -99,29 +100,29 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
|
||||
{/* Generating state */}
|
||||
{!ready && (
|
||||
<Box sx={{ px: 3, py: 1.5, borderBottom: '1px solid #e2e8f0', bgcolor: '#f8fafc', flexShrink: 0 }}>
|
||||
<Box sx={{ px: 3, py: 1.5, borderBottom: `1px solid ${DS_BORDER.default}`, bgcolor: DS_SURFACE.neutral.bg, flexShrink: 0 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 1 }}>
|
||||
<CircularProgress size={16} />
|
||||
<Typography variant="caption" sx={{ color: '#374151' }}>
|
||||
PDF-Bericht wird generiert…
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: '#94a3b8', ml: 'auto' }}>{Math.round(progress)}%</Typography>
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.disabled, ml: 'auto' }}>{Math.round(progress)}%</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progress}
|
||||
sx={{ height: 4, borderRadius: 2, bgcolor: '#e2e8f0', '& .MuiLinearProgress-bar': { bgcolor: '#1e3a5f' } }}
|
||||
sx={{ height: 4, borderRadius: 2, bgcolor: DS_BORDER.default, '& .MuiLinearProgress-bar': { bgcolor: DS_TEXT.brand } }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* PDF Preview */}
|
||||
<Box sx={{ flex: 1, overflow: 'auto', bgcolor: '#f1f5f9', p: 3 }}>
|
||||
<Box sx={{ flex: 1, overflow: 'auto', bgcolor: DS_BG.subtle, p: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: 'white',
|
||||
borderRadius: 1.5,
|
||||
border: '1px solid #cbd5e1',
|
||||
border: `1px solid ${DS_BORDER.strong}`,
|
||||
boxShadow: '0 4px 16px rgba(15,23,42,0.08)',
|
||||
p: 4,
|
||||
fontFamily: '"Georgia", "Times New Roman", serif',
|
||||
@@ -133,23 +134,23 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
{/* Document header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<FileText size={18} color="#1e3a5f" />
|
||||
<Typography sx={{ fontFamily: 'inherit', fontWeight: 700, color: '#1e3a5f', fontSize: '0.75rem', letterSpacing: 1, textTransform: 'uppercase' }}>
|
||||
<FileText size={18} color={DS_TEXT.brand} />
|
||||
<Typography sx={{ fontFamily: 'inherit', fontWeight: 700, color: DS_TEXT.brand, fontSize: '0.75rem', letterSpacing: 1, textTransform: 'uppercase' }}>
|
||||
Marktsignal-Bericht
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ textAlign: 'right' }}>
|
||||
<Typography variant="caption" sx={{ display: 'block', color: '#94a3b8', fontFamily: 'inherit' }}>Wincasa AG · Zürich</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', color: '#94a3b8', fontFamily: 'inherit' }}>{new Date().toLocaleDateString('de-CH')}</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', color: DS_TEXT.disabled, fontFamily: 'inherit' }}>Wincasa AG · Zürich</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', color: DS_TEXT.disabled, fontFamily: 'inherit' }}>{new Date().toLocaleDateString('de-CH')}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ borderBottom: '2px solid #1e3a5f', mb: 2.5 }} />
|
||||
<Box sx={{ borderBottom: `2px solid ${DS_TEXT.brand}`, mb: 2.5 }} />
|
||||
|
||||
<Typography sx={{ fontFamily: 'inherit', fontWeight: 700, color: '#0f172a', fontSize: '1.1rem', mb: 0.5 }}>
|
||||
Standortintelligenz & Marktsignale
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: '#64748b', fontSize: '0.825rem', mb: 3 }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: DS_TEXT.muted, fontSize: '0.825rem', mb: 3 }}>
|
||||
Objekt-ID: {propertyId} · Generiert: {report?.generatedAt ? new Date(report.generatedAt).toLocaleDateString('de-CH') : new Date().toLocaleDateString('de-CH')} · {signals.length} Signale
|
||||
</Typography>
|
||||
|
||||
@@ -166,18 +167,18 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
<Typography sx={{ fontFamily: 'inherit', fontWeight: 700, color, fontSize: '0.875rem', textTransform: 'uppercase', letterSpacing: 0.5 }}>
|
||||
{label}
|
||||
</Typography>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: '#94a3b8', fontSize: '0.75rem', ml: 0.5 }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: DS_TEXT.disabled, fontSize: '0.75rem', ml: 0.5 }}>
|
||||
({catSignals.length})
|
||||
</Typography>
|
||||
</Box>
|
||||
{catSignals.map((s, i) => (
|
||||
<Box key={s.id} sx={{ mb: i < catSignals.length - 1 ? 2 : 0, pl: 1.5, borderLeft: `2px solid #e2e8f0` }}>
|
||||
<Box key={s.id} sx={{ mb: i < catSignals.length - 1 ? 2 : 0, pl: 1.5, borderLeft: `2px solid ${DS_BORDER.default}` }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 1, mb: 0.5 }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', fontWeight: 700, color: '#0f172a', fontSize: '0.875rem', lineHeight: 1.4 }}>
|
||||
{s.title}
|
||||
</Typography>
|
||||
{s.confidence != null && (
|
||||
<Typography sx={{ fontFamily: 'inherit', color: '#64748b', fontSize: '0.75rem', flexShrink: 0 }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: DS_TEXT.muted, fontSize: '0.75rem', flexShrink: 0 }}>
|
||||
KI-Konfidenz: {Math.round(s.confidence * 100)}%
|
||||
</Typography>
|
||||
)}
|
||||
@@ -187,12 +188,12 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 2 }}>
|
||||
{s.date && (
|
||||
<Typography sx={{ fontFamily: 'inherit', color: '#94a3b8', fontSize: '0.75rem' }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: DS_TEXT.disabled, fontSize: '0.75rem' }}>
|
||||
{new Date(s.date).toLocaleDateString('de-CH')}
|
||||
</Typography>
|
||||
)}
|
||||
{s.source && (
|
||||
<Typography sx={{ fontFamily: 'inherit', color: '#94a3b8', fontSize: '0.75rem' }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: DS_TEXT.disabled, fontSize: '0.75rem' }}>
|
||||
Quelle: {s.source}
|
||||
</Typography>
|
||||
)}
|
||||
@@ -204,14 +205,14 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
})}
|
||||
|
||||
{signals.length === 0 && (
|
||||
<Typography sx={{ fontFamily: 'inherit', color: '#94a3b8', fontSize: '0.875rem', fontStyle: 'italic', textAlign: 'center', py: 4 }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: DS_TEXT.disabled, fontSize: '0.875rem', fontStyle: 'italic', textAlign: 'center', py: 4 }}>
|
||||
Keine Marktsignale für dieses Objekt verfügbar.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{/* Footer */}
|
||||
<Box sx={{ borderTop: '1px solid #e2e8f0', mt: 3, pt: 2 }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: '#94a3b8', fontSize: '0.75rem', textAlign: 'center' }}>
|
||||
<Box sx={{ borderTop: `1px solid ${DS_BORDER.default}`, mt: 3, pt: 2 }}>
|
||||
<Typography sx={{ fontFamily: 'inherit', color: DS_TEXT.disabled, fontSize: '0.75rem', textAlign: 'center' }}>
|
||||
Dieser Bericht wurde automatisch auf Basis von KI-generierten Marktsignalen erstellt. · Wincasa AG
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -219,7 +220,7 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
</Box>
|
||||
|
||||
{/* Footer actions */}
|
||||
<Box sx={{ px: 3, py: 1.5, borderTop: '1px solid #e2e8f0', bgcolor: 'white', display: 'flex', justifyContent: 'flex-end', gap: 1, flexShrink: 0 }}>
|
||||
<Box sx={{ px: 3, py: 1.5, borderTop: `1px solid ${DS_BORDER.default}`, bgcolor: 'white', display: 'flex', justifyContent: 'flex-end', gap: 1, flexShrink: 0 }}>
|
||||
<Button variant="outlined" onClick={onClose} sx={{ textTransform: 'none' }}>
|
||||
Schliessen
|
||||
</Button>
|
||||
@@ -228,7 +229,7 @@ export function BerichtDialog({ onClose, report, propertyId }: BerichtDialogProp
|
||||
startIcon={<Download size={15} />}
|
||||
onClick={handleDownload}
|
||||
disabled={!ready}
|
||||
sx={{ textTransform: 'none', bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#16304d' } }}
|
||||
sx={{ textTransform: 'none', bgcolor: DS_TEXT.brand, '&:hover': { bgcolor: '#16304d' } }}
|
||||
>
|
||||
Herunterladen
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user