d15a13e485
- Delete all ops page components (ReviewQueue, AIMonitoring, Governance, SourceMonitoring, ActivityTimeline, SignalPipeline) - Remove OPERATIONS workspace from AppShell config, nav order, path detection - Remove all /ops/* routes from App.tsx - Remove WorkspaceType.OPERATIONS from allowedWorkspaces in authService, sessionStore, permissions - Keep MarketIntelligence page (already moved to /supply/market-intelligence) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import { Box, Chip, LinearProgress, Typography } from '@mui/material'
|
|
import { SignalTypeBadge } from './SignalTypeBadge'
|
|
import { SensitivityBadge } from './SensitivityBadge'
|
|
import { SignalReviewStatusBadge } from './SignalReviewStatusBadge'
|
|
import { FutureSignalDisclaimer } from './FutureSignalDisclaimer'
|
|
import type { FutureSignal } from '../../domain/futureSignal'
|
|
|
|
const SOURCE_LABELS: Record<string, string> = {
|
|
PRESS: 'Presse',
|
|
CONSTRUCTION_PERMIT: 'Baubewilligung',
|
|
JOB_POSTING: 'Stelleninserat',
|
|
COMPANY_REPORT: 'Geschäftsbericht',
|
|
MARKET_DATA: 'Marktdaten',
|
|
MANUAL: 'Manuell',
|
|
}
|
|
|
|
function probColor(p: number): string {
|
|
return p >= 0.7 ? '#1a7a4a' : p >= 0.5 ? '#d97706' : '#c0392b'
|
|
}
|
|
|
|
interface Props {
|
|
signal: FutureSignal
|
|
isSelected: boolean
|
|
onSelect: (signal: FutureSignal) => void
|
|
}
|
|
|
|
export function FutureSignalCard({ signal, isSelected, onSelect }: Props) {
|
|
const isConfidential = signal.sensitivityLevel === 'CONFIDENTIAL'
|
|
|
|
return (
|
|
<Box
|
|
onClick={() => onSelect(signal)}
|
|
sx={{
|
|
p: 2,
|
|
cursor: 'pointer',
|
|
borderBottom: '1px solid #f1f5f9',
|
|
borderLeft: isSelected
|
|
? '3px solid #1e3a5f'
|
|
: isConfidential
|
|
? '3px solid #d97706'
|
|
: '3px solid transparent',
|
|
bgcolor: isSelected ? '#eff6ff' : isConfidential ? '#fffbeb' : 'transparent',
|
|
'&:hover': { bgcolor: isSelected ? '#eff6ff' : '#f8fafc' },
|
|
}}
|
|
>
|
|
{/* Row 1: type + sensitivity + review status */}
|
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mb: 1 }}>
|
|
<SignalTypeBadge type={signal.signalType} />
|
|
<SensitivityBadge level={signal.sensitivityLevel} />
|
|
<SignalReviewStatusBadge status={signal.reviewStatus} />
|
|
</Box>
|
|
|
|
{/* Row 2: title / company / location */}
|
|
<Typography variant="body2" sx={{ fontWeight: 600, mb: 0.25 }}>
|
|
{signal.title ?? signal.companyName ?? signal.locationHint}
|
|
</Typography>
|
|
{(signal.title || signal.companyName) && (
|
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 0.75 }}>
|
|
{signal.locationHint}
|
|
</Typography>
|
|
)}
|
|
|
|
{/* Row 3: probability */}
|
|
<Box sx={{ mb: 0.75 }}>
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.25 }}>
|
|
<Typography variant="caption" color="text.secondary">Wahrscheinlichkeit</Typography>
|
|
<Typography variant="caption" sx={{ fontWeight: 700, color: probColor(signal.probability) }}>
|
|
{Math.round(signal.probability * 100)}%
|
|
</Typography>
|
|
</Box>
|
|
<LinearProgress
|
|
variant="determinate"
|
|
value={signal.probability * 100}
|
|
sx={{
|
|
height: 4,
|
|
borderRadius: 2,
|
|
bgcolor: '#f1f5f9',
|
|
'& .MuiLinearProgress-bar': { bgcolor: probColor(signal.probability) },
|
|
}}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Row 4: meta chips */}
|
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, mb: 1 }}>
|
|
<Chip label={`${signal.timeHorizonMonths}M`} size="small" sx={{ fontSize: 10, height: 18 }} />
|
|
{signal.areaSqmEstimate && (
|
|
<Chip label={`~${signal.areaSqmEstimate.toLocaleString('de-CH')} m²`} size="small" sx={{ fontSize: 10, height: 18 }} />
|
|
)}
|
|
<Chip
|
|
label={SOURCE_LABELS[signal.source.type] ?? signal.source.type}
|
|
size="small"
|
|
variant="outlined"
|
|
sx={{ fontSize: 10, height: 18 }}
|
|
/>
|
|
</Box>
|
|
|
|
{/* Footer: mini disclaimer */}
|
|
<FutureSignalDisclaimer mini />
|
|
</Box>
|
|
)
|
|
}
|