import { useState, useEffect } from 'react'; import FloorGrid from './FloorGrid'; import FloorToggle from './FloorToggle'; import HealthLegend from './HealthLegend'; /** * GrowRoomHeatmap - Visual heat-map representation of plant health * * This component displays a multi-level grow room with color-coded beds * based on plant health scores. Reduces cognitive load by showing problems * as visual zones instead of text lists. * * @param roomId - Unique identifier for the grow room * * Integration: * 1. Replace mockFetchRoomLayout with real API call to /api/rooms/:roomId/layout * 2. Replace mockFetchRoomHealth with real API call to /api/rooms/:roomId/health * 3. For real-time updates, add WebSocket connection or polling interval * 4. Add error handling and loading states */ export interface Bed { bed_id: string; floor: 'floor_1' | 'floor_2'; row: number; column: number; plant_batch_id?: string; status: 'active' | 'empty' | 'maintenance'; health_score: number; // 0-100 sensors?: { temp?: number; humidity?: number; ec?: number; par?: number; }; last_alert?: string; } export interface RoomLayout { room_id: string; name: string; floors: Array<'floor_1' | 'floor_2'>; grid: { rows: number; columns: number; }; } interface GrowRoomHeatmapProps { roomId: string; } export default function GrowRoomHeatmap({ roomId }: GrowRoomHeatmapProps) { const [currentFloor, setCurrentFloor] = useState<'floor_1' | 'floor_2'>('floor_1'); const [layout, setLayout] = useState(null); const [beds, setBeds] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { loadRoomData(); // TODO: For real-time updates, add polling or WebSocket // const interval = setInterval(loadRoomData, 30000); // Poll every 30s // return () => clearInterval(interval); }, [roomId]); async function loadRoomData() { setIsLoading(true); setError(null); try { // TODO: Replace with real API calls const layoutData = await mockFetchRoomLayout(roomId); const healthData = await mockFetchRoomHealth(roomId); setLayout(layoutData); setBeds(healthData); } catch (err: any) { setError(err.message || 'Failed to load room data'); } finally { setIsLoading(false); } } // Filter beds for current floor const currentFloorBeds = beds.filter(bed => bed.floor === currentFloor); if (isLoading) { return (
Loading room data...
); } if (error) { return (
Error: {error}
); } if (!layout) { return (
No room data available
); } return (
{/* Header */}

{layout.name}

Plant Health Heat Map

{/* Legend */} {/* Grid */}
{/* Stats Summary */}
b.status === 'active').length} color="green" /> b.health_score < 70).length} color="yellow" /> b.health_score < 30).length} color="red" />
); } // Stats card component function StatCard({ label, value, color }: { label: string; value: number; color: string }) { const colorClasses = { blue: 'bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300', green: 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-300', yellow: 'bg-yellow-50 dark:bg-yellow-900/20 text-yellow-700 dark:text-yellow-300', red: 'bg-red-50 dark:bg-red-900/20 text-red-700 dark:text-red-300', }; return (
{value}
{label}
); } // ============================================================================ // MOCK API FUNCTIONS // TODO: Replace these with real API calls // ============================================================================ async function mockFetchRoomLayout(roomId: string): Promise { // Simulate API delay await new Promise(resolve => setTimeout(resolve, 500)); return { room_id: roomId, name: 'Veg Room A', floors: ['floor_1', 'floor_2'], grid: { rows: 6, columns: 8, }, }; } async function mockFetchRoomHealth(roomId: string): Promise { // Simulate API delay await new Promise(resolve => setTimeout(resolve, 500)); const beds: Bed[] = []; // Generate mock data for both floors for (const floor of ['floor_1', 'floor_2'] as const) { for (let row = 0; row < 6; row++) { for (let col = 0; col < 8; col++) { // Randomly make some beds empty const isEmpty = Math.random() < 0.2; beds.push({ bed_id: `${floor}_${row}_${col}`, floor, row, column: col, plant_batch_id: isEmpty ? undefined : `batch_${Math.floor(Math.random() * 10)}`, status: isEmpty ? 'empty' : 'active', health_score: isEmpty ? 0 : Math.floor(Math.random() * 100), sensors: isEmpty ? undefined : { temp: 72 + Math.random() * 8, humidity: 55 + Math.random() * 15, ec: 1.2 + Math.random() * 0.8, par: 400 + Math.random() * 400, }, last_alert: isEmpty ? undefined : (Math.random() < 0.3 ? '2 hours ago' : undefined), }); } } } return beds; }