Implement report verification interface with audit log
- Add verify/dispute endpoints to backend API - Create verification service methods with auto-hide logic for 3+ disputes - Update ReportDetailModal with verification buttons and comment forms - Add verification audit log display showing history of verifications/disputes - Implement auto-hide warning for disputed reports - Update MapPage to handle verification actions and refresh data - Add verification counts to report responses - Filter out disputed reports from public view by default - Add verification test script Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '@clerk/clerk-react';
|
||||
import { MapView, MapFilters } from '@/components/map';
|
||||
import { ReportDetailModal } from '@/components/report';
|
||||
import { analyticsService } from '@/services/analyticsService';
|
||||
import { reportService } from '@/services/reportService';
|
||||
import { eventService } from '@/services/eventService';
|
||||
import type { Report, FilterState, HotspotWithSeverity, CleanupEvent } from '@/types';
|
||||
import type { Report, FilterState, HotspotWithSeverity, CleanupEvent, ReportResponseDTO } from '@/types';
|
||||
|
||||
/**
|
||||
* Map page - Interactive map displaying litter reports and cleanup events
|
||||
*/
|
||||
export function MapPage() {
|
||||
const { getToken, isSignedIn } = useAuth();
|
||||
const [selectedReportId, setSelectedReportId] = useState<string | null>(null);
|
||||
const [selectedReport, setSelectedReport] = useState<ReportResponseDTO | null>(null);
|
||||
const [isLoadingReport, setIsLoadingReport] = useState(false);
|
||||
const [filters, setFilters] = useState<FilterState>({});
|
||||
const [showClusters, setShowClusters] = useState(true);
|
||||
const [showHeatmap, setShowHeatmap] = useState(false);
|
||||
@@ -23,6 +28,11 @@ export function MapPage() {
|
||||
const [isLoadingEvents, setIsLoadingEvents] = useState(true);
|
||||
const [modalImageUrl, setModalImageUrl] = useState<string | null>(null);
|
||||
|
||||
// Set up token getter for report service
|
||||
useEffect(() => {
|
||||
reportService.setTokenGetter(getToken);
|
||||
}, [getToken]);
|
||||
|
||||
// Fetch reports from API
|
||||
useEffect(() => {
|
||||
const fetchReports = async () => {
|
||||
@@ -233,10 +243,83 @@ export function MapPage() {
|
||||
},
|
||||
];
|
||||
|
||||
const handleMarkerClick = (reportId: string) => {
|
||||
const handleMarkerClick = async (reportId: string) => {
|
||||
setSelectedReportId(reportId);
|
||||
console.log('Report clicked:', reportId);
|
||||
// In later tasks, this will open a modal with full report details
|
||||
setIsLoadingReport(true);
|
||||
|
||||
try {
|
||||
const report = await reportService.getReport(reportId);
|
||||
setSelectedReport(report);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch report details:', error);
|
||||
} finally {
|
||||
setIsLoadingReport(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setSelectedReportId(null);
|
||||
setSelectedReport(null);
|
||||
};
|
||||
|
||||
const handleVerify = async (reportId: string, comment?: string) => {
|
||||
try {
|
||||
await reportService.verifyReport(reportId, comment);
|
||||
// Refresh the report details
|
||||
const updatedReport = await reportService.getReport(reportId);
|
||||
setSelectedReport(updatedReport);
|
||||
// Refresh the reports list
|
||||
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: any) {
|
||||
console.error('Failed to verify report:', error);
|
||||
alert(error.message || 'Failed to verify report. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDispute = async (reportId: string, comment?: string) => {
|
||||
try {
|
||||
await reportService.disputeReport(reportId, comment);
|
||||
// Refresh the report details
|
||||
const updatedReport = await reportService.getReport(reportId);
|
||||
setSelectedReport(updatedReport);
|
||||
// Refresh the reports list
|
||||
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: any) {
|
||||
console.error('Failed to dispute report:', error);
|
||||
alert(error.message || 'Failed to dispute report. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
const handleMapClick = (lat: number, lng: number) => {
|
||||
@@ -363,17 +446,24 @@ export function MapPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedReportId && (
|
||||
<div className="fixed bottom-4 left-4 bg-white p-4 rounded-lg shadow-lg z-[1000]">
|
||||
<p className="text-sm text-gray-600">
|
||||
Selected Report ID: {selectedReportId}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => setSelectedReportId(null)}
|
||||
className="mt-2 text-xs text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
{/* Report Detail Modal */}
|
||||
{selectedReport && (
|
||||
<ReportDetailModal
|
||||
report={selectedReport}
|
||||
isOpen={!!selectedReport}
|
||||
onClose={handleCloseModal}
|
||||
onVerify={isSignedIn ? handleVerify : undefined}
|
||||
onDispute={isSignedIn ? handleDispute : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Loading indicator for report details */}
|
||||
{isLoadingReport && selectedReportId && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white p-6 rounded-lg shadow-xl">
|
||||
<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 report details...</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user