Link map items with events - Display cleanup events on interactive map

- Added event fetching in MapPage component
- Events now display on map with custom markers (calendar emoji)
- Enhanced event popup with detailed information (date, time, participants, litter collected)
- Added event visibility toggle in MapFilters
- Events use different colors based on status (upcoming=amber, completed=green, cancelled=gray)
- Sample event data provided as fallback
- Fixed date filter type conversion for API calls
This commit is contained in:
2025-11-16 21:05:23 +00:00
parent 7153e55f4d
commit 91ecbd2443
3 changed files with 179 additions and 9 deletions

View File

@@ -2,7 +2,8 @@ import { useState, useEffect } from 'react';
import { MapView, MapFilters } from '@/components/map';
import { analyticsService } from '@/services/analyticsService';
import { reportService } from '@/services/reportService';
import type { Report, FilterState, HotspotWithSeverity } from '@/types';
import { eventService } from '@/services/eventService';
import type { Report, FilterState, HotspotWithSeverity, CleanupEvent } from '@/types';
/**
* Map page - Interactive map displaying litter reports and cleanup events
@@ -13,10 +14,13 @@ export function MapPage() {
const [showClusters, setShowClusters] = useState(true);
const [showHeatmap, setShowHeatmap] = useState(false);
const [showHotspots, setShowHotspots] = useState(false);
const [showEvents, setShowEvents] = useState(true);
const [hotspots, setHotspots] = useState<HotspotWithSeverity[]>([]);
const [isLoadingHotspots, setIsLoadingHotspots] = useState(false);
const [reports, setReports] = useState<Report[]>([]);
const [isLoadingReports, setIsLoadingReports] = useState(true);
const [events, setEvents] = useState<CleanupEvent[]>([]);
const [isLoadingEvents, setIsLoadingEvents] = useState(true);
const [modalImageUrl, setModalImageUrl] = useState<string | null>(null);
// Fetch reports from API
@@ -24,7 +28,25 @@ export function MapPage() {
const fetchReports = async () => {
try {
setIsLoadingReports(true);
const data = await reportService.getReports(filters);
// Convert FilterState to API format (Date objects to ISO strings)
const apiFilters: Parameters<typeof reportService.getReports>[0] = filters.dateRange
? {
dateRange: {
start: filters.dateRange.start.toISOString(),
end: filters.dateRange.end.toISOString(),
},
litterTypes: filters.litterTypes,
quantities: filters.quantities,
verificationStatus: filters.verificationStatus,
}
: {
litterTypes: filters.litterTypes,
quantities: filters.quantities,
verificationStatus: filters.verificationStatus,
};
const data = await reportService.getReports(apiFilters);
setReports(data);
} catch (error) {
console.error('Failed to fetch reports:', error);
@@ -38,6 +60,26 @@ export function MapPage() {
fetchReports();
}, [filters]);
// Fetch events from API
useEffect(() => {
const fetchEvents = async () => {
try {
setIsLoadingEvents(true);
// Fetch upcoming and completed events to show on map
const data = await eventService.getEvents();
setEvents(data);
} catch (error) {
console.error('Failed to fetch events:', error);
// Fall back to sample data on error
setEvents(sampleEvents);
} finally {
setIsLoadingEvents(false);
}
};
fetchEvents();
}, []);
// Set up global function for opening image modal
useEffect(() => {
(window as any).openImageModal = (url: string) => {
@@ -49,6 +91,55 @@ export function MapPage() {
}, []);
// Sample data for fallback
const sampleEvents: CleanupEvent[] = [
{
id: 'event1',
organizerId: 'user1',
title: 'Sandymount Beach Cleanup',
description: 'Join us for a community cleanup at Sandymount Beach',
latitude: 53.3333,
longitude: -6.2167,
locationName: 'Sandymount Beach, Dublin',
scheduledDate: new Date('2024-02-15T10:00:00'),
duration: 120,
status: 'upcoming',
participantCount: 15,
createdAt: new Date('2024-01-10'),
updatedAt: new Date('2024-01-10'),
},
{
id: 'event2',
organizerId: 'user2',
title: 'Galway Bay Cleanup',
description: 'Monthly cleanup event at Salthill Beach',
latitude: 53.2577,
longitude: -9.0810,
locationName: 'Salthill Beach, Galway',
scheduledDate: new Date('2024-02-20T14:00:00'),
duration: 90,
status: 'upcoming',
participantCount: 8,
createdAt: new Date('2024-01-12'),
updatedAt: new Date('2024-01-12'),
},
{
id: 'event3',
organizerId: 'user3',
title: 'Cork Harbor Cleanup - Completed',
description: 'Successful cleanup event with great turnout',
latitude: 51.8503,
longitude: -8.2944,
locationName: 'Cork Harbor',
scheduledDate: new Date('2024-01-10T11:00:00'),
duration: 150,
status: 'completed',
participantCount: 25,
litterCollected: 45.5,
createdAt: new Date('2024-01-05'),
updatedAt: new Date('2024-01-10'),
},
];
const sampleReports: Report[] = [
{
id: '1',
@@ -165,6 +256,10 @@ export function MapPage() {
setShowHotspots(!showHotspots);
};
const handleToggleEvents = () => {
setShowEvents(!showEvents);
};
// Fetch hotspots when hotspot view is enabled
useEffect(() => {
if (showHotspots && hotspots.length === 0) {
@@ -229,16 +324,19 @@ export function MapPage() {
<div className="flex-1 p-4">
<div className="h-full bg-white rounded-lg shadow-lg overflow-hidden relative">
{isLoadingReports ? (
{isLoadingReports || isLoadingEvents ? (
<div className="absolute inset-0 flex items-center justify-center bg-gray-50">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading reports...</p>
<p className="mt-4 text-gray-600">
Loading {isLoadingReports && isLoadingEvents ? 'map data' : isLoadingReports ? 'reports' : 'events'}...
</p>
</div>
</div>
) : (
<MapView
reports={reports}
events={showEvents ? events : []}
hotspots={hotspots}
filters={filters}
showClusters={showClusters}
@@ -256,6 +354,8 @@ export function MapPage() {
showClusters={showClusters}
onToggleHeatmap={handleToggleHeatmap}
showHeatmap={showHeatmap}
onToggleEvents={handleToggleEvents}
showEvents={showEvents}
onToggleHotspots={handleToggleHotspots}
showHotspots={showHotspots}
isLoadingHotspots={isLoadingHotspots}