b4d398270f
- Part A: Replace InquiryStatus badges with WhatsApp-style unread indicators (isRead, unreadCount, lastReadAt on Inquiry; markThreadAsRead in service + hook) - Part B: RelatedPropertyCardPanel — reposition "Objekt ansehen", add "Weitere Matches" section via matchService.getAdditionalMatchesForInquiry - Part C: PreparationWizard 4-step dialog — property selection, report generation progress, field editing + ReportObjectFieldSelector, PDF preview + finalize - Part D: OfferCreationWizard 4-step dialog triggered from first tenant message — data capture, field editing, viewing appointments, PDF generation + attach to reply - Part E: LatentInquiryReportPreview (2-page A4 doc), ReportObjectFieldSelector (5 accordion groups, 35+ optional fields), mapImageUrl on Property domain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
58 lines
2.7 KiB
TypeScript
58 lines
2.7 KiB
TypeScript
import type { OfferReportDraft, OfferEditableField } from '../domain/offerReport'
|
|
import { mockOfferReportDrafts } from '../mock-data/offerReportDrafts'
|
|
|
|
const store: OfferReportDraft[] = mockOfferReportDrafts.map(d => ({ ...d }))
|
|
|
|
function buildDefaultFields(tenantName: string, propertyTitle: string): OfferEditableField[] {
|
|
return [
|
|
{ id: 'recipient_salutation', label: 'Anrede / Einleitung', value: `Sehr geehrte(r) ${tenantName}`, fieldType: 'text' },
|
|
{ id: 'offer_intro', label: 'Angebotsbeschreibung', value: `Wir freuen uns, Ihnen folgendes Angebot für die Liegenschaft «${propertyTitle}» zu unterbreiten.`, fieldType: 'textarea' },
|
|
{ id: 'highlighted_criteria', label: 'Highlights der Liegenschaft', value: 'Exzellente Lage · Modernster Ausbaustandard · Flexible Mietkonditionen', fieldType: 'textarea' },
|
|
{ id: 'viewing_intro', label: 'Besichtigungstermine', value: 'Gerne laden wir Sie zu einer persönlichen Besichtigung ein. Folgende Termine sind verfügbar:', fieldType: 'textarea' },
|
|
{ id: 'next_steps', label: 'Nächste Schritte', value: 'Nach Ihrer Terminbestätigung erhalten Sie alle weiteren Unterlagen.', fieldType: 'textarea' },
|
|
{ id: 'closing', label: 'Abschluss', value: 'Mit freundlichen Grüssen\nWincasa AG', fieldType: 'textarea' },
|
|
]
|
|
}
|
|
|
|
export const offerReportService = {
|
|
async getByInquiry(inquiryId: string): Promise<OfferReportDraft | null> {
|
|
return store.find(d => d.inquiryId === inquiryId) ?? null
|
|
},
|
|
|
|
async create(inquiryId: string, propertyId: string, tenantName = '', propertyTitle = ''): Promise<OfferReportDraft> {
|
|
const existing = store.find(d => d.inquiryId === inquiryId && d.propertyId === propertyId)
|
|
if (existing) return existing
|
|
const draft: OfferReportDraft = {
|
|
id: crypto.randomUUID(),
|
|
inquiryId,
|
|
propertyId,
|
|
editableFields: buildDefaultFields(tenantName, propertyTitle),
|
|
viewingAppointments: [],
|
|
status: 'draft',
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
store.push(draft)
|
|
return draft
|
|
},
|
|
|
|
async update(draftId: string, data: Partial<OfferReportDraft>): Promise<OfferReportDraft> {
|
|
const idx = store.findIndex(d => d.id === draftId)
|
|
if (idx === -1) throw new Error(`Offer draft ${draftId} not found`)
|
|
store[idx] = { ...store[idx], ...data, updatedAt: new Date().toISOString() }
|
|
return store[idx]
|
|
},
|
|
|
|
async generatePdf(draftId: string): Promise<OfferReportDraft> {
|
|
const idx = store.findIndex(d => d.id === draftId)
|
|
if (idx === -1) throw new Error(`Offer draft ${draftId} not found`)
|
|
store[idx] = {
|
|
...store[idx],
|
|
status: 'finalized',
|
|
pdfUrl: `/mock-reports/${draftId}.pdf`,
|
|
updatedAt: new Date().toISOString(),
|
|
}
|
|
return store[idx]
|
|
},
|
|
}
|