import { useTranslation } from '@/hooks/useI18n'; import type { BackendMatch } from '@/schemas/backend/match'; import { LatLngTuple } from 'leaflet'; import React, { useMemo } from 'react'; import { Polyline, Popup } from 'react-leaflet'; import { formatCurrency } from '../../lib/fin'; interface MatchLinesProps { matches: BackendMatch[]; selectedMatchId: string | null; onMatchSelect: (matchId: string) => void; } /** * Individual match line component memoized to prevent unnecessary re-renders */ const MatchLine = React.memo<{ match: BackendMatch; positions: LatLngTuple[]; isSelected: boolean; onClick: () => void; }>(({ match, positions, isSelected, onClick }) => { const { t } = useTranslation(); const getLineColor = () => { switch (match.Status) { case 'live': return 'hsl(var(--primary))'; case 'contracted': return 'hsl(var(--success))'; case 'negotiating': return 'hsl(var(--warning))'; case 'reserved': return 'hsl(var(--secondary))'; default: return 'hsl(var(--muted-foreground))'; } }; const formatScore = (score: number) => { return `${(score * 100).toFixed(1)}%`; }; // use central fin.formatCurrency return ( { const layer = e.target; layer.setStyle({ weight: isSelected ? 6 : 4, opacity: 1, }); }, mouseout: (e) => { const layer = e.target; layer.setStyle({ weight: isSelected ? 5 : 3, opacity: isSelected ? 1 : 0.7, }); }, }} >

{t('matchesMap.matchConnection', 'Match Connection')}

{t(`matchStatus.${match.Status}`, match.Status)}
{t('matchesMap.compatibility', 'Compatibility')}:
{formatScore(match.CompatibilityScore)}
{t('matchesMap.distance', 'Distance')}:
{match.DistanceKm.toFixed(1)} km
{t('matchesMap.economicValue', 'Economic Value')}:
{formatCurrency(match.EconomicValue)}
{match.EconomicImpact?.annual_savings && (
{t('matchesMap.annualSavings', 'Annual Savings')}:
{formatCurrency(match.EconomicImpact.annual_savings)}
)}
); }); MatchLine.displayName = 'MatchLine'; const MatchLines: React.FC = ({ matches, selectedMatchId, onMatchSelect }) => { // Transform matches into line data with positions const matchLines = useMemo(() => { return matches .map((match) => { // For now, we'll need to get site coordinates from the match data // In a real implementation, we'd join with site data // For now, creating mock positions based on match ID for demonstration const baseLat = 55.1644; // Bugulma center const baseLng = 50.205; // Generate pseudo-random but consistent positions based on match ID const hash = match.ID.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); const lat1 = baseLat + ((hash % 100) - 50) * 0.01; const lng1 = baseLng + (((hash * 7) % 100) - 50) * 0.01; const lat2 = baseLat + (((hash * 13) % 100) - 50) * 0.01; const lng2 = baseLng + (((hash * 17) % 100) - 50) * 0.01; return { match, positions: [[lat1, lng1] as LatLngTuple, [lat2, lng2] as LatLngTuple], isSelected: match.ID === selectedMatchId, }; }) .filter((line) => line.positions.length === 2); }, [matches, selectedMatchId]); return ( <> {matchLines.map(({ match, positions, isSelected }) => ( onMatchSelect(match.ID)} /> ))} ); }; export default React.memo(MatchLines);