import L from 'leaflet'; import React, { useMemo } from 'react'; import { Marker, Popup } from 'react-leaflet'; import MarkerClusterGroup from 'react-leaflet-markercluster'; import { useTranslation } from '@/hooks/useI18n'; import type { BackendMatch } from '@/schemas/backend/match'; // Resource type color mapping const resourceTypeColors: Record = { heat: '#FF6B6B', water: '#4ECDC4', steam: '#95E1D3', CO2: '#45B7D1', biowaste: '#96CEB4', cooling: '#FECA57', logistics: '#FF9FF3', materials: '#54A0FF', service: '#5F27CD', }; interface ResourceFlowMarkersProps { matches: BackendMatch[]; selectedMatchId: string | null; onMatchSelect: (matchId: string) => void; } /** * Individual resource flow marker component */ const ResourceFlowMarker = React.memo<{ position: [number, number]; resourceType: string; direction: string; match: BackendMatch; isInSelectedMatch: boolean; onClick: () => void; }>(({ position, resourceType, direction, match, isInSelectedMatch, onClick }) => { const { t } = useTranslation(); const color = resourceTypeColors[resourceType] || '#6C757D'; // Create custom icon const icon = useMemo(() => { const markerHtml = `
`; return L.divIcon({ html: markerHtml, className: 'custom-resource-flow-marker', iconSize: [26, 26], iconAnchor: [13, 13], }); }, [color, isInSelectedMatch]); const formatScore = (score: number) => { return `${(score * 100).toFixed(1)}%`; }; const formatCurrency = (value: number) => { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); }; return ( { // Could add hover effects here }, mouseout: (e) => { // Reset hover effects here }, }} >

{t(`resourceTypes.${resourceType}`, { defaultValue: resourceType })} {t('matchesMap.flow')}

{direction === 'input' ? t('matchesMap.input') : t('matchesMap.output')}
{t('matchesMap.matchId')}:
{match.ID}
{t('matchesMap.compatibility')}:
{formatScore(match.CompatibilityScore)}
{t('matchesMap.distance')}:
{match.DistanceKm.toFixed(1)} km
{t('matchesMap.economicValue')}:
{formatCurrency(match.EconomicValue)}
{t('matchDetail.updateStatus')}: {t(`matchStatus.${match.Status}`, { defaultValue: match.Status })}
); }); ResourceFlowMarker.displayName = 'ResourceFlowMarker'; const ResourceFlowMarkers: React.FC = ({ matches, selectedMatchId, onMatchSelect }) => { // Transform matches into resource flow markers const resourceFlowMarkers = useMemo(() => { const markers: Array<{ id: string; position: [number, number]; resourceType: string; direction: string; match: BackendMatch; }> = []; matches.forEach(match => { // For now, we'll generate mock positions based on match ID // In a real implementation, we'd get coordinates from site data const baseLat = 55.1644; // Bugulma center const baseLng = 50.2050; 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; // Add source flow marker (assuming it's an output) markers.push({ id: `${match.ID}-source`, position: [lat1, lng1], resourceType: 'heat', // Would come from match data direction: 'output', match, }); // Add target flow marker (assuming it's an input) markers.push({ id: `${match.ID}-target`, position: [lat2, lng2], resourceType: 'heat', // Would come from match data direction: 'input', match, }); }); return markers; }, [matches]); return ( { const count = cluster.getChildCount(); const size = count < 10 ? 'small' : count < 100 ? 'medium' : 'large'; const className = `marker-cluster marker-cluster-${size}`; return L.divIcon({ html: `
${count}
`, className, iconSize: [40, 40], }); }} > {resourceFlowMarkers.map(marker => ( onMatchSelect(marker.match.ID)} /> ))}
); }; export default React.memo(ResourceFlowMarkers);