import { useState, useEffect, useRef } from 'react' import { useNavigate, useSearchParams } from 'react-router' import { Box, Typography, TextField, Chip, Avatar, IconButton, InputAdornment, Alert, } from '@mui/material' import { Search, Send, Paperclip, ArrowLeft, Bot, Building2, Kanban } from 'lucide-react' import { mockDemandInquiries } from '../../mock-data/demandInquiries' import { useInquiryStore } from '../../stores/inquiryStore' import { usePipelineItems, useMoveStage } from '../../hooks/usePipeline' import type { InquiryMessage } from '../../domain/inquiry' import { STAGE_ORDER, STAGE_LABELS, detectKiStage } from './anfragenKiDetection' import { AnfragenMessageBubble } from '../../components/demand/AnfragenMessageBubble' import { AnfragenInquiryItem } from '../../components/demand/AnfragenInquiryItem' import { INQUIRY_STATUS_META, DS_COLORS, DS_TEXT, DS_BG, DS_BORDER, DS_SURFACE } from '../../lib/ds' import { useSessionStore } from '../../stores/sessionStore' // ── Config ──────────────────────────────────────────────────────────────────── const FILTER_TABS = [ { key: 'all', label: 'Alle' }, { key: 'new', label: 'Neu' }, { key: 'in_progress', label: 'Aktiv' }, { key: 'answered', label: 'Beantwortet' }, ] // ── Anfragen page ───────────────────────────────────────────────────────────── export default function Anfragen() { const navigate = useNavigate() const [searchParams] = useSearchParams() const { data: pipelineItems = [] } = usePipelineItems() const { mutate: moveStage } = useMoveStage() const preselectedId = searchParams.get('inquiry') const { currentUser } = useSessionStore() const storeInquiries = useInquiryStore(s => s.sentInquiries) const [inquiries, setInquiries] = useState(mockDemandInquiries) const allInquiries = [...storeInquiries, ...inquiries] const [selectedId, setSelectedId] = useState( preselectedId ?? mockDemandInquiries[0]?.id ?? null ) const [search, setSearch] = useState('') const [statusFilter, setStatusFilter] = useState('all') const [replyText, setReplyText] = useState('') const [mobileShowChat, setMobileShowChat] = useState(!!preselectedId) const [kiAlert, setKiAlert] = useState<{ title: string; stage: string } | null>(null) const threadRef = useRef(null) const filtered = allInquiries.filter(inq => { const q = search.toLowerCase() const matchesSearch = !q || inq.tenantName.toLowerCase().includes(q) || inq.subject.toLowerCase().includes(q) || (inq.tenantCompany?.toLowerCase().includes(q) ?? false) || (inq.propertyAddress?.toLowerCase().includes(q) ?? false) || (inq.propertyManagerName?.toLowerCase().includes(q) ?? false) || (inq.propertyManagerCompany?.toLowerCase().includes(q) ?? false) const matchesStatus = statusFilter === 'all' || inq.status === statusFilter return matchesSearch && matchesStatus }) 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 ? pipelineItems.find(i => i.propertyId === selected.propertyId) : (selected ? pipelineItems.find(i => i.inquiryId === selected.id) : undefined) useEffect(() => { if (threadRef.current) { threadRef.current.scrollTop = threadRef.current.scrollHeight } }, [selected?.thread.length]) // Auto-dismiss KI alert after 6s useEffect(() => { if (!kiAlert) return const t = setTimeout(() => setKiAlert(null), 6000) return () => clearTimeout(t) }, [kiAlert]) function handleSelect(id: string) { setSelectedId(id) setInquiries(prev => prev.map(i => i.id === id ? { ...i, isRead: true, unreadCount: 0 } : i)) setMobileShowChat(true) setKiAlert(null) } function handleSend() { if (!replyText.trim() || !selectedId) return const text = replyText.trim() const msg: InquiryMessage = { id: `msg-${Date.now()}`, inquiryId: selectedId, senderType: 'tenant', senderName: 'Sie', body: text, attachments: [], createdAt: new Date().toISOString(), } setInquiries(prev => prev.map(i => i.id === selectedId ? { ...i, thread: [...i.thread, msg], status: 'in_progress', updatedAt: new Date().toISOString() } : i )) setReplyText('') // KI: detect stage transition from message content const ki = detectKiStage(text) if (ki && selected) { const pipelineItem = selected.propertyId ? pipelineItems.find(i => i.propertyId === selected.propertyId) : pipelineItems.find(i => i.inquiryId === selectedId) if (pipelineItem) { const currentIdx = STAGE_ORDER.indexOf(pipelineItem.stage) const targetIdx = STAGE_ORDER.indexOf(ki.stage) if (targetIdx > currentIdx) { moveStage({ id: pipelineItem.id, stage: ki.stage }) setKiAlert({ title: pipelineItem.title, stage: STAGE_LABELS[ki.stage] }) } } } } return ( {/* ── Left panel ── */} Anfragen {totalUnread > 0 && ( )} setSearch(e.target.value)} slotProps={{ input: { startAdornment: } }} sx={{ mb: 1.25 }} /> {FILTER_TABS.map(tab => ( setStatusFilter(tab.key)} sx={{ height: 22, fontSize: '0.7rem', cursor: 'pointer', bgcolor: statusFilter === tab.key ? 'primary.main' : DS_BG.subtle, color: statusFilter === tab.key ? 'white' : DS_TEXT.secondary, fontWeight: statusFilter === tab.key ? 700 : 400, '&:hover': { bgcolor: statusFilter === tab.key ? 'primary.dark' : DS_BG.muted }, }} /> ))} {filtered.length === 0 ? ( Keine Anfragen gefunden. ) : filtered.map(inq => ( i.propertyId === inq.propertyId) : pipelineItems.find(i => i.inquiryId === inq.id))} perspective="demand" onSelect={handleSelect} /> ))} {/* ── Right panel: chat ── */} {!selected ? ( Anfrage auswählen Wählen Sie links eine Anfrage aus. ) : ( <> {/* Chat header */} setMobileShowChat(false)}> {(selected.propertyAddress ?? selected.subject).split(' ').map((n: string) => n[0]).join('').toUpperCase().slice(0, 2)} {selected.propertyAddress ?? selected.subject} {(selected.propertyManagerCompany ?? selected.propertyManagerName) && ( {selected.propertyManagerCompany ?? selected.propertyManagerName ?? ''} )} {selected.subject} {selected.matchScore && ( = 80 ? DS_SURFACE.success.bg : DS_SURFACE.warning.bg, color: selected.matchScore >= 80 ? DS_TEXT.success : DS_TEXT.warning, fontWeight: 700, height: 22, fontSize: '0.75rem', border: `1px solid ${selected.matchScore >= 80 ? DS_SURFACE.success.border : DS_SURFACE.warning.border}`, }} /> )} {/* Pipeline link */} {linkedPipelineItem && ( } label={STAGE_LABELS[linkedPipelineItem.stage] ?? linkedPipelineItem.stage} size="small" onClick={() => navigate('/demand/pipeline')} sx={{ height: 22, fontSize: '0.75rem', cursor: 'pointer', bgcolor: DS_COLORS.futureCard.signal.headerBg, fontWeight: 600, color: 'primary.main', border: `1px solid ${DS_COLORS.futureCard.signal.border}`, '& .MuiChip-icon': { color: 'primary.main' }, '&:hover': { bgcolor: DS_COLORS.futureCard.signal.badgeBg }, }} /> )} {/* Property reference */} {(linkedPipelineItem?.propertyAddress ?? selected.subject) && ( {linkedPipelineItem?.propertyAddress ?? selected.subject} )} {/* KI stage-change alert */} {kiAlert && ( } onClose={() => setKiAlert(null)} sx={{ py: 0.5, bgcolor: DS_COLORS.futureCard.controlled.headerBg, color: DS_COLORS.futureCard.controlled.badgeText, border: `1px solid ${DS_COLORS.futureCard.controlled.border}`, '& .MuiAlert-icon': { color: DS_COLORS.futureCard.controlled.accent } }} > KI erkannt: „{kiAlert.title}" wurde in der Pipeline auf {kiAlert.stage} verschoben.{' '} navigate('/demand/pipeline')}> Pipeline öffnen )} {/* Thread */} {selected.thread.map(msg => ( ))} {/* Composer */} setReplyText(e.target.value)} onKeyDown={e => { if (e.key === 'Enter' && e.ctrlKey) handleSend() }} sx={{ '& .MuiOutlinedInput-root': { borderRadius: 2 } }} /> Ctrl + Enter · KI erkennt Terminvereinbarungen automatisch )} ) }