Files

96 lines
3.4 KiB
TypeScript
Raw Permalink Normal View History

/**
* SchoolMap Component
* Client-side Leaflet map wrapper for displaying school locations
*/
'use client';
import dynamic from 'next/dynamic';
import { useRef, useState, useEffect, useCallback } from 'react';
import type { School } from '@/lib/types';
import styles from './SchoolMap.module.css';
// Dynamic import to avoid SSR issues with Leaflet
const LeafletMap = dynamic(() => import('./LeafletMapInner'), {
ssr: false,
loading: () => (
<div className={styles.mapLoading}>
<div className={styles.spinner}></div>
<p>Loading map...</p>
</div>
),
});
interface SchoolMapProps {
schools: School[];
center?: [number, number];
zoom?: number;
referencePoint?: [number, number];
onMarkerClick?: (school: School) => void;
}
export function SchoolMap({ schools, center, zoom = 13, referencePoint, onMarkerClick }: SchoolMapProps) {
const wrapperRef = useRef<HTMLDivElement>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
// Sync state with browser fullscreen events (e.g. Escape key)
useEffect(() => {
const onFsChange = () => setIsFullscreen(!!document.fullscreenElement);
document.addEventListener('fullscreenchange', onFsChange);
return () => document.removeEventListener('fullscreenchange', onFsChange);
}, []);
const toggleFullscreen = useCallback(() => {
if (!document.fullscreenElement) {
wrapperRef.current?.requestFullscreen();
} else {
document.exitFullscreen();
}
}, []);
// Calculate center if not provided
const mapCenter: [number, number] = center || (() => {
if (schools.length === 0) return [51.5074, -0.1278];
if (schools.length === 1 && schools[0].latitude && schools[0].longitude) {
return [schools[0].latitude, schools[0].longitude];
}
const validSchools = schools.filter(s => s.latitude && s.longitude);
if (validSchools.length === 0) return [51.5074, -0.1278];
const avgLat = validSchools.reduce((sum, s) => sum + (s.latitude || 0), 0) / validSchools.length;
const avgLng = validSchools.reduce((sum, s) => sum + (s.longitude || 0), 0) / validSchools.length;
return [avgLat, avgLng];
})();
return (
<div ref={wrapperRef} className={`${styles.mapWrapper} ${isFullscreen ? styles.fullscreen : ''}`}>
<button
className={styles.fullscreenBtn}
onClick={toggleFullscreen}
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
aria-label={isFullscreen ? 'Exit fullscreen' : 'View map fullscreen'}
>
{isFullscreen ? (
/* Compress icon */
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="18" height="18" strokeLinecap="round" strokeLinejoin="round">
<path d="M8 3v3a2 2 0 0 1-2 2H3"/><path d="M21 8h-3a2 2 0 0 1-2-2V3"/>
<path d="M3 16h3a2 2 0 0 1 2 2v3"/><path d="M16 21v-3a2 2 0 0 1 2-2h3"/>
</svg>
) : (
/* Expand icon */
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" width="18" height="18" strokeLinecap="round" strokeLinejoin="round">
<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/>
<path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>
</svg>
)}
</button>
<LeafletMap
schools={schools}
center={mapCenter}
zoom={zoom}
referencePoint={referencePoint}
onMarkerClick={onMarkerClick}
/>
</div>
);
}