Files
school_compare/nextjs-app/components/LeafletMapInner.tsx
Tudor 19e5199443
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m12s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Improve professional appearance: logo, favicon, and remove emoji icons
1. Added original favicon
   - Copied favicon.svg from original frontend
   - Added favicon reference to layout metadata
   - Professional icon with brand colors

2. Updated logo in navigation
   - Replaced emoji with proper SVG logo from original design
   - Uses circular target design with crosshairs
   - Matches brand identity with coral accent color

3. Removed emoji icons throughout app for professional look
   - Removed 📍 (location pin) from school locations
   - Removed 🏫 (school building) from school types
   - Removed 🔢 from URN labels and section headings
   - Kept meaningful symbols (✓, +) in buttons only
   - Updated map popup button color to brand coral (#e07256)

Components updated:
- Navigation: Professional SVG logo
- HomeView: Clean location banner
- SchoolDetailView: No decorative emojis in metadata
- ComparisonView: Text-only school information
- SchoolSearchModal: Clean school listings
- LeafletMapInner: Professional map popups

Result: More polished, professional appearance suitable for
educational data platform

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-02 22:34:14 +00:00

105 lines
3.5 KiB
TypeScript

/**
* LeafletMapInner Component
* Internal Leaflet map implementation (client-side only)
*/
'use client';
import { useEffect, useRef } from 'react';
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import type { School } from '@/lib/types';
// Fix for default marker icons in Next.js
delete (L.Icon.Default.prototype as any)._getIconUrl;
L.Icon.Default.mergeOptions({
iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
});
interface LeafletMapInnerProps {
schools: School[];
center: [number, number];
zoom: number;
onMarkerClick?: (school: School) => void;
}
export default function LeafletMapInner({ schools, center, zoom, onMarkerClick }: LeafletMapInnerProps) {
const mapRef = useRef<L.Map | null>(null);
const mapContainerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!mapContainerRef.current) return;
// Initialize map
if (!mapRef.current) {
mapRef.current = L.map(mapContainerRef.current).setView(center, zoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 19,
}).addTo(mapRef.current);
}
// Clear existing markers
mapRef.current.eachLayer((layer) => {
if (layer instanceof L.Marker) {
mapRef.current!.removeLayer(layer);
}
});
// Add markers for schools
schools.forEach((school) => {
if (school.latitude && school.longitude && mapRef.current) {
const marker = L.marker([school.latitude, school.longitude]).addTo(mapRef.current);
// Create popup content
const popupContent = `
<div style="min-width: 200px;">
<strong style="font-size: 14px; display: block; margin-bottom: 8px;">${school.school_name}</strong>
${school.local_authority ? `<div style="font-size: 12px; color: #666; margin-bottom: 4px;">${school.local_authority}</div>` : ''}
${school.school_type ? `<div style="font-size: 12px; color: #666; margin-bottom: 8px;">${school.school_type}</div>` : ''}
<a href="/school/${school.urn}" style="display: inline-block; margin-top: 8px; padding: 6px 12px; background: #e07256; color: white; text-decoration: none; border-radius: 4px; font-size: 12px;">View Details</a>
</div>
`;
marker.bindPopup(popupContent);
if (onMarkerClick) {
marker.on('click', () => onMarkerClick(school));
}
}
});
// Update map view
if (schools.length > 1) {
const bounds = L.latLngBounds(
schools
.filter(s => s.latitude && s.longitude)
.map(s => [s.latitude!, s.longitude!] as [number, number])
);
mapRef.current.fitBounds(bounds, { padding: [50, 50] });
} else {
mapRef.current.setView(center, zoom);
}
// Cleanup
return () => {
// Don't destroy map on every update, just clean markers
};
}, [schools, center, zoom, onMarkerClick]);
// Cleanup map on unmount
useEffect(() => {
return () => {
if (mapRef.current) {
mapRef.current.remove();
mapRef.current = null;
}
};
}, []);
return <div ref={mapContainerRef} style={{ width: '100%', height: '100%' }} />;
}