Files
ocean-cleanup/src/components/analytics/TimeSeriesChart.tsx
Paul Bailey d91a13da60 feat: implement analytics endpoints (task 28)
- Created analytics service with summary, trends, comparison, and aggregated data methods
- Implemented 5 analytics endpoints: summary, trends, comparison, aggregated, and CSV export
- Added time series data with day/week/month interval support
- Implemented period-over-period comparison metrics with percentage changes
- Added CSV export functionality using json2csv
- Updated frontend analytics service to unwrap API responses
- Fixed chart components to work with new data structure (reportCount, eventCount, litterCollected)
- Updated TypeScript types to match backend API structure
- All endpoints tested and verified working

Requirements satisfied: 5.1, 5.2, 5.3, 5.4, 5.5
2025-11-17 00:13:07 +00:00

94 lines
2.8 KiB
TypeScript

import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
import type { TimeSeriesDataPoint } from '@/types';
interface TimeSeriesChartProps {
data: TimeSeriesDataPoint[];
loading?: boolean;
}
export function TimeSeriesChart({ data, loading }: TimeSeriesChartProps) {
if (loading) {
return (
<div className="bg-white rounded-lg shadow p-6">
<div className="animate-pulse">
<div className="h-6 bg-gray-200 rounded w-1/3 mb-4" />
<div className="h-64 bg-gray-200 rounded" />
</div>
</div>
);
}
if (!data || data.length === 0) {
return (
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Report Trends</h3>
<div className="h-64 flex items-center justify-center text-gray-500">
No data available for the selected period
</div>
</div>
);
}
return (
<div className="bg-white rounded-lg shadow p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Report Trends Over Time</h3>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#e5e7eb" />
<XAxis
dataKey="date"
stroke="#6b7280"
style={{ fontSize: '12px' }}
/>
<YAxis
stroke="#6b7280"
style={{ fontSize: '12px' }}
/>
<Tooltip
contentStyle={{
backgroundColor: '#fff',
border: '1px solid #e5e7eb',
borderRadius: '0.5rem',
padding: '0.75rem'
}}
/>
<Legend
wrapperStyle={{ paddingTop: '1rem' }}
/>
<Line
type="monotone"
dataKey="reportCount"
stroke="#3b82f6"
strokeWidth={2}
name="Reports"
dot={{ fill: '#3b82f6', r: 4 }}
activeDot={{ r: 6 }}
/>
{data.some(d => d.eventCount !== undefined) && (
<Line
type="monotone"
dataKey="eventCount"
stroke="#10b981"
strokeWidth={2}
name="Events"
dot={{ fill: '#10b981', r: 4 }}
activeDot={{ r: 6 }}
/>
)}
{data.some(d => d.litterCollected > 0) && (
<Line
type="monotone"
dataKey="litterCollected"
stroke="#f59e0b"
strokeWidth={2}
name="Litter Collected (kg)"
dot={{ fill: '#f59e0b', r: 4 }}
activeDot={{ r: 6 }}
/>
)}
</LineChart>
</ResponsiveContainer>
</div>
);
}