ca-grow-ops-manager/frontend/src/components/heatmap/BedCell.tsx
fullsizemalt b20edc0c33 fix: Add missing heatmap components and User.name field
🔧 Build Fixes:
- Created FloorToggle component
- Created HealthLegend component
- Added name field to User interface

Components complete for heatmap feature
2025-12-09 14:43:54 -08:00

160 lines
4.8 KiB
TypeScript

import { useState } from 'react';
import { Bed } from './GrowRoomHeatmap';
import BedTooltip from './BedTooltip';
/**
* BedCell - Individual bed cell in the grid
*
* Renders a single bed with color-coded health score.
* Shows tooltip on hover with detailed bed information.
*
* Color scale:
* - 90-100: Dark green (excellent)
* - 70-89: Light green (good)
* - 50-69: Yellow (fair)
* - 30-49: Orange (needs attention)
* - 0-29: Red (critical)
* - Empty: Gray outline (no plant)
*/
interface BedCellProps {
bed?: Bed;
x: number;
y: number;
size: number;
row: number;
column: number;
}
export default function BedCell({ bed, x, y, size, row, column }: BedCellProps) {
const [showTooltip, setShowTooltip] = useState(false);
const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 });
// Get color based on health score
const getHealthColor = (score: number): string => {
if (score >= 90) return '#059669'; // emerald-600
if (score >= 70) return '#10b981'; // emerald-500
if (score >= 50) return '#eab308'; // yellow-500
if (score >= 30) return '#f97316'; // orange-500
return '#dc2626'; // red-600
};
const handleMouseEnter = (e: React.MouseEvent) => {
if (bed && bed.status !== 'empty') {
setTooltipPosition({ x: e.clientX, y: e.clientY });
setShowTooltip(true);
}
};
const handleMouseLeave = () => {
setShowTooltip(false);
};
const handleClick = () => {
if (bed && bed.status !== 'empty') {
// TODO: Navigate to bed detail page
console.log('Navigate to bed:', bed.bed_id);
}
};
// Empty bed
if (!bed || bed.status === 'empty') {
return (
<g>
<rect
x={x}
y={y}
width={size}
height={size}
fill="transparent"
stroke="#94a3b8"
strokeWidth="1"
strokeDasharray="4 4"
rx="4"
className="opacity-30"
/>
<text
x={x + size / 2}
y={y + size / 2}
textAnchor="middle"
dominantBaseline="middle"
className="text-xs fill-slate-400 dark:fill-slate-600"
>
Empty
</text>
</g>
);
}
const healthColor = getHealthColor(bed.health_score);
const hasAlert = !!bed.last_alert;
return (
<>
<g
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
className="cursor-pointer transition-opacity hover:opacity-90"
>
{/* Main cell */}
<rect
x={x}
y={y}
width={size}
height={size}
fill={healthColor}
stroke={hasAlert ? '#dc2626' : '#64748b'}
strokeWidth={hasAlert ? '3' : '1'}
rx="4"
/>
{/* Health score text */}
<text
x={x + size / 2}
y={y + size / 2}
textAnchor="middle"
dominantBaseline="middle"
className="text-sm font-bold fill-white"
style={{ pointerEvents: 'none' }}
>
{bed.health_score}
</text>
{/* Alert indicator */}
{hasAlert && (
<circle
cx={x + size - 10}
cy={y + 10}
r="6"
fill="#dc2626"
stroke="white"
strokeWidth="2"
/>
)}
{/* Batch ID (small text) */}
{bed.plant_batch_id && (
<text
x={x + size / 2}
y={y + size - 8}
textAnchor="middle"
className="text-xs fill-white opacity-70"
style={{ pointerEvents: 'none' }}
>
{bed.plant_batch_id.substring(0, 8)}
</text>
)}
</g>
{/* Tooltip */}
{showTooltip && bed && (
<BedTooltip
bed={bed}
position={tooltipPosition}
onClose={() => setShowTooltip(false)}
/>
)}
</>
);
}