# Frontend Component Architecture Overview of GeoQuery's React components and their interactions. --- ## Component Hierarchy ``` App (Next.js Layout) └── Main Page ├── ChatPanel │ ├── MessageList │ ├── InputForm │ └── CitationLinks ├── MapViewer │ ├── LeafletMap │ ├── LayerControl │ │ ├── SortableLayerItem (draggable) │ │ └── LayerSettings │ └── EmptyState └── DataExplorer ├── ResultsTable └── ExportButton ``` --- ## Core Components ### ChatPanel (`components/ChatPanel.tsx`) Main conversational interface with streaming support. **Props**: ```typescript interface ChatPanelProps { onLayerAdd: (layer: MapLayer) => void; } ``` **State**: ```typescript const [messages, setMessages] = useState([]); const [isLoading, setIsLoading] = useState(false); const [currentThought, setCurrentThought] = useState(''); ``` **Key Features**: - SSE (Server-Sent Events) for streaming - Real-time thought process display - Markdown rendering - Citation link generation - Layer reference chips **SSE Implementation**: ```typescript const response = await fetch('/api/chat', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({message, history}) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const {value, done} = await reader.read(); if (done) break; const chunk = decoder.decode(value); // Process SSE events... } ``` **Event Handling**: - `status`: Updates loading status - `intent`: Shows detected intent - `chunk`: Streams text incrementally - `result`: Adds map layer and displays data --- ### MapViewer (`components/MapViewer.tsx`) Interactive Leaflet map with layer management. **Props**: ```typescript interface MapViewerProps { layers: MapLayer[]; onLayerUpdate: (id: string, updates: Partial) => void; onLayerRemove: (id: string) => void; onLayerReorder: (fromIndex: number, toIndex: number) => void; } ``` **Layer Model**: ```typescript interface MapLayer { id: string; name: string; data: GeoJSON.FeatureCollection; visible: boolean; style: { color: string; fillColor: string; fillOpacity: number; weight: number; }; choropleth?: { enabled: boolean; column: string; palette: string; scale: 'linear' | 'log'; min?: number; max?: number; }; pointMarker?: { icon: string; style: 'icon' | 'circle'; color: string; size: number; }; } ``` **Key Features:** - Layer visibility toggle - Style customization (color, opacity, weight) - Drag-and-drop layer reordering - Auto-fit bounds to new layers - Choropleth visualization - Point rendering modes (icon vs circle) **Point Rendering Logic**: ```typescript const pointToLayer = (feature: any, latlng: L.LatLng) => { if (layer.pointMarker?.style === "circle") { // Simple circle for large datasets return L.circleMarker(latlng, { radius: 5, fillColor: layer.pointMarker.color, color: '#ffffff', weight: 2, opacity: 1, fillOpacity: 0.7 }); } // Emoji icon for POI return L.marker(latlng, { icon: L.divIcon({ html: `
${layer.pointMarker.icon}
`, className: 'custom-emoji-marker', iconSize: [32, 32] }) }); }; ``` **Choropleth Implementation**: ```typescript // Calculate color based on value const fillColor = getChoroplethColor( feature.properties[choropleth.column], choropleth.min, choropleth.max, choropleth.palette, choropleth.scale ); ``` --- ### DataExplorer (`components/DataExplorer.tsx`) Tabular data view with export capabilities. **Props**: ```typescript interface DataExplorerProps { data: any[]; visible: boolean; } ``` **Features**: - Responsive table - Column sorting - CSV export - Pagination for large datasets --- ## Sub-Components ### SortableLayerItem Draggable layer control item using `@dnd-kit`. **Features**: - Drag handle with visual feedback - Layer visibility toggle - Settings expansion - Style controls (color pickers, sliders) - Remove layer button **Drag-and-Drop**: ```typescript const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: layer.id }); const style = { transform: CSS.Transform.toString(transform), transition }; ``` ### AutoFitBounds Utility component that auto-zooms map to new layers. ```typescript function AutoFitBounds({ layers }: { layers: MapLayer[] }) { const map = useMap(); useEffect(() => { if (layers.length > prevLayersLength.current) { const latestLayer = layers[layers.length - 1]; const bounds = L.geoJSON(latestLayer.data).getBounds(); if (bounds.isValid()) { map.fitBounds(bounds, { padding: [50, 50] }); } } }, [layers]); return null; } ``` --- ## State Management ### Global State (Main Page) ```typescript const [layers, setLayers] = useState([]); const handleLayerAdd = (geojson: GeoJSON.FeatureCollection) => { const newLayer: MapLayer = { id: geojson.properties.layer_id, name: geojson.properties.layer_name, data: geojson, visible: true, style: geojson.properties.style, choropleth: geojson.properties.choropleth, pointMarker: geojson.properties.pointMarker }; setLayers(prev => [...prev, newLayer]); }; ``` ### Layer Updates ```typescript const handleLayerUpdate = (id: string, updates: Partial) => { setLayers(prev => prev.map(layer => layer.id === id ? { ...layer, ...updates } : layer )); }; ``` --- ## Styling & Theming ### Global Styles (`app/globals.css`) - Tailwind CSS for utility-first styling - Custom CSS variables for theming - Leaflet overrides for custom markers ### Color Palettes ```typescript const COLOR_PALETTES = { viridis: ['#440154', '#482878', ... '#fde725'], blues: ['#f7fbff', ... '#08306b'], reds: ['#fff5f0', ... '#67000d'] }; ``` --- ## Performance Optimizations ### React Optimizations 1. **Memoization**: ```typescript const MemoizedGeoJSON = React.memo(GeoJSON); ``` 2. **Key Management**: ```typescript key={layer.id + JSON.stringify(layer.style)} ``` 3. **Lazy Loading**: - Components loaded on-demand - Map tiles loaded progressively ### Leaflet Optimizations 1. **Circle Markers for Large Datasets**: - Use `L.circleMarker` instead of `L.divIcon` for >500 points - Significantly faster rendering 2. **Layer Virtualization**: - Only render visible layers - Remove offscreen features --- ## Responsive Design ### Breakpoints - **Mobile** (<640px): Stacked layout - **Tablet** (640-1024px): Sidebar collapses - **Desktop** (>1024px): Full sidebar ### Mobile Adaptations - Layer legend becomes bottom sheet - Map controls repositioned - Touch-friendly drag handles --- ## Accessibility - **Keyboard Navigation**: Tab through controls - **Screen Readers**: ARIA labels on interactive elements - **Color Contrast**: WCAG AA compliant - **Focus Indicators**: Visible focus states --- ## Icons & Assets - **Lucide React**: Icon library for UI elements - **Leaflet Markers**: Default and custom markers - **Emoji Icons**: Unicode emojis for POI markers --- ## Next Steps - **Backend Services**: [../backend/CORE_SERVICES.md](../backend/CORE_SERVICES.md) - **API Reference**: [../backend/API_ENDPOINTS.md](../backend/API_ENDPOINTS.md) - **Data Flow**: [../DATA_FLOW.md](../DATA_FLOW.md)