import React, { useState, useEffect } from 'react'; import { GateEvent, Settings, GateStatus } from './types'; import * as api from './api'; import { SettingsDialog } from './components/SettingsDialog'; import { Cog6ToothIcon } from '@heroicons/react/24/outline'; function App() { const [events, setEvents] = useState([]); const [hasMoreEvents, setHasMoreEvents] = useState(false); const [totalEvents, setTotalEvents] = useState(0); const [settings, setSettings] = useState({ maxOpenTimeSeconds: 300, triggerDuration: 500, mqtt: { broker: 'localhost', port: '1883', username: '', password: '', clientId: 'gatekeeper', enabled: false }, gpio: { gatePin: 17, statusPin: 27 }, logging: { level: 'WARNING', maxBytes: 10 * 1024 * 1024, // 10MB backupCount: 5 } }); const [gateStatus, setGateStatus] = useState({ isOpen: false, lastChanged: new Date().toISOString() }); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const formatDate = (isoString: string) => { const date = new Date(isoString); return date.toLocaleString(); }; useEffect(() => { const loadData = async () => { try { const [eventsData, settingsData, statusData] = await Promise.all([ api.getEvents(), api.getSettings(), api.getGateStatus() ]); setEvents(eventsData.events); setHasMoreEvents(eventsData.hasMore); setTotalEvents(eventsData.total); setSettings(settingsData); setGateStatus(statusData); } catch (err) { setError('Failed to load data'); console.error(err); } }; const interval = setInterval(async () => { try { // Only update status and most recent events const [statusData, eventsData] = await Promise.all([ api.getGateStatus(), api.getEvents(10, 0) ]); setGateStatus(statusData); // Update only if we're on the first page and don't have more events loaded if (events.length <= 10 && !hasMoreEvents) { setEvents(eventsData.events); setHasMoreEvents(eventsData.hasMore); setTotalEvents(eventsData.total); } else { // If we have more events loaded, just prepend any new events const newEvents = eventsData.events.filter( newEvent => !events.some( existingEvent => existingEvent.timestamp === newEvent.timestamp && existingEvent.action === newEvent.action ) ); if (newEvents.length > 0) { setEvents(prev => [...newEvents, ...prev]); setTotalEvents(prev => prev + newEvents.length); } } } catch (err) { console.error('Failed to update status:', err); } }, 1000); loadData(); return () => clearInterval(interval); }, []); const handleGateControl = async () => { setLoading(true); setError(null); try { const result = await api.triggerGate(); if (result.success) { const newEvents = await api.getEvents(); setEvents(newEvents.events); setGateStatus(prev => ({ ...prev, isOpen: result.isOpen })); } } catch (err) { setError('Failed to trigger gate'); console.error(err); } finally { setLoading(false); } }; const handleSettingsSave = async () => { setLoading(true); setError(null); try { await api.updateSettings(settings); setIsSettingsOpen(false); } catch (err) { setError('Failed to update settings'); console.error(err); } finally { setLoading(false); } }; const handleLoadMore = async () => { try { const moreEvents = await api.getEvents(10, events.length); if (moreEvents.events.length > 0) { setEvents(prev => [...prev, ...moreEvents.events]); setHasMoreEvents(moreEvents.hasMore); setTotalEvents(moreEvents.total); } } catch (err) { setError('Failed to load more events'); console.error(err); } }; return (

Gate Control

{/* Gate Status */}

Status: {gateStatus.isOpen ? "Open" : "Closed"}

Last changed: {formatDate(gateStatus.lastChanged)}

{/* Gate Control Button */}
{/* Error Display */} {error && (
{error}
)} {/* Recent Events */}

Recent Events

{events.map((event, index) => (
{event.action} ({event.source})
{formatDate(event.timestamp)}
))}
{hasMoreEvents && ( )}
{/* Settings Dialog */} { setIsSettingsOpen(false); // loadSettings(); // Refresh settings after dialog closes }} />
); } export default App;