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
225 lines
8 KiB
TypeScript
225 lines
8 KiB
TypeScript
/**
|
|
* Analytics Dashboard - Main Page
|
|
* Comprehensive overview of all analytics data
|
|
*/
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import Link from 'next/link';
|
|
import {
|
|
KPICard,
|
|
DateRangePicker,
|
|
LineChart,
|
|
BarChart,
|
|
PieChart,
|
|
} from '../../components/analytics';
|
|
import { TimeRange, AnalyticsOverview, PlantAnalytics, TransportAnalytics } from '../../lib/analytics/types';
|
|
|
|
export default function AnalyticsDashboard() {
|
|
const [timeRange, setTimeRange] = useState<TimeRange>('30d');
|
|
const [loading, setLoading] = useState(true);
|
|
const [overview, setOverview] = useState<AnalyticsOverview | null>(null);
|
|
const [plantData, setPlantData] = useState<PlantAnalytics | null>(null);
|
|
const [transportData, setTransportData] = useState<TransportAnalytics | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [timeRange]);
|
|
|
|
const fetchData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const [overviewRes, plantRes, transportRes] = await Promise.all([
|
|
fetch(`/api/analytics/overview?timeRange=${timeRange}`),
|
|
fetch(`/api/analytics/plants?timeRange=${timeRange}`),
|
|
fetch(`/api/analytics/transport?timeRange=${timeRange}`),
|
|
]);
|
|
|
|
const overviewData = await overviewRes.json();
|
|
const plantDataRes = await plantRes.json();
|
|
const transportDataRes = await transportRes.json();
|
|
|
|
setOverview(overviewData.data);
|
|
setPlantData(plantDataRes.data);
|
|
setTransportData(transportDataRes.data);
|
|
} catch (error) {
|
|
console.error('Failed to fetch analytics data:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleExport = (format: 'csv' | 'json') => {
|
|
window.open(`/api/analytics/export?format=${format}&timeRange=${timeRange}`, '_blank');
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100">
|
|
{/* Header */}
|
|
<div className="bg-gradient-to-r from-green-600 to-emerald-600 text-white">
|
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Analytics Dashboard</h1>
|
|
<p className="text-green-200 mt-1">
|
|
Comprehensive insights into your LocalGreenChain network
|
|
</p>
|
|
</div>
|
|
<div className="flex items-center space-x-4">
|
|
<button
|
|
onClick={() => handleExport('csv')}
|
|
className="px-4 py-2 bg-white/20 hover:bg-white/30 rounded-lg text-sm font-medium transition-colors"
|
|
>
|
|
Export CSV
|
|
</button>
|
|
<button
|
|
onClick={() => handleExport('json')}
|
|
className="px-4 py-2 bg-white/20 hover:bg-white/30 rounded-lg text-sm font-medium transition-colors"
|
|
>
|
|
Export JSON
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="max-w-7xl mx-auto px-4 py-8">
|
|
{/* Time Range Selector */}
|
|
<div className="mb-8">
|
|
<DateRangePicker value={timeRange} onChange={setTimeRange} />
|
|
</div>
|
|
|
|
{/* Navigation Tabs */}
|
|
<div className="flex space-x-4 mb-8">
|
|
<Link href="/analytics">
|
|
<a className="px-4 py-2 bg-green-500 text-white rounded-lg font-medium">Overview</a>
|
|
</Link>
|
|
<Link href="/analytics/plants">
|
|
<a className="px-4 py-2 bg-white hover:bg-gray-50 text-gray-700 rounded-lg font-medium shadow">Plants</a>
|
|
</Link>
|
|
<Link href="/analytics/transport">
|
|
<a className="px-4 py-2 bg-white hover:bg-gray-50 text-gray-700 rounded-lg font-medium shadow">Transport</a>
|
|
</Link>
|
|
<Link href="/analytics/farms">
|
|
<a className="px-4 py-2 bg-white hover:bg-gray-50 text-gray-700 rounded-lg font-medium shadow">Farms</a>
|
|
</Link>
|
|
<Link href="/analytics/sustainability">
|
|
<a className="px-4 py-2 bg-white hover:bg-gray-50 text-gray-700 rounded-lg font-medium shadow">Sustainability</a>
|
|
</Link>
|
|
</div>
|
|
|
|
{/* KPI Cards */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
|
<KPICard
|
|
title="Total Plants"
|
|
value={overview?.totalPlants || 0}
|
|
trend={overview?.trendsData?.[0]?.direction || 'stable'}
|
|
changePercent={overview?.trendsData?.[0]?.changePercent}
|
|
color="green"
|
|
loading={loading}
|
|
/>
|
|
<KPICard
|
|
title="Carbon Saved"
|
|
value={transportData?.carbonSavedKg?.toFixed(1) || '0'}
|
|
unit="kg CO2"
|
|
trend="up"
|
|
changePercent={12.5}
|
|
color="teal"
|
|
loading={loading}
|
|
/>
|
|
<KPICard
|
|
title="Food Miles"
|
|
value={transportData?.totalDistanceKm?.toFixed(0) || '0'}
|
|
unit="km"
|
|
trend="down"
|
|
changePercent={-8.7}
|
|
color="blue"
|
|
loading={loading}
|
|
/>
|
|
<KPICard
|
|
title="Active Users"
|
|
value={overview?.activeUsers || 0}
|
|
trend="up"
|
|
changePercent={8.3}
|
|
color="purple"
|
|
loading={loading}
|
|
/>
|
|
</div>
|
|
|
|
{/* Charts Row */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
{/* Plant Registrations Trend */}
|
|
{plantData?.registrationsTrend && (
|
|
<LineChart
|
|
data={plantData.registrationsTrend}
|
|
xKey="label"
|
|
yKey="value"
|
|
title="Plant Registrations Over Time"
|
|
height={300}
|
|
/>
|
|
)}
|
|
|
|
{/* Species Distribution */}
|
|
{plantData?.plantsBySpecies && (
|
|
<PieChart
|
|
data={plantData.plantsBySpecies}
|
|
dataKey="count"
|
|
nameKey="species"
|
|
title="Plants by Species"
|
|
height={300}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Transport Charts */}
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
{/* Transport Methods */}
|
|
{transportData?.eventsByMethod && (
|
|
<BarChart
|
|
data={transportData.eventsByMethod}
|
|
xKey="method"
|
|
yKey="count"
|
|
title="Transport Events by Method"
|
|
height={300}
|
|
horizontal
|
|
/>
|
|
)}
|
|
|
|
{/* Carbon Trend */}
|
|
{transportData?.carbonTrend && (
|
|
<LineChart
|
|
data={transportData.carbonTrend}
|
|
xKey="label"
|
|
yKey="value"
|
|
title="Carbon Emissions Trend"
|
|
colors={['#ef4444']}
|
|
height={300}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{/* Summary Stats */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
|
<h3 className="text-lg font-bold text-gray-900 mb-4">Network Summary</h3>
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<p className="text-2xl font-bold text-gray-900">{overview?.plantsRegisteredToday || 0}</p>
|
|
<p className="text-sm text-gray-500">Registered Today</p>
|
|
</div>
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<p className="text-2xl font-bold text-gray-900">{overview?.plantsRegisteredThisWeek || 0}</p>
|
|
<p className="text-sm text-gray-500">This Week</p>
|
|
</div>
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<p className="text-2xl font-bold text-gray-900">{overview?.plantsRegisteredThisMonth || 0}</p>
|
|
<p className="text-sm text-gray-500">This Month</p>
|
|
</div>
|
|
<div className="text-center p-4 bg-gray-50 rounded-lg">
|
|
<p className="text-2xl font-bold text-green-600">{overview?.growthRate?.toFixed(1) || 0}%</p>
|
|
<p className="text-sm text-gray-500">Growth Rate</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|