feat: F015 shortlist management
3-panel Shortlists page with left list, center detail, right decision brief panel. AddToShortlistDialog wired into result feed cards and match detail. Full DRAFT→REVIEW_READY→FINALIZED workflow, AI-generated decision brief draft, duplicate prevention, and compare-view integration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,45 @@ import type { AssetType } from '../domain/enums'
|
||||
import type { ParseNeedResult, ParsedNeedCriteria, FollowUpQuestion } from '../domain/needBuilder'
|
||||
import type { UnifiedMatchResult } from '../domain/unifiedResult'
|
||||
|
||||
// ── Decision Brief ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface DecisionBrief {
|
||||
id: string
|
||||
shortlistId: string
|
||||
summary: string
|
||||
sections: { title: string; body: string }[]
|
||||
generatedAt: string
|
||||
isDraft: true
|
||||
}
|
||||
|
||||
function buildMockDecisionBrief(shortlistId: string): DecisionBrief {
|
||||
return {
|
||||
id: crypto.randomUUID(),
|
||||
shortlistId,
|
||||
summary: 'Die Shortlist enthält qualitativ hochwertige Matches mit starker Standortübereinstimmung. Die verfügbaren Flächen decken den Bedarf gut ab. Zwei Objekte eignen sich als Erstbesichtigungen.',
|
||||
sections: [
|
||||
{
|
||||
title: 'Zusammenfassung der Objekte',
|
||||
body: 'Die Shortlist umfasst mehrere Objekte aus dem verifizierten Portfolio. Die Matchscores liegen zwischen 74 und 88, was auf eine gute bis sehr gute Übereinstimmung mit den Suchkriterien hinweist.',
|
||||
},
|
||||
{
|
||||
title: 'Standortbewertung',
|
||||
body: 'Die Mehrheit der Objekte befindet sich in bevorzugten Lagen. Die ÖV-Anbindung ist bei allen Objekten als gut bis sehr gut einzustufen.',
|
||||
},
|
||||
{
|
||||
title: 'Budgetanalyse',
|
||||
body: 'Die Mietpreise liegen im budgetkonformen Bereich. Keine der Optionen überschreitet das maximale Budget pro m².',
|
||||
},
|
||||
{
|
||||
title: 'Empfohlene nächste Schritte',
|
||||
body: '1. Besichtigung der Top-2-Objekte vereinbaren. 2. Detaillierte Flächenpläne anfordern. 3. Vertragskonditionen prüfen lassen.',
|
||||
},
|
||||
],
|
||||
generatedAt: new Date().toISOString(),
|
||||
isDraft: true,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Compare Summary ───────────────────────────────────────────────────────────
|
||||
|
||||
export interface ComparisonSummary {
|
||||
@@ -284,7 +323,7 @@ function mockParseNeed(input: string): ParseNeedResult {
|
||||
timing,
|
||||
mustHaveCriteria,
|
||||
infrastructureRequirements: [],
|
||||
accessibilityRequirements: mustHaveCriteria.includes('Gute ÖV-Anbindung') ? ['ÖV-Anbindung'] : [],
|
||||
accessibilityRequirements: [],
|
||||
prestigeImportance,
|
||||
flexibilityNeed: lower.includes('flexibel') ? 'HIGH' : 'MEDIUM',
|
||||
expansionPotential: lower.includes('wachstum') || lower.includes('expansion'),
|
||||
@@ -377,4 +416,9 @@ export const aiService = {
|
||||
const result = mockParseNeed(JSON.stringify(criteria))
|
||||
return { data: result.followUpQuestionCandidates }
|
||||
},
|
||||
|
||||
async generateDecisionBrief(shortlistId: string): Promise<ItemResponse<DecisionBrief>> {
|
||||
await new Promise(r => setTimeout(r, 1800))
|
||||
return { data: buildMockDecisionBrief(shortlistId) }
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MockupShortlistProvider } from '../provider/MockupShortlistProvider'
|
||||
import type { ShortlistFilters } from '../provider/IShortlistProvider'
|
||||
import type { Shortlist, CreateShortlistInput, UpdateShortlistInput } from '../domain/shortlist'
|
||||
import type { Shortlist, CreateShortlistInput, UpdateShortlistInput, ShortlistItemInput } from '../domain/shortlist'
|
||||
import type { ListResponse, ItemResponse } from './types'
|
||||
|
||||
const provider = MockupShortlistProvider
|
||||
@@ -22,12 +22,12 @@ export const shortlistService = {
|
||||
const data = await provider.update(id, input)
|
||||
return { data }
|
||||
},
|
||||
async addItem(id: string, propertyId: string, note?: string): Promise<ItemResponse<Shortlist>> {
|
||||
const data = await provider.addItem(id, propertyId, note)
|
||||
async addItem(id: string, item: ShortlistItemInput): Promise<ItemResponse<Shortlist>> {
|
||||
const data = await provider.addItem(id, item)
|
||||
return { data }
|
||||
},
|
||||
async removeItem(id: string, propertyId: string): Promise<ItemResponse<Shortlist>> {
|
||||
const data = await provider.removeItem(id, propertyId)
|
||||
async removeItem(id: string, resultId: string): Promise<ItemResponse<Shortlist>> {
|
||||
const data = await provider.removeItem(id, resultId)
|
||||
return { data }
|
||||
},
|
||||
async remove(id: string): Promise<ItemResponse<void>> {
|
||||
|
||||
Reference in New Issue
Block a user