localgreenchain/pages/analytics/plants.tsx
Claude 816c3b3f2e
Implement Agent 7: Advanced Analytics Dashboard
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
2025-11-23 04:02:07 +00:00

182 lines
5.7 KiB
TypeScript

/**
* Plant Analytics Page
* Detailed analytics for plant registrations and lineage
*/
import { useState, useEffect } from 'react';
import Link from 'next/link';
import {
KPICard,
DateRangePicker,
LineChart,
BarChart,
PieChart,
DataTable,
TrendIndicator,
} from '../../components/analytics';
import { TimeRange, PlantAnalytics } from '../../lib/analytics/types';
export default function PlantAnalyticsPage() {
const [timeRange, setTimeRange] = useState<TimeRange>('30d');
const [loading, setLoading] = useState(true);
const [data, setData] = useState<PlantAnalytics | null>(null);
useEffect(() => {
fetchData();
}, [timeRange]);
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(`/api/analytics/plants?timeRange=${timeRange}`);
const result = await response.json();
setData(result.data);
} catch (error) {
console.error('Failed to fetch plant analytics:', error);
} finally {
setLoading(false);
}
};
const speciesColumns = [
{ key: 'species', header: 'Species' },
{ key: 'count', header: 'Count', align: 'right' as const },
{ key: 'percentage', header: '% Share', align: 'right' as const, render: (v: number) => `${v.toFixed(1)}%` },
{ key: 'trend', header: 'Trend', render: (v: string) => <TrendIndicator direction={v as any} /> },
];
const growerColumns = [
{ key: 'name', header: 'Grower' },
{ key: 'totalPlants', header: 'Plants', align: 'right' as const },
{ key: 'totalSpecies', header: 'Species', align: 'right' as const },
{ key: 'averageGeneration', header: 'Avg Gen', align: 'right' as const, render: (v: number) => v.toFixed(1) },
];
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">
<h1 className="text-3xl font-bold">Plant Analytics</h1>
<p className="text-green-200 mt-1">Detailed insights into plant registrations and lineage</p>
</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-white hover:bg-gray-50 text-gray-700 rounded-lg font-medium shadow">Overview</a>
</Link>
<Link href="/analytics/plants">
<a className="px-4 py-2 bg-green-500 text-white rounded-lg font-medium">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-4 gap-6 mb-8">
<KPICard
title="Total Plants"
value={data?.totalPlants || 0}
trend="up"
color="green"
loading={loading}
/>
<KPICard
title="Unique Species"
value={data?.plantsBySpecies?.length || 0}
trend="stable"
color="blue"
loading={loading}
/>
<KPICard
title="Avg Lineage Depth"
value={data?.averageLineageDepth?.toFixed(1) || '0'}
unit="generations"
trend="up"
color="purple"
loading={loading}
/>
<KPICard
title="Top Growers"
value={data?.topGrowers?.length || 0}
trend="stable"
color="teal"
loading={loading}
/>
</div>
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{data?.registrationsTrend && (
<LineChart
data={data.registrationsTrend}
xKey="label"
yKey="value"
title="Plant Registrations Over Time"
height={300}
/>
)}
{data?.plantsBySpecies && (
<PieChart
data={data.plantsBySpecies}
dataKey="count"
nameKey="species"
title="Distribution by Species"
height={300}
/>
)}
</div>
{/* Generation Distribution */}
{data?.plantsByGeneration && (
<div className="mb-8">
<BarChart
data={data.plantsByGeneration}
xKey="generation"
yKey="count"
title="Plants by Generation"
height={250}
/>
</div>
)}
{/* Tables */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{data?.plantsBySpecies && (
<DataTable
data={data.plantsBySpecies}
columns={speciesColumns}
title="Species Breakdown"
pageSize={6}
/>
)}
{data?.topGrowers && (
<DataTable
data={data.topGrowers}
columns={growerColumns}
title="Top Growers"
pageSize={6}
/>
)}
</div>
</div>
</div>
);
}