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:
@@ -1,6 +1,6 @@
|
||||
import { MockupInquiryProvider } from '../provider/MockupInquiryProvider'
|
||||
import type { InquiryFilters } from '../provider/IInquiryProvider'
|
||||
import type { Inquiry, Attachment } from '../domain/inquiry'
|
||||
import type { InquiryFilters, CreateInquiryInput, CreateOfferInput } from '../provider/IInquiryProvider'
|
||||
import type { Inquiry, Attachment, InquiryMessage } from '../domain/inquiry'
|
||||
import type { ListResponse, ItemResponse } from './types'
|
||||
import { throwServiceError } from './errors'
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface InquiryReplyPayload {
|
||||
subject?: string
|
||||
body: string
|
||||
attachments?: Attachment[]
|
||||
senderType?: InquiryMessage['senderType'] // default 'supply_user'
|
||||
senderName?: string
|
||||
}
|
||||
|
||||
export const inquiryService = {
|
||||
@@ -31,14 +33,32 @@ export const inquiryService = {
|
||||
}
|
||||
},
|
||||
|
||||
async createInquiry(input: CreateInquiryInput): Promise<ItemResponse<Inquiry>> {
|
||||
try {
|
||||
const data = await provider.createInquiry(input)
|
||||
return { data }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
|
||||
async createOffer(input: CreateOfferInput): Promise<ItemResponse<Inquiry>> {
|
||||
try {
|
||||
const data = await provider.createOffer(input)
|
||||
return { data }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
}
|
||||
},
|
||||
|
||||
async sendInquiryReply(
|
||||
inquiryId: string,
|
||||
payload: InquiryReplyPayload,
|
||||
): Promise<ItemResponse<Inquiry>> {
|
||||
try {
|
||||
const data = await provider.addMessage(inquiryId, {
|
||||
senderType: 'supply_user',
|
||||
senderName: 'Wincasa AG',
|
||||
senderType: payload.senderType ?? 'supply_user',
|
||||
senderName: payload.senderName ?? 'Wincasa AG',
|
||||
subject: payload.subject,
|
||||
body: payload.body,
|
||||
attachments: payload.attachments ?? [],
|
||||
@@ -58,9 +78,9 @@ export const inquiryService = {
|
||||
}
|
||||
},
|
||||
|
||||
async getUnreadCount(): Promise<ItemResponse<number>> {
|
||||
async getUnreadCount(filters?: InquiryFilters): Promise<ItemResponse<number>> {
|
||||
try {
|
||||
const data = await provider.getUnreadCount()
|
||||
const data = await provider.getUnreadCount(filters)
|
||||
return { data }
|
||||
} catch (err) {
|
||||
throwServiceError(err)
|
||||
|
||||
Reference in New Issue
Block a user