import React, { useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { AlertTriangle, ArrowLeft, ArrowRight, CheckCircle, Clock, DollarSign, FileText, MapPin, MessageSquare, TrendingUp } from 'lucide-react'; import { MainLayout } from '@/components/layout/MainLayout.tsx'; import PageHeader from '@/components/layout/PageHeader.tsx'; import MatchCard from '@/components/matches/MatchCard.tsx'; import Badge from '@/components/ui/Badge.tsx'; import Button from '@/components/ui/Button.tsx'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/Card.tsx'; import MetricItem from '@/components/ui/MetricItem.tsx'; import Select from '@/components/ui/Select.tsx'; import Textarea from '@/components/ui/Textarea.tsx'; import type { TimelineEntry } from '@/components/ui/Timeline.tsx'; import Timeline from '@/components/ui/Timeline.tsx'; import { Container, Flex, Grid, Stack } from '@/components/ui/layout'; import { useAuth } from '@/contexts/AuthContext.tsx'; import { useMatch, useUpdateMatchStatus } from '@/hooks/api/useMatchingAPI.ts'; import { useTranslation } from '@/hooks/useI18n.tsx'; import { useNavigation } from '@/hooks/useNavigation.tsx'; const MatchNegotiationPage = () => { 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(''); const [showMessageModal, setShowMessageModal] = useState(false); const [messageText, setMessageText] = 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_changed': return t('matchNegotiation.statusChanged', 'Status Changed'); case 'comment': return t('matchNegotiation.commentAdded', 'Comment Added'); case 'update': return t('matchNegotiation.matchUpdated', 'Match Updated'); default: return value || action; } }; // Get allowed next statuses based on current status const allowedNextStatuses = useMemo(() => { if (!match) return []; const statusMap: Record = { 'suggested': ['negotiating', 'reserved', 'failed', 'cancelled'], 'negotiating': ['reserved', 'contracted', 'failed', 'cancelled'], 'reserved': ['contracted', 'negotiating', 'failed', 'cancelled'], 'contracted': ['live', 'failed', 'cancelled'], 'live': ['failed', 'cancelled'], 'failed': [], 'cancelled': [], }; return statusMap[match.Status] || []; }, [match]); const statusOptions = allowedNextStatuses.map(status => ({ value: status, label: t(`matchStatus.${status}`, status), })); 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 handleSendMessage = async () => { // TODO: Implement message sending functionality // This would require a messaging API endpoint console.log('Sending message:', messageText); setShowMessageModal(false); setMessageText(''); }; 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)}%`; }; const getStatusBadgeVariant = (status: string) => { switch (status) { case 'live': return 'default'; case 'contracted': return 'default'; case 'negotiating': return 'secondary'; case 'reserved': return 'outline'; case 'failed': return 'destructive'; case 'cancelled': return 'destructive'; default: return 'outline'; } }; const getNextActionText = (currentStatus: string) => { switch (currentStatus) { case 'suggested': return t('matchNegotiation.startNegotiation', 'Start Negotiation'); case 'negotiating': return t('matchNegotiation.proposeContract', 'Propose Contract'); case 'reserved': return t('matchNegotiation.finalizeContract', 'Finalize Contract'); case 'contracted': return t('matchNegotiation.activateMatch', 'Activate Match'); default: return t('matchNegotiation.updateStatus', 'Update Status'); } }; if (isLoading) { return (
); } if (error || !match) { return (

{error?.message || t('matchNegotiation.notFound', 'Negotiation not found')}

); } return ( {/* Current Status & Actions */}
{t('matchNegotiation.currentStatus', 'Current Status')}
{t(`matchStatus.${match.Status}`, match.Status)}
{allowedNextStatuses.length > 0 && ( )}
{/* Match Overview */} {/* Key Negotiation Metrics */} } label={t('matchDetail.compatibilityScore')} value={formatScore(match.CompatibilityScore)} /> } label={t('matchDetail.economicValue')} value={formatCurrency(match.EconomicValue)} /> } label={t('matchDetail.distance')} value={`${match.DistanceKm.toFixed(1)} km`} /> } label={t('matchNegotiation.daysInNegotiation', 'Days in Negotiation')} value={calculateNegotiationDays(match)} /> {/* Negotiation Progress */} {t('matchNegotiation.negotiationProgress', 'Negotiation Progress')} {/* Economic Impact & Risk Assessment */} {/* Economic Impact */} {match.EconomicImpact && ( {t('matchNegotiation.potentialImpact', 'Potential Impact')} {match.EconomicImpact.annual_savings && (
{t('matchDetail.annualSavings')} {formatCurrency(match.EconomicImpact.annual_savings)}
)} {match.EconomicImpact.co2_avoided_tonnes && (
{t('matchNegotiation.co2Avoided', 'CO₂ Avoided')} {match.EconomicImpact.co2_avoided_tonnes.toFixed(1)} t/year
)} {match.EconomicImpact.payback_years && (
{t('matchDetail.paybackPeriod')} {match.EconomicImpact.payback_years.toFixed(1)} years
)}
)} {/* Risk Assessment */} {match.RiskAssessment && ( {t('matchDetail.riskAssessment')}
{Object.entries(match.RiskAssessment).map(([key, value]) => { if (typeof value !== 'number') return null; return (
{key.replace('_', ' ')} Risk 0.7 ? 'destructive' : value > 0.4 ? 'secondary' : 'default' } > {formatScore(value)}
); })}
)}
{/* Negotiation History Timeline */} {t('matchNegotiation.negotiationHistory', 'Negotiation History')}
{/* Status Update Modal */} {showStatusUpdate && (
{t('matchNegotiation.updateStatus', 'Update Match Status')}