localgreenchain/pages/analytics/farms.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

253 lines
8.4 KiB
TypeScript

/**
* Farm Analytics Page
* Vertical farm performance and resource analytics
*/
import { useState, useEffect } from 'react';
import Link from 'next/link';
import {
KPICard,
DateRangePicker,
LineChart,
BarChart,
Gauge,
DataTable,
} from '../../components/analytics';
import { TimeRange, FarmAnalytics } from '../../lib/analytics/types';
export default function FarmAnalyticsPage() {
const [timeRange, setTimeRange] = useState<TimeRange>('30d');
const [loading, setLoading] = useState(true);
const [data, setData] = useState<FarmAnalytics | null>(null);
useEffect(() => {
fetchData();
}, [timeRange]);
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(`/api/analytics/farms?timeRange=${timeRange}`);
const result = await response.json();
setData(result.data);
} catch (error) {
console.error('Failed to fetch farm analytics:', error);
} finally {
setLoading(false);
}
};
const zoneColumns = [
{ key: 'zoneName', header: 'Zone' },
{ key: 'currentCrop', header: 'Crop' },
{
key: 'healthScore',
header: 'Health',
align: 'right' as const,
render: (v: number) => (
<span className={`font-medium ${v >= 90 ? 'text-green-600' : v >= 70 ? 'text-yellow-600' : 'text-red-600'}`}>
{v}%
</span>
),
},
{ key: 'yieldKg', header: 'Yield (kg)', align: 'right' as const, render: (v: number) => v.toFixed(1) },
{ key: 'efficiency', header: 'Efficiency', align: 'right' as const, render: (v: number) => `${v}%` },
];
const cropColumns = [
{ key: 'cropType', header: 'Crop' },
{ key: 'batches', header: 'Batches', align: 'right' as const },
{ key: 'averageYieldKg', header: 'Avg Yield (kg)', align: 'right' as const, render: (v: number) => v.toFixed(1) },
{ key: 'growthDays', header: 'Growth Days', align: 'right' as const },
{
key: 'successRate',
header: 'Success Rate',
align: 'right' as const,
render: (v: number) => (
<span className={`font-medium ${v >= 90 ? 'text-green-600' : v >= 75 ? 'text-yellow-600' : 'text-red-600'}`}>
{v.toFixed(1)}%
</span>
),
},
];
const predictionColumns = [
{ key: 'cropType', header: 'Crop' },
{ key: 'predictedYieldKg', header: 'Predicted Yield', align: 'right' as const, render: (v: number) => `${v.toFixed(1)} kg` },
{ key: 'confidence', header: 'Confidence', align: 'right' as const, render: (v: number) => `${(v * 100).toFixed(0)}%` },
{ key: 'harvestDate', header: 'Harvest Date' },
];
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100">
{/* Header */}
<div className="bg-gradient-to-r from-emerald-600 to-green-600 text-white">
<div className="max-w-7xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold">Farm Analytics</h1>
<p className="text-emerald-200 mt-1">Vertical farm performance and resource optimization</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-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-emerald-500 text-white rounded-lg font-medium">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 Farms"
value={data?.totalFarms || 0}
trend="up"
color="green"
loading={loading}
/>
<KPICard
title="Active Zones"
value={data?.totalZones || 0}
trend="stable"
color="blue"
loading={loading}
/>
<KPICard
title="Active Batches"
value={data?.activeBatches || 0}
trend="up"
color="purple"
loading={loading}
/>
<KPICard
title="Avg Yield"
value={data?.averageYieldKg?.toFixed(1) || '0'}
unit="kg/batch"
trend="up"
color="teal"
loading={loading}
/>
</div>
{/* Resource Gauges */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 mb-8">
<Gauge
value={data?.resourceUsage?.waterEfficiency || 0}
title="Water Efficiency"
unit="%"
/>
<Gauge
value={data?.resourceUsage?.energyEfficiency || 0}
title="Energy Efficiency"
unit="%"
/>
<Gauge
value={(data?.completedBatches || 0) / ((data?.activeBatches || 1) + (data?.completedBatches || 0)) * 100}
title="Completion Rate"
unit="%"
/>
<Gauge
value={90}
title="Overall Health"
unit="%"
/>
</div>
{/* Resource Usage Stats */}
<div className="bg-white rounded-lg shadow-lg p-6 mb-8">
<h3 className="text-lg font-bold text-gray-900 mb-4">Resource Usage Summary</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="text-center p-4 bg-blue-50 rounded-lg">
<p className="text-3xl font-bold text-blue-600">
{data?.resourceUsage?.waterLiters?.toLocaleString() || 0}
</p>
<p className="text-sm text-gray-500">Water Used (L)</p>
</div>
<div className="text-center p-4 bg-yellow-50 rounded-lg">
<p className="text-3xl font-bold text-yellow-600">
{data?.resourceUsage?.energyKwh?.toLocaleString() || 0}
</p>
<p className="text-sm text-gray-500">Energy Used (kWh)</p>
</div>
<div className="text-center p-4 bg-green-50 rounded-lg">
<p className="text-3xl font-bold text-green-600">
{data?.resourceUsage?.nutrientsKg?.toLocaleString() || 0}
</p>
<p className="text-sm text-gray-500">Nutrients Used (kg)</p>
</div>
</div>
</div>
{/* Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
{data?.batchCompletionTrend && (
<LineChart
data={data.batchCompletionTrend}
xKey="label"
yKey="value"
title="Batch Completions Over Time"
height={300}
/>
)}
{data?.topPerformingCrops && (
<BarChart
data={data.topPerformingCrops}
xKey="cropType"
yKey="averageYieldKg"
title="Average Yield by Crop"
height={300}
/>
)}
</div>
{/* Tables */}
<div className="space-y-6">
{data?.performanceByZone && (
<DataTable
data={data.performanceByZone}
columns={zoneColumns}
title="Zone Performance"
pageSize={6}
/>
)}
{data?.topPerformingCrops && (
<DataTable
data={data.topPerformingCrops}
columns={cropColumns}
title="Crop Performance"
pageSize={6}
/>
)}
{data?.yieldPredictions && (
<DataTable
data={data.yieldPredictions}
columns={predictionColumns}
title="Upcoming Harvest Predictions"
pageSize={5}
/>
)}
</div>
</div>
</div>
);
}