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
This commit is contained in:
@@ -46,6 +46,13 @@ export function AnalyticsPage() {
|
||||
totalEvents: 0,
|
||||
totalUsers: 0,
|
||||
totalLitterCollected: 0,
|
||||
reportsByType: {},
|
||||
reportsByQuantity: {},
|
||||
verifiedReports: 0,
|
||||
cleanedReports: 0,
|
||||
activeUsers: 0,
|
||||
upcomingEvents: 0,
|
||||
completedEvents: 0,
|
||||
});
|
||||
setError('Analytics API not yet implemented. Showing placeholder data.');
|
||||
} finally {
|
||||
@@ -85,29 +92,30 @@ export function AnalyticsPage() {
|
||||
date.setDate(date.getDate() + Math.floor(i * daysDiff / dataPoints));
|
||||
mockTimeSeries.push({
|
||||
date: date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
|
||||
reports: Math.floor(Math.random() * 20) + 5,
|
||||
events: Math.floor(Math.random() * 5),
|
||||
reportCount: Math.floor(Math.random() * 20) + 5,
|
||||
eventCount: Math.floor(Math.random() * 5),
|
||||
litterCollected: Math.floor(Math.random() * 50) + 10,
|
||||
});
|
||||
}
|
||||
setTimeSeriesData(mockTimeSeries);
|
||||
|
||||
// Generate mock litter type distribution
|
||||
const mockLitterTypes: LitterTypeDistribution[] = [
|
||||
{ type: 'plastic', count: 45, percentage: 45 },
|
||||
{ type: 'metal', count: 15, percentage: 15 },
|
||||
{ type: 'glass', count: 12, percentage: 12 },
|
||||
{ type: 'organic', count: 10, percentage: 10 },
|
||||
{ type: 'hazardous', count: 8, percentage: 8 },
|
||||
{ type: 'other', count: 10, percentage: 10 },
|
||||
{ type: 'plastic', count: 45 },
|
||||
{ type: 'metal', count: 15 },
|
||||
{ type: 'glass', count: 12 },
|
||||
{ type: 'organic', count: 10 },
|
||||
{ type: 'hazardous', count: 8 },
|
||||
{ type: 'other', count: 10 },
|
||||
];
|
||||
setLitterTypeData(mockLitterTypes);
|
||||
|
||||
// Generate mock quantity aggregation
|
||||
const mockQuantities: QuantityAggregation[] = [
|
||||
{ quantity: 'minimal', count: 30, percentage: 30 },
|
||||
{ quantity: 'moderate', count: 40, percentage: 40 },
|
||||
{ quantity: 'significant', count: 20, percentage: 20 },
|
||||
{ quantity: 'severe', count: 10, percentage: 10 },
|
||||
{ level: 'minimal', count: 30 },
|
||||
{ level: 'moderate', count: 40 },
|
||||
{ level: 'significant', count: 20 },
|
||||
{ level: 'severe', count: 10 },
|
||||
];
|
||||
setQuantityData(mockQuantities);
|
||||
} finally {
|
||||
@@ -211,14 +219,6 @@ export function AnalyticsPage() {
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||
</svg>
|
||||
}
|
||||
trend={
|
||||
summary.periodComparison
|
||||
? {
|
||||
value: summary.periodComparison.reports,
|
||||
isPositive: summary.periodComparison.reports > 0,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
subtitle="Litter reports submitted"
|
||||
/>
|
||||
|
||||
@@ -230,33 +230,17 @@ export function AnalyticsPage() {
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
}
|
||||
trend={
|
||||
summary.periodComparison
|
||||
? {
|
||||
value: summary.periodComparison.events,
|
||||
isPositive: summary.periodComparison.events > 0,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
subtitle="Cleanup events organized"
|
||||
/>
|
||||
|
||||
<StatCard
|
||||
title="Active Users"
|
||||
value={summary.totalUsers.toLocaleString()}
|
||||
value={summary.activeUsers.toLocaleString()}
|
||||
icon={
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||
</svg>
|
||||
}
|
||||
trend={
|
||||
summary.periodComparison
|
||||
? {
|
||||
value: summary.periodComparison.users,
|
||||
isPositive: summary.periodComparison.users > 0,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
subtitle="Contributing to the platform"
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user