Skip to main content

Frontend Overview

The HCMC Traffic Monitoring System frontend is a modern React 18 application built with TypeScript and Vite. It provides an interactive, real-time traffic visualization interface.

🎯 Key Features​

  • πŸ—ΊοΈ Interactive MapLibre GL Map - 1,000+ camera markers with real-time updates
  • πŸ“Š Analytics Dashboard - 7 chart types for data visualization
  • πŸ“± Citizen Reports - Mobile-friendly report submission
  • ⏱️ Time Machine - Historical data playback
  • πŸ€– AI Agent UI - Interactive panels for investigator, predictor, health advisor
  • πŸ”„ Real-time Updates - WebSocket connections for live data
  • 🎨 Modern UI - Tailwind CSS + Framer Motion animations

πŸ—οΈ Architecture​

apps/traffic-web-app/frontend/
β”œβ”€β”€ src/
β”‚ β”œβ”€β”€ components/ # 40+ React components
β”‚ β”‚ β”œβ”€β”€ TrafficMap.tsx
β”‚ β”‚ β”œβ”€β”€ Sidebar.tsx
β”‚ β”‚ β”œβ”€β”€ FilterPanel.tsx
β”‚ β”‚ β”œβ”€β”€ AnalyticsDashboard.tsx
β”‚ β”‚ β”œβ”€β”€ map/ # Map-specific components
β”‚ β”‚ β”‚ β”œβ”€β”€ CameraMarkers.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ AccidentMarkers.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ WeatherOverlay.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ AQIHeatmap.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ VehicleHeatmap.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ SpeedZones.tsx
β”‚ β”‚ β”‚ └── PatternZones.tsx
β”‚ β”‚ β”œβ”€β”€ charts/ # Recharts visualizations
β”‚ β”‚ β”‚ β”œβ”€β”€ LineChart.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ BarChart.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ PieChart.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ AreaChart.tsx
β”‚ β”‚ β”‚ └── RadarChart.tsx
β”‚ β”‚ β”œβ”€β”€ citizen/ # Citizen report system
β”‚ β”‚ β”‚ β”œβ”€β”€ CitizenReportForm.tsx (480 lines)
β”‚ β”‚ β”‚ β”œβ”€β”€ CitizenReportMap.tsx
β”‚ β”‚ β”‚ β”œβ”€β”€ CitizenReportMarkers.tsx
β”‚ β”‚ β”‚ └── CitizenReportList.tsx
β”‚ β”‚ β”œβ”€β”€ routing/ # Route planning
β”‚ β”‚ β”‚ β”œβ”€β”€ RoutePlanner.tsx (510 lines)
β”‚ β”‚ β”‚ β”œβ”€β”€ RouteVisualization.tsx
β”‚ β”‚ β”‚ └── CircleDrawTool.tsx
β”‚ β”‚ β”œβ”€β”€ timemachine/ # Historical playback
β”‚ β”‚ β”‚ β”œβ”€β”€ TimeMachine.tsx (450 lines)
β”‚ β”‚ β”‚ β”œβ”€β”€ CorrelationPanel.tsx (385 lines)
β”‚ β”‚ β”‚ └── TimelineSlider.tsx
β”‚ β”‚ └── agents/ # AI Agent interfaces
β”‚ β”‚ β”œβ”€β”€ InvestigatorPanel.tsx (420 lines)
β”‚ β”‚ β”œβ”€β”€ PredictiveTimeline.tsx (380 lines)
β”‚ β”‚ └── HealthAdvisorChat.tsx (350 lines)
β”‚ β”œβ”€β”€ pages/
β”‚ β”‚ β”œβ”€β”€ Dashboard.tsx (380 lines)
β”‚ β”‚ └── LandingPage.tsx (420 lines)
β”‚ β”œβ”€β”€ services/
β”‚ β”‚ β”œβ”€β”€ api.ts (280 lines) - REST API client
β”‚ β”‚ β”œβ”€β”€ websocket.ts (195 lines) - WebSocket client
β”‚ β”‚ └── citizenReportService.ts (220 lines)
β”‚ β”œβ”€β”€ store/
β”‚ β”‚ └── trafficStore.ts (340 lines) - Zustand state
β”‚ β”œβ”€β”€ types/
β”‚ β”‚ β”œβ”€β”€ camera.ts
β”‚ β”‚ β”œβ”€β”€ accident.ts
β”‚ β”‚ β”œβ”€β”€ weather.ts
β”‚ β”‚ └── index.ts (600+ lines total)
β”‚ β”œβ”€β”€ utils/
β”‚ β”‚ β”œβ”€β”€ formatters.ts
β”‚ β”‚ β”œβ”€β”€ validators.ts
β”‚ β”‚ └── constants.ts
β”‚ β”œβ”€β”€ App.tsx
β”‚ └── main.tsx
β”œβ”€β”€ public/
β”‚ β”œβ”€β”€ icons/
β”‚ └── images/
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ vite.config.ts
└── tailwind.config.js

πŸ“¦ Technology Stack​

Core​

  • React 18.2 - UI framework with concurrent rendering
  • TypeScript 5.2 - Type-safe JavaScript
  • Vite 5.0 - Fast build tool (HMR < 50ms)

Map & Visualization​

  • MapLibre GL JS 4.7 - Interactive vector tile maps (BSD-3-Clause)
  • react-map-gl 7.1 - React bindings for MapLibre (MIT)
  • Recharts 2.10 - Composable charting library
  • D3.js - Advanced visualizations

Migration Note (2025-12): Migrated from react-leaflet (Hippocratic) to react-map-gl + MapLibre GL JS for 100% MIT compatibility.

State Management​

  • Zustand 4.4 - Lightweight state management (< 1KB)
  • React Query - Server state synchronization

Styling​

  • Tailwind CSS 3.3 - Utility-first CSS
  • Framer Motion 10 - Animation library
  • CSS Modules - Component-scoped styles

Real-time​

  • Socket.io Client - WebSocket connections
  • Axios 1.6 - HTTP client with interceptors

UI Components​

  • Radix UI - Accessible component primitives
  • Lucide React - Icon library
  • React Hook Form - Form management

πŸ—ΊοΈ TrafficMap Component​

The core component that renders the interactive map.

// src/components/TrafficMap.tsx (450 lines)
// Using MIT-licensed react-map-gl + MapLibre GL JS
import { MapContainer } from './map';
import CameraMarkers from './map/CameraMarkers';
import AccidentMarkers from './map/AccidentMarkers';
import WeatherOverlay from './map/WeatherOverlay';

export default function TrafficMap() {
const { cameras, accidents, filters } = useTrafficStore();

return (
<MapContainer
center={[10.8231, 106.6297]} // Ho Chi Minh City
zoom={13}
className="h-full w-full"
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='&copy; OpenStreetMap contributors'
/>

{filters.showCameras && <CameraMarkers cameras={cameras} />}
{filters.showAccidents && <AccidentMarkers accidents={accidents} />}
{filters.showWeather && <WeatherOverlay />}
{filters.showAQI && <AQIHeatmap />}
{filters.showVehicles && <VehicleHeatmap />}
</MapContainer>
);
}

πŸ“Š Analytics Dashboard​

7 chart types for comprehensive data visualization.

// src/components/AnalyticsDashboard.tsx (680 lines)
export default function AnalyticsDashboard() {
const { trafficData, timeRange } = useTrafficStore();

return (
<div className="grid grid-cols-4 grid-rows-3 gap-4 p-4">
{/* Row 1: Time-series */}
<Card className="col-span-2">
<LineChart data={trafficData.hourly} />
</Card>
<Card>
<BarChart data={trafficData.byLocation} />
</Card>
<Card>
<PieChart data={trafficData.byType} />
</Card>

{/* Row 2: Comparisons */}
<Card className="col-span-2">
<AreaChart data={trafficData.trends} />
</Card>
<Card className="col-span-2">
<RadarChart data={trafficData.patterns} />
</Card>

{/* Row 3: Details */}
<Card className="col-span-3">
<ScatterPlot data={trafficData.correlations} />
</Card>
<Card>
<GaugeChart value={trafficData.congestionScore} />
</Card>
</div>
);
}

🎨 State Management (Zustand)​

Lightweight global state with TypeScript.

// src/store/trafficStore.ts (340 lines)
import { create } from 'zustand';

interface TrafficStore {
// State
cameras: Camera[];
accidents: Accident[];
weather: Weather | null;
filters: Filters;
selectedCamera: Camera | null;

// Actions
setCameras: (cameras: Camera[]) => void;
addAccident: (accident: Accident) => void;
updateFilters: (filters: Partial<Filters>) => void;
selectCamera: (camera: Camera | null) => void;
}

export const useTrafficStore = create<TrafficStore>((set) => ({
cameras: [],
accidents: [],
weather: null,
filters: {
showCameras: true,
showAccidents: true,
showWeather: false,
timeRange: '24h',
},
selectedCamera: null,

setCameras: (cameras) => set({ cameras }),
addAccident: (accident) =>
set((state) => ({
accidents: [...state.accidents, accident]
})),
updateFilters: (filters) =>
set((state) => ({
filters: { ...state.filters, ...filters }
})),
selectCamera: (camera) => set({ selectedCamera: camera }),
}));

πŸ”Œ WebSocket Integration​

Real-time updates via WebSocket.

// src/services/websocket.ts (195 lines)
import { io, Socket } from 'socket.io-client';
import { useTrafficStore } from '../store/trafficStore';

class WebSocketService {
private socket: Socket | null = null;

connect() {
this.socket = io('http://localhost:8001', {
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 5,
});

this.socket.on('connect', () => {
console.log('[WS] Connected');
});

this.socket.on('accident', (data: Accident) => {
console.log('[WS] New accident:', data);
useTrafficStore.getState().addAccident(data);
});

this.socket.on('camera_update', (data: Camera) => {
console.log('[WS] Camera update:', data);
// Update camera state
});

this.socket.on('disconnect', () => {
console.log('[WS] Disconnected');
});
}

disconnect() {
this.socket?.disconnect();
}

subscribe(event: string, callback: (data: any) => void) {
this.socket?.on(event, callback);
}
}

export const wsService = new WebSocketService();

πŸ“± Citizen Report System​

Mobile-friendly form for submitting traffic reports.

// src/components/citizen/CitizenReportForm.tsx (480 lines)
export default function CitizenReportForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const [location, setLocation] = useState<[number, number] | null>(null);
const [photo, setPhoto] = useState<File | null>(null);

const onSubmit = async (data) => {
const formData = new FormData();
formData.append('type', data.type);
formData.append('description', data.description);
formData.append('location', JSON.stringify(location));
if (photo) formData.append('photo', photo);

try {
const response = await citizenReportService.submit(formData);
toast.success('Report submitted! ID: ' + response.id);
} catch (error) {
toast.error('Failed to submit report');
}
};

return (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<Select {...register('type', { required: true })}>
<option value="accident">Accident</option>
<option value="congestion">Congestion</option>
<option value="pothole">Pothole</option>
<option value="flooding">Flooding</option>
</Select>

<MapPicker onLocationSelect={setLocation} />

<ImageUpload onChange={setPhoto} />

<Textarea
{...register('description', { required: true, minLength: 10 })}
placeholder="Describe the issue..."
/>

<Button type="submit">Submit Report</Button>
</form>
);
}

⏱️ Time Machine Feature​

Playback historical traffic data.

// src/components/timemachine/TimeMachine.tsx (450 lines)
export default function TimeMachine() {
const [playbackTime, setPlaybackTime] = useState(Date.now() - 3600000);
const [isPlaying, setIsPlaying] = useState(false);
const [speed, setSpeed] = useState(1);

useEffect(() => {
if (!isPlaying) return;

const interval = setInterval(() => {
setPlaybackTime((t) => t + 1000 * speed);
}, 1000);

return () => clearInterval(interval);
}, [isPlaying, speed]);

return (
<div className="fixed bottom-4 left-1/2 -translate-x-1/2 z-50">
<Card className="p-4 w-[600px]">
<div className="flex items-center gap-4">
<Button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? <Pause /> : <Play />}
</Button>

<Slider
value={[playbackTime]}
min={Date.now() - 86400000} // 24h ago
max={Date.now()}
step={60000} // 1 minute
onValueChange={([value]) => setPlaybackTime(value)}
className="flex-1"
/>

<Select value={speed} onChange={(e) => setSpeed(Number(e.target.value))}>
<option value={1}>1x</option>
<option value={2}>2x</option>
<option value={5}>5x</option>
<option value={10}>10x</option>
</Select>

<span className="text-sm">
{new Date(playbackTime).toLocaleString()}
</span>
</div>
</Card>
</div>
);
}

🎨 Styling with Tailwind CSS​

// Example: Styled components with Tailwind
<div className="flex flex-col gap-4 p-6 bg-white dark:bg-gray-900 rounded-lg shadow-lg">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
Traffic Overview
</h2>

<div className="grid grid-cols-3 gap-4">
<StatCard
icon={Camera}
label="Active Cameras"
value={1234}
className="bg-blue-50 dark:bg-blue-900"
/>
<StatCard
icon={AlertTriangle}
label="Active Accidents"
value={12}
className="bg-red-50 dark:bg-red-900"
/>
<StatCard
icon={Activity}
label="Congestion Level"
value="Medium"
className="bg-yellow-50 dark:bg-yellow-900"
/>
</div>
</div>

πŸ“– Component Documentation​

πŸš€ Development​

# Install dependencies
cd apps/traffic-web-app/frontend
npm install

# Start dev server (HMR enabled)
npm run dev

# Build for production
npm run build

# Preview production build
npm run preview

πŸ“Š Performance Metrics​

  • Initial Load: < 2s (with code splitting)
  • Time to Interactive (TTI): < 3s
  • Lighthouse Score: 95+ (Performance, Accessibility, Best Practices)
  • Bundle Size: 450KB gzipped (with tree-shaking)

Next: Explore TrafficMap Component details.