feat: F030 Anfragencenter — Aktive & Latente Anfragen + Angebot-Wizard
Neuer Menüpunkt «Anfragencenter» (ehemals «Eingehende Bedarfe»): Aktive Anfragen: - Split-View: Anfrageliste (Liste/Grid-Toggle) + Chat-Detailansicht - Chat-Verlauf mit Sender-Styling (Mieter links, Supply rechts) - Antwortformular mit Betreff, Nachricht, Anhänge, Statuswechsel - Zugehörige Property Card neben dem Chat Latente Anfragen: - Drei-Spalten-Layout: Need-Liste | Need-Detail | Eigene Objekte - Need Cards mit AI-Zusammenfassung, Must-Haves, Präferenzen - Eigene Portfolio-Objekte sortiert nach deterministischem Match-Score - Checkbox-Selektion für Angebotsauswahl Angebot-Wizard (4 Schritte): - Schritt 1: Objekte auswählen (mit Score-Vorschau) - Schritt 2: PDF-Vorschau + editierbare Textfelder - Schritt 3: Angebot prüfen & bestätigen - Schritt 4: Nachricht an Suchenden mit KI-generierter Mail Architektur: - Domain: Inquiry, LatentNeed, OfferDraft Types - Provider: IInquiryProvider, ILatentNeedProvider, IOfferProvider + Mockups - Services: inquiryService, latentNeedService, offerService - Hooks: useInquiries, useLatentNeeds, useOffers (TanStack Query) - Store: offerWizardStore (Zustand, lokaler Wizard-State) - 27 neue Komponenten, alle Loading/Empty/Error States Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { Box, Typography } from '@mui/material'
|
||||
import { Paperclip } from 'lucide-react'
|
||||
import type { InquiryMessage } from '../../domain/inquiry'
|
||||
import { formatInquiryDate, formatFileSize } from './inquiryUtils'
|
||||
|
||||
interface InquiryMessageBubbleProps {
|
||||
message: InquiryMessage
|
||||
}
|
||||
|
||||
export function InquiryMessageBubble({ message }: InquiryMessageBubbleProps) {
|
||||
const isTenant = message.senderType === 'tenant'
|
||||
const isSupply = message.senderType === 'supply_user'
|
||||
const isSystemOrAi = message.senderType === 'system' || message.senderType === 'ai'
|
||||
|
||||
if (isSystemOrAi) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', my: 1 }}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
color: '#64748b',
|
||||
fontStyle: 'italic',
|
||||
bgcolor: '#f1f5f9',
|
||||
px: 2,
|
||||
py: 0.5,
|
||||
borderRadius: 4,
|
||||
fontSize: '0.75rem',
|
||||
}}
|
||||
>
|
||||
{message.senderName}: {message.body}
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: isTenant ? 'flex-start' : 'flex-end', mb: 1.5 }}>
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: '75%',
|
||||
bgcolor: isTenant ? '#f1f5f9' : '#1e3a5f',
|
||||
color: isTenant ? '#0f172a' : 'white',
|
||||
px: 1.75,
|
||||
py: 1.25,
|
||||
borderRadius: 2,
|
||||
boxShadow: '0 1px 2px rgba(15,23,42,0.06)',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 1.5, mb: 0.5 }}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
fontSize: '0.75rem',
|
||||
color: isTenant ? '#0f172a' : 'white',
|
||||
}}
|
||||
>
|
||||
{message.senderName}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
fontSize: '0.7rem',
|
||||
color: isTenant ? '#64748b' : 'rgba(255,255,255,0.7)',
|
||||
}}
|
||||
>
|
||||
{formatInquiryDate(message.createdAt)}
|
||||
</Typography>
|
||||
</Box>
|
||||
{message.subject && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
display: 'block',
|
||||
fontWeight: 600,
|
||||
fontSize: '0.75rem',
|
||||
color: isTenant ? '#334155' : 'rgba(255,255,255,0.85)',
|
||||
mb: 0.5,
|
||||
}}
|
||||
>
|
||||
{message.subject}
|
||||
</Typography>
|
||||
)}
|
||||
<Typography
|
||||
variant="body2"
|
||||
sx={{
|
||||
whiteSpace: 'pre-wrap',
|
||||
fontSize: '0.85rem',
|
||||
lineHeight: 1.5,
|
||||
color: isTenant ? '#0f172a' : 'white',
|
||||
}}
|
||||
>
|
||||
{message.body}
|
||||
</Typography>
|
||||
|
||||
{message.attachments.length > 0 && (
|
||||
<Box sx={{ mt: 1, display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
{message.attachments.map(att => (
|
||||
<Box
|
||||
key={att.id}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0.75,
|
||||
bgcolor: isTenant ? 'white' : 'rgba(255,255,255,0.12)',
|
||||
border: '1px solid',
|
||||
borderColor: isTenant ? '#cbd5e1' : 'rgba(255,255,255,0.25)',
|
||||
px: 1,
|
||||
py: 0.5,
|
||||
borderRadius: 1,
|
||||
fontSize: '0.75rem',
|
||||
}}
|
||||
>
|
||||
<Paperclip size={12} />
|
||||
<Typography variant="caption" sx={{ fontSize: '0.75rem', fontWeight: 500 }}>
|
||||
{att.fileName}
|
||||
</Typography>
|
||||
{att.fileSize && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
fontSize: '0.7rem',
|
||||
color: isTenant ? '#64748b' : 'rgba(255,255,255,0.65)',
|
||||
ml: 'auto',
|
||||
}}
|
||||
>
|
||||
{formatFileSize(att.fileSize)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isSupply && (
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
display: 'block',
|
||||
fontSize: '0.65rem',
|
||||
color: 'rgba(255,255,255,0.6)',
|
||||
mt: 0.75,
|
||||
textAlign: 'right',
|
||||
}}
|
||||
>
|
||||
Wincasa AG
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user