import type { User } from 'oidc-client-ts'; import { useEffect, useState, useRef, useCallback } from 'react'; import './App.css'; import { getUser, handleCallback } from './auth/authService'; import AlertError from './components/AlertError'; import LoginModal from './components/LoginModal'; import { Map } from './components/Map'; import { FilterPanel, type ParameterValues, DEFAULT_FILTER_VALUES } from './components/FilterPanel'; import { Header } from './components/Header'; import { StatsBar, type ViewMode } from './components/StatsBar'; import { ListView } from './components/ListView'; import { StreamingProgressBar } from './components/StreamingProgressBar'; import { Sheet, SheetContent, SheetTrigger } from './components/ui/sheet'; import { Button } from './components/ui/button'; import { Filter } from 'lucide-react'; import type { GeoJSONFeatureCollection, PropertyProperties, PropertyFeature } from '@/types'; import { refreshListings, fetchTasksForUser, streamListingGeoJSON, type StreamingProgress } from '@/services'; function App() { const [listingData, setListingData] = useState(null); const [taskID, setTaskID] = useState(null); const [user, setUser] = useState(null); const [queryParameters, setQueryParameters] = useState(null); const [submitError, setSubmitError] = useState(null); const [alertDialogIsOpen, setAlertDialogIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const [viewMode, setViewMode] = useState('map'); const [mobileFilterOpen, setMobileFilterOpen] = useState(false); const [highlightedProperty, setHighlightedProperty] = useState(null); const [streamingProgress, setStreamingProgress] = useState(null); // Ref to track accumulated features during streaming const accumulatedFeaturesRef = useRef([]); // Ref to track if initial load has been triggered const initialLoadTriggeredRef = useRef(false); useEffect(() => { // Check if this is a callback from Authentik (after login) if (window.location.pathname === '/callback') { handleCallback().then(() => { window.location.href = '/'; // Redirect to home after login }); return; } // Load user data getUser().then(setUser); }, []); useEffect(() => { if (!user) { return; } fetchTasksForUser(user).then((tasks) => { if (tasks && tasks.length > 0) { setTaskID(tasks[0]); } }); }, [user, taskID]); // Load listings function - used by both auto-load and manual submit const loadListings = useCallback(async (parameters: ParameterValues) => { if (!user) return; setQueryParameters(parameters); setMobileFilterOpen(false); setIsLoading(true); accumulatedFeaturesRef.current = []; setStreamingProgress({ count: 0 }); setListingData(null); try { for await (const batch of streamListingGeoJSON(user, parameters, (progress) => { setStreamingProgress(progress); })) { accumulatedFeaturesRef.current.push(...batch); setListingData({ type: 'FeatureCollection', features: [...accumulatedFeaturesRef.current] }); } } catch (error) { if (error instanceof Error) { setSubmitError(error.message); } else { setSubmitError(String(error)); } setAlertDialogIsOpen(true); } finally { setIsLoading(false); setStreamingProgress(null); } }, [user]); // Auto-load data with default filters when user is authenticated useEffect(() => { if (!user || initialLoadTriggeredRef.current) { return; } initialLoadTriggeredRef.current = true; const defaultParams: ParameterValues = { ...DEFAULT_FILTER_VALUES, available_from: new Date(), }; loadListings(defaultParams); }, [user, loadListings]); if (!user) { return ; } const onSubmit = async (action: 'fetch-data' | 'visualize', parameters: ParameterValues) => { if (action === 'visualize') { loadListings(parameters); } else if (action === 'fetch-data') { setQueryParameters(parameters); setMobileFilterOpen(false); setIsLoading(true); try { const data = await refreshListings(user!, parameters); setTaskID(data.task_id); } catch (error) { if (error instanceof Error) { setSubmitError(error.message); } else { setSubmitError(String(error)); } setAlertDialogIsOpen(true); } finally { setIsLoading(false); } } }; const handlePropertyClick = (property: PropertyProperties, _coordinates: [number, number]) => { setHighlightedProperty(property.url); // Optionally: pan map to coordinates }; const renderMainContent = () => { if (!listingData) { return (
{isLoading ? ( <>
🏠

Loading Properties...

Fetching listings with default filters. You can adjust filters on the left.

) : ( <>
🏠

Welcome to Property Explorer

Use the filters on the left to find properties. Apply filters to visualize existing data or refresh to fetch new listings.

)}
); } if (listingData.features.length === 0) { return (
🔍

No listings found

Try adjusting the filters or run a data refresh to fetch new listings.

); } return ( <> {/* Map View */} {(viewMode === 'map' || viewMode === 'split') && (
)} {/* List View */} {(viewMode === 'list' || viewMode === 'split') && (
)} ); }; const handleTaskCancelled = () => { setTaskID(null); }; return (
{/* Header */}
{/* Main content area */}
{/* Filter Panel - Desktop (fixed sidebar) */}
{/* Filter Panel - Mobile (sheet) */}
{/* Main View Area */}
{/* Streaming Progress Bar */}
{/* Map/List Container */}
{renderMainContent()}
{/* Stats Bar */} {listingData && listingData.features.length > 0 && (
)}
{/* Error Dialog */}
); } export default App;