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
253 lines
8.4 KiB
TypeScript
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>
|
|
);
|
|
}
|