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:
2025-11-17 00:13:07 +00:00
parent ea08bf4289
commit d91a13da60
16 changed files with 1051 additions and 78 deletions

View File

@@ -15,13 +15,38 @@ class AnalyticsService {
}
const queryString = params.toString();
return apiClient.get<AnalyticsSummary>(`/analytics/summary${queryString ? `?${queryString}` : ''}`);
const response = await apiClient.get<{ success: boolean; data: AnalyticsSummary }>(`/analytics/summary${queryString ? `?${queryString}` : ''}`);
return response.data;
}
/**
* Get time series data for report trends
*/
async getTimeSeries(dateRange?: { start: string; end: string }): Promise<TimeSeriesDataPoint[]> {
async getTimeSeries(
dateRange?: { start: string; end: string },
interval: 'day' | 'week' | 'month' = 'day'
): Promise<TimeSeriesDataPoint[]> {
const params = new URLSearchParams();
if (dateRange) {
params.append('startDate', dateRange.start);
params.append('endDate', dateRange.end);
}
params.append('interval', interval);
const queryString = params.toString();
const response = await apiClient.get<{ success: boolean; data: TimeSeriesDataPoint[] }>(`/analytics/trends${queryString ? `?${queryString}` : ''}`);
return response.data;
}
/**
* Get aggregated data (litter types, quantities, verification status)
*/
async getAggregatedData(dateRange?: { start: string; end: string }): Promise<{
litterTypes: LitterTypeDistribution[];
quantities: QuantityAggregation[];
verificationStatus: Array<{ status: string; count: number }>;
}> {
const params = new URLSearchParams();
if (dateRange) {
@@ -30,37 +55,56 @@ class AnalyticsService {
}
const queryString = params.toString();
return apiClient.get<TimeSeriesDataPoint[]>(`/analytics/trends${queryString ? `?${queryString}` : ''}`);
const response = await apiClient.get<{ success: boolean; data: {
litterTypes: LitterTypeDistribution[];
quantities: QuantityAggregation[];
verificationStatus: Array<{ status: string; count: number }>;
} }>(`/analytics/aggregated${queryString ? `?${queryString}` : ''}`);
return response.data;
}
/**
* Get litter type distribution data
*/
async getLitterTypeDistribution(dateRange?: { start: string; end: string }): Promise<LitterTypeDistribution[]> {
const params = new URLSearchParams();
if (dateRange) {
params.append('startDate', dateRange.start);
params.append('endDate', dateRange.end);
}
const queryString = params.toString();
return apiClient.get<LitterTypeDistribution[]>(`/analytics/litter-types${queryString ? `?${queryString}` : ''}`);
const data = await this.getAggregatedData(dateRange);
return data.litterTypes;
}
/**
* Get quantity estimation aggregation data
*/
async getQuantityAggregation(dateRange?: { start: string; end: string }): Promise<QuantityAggregation[]> {
const params = new URLSearchParams();
if (dateRange) {
params.append('startDate', dateRange.start);
params.append('endDate', dateRange.end);
}
const data = await this.getAggregatedData(dateRange);
return data.quantities;
}
const queryString = params.toString();
return apiClient.get<QuantityAggregation[]>(`/analytics/quantities${queryString ? `?${queryString}` : ''}`);
/**
* Get comparison metrics for period-over-period analysis
*/
async getComparisonMetrics(
currentStart: string,
currentEnd: string,
previousStart: string,
previousEnd: string
): Promise<{
current: { reports: number; events: number; users: number };
previous: { reports: number; events: number; users: number };
percentageChange: { reports: number; events: number; users: number };
}> {
const params = new URLSearchParams({
currentStart,
currentEnd,
previousStart,
previousEnd,
});
const response = await apiClient.get<{ success: boolean; data: {
current: { reports: number; events: number; users: number };
previous: { reports: number; events: number; users: number };
percentageChange: { reports: number; events: number; users: number };
} }>(`/analytics/comparison?${params.toString()}`);
return response.data;
}
/**