import React, { useState, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { MainLayout } from '@/components/layout/MainLayout.tsx'; import PageHeader from '@/components/layout/PageHeader.tsx'; import MatchCard from '@/components/matches/MatchCard.tsx'; import Timeline from '@/components/ui/Timeline.tsx'; import MetricItem from '@/components/ui/MetricItem.tsx'; import { Container, Stack, Grid, Flex } from '@/components/ui/layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx'; import Button from '@/components/ui/Button.tsx'; import Badge from '@/components/ui/Badge.tsx'; import Select from '@/components/ui/Select.tsx'; import Textarea from '@/components/ui/Textarea.tsx'; import { useTranslation } from '@/hooks/useI18n.tsx'; import { useMatch, useUpdateMatchStatus } from '@/hooks/api/useMatchingAPI.ts'; import { useNavigation } from '@/hooks/useNavigation.tsx'; import { useAuth } from '@/contexts/AuthContext.tsx'; import { AlertTriangle, ArrowLeft, CheckCircle, Clock, DollarSign, MapPin, MessageSquare, TrendingUp } from 'lucide-react'; import type { TimelineEntry } from '@/components/ui/Timeline.tsx'; const MatchDetailPage = () => { const { id: matchId } = useParams<{ id: string }>(); const navigate = useNavigate(); const { t } = useTranslation(); const { handleBackNavigation, handleFooterNavigate } = useNavigation(); const { user } = useAuth(); const { data: match, isLoading, error } = useMatch(matchId); const updateStatusMutation = useUpdateMatchStatus(); const [showStatusUpdate, setShowStatusUpdate] = useState(false); const [newStatus, setNewStatus] = useState(''); const [statusNotes, setStatusNotes] = useState(''); // Transform match history to timeline format const timelineEntries: TimelineEntry[] = useMemo(() => { if (!match?.History) return []; return match.History.map((entry, index) => ({ id: `history-${index}`, timestamp: entry.timestamp, title: getHistoryTitle(entry.action, entry.new_value || entry.old_value), description: entry.notes, actor: entry.actor, action: entry.action, oldValue: entry.old_value, newValue: entry.new_value, })); }, [match?.History]); const getHistoryTitle = (action: string, value?: string) => { switch (action) { case 'status_change': return t('matchDetail.statusChanged', 'Status changed'); case 'comment': return t('matchDetail.commentAdded', 'Comment added'); case 'update': return t('matchDetail.matchUpdated', 'Match updated'); default: return value || action; } }; const handleStatusUpdate = async () => { if (!match || !newStatus || !user) return; try { await updateStatusMutation.mutateAsync({ matchId: match.ID, status: newStatus, actor: user.email || user.name || 'Unknown', notes: statusNotes, }); setShowStatusUpdate(false); setNewStatus(''); setStatusNotes(''); } catch (error) { console.error('Failed to update match status:', error); } }; const formatCurrency = (value: number) => { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); }; const formatScore = (score: number) => { return `${(score * 100).toFixed(1)}%`; }; if (isLoading) { return (
); } if (error || !match) { return (

{error?.message || t('matchDetail.notFound', 'Match not found')}

); } const statusOptions = [ { value: 'suggested', label: t('matchStatus.suggested', 'Suggested') }, { value: 'negotiating', label: t('matchStatus.negotiating', 'Negotiating') }, { value: 'reserved', label: t('matchStatus.reserved', 'Reserved') }, { value: 'contracted', label: t('matchStatus.contracted', 'Contracted') }, { value: 'live', label: t('matchStatus.live', 'Live') }, { value: 'failed', label: t('matchStatus.failed', 'Failed') }, { value: 'cancelled', label: t('matchStatus.cancelled', 'Cancelled') }, ]; return ( {/* Match Overview Card */} {/* Key Metrics */} } label={t('matchDetail.compatibilityScore', 'Compatibility')} value={formatScore(match.CompatibilityScore)} /> } label={t('matchDetail.economicValue', 'Economic Value')} value={formatCurrency(match.EconomicValue)} /> } label={t('matchDetail.distance', 'Distance')} value={`${match.DistanceKm.toFixed(1)} km`} /> } label={t('matchDetail.priority', 'Priority')} value={match.Priority.toString()} /> {/* Economic Impact & Risk Assessment */} {/* Economic Impact */} {match.EconomicImpact && ( {t('matchDetail.economicImpact', 'Economic Impact')} {match.EconomicImpact.annual_savings && (
{t('matchDetail.annualSavings', 'Annual Savings')} {formatCurrency(match.EconomicImpact.annual_savings)}
)} {match.EconomicImpact.npv && (
{t('matchDetail.npv', 'Net Present Value')} {formatCurrency(match.EconomicImpact.npv)}
)} {match.EconomicImpact.irr && (
{t('matchDetail.irr', 'Internal Rate of Return')} {formatScore(match.EconomicImpact.irr)}
)} {match.EconomicImpact.payback_years && (
{t('matchDetail.paybackPeriod', 'Payback Period')} {match.EconomicImpact.payback_years.toFixed(1)} years
)}
)} {/* Risk Assessment */} {match.RiskAssessment && ( {t('matchDetail.riskAssessment', 'Risk Assessment')} {match.RiskAssessment.technical_risk !== undefined && (
{t('matchDetail.technicalRisk', 'Technical Risk')} 0.7 ? 'destructive' : match.RiskAssessment.technical_risk > 0.4 ? 'secondary' : 'default' } > {formatScore(match.RiskAssessment.technical_risk)}
)} {match.RiskAssessment.regulatory_risk !== undefined && (
{t('matchDetail.regulatoryRisk', 'Regulatory Risk')} 0.7 ? 'destructive' : match.RiskAssessment.regulatory_risk > 0.4 ? 'secondary' : 'default' } > {formatScore(match.RiskAssessment.regulatory_risk)}
)} {match.RiskAssessment.market_risk !== undefined && (
{t('matchDetail.marketRisk', 'Market Risk')} 0.7 ? 'destructive' : match.RiskAssessment.market_risk > 0.4 ? 'secondary' : 'default' } > {formatScore(match.RiskAssessment.market_risk)}
)}
)}
{/* Status Update Section */} {t('matchDetail.updateStatus', 'Update Status')} {showStatusUpdate && (