Files
property-match/src/components/ops/SignalInbox.tsx
T
Benjamin Sutter cb18f3c381 feat: F021 market intelligence & signal discovery
Signal domain types, mock data (8 signals), provider interface + MockupMarketIntelligenceProvider, marketIntelligenceService, TanStack Query hooks, 10 ops components (SignalInbox, MarketSignalCard, MarketSignalFilterBar, MarketSignalSkeleton, MarketSignalEmptyState, SignalConfidenceBadge, SourceReliabilityBadge, SensitivityWarningPanel, SignalConversionPanel, SignalEvidenceList), MarketIntelligence page wired into AppShell nav and App router.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 17:07:58 +02:00

98 lines
2.9 KiB
TypeScript

import { Box, Chip, TextField, Typography } from '@mui/material'
import { Search } from 'lucide-react'
import type { MarketSignal, MarketSignalFilters } from '../../domain/marketSignal'
import { MarketSignalCard } from './MarketSignalCard'
import { MarketSignalFilterBar } from './MarketSignalFilterBar'
import { MarketSignalSkeleton } from './MarketSignalSkeleton'
import { MarketSignalEmptyState } from './MarketSignalEmptyState'
interface SignalInboxProps {
signals: MarketSignal[]
isLoading: boolean
selectedId: string | null
onSelect: (id: string) => void
filters: MarketSignalFilters
onFiltersChange: (f: MarketSignalFilters) => void
}
export function SignalInbox({
signals,
isLoading,
selectedId,
onSelect,
filters,
onFiltersChange,
}: SignalInboxProps) {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
{/* Inbox header */}
<Box
sx={{
px: 1.5,
py: 1.25,
borderBottom: '1px solid #e2e8f0',
display: 'flex',
alignItems: 'center',
gap: 1,
flexShrink: 0,
}}
>
<Typography variant="subtitle2" sx={{ fontWeight: 600, flex: 1 }}>
Signal-Inbox
</Typography>
<Chip
label={isLoading ? '…' : signals.length}
size="small"
sx={{ bgcolor: '#1e3a5f', color: '#fff', fontSize: '0.7rem', height: 20 }}
/>
</Box>
{/* Search */}
<Box sx={{ px: 1.5, py: 1, borderBottom: '1px solid #f1f5f9', flexShrink: 0 }}>
<TextField
size="small"
placeholder="Suchen..."
fullWidth
value={filters.search ?? ''}
onChange={(e) => onFiltersChange({ ...filters, search: e.target.value || undefined })}
slotProps={{
input: {
startAdornment: <Search size={14} color="#94a3b8" style={{ marginRight: 6 }} />,
},
}}
sx={{ '& .MuiOutlinedInput-root': { fontSize: '0.8rem' } }}
/>
</Box>
{/* Filters */}
<Box sx={{ flexShrink: 0 }}>
<MarketSignalFilterBar filters={filters} onChange={onFiltersChange} />
</Box>
{/* Signal list */}
<Box sx={{ flex: 1, overflowY: 'auto' }}>
{isLoading ? (
Array.from({ length: 5 }).map((_, i) => <MarketSignalSkeleton key={i} />)
) : signals.length === 0 ? (
<MarketSignalEmptyState
variant={
filters.sourceCategory || filters.processingStatus || filters.search
? 'no-results'
: 'empty-inbox'
}
/>
) : (
signals.map((signal) => (
<MarketSignalCard
key={signal.id}
signal={signal}
selected={signal.id === selectedId}
onClick={() => onSelect(signal.id)}
/>
))
)}
</Box>
</Box>
)
}