'use client'; import { useState, useEffect } from 'react'; import { calculateDistance, getCurrentLocation, getCoordinatesFromZip, isValidSanDiegoZip } from '../utils/notifications'; interface LocationData { name: string; lat: number; lng: number; available: boolean; passType: string; } interface LibraryListProps { locations: LocationData[]; } interface LocationWithDistance extends LocationData { distance?: number; } export default function LibraryList({ locations }: { readonly locations: LocationData[] }) { // Default to 92129 zip and distance sort const defaultZip = '92129'; const defaultCoords = getCoordinatesFromZip(defaultZip); const [sortBy, setSortBy] = useState<'name' | 'distance' | 'availability'>('distance'); const [userLocation, setUserLocation] = useState<{ lat: number; lng: number } | null>(defaultCoords ? { lat: defaultCoords.lat, lng: defaultCoords.lng } : null); const [locationsWithDistance, setLocationsWithDistance] = useState(locations); const [locationPermission, setLocationPermission] = useState<'granted' | 'denied' | 'prompt' | 'loading'>(defaultCoords ? 'granted' : 'prompt'); const [zipCode, setZipCode] = useState(defaultZip); const [zipError, setZipError] = useState(''); const requestLocation = async () => { setLocationPermission('loading'); const location = await getCurrentLocation(); if (location) { setUserLocation(location); setLocationPermission('granted'); setZipCode(''); // Clear zip code when GPS is used } else { setLocationPermission('denied'); } }; const handleZipCode = () => { if (!zipCode.trim()) { setZipError('Please enter a zip code'); return; } if (!isValidSanDiegoZip(zipCode.trim())) { setZipError('Please enter a valid San Diego area zip code (92101-92173)'); return; } const coords = getCoordinatesFromZip(zipCode.trim()); if (coords) { setUserLocation({ lat: coords.lat, lng: coords.lng }); setLocationPermission('granted'); setZipError(''); } else { setZipError('Invalid zip code'); } }; useEffect(() => { if (userLocation) { const withDistances = locations.map(loc => ({ ...loc, distance: calculateDistance(userLocation.lat, userLocation.lng, loc.lat, loc.lng) })); setLocationsWithDistance(withDistances); } else { setLocationsWithDistance(locations); } }, [locations, userLocation]); const sortedLocations = [...locationsWithDistance].sort((a, b) => { switch (sortBy) { case 'name': return a.name.localeCompare(b.name); case 'distance': { if (!a.distance && !b.distance) return 0; if (!a.distance) return 1; if (!b.distance) return -1; // Round to avoid floating point precision issues const aDist = Math.round(a.distance * 10000) / 10000; const bDist = Math.round(b.distance * 10000) / 10000; return aDist - bDist; } case 'availability': if (a.available === b.available) { return a.name.localeCompare(b.name); } return a.available ? -1 : 1; default: return 0; } }); return (
All Libraries ({locations.length})
Sort by:
{locationPermission === 'prompt' && ( )} or setZipCode(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleZipCode()} style={{ fontSize: '10px', padding: '2px 4px', border: '1px solid #ccc', borderRadius: '3px', width: '80px', }} />
{zipError && (
{zipError}
)} {locationPermission === 'loading' && ( Getting location... )} {locationPermission === 'denied' && ( GPS denied - use zip code )} {locationPermission === 'granted' && userLocation && ( 📍 Location set )}
{sortedLocations.map((loc) => (
{loc.name}
{loc.distance && (
{loc.distance.toFixed(1)} mi
)}
{loc.available ? 'Available' : 'Not Available'}
))}
); }