Runde 8 hat Livia zur Research- & Market-Intelligence-Agentin gemacht. Die Exposé-Erstellung war der Kern ihrer alten Rolle und fällt mit ihr weg — sie wird nicht nachgeordnet weitergeführt. Ein zweiter Reiter hätte die Funktion sichtbar gelassen und die neue Positionierung damit halbiert. Entfernt: 23 Dateien (Arbeitsbereich, dreistufiger Assistent, Dossier mit elf Bereichen, Provider, Services, Hooks, Feldkatalog, Mockdaten, deterministischer Textbaustein) sowie `generateExposeText` aus `IAIService` und beiden Implementierungen. Livias Arbeitsplatz zeigt jetzt nur noch die Recherche-Leads — ohne Reiterbalken, weil eine Leiste mit einem Eintrag eine Alternative behauptet, die es nicht gibt. Ihr Personalblatt, die Bearbeitungsverlaufs-Einträge und die Teamdokumentation sind auf die neue Rolle nachgezogen; die beiden Beispielvorgänge zeigen jetzt eine Leaderzeugung und ein Review mit widersprüchlichen Quellen statt Lagebeschrieb und Exposétext. Bei Nora entfällt die Mehrfachauswahl in der Objektliste: sie diente allein der Weiterleitung an die Exposé-Erstellung. Eine Auswahl ohne Ziel wäre ein Bedienelement, das nichts auslöst. Die Matching-Darstellung selbst bleibt unverändert. Der Wiederaufbau ist in `archive/livia/` beschrieben; das Manifest hält jetzt fest, welche seiner Annahmen eingetreten sind und welche nicht — Livia bleibt im Team, nur die Funktion ist weg. Geprüft: tsc 0 Fehler, eslint 0 Fehler/0 Warnungen, 423/423 Tests grün, Produktionsbuild erfolgreich, Smoke-Test über sieben Seiten ohne Laufzeitfehler, 12/12 Abnahmekriterien erfüllt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
112 lines
4.5 KiB
TypeScript
112 lines
4.5 KiB
TypeScript
import { Box, Chip, Typography } from '@mui/material'
|
|
import { Building2, ExternalLink } from 'lucide-react'
|
|
import type { Property } from '../../domain/property'
|
|
import type { ExtractedContact } from '../../domain/futureSignal'
|
|
import { ObjectDeepLink } from '../team'
|
|
import { CONTACT_ICONS } from './marketLeadHelpers'
|
|
import { DS_BG, DS_BRAND, DS_SLATE, DS_TEXT } from '../../lib/ds'
|
|
|
|
/**
|
|
* Bausteine der Signal-Detailansicht auf der Nora-Seite.
|
|
*
|
|
* Ausgezogen aus `MarketIntelligence.tsx` — die Seite hält jetzt nur noch
|
|
* Reiter, Auswahl und Rahmen.
|
|
*/
|
|
|
|
// ── Left pane list item ──────────────────────────────────────────────────────
|
|
|
|
// ── Contact row ──────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Extrahierte Kontaktdaten (Runde 4, §7.3).
|
|
*
|
|
* Ohne Konfidenzstufe und ohne Farbpunkt: gezeigt wird nur noch, was klar
|
|
* belegbar ist — siehe `confirmableContacts`. Ein Wert mit dem Hinweis
|
|
* «spekulativ» hilft niemandem; entweder man kann ihn anschreiben oder nicht.
|
|
*/
|
|
function ContactRow({ contact }: { contact: ExtractedContact }) {
|
|
const icon = CONTACT_ICONS[contact.type]
|
|
const isClickable = contact.type === 'WEBSITE' || contact.type === 'LINKEDIN' || contact.type === 'EMAIL'
|
|
const href = contact.type === 'EMAIL' ? `mailto:${contact.value}` : contact.type === 'WEBSITE' || contact.type === 'LINKEDIN' ? `https://${contact.value.replace(/^https?:\/\//, '')}` : undefined
|
|
|
|
return (
|
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1.25, py: 0.75, borderBottom: '1px solid #f8fafc' }}>
|
|
<Box sx={{ color: DS_SLATE[500], mt: 0.125, flexShrink: 0 }}>{icon}</Box>
|
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
{isClickable && href ? (
|
|
<Box
|
|
component="a"
|
|
href={href}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
sx={{ color: DS_BRAND.main, fontWeight: 500, fontSize: '0.8125rem', textDecoration: 'none', '&:hover': { textDecoration: 'underline' }, display: 'flex', alignItems: 'center', gap: 0.5 }}
|
|
>
|
|
{contact.value}
|
|
<ExternalLink size={11} />
|
|
</Box>
|
|
) : (
|
|
<Typography variant="body2" sx={{ fontWeight: 500, fontSize: '0.8125rem', color: DS_SLATE[900] }}>
|
|
{contact.value}
|
|
</Typography>
|
|
)}
|
|
{contact.label && (
|
|
<Typography variant="caption" sx={{ color: DS_SLATE[400], fontSize: '0.7rem' }}>
|
|
{contact.label}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
// ── Objektzeile ──────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Ein passendes Portfolioobjekt zu einem Signal.
|
|
*
|
|
* Der Anschreiben-Entwurf und die Reminder-Aktion sind mit Runde 4 entfallen
|
|
* (§7.3), die Mehrfachauswahl mit Runde 8: sie diente allein dazu, Objekte an
|
|
* die Exposé-Erstellung weiterzureichen, und diese Funktion gibt es bei Livia
|
|
* nicht mehr. Eine Auswahl ohne Ziel wäre ein Bedienelement, das nichts
|
|
* auslöst — schlimmer als keines.
|
|
*
|
|
* Geblieben ist, was Nora für die Entscheidung braucht: welches Objekt, wo,
|
|
* wie gross, zu welchem Preis und wie gut die Fläche passt.
|
|
*/
|
|
function SignalPropertyRow({
|
|
p, fit,
|
|
}: {
|
|
p: Property
|
|
fit: number | null
|
|
}) {
|
|
return (
|
|
<Box
|
|
sx={{
|
|
display: 'flex', alignItems: 'center', gap: 1,
|
|
px: 1.5, py: 1.25, mb: 1,
|
|
border: '1px solid #e2e8f0',
|
|
borderRadius: 1.5,
|
|
bgcolor: 'white',
|
|
}}
|
|
>
|
|
<Building2 size={14} color="#152642" style={{ flexShrink: 0 }} />
|
|
<Box sx={{ flex: 1, minWidth: 0 }}>
|
|
<ObjectDeepLink propertyId={p.id} label={p.title} fontWeight={600} />
|
|
<Typography variant="caption" sx={{ color: DS_SLATE[500] }}>
|
|
{p.location.city} · {p.areaSqm.toLocaleString('de-CH')} m² · CHF {p.rentPricePerSqm}/m²/J
|
|
</Typography>
|
|
</Box>
|
|
{fit !== null && (
|
|
<Chip
|
|
label={`${fit}% Flächenmatch`}
|
|
size="small"
|
|
sx={{ height: 20, fontSize: '0.65rem', fontWeight: 600, flexShrink: 0, bgcolor: DS_BG.subtle, color: DS_TEXT.secondary }}
|
|
/>
|
|
)}
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
|
|
export { ContactRow, SignalPropertyRow }
|