mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Remove nested git repository from bugulma/frontend/.git - Add all frontend files to main repository tracking - Convert from separate frontend/backend repos to unified monorepo - Preserve all frontend code and development history as tracked files - Eliminate nested repository complexity for simpler development workflow This creates a proper monorepo structure with frontend and backend coexisting in the same repository for easier development and deployment.
191 lines
6.1 KiB
TypeScript
191 lines
6.1 KiB
TypeScript
import L, { 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 { mapBackendSectorToTranslationKey } from '@/lib/sector-mapper.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.ID, 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 (
|
|
<Marker
|
|
position={position}
|
|
icon={icon}
|
|
zIndexOffset={isSelected ? 1000 : isHovered ? 500 : 0}
|
|
keyboard={true}
|
|
interactive={true}
|
|
riseOnHover={true}
|
|
eventHandlers={{
|
|
click: handleClick,
|
|
mouseover: handleMouseOver,
|
|
mouseout: handleMouseOut,
|
|
}}
|
|
>
|
|
<Popup closeButton={true} autoPan={true} maxWidth={300}>
|
|
<div className="p-1">
|
|
<h3 className="text-base font-semibold mb-1">{org.Name}</h3>
|
|
{org.Sector && (
|
|
<p className="text-sm text-muted-foreground">{org.Sector}</p>
|
|
)}
|
|
{org.Description && (
|
|
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
|
|
{org.Description}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</Popup>
|
|
</Marker>
|
|
);
|
|
}, (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<OrganizationMarkersProps> = ({
|
|
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 (
|
|
<OrganizationMarker
|
|
key={`${org.ID}-${site.Latitude.toFixed(6)}-${site.Longitude.toFixed(6)}`}
|
|
org={org}
|
|
site={site}
|
|
sector={sectorDisplay}
|
|
isSelected={isSelected}
|
|
isHovered={isHovered}
|
|
onSelect={handleSelectOrg}
|
|
onHover={setHoveredOrgId}
|
|
/>
|
|
);
|
|
})}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default React.memo(OrganizationMarkers);
|