localgreenchain/pages/vertical-farm/[farmId]/analytics.tsx
Claude 2f7f22ca22
Add vertical farming UI components and pages
Components:
- FarmCard: Farm summary display with status and metrics
- ZoneGrid: Multi-level zone layout visualization
- ZoneDetailCard: Individual zone details with environment readings
- EnvironmentGauge: Real-time environmental parameter display
- BatchProgress: Crop batch progress tracking with health scores
- RecipeSelector: Growing recipe browser and selector
- AlertPanel: Environment alerts display and management
- GrowthStageIndicator: Visual growth stage progress tracker
- ResourceUsageChart: Energy/water usage analytics visualization

Pages:
- /vertical-farm: Dashboard with farm listing and stats
- /vertical-farm/register: Multi-step farm registration form
- /vertical-farm/[farmId]: Farm detail view with zones and alerts
- /vertical-farm/[farmId]/zones: Zone management with batch starting
- /vertical-farm/[farmId]/batches: Batch management and harvesting
- /vertical-farm/[farmId]/analytics: Farm analytics and performance metrics
2025-11-22 18:35:57 +00:00

289 lines
13 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import Head from 'next/head';
import { FarmAnalytics, ResourceUsage, VerticalFarm } from '../../../lib/vertical-farming/types';
import ResourceUsageChart from '../../../components/vertical-farm/ResourceUsageChart';
export default function FarmAnalyticsPage() {
const router = useRouter();
const { farmId } = router.query;
const [farm, setFarm] = useState<VerticalFarm | null>(null);
const [analytics, setAnalytics] = useState<FarmAnalytics | null>(null);
const [resourceUsage, setResourceUsage] = useState<ResourceUsage | null>(null);
const [loading, setLoading] = useState(true);
const [period, setPeriod] = useState(30);
useEffect(() => {
if (farmId) {
fetchData();
}
}, [farmId, period]);
const fetchData = async () => {
setLoading(true);
try {
const [farmRes, analyticsRes] = await Promise.all([
fetch(`/api/vertical-farm/${farmId}`),
fetch(`/api/vertical-farm/${farmId}/analytics?period=${period}`),
]);
const farmData = await farmRes.json();
const analyticsData = await analyticsRes.json();
if (farmData.success) setFarm(farmData.farm);
if (analyticsData.success) {
setAnalytics(analyticsData.analytics);
if (analyticsData.resourceUsage) {
setResourceUsage(analyticsData.resourceUsage);
}
}
} catch (error) {
console.error('Error fetching analytics:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100 flex items-center justify-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600" />
</div>
);
}
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100">
<Head>
<title>Analytics - {farm?.name || 'Vertical Farm'}</title>
</Head>
<header className="bg-white shadow-sm">
<div className="max-w-7xl mx-auto px-4 py-6 sm:px-6 lg:px-8">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href={`/vertical-farm/${farmId}`}>
<a className="text-gray-600 hover:text-gray-900">&larr; Back</a>
</Link>
<h1 className="text-2xl font-bold text-gray-900">Farm Analytics</h1>
</div>
<div className="flex gap-2">
{[7, 30, 90].map(p => (
<button
key={p}
onClick={() => setPeriod(p)}
className={`px-3 py-1 rounded-lg text-sm ${
period === p
? 'bg-green-600 text-white'
: 'bg-white text-gray-700 hover:bg-gray-100'
}`}
>
{p}d
</button>
))}
</div>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
{analytics ? (
<div className="space-y-8">
{/* Production Overview */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Total Yield</p>
<p className="text-3xl font-bold text-green-600">{analytics.totalYieldKg} kg</p>
<p className="text-xs text-gray-500">{period} day period</p>
</div>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Yield/m²/year</p>
<p className="text-3xl font-bold text-blue-600">{analytics.yieldPerSqmPerYear} kg</p>
</div>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Crop Cycles</p>
<p className="text-3xl font-bold text-purple-600">{analytics.cropCyclesCompleted}</p>
<p className="text-xs text-gray-500">Avg {analytics.averageCyclesDays.toFixed(0)} days</p>
</div>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Success Rate</p>
<p className="text-3xl font-bold text-teal-600">{analytics.cropSuccessRate}%</p>
</div>
</div>
{/* Quality Metrics */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-6">Quality Metrics</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<div className="text-center">
<div className="w-24 h-24 mx-auto mb-2 relative">
<svg className="w-24 h-24 transform -rotate-90">
<circle
cx="48"
cy="48"
r="40"
stroke="#e5e7eb"
strokeWidth="8"
fill="none"
/>
<circle
cx="48"
cy="48"
r="40"
stroke="#10b981"
strokeWidth="8"
fill="none"
strokeDasharray={`${(analytics.averageQualityScore / 100) * 251.2} 251.2`}
/>
</svg>
<span className="absolute inset-0 flex items-center justify-center text-xl font-bold text-gray-900">
{analytics.averageQualityScore.toFixed(0)}
</span>
</div>
<p className="text-sm text-gray-600">Quality Score</p>
</div>
<div className="text-center">
<p className="text-4xl font-bold text-green-600">{analytics.gradeAPercent}%</p>
<p className="text-sm text-gray-600">Grade A</p>
</div>
<div className="text-center">
<p className="text-4xl font-bold text-orange-600">{analytics.wastagePercent}%</p>
<p className="text-sm text-gray-600">Wastage</p>
</div>
<div className="text-center">
<p className="text-4xl font-bold text-blue-600">{analytics.spaceUtilization}%</p>
<p className="text-sm text-gray-600">Space Utilization</p>
</div>
</div>
</div>
{/* Financial Overview */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-6">Financial Performance</h2>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<div className="bg-green-50 rounded-lg p-4">
<p className="text-sm text-green-700">Revenue</p>
<p className="text-2xl font-bold text-green-900">
${analytics.revenueUsd.toLocaleString()}
</p>
</div>
<div className="bg-red-50 rounded-lg p-4">
<p className="text-sm text-red-700">Costs</p>
<p className="text-2xl font-bold text-red-900">
${analytics.costUsd.toLocaleString()}
</p>
</div>
<div className="bg-blue-50 rounded-lg p-4">
<p className="text-sm text-blue-700">Profit Margin</p>
<p className="text-2xl font-bold text-blue-900">{analytics.profitMarginPercent}%</p>
</div>
<div className="bg-purple-50 rounded-lg p-4">
<p className="text-sm text-purple-700">Revenue/m²/year</p>
<p className="text-2xl font-bold text-purple-900">
${analytics.revenuePerSqm.toLocaleString()}
</p>
</div>
</div>
</div>
{/* Environmental Impact */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-6">Environmental Impact</h2>
<div className="grid grid-cols-3 gap-6">
<div className="text-center p-4 bg-gray-50 rounded-lg">
<p className="text-3xl font-bold text-gray-900">
{analytics.carbonFootprintKgPerKg.toFixed(2)}
</p>
<p className="text-sm text-gray-600">kg CO2 / kg produce</p>
<p className="text-xs text-green-600 mt-1">90% less than field farming</p>
</div>
<div className="text-center p-4 bg-gray-50 rounded-lg">
<p className="text-3xl font-bold text-gray-900">
{analytics.waterUseLPerKg.toFixed(1)}
</p>
<p className="text-sm text-gray-600">L water / kg produce</p>
<p className="text-xs text-green-600 mt-1">95% less than field farming</p>
</div>
<div className="text-center p-4 bg-gray-50 rounded-lg">
<p className="text-3xl font-bold text-gray-900">
{analytics.energyUseKwhPerKg.toFixed(1)}
</p>
<p className="text-sm text-gray-600">kWh / kg produce</p>
</div>
</div>
</div>
{/* Top Crops */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="font-semibold text-gray-900 mb-4">Top Crops by Yield</h3>
<div className="space-y-3">
{analytics.topCropsByYield.map((crop, idx) => (
<div key={crop.crop} className="flex justify-between items-center">
<span className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-500">{idx + 1}</span>
<span className="font-medium">{crop.crop}</span>
</span>
<span className="text-green-600 font-semibold">{crop.yieldKg} kg</span>
</div>
))}
</div>
</div>
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="font-semibold text-gray-900 mb-4">Top Crops by Revenue</h3>
<div className="space-y-3">
{analytics.topCropsByRevenue.map((crop, idx) => (
<div key={crop.crop} className="flex justify-between items-center">
<span className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-500">{idx + 1}</span>
<span className="font-medium">{crop.crop}</span>
</span>
<span className="text-blue-600 font-semibold">${crop.revenueUsd}</span>
</div>
))}
</div>
</div>
<div className="bg-white rounded-lg shadow-lg p-6">
<h3 className="font-semibold text-gray-900 mb-4">Top Crops by Efficiency</h3>
<div className="space-y-3">
{analytics.topCropsByEfficiency.map((crop, idx) => (
<div key={crop.crop} className="flex justify-between items-center">
<span className="flex items-center gap-2">
<span className="text-sm font-semibold text-gray-500">{idx + 1}</span>
<span className="font-medium">{crop.crop}</span>
</span>
<span className="text-purple-600 font-semibold">{crop.efficiencyScore}%</span>
</div>
))}
</div>
</div>
</div>
{/* Resource Usage */}
{resourceUsage && (
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-6">Resource Usage</h2>
<ResourceUsageChart usage={resourceUsage} />
</div>
)}
{/* Generated Timestamp */}
<p className="text-center text-sm text-gray-500">
Analytics generated: {new Date(analytics.generatedAt).toLocaleString()}
</p>
</div>
) : (
<div className="bg-white rounded-lg shadow-lg p-12 text-center">
<p className="text-gray-600 mb-4">No analytics data available yet</p>
<p className="text-sm text-gray-500">
Complete some crop batches to see analytics data
</p>
</div>
)}
</main>
</div>
);
}