From c70fc77fa7ce47665887c93f602b6c196ad722cc Mon Sep 17 00:00:00 2001 From: Paul Bailey Date: Sun, 16 Nov 2025 22:51:10 +0000 Subject: [PATCH] Implement Mark as Cleaned functionality - Add showCleaned filter to FilterState type - Update MapView to show green markers with checkmark for cleaned reports - Add cleaned status filter toggle in MapFilters component - Display cleaned information in ReportDetailModal - Hide action buttons for already cleaned reports - Wire up markAsCleaned functionality in MapPage - Backend endpoint and database fields already in place --- .kiro/specs/beach-litter-mapping/tasks.md | 2 +- src/components/map/MapFilters.tsx | 22 +++++++++++++++++++ src/components/map/MapView.tsx | 23 +++++++++++++++----- src/components/report/ReportDetailModal.tsx | 24 +++++++++++++++++++-- src/types/index.ts | 1 + 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/.kiro/specs/beach-litter-mapping/tasks.md b/.kiro/specs/beach-litter-mapping/tasks.md index ac7c9e9..6e0c300 100644 --- a/.kiro/specs/beach-litter-mapping/tasks.md +++ b/.kiro/specs/beach-litter-mapping/tasks.md @@ -145,7 +145,7 @@ ## Cleanup Tracking System -- [ ] 18. Implement "Mark as Cleaned" functionality +- [-] 18. Implement "Mark as Cleaned" functionality - Add `cleaned_at` timestamp field to reports table - Add `cleaned_by_user_id` field to track who marked it as cleaned - Create POST /api/reports/:id/mark-cleaned backend endpoint diff --git a/src/components/map/MapFilters.tsx b/src/components/map/MapFilters.tsx index fb06744..ddf0481 100644 --- a/src/components/map/MapFilters.tsx +++ b/src/components/map/MapFilters.tsx @@ -218,6 +218,28 @@ export function MapFilters({ )} + {/* Show Cleaned Reports Toggle */} +
+ + +
+ {/* Hotspot Legend */} {showHotspots && onToggleHotspots && (
diff --git a/src/components/map/MapView.tsx b/src/components/map/MapView.tsx index a6c71c5..b9edf57 100644 --- a/src/components/map/MapView.tsx +++ b/src/components/map/MapView.tsx @@ -152,6 +152,11 @@ export function MapView({ } } + // Filter by cleaned status + if (filters.showCleaned === false && report.cleanedAt) { + return false; + } + return true; }); }; @@ -181,7 +186,8 @@ export function MapView({ // Add report markers filteredReports.forEach((report) => { - const icon = createCustomMarkerIcon(report.litterType, report.quantity); + const isCleaned = !!report.cleanedAt; + const icon = createCustomMarkerIcon(report.litterType, report.quantity, isCleaned); const marker = L.marker([report.latitude, report.longitude], { icon }); // Add popup with report details @@ -204,7 +210,12 @@ export function MapView({

${getLitterTypeLabel(report.litterType)}

Quantity: ${report.quantity}

Status: ${report.verificationStatus}

-

Date: ${new Date(report.createdAt).toLocaleDateString()}

+ ${isCleaned && report.cleanedAt ? ` +

✓ Cleaned up

+

Cleaned: ${new Date(report.cleanedAt).toLocaleDateString()}

+ ` : ` +

Date: ${new Date(report.createdAt).toLocaleDateString()}

+ `} ${report.description ? `

${report.description}

` : ''}
`); @@ -503,7 +514,7 @@ export function MapView({ } // Helper function to create custom marker icons based on litter type -function createCustomMarkerIcon(litterType: string, quantity: string): L.DivIcon { +function createCustomMarkerIcon(litterType: string, quantity: string, isCleaned: boolean = false): L.DivIcon { const colors = { plastic: '#3B82F6', // blue metal: '#6B7280', // gray @@ -520,7 +531,8 @@ function createCustomMarkerIcon(litterType: string, quantity: string): L.DivIcon severe: 36, }; - const color = colors[litterType as keyof typeof colors] || colors.other; + // Use green color for cleaned reports, otherwise use litter type color + const color = isCleaned ? '#10B981' : (colors[litterType as keyof typeof colors] || colors.other); const size = sizes[quantity as keyof typeof sizes] || sizes.moderate; return L.divIcon({ @@ -534,6 +546,7 @@ function createCustomMarkerIcon(litterType: string, quantity: string): L.DivIcon border-radius: 50% 50% 50% 0; transform: rotate(-45deg); box-shadow: 0 2px 4px rgba(0,0,0,0.3); + ${isCleaned ? 'opacity: 0.7;' : ''} ">
- ${getLitterTypeIcon(litterType)} + ${isCleaned ? '✓' : getLitterTypeIcon(litterType)}
`, diff --git a/src/components/report/ReportDetailModal.tsx b/src/components/report/ReportDetailModal.tsx index 69ef6f1..b39213a 100644 --- a/src/components/report/ReportDetailModal.tsx +++ b/src/components/report/ReportDetailModal.tsx @@ -211,6 +211,26 @@ export function ReportDetailModal({ report, isOpen, onClose, onVerify, onDispute )} + {/* Cleaned Status */} + {report.cleanedAt && ( +
+
+ + + +

Cleaned Up

+
+

+ This litter was cleaned up on {format(new Date(report.cleanedAt), 'PPP')} +

+ {report.cleanedByUserId && ( +

+ Marked as cleaned by a community member +

+ )} +
+ )} + {/* Description */} {report.description && (
@@ -358,7 +378,7 @@ export function ReportDetailModal({ report, isOpen, onClose, onVerify, onDispute
{/* Action Buttons */} - {(onVerify || onDispute || onMarkCleaned) && ( + {(onVerify || onDispute || onMarkCleaned) && !report.cleanedAt && (
{(onVerify || onDispute) && (

@@ -398,7 +418,7 @@ export function ReportDetailModal({ report, isOpen, onClose, onVerify, onDispute - Cleaned Up + Mark as Cleaned )}

diff --git a/src/types/index.ts b/src/types/index.ts index fd49fc8..3c4a119 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -141,6 +141,7 @@ export interface FilterState { litterTypes?: LitterType[]; quantities?: QuantityLevel[]; verificationStatus?: VerificationStatus[]; + showCleaned?: boolean; } // Analytics types