Files
sd-park-pass-map/app/page.tsx

147 lines
4.8 KiB
TypeScript

'use client';
import dynamic from 'next/dynamic';
import { useState, useEffect } from 'react';
import LibraryList from './components/LibraryList';
import NotificationPanel from './components/NotificationPanel';
// Dynamically import the map component to avoid SSR issues
const MapComponent = dynamic(() => import('./components/MapComponent'), {
ssr: false,
loading: () => <div>Loading map...</div>
});
interface LocationData {
name: string;
lat: number;
lng: number;
available: boolean;
passType: string;
}
interface ApiResponse {
locations: LocationData[];
lastUpdated: string;
}
export default function Home() {
const [locations, setLocations] = useState<LocationData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [lastUpdated, setLastUpdated] = useState<string>('');
// Start with menu collapsed only on mobile
const [panelCollapsed, setPanelCollapsed] = useState(false);
useEffect(() => {
// Collapse menu on mobile only
if (typeof window !== 'undefined') {
const isMobile = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(window.navigator.userAgent);
if (isMobile) setPanelCollapsed(true);
}
// Trigger data refresh on page load
fetchAvailability();
}, []);
const fetchAvailability = async () => {
try {
setLoading(true);
const response = await fetch('/api/availability');
if (!response.ok) {
throw new Error('Failed to fetch data');
}
const data: ApiResponse = await response.json();
setLocations(data.locations);
setLastUpdated(data.lastUpdated);
setError(null);
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
const availableCount = locations.filter(loc => loc.available).length;
const totalCount = locations.length;
return (
<div style={{ position: 'relative', height: '100vh' }}>
<div className={`info-panel ${panelCollapsed ? 'collapsed' : ''}`}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' }}>
<h2 style={{ fontSize: '18px', fontWeight: 'bold', margin: 0 }}>
SD Beach Park Pass Locator
</h2>
<button
onClick={() => setPanelCollapsed(!panelCollapsed)}
style={{
background: 'none',
border: '1px solid #ccc',
borderRadius: '4px',
padding: '4px 8px',
cursor: 'pointer',
fontSize: '12px',
}}
title={panelCollapsed ? 'Expand panel' : 'Collapse panel'}
>
{panelCollapsed ? '▶' : '◀'}
</button>
</div>
{!panelCollapsed && (
<>
{loading && <p>Loading availability data...</p>}
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
{!loading && !error && (
<>
<p style={{ marginBottom: '10px' }}>
<strong>{availableCount}</strong> of <strong>{totalCount}</strong> locations have available passes
</p>
<LibraryList locations={locations} />
<div className="legend">
<h4 style={{ marginBottom: '8px' }}>Legend:</h4>
<div className="legend-item">
<div className="legend-color available"></div>
<span>Available</span>
</div>
<div className="legend-item">
<div className="legend-color unavailable"></div>
<span>Not Available</span>
</div>
</div>
<NotificationPanel locations={locations} />
{lastUpdated && (
<p style={{ fontSize: '12px', color: '#666', marginTop: '10px' }}>
Last updated: {new Date(lastUpdated).toLocaleString()}
</p>
)}
<button
onClick={fetchAvailability}
style={{
marginTop: '10px',
padding: '8px 16px',
backgroundColor: '#007cba',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
width: '100%',
}}
>
Refresh Data
</button>
</>
)}
</>
)}
</div>
{!loading && !error && <MapComponent locations={locations} />}
</div>
);
}