ca-grow-ops-manager/frontend/src/components/heatmap/BedTooltip.tsx
fullsizemalt 47de301f77
Some checks are pending
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run
style: Dark/Light mode contrast audit
- Boost text contrast in both themes
- Strengthen border visibility (subtle borders now visible)
- Convert 39 files from hardcoded dark:/light: to CSS vars
- Tertiary text now more readable on both backgrounds
2025-12-27 12:12:10 -08:00

153 lines
6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useRef } from 'react';
import { Bed } from './GrowRoomHeatmap';
/**
* BedTooltip - Hover tooltip showing bed details
*
* Displays key information about a bed when hovering over it.
* Positioned near the mouse cursor.
*/
interface BedTooltipProps {
bed: Bed;
position: { x: number; y: number };
onClose: () => void;
}
export default function BedTooltip({ bed, position, onClose }: BedTooltipProps) {
const tooltipRef = useRef<HTMLDivElement>(null);
// Close on click outside
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (tooltipRef.current && !tooltipRef.current.contains(event.target as Node)) {
onClose();
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [onClose]);
const getHealthLabel = (score: number): string => {
if (score >= 90) return 'Excellent';
if (score >= 70) return 'Good';
if (score >= 50) return 'Fair';
if (score >= 30) return 'Needs Attention';
return 'Critical';
};
const getHealthColor = (score: number): string => {
if (score >= 90) return 'text-[var(--color-primary)] dark:text-emerald-400';
if (score >= 70) return 'text-[var(--color-primary)] dark:text-emerald-300';
if (score >= 50) return 'text-yellow-500 dark:text-yellow-300';
if (score >= 30) return 'text-orange-500 dark:text-orange-300';
return 'text-red-600 dark:text-red-400';
};
return (
<div
ref={tooltipRef}
className="fixed z-50 bg-[var(--color-bg-elevated)] rounded-lg shadow-2xl border border-[var(--color-border-default)] p-4 min-w-[280px]"
style={{
left: position.x + 10,
top: position.y + 10,
}}
>
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div>
<div className="text-sm font-medium text-slate-600 dark:text-[var(--color-text-tertiary)]">
Bed {bed.bed_id}
</div>
{bed.plant_batch_id && (
<div className="text-xs text-[var(--color-text-tertiary)] dark:text-[var(--color-text-tertiary)] mt-1">
Batch: {bed.plant_batch_id}
</div>
)}
</div>
<button
onClick={onClose}
className="text-[var(--color-text-tertiary)] hover:text-slate-600 dark:hover:text-slate-300"
>
</button>
</div>
{/* Health Score */}
<div className="mb-3">
<div className="flex items-center justify-between">
<span className="text-sm text-slate-600 dark:text-[var(--color-text-tertiary)]">
Health Score
</span>
<span className={`text-lg font-bold ${getHealthColor(bed.health_score)}`}>
{bed.health_score}
</span>
</div>
<div className="text-xs text-[var(--color-text-tertiary)] dark:text-[var(--color-text-tertiary)] mt-1">
{getHealthLabel(bed.health_score)}
</div>
</div>
{/* Sensors */}
{bed.sensors && (
<div className="space-y-2 mb-3">
<div className="text-xs font-medium text-slate-600 dark:text-[var(--color-text-tertiary)] mb-2">
Sensor Readings
</div>
<div className="grid grid-cols-2 gap-2">
{bed.sensors.temp !== undefined && (
<SensorReading
label="Temp"
value={`${bed.sensors.temp.toFixed(1)}°F`}
/>
)}
{bed.sensors.humidity !== undefined && (
<SensorReading
label="Humidity"
value={`${bed.sensors.humidity.toFixed(0)}%`}
/>
)}
{bed.sensors.ec !== undefined && (
<SensorReading
label="EC"
value={bed.sensors.ec.toFixed(2)}
/>
)}
{bed.sensors.par !== undefined && (
<SensorReading
label="PAR"
value={bed.sensors.par.toFixed(0)}
/>
)}
</div>
</div>
)}
{/* Last Alert */}
{bed.last_alert && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded p-2">
<div className="text-xs font-medium text-red-700 dark:text-red-400">
Last Alert: {bed.last_alert}
</div>
</div>
)}
{/* Actions */}
<div className="mt-3 pt-3 border-t border-[var(--color-border-default)]">
<button className="text-sm text-[var(--color-primary)] dark:text-emerald-400 hover:underline">
View Details
</button>
</div>
</div>
);
}
function SensorReading({ label, value }: { label: string; value: string }) {
return (
<div className="bg-slate-50 dark:bg-slate-700/50 rounded p-2">
<div className="text-xs text-[var(--color-text-tertiary)]">{label}</div>
<div className="text-sm font-medium text-[var(--color-text-primary)]">{value}</div>
</div>
);
}