feat: in-memory inquiry flow — send from card, pipeline, and detail
New inquiryStore (Zustand) holds sent inquiries for the session. InquiryQuickDialog reads from the store (no props), renders wherever needed. Sent inquiries appear immediately at the top of the Anfragen page. Entry points: - Match cards: "Anfrage" button on every card in the results feed - MatchDetail: primary action in NextActionsPanel - Pipeline: chat icon on every DraggableCard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
CheckCircle2,
|
||||
} from 'lucide-react'
|
||||
import { useSessionStore } from '../../stores/sessionStore'
|
||||
import { useInquiryStore } from '../../stores/inquiryStore'
|
||||
import { LocationPreview } from '../shared/LocationPreview'
|
||||
import { HeatBadge } from '../shared/HeatBadge'
|
||||
import { getScoreTier, SCORE_THEME } from '../shared/scoreTheme'
|
||||
@@ -25,6 +26,7 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro
|
||||
const tier = getScoreTier(vm.matchScore)
|
||||
const theme = SCORE_THEME[tier]
|
||||
const { currentUser } = useSessionStore()
|
||||
const openInquiryDialog = useInquiryStore(s => s.openInquiryDialog)
|
||||
const isPortfolioOwner = currentUser?.role === 'PROPERTY_MANAGER' || currentUser?.role === 'ORGANIZATION_ADMIN'
|
||||
const rt = RESULT_TYPE_META[vm.resultType] ?? { label: vm.resultType, color: '#64748b' }
|
||||
const isFuture = vm.resultType === 'FUTURE_AVAILABILITY'
|
||||
@@ -141,6 +143,23 @@ export function IntelligenceMatchCard({ vm, imageUrl, lat, lng, cityLabel }: Pro
|
||||
|
||||
{/* Actions */}
|
||||
<Box sx={{ display: 'flex', gap: 0.75, mt: 1.5, pt: 1.25, borderTop: '1px solid rgba(0,0,0,0.06)' }}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
openInquiryDialog({
|
||||
propertyTitle: vm.title,
|
||||
location: vm.locationLabel ?? '',
|
||||
matchScore: vm.matchScore,
|
||||
matchId: vm.id,
|
||||
propertyId: vm.propertyId,
|
||||
})
|
||||
}}
|
||||
sx={{ textTransform: 'none', fontSize: '0.7rem', py: 0.375, px: 1, bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' } }}
|
||||
>
|
||||
Anfrage
|
||||
</Button>
|
||||
{vm.actions.map(a => (
|
||||
<Button
|
||||
key={a.id}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import {
|
||||
Box, Button, Dialog, DialogActions, DialogContent, DialogTitle,
|
||||
TextField, Typography,
|
||||
} from '@mui/material'
|
||||
import { Send } from 'lucide-react'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
import { useInquiryStore } from '../../stores/inquiryStore'
|
||||
import { DS_BG, DS_BORDER, DS_TEXT } from '../../lib/ds'
|
||||
|
||||
function buildTemplate(propertyTitle: string, location: string, areaLabel?: string): string {
|
||||
const areaLine = areaLabel ? `\nDie Fläche von ${areaLabel} entspricht unserem Bedarf.` : ''
|
||||
return `Guten Tag\n\nWir interessieren uns für Ihre Fläche «${propertyTitle}» in ${location}.${areaLine} Gerne würden wir einen Besichtigungstermin vereinbaren und offene Fragen klären.\n\nFür Rückfragen stehen wir jederzeit zur Verfügung.\n\nFreundliche Grüsse`
|
||||
}
|
||||
|
||||
export function InquiryQuickDialog() {
|
||||
const showToast = useToastStore(s => s.showToast)
|
||||
const dialogOpen = useInquiryStore(s => s.dialogOpen)
|
||||
const pendingInquiry = useInquiryStore(s => s.pendingInquiry)
|
||||
const closeInquiryDialog = useInquiryStore(s => s.closeInquiryDialog)
|
||||
const addInquiry = useInquiryStore(s => s.addInquiry)
|
||||
|
||||
const [message, setMessage] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogOpen && pendingInquiry) {
|
||||
setMessage(buildTemplate(pendingInquiry.propertyTitle, pendingInquiry.location, pendingInquiry.areaLabel))
|
||||
}
|
||||
}, [dialogOpen, pendingInquiry])
|
||||
|
||||
function handleSend() {
|
||||
if (!pendingInquiry) return
|
||||
addInquiry(pendingInquiry, message)
|
||||
showToast('Anfrage gesendet — Sie erhalten eine Antwort per E-Mail.', 'success')
|
||||
closeInquiryDialog()
|
||||
}
|
||||
|
||||
if (!pendingInquiry) return null
|
||||
|
||||
return (
|
||||
<Dialog open={dialogOpen} onClose={closeInquiryDialog} maxWidth="sm" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700, pb: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Send size={16} />
|
||||
Anfrage senden
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ pt: 0 }}>
|
||||
<Box sx={{ bgcolor: DS_BG.subtle, border: `1px solid ${DS_BORDER.default}`, borderRadius: 1.5, p: 1.5, mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{pendingInquiry.propertyTitle}</Typography>
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.secondary }}>
|
||||
{pendingInquiry.location}
|
||||
{pendingInquiry.areaLabel && ` · ${pendingInquiry.areaLabel}`}
|
||||
{pendingInquiry.rentLabel && ` · ${pendingInquiry.rentLabel}`}
|
||||
{' · '}Match {pendingInquiry.matchScore}%
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={8}
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
sx={{ '& .MuiInputBase-root': { fontSize: '0.875rem' } }}
|
||||
/>
|
||||
|
||||
<Typography variant="caption" sx={{ color: DS_TEXT.muted, display: 'block', mt: 1 }}>
|
||||
Der Text kann vor dem Absenden angepasst werden.
|
||||
</Typography>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ px: 3, pb: 2, gap: 1 }}>
|
||||
<Button onClick={closeInquiryDialog} size="small" sx={{ color: DS_TEXT.muted }}>Abbrechen</Button>
|
||||
<Button
|
||||
onClick={handleSend}
|
||||
variant="contained"
|
||||
size="small"
|
||||
disabled={!message.trim()}
|
||||
startIcon={<Send size={14} />}
|
||||
sx={{ bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' } }}
|
||||
>
|
||||
Anfrage senden
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,23 +1,17 @@
|
||||
import { Box, Button, Paper, Typography } from '@mui/material'
|
||||
import { MessageSquare } from 'lucide-react'
|
||||
import type { Match, NextBestAction } from '../../domain/match'
|
||||
|
||||
const PRIORITY_ORDER: Record<string, number> = { HIGH: 0, MEDIUM: 1, LOW: 2 }
|
||||
|
||||
const PRIORITY_COLOR: Record<string, 'contained' | 'outlined'> = {
|
||||
HIGH: 'contained',
|
||||
MEDIUM: 'outlined',
|
||||
LOW: 'outlined',
|
||||
}
|
||||
|
||||
interface Props {
|
||||
match: Match
|
||||
onCompare?: () => void
|
||||
onShortlist?: () => void
|
||||
onReject?: () => void
|
||||
onReview?: () => void
|
||||
onPipeline?: () => void
|
||||
onInquire?: () => void
|
||||
}
|
||||
|
||||
export function NextActionsPanel({ match, onCompare, onShortlist, onReject, onReview }: Props) {
|
||||
export function NextActionsPanel({ match, onCompare, onPipeline, onInquire }: Props) {
|
||||
const engineActions: NextBestAction[] = [...(match.nextBestActions ?? [])].sort(
|
||||
(a, b) => (PRIORITY_ORDER[a.priority] ?? 2) - (PRIORITY_ORDER[b.priority] ?? 2)
|
||||
)
|
||||
@@ -26,37 +20,41 @@ export function NextActionsPanel({ match, onCompare, onShortlist, onReject, onRe
|
||||
<Paper sx={{ p: 2.5, mb: 2 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1.5 }}>Empfohlene Aktionen</Typography>
|
||||
|
||||
{engineActions.length > 0 && (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75, mb: 1.5 }}>
|
||||
{engineActions.map((action, i) => (
|
||||
<Box key={i}>
|
||||
<Button
|
||||
fullWidth
|
||||
size="small"
|
||||
variant={PRIORITY_COLOR[action.priority]}
|
||||
sx={
|
||||
action.priority === 'HIGH'
|
||||
? { bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' }, justifyContent: 'flex-start' }
|
||||
: { justifyContent: 'flex-start' }
|
||||
}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
{action.description && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25, px: 1 }}>
|
||||
{action.description}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Standard actions */}
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.75 }}>
|
||||
{onShortlist && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onShortlist} sx={{ justifyContent: 'flex-start' }}>
|
||||
Zur Shortlist hinzufügen
|
||||
{onInquire && (
|
||||
<Button
|
||||
fullWidth
|
||||
size="small"
|
||||
variant="contained"
|
||||
startIcon={<MessageSquare size={14} />}
|
||||
onClick={onInquire}
|
||||
sx={{ justifyContent: 'flex-start', bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' } }}
|
||||
>
|
||||
Anfrage senden
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{engineActions.length > 0 && engineActions.map((action, i) => (
|
||||
<Box key={i}>
|
||||
<Button
|
||||
fullWidth
|
||||
size="small"
|
||||
variant="outlined"
|
||||
sx={{ justifyContent: 'flex-start' }}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
{action.description && (
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.25, px: 1 }}>
|
||||
{action.description}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{onPipeline && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onPipeline} sx={{ justifyContent: 'flex-start' }}>
|
||||
Zur Pipeline hinzufügen
|
||||
</Button>
|
||||
)}
|
||||
{onCompare && (
|
||||
@@ -64,16 +62,6 @@ export function NextActionsPanel({ match, onCompare, onShortlist, onReject, onRe
|
||||
Zum Vergleich hinzufügen
|
||||
</Button>
|
||||
)}
|
||||
{onReview && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onReview} sx={{ justifyContent: 'flex-start', color: '#d97706', borderColor: '#d97706' }}>
|
||||
Zur Überprüfung senden
|
||||
</Button>
|
||||
)}
|
||||
{onReject && (
|
||||
<Button fullWidth size="small" variant="outlined" onClick={onReject} sx={{ justifyContent: 'flex-start', color: '#c0392b', borderColor: '#c0392b' }}>
|
||||
Match ablehnen
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ export function DroppableColumn({
|
||||
selectedId,
|
||||
onSelect,
|
||||
onChatClick,
|
||||
onCardChatClick,
|
||||
isOver,
|
||||
}: {
|
||||
stage: { key: PipelineStage; label: string; color: string; bgColor: string }
|
||||
@@ -18,6 +19,7 @@ export function DroppableColumn({
|
||||
selectedId: string | null
|
||||
onSelect: (item: PipelineItem) => void
|
||||
onChatClick: (inquiryId: string) => void
|
||||
onCardChatClick?: (item: PipelineItem) => void
|
||||
isOver: boolean
|
||||
}) {
|
||||
const { setNodeRef } = useDroppable({ id: stage.key })
|
||||
@@ -43,7 +45,13 @@ export function DroppableColumn({
|
||||
item={item}
|
||||
isSelected={item.id === selectedId}
|
||||
onSelect={onSelect}
|
||||
onChatClick={item.inquiryId ? (e) => { e.stopPropagation(); onChatClick(item.inquiryId!) } : undefined}
|
||||
onChatClick={
|
||||
item.inquiryId
|
||||
? (e) => { e.stopPropagation(); onChatClick(item.inquiryId!) }
|
||||
: onCardChatClick
|
||||
? (e) => { e.stopPropagation(); onCardChatClick(item) }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{items.length === 0 && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
} from '@mui/material'
|
||||
import { Search, Send, Paperclip, ArrowLeft, Bot, Building2, Kanban } from 'lucide-react'
|
||||
import { mockInquiries } from '../../mock-data/inquiries'
|
||||
import { useInquiryStore } from '../../stores/inquiryStore'
|
||||
import { usePipelineItems, useMoveStage } from '../../hooks/usePipeline'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
import type { InquiryMessage } from '../../domain/inquiry'
|
||||
@@ -34,7 +35,10 @@ export default function Anfragen() {
|
||||
|
||||
const preselectedId = searchParams.get('inquiry')
|
||||
|
||||
const storeInquiries = useInquiryStore(s => s.sentInquiries)
|
||||
const [inquiries, setInquiries] = useState(mockInquiries)
|
||||
const allInquiries = [...storeInquiries, ...inquiries]
|
||||
|
||||
const [selectedId, setSelectedId] = useState<string | null>(
|
||||
preselectedId ?? mockInquiries[0]?.id ?? null
|
||||
)
|
||||
@@ -45,7 +49,7 @@ export default function Anfragen() {
|
||||
const [kiAlert, setKiAlert] = useState<{ title: string; stage: string } | null>(null)
|
||||
const threadRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const filtered = inquiries.filter(inq => {
|
||||
const filtered = allInquiries.filter(inq => {
|
||||
const q = search.toLowerCase()
|
||||
const matchesSearch = !q ||
|
||||
inq.tenantName.toLowerCase().includes(q) ||
|
||||
@@ -55,8 +59,8 @@ export default function Anfragen() {
|
||||
return matchesSearch && matchesStatus
|
||||
})
|
||||
|
||||
const selected = inquiries.find(i => i.id === selectedId) ?? null
|
||||
const totalUnread = inquiries.reduce((sum, i) => sum + i.unreadCount, 0)
|
||||
const selected = allInquiries.find(i => i.id === selectedId) ?? null
|
||||
const totalUnread = allInquiries.reduce((sum, i) => sum + i.unreadCount, 0)
|
||||
|
||||
// Pipeline link for currently selected inquiry
|
||||
const linkedPipelineItem = selected?.propertyId
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useState } from 'react'
|
||||
import { InquiryQuickDialog } from '../../components/match-detail/InquiryQuickDialog'
|
||||
import { Box, Button, Chip, CircularProgress, Paper, Typography } from '@mui/material'
|
||||
import { AlertTriangle, ArrowLeft, ChevronDown, ChevronUp } from 'lucide-react'
|
||||
import { useNavigate, useParams } from 'react-router'
|
||||
import { useCompareStore } from '../../stores/compareStore'
|
||||
import { usePipelineStore } from '../../stores/pipelineStore'
|
||||
import { useInquiryStore } from '../../stores/inquiryStore'
|
||||
import { AddToPipelineDialog } from '../../components/shortlist'
|
||||
import { MatchReasonList } from '../../components/match-card/MatchReasonList'
|
||||
import { PropertyMap } from '../../components/shared'
|
||||
@@ -46,6 +48,7 @@ export default function MatchDetail() {
|
||||
const navigate = useNavigate()
|
||||
const { addToCompare } = useCompareStore()
|
||||
const { openSavedDialog } = usePipelineStore()
|
||||
const { openInquiryDialog } = useInquiryStore()
|
||||
const [showFullAnalysis, setShowFullAnalysis] = useState(false)
|
||||
|
||||
const { match, property, need, signal, isLoading, isFuture } = useMatchDetailData(matchId ?? '')
|
||||
@@ -126,6 +129,7 @@ export default function MatchDetail() {
|
||||
return (
|
||||
<Box sx={{ bgcolor: DS_BG.page, minHeight: '100vh' }}>
|
||||
<AddToPipelineDialog />
|
||||
<InquiryQuickDialog />
|
||||
|
||||
{/* Sticky back nav */}
|
||||
<Box sx={{
|
||||
@@ -164,7 +168,7 @@ export default function MatchDetail() {
|
||||
/>
|
||||
|
||||
{/* ── Main content ── */}
|
||||
<Box sx={{ maxWidth: 780, mx: 'auto', px: { xs: 1.5, sm: 3 }, py: { xs: 2, sm: 3 }, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Box sx={{ maxWidth: { sm: 780, md: 960, lg: 1100 }, mx: 'auto', px: { xs: 1.5, sm: 3 }, py: { xs: 2, sm: 3 }, display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
|
||||
{/* ── Section A: Warum dieser Match? ── */}
|
||||
<Paper sx={{ p: 2.5 }}>
|
||||
@@ -196,9 +200,14 @@ export default function MatchDetail() {
|
||||
<NextActionsPanel
|
||||
match={match}
|
||||
onCompare={handleCompare}
|
||||
onShortlist={handleShortlist}
|
||||
onReview={() => {}}
|
||||
onReject={() => {}}
|
||||
onPipeline={handleShortlist}
|
||||
onInquire={() => openInquiryDialog({
|
||||
propertyTitle: title,
|
||||
location,
|
||||
areaLabel: property?.areaSqm ? `${property.areaSqm.toLocaleString('de-CH')} m²` : undefined,
|
||||
rentLabel: property?.rentPricePerSqm ? `CHF ${property.rentPricePerSqm}/m²/Jahr` : undefined,
|
||||
matchScore: match.matchScore,
|
||||
})}
|
||||
/>
|
||||
|
||||
{/* ── Full analysis toggle ── */}
|
||||
|
||||
@@ -9,7 +9,9 @@ import {
|
||||
import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core'
|
||||
import { TrendingUp } from 'lucide-react'
|
||||
import { usePipelineItems, useMoveStage } from '../../hooks/usePipeline'
|
||||
import { useInquiryStore } from '../../stores/inquiryStore'
|
||||
import { AddToPipelineDialog } from '../../components/shortlist'
|
||||
import { InquiryQuickDialog } from '../../components/match-detail/InquiryQuickDialog'
|
||||
import type { PipelineItem, PipelineStage } from '../../domain/pipeline'
|
||||
import { STAGES } from '../../components/pipeline/pipelineConstants'
|
||||
import { DraggableCard } from '../../components/pipeline/PipelineCard'
|
||||
@@ -22,6 +24,7 @@ export default function Pipeline() {
|
||||
const navigate = useNavigate()
|
||||
const { data: items = [] } = usePipelineItems()
|
||||
const { mutate: moveStage } = useMoveStage()
|
||||
const openInquiryDialog = useInquiryStore(s => s.openInquiryDialog)
|
||||
const [selectedItem, setSelectedItem] = useState<PipelineItem | null>(null)
|
||||
const [activeId, setActiveId] = useState<string | null>(null)
|
||||
const [overId, setOverId] = useState<string | null>(null)
|
||||
@@ -66,6 +69,7 @@ export default function Pipeline() {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
<AddToPipelineDialog />
|
||||
<InquiryQuickDialog />
|
||||
|
||||
{/* Header */}
|
||||
<Box sx={{
|
||||
@@ -123,6 +127,12 @@ export default function Pipeline() {
|
||||
selectedId={syncedSelected?.id ?? null}
|
||||
onSelect={handleSelect}
|
||||
onChatClick={(inquiryId) => navigate(`/demand/anfragen?inquiry=${inquiryId}`)}
|
||||
onCardChatClick={(item) => openInquiryDialog({
|
||||
propertyTitle: item.title,
|
||||
location: item.location ?? '',
|
||||
matchScore: item.matchScore ?? 0,
|
||||
propertyId: item.propertyId,
|
||||
})}
|
||||
isOver={isOver}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
UnifiedResultFeed,
|
||||
} from '../../components/results'
|
||||
import { AddToPipelineDialog } from '../../components/shortlist'
|
||||
import { InquiryQuickDialog } from '../../components/match-detail/InquiryQuickDialog'
|
||||
import { ErrorState } from '../../components/ui'
|
||||
import { useSessionStore } from '../../stores/sessionStore'
|
||||
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
||||
@@ -101,6 +102,7 @@ export default function Results() {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
|
||||
<AddToPipelineDialog />
|
||||
<InquiryQuickDialog />
|
||||
<ResultFeedHeader
|
||||
total={sorted.length}
|
||||
platformCount={platformCount}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { create } from 'zustand'
|
||||
import type { Inquiry, InquiryMessage } from '../domain/inquiry'
|
||||
|
||||
export interface PendingInquiry {
|
||||
propertyTitle: string
|
||||
location: string
|
||||
areaLabel?: string
|
||||
rentLabel?: string
|
||||
matchScore: number
|
||||
matchId?: string
|
||||
propertyId?: string
|
||||
}
|
||||
|
||||
interface InquiryStore {
|
||||
// Dialog state
|
||||
dialogOpen: boolean
|
||||
pendingInquiry: PendingInquiry | null
|
||||
openInquiryDialog: (item: PendingInquiry) => void
|
||||
closeInquiryDialog: () => void
|
||||
|
||||
// Sent inquiries (in-memory, persists for the session)
|
||||
sentInquiries: Inquiry[]
|
||||
addInquiry: (item: PendingInquiry, message: string) => void
|
||||
}
|
||||
|
||||
export const useInquiryStore = create<InquiryStore>((set) => ({
|
||||
dialogOpen: false,
|
||||
pendingInquiry: null,
|
||||
openInquiryDialog: (item) => set({ dialogOpen: true, pendingInquiry: item }),
|
||||
closeInquiryDialog: () => set({ dialogOpen: false, pendingInquiry: null }),
|
||||
|
||||
sentInquiries: [],
|
||||
addInquiry: (item, message) => {
|
||||
const now = new Date().toISOString()
|
||||
const msgId = `msg-sent-${Date.now()}`
|
||||
const id = `sent-${Date.now()}`
|
||||
|
||||
const thread: InquiryMessage = {
|
||||
id: msgId,
|
||||
inquiryId: id,
|
||||
senderType: 'tenant',
|
||||
senderName: 'Admin User',
|
||||
body: message,
|
||||
attachments: [],
|
||||
createdAt: now,
|
||||
}
|
||||
|
||||
const inquiry: Inquiry = {
|
||||
id,
|
||||
organizationId: 'org-wincasa',
|
||||
propertyId: item.propertyId ?? 'unknown',
|
||||
tenantName: 'Admin User',
|
||||
tenantCompany: 'Wincasa AG',
|
||||
subject: 'Anfrage: ' + item.propertyTitle,
|
||||
message,
|
||||
status: 'new',
|
||||
unreadCount: 0,
|
||||
isRead: true,
|
||||
matchScore: item.matchScore,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
thread: [thread],
|
||||
}
|
||||
|
||||
set(state => ({ sentInquiries: [inquiry, ...state.sentInquiries] }))
|
||||
},
|
||||
}))
|
||||
Reference in New Issue
Block a user