All files / frontend/src/components/Map MapContainer.tsx

78.94% Statements 45/57
50% Branches 4/8
100% Functions 1/1
78.94% Lines 45/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91            1x 1x 1x 1x 1x 1x                         1x 55x 55x 55x 55x 55x 55x 55x 55x 55x 55x 55x     55x 37x 37x 37x 37x   37x 37x 37x 37x 37x   37x 37x 37x   37x   18x 55x   55x     55x 18x 18x 18x 18x 18x       55x                           55x   55x   1x  
/**
 * MapContainer Component
 * 
 * Base Mapbox GL JS map component with WebSDR markers and localization display
 */
 
import React, { useRef } from 'react';
import 'mapbox-gl/dist/mapbox-gl.css';
import './Map.css';
import { useMapbox } from '@/hooks/useMapbox';
import WebSDRMarkers from './WebSDRMarkers';
import LocalizationLayer from './LocalizationLayer';
import type { WebSDRConfig, WebSDRHealthStatus } from '@/services/api/types';
import type { LocalizationResult } from '@/services/api/types';
 
export interface MapContainerProps {
    websdrs: WebSDRConfig[];
    healthStatus: Record<number, WebSDRHealthStatus>;
    localizations: LocalizationResult[];
    onLocalizationClick?: (localization: LocalizationResult) => void;
    style?: React.CSSProperties;
    className?: string;
}
 
const MapContainer: React.FC<MapContainerProps> = ({
    websdrs,
    healthStatus,
    localizations,
    onLocalizationClick,
    style,
    className = '',
}) => {
    const mapContainerRef = useRef<HTMLDivElement>(null);
    const { map, isLoaded, error } = useMapbox({
        container: mapContainerRef.current!,
    });
 
    // Show error if map fails to load
    if (error) {
        return (
            <div
                className={`d-flex align-items-center justify-content-center bg-body-secondary ${className}`}
                style={style || { height: '500px' }}
            >
                <div className="alert alert-warning mb-0 m-3">
                    <i className="ph ph-warning me-2"></i>
                    <strong>Map Error:</strong> {error}
                    <br />
                    <small className="text-muted">
                        Please configure VITE_MAPBOX_TOKEN in your .env file.
                    </small>
                </div>
            </div>
        );
    }
 
    return (
        <div className={`position-relative ${className}`} style={style || { height: '500px' }}>
            {/* Map container */}
            <div ref={mapContainerRef} className="w-100 h-100" />
 
            {/* Loading overlay */}
            {!isLoaded && (
                <div className="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center bg-dark bg-opacity-50">
                    <div className="spinner-border text-light" role="status">
                        <span className="visually-hidden">Loading map...</span>
                    </div>
                </div>
            )}
 
            {/* Render map layers when loaded */}
            {isLoaded && map && (
                <>
                    <WebSDRMarkers
                        map={map}
                        websdrs={websdrs}
                        healthStatus={healthStatus}
                    />
                    <LocalizationLayer
                        map={map}
                        localizations={localizations}
                        onLocalizationClick={onLocalizationClick}
                    />
                </>
            )}
        </div>
    );
};
 
export default MapContainer;