From f03342700249418e9860d7237c89eb3a54f335f3 Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Fri, 19 Dec 2025 13:53:19 -0800 Subject: [PATCH] feat: Sprint 6 - Final Polish - PlantDataCard.tsx: Deterministic mock data seeding via plant ID - Added Batch ID, Batch Name, and Last Updated timestamp - Improved sparkline history generation for realism - Enhanced UI with glassmorphic refinements and animations - Contextual actions styling --- .../components/facility3d/PlantDataCard.tsx | 109 +++++++++++++----- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/frontend/src/components/facility3d/PlantDataCard.tsx b/frontend/src/components/facility3d/PlantDataCard.tsx index 7104349..d1d6fab 100644 --- a/frontend/src/components/facility3d/PlantDataCard.tsx +++ b/frontend/src/components/facility3d/PlantDataCard.tsx @@ -1,4 +1,5 @@ -import { X, MapPin, Leaf, Activity, Calendar } from 'lucide-react'; +import { X, MapPin, Leaf, Activity, Calendar, Box, Clock } from 'lucide-react'; +import { useMemo } from 'react'; import type { PlantPosition } from './types'; import { LifecycleTimeline } from './LifecycleTimeline'; import { VitalsGrid } from './VitalGauge'; @@ -8,22 +9,56 @@ interface PlantDataCardProps { onClose: () => void; } -// Mock vitals for demo - now with consistent values per plant +/** + * Mock vitals for demo - now with consistent values and history per plant. + * Seeding by plantId ensures that clicking the same plant twice shows the same data, + * and different plants show unique, deterministic data. + */ const getMockVitals = (plantId?: string) => { - // Use plant ID to seed pseudo-random values for consistency - const seed = plantId ? plantId.charCodeAt(plantId.length - 1) : 42; + // Generate a deterministic seed from the plant ID + const seed = plantId ? plantId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) : 42; + + // Generate deterministic history based on seed + const generateHistory = (base: number, variance: number, points: number = 12) => { + const history: number[] = []; + for (let i = 0; i < points; i++) { + // Using Math.sin with the seed to create a deterministic "wavy" history + const noise = Math.sin(seed + i * 0.8) * variance; + history.push(base + noise); + } + return history; + }; + + const healthBase = 88 + (seed % 10); + const tempBase = 74 + (seed % 4); + const humBase = 58 + (seed % 8); + const vpdBase = 1.05 + (seed % 5) / 20; + return { - health: 75 + (seed % 20), - temperature: 72 + (seed % 8), - humidity: 55 + ((seed * 7) % 15), - vpd: 1.0 + ((seed % 10) / 25), + health: healthBase, + healthHistory: generateHistory(healthBase, 2), + temperature: tempBase, + tempHistory: generateHistory(tempBase, 1.5), + humidity: humBase, + humHistory: generateHistory(humBase, 4), + vpd: vpdBase, + vpdHistory: generateHistory(vpdBase, 0.15), + batch: `B-${(seed % 9999).toString(16).toUpperCase().padStart(4, '0')}`, + batchName: `Batch ${(seed % 150) + 101}`, + lastUpdated: new Date(Date.now() - (seed % 45) * 60 * 1000).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), }; }; export function PlantDataCard({ plant, onClose }: PlantDataCardProps) { - if (!plant?.plant) return null; + // Early escape and null safety + if (!plant || !plant.plant) { + return null; + } - const vitals = getMockVitals(plant.plant.id); + const { plant: plantInfo } = plant; + + // Memoize vitals based on plant ID to prevent re-rolls on every render + const vitals = useMemo(() => getMockVitals(plantInfo.id), [plantInfo.id]); // Format position breadcrumb const position = plant.breadcrumb @@ -31,21 +66,24 @@ export function PlantDataCard({ plant, onClose }: PlantDataCardProps) { : `R${plant.row} • C${plant.column} • T${plant.tier}`; return ( -
+
{/* Header */}
- {plant.plant.tagNumber} + {plantInfo.tagNumber} - - {plant.plant.stage?.replace('_', ' ') || 'VEG'} + + {plantInfo.stage?.replace('_', ' ') || 'VEG'}
- {plant.plant.strain || 'Unknown Strain'} + {plantInfo.strain || 'Unknown Strain'}
- {/* Position */} -
-
- + {/* Position & Batch Metadata */} +
+
+ {position}
+
+
+ + {vitals.batch} + + {vitals.batchName} +
+
+ + {vitals.lastUpdated} +
+
{/* Lifecycle Timeline */}
-
+
- LIFECYCLE + Lifecycle Timeline
- +
- {/* Vitals Grid - Now with sparklines */} + {/* Vitals Grid with Sparklines */}
-
+
- VITALS + Environmental Vitals
- {/* Quick Actions */} + {/* Contextual Actions */}
- - -
); } -