localgreenchain/pages/vertical-farm/[farmId]/index.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

264 lines
11 KiB
TypeScript

import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';
import Head from 'next/head';
import { VerticalFarm, CropBatch } from '../../../lib/vertical-farming/types';
import ZoneGrid from '../../../components/vertical-farm/ZoneGrid';
import AlertPanel from '../../../components/vertical-farm/AlertPanel';
export default function FarmDetail() {
const router = useRouter();
const { farmId } = router.query;
const [farm, setFarm] = useState<VerticalFarm | null>(null);
const [batches, setBatches] = useState<CropBatch[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (farmId) {
fetchFarmDetails();
}
}, [farmId]);
const fetchFarmDetails = async () => {
try {
const [farmRes, batchesRes] = await Promise.all([
fetch(`/api/vertical-farm/${farmId}`),
fetch(`/api/vertical-farm/${farmId}/batches`),
]);
const farmData = await farmRes.json();
const batchesData = await batchesRes.json();
if (farmData.success) {
setFarm(farmData.farm);
}
if (batchesData.success) {
setBatches(batchesData.batches);
}
} catch (error) {
console.error('Error fetching farm:', 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="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto" />
<p className="text-gray-600 mt-4">Loading farm details...</p>
</div>
</div>
);
}
if (!farm) {
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100 flex items-center justify-center">
<div className="text-center">
<h1 className="text-2xl font-bold text-gray-900 mb-4">Farm Not Found</h1>
<Link href="/vertical-farm">
<a className="text-green-600 hover:text-green-700">Back to Dashboard</a>
</Link>
</div>
</div>
);
}
const allAlerts = farm.zones.flatMap(z => z.currentEnvironment?.alerts || []);
const activeBatches = batches.filter(b => b.status !== 'completed' && b.status !== 'failed');
const upcomingHarvests = activeBatches.filter(b => {
const daysUntil = (new Date(b.expectedHarvestDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24);
return daysUntil <= 7 && daysUntil >= 0;
});
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100">
<Head>
<title>{farm.name} - LocalGreenChain</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">
<a className="text-gray-600 hover:text-gray-900">&larr; Back</a>
</Link>
<h1 className="text-2xl font-bold text-gray-900">{farm.name}</h1>
<span
className={`px-3 py-1 rounded-full text-xs font-semibold ${
farm.status === 'operational'
? 'bg-green-100 text-green-800'
: farm.status === 'maintenance'
? 'bg-yellow-100 text-yellow-800'
: 'bg-gray-100 text-gray-800'
}`}
>
{farm.status}
</span>
</div>
<nav className="flex gap-3">
<Link href={`/vertical-farm/${farmId}/zones`}>
<a className="px-4 py-2 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200">Zones</a>
</Link>
<Link href={`/vertical-farm/${farmId}/batches`}>
<a className="px-4 py-2 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200">Batches</a>
</Link>
<Link href={`/vertical-farm/${farmId}/analytics`}>
<a className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700">Analytics</a>
</Link>
</nav>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 py-8 sm:px-6 lg:px-8">
{/* Quick Stats */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-8">
<div className="bg-white rounded-lg shadow p-4">
<p className="text-sm text-gray-600">Active Plants</p>
<p className="text-2xl font-bold text-green-600">{farm.specs.currentActivePlants.toLocaleString()}</p>
</div>
<div className="bg-white rounded-lg shadow p-4">
<p className="text-sm text-gray-600">Growing Area</p>
<p className="text-2xl font-bold text-blue-600">{farm.specs.growingAreaSqm}m²</p>
</div>
<div className="bg-white rounded-lg shadow p-4">
<p className="text-sm text-gray-600">Active Batches</p>
<p className="text-2xl font-bold text-purple-600">{activeBatches.length}</p>
</div>
<div className="bg-white rounded-lg shadow p-4">
<p className="text-sm text-gray-600">Capacity</p>
<p className="text-2xl font-bold text-orange-600">{farm.currentCapacityUtilization}%</p>
</div>
<div className="bg-white rounded-lg shadow p-4">
<p className="text-sm text-gray-600">Efficiency</p>
<p className="text-2xl font-bold text-teal-600">{farm.energyEfficiencyScore}%</p>
</div>
</div>
{/* Alerts */}
{allAlerts.length > 0 && (
<div className="mb-8">
<AlertPanel alerts={allAlerts} />
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Main Content */}
<div className="lg:col-span-2 space-y-6">
{/* Zone Overview */}
<div className="bg-white rounded-lg shadow-lg p-6">
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold text-gray-900">Zone Overview</h2>
<Link href={`/vertical-farm/${farmId}/zones`}>
<a className="text-green-600 hover:text-green-700 text-sm">View All &rarr;</a>
</Link>
</div>
<ZoneGrid zones={farm.zones.slice(0, 6)} />
</div>
{/* Upcoming Harvests */}
{upcomingHarvests.length > 0 && (
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">Upcoming Harvests</h2>
<div className="space-y-3">
{upcomingHarvests.map(batch => {
const daysUntil = Math.ceil(
(new Date(batch.expectedHarvestDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24)
);
return (
<div
key={batch.id}
className="flex justify-between items-center p-3 bg-purple-50 rounded-lg"
>
<div>
<p className="font-medium text-gray-900">{batch.cropType}</p>
<p className="text-sm text-gray-600">{batch.plantCount} plants</p>
</div>
<div className="text-right">
<p className="font-semibold text-purple-600">
{daysUntil === 0 ? 'Today' : `${daysUntil} day${daysUntil > 1 ? 's' : ''}`}
</p>
<p className="text-sm text-gray-600">{batch.expectedYieldKg.toFixed(1)} kg expected</p>
</div>
</div>
);
})}
</div>
</div>
)}
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Farm Info */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">Farm Details</h2>
<div className="space-y-3 text-sm">
<div>
<p className="text-gray-600">Location</p>
<p className="font-medium">{farm.location.city}, {farm.location.country}</p>
<p className="text-gray-500">{farm.location.address}</p>
</div>
<div>
<p className="text-gray-600">Building</p>
<p className="font-medium">{farm.specs.buildingType.replace('_', ' ')}</p>
</div>
<div>
<p className="text-gray-600">Levels</p>
<p className="font-medium">{farm.specs.numberOfLevels}</p>
</div>
<div>
<p className="text-gray-600">Automation</p>
<p className="font-medium">{farm.automationLevel.replace('_', ' ')}</p>
</div>
<div>
<p className="text-gray-600">Operational Since</p>
<p className="font-medium">{new Date(farm.operationalSince).toLocaleDateString()}</p>
</div>
</div>
</div>
{/* Quick Actions */}
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">Quick Actions</h2>
<div className="space-y-2">
<Link href={`/vertical-farm/${farmId}/batches?action=new`}>
<a className="block w-full px-4 py-2 bg-green-100 text-green-700 rounded-lg text-center hover:bg-green-200 transition">
Start New Batch
</a>
</Link>
<button className="w-full px-4 py-2 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition">
Record Environment
</button>
<button className="w-full px-4 py-2 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition">
Schedule Maintenance
</button>
</div>
</div>
{/* Certifications */}
{farm.specs.certifications.length > 0 && (
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-lg font-bold text-gray-900 mb-3">Certifications</h2>
<div className="flex flex-wrap gap-2">
{farm.specs.certifications.map(cert => (
<span
key={cert}
className="px-3 py-1 bg-green-100 text-green-700 rounded-full text-sm"
>
{cert.toUpperCase()}
</span>
))}
</div>
</div>
)}
</div>
</div>
</main>
</div>
);
}