import { LatLngTuple } from 'leaflet'; import React, { useCallback, useMemo } from 'react'; import { Marker, Popup } from 'react-leaflet'; import { getSectorDisplay } from '@/constants.tsx'; import { useMapActions, useMapInteraction } from '@/contexts/MapContexts.tsx'; import { useOrganizationSites } from '@/hooks/map/useOrganizationSites.ts'; import { Organization } from '@/types.ts'; import { getCachedOrganizationIcon } from '@/utils/map/iconCache.ts'; interface OrganizationMarkersProps { organizations: Organization[]; selectedOrg: Organization | null; hoveredOrgId: string | null; } /** * Individual marker component memoized to prevent unnecessary re-renders */ const OrganizationMarker = React.memo<{ org: Organization; site: { Latitude: number; Longitude: number }; sector: { icon?: React.ReactElement; colorKey?: string } | null; isSelected: boolean; isHovered: boolean; onSelect: (org: Organization) => void; onHover: (orgId: string | null) => void; }>( ({ org, site, sector, isSelected, isHovered, onSelect, onHover }) => { const position: LatLngTuple = useMemo( () => [site.Latitude, site.Longitude], [site.Latitude, site.Longitude] ); const icon = useMemo( () => getCachedOrganizationIcon(org.ID, org, sector, isSelected, isHovered), [org, sector, isSelected, isHovered] ); const handleClick = useCallback(() => { onSelect(org); }, [org, onSelect]); const handleMouseOver = useCallback(() => { onHover(org.ID); }, [org.ID, onHover]); const handleMouseOut = useCallback(() => { onHover(null); }, [onHover]); // Don't render if coordinates are invalid if (!site.Latitude || !site.Longitude || site.Latitude === 0 || site.Longitude === 0) { return null; } return (

{org.Name}

{org.Sector &&

{org.Sector}

} {org.Description && (

{org.Description}

)}
); }, (prevProps, nextProps) => { // Custom comparison function for React.memo return ( prevProps.org.ID === nextProps.org.ID && prevProps.site.Latitude === nextProps.site.Latitude && prevProps.site.Longitude === nextProps.site.Longitude && prevProps.isSelected === nextProps.isSelected && prevProps.isHovered === nextProps.isHovered && prevProps.sector?.colorKey === nextProps.sector?.colorKey ); } ); OrganizationMarker.displayName = 'OrganizationMarker'; const OrganizationMarkers: React.FC = ({ organizations, selectedOrg, hoveredOrgId, }) => { const { handleSelectOrg } = useMapActions(); const { setHoveredOrgId } = useMapInteraction(); const { orgSitesMap } = useOrganizationSites(organizations); // No need for sector map - using getSectorDisplay directly // Filter organizations that have valid coordinates (from site or organization) const organizationsWithCoordinates = useMemo(() => { const result = organizations .filter((org) => org.ID && org.ID.trim() !== '') .map((org) => { const site = orgSitesMap.get(org.ID); // Use site coordinates if available and valid if ( site && site.Latitude && site.Longitude && site.Latitude !== 0 && site.Longitude !== 0 ) { return { org, site }; } // Fallback to organization coordinates if available and valid if (org.Latitude && org.Longitude && org.Latitude !== 0 && org.Longitude !== 0) { return { org, site: { Latitude: org.Latitude, Longitude: org.Longitude } }; } return null; }) .filter( (item): item is { org: Organization; site: { Latitude: number; Longitude: number } } => item !== null ); return result; }, [organizations, orgSitesMap]); // Debug: Log marker count and details in development if (process.env.NODE_ENV === 'development') { console.log(`[OrganizationMarkers] Rendering ${organizationsWithCoordinates.length} markers`, { totalOrganizations: organizations.length, withCoordinates: organizationsWithCoordinates.length, organizationsSample: organizations.slice(0, 3).map((org) => ({ id: org.ID, name: org.Name, coords: [org.Latitude || 0, org.Longitude || 0], })), sample: organizationsWithCoordinates.slice(0, 3).map(({ org, site }) => ({ name: org.Name, coords: site ? [site.Latitude, site.Longitude] : null, hasLogo: !!org.LogoURL, })), }); // Additional debug info if (organizations.length === 0) { console.warn( '[OrganizationMarkers] No organizations received! Check API connection and data flow.' ); } } return ( <> {organizationsWithCoordinates.map(({ org, site }) => { const sectorDisplay = getSectorDisplay(org.Sector); const isSelected = selectedOrg?.ID === org.ID; const isHovered = hoveredOrgId === org.ID; // Skip rendering if coordinates are invalid if (!site.Latitude || !site.Longitude || site.Latitude === 0 || site.Longitude === 0) { return null; } return ( ); })} ); }; export default React.memo(OrganizationMarkers);