import { useState } from 'react' import { Box, Button, Card, Chip, Typography, TextField, Stack, CircularProgress, Divider, Alert, } from '@mui/material' import { Target, TrendingUp } from 'lucide-react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { matchService } from '../../services/matchService' import { futureSignalService } from '../../services/futureSignalService' import { RiskLevel } from '../../domain/enums' import { EmptyState } from '../../components/ui' type ReviewItemType = 'MATCH' | 'SIGNAL' interface ReviewItem { id: string type: ReviewItemType title: string confidence: number risk?: RiskLevel summary?: string probability?: number } function getPriorityLabel(confidence: number): string { return confidence > 0.8 ? 'Kritisch' : 'Normal' } function getPriorityColor(confidence: number): 'error' | 'primary' { return confidence > 0.8 ? 'error' : 'primary' } function getRiskLabel(risk?: RiskLevel): string { if (!risk) return '–' switch (risk) { case RiskLevel.LOW: return 'Niedrig' case RiskLevel.MEDIUM: return 'Mittel' case RiskLevel.HIGH: return 'Hoch' case RiskLevel.CRITICAL: return 'Kritisch' } } function getRiskColor(risk?: RiskLevel): 'success' | 'warning' | 'error' | 'default' { if (!risk) return 'default' if (risk === RiskLevel.LOW) return 'success' if (risk === RiskLevel.MEDIUM) return 'warning' return 'error' } export default function ReviewQueue() { const queryClient = useQueryClient() const [activeItem, setActiveItem] = useState(null) const [reviewNotes, setReviewNotes] = useState('') const [approvedIds, setApprovedIds] = useState>(new Set()) const [rejectedIds, setRejectedIds] = useState>(new Set()) const { data: matchResp, isLoading: matchLoading } = useQuery({ queryKey: ['matches'], queryFn: () => matchService.getAll(), }) const { data: signalResp, isLoading: signalLoading } = useQuery({ queryKey: ['futureSignals'], queryFn: () => futureSignalService.getAll(), }) const matches = matchResp?.data ?? [] const signals = signalResp?.data ?? [] // Review items = matches NOT approved + future signals NOT verified const matchItems: ReviewItem[] = matches .filter(m => !m.isApproved && !approvedIds.has(m.id) && !rejectedIds.has(m.id)) .map(m => ({ id: m.id, type: 'MATCH' as ReviewItemType, title: `Match: ${m.propertyId} / ${m.needId}`, confidence: m.confidenceLevel, risk: m.riskLevel, summary: m.explainabilitySummary, })) const signalItems: ReviewItem[] = signals .filter(s => !s.isVerified && !approvedIds.has(s.id) && !rejectedIds.has(s.id)) .map(s => ({ id: s.id, type: 'SIGNAL' as ReviewItemType, title: `${s.signalType}: ${s.locationHint}`, confidence: s.confidenceScore, risk: s.riskLevel, probability: s.probability, summary: s.disclaimer, })) const allItems = [...matchItems, ...signalItems] const selectedItem = allItems.find(i => i.id === activeItem) const isLoading = matchLoading || signalLoading const handleApprove = async () => { if (!activeItem) return const item = allItems.find(i => i.id === activeItem) if (item?.type === 'MATCH') { await matchService.approve(activeItem, 'admin@ideal-sharing.ch') await queryClient.invalidateQueries({ queryKey: ['matches'] }) } else if (item?.type === 'SIGNAL') { await futureSignalService.verify(activeItem, 'admin@ideal-sharing.ch') await queryClient.invalidateQueries({ queryKey: ['futureSignals'] }) } setApprovedIds(prev => new Set([...prev, activeItem])) setActiveItem(null) setReviewNotes('') } const handleReject = () => { if (!activeItem) return setRejectedIds(prev => new Set([...prev, activeItem])) setActiveItem(null) setReviewNotes('') } if (isLoading) { return ( ) } const totalPending = matchItems.length + signalItems.length return ( {/* Page Header */} Review Queue {totalPending > 0 && ( )} Human-in-the-loop Prüfung {/* Stats row */} Offene Reviews 0 ? 'warning.main' : 'text.primary' }}> {totalPending} Ausstehende Prüfungen Signale zur Prüfung 0 ? 'warning.main' : 'text.primary' }}> {signalItems.length} Unverifizierte Signale {/* Two-column layout */} {/* Left: Item List */} Ausstehende Elemente {allItems.length === 0 ? ( ) : ( {allItems.map(item => ( { setActiveItem(item.id) setReviewNotes('') }} sx={{ px: 2, py: 1.5, cursor: 'pointer', borderBottom: '1px solid #f8fafc', bgcolor: activeItem === item.id ? '#eff6ff' : 'white', '&:hover': { bgcolor: activeItem === item.id ? '#eff6ff' : '#fafafa' }, display: 'flex', alignItems: 'flex-start', gap: 1.5, }} > {item.type === 'MATCH' ? : } {item.title} ))} )} {/* Right: Review Panel */} {!selectedItem ? ( ) : ( {selectedItem.title} {/* Key facts */} Konfidenz {Math.round(selectedItem.confidence * 100)}% {selectedItem.probability != null && ( Wahrscheinlichkeit {Math.round(selectedItem.probability * 100)}% )} Risiko {selectedItem.summary && ( {selectedItem.summary} )} {/* Notes */} Notizen setReviewNotes(e.target.value)} size="small" sx={{ mb: 2 }} /> {/* Decision buttons */} )} ) }