localgreenchain/pages/vertical-farm/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

161 lines
5.9 KiB
TypeScript

import { useState, useEffect } from 'react';
import Link from 'next/link';
import Head from 'next/head';
import FarmCard from '../../components/vertical-farm/FarmCard';
import { VerticalFarm } from '../../lib/vertical-farming/types';
export default function VerticalFarmDashboard() {
const [farms, setFarms] = useState<VerticalFarm[]>([]);
const [loading, setLoading] = useState(true);
const [stats, setStats] = useState({
totalFarms: 0,
totalPlants: 0,
upcomingHarvests: 0,
activeZones: 0,
});
useEffect(() => {
fetchFarms();
}, []);
const fetchFarms = async () => {
try {
const response = await fetch('/api/vertical-farm/list');
const data = await response.json();
if (data.success) {
setFarms(data.farms);
calculateStats(data.farms);
}
} catch (error) {
console.error('Error fetching farms:', error);
} finally {
setLoading(false);
}
};
const calculateStats = (farmList: VerticalFarm[]) => {
const totalPlants = farmList.reduce((sum, f) => sum + f.specs.currentActivePlants, 0);
const activeZones = farmList.reduce(
(sum, f) => sum + f.zones.filter(z => z.status === 'growing' || z.status === 'planted').length,
0
);
const upcomingHarvests = farmList.reduce(
(sum, f) =>
sum +
f.zones.filter(z => {
if (!z.expectedHarvestDate) return false;
const daysUntil = (new Date(z.expectedHarvestDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24);
return daysUntil <= 7 && daysUntil >= 0;
}).length,
0
);
setStats({
totalFarms: farmList.length,
totalPlants,
upcomingHarvests,
activeZones,
});
};
return (
<div className="min-h-screen bg-gradient-to-br from-green-50 to-emerald-100">
<Head>
<title>Vertical Farms - LocalGreenChain</title>
</Head>
{/* Header */}
<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">
<Link href="/">
<a className="text-2xl font-bold text-green-800">
LocalGreenChain
</a>
</Link>
<nav className="flex gap-4">
<Link href="/vertical-farm/register">
<a className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition">
Register Farm
</a>
</Link>
</nav>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 py-12 sm:px-6 lg:px-8">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold text-gray-900">Vertical Farm Dashboard</h1>
</div>
{/* Stats Overview */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Total Farms</p>
<p className="text-3xl font-bold text-green-600">{stats.totalFarms}</p>
</div>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Active Plants</p>
<p className="text-3xl font-bold text-green-600">{stats.totalPlants.toLocaleString()}</p>
</div>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Active Zones</p>
<p className="text-3xl font-bold text-blue-600">{stats.activeZones}</p>
</div>
<div className="bg-white rounded-lg shadow p-6">
<p className="text-sm text-gray-600">Harvests This Week</p>
<p className="text-3xl font-bold text-purple-600">{stats.upcomingHarvests}</p>
</div>
</div>
{/* Farms Grid */}
{loading ? (
<div className="text-center py-12">
<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 farms...</p>
</div>
) : farms.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{farms.map(farm => (
<FarmCard key={farm.id} farm={farm} />
))}
</div>
) : (
<div className="bg-white rounded-lg shadow-lg p-12 text-center">
<div className="text-6xl mb-4">🌱</div>
<h2 className="text-2xl font-bold text-gray-900 mb-2">No Vertical Farms Yet</h2>
<p className="text-gray-600 mb-6">
Start your controlled environment agriculture journey by registering your first vertical farm.
</p>
<Link href="/vertical-farm/register">
<a className="inline-block px-6 py-3 bg-green-600 text-white font-semibold rounded-lg hover:bg-green-700 transition">
Register Your First Farm
</a>
</Link>
</div>
)}
{/* Quick Actions */}
{farms.length > 0 && (
<div className="mt-8 bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">Quick Actions</h2>
<div className="flex flex-wrap gap-4">
<Link href="/vertical-farm/register">
<a className="px-4 py-2 bg-green-100 text-green-700 rounded-lg hover:bg-green-200 transition">
+ Add New Farm
</a>
</Link>
<button className="px-4 py-2 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition">
View All Batches
</button>
<button className="px-4 py-2 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition">
Export Analytics
</button>
</div>
</div>
)}
</main>
</div>
);
}