feat: merge Merkliste into Pipeline (SAVED stage) + Anfragen chat page
- Remove standalone Merkliste/Shortlists nav item; pipeline now covers the full funnel - Add SAVED as first PipelineStage — 'Merken' on result cards lands here - pipelineStore (Zustand) holds shared items; AddToPipelineDialog replaces AddToShortlistDialog in demand workspace - New /demand/anfragen split-panel: searchable inquiry list + chat thread with compose - Pipeline detail panel: KI insight, stage actions, editable notes, mock documents - ShortlistItemCard: cards clickable → property detail, Anfrage button with inline dialog - Supply workspace (FutureAvailability, Anfragencenter) unchanged Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -103,7 +103,7 @@ const WORKSPACE_CONFIG: Record<WorkspaceType, WorkspaceConfig> = {
|
||||
{ path: '/demand/ai-search', label: 'Flächensuche', icon: Search },
|
||||
{ path: '/demand/results', label: 'Ergebnisse', icon: List },
|
||||
{ path: '/demand/compare', label: 'Vergleich', icon: Columns2 },
|
||||
{ path: '/demand/shortlists', label: 'Shortlists', icon: Bookmark },
|
||||
{ path: '/demand/anfragen', label: 'Anfragen', icon: MessageSquare },
|
||||
{ path: '/demand/pipeline', label: 'Deal Pipeline', icon: Kanban },
|
||||
],
|
||||
},
|
||||
@@ -134,6 +134,7 @@ function getPageNameFromPath(pathname: string): string {
|
||||
if (/^\/demand\/results\/.+/.test(pathname)) return 'Match Detail'
|
||||
if (/^\/demand\/property\//.test(pathname)) return 'Objekt Detail'
|
||||
if (/^\/supply\/properties\/.+/.test(pathname)) return 'Objekt Detail'
|
||||
if (pathname === '/demand/anfragen') return 'Anfragen'
|
||||
const segment = pathname.split('/').filter(Boolean).pop() ?? ''
|
||||
return segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ')
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MatchCardCompact } from '../match-card/MatchCardCompact'
|
||||
import { IntelligenceMatchCard } from '../match-card/IntelligenceMatchCard'
|
||||
import { buildMatchCardViewModel } from '../../features/matching/matchCardAdapter'
|
||||
import { useCompareStore } from '../../stores/compareStore'
|
||||
import { useShortlistStore } from '../../stores/shortlistStore'
|
||||
import { usePipelineStore } from '../../stores/pipelineStore'
|
||||
import type { UnifiedMatchResult } from '../../domain/unifiedResult'
|
||||
import type { MatchCardAction } from '../match-card/MatchCardViewModel'
|
||||
|
||||
@@ -22,7 +22,7 @@ function getResultTitle(result: UnifiedMatchResult): string {
|
||||
export function UnifiedResultCard({ result, view = 'list' }: Props) {
|
||||
const navigate = useNavigate()
|
||||
const { addToCompare, removeFromCompare, isInCompare, isFull } = useCompareStore()
|
||||
const { openAddDialog } = useShortlistStore()
|
||||
const { openSavedDialog } = usePipelineStore()
|
||||
const inCompare = isInCompare(result.matchId)
|
||||
|
||||
const isPortfolio = result.resultType === 'VERIFIED_PORTFOLIO'
|
||||
@@ -30,17 +30,15 @@ export function UnifiedResultCard({ result, view = 'list' }: Props) {
|
||||
const actions: MatchCardAction[] = [
|
||||
{
|
||||
id: 'shortlist',
|
||||
label: 'Shortlist',
|
||||
label: 'Merken',
|
||||
actionType: 'SAVE_SHORTLIST',
|
||||
variant: 'secondary',
|
||||
onClick: () => openAddDialog({
|
||||
onClick: () => openSavedDialog({
|
||||
resultId: result.matchId,
|
||||
resultType: result.resultType,
|
||||
title: getResultTitle(result),
|
||||
matchScore: result.matchScore,
|
||||
confidenceScore: result.match.confidenceLevel,
|
||||
sourceLabel: result.resultType,
|
||||
addedBy: 'admin@ideal-sharing.ch',
|
||||
location: result.resultType !== 'FUTURE_AVAILABILITY' ? result.property.location?.city : undefined,
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
Box, Button, CircularProgress, Dialog, DialogActions,
|
||||
DialogContent, DialogTitle, TextField, Typography,
|
||||
} from '@mui/material'
|
||||
import { Bookmark } from 'lucide-react'
|
||||
import { usePipelineStore } from '../../stores/pipelineStore'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
|
||||
export function AddToPipelineDialog() {
|
||||
const { dialogOpen, pendingItem, closeSavedDialog, confirmSaved } = usePipelineStore()
|
||||
const showToast = useToastStore((s) => s.showToast)
|
||||
const [note, setNote] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
function handleClose() {
|
||||
closeSavedDialog()
|
||||
setNote('')
|
||||
}
|
||||
|
||||
async function handleConfirm() {
|
||||
setSaving(true)
|
||||
const result = confirmSaved(note.trim() || undefined)
|
||||
setSaving(false)
|
||||
setNote('')
|
||||
if (result === 'duplicate') {
|
||||
showToast('Bereits in der Pipeline vorhanden.', 'warning')
|
||||
} else {
|
||||
showToast('Zur Pipeline (Gemerkt) hinzugefügt.')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={dialogOpen} onClose={handleClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700, pb: 1, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Bookmark size={16} />
|
||||
Merken
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ pt: 0 }}>
|
||||
{pendingItem && (
|
||||
<Box sx={{ bgcolor: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: 1.5, p: 1.5, mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{pendingItem.title}</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
Match {pendingItem.matchScore}% · {pendingItem.resultType}
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 1.5 }}>
|
||||
Das Objekt wird in Ihrer Pipeline unter <strong>Gemerkt</strong> gespeichert. Von dort können Sie es qualifizieren und weiterverfolgen.
|
||||
</Typography>
|
||||
<TextField
|
||||
size="small"
|
||||
fullWidth
|
||||
multiline
|
||||
rows={2}
|
||||
label="Notiz (optional)"
|
||||
value={note}
|
||||
onChange={e => setNote(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button onClick={handleClose} disabled={saving}>Abbrechen</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleConfirm}
|
||||
disabled={saving}
|
||||
endIcon={saving ? <CircularProgress size={14} color="inherit" /> : undefined}
|
||||
sx={{ bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#162d4a' } }}
|
||||
>
|
||||
Merken
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { Alert, Box, Button, Chip, TextField, Typography } from '@mui/material'
|
||||
import { Alert, Box, Button, Chip, IconButton, TextField, Tooltip, Typography } from '@mui/material'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { CheckCircle, Columns2, Pencil } from 'lucide-react'
|
||||
import { CheckCircle, Columns2, LayoutGrid, List, Pencil } from 'lucide-react'
|
||||
import { ShortlistStatusBadge } from './ShortlistStatusBadge'
|
||||
import { ShortlistItemCard } from './ShortlistItemCard'
|
||||
import { ShortlistEmptyState } from './ShortlistEmptyState'
|
||||
@@ -23,6 +23,9 @@ export function ShortlistDetail({ shortlist }: Props) {
|
||||
|
||||
const [editingTitle, setEditingTitle] = useState(false)
|
||||
const [titleDraft, setTitleDraft] = useState(shortlist.title)
|
||||
const [view, setView] = useState<'list' | 'grid'>(() =>
|
||||
(localStorage.getItem('view-shortlist') as 'list' | 'grid') ?? 'list'
|
||||
)
|
||||
|
||||
const isFinalized = shortlist.status === ShortlistStatus.FINALIZED
|
||||
const isDraft = shortlist.status === ShortlistStatus.DRAFT
|
||||
@@ -137,7 +140,7 @@ export function ShortlistDetail({ shortlist }: Props) {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', alignItems: 'center' }}>
|
||||
{isDraft && (
|
||||
<Button
|
||||
size="small"
|
||||
@@ -173,21 +176,56 @@ export function ShortlistDetail({ shortlist }: Props) {
|
||||
{shortlist.description && (
|
||||
<Chip label={shortlist.description} size="small" variant="outlined" sx={{ fontSize: 11 }} />
|
||||
)}
|
||||
<Box sx={{ ml: 'auto', display: 'flex', gap: 0.5 }}>
|
||||
<Tooltip title="Listenansicht">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => { setView('list'); localStorage.setItem('view-shortlist', 'list') }}
|
||||
sx={{ color: view === 'list' ? '#1e3a5f' : '#94a3b8', bgcolor: view === 'list' ? '#eff6ff' : 'transparent' }}
|
||||
>
|
||||
<List size={16} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Kartenansicht">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => { setView('grid'); localStorage.setItem('view-shortlist', 'grid') }}
|
||||
sx={{ color: view === 'grid' ? '#1e3a5f' : '#94a3b8', bgcolor: view === 'grid' ? '#eff6ff' : 'transparent' }}
|
||||
>
|
||||
<LayoutGrid size={16} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Items */}
|
||||
<Box sx={{ flex: 1, overflowY: 'auto' }}>
|
||||
<Box sx={{ flex: 1, overflowY: 'auto', p: view === 'grid' ? 2 : 0 }}>
|
||||
{shortlist.items.length === 0
|
||||
? <ShortlistEmptyState context="empty-shortlist" />
|
||||
: shortlist.items.map(item => (
|
||||
<ShortlistItemCard
|
||||
key={item.resultId}
|
||||
item={item}
|
||||
shortlistId={shortlist.id}
|
||||
isFinalized={isFinalized}
|
||||
/>
|
||||
))
|
||||
: view === 'grid'
|
||||
? (
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))', gap: 2 }}>
|
||||
{shortlist.items.map(item => (
|
||||
<ShortlistItemCard
|
||||
key={item.resultId}
|
||||
item={item}
|
||||
shortlistId={shortlist.id}
|
||||
isFinalized={isFinalized}
|
||||
view="grid"
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
: shortlist.items.map(item => (
|
||||
<ShortlistItemCard
|
||||
key={item.resultId}
|
||||
item={item}
|
||||
shortlistId={shortlist.id}
|
||||
isFinalized={isFinalized}
|
||||
view="list"
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { Box, Chip, IconButton, Typography } from '@mui/material'
|
||||
import { X } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import {
|
||||
Box, Card, Chip, IconButton, Typography,
|
||||
Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField,
|
||||
} from '@mui/material'
|
||||
import { X, MessageSquare, ExternalLink } from 'lucide-react'
|
||||
import { useRemoveFromShortlist } from '../../hooks/useShortlists'
|
||||
import { useToastStore } from '../../stores/toastStore'
|
||||
import type { ShortlistItem } from '../../domain/shortlist'
|
||||
@@ -17,62 +22,213 @@ interface Props {
|
||||
item: ShortlistItem
|
||||
shortlistId: string
|
||||
isFinalized: boolean
|
||||
view?: 'list' | 'grid'
|
||||
}
|
||||
|
||||
export function ShortlistItemCard({ item, shortlistId, isFinalized }: Props) {
|
||||
export function ShortlistItemCard({ item, shortlistId, isFinalized, view = 'list' }: Props) {
|
||||
const navigate = useNavigate()
|
||||
const removeItem = useRemoveFromShortlist()
|
||||
const showToast = useToastStore((s) => s.showToast)
|
||||
const [inquiryOpen, setInquiryOpen] = useState(false)
|
||||
const [inquiryMsg, setInquiryMsg] = useState('')
|
||||
|
||||
const addedDate = new Date(item.addedAt).toLocaleDateString('de-CH', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
p: 2,
|
||||
borderBottom: '1px solid #f1f5f9',
|
||||
opacity: isFinalized ? 0.75 : 1,
|
||||
display: 'flex',
|
||||
gap: 1.5,
|
||||
alignItems: 'flex-start',
|
||||
}}>
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5, flexWrap: 'wrap' }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>{item.title}</Typography>
|
||||
<Chip
|
||||
label={RESULT_TYPE_LABEL[item.resultType] ?? item.resultType}
|
||||
size="small"
|
||||
sx={{ fontSize: 10, height: 18 }}
|
||||
/>
|
||||
<Chip
|
||||
label={`${item.matchScore}`}
|
||||
size="small"
|
||||
sx={{ bgcolor: SCORE_COLOR(item.matchScore), color: 'white', fontSize: 10, height: 18, fontWeight: 700 }}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
|
||||
{item.sourceLabel ?? item.resultType} · {item.addedBy} · {addedDate}
|
||||
const detailPath = item.propertyId
|
||||
? `/demand/property/${item.propertyId}`
|
||||
: `/demand/results/${item.resultId}`
|
||||
|
||||
function handleCardClick() {
|
||||
navigate(detailPath)
|
||||
}
|
||||
|
||||
function handleSendInquiry() {
|
||||
if (!inquiryMsg.trim()) return
|
||||
showToast('Anfrage gesendet. Sie finden sie unter Anfragen.')
|
||||
setInquiryOpen(false)
|
||||
setInquiryMsg('')
|
||||
navigate('/demand/anfragen')
|
||||
}
|
||||
|
||||
const removeBtn = !isFinalized && (
|
||||
<IconButton
|
||||
size="small"
|
||||
disabled={removeItem.isPending}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
removeItem.mutate(
|
||||
{ shortlistId, resultId: item.resultId },
|
||||
{
|
||||
onSuccess: () => showToast('Objekt aus Shortlist entfernt.'),
|
||||
onError: () => showToast('Entfernen fehlgeschlagen.', 'error'),
|
||||
}
|
||||
)
|
||||
}}
|
||||
sx={{ color: '#94a3b8', '&:hover': { color: '#c0392b' }, flexShrink: 0 }}
|
||||
>
|
||||
<X size={14} />
|
||||
</IconButton>
|
||||
)
|
||||
|
||||
const inquiryDialog = (
|
||||
<Dialog open={inquiryOpen} onClose={() => setInquiryOpen(false)} maxWidth="sm" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700, fontSize: '1rem', pb: 1 }}>
|
||||
Anfrage stellen
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ pt: 0 }}>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
<strong>{item.title}</strong>
|
||||
</Typography>
|
||||
{item.note && (
|
||||
<Typography variant="caption" sx={{ display: 'block', mt: 0.5, color: '#475569', fontStyle: 'italic' }}>
|
||||
{item.note}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
{!isFinalized && (
|
||||
<IconButton
|
||||
<TextField
|
||||
label="Betreff"
|
||||
size="small"
|
||||
disabled={removeItem.isPending}
|
||||
onClick={() => removeItem.mutate(
|
||||
{ shortlistId, resultId: item.resultId },
|
||||
{
|
||||
onSuccess: () => showToast('Objekt aus Shortlist entfernt.'),
|
||||
onError: () => showToast('Entfernen fehlgeschlagen.', 'error'),
|
||||
}
|
||||
)}
|
||||
sx={{ color: '#94a3b8', '&:hover': { color: '#c0392b' }, flexShrink: 0 }}
|
||||
fullWidth
|
||||
defaultValue={`Anfrage zu ${item.title}`}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
label="Nachricht"
|
||||
multiline
|
||||
minRows={4}
|
||||
fullWidth
|
||||
size="small"
|
||||
placeholder="Beschreiben Sie Ihr Interesse und Ihre Anforderungen…"
|
||||
value={inquiryMsg}
|
||||
onChange={e => setInquiryMsg(e.target.value)}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
<Button size="small" onClick={() => setInquiryOpen(false)}>Abbrechen</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
onClick={handleSendInquiry}
|
||||
disabled={!inquiryMsg.trim()}
|
||||
sx={{ bgcolor: '#1e3a5f', '&:hover': { bgcolor: '#1a3050' } }}
|
||||
>
|
||||
<X size={14} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
Anfrage senden
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
if (view === 'grid') {
|
||||
return (
|
||||
<>
|
||||
<Card
|
||||
onClick={handleCardClick}
|
||||
sx={{
|
||||
p: 2, opacity: isFinalized ? 0.75 : 1,
|
||||
display: 'flex', flexDirection: 'column', gap: 1, height: '100%',
|
||||
cursor: 'pointer',
|
||||
'&:hover': { boxShadow: '0 4px 12px rgba(0,0,0,0.12)', borderColor: '#bfdbfe' },
|
||||
transition: 'box-shadow 0.15s ease, border-color 0.15s ease',
|
||||
border: '1px solid #e2e8f0',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
|
||||
<Box sx={{
|
||||
width: 44, height: 44, borderRadius: '50%', flexShrink: 0,
|
||||
bgcolor: SCORE_COLOR(item.matchScore),
|
||||
display: 'flex', flexDirection: 'column',
|
||||
alignItems: 'center', justifyContent: 'center',
|
||||
}}>
|
||||
<Typography sx={{ fontWeight: 800, fontSize: '0.95rem', color: 'white', lineHeight: 1 }}>
|
||||
{item.matchScore}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', gap: 0.25 }}>
|
||||
{!isFinalized && item.resultType !== 'FUTURE_AVAILABILITY' && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={e => { e.stopPropagation(); setInquiryOpen(true) }}
|
||||
sx={{ color: '#94a3b8', '&:hover': { color: '#1e3a5f' } }}
|
||||
>
|
||||
<MessageSquare size={14} />
|
||||
</IconButton>
|
||||
)}
|
||||
{removeBtn}
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ fontWeight: 700, lineHeight: 1.3 }}>{item.title}</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||
<Chip
|
||||
label={RESULT_TYPE_LABEL[item.resultType] ?? item.resultType}
|
||||
size="small"
|
||||
sx={{ fontSize: 10, height: 18 }}
|
||||
/>
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mt: 'auto' }}>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ flex: 1 }}>
|
||||
{item.addedBy} · {addedDate}
|
||||
</Typography>
|
||||
<ExternalLink size={11} color="#94a3b8" />
|
||||
</Box>
|
||||
{item.note && (
|
||||
<Typography variant="caption" sx={{ color: '#475569', fontStyle: 'italic' }}>
|
||||
{item.note}
|
||||
</Typography>
|
||||
)}
|
||||
</Card>
|
||||
{inquiryDialog}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
onClick={handleCardClick}
|
||||
sx={{
|
||||
p: 2,
|
||||
borderBottom: '1px solid #f1f5f9',
|
||||
opacity: isFinalized ? 0.75 : 1,
|
||||
display: 'flex',
|
||||
gap: 1.5,
|
||||
alignItems: 'flex-start',
|
||||
cursor: 'pointer',
|
||||
'&:hover': { bgcolor: '#f8fafc' },
|
||||
transition: 'background-color 0.1s ease',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5, flexWrap: 'wrap' }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600, color: '#1e3a5f' }}>{item.title}</Typography>
|
||||
<Chip
|
||||
label={RESULT_TYPE_LABEL[item.resultType] ?? item.resultType}
|
||||
size="small"
|
||||
sx={{ fontSize: 10, height: 18 }}
|
||||
/>
|
||||
<Chip
|
||||
label={`${item.matchScore}`}
|
||||
size="small"
|
||||
sx={{ bgcolor: SCORE_COLOR(item.matchScore), color: 'white', fontSize: 10, height: 18, fontWeight: 700 }}
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
|
||||
{item.sourceLabel ?? item.resultType} · {item.addedBy} · {addedDate}
|
||||
</Typography>
|
||||
{item.note && (
|
||||
<Typography variant="caption" sx={{ display: 'block', mt: 0.5, color: '#475569', fontStyle: 'italic' }}>
|
||||
{item.note}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.25 }}>
|
||||
{!isFinalized && item.resultType !== 'FUTURE_AVAILABILITY' && (
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={e => { e.stopPropagation(); setInquiryOpen(true) }}
|
||||
sx={{ color: '#94a3b8', '&:hover': { color: '#1e3a5f' } }}
|
||||
title="Anfrage stellen"
|
||||
>
|
||||
<MessageSquare size={14} />
|
||||
</IconButton>
|
||||
)}
|
||||
{removeBtn}
|
||||
</Box>
|
||||
</Box>
|
||||
{inquiryDialog}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,3 +6,4 @@ export { ShortlistList } from './ShortlistList'
|
||||
export { ShortlistDetail } from './ShortlistDetail'
|
||||
export { DecisionBriefDraftPanel } from './DecisionBriefDraftPanel'
|
||||
export { AddToShortlistDialog } from './AddToShortlistDialog'
|
||||
export { AddToPipelineDialog } from './AddToPipelineDialog'
|
||||
|
||||
Reference in New Issue
Block a user