feat(messages): unified conversation inbox — connect demand ↔ supply, offers reach the seeker

Phase 1 of the messaging rework: one conversation/thread model (Inquiry) is the single source of truth, read perspectivally by both sides.

- domain/inquiry: add kind (INQUIRY|OFFER), tenantOrgId (demand org), offeredPropertyIds
- provider/service: InquiryFilters gains tenantOrgId+kind; new createInquiry (demand→supply) & createOffer (supply→demand) materialize threads; addMessage bumps unread; reply carries senderType (tenant vs supply_user)
- hooks: useCreateInquiry, useCreateOffer; reply payload carries sender perspective
- demand: InquiryQuickDialog → createInquiry via provider (session-based tenant/org, no hardcode); Anfragen.tsx reads via React Query (tenantOrgId) with Gesendet/Erhalten tabs, replies via provider, shows offered properties for OFFER threads; inquiryStore reduced to dialog-only (removes Zustand server-data island)
- supply: OfferChatComposer send now materializes an OFFER conversation into the seeker's inbox (routed via latentNeed.tenantOrgId); Anfragencenter gains a "Gesendet" tab; ActiveInquiriesTab filters by perspective (org + kind)
- seed: merge demand inquiries with tenantOrgId=org-mobimo + 2 OFFER seeds; provider seeds from both sets

Closes the loop: a sent inquiry reaches the Verwaltung and a sent offer reaches the Nachfrager — both answerable in one thread.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-06-21 01:32:13 +02:00
parent 6206447dae
commit a8b54af0b8
13 changed files with 349 additions and 110 deletions
+10
View File
@@ -20,10 +20,20 @@ export interface InquiryMessage {
export type InquiryStatus = 'new' | 'in_progress' | 'answered' | 'archived'
/** Konversationstyp: Nachfrager-initiierte Anfrage vs. Verwaltung-initiiertes Angebot. */
export type InquiryKind = 'INQUIRY' | 'OFFER'
export interface Inquiry {
id: string
/** Supply-Organisation (Eigentümer/Verwaltung des Objekts). */
organizationId: string
/** Nachfrager-Organisation — für das Demand-Postfach. */
tenantOrgId?: string
/** INQUIRY (Demand→Supply) oder OFFER (Supply→Demand). Default INQUIRY. */
kind?: InquiryKind
propertyId: string
/** Bei OFFER: alle im Angebot enthaltenen Objekte. */
offeredPropertyIds?: string[]
needId?: string
tenantName: string
tenantCompany?: string
+2
View File
@@ -5,6 +5,8 @@ export interface LatentNeed {
id: string
title: string
tenantCompany?: string
/** Nachfrager-Organisation — Empfänger eines Angebots (Demo-Default-Routing: org-mobimo). */
tenantOrgId?: string
assetType: AssetType
desiredLocation: string
sizeRange: { min: number; max: number }