d200d4e930
Setzt das Umsetzungsbriefing Runde 4 im bestehenden Frontend um. Informationsarchitektur - «Teamübersicht» heisst «Meine Agenten»; ihr bisheriger Inhalt (Kreisgrafik, Auswertung, Verbindungszusammenfassung) ist entfallen. - Personalverwaltung, Bearbeitungsverlauf und Kanäle & Systeme sind jetzt Reiter derselben Seite (?section=), gestaltet wie die Auswahl im Bearbeitungsverlauf. Es wird nur der gewählte Bereich gerendert. - Die alten Unterseitenpfade leiten um, damit Lesezeichen nicht brechen. Agentenbestand - Reto und Lea vollständig entfernt — aus Navigation, Dossiers, Protokoll, Verbindungen, Vorgängen und Porträtbestand. - Retos Aufgaben liegen bei Bruno: Nachbereitung, WhatsApp-Anruf, Protokoll und Kundennotiz, Ablage im CRM. - Livia ist «Exposé Master»: Lageberichte, Inserate, Angebotsbroschüren. - Sidebar führt Ferdi, Bruno, Livia, Nora, Sina mit Porträt und Funktion. Agentenseiten - Ein gemeinsamer AgentWorkspaceHero auf allen fünf Seiten. - Ferdi: Auswertungskarten, Priorität und Typfarben entfallen; Fälligkeit nur bei fünf Tagen oder weniger rot; Objektlinks nach «Meine Objekte»; neu die Terminplanung im verbundenen Kalender mit typgerechtem PDF-Ausschnitt. - Sina: reduzierte, filter- und sortierbare Objektübersicht; Detailansicht direkt editierbar, leere Pflichtfelder rot umrandet. - Nora: Signale ohne Prozentsätze und Konfidenzstufen, nur belegbare Angaben; Mehrfachauswahl leitet Objekte an Livia weiter. - Livia: aktive und archivierte Leads, Arbeitsbereich gleitet an den oberen Rand; dreistufiger Exposé-Prozess Hochladen → Exposé → Export. - Bruno: neue Seite mit Auftragsliste und Vor-/Nachbereitungs-Drawer; Glocke warnt bei Besichtigung unter 24 Stunden ohne Bericht. Datenschicht - Neu: Kalender, Exposé-Leads, Exposé-Entwürfe, Besichtigungsaufträge — je Domain, Provider, Service und Hook. - IAIService um generateExposeText erweitert; der Entwurf nutzt ausschliesslich erfasste Objektdaten und meldet Lücken, statt sie zu füllen. - Alle Objektverweise zeigen auf reale Einträge aus «Meine Objekte»; neue Detailroute /supply/properties/:propertyId. Offen: Chat, Kalender, CRM und DMS sind Frontend-Simulation ohne Anbindung. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
103 lines
3.9 KiB
TypeScript
103 lines
3.9 KiB
TypeScript
import { Box, LinearProgress, Tooltip, Typography, Chip } from '@mui/material'
|
|
import { AlertTriangle } from 'lucide-react'
|
|
import { dataQualityColor, dataQualityHex, formatPercent } from '../../lib/utils'
|
|
import { FRESHNESS_LABELS } from '../../lib/constants'
|
|
import type { DataQuality } from '../../domain/property'
|
|
import { DS_ACCENT, DS_SLATE } from '../../lib/ds'
|
|
|
|
interface DataQualityBarProps {
|
|
quality: DataQuality
|
|
compact?: boolean
|
|
showWarnings?: boolean
|
|
}
|
|
|
|
export function DataQualityBar({ quality, compact = false, showWarnings = true }: DataQualityBarProps) {
|
|
const color = dataQualityColor(quality.score)
|
|
const hex = dataQualityHex(quality.score)
|
|
const pct = Math.round(quality.score * 100)
|
|
|
|
const tooltipContent = (
|
|
<Box sx={{ p: 0.5 }}>
|
|
<Typography variant="caption" sx={{ fontWeight: 600, display: 'block', mb: 0.5 }}>
|
|
Datenqualität {formatPercent(quality.score)}
|
|
</Typography>
|
|
{quality.missingCriticalFields.length > 0 && (
|
|
<Box sx={{ mb: 0.5 }}>
|
|
<Typography variant="caption" sx={{ color: DS_ACCENT.danger.border, display: 'block' }}>Fehlende Pflichtfelder:</Typography>
|
|
{quality.missingCriticalFields.map(f => (
|
|
<Typography key={f} variant="caption" sx={{ display: 'block', pl: 1 }}>• {f}</Typography>
|
|
))}
|
|
</Box>
|
|
)}
|
|
{quality.warnings.length > 0 && (
|
|
<Box>
|
|
<Typography variant="caption" sx={{ color: DS_ACCENT.warning.border, display: 'block' }}>Warnungen:</Typography>
|
|
{quality.warnings.map((w, i) => (
|
|
<Typography key={i} variant="caption" sx={{ display: 'block', pl: 1 }}>• {w}</Typography>
|
|
))}
|
|
</Box>
|
|
)}
|
|
<Typography variant="caption" sx={{ color: DS_SLATE[400], display: 'block', mt: 0.5 }}>
|
|
Aktualität: {FRESHNESS_LABELS[quality.freshness]}
|
|
{quality.lastVerifiedAt ? ` · Geprüft: ${quality.lastVerifiedAt}` : ''}
|
|
</Typography>
|
|
</Box>
|
|
)
|
|
|
|
if (compact) {
|
|
return (
|
|
<Tooltip title={tooltipContent} arrow placement="top">
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75, cursor: 'default' }}>
|
|
<Box sx={{ width: 56 }}>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
value={pct}
|
|
color={color}
|
|
sx={{ height: 5, borderRadius: 3 }}
|
|
/>
|
|
</Box>
|
|
<Typography variant="caption" sx={{ color: hex, fontWeight: 600, fontSize: '0.7rem', whiteSpace: 'nowrap' }}>
|
|
{pct}%
|
|
</Typography>
|
|
{quality.missingCriticalFields.length > 0 && showWarnings && (
|
|
<AlertTriangle size={11} color="#d97706" />
|
|
)}
|
|
</Box>
|
|
</Tooltip>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<Box>
|
|
<Tooltip title={tooltipContent} arrow placement="top">
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, cursor: 'default' }}>
|
|
<Box sx={{ flex: 1, minWidth: 80 }}>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
value={pct}
|
|
color={color}
|
|
sx={{ height: 6, borderRadius: 3 }}
|
|
/>
|
|
</Box>
|
|
<Typography variant="caption" sx={{ color: hex, fontWeight: 600, whiteSpace: 'nowrap' }}>
|
|
{pct}%
|
|
</Typography>
|
|
</Box>
|
|
</Tooltip>
|
|
{showWarnings && quality.missingCriticalFields.length > 0 && (
|
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.25, mt: 0.5 }}>
|
|
{quality.missingCriticalFields.slice(0, 2).map(f => (
|
|
<Chip key={f} label={f} size="small" color="error" variant="outlined"
|
|
sx={{ height: 16, fontSize: '0.625rem' }} />
|
|
))}
|
|
{quality.missingCriticalFields.length > 2 && (
|
|
<Typography variant="caption" sx={{ color: 'error.main' }}>
|
|
+{quality.missingCriticalFields.length - 2}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
)
|
|
}
|