Initial commit: working SD Park Pass Map app with deploy scripts and .gitignore
This commit is contained in:
146
app/page.tsx
Normal file
146
app/page.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user