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:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user