147 lines
4.8 KiB
TypeScript
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>
|
|
);
|
|
}
|