feat(agenten): Runde 4 — «Meine Agenten» mit fünf digitalen Mitarbeitenden
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>
This commit is contained in:
@@ -11,20 +11,15 @@ import { useShortlistStore } from '../../stores/shortlistStore'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
import { ReviewStatus } from '../../domain/enums'
|
||||
import type { FutureSignal } from '../../domain/futureSignal'
|
||||
import { SOURCE_TYPE_LABELS } from '../../lib/constants'
|
||||
import { DS_ACCENT, DS_SLATE } from '../../lib/ds'
|
||||
|
||||
|
||||
const SOURCE_LABELS: Record<string, string> = {
|
||||
PRESS: 'Pressebericht',
|
||||
CONSTRUCTION_PERMIT: 'Baubewilligung',
|
||||
JOB_POSTING: 'Stelleninserat',
|
||||
COMPANY_REPORT: 'Geschäftsbericht',
|
||||
MARKET_DATA: 'Marktdaten',
|
||||
MANUAL: 'Manuell erfasst',
|
||||
}
|
||||
|
||||
const CREDIBILITY_META: Record<string, { label: string; color: string }> = {
|
||||
HIGH: { label: 'Hoch', color: '#1a7a4a' },
|
||||
MEDIUM: { label: 'Mittel', color: '#d97706' },
|
||||
LOW: { label: 'Niedrig', color: '#c0392b' },
|
||||
HIGH: { label: 'Hoch', color: DS_ACCENT.success.main },
|
||||
MEDIUM: { label: 'Mittel', color: DS_ACCENT.warning.main },
|
||||
LOW: { label: 'Niedrig', color: DS_ACCENT.danger.main },
|
||||
}
|
||||
|
||||
function BarRow({ label, value }: { label: string; value: number }) {
|
||||
@@ -38,7 +33,7 @@ function BarRow({ label, value }: { label: string; value: number }) {
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={value * 100}
|
||||
sx={{ height: 5, borderRadius: 2, bgcolor: '#f1f5f9', '& .MuiLinearProgress-bar': { bgcolor: color } }}
|
||||
sx={{ height: 5, borderRadius: 2, bgcolor: DS_SLATE[100], '& .MuiLinearProgress-bar': { bgcolor: color } }}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
@@ -92,12 +87,12 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
title: signal.title ?? signal.companyName ?? signal.locationHint,
|
||||
matchScore: Math.round(signal.confidenceScore * 100),
|
||||
confidenceScore: signal.confidenceScore,
|
||||
sourceLabel: SOURCE_LABELS[signal.source.type] ?? signal.source.type,
|
||||
sourceLabel: SOURCE_TYPE_LABELS[signal.source.type] ?? signal.source.type,
|
||||
addedBy: 'admin@ideal-sharing.ch',
|
||||
})
|
||||
}
|
||||
|
||||
const credMeta = CREDIBILITY_META[signal.source.credibility] ?? { label: signal.source.credibility, color: '#64748b' }
|
||||
const credMeta = CREDIBILITY_META[signal.source.credibility] ?? { label: signal.source.credibility, color: DS_SLATE[500] }
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
@@ -112,7 +107,7 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
<Typography variant="caption" color="text.secondary">{signal.locationHint}</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<IconButton size="small" onClick={onClose} sx={{ color: '#94a3b8', mt: -0.5 }}>
|
||||
<IconButton size="small" onClick={onClose} sx={{ color: DS_SLATE[400], mt: -0.5 }}>
|
||||
<X size={16} />
|
||||
</IconButton>
|
||||
</Box>
|
||||
@@ -121,7 +116,7 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
<SensitivityBadge level={signal.sensitivityLevel} />
|
||||
<SignalReviewStatusBadge status={signal.reviewStatus} />
|
||||
{signal.isVerified && (
|
||||
<Chip label="Verifiziert" size="small" sx={{ bgcolor: '#1a7a4a', color: 'white', fontSize: 10 }} />
|
||||
<Chip label="Verifiziert" size="small" sx={{ bgcolor: DS_ACCENT.success.main, color: 'white', fontSize: 10 }} />
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -169,7 +164,7 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, mb: 1.5 }}>
|
||||
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 140 }}>Typ</Typography>
|
||||
<Typography variant="caption">{SOURCE_LABELS[signal.source.type] ?? signal.source.type}</Typography>
|
||||
<Typography variant="caption">{SOURCE_TYPE_LABELS[signal.source.type] ?? signal.source.type}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ minWidth: 140 }}>Glaubwürdigkeit</Typography>
|
||||
@@ -188,7 +183,7 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
<>
|
||||
<Divider sx={{ my: 1.5 }} />
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, display: 'block', mb: 0.75 }}>Evidenz</Typography>
|
||||
<Paper variant="outlined" sx={{ p: 1.5, bgcolor: '#f8fafc' }}>
|
||||
<Paper variant="outlined" sx={{ p: 1.5, bgcolor: DS_SLATE[50] }}>
|
||||
<Typography variant="body2" color="text.secondary">{signal.evidence.summary}</Typography>
|
||||
</Paper>
|
||||
</>
|
||||
@@ -222,7 +217,7 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
<Button
|
||||
fullWidth size="small" variant="outlined"
|
||||
onClick={handleSendReview}
|
||||
sx={{ justifyContent: 'flex-start', color: '#d97706', borderColor: '#d97706' }}
|
||||
sx={{ justifyContent: 'flex-start', color: DS_ACCENT.warning.main, borderColor: DS_ACCENT.warning.main }}
|
||||
>
|
||||
Zur Prüfung senden
|
||||
</Button>
|
||||
@@ -232,7 +227,7 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
fullWidth size="small" variant="contained"
|
||||
disabled={updateStatus.isPending}
|
||||
onClick={() => handleStatus(ReviewStatus.APPROVED)}
|
||||
sx={{ bgcolor: '#1a7a4a', '&:hover': { bgcolor: '#15643c' }, justifyContent: 'flex-start' }}
|
||||
sx={{ bgcolor: DS_ACCENT.success.main, '&:hover': { bgcolor: DS_ACCENT.success.darkAlt }, justifyContent: 'flex-start' }}
|
||||
endIcon={updateStatus.isPending ? <CircularProgress size={14} color="inherit" /> : undefined}
|
||||
>
|
||||
Genehmigen
|
||||
@@ -243,7 +238,7 @@ export function FutureSignalDetailPanel({ signal, onClose }: Props) {
|
||||
fullWidth size="small" variant="outlined"
|
||||
disabled={updateStatus.isPending}
|
||||
onClick={() => handleStatus(ReviewStatus.REJECTED)}
|
||||
sx={{ justifyContent: 'flex-start', color: '#c0392b', borderColor: '#c0392b' }}
|
||||
sx={{ justifyContent: 'flex-start', color: DS_ACCENT.danger.main, borderColor: DS_ACCENT.danger.main }}
|
||||
>
|
||||
Ablehnen
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user