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,7 +1,7 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { inquiryService } from '../services/inquiryService'
|
||||
import type { InquiryFilters } from '../provider/IInquiryProvider'
|
||||
import type { Attachment } from '../domain/inquiry'
|
||||
import type { InquiryFilters, CreateInquiryInput, CreateOfferInput } from '../provider/IInquiryProvider'
|
||||
import type { Attachment, InquiryMessage } from '../domain/inquiry'
|
||||
import { useToastStore } from '../stores/toastStore'
|
||||
|
||||
export function useActiveInquiries(filters?: InquiryFilters) {
|
||||
@@ -29,7 +29,7 @@ export function useSendInquiryReply() {
|
||||
payload,
|
||||
}: {
|
||||
inquiryId: string
|
||||
payload: { subject?: string; body: string; attachments?: Attachment[] }
|
||||
payload: { subject?: string; body: string; attachments?: Attachment[]; senderType?: InquiryMessage['senderType']; senderName?: string }
|
||||
}) => inquiryService.sendInquiryReply(inquiryId, payload),
|
||||
onSuccess: (_data, { inquiryId }) => {
|
||||
qc.invalidateQueries({ queryKey: ['inquiry', inquiryId] })
|
||||
@@ -41,6 +41,31 @@ export function useSendInquiryReply() {
|
||||
})
|
||||
}
|
||||
|
||||
/** Demand→Supply: neue Anfrage erstellen (ersetzt die alte Zustand-Insel). */
|
||||
export function useCreateInquiry() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (input: CreateInquiryInput) => inquiryService.createInquiry(input),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['inquiries'] })
|
||||
},
|
||||
onError: () => {
|
||||
useToastStore.getState().showToast('Anfrage konnte nicht gesendet werden.', 'error')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/** Supply→Demand: Angebot als Konversation im Nachfrager-Postfach materialisieren. */
|
||||
export function useCreateOffer() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: (input: CreateOfferInput) => inquiryService.createOffer(input),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['inquiries'] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useMarkThreadAsRead() {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
|
||||
Reference in New Issue
Block a user