| # 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<Message[]>([]); | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [currentThought, setCurrentThought] = useState<string>(''); | |
| ``` | |
| **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<MapLayer>) => 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: `<div style="font-size: 32px">${layer.pointMarker.icon}</div>`, | |
| 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<MapLayer[]>([]); | |
| 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<MapLayer>) => { | |
| 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) | |