e95490eb72
- Grundriss (FloorPlanSection): PropertyUnit.floorPlanUrl?, Property.floorPlanUrl?; FloorPlanSection in MatchDetail + PropertyDetail; FloorPlanUrlSection in NewListing-Formular - Listenansicht (MatchCardCompact): horizontales Layout mit 120px Bildstreifen, Score-Badge, Asset-Label-Overlay, alle 3 grünen Punkte, Anfrage-Button - Light Industrial → "Gewerbe" überall (NeedInput, NeedCardPreview, CriteriaReviewPanel, newListingConstants, MyListings, propertyHelpers) - "Zum Originalinserat"-Button nur bei Maison-Work-Objekten - Image-Pool: propertyImageResolver mit sequentiellem Pool-Index (keine doppelten Bilder), nur Innenaufnahmen, rotate()-Trick für Sub-Pools - Overlay-Labels (Objekttyp + Stadtteil) in LocationPreview + IntelligenceMatchCard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
49 lines
2.4 KiB
TypeScript
49 lines
2.4 KiB
TypeScript
import type { PipelineItem } from '../../domain/pipeline'
|
|
export { matchScoreHex as scoreColor } from '../../lib/utils'
|
|
|
|
export function detailPath(item: PipelineItem): string | null {
|
|
// Both 'match-XXX' (static mock) and 'm__...' (deterministic dynamic) IDs are stable across sessions
|
|
const isStableMatchId = item.matchId?.startsWith('match-') || item.matchId?.startsWith('m__')
|
|
if (isStableMatchId) return `/demand/results/${item.matchId}`
|
|
if (item.propertyId) return `/demand/property/${item.propertyId}`
|
|
if (item.matchId) return `/demand/results/${item.matchId}`
|
|
return null
|
|
}
|
|
|
|
export function getKiInsight(item: PipelineItem): { summary: string; positives: string[]; risks: string[] } {
|
|
if (item.stage === 'SAVED') return {
|
|
summary: `Merkliste-Eintrag mit ${item.matchScore}% Match. Prüfen Sie, ob dieses Objekt qualifiziert werden soll.`,
|
|
positives: [`Match ${item.matchScore}%`],
|
|
risks: ['Noch nicht qualifiziert'],
|
|
}
|
|
if (item.stage === 'CLOSED_WON') return {
|
|
summary: `Abschluss erfolgreich. ${item.title} wurde zu ${item.matchScore}% Match abgeschlossen.`,
|
|
positives: ['Vertraglich gesichert', `Match ${item.matchScore}%`, 'Alle Kriterien erfüllt'],
|
|
risks: [],
|
|
}
|
|
if (item.stage === 'CLOSED_LOST') return {
|
|
summary: item.notes ?? 'Objekt nicht realisiert.',
|
|
positives: [],
|
|
risks: ['Nicht verfügbar', 'Alternative Optionen prüfen'],
|
|
}
|
|
const s = item.matchScore
|
|
return {
|
|
summary: s >= 80
|
|
? `Starkes Objekt (${s}%) — deckt die wesentlichen Suchkriterien ab. Prozess aktiv weitertreiben.`
|
|
: s >= 65
|
|
? `Solides Objekt (${s}%) mit Potenzial. Gezielte Klärung offener Punkte empfohlen.`
|
|
: `Schwächerer Match (${s}%). Abweichungen kritisch prüfen bevor weitere Ressourcen investiert werden.`,
|
|
positives: [
|
|
...(s >= 80 ? [`Match ${s}% — hohe Übereinstimmung`] : []),
|
|
...(item.areaLabel ? [`Fläche: ${item.areaLabel}`] : []),
|
|
...(item.resultType === 'VERIFIED_PORTFOLIO' ? ['Geprüftes Portfolio-Objekt'] : []),
|
|
...(item.stage === 'NEGOTIATION' ? ['Verhandlung läuft — kurz vor Abschluss'] : []),
|
|
].slice(0, 3),
|
|
risks: [
|
|
...(s < 80 ? [`Match ${s}% — Abweichungen prüfen`] : []),
|
|
...(item.notes?.includes('Budget') ? ['Budget-Diskrepanz erwähnt'] : []),
|
|
...(item.resultType === 'FUTURE_AVAILABILITY' ? ['Verfügbarkeit noch nicht bestätigt'] : []),
|
|
].slice(0, 2),
|
|
}
|
|
}
|