Skip to main content

Complete Frontend Components Reference

Overview

Comprehensive documentation for all 40+ React components in the HCMC Traffic Management System frontend, built with React 18.2, TypeScript, MapLibre GL, and modern web technologies.


Table of Contents

Map Components

  1. TrafficMap
  2. CameraMarkers
  3. AccidentMarkers
  4. CongestionZones
  5. WeatherOverlay
  6. AQIHeatmap
  7. VehicleHeatmap
  8. SpeedZones
  9. PatternZones
  10. RouteOverlay

UI Components

  1. MapLegend
  2. SimpleLegend
  3. FilterPanel
  4. Sidebar
  5. Header
  6. Footer
  7. CameraDetailModal
  8. AccidentDetailModal

Feature Components

  1. CitizenReportForm
  2. CitizenReportMap
  3. RoutePlanner
  4. TimeMachine
  5. AnalyticsDashboard
  6. RealTimeMonitor
  7. AlertPanel
  8. NotificationCenter

Chart Components

  1. LineChart
  2. BarChart
  3. PieChart
  4. Heatmap
  5. AccidentFrequencyChart
  6. TrafficTrendChart
  7. CorrelationPanel

Form Components

  1. LocationPicker
  2. PhotoUpload
  3. DateRangePicker
  4. TimeRangePicker
  5. CategorySelect

Utility Components

  1. Loading
  2. ErrorBoundary
  3. MetricCard
  4. StatusBadge

Map Components

TrafficMap

Overview

Core interactive map component displaying real-time traffic conditions, camera feeds, accidents, congestion zones, and weather overlays using MapLibre GL.

Props

interface TrafficMapProps {
center?: [number, number]; // Map center [lat, lon]
zoom?: number; // Initial zoom level (1-18)
height?: string; // Map container height
showCameras?: boolean; // Show camera markers
showAccidents?: boolean; // Show accident markers
showCongestion?: boolean; // Show congestion zones
showWeather?: boolean; // Show weather overlay
showAQI?: boolean; // Show air quality heatmap
showVehicles?: boolean; // Show vehicle heatmap
enableClustering?: boolean; // Enable marker clustering
enableGeolocation?: boolean; // Show user location
onCameraClick?: (cameraId: string) => void;
onAccidentClick?: (accidentId: string) => void;
onZoneClick?: (zoneId: string) => void;
onMapClick?: (lat: number, lon: number) => void;
style?: React.CSSProperties;
}

Usage

import TrafficMap from '@/components/TrafficMap';

// Basic usage
<TrafficMap
center={[10.7769, 106.7009]}
zoom={13}
height="600px"
/>

// Full features
<TrafficMap
center={[10.7769, 106.7009]}
zoom={13}
height="100vh"
showCameras={true}
showAccidents={true}
showCongestion={true}
showWeather={true}
showAQI={true}
enableClustering={true}
enableGeolocation={true}
onCameraClick={(id) => console.log('Camera:', id)}
onAccidentClick={(id) => console.log('Accident:', id)}
style={{ borderRadius: '8px' }}
/>

// With state management
const [mapConfig, setMapConfig] = useState({
showCameras: true,
showAccidents: true,
showCongestion: false
});

<TrafficMap {...mapConfig} />

Features

  • Real-time Updates: WebSocket-powered live data
  • Multiple Layers: Toggle-able overlay layers
  • Marker Clustering: Performance optimization for 100+ markers
  • Responsive: Mobile-friendly with touch gestures
  • Custom Controls: Zoom, fullscreen, layer controls
  • Geolocation: User location tracking

Styling

.traffic-map {
width: 100%;
height: 600px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.maplibregl-map {
font-family: 'Inter', sans-serif;
background: #f5f5f5;
}

CameraMarkers

Overview

Displays camera locations on map with clickable markers showing camera status and latest image preview.

Props

interface CameraMarkersProps {
cameras: Camera[];
onCameraClick?: (cameraId: string) => void;
clustering?: boolean;
showStatus?: boolean;
iconSize?: number;
}

interface Camera {
id: string;
name: string;
location: { lat: number; lon: number };
status: 'active' | 'inactive' | 'error';
last_update: string;
}

Usage

import CameraMarkers from '@/components/CameraMarkers';

const cameras = [
{
id: 'CAM_001',
name: 'District 1 - Nguyen Hue',
location: { lat: 10.7769, lon: 106.7009 },
status: 'active',
last_update: '2025-11-29T10:30:00Z'
}
];

<MapContainer>
<CameraMarkers
cameras={cameras}
onCameraClick={(id) => handleCameraClick(id)}
clustering={true}
showStatus={true}
iconSize={32}
/>
</MapContainer>

Features

  • Status Indicators: Color-coded by camera status
  • Clustering: Group nearby cameras at low zoom
  • Popup Preview: Show camera info on hover
  • Custom Icons: SVG camera icons
  • Batch Updates: Efficient re-rendering

AccidentMarkers

Overview

Displays accident locations with severity-based styling and animated alerts for critical incidents.

Props

interface AccidentMarkersProps {
accidents: Accident[];
onAccidentClick?: (accidentId: string) => void;
showSeverity?: boolean;
animateCritical?: boolean;
}

interface Accident {
id: string;
location: { lat: number; lon: number };
severity: 'minor' | 'moderate' | 'severe' | 'critical';
timestamp: string;
vehicles_involved: number;
}

Usage

import AccidentMarkers from '@/components/AccidentMarkers';

<AccidentMarkers
accidents={accidents}
onAccidentClick={(id) => showAccidentDetails(id)}
showSeverity={true}
animateCritical={true}
/>

Styling

.accident-marker {
transition: transform 0.2s;
}

.accident-marker.critical {
animation: pulse 2s infinite;
color: #d32f2f;
}

@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.7; }
}

CongestionZones

Overview

Renders congestion zones as colored polygons with opacity based on severity level.

Props

interface CongestionZonesProps {
zones: CongestionZone[];
onZoneClick?: (zoneId: string) => void;
showLabels?: boolean;
interactive?: boolean;
}

interface CongestionZone {
id: string;
name: string;
polygon: [number, number][];
level: 'free_flow' | 'light' | 'moderate' | 'heavy' | 'severe';
avg_speed: number;
}

Usage

import CongestionZones from '@/components/CongestionZones';

<CongestionZones
zones={congestionZones}
onZoneClick={(id) => console.log('Zone:', id)}
showLabels={true}
interactive={true}
/>

Color Scheme

const congestionColors = {
free_flow: '#4caf50', // Green
light: '#8bc34a', // Light green
moderate: '#ffeb3b', // Yellow
heavy: '#ff9800', // Orange
severe: '#f44336' // Red
};

WeatherOverlay

Overview

Displays weather information overlay with temperature, conditions, and precipitation data.

Props

interface WeatherOverlayProps {
weather: WeatherData;
showForecast?: boolean;
showRadar?: boolean;
opacity?: number;
}

interface WeatherData {
temperature: number;
conditions: string;
precipitation: number;
wind: { speed: number; direction: number };
}

Usage

import WeatherOverlay from '@/components/WeatherOverlay';

<WeatherOverlay
weather={currentWeather}
showForecast={true}
showRadar={true}
opacity={0.6}
/>

AQIHeatmap

Overview

Air quality index heatmap showing pollution levels across the city.

Props

interface AQIHeatmapProps {
data: AQIDataPoint[];
radius?: number;
blur?: number;
maxOpacity?: number;
gradient?: Record<number, string>;
}

interface AQIDataPoint {
lat: number;
lon: number;
aqi: number;
}

Usage

import AQIHeatmap from '@/components/AQIHeatmap';

const aqiGradient = {
0.0: '#00e400', // Good
0.2: '#ffff00', // Moderate
0.4: '#ff7e00', // Unhealthy for Sensitive
0.6: '#ff0000', // Unhealthy
0.8: '#8f3f97', // Very Unhealthy
1.0: '#7e0023' // Hazardous
};

<AQIHeatmap
data={aqiData}
radius={25}
blur={15}
maxOpacity={0.6}
gradient={aqiGradient}
/>

VehicleHeatmap

Overview

Real-time vehicle density heatmap based on traffic camera detections.

Props

interface VehicleHeatmapProps {
data: VehicleDensityPoint[];
radius?: number;
blur?: number;
updateInterval?: number;
}

Usage

<VehicleHeatmap
data={vehicleDensity}
radius={30}
blur={20}
updateInterval={30000}
/>

UI Components

MapLegend

Overview

Interactive legend showing all map layers with toggle controls.

Props

interface MapLegendProps {
position?: 'topright' | 'topleft' | 'bottomright' | 'bottomleft';
layers: LayerConfig[];
onLayerToggle?: (layerId: string, visible: boolean) => void;
collapsible?: boolean;
}

interface LayerConfig {
id: string;
name: string;
icon: React.ReactNode;
visible: boolean;
color?: string;
}

Usage

import MapLegend from '@/components/MapLegend';

const layers = [
{ id: 'cameras', name: 'Cameras', icon: <CameraIcon />, visible: true },
{ id: 'accidents', name: 'Accidents', icon: <AccidentIcon />, visible: true, color: '#d32f2f' },
{ id: 'congestion', name: 'Congestion', icon: <TrafficIcon />, visible: false }
];

<MapLegend
position="topright"
layers={layers}
onLayerToggle={(id, visible) => handleLayerToggle(id, visible)}
collapsible={true}
/>

Styling

.map-legend {
background: white;
padding: 16px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}

.legend-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px;
cursor: pointer;
transition: background 0.2s;
}

.legend-item:hover {
background: #f5f5f5;
}

FilterPanel

Overview

Advanced filtering panel for map layers and data visualization.

Props

interface FilterPanelProps {
filters: FilterState;
onFilterChange: (filters: FilterState) => void;
categories: string[];
dateRange?: [Date, Date];
locations?: string[];
}

interface FilterState {
categories: string[];
severity?: string[];
dateRange?: [Date, Date];
locations?: string[];
timeOfDay?: string;
}

Usage

import FilterPanel from '@/components/FilterPanel';

const [filters, setFilters] = useState({
categories: ['accidents', 'congestion'],
severity: ['moderate', 'severe'],
dateRange: [new Date('2025-11-20'), new Date('2025-12-05')]
});

<FilterPanel
filters={filters}
onFilterChange={setFilters}
categories={['accidents', 'congestion', 'weather']}
locations={['District 1', 'District 3', 'District 5']}
/>

Overview

Collapsible sidebar with navigation and quick access to features.

Props

interface SidebarProps {
open?: boolean;
onToggle?: (open: boolean) => void;
width?: number;
position?: 'left' | 'right';
children: React.ReactNode;
}

Usage

import Sidebar from '@/components/Sidebar';

<Sidebar open={sidebarOpen} onToggle={setSidebarOpen} width={300}>
<nav>
<Link to="/dashboard">Dashboard</Link>
<Link to="/cameras">Cameras</Link>
<Link to="/analytics">Analytics</Link>
</nav>
</Sidebar>

CameraDetailModal

Overview

Modal displaying detailed camera information with live feed and statistics.

Props

interface CameraDetailModalProps {
camera: CameraDetail;
open: boolean;
onClose: () => void;
showLiveFeed?: boolean;
showStatistics?: boolean;
}

interface CameraDetail {
id: string;
name: string;
location: Location;
status: string;
stream_url?: string;
stats?: CameraStats;
}

Usage

import CameraDetailModal from '@/components/CameraDetailModal';

<CameraDetailModal
camera={selectedCamera}
open={modalOpen}
onClose={() => setModalOpen(false)}
showLiveFeed={true}
showStatistics={true}
/>

Feature Components

CitizenReportForm

Overview

Form for citizens to submit traffic reports with photo upload and location selection.

Props

interface CitizenReportFormProps {
onSubmit: (report: CitizenReport) => void;
defaultLocation?: [number, number];
enableCamera?: boolean;
maxPhotos?: number;
categories?: string[];
}

interface CitizenReport {
category: string;
description: string;
location: { lat: number; lon: number };
images: File[];
reporter?: { name: string; contact: string };
}

Usage

import CitizenReportForm from '@/components/CitizenReportForm';

const handleSubmit = async (report: CitizenReport) => {
const result = await submitReport(report);
console.log('Report submitted:', result.id);
};

<CitizenReportForm
onSubmit={handleSubmit}
defaultLocation={[10.7769, 106.7009]}
enableCamera={true}
maxPhotos={3}
categories={['accident', 'congestion', 'hazard', 'other']}
/>

Form Fields

// Category selection
<Select
label="Report Type"
options={[
{ value: 'accident', label: 'Traffic Accident' },
{ value: 'congestion', label: 'Traffic Jam' },
{ value: 'hazard', label: 'Road Hazard' },
{ value: 'construction', label: 'Construction' },
{ value: 'other', label: 'Other Issue' }
]}
/>

// Location picker
<LocationPicker
defaultLocation={defaultLocation}
onLocationChange={(coords) => setLocation(coords)}
/>

// Photo upload
<PhotoUpload
max={maxPhotos}
accept="image/*"
onUpload={(files) => setPhotos(files)}
enableCamera={enableCamera}
/>

// Description
<TextArea
label="Description"
placeholder="Describe the issue in detail..."
minLength={20}
required
/>

Validation

const validateReport = (report: CitizenReport): ValidationResult => {
const errors: string[] = [];

if (!report.category) errors.push('Category is required');
if (!report.location) errors.push('Location is required');
if (report.description.length < 20) errors.push('Description must be at least 20 characters');
if (report.images.length === 0) errors.push('At least one photo is required');

return {
isValid: errors.length === 0,
errors
};
};

RoutePlanner

Overview

Interactive route planning with traffic-aware routing and alternative route suggestions.

Props

interface RoutePlannerProps {
origin?: [number, number];
destination?: [number, number];
onRouteCalculated?: (route: Route) => void;
avoidCongestion?: boolean;
avoidAccidents?: boolean;
preferredRouteType?: 'fastest' | 'shortest' | 'eco';
}

interface Route {
coordinates: [number, number][];
distance: number;
duration: number;
traffic_delay: number;
alternative_routes?: Route[];
}

Usage

import RoutePlanner from '@/components/RoutePlanner';

<RoutePlanner
origin={[10.7769, 106.7009]}
destination={[10.8231, 106.6297]}
onRouteCalculated={(route) => console.log('Route:', route)}
avoidCongestion={true}
avoidAccidents={true}
preferredRouteType="fastest"
/>

Features

  • Traffic-Aware Routing: Real-time traffic consideration
  • Alternative Routes: Multiple route options
  • ETA Calculation: Accurate arrival time
  • Turn-by-Turn: Navigation instructions
  • Avoid Zones: Exclude accidents/congestion

TimeMachine

Overview

Historical data visualization with timeline slider for replay of past traffic conditions.

Props

interface TimeMachineProps {
startDate: Date;
endDate: Date;
currentTime: Date;
onTimeChange: (time: Date) => void;
playbackSpeed?: number;
showPlayControls?: boolean;
}

Usage

import TimeMachine from '@/components/TimeMachine';

const [currentTime, setCurrentTime] = useState(new Date());

<TimeMachine
startDate={new Date('2025-11-20')}
endDate={new Date('2025-12-05')}
currentTime={currentTime}
onTimeChange={setCurrentTime}
playbackSpeed={1}
showPlayControls={true}
/>

Controls

  • Play/Pause: Auto-advance timeline
  • Speed Control: 1x, 2x, 5x, 10x
  • Jump To: Date/time picker
  • Bookmarks: Save interesting moments

AnalyticsDashboard

Overview

Comprehensive analytics dashboard with charts, metrics, and insights.

Props

interface AnalyticsDashboardProps {
timeRange: '24h' | '7d' | '30d' | '1y';
locations?: string[];
refreshInterval?: number;
showRealtime?: boolean;
showTrends?: boolean;
showCorrelations?: boolean;
onExport?: (format: 'csv' | 'pdf') => void;
}

Usage

import AnalyticsDashboard from '@/components/AnalyticsDashboard';

<AnalyticsDashboard
timeRange="7d"
locations={['District 1', 'District 3']}
refreshInterval={60000}
showRealtime={true}
showTrends={true}
showCorrelations={true}
onExport={(format) => handleExport(format)}
/>

Metrics

<div className="metrics-grid">
<MetricCard
title="Total Vehicles"
value={totalVehicles}
change={+5.2}
icon={<CarIcon />}
/>
<MetricCard
title="Average Speed"
value={avgSpeed}
unit="km/h"
change={-2.1}
icon={<SpeedIcon />}
/>
<MetricCard
title="Accidents Today"
value={accidentsToday}
severity="high"
icon={<AccidentIcon />}
/>
<MetricCard
title="Congestion Level"
value={congestionLevel}
max={1}
icon={<TrafficIcon />}
/>
</div>

RealTimeMonitor

Overview

Real-time monitoring panel with live updates and alerts.

Props

interface RealTimeMonitorProps {
cameras?: string[];
zones?: string[];
updateInterval?: number;
showAlerts?: boolean;
maxItems?: number;
}

Usage

import RealTimeMonitor from '@/components/RealTimeMonitor';

<RealTimeMonitor
cameras={['CAM_001', 'CAM_002']}
zones={['ZONE_001']}
updateInterval={5000}
showAlerts={true}
maxItems={50}
/>

Chart Components

LineChart

Overview

Responsive line chart for time-series data visualization.

Props

interface LineChartProps {
data: DataPoint[];
xAxis: string;
yAxis: string;
title?: string;
color?: string;
height?: number;
showGrid?: boolean;
showTooltip?: boolean;
}

interface DataPoint {
[key: string]: string | number;
}

Usage

import LineChart from '@/components/LineChart';

const trafficData = [
{ time: '00:00', vehicles: 120 },
{ time: '01:00', vehicles: 80 },
{ time: '02:00', vehicles: 60 },
// ...
];

<LineChart
data={trafficData}
xAxis="time"
yAxis="vehicles"
title="24-Hour Traffic Trend"
color="#1976d2"
height={400}
showGrid={true}
showTooltip={true}
/>

BarChart

Overview

Vertical or horizontal bar chart for categorical data comparison.

Props

interface BarChartProps {
data: DataPoint[];
xAxis: string;
yAxis: string;
title?: string;
orientation?: 'vertical' | 'horizontal';
colors?: string[];
height?: number;
}

Usage

import BarChart from '@/components/BarChart';

const locationData = [
{ location: 'District 1', accidents: 45 },
{ location: 'District 3', accidents: 32 },
{ location: 'District 5', accidents: 28 }
];

<BarChart
data={locationData}
xAxis="location"
yAxis="accidents"
title="Accidents by Location"
orientation="vertical"
colors={['#1976d2', '#388e3c', '#f57c00']}
height={300}
/>

PieChart

Overview

Pie or donut chart for proportional data visualization.

Props

interface PieChartProps {
data: { label: string; value: number; color?: string }[];
title?: string;
donut?: boolean;
showLabels?: boolean;
showLegend?: boolean;
height?: number;
}

Usage

import PieChart from '@/components/PieChart';

const severityData = [
{ label: 'Minor', value: 45, color: '#4caf50' },
{ label: 'Moderate', value: 32, color: '#ff9800' },
{ label: 'Severe', value: 18, color: '#f44336' },
{ label: 'Critical', value: 5, color: '#9c27b0' }
];

<PieChart
data={severityData}
title="Accident Severity Distribution"
donut={true}
showLabels={true}
showLegend={true}
height={300}
/>

AccidentFrequencyChart

Overview

Specialized chart showing accident frequency patterns by time, location, or other factors.

Props

interface AccidentFrequencyChartProps {
data: AccidentData[];
groupBy: 'hour' | 'day' | 'week' | 'month' | 'location';
showSeverity?: boolean;
height?: number;
}

Usage

import AccidentFrequencyChart from '@/components/AccidentFrequencyChart';

<AccidentFrequencyChart
data={accidentHistory}
groupBy="hour"
showSeverity={true}
height={400}
/>

Form Components

LocationPicker

Overview

Interactive map-based location picker with GPS detection and manual pin placement.

Props

interface LocationPickerProps {
defaultLocation?: [number, number];
onLocationChange: (coords: [number, number]) => void;
enableGPS?: boolean;
height?: number;
zoom?: number;
}

Usage

import LocationPicker from '@/components/LocationPicker';

const [location, setLocation] = useState<[number, number] | null>(null);

<LocationPicker
defaultLocation={[10.7769, 106.7009]}
onLocationChange={setLocation}
enableGPS={true}
height={400}
zoom={15}
/>

PhotoUpload

Overview

Photo upload component with camera capture, preview, and drag-and-drop support.

Props

interface PhotoUploadProps {
max?: number;
accept?: string;
onUpload: (files: File[]) => void;
enableCamera?: boolean;
maxSize?: number;
preview?: boolean;
}

Usage

import PhotoUpload from '@/components/PhotoUpload';

<PhotoUpload
max={3}
accept="image/*"
onUpload={(files) => handlePhotoUpload(files)}
enableCamera={true}
maxSize={5 * 1024 * 1024} // 5MB
preview={true}
/>

DateRangePicker

Overview

Date range selector with calendar interface and preset ranges.

Props

interface DateRangePickerProps {
value: [Date, Date];
onChange: (range: [Date, Date]) => void;
minDate?: Date;
maxDate?: Date;
presets?: PresetRange[];
}

interface PresetRange {
label: string;
range: [Date, Date];
}

Usage

import DateRangePicker from '@/components/DateRangePicker';

const [dateRange, setDateRange] = useState<[Date, Date]>([
new Date('2025-11-20'),
new Date('2025-12-05')
]);

const presets = [
{ label: 'Last 7 days', range: [subDays(new Date(), 7), new Date()] },
{ label: 'Last 30 days', range: [subDays(new Date(), 30), new Date()] },
{ label: 'This month', range: [startOfMonth(new Date()), endOfMonth(new Date())] }
];

<DateRangePicker
value={dateRange}
onChange={setDateRange}
presets={presets}
/>

Utility Components

Loading

Overview

Loading spinner with customizable size, color, and message.

Props

interface LoadingProps {
size?: 'small' | 'medium' | 'large';
color?: string;
message?: string;
fullscreen?: boolean;
}

Usage

import Loading from '@/components/Loading';

// Inline loading
<Loading size="medium" message="Loading data..." />

// Fullscreen loading
<Loading fullscreen={true} message="Please wait..." />

// Conditional rendering
{loading && <Loading />}

ErrorBoundary

Overview

React error boundary for graceful error handling.

Props

interface ErrorBoundaryProps {
children: React.ReactNode;
fallback?: React.ReactNode;
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}

Usage

import ErrorBoundary from '@/components/ErrorBoundary';

<ErrorBoundary
fallback={<ErrorFallback />}
onError={(error, errorInfo) => logError(error, errorInfo)}
>
<App />
</ErrorBoundary>

MetricCard

Overview

Card displaying key metrics with trend indicators and icons.

Props

interface MetricCardProps {
title: string;
value: number | string;
unit?: string;
change?: number;
icon?: React.ReactNode;
severity?: 'low' | 'medium' | 'high' | 'critical';
max?: number;
}

Usage

import MetricCard from '@/components/MetricCard';

<MetricCard
title="Average Speed"
value={35.5}
unit="km/h"
change={-2.1}
icon={<SpeedIcon />}
/>

<MetricCard
title="Congestion Level"
value={0.75}
max={1}
severity="high"
icon={<TrafficIcon />}
/>

StatusBadge

Overview

Badge component for displaying status with color coding.

Props

interface StatusBadgeProps {
status: string;
variant?: 'filled' | 'outlined' | 'text';
size?: 'small' | 'medium' | 'large';
color?: string;
}

Usage

import StatusBadge from '@/components/StatusBadge';

<StatusBadge status="active" variant="filled" color="#4caf50" />
<StatusBadge status="error" variant="outlined" color="#f44336" />
<StatusBadge status="pending" variant="text" size="small" />

Integration Examples

Complete Dashboard

import {
TrafficMap,
AnalyticsDashboard,
RealTimeMonitor,
FilterPanel,
Sidebar
} from '@/components';

function Dashboard() {
const [filters, setFilters] = useState({});

return (
<div className="dashboard">
<Sidebar>
<FilterPanel filters={filters} onFilterChange={setFilters} />
</Sidebar>

<main>
<TrafficMap {...filters} height="60vh" />
<AnalyticsDashboard timeRange="7d" />
<RealTimeMonitor />
</main>
</div>
);
}

Citizen Report Page

import { CitizenReportForm, CitizenReportMap } from '@/components';

function ReportPage() {
const [location, setLocation] = useState(null);

return (
<div className="report-page">
<CitizenReportMap onLocationSelect={setLocation} />
<CitizenReportForm defaultLocation={location} />
</div>
);
}

Performance Best Practices

Memoization

import { memo, useMemo } from 'react';

const TrafficMap = memo(({ cameras, accidents }) => {
const markers = useMemo(() =>
cameras.map(camera => createMarker(camera)),
[cameras]
);

return <Map>{markers}</Map>;
});

Virtualization

import { FixedSizeList } from 'react-window';

<FixedSizeList
height={600}
itemCount={cameras.length}
itemSize={50}
>
{({ index, style }) => (
<CameraItem camera={cameras[index]} style={style} />
)}
</FixedSizeList>

Lazy Loading

import { lazy, Suspense } from 'react';

const AnalyticsDashboard = lazy(() => import('@/components/AnalyticsDashboard'));

<Suspense fallback={<Loading />}>
<AnalyticsDashboard />
</Suspense>

Styling Guidelines

CSS Modules

import styles from './TrafficMap.module.css';

<div className={styles.mapContainer}>
<div className={styles.controls}>
{/* Controls */}
</div>
</div>

Styled Components

import styled from 'styled-components';

const MapContainer = styled.div`
width: 100%;
height: ${props => props.height};
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
`;

Tailwind CSS

<div className="w-full h-screen rounded-lg shadow-md">
<TrafficMap />
</div>

Testing

Component Tests

import { render, screen, fireEvent } from '@testing-library/react';
import TrafficMap from './TrafficMap';

describe('TrafficMap', () => {
it('renders map with markers', () => {
render(<TrafficMap cameras={mockCameras} />);
expect(screen.getAllByRole('marker')).toHaveLength(5);
});

it('calls onCameraClick when marker clicked', () => {
const handleClick = jest.fn();
render(<TrafficMap cameras={mockCameras} onCameraClick={handleClick} />);

fireEvent.click(screen.getByTestId('camera-CAM_001'));
expect(handleClick).toHaveBeenCalledWith('CAM_001');
});
});

Accessibility

ARIA Labels

<button
aria-label="Show camera details"
onClick={() => showDetails(camera.id)}
>
<CameraIcon />
</button>

Keyboard Navigation

<div
role="button"
tabIndex={0}
onKeyPress={(e) => e.key === 'Enter' && handleClick()}
onClick={handleClick}
>
{content}
</div>

License

MIT License - Copyright (c) 2025 UIP Contributors (Nguyễn Nhật Quang, Nguyễn Việt Hoàng, Nguyễn Đình Anh Tuấn)

See LICENSE for details.