feat: implement report submission and display (task 9)
- Created reportService for API interactions (upload, create, get, verify, dispute) - Built ReportDetailModal component with photo gallery and verification UI - Updated ReportForm with actual API submission and progress tracking - Added error handling and success states - Created ReportDetailDemo page to showcase modal functionality - Added route for demo page at /demo/report-detail - Exported services through index file for cleaner imports - Documented implementation in SUBMISSION_IMPLEMENTATION.md Requirements fulfilled: 1.8, 7.4
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { PhotoUpload, type PhotoMetadata } from './PhotoUpload';
|
||||
import { LocationSelector } from './LocationSelector';
|
||||
import { reportService } from '@/services/reportService';
|
||||
import type { LitterType, QuantityLevel, LocationSource, Coordinates } from '@/types';
|
||||
|
||||
interface EnvironmentalConcern {
|
||||
@@ -53,6 +55,7 @@ const CONCERN_TYPES = [
|
||||
];
|
||||
|
||||
export function ReportForm() {
|
||||
const navigate = useNavigate();
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [formData, setFormData] = useState<FormData>({
|
||||
photos: [],
|
||||
@@ -65,6 +68,8 @@ export function ReportForm() {
|
||||
});
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [submitError, setSubmitError] = useState<string | null>(null);
|
||||
const [uploadProgress, setUploadProgress] = useState<string>('');
|
||||
|
||||
const totalSteps = 4;
|
||||
|
||||
@@ -130,31 +135,70 @@ export function ReportForm() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!formData.location || !formData.locationSource || !formData.litterType || !formData.quantity) {
|
||||
setSubmitError('Please complete all required fields');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
setSubmitError(null);
|
||||
|
||||
try {
|
||||
// TODO: Implement API submission in future task
|
||||
console.log('Submitting report:', formData);
|
||||
// Step 1: Upload photos
|
||||
setUploadProgress('Uploading photos...');
|
||||
const photoUrls: string[] = [];
|
||||
|
||||
// Simulate API call
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
for (let i = 0; i < formData.photos.length; i++) {
|
||||
const photo = formData.photos[i];
|
||||
if (!photo) continue;
|
||||
|
||||
setUploadProgress(`Uploading photo ${i + 1} of ${formData.photos.length}...`);
|
||||
const uploadResult = await reportService.uploadPhoto(photo.file);
|
||||
photoUrls.push(uploadResult.url);
|
||||
}
|
||||
|
||||
// Step 2: Submit report
|
||||
setUploadProgress('Creating report...');
|
||||
const reportData = {
|
||||
latitude: formData.location!.latitude,
|
||||
longitude: formData.location!.longitude,
|
||||
locationSource: formData.locationSource!,
|
||||
litterType: formData.litterType as LitterType,
|
||||
quantity: formData.quantity as QuantityLevel,
|
||||
description: formData.description || undefined,
|
||||
photoUrls,
|
||||
photoTimestamp: formData.photos[0]?.timestamp?.toISOString(),
|
||||
environmentalConcerns: formData.environmentalConcerns.length > 0
|
||||
? formData.environmentalConcerns.filter(c => c.type && c.description)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
await reportService.createReport(reportData);
|
||||
|
||||
alert('Report submitted successfully!');
|
||||
setUploadProgress('Report submitted successfully!');
|
||||
|
||||
// Reset form
|
||||
setFormData({
|
||||
photos: [],
|
||||
location: null,
|
||||
locationSource: null,
|
||||
litterType: '',
|
||||
quantity: '',
|
||||
description: '',
|
||||
environmentalConcerns: [],
|
||||
});
|
||||
setCurrentStep(1);
|
||||
// Show success message
|
||||
setTimeout(() => {
|
||||
// Reset form
|
||||
setFormData({
|
||||
photos: [],
|
||||
location: null,
|
||||
locationSource: null,
|
||||
litterType: '',
|
||||
quantity: '',
|
||||
description: '',
|
||||
environmentalConcerns: [],
|
||||
});
|
||||
setCurrentStep(1);
|
||||
setUploadProgress('');
|
||||
|
||||
// Navigate to map page to see the new report
|
||||
navigate('/map');
|
||||
}, 1500);
|
||||
} catch (error) {
|
||||
console.error('Error submitting report:', error);
|
||||
alert('Failed to submit report. Please try again.');
|
||||
setSubmitError(error instanceof Error ? error.message : 'Failed to submit report. Please try again.');
|
||||
setUploadProgress('');
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
@@ -498,6 +542,30 @@ export function ReportForm() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Error Message */}
|
||||
{submitError && (
|
||||
<div className="p-4 bg-red-50 border border-red-200 rounded-lg flex items-start gap-3">
|
||||
<svg className="w-5 h-5 text-red-600 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" />
|
||||
</svg>
|
||||
<div>
|
||||
<h3 className="font-medium text-red-900">Submission Error</h3>
|
||||
<p className="text-sm text-red-700 mt-1">{submitError}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Upload Progress */}
|
||||
{uploadProgress && (
|
||||
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg flex items-center gap-3">
|
||||
<svg className="animate-spin h-5 w-5 text-blue-600" fill="none" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
<p className="text-sm text-blue-900 font-medium">{uploadProgress}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Photos Summary */}
|
||||
<div className="border-t border-gray-200 pt-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-3">Photos</h3>
|
||||
|
||||
Reference in New Issue
Block a user