Add comprehensive analytics system with: - Analytics data layer (aggregator, metrics, trends, cache) - 6 API endpoints (overview, plants, transport, farms, sustainability, export) - 6 chart components (LineChart, BarChart, PieChart, AreaChart, Gauge, Heatmap) - 5 dashboard widgets (KPICard, TrendIndicator, DataTable, DateRangePicker, FilterPanel) - 5 dashboard pages (overview, plants, transport, farms, sustainability) - Export functionality (CSV, JSON) Dependencies added: recharts, d3, date-fns Also includes minor fixes: - Fix EnvironmentalForm spread type error - Fix AgentOrchestrator Map iteration issues - Fix next.config.js image domains undefined error - Add downlevelIteration to tsconfig
155 lines
4.5 KiB
TypeScript
155 lines
4.5 KiB
TypeScript
/**
|
|
* Analytics Export API
|
|
* Exports analytics data in various formats (CSV, JSON)
|
|
*/
|
|
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import {
|
|
getAnalyticsOverview,
|
|
getPlantAnalytics,
|
|
getTransportAnalytics,
|
|
getFarmAnalytics,
|
|
getSustainabilityAnalytics,
|
|
TimeRange,
|
|
AnalyticsFilters,
|
|
} from '../../../lib/analytics';
|
|
import { format } from 'date-fns';
|
|
|
|
interface ExportData {
|
|
overview?: any;
|
|
plants?: any;
|
|
transport?: any;
|
|
farms?: any;
|
|
sustainability?: any;
|
|
}
|
|
|
|
function convertToCSV(data: any[], headers: string[]): string {
|
|
const headerRow = headers.join(',');
|
|
const rows = data.map(item =>
|
|
headers.map(header => {
|
|
const value = item[header];
|
|
if (value === null || value === undefined) return '';
|
|
if (typeof value === 'string' && value.includes(',')) {
|
|
return `"${value.replace(/"/g, '""')}"`;
|
|
}
|
|
return String(value);
|
|
}).join(',')
|
|
);
|
|
return [headerRow, ...rows].join('\n');
|
|
}
|
|
|
|
function generatePlantCSV(plantData: any): string {
|
|
const speciesData = plantData.plantsBySpecies.map((s: any) => ({
|
|
species: s.species,
|
|
count: s.count,
|
|
percentage: s.percentage,
|
|
trend: s.trend,
|
|
}));
|
|
return convertToCSV(speciesData, ['species', 'count', 'percentage', 'trend']);
|
|
}
|
|
|
|
function generateTransportCSV(transportData: any): string {
|
|
const methodData = transportData.eventsByMethod.map((m: any) => ({
|
|
method: m.method,
|
|
count: m.count,
|
|
distanceKm: m.distanceKm,
|
|
carbonKg: m.carbonKg,
|
|
efficiency: m.efficiency,
|
|
}));
|
|
return convertToCSV(methodData, ['method', 'count', 'distanceKm', 'carbonKg', 'efficiency']);
|
|
}
|
|
|
|
function generateSustainabilityCSV(sustainData: any): string {
|
|
const goalsData = sustainData.goals.map((g: any) => ({
|
|
name: g.name,
|
|
target: g.target,
|
|
current: g.current,
|
|
unit: g.unit,
|
|
progress: g.progress,
|
|
status: g.status,
|
|
}));
|
|
return convertToCSV(goalsData, ['name', 'target', 'current', 'unit', 'progress', 'status']);
|
|
}
|
|
|
|
export default async function handler(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse
|
|
) {
|
|
if (req.method !== 'GET') {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
try {
|
|
const exportFormat = (req.query.format as string) || 'json';
|
|
const timeRange = (req.query.timeRange as TimeRange) || '30d';
|
|
const sections = req.query.sections
|
|
? (req.query.sections as string).split(',')
|
|
: ['overview', 'plants', 'transport', 'farms', 'sustainability'];
|
|
|
|
const filters: AnalyticsFilters = { timeRange };
|
|
const exportData: ExportData = {};
|
|
|
|
// Fetch requested sections
|
|
if (sections.includes('overview')) {
|
|
exportData.overview = await getAnalyticsOverview(filters);
|
|
}
|
|
if (sections.includes('plants')) {
|
|
exportData.plants = await getPlantAnalytics(filters);
|
|
}
|
|
if (sections.includes('transport')) {
|
|
exportData.transport = await getTransportAnalytics(filters);
|
|
}
|
|
if (sections.includes('farms')) {
|
|
exportData.farms = await getFarmAnalytics(filters);
|
|
}
|
|
if (sections.includes('sustainability')) {
|
|
exportData.sustainability = await getSustainabilityAnalytics(filters);
|
|
}
|
|
|
|
const timestamp = format(new Date(), 'yyyy-MM-dd_HH-mm-ss');
|
|
|
|
if (exportFormat === 'csv') {
|
|
// Generate combined CSV
|
|
let csvContent = '';
|
|
|
|
if (exportData.plants) {
|
|
csvContent += '# Plant Analytics - Species Distribution\n';
|
|
csvContent += generatePlantCSV(exportData.plants);
|
|
csvContent += '\n\n';
|
|
}
|
|
|
|
if (exportData.transport) {
|
|
csvContent += '# Transport Analytics - By Method\n';
|
|
csvContent += generateTransportCSV(exportData.transport);
|
|
csvContent += '\n\n';
|
|
}
|
|
|
|
if (exportData.sustainability) {
|
|
csvContent += '# Sustainability Goals\n';
|
|
csvContent += generateSustainabilityCSV(exportData.sustainability);
|
|
}
|
|
|
|
res.setHeader('Content-Type', 'text/csv');
|
|
res.setHeader('Content-Disposition', `attachment; filename=analytics_export_${timestamp}.csv`);
|
|
return res.status(200).send(csvContent);
|
|
}
|
|
|
|
// Default: JSON format
|
|
res.setHeader('Content-Type', 'application/json');
|
|
res.setHeader('Content-Disposition', `attachment; filename=analytics_export_${timestamp}.json`);
|
|
|
|
return res.status(200).json({
|
|
exportedAt: new Date().toISOString(),
|
|
timeRange,
|
|
sections,
|
|
data: exportData,
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Analytics export error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to export analytics data',
|
|
});
|
|
}
|
|
}
|