feat: Pulse offline status indicator
This commit is contained in:
parent
1abb972d37
commit
22d0668ba1
1 changed files with 33 additions and 9 deletions
|
|
@ -24,6 +24,12 @@ interface PulseSensorCardProps {
|
||||||
export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardProps) {
|
export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Determine offline status (older than 15 mins)
|
||||||
|
const now = new Date();
|
||||||
|
const readingTime = new Date(reading.timestamp);
|
||||||
|
const diffMinutes = (now.getTime() - readingTime.getTime()) / 1000 / 60;
|
||||||
|
const isOffline = diffMinutes > 15;
|
||||||
|
|
||||||
// Determine status color based on VPD (gold standard for crop health)
|
// Determine status color based on VPD (gold standard for crop health)
|
||||||
const getStatusColor = (vpd: number) => {
|
const getStatusColor = (vpd: number) => {
|
||||||
if (vpd < 0.8 || vpd > 1.2) return 'text-amber-500'; // Warning
|
if (vpd < 0.8 || vpd > 1.2) return 'text-amber-500'; // Warning
|
||||||
|
|
@ -31,7 +37,11 @@ export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardPr
|
||||||
return 'text-emerald-500'; // Good
|
return 'text-emerald-500'; // Good
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusColor = getStatusColor(reading.vpd);
|
// If offline, use neutral/error color for metrics
|
||||||
|
const statusColor = isOffline ? 'text-slate-400 dark:text-slate-500' : getStatusColor(reading.vpd);
|
||||||
|
const badgeColor = isOffline
|
||||||
|
? 'text-rose-500 bg-rose-500/10 border-rose-500/20'
|
||||||
|
: cn("bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700", statusColor);
|
||||||
|
|
||||||
// Simple Sparkline
|
// Simple Sparkline
|
||||||
const Sparkline = ({ data, color = "#10b981", width = 120, height = 40 }: { data: number[], color?: string, width?: number, height?: number }) => {
|
const Sparkline = ({ data, color = "#10b981", width = 120, height = 40 }: { data: number[], color?: string, width?: number, height?: number }) => {
|
||||||
|
|
@ -74,7 +84,12 @@ export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardPr
|
||||||
>
|
>
|
||||||
<div className="flex justify-between items-start mb-4">
|
<div className="flex justify-between items-start mb-4">
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="p-2.5 rounded-xl bg-emerald-500/10 text-emerald-500 ring-1 ring-emerald-500/20">
|
<div className={cn(
|
||||||
|
"p-2.5 rounded-xl ring-1",
|
||||||
|
isOffline
|
||||||
|
? "bg-slate-100 text-slate-400 ring-slate-200 dark:bg-slate-800 dark:text-slate-500 dark:ring-slate-700"
|
||||||
|
: "bg-emerald-500/10 text-emerald-500 ring-emerald-500/20"
|
||||||
|
)}>
|
||||||
<Activity size={18} />
|
<Activity size={18} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -85,8 +100,8 @@ export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardPr
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cn("text-[10px] font-bold px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700", statusColor)}>
|
<div className={cn("text-[10px] font-bold px-2 py-0.5 rounded-full border", badgeColor)}>
|
||||||
{reading.timestamp ? 'LIVE' : 'OFFLINE'}
|
{isOffline ? 'OFFLINE' : 'LIVE'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -96,7 +111,7 @@ export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardPr
|
||||||
<Thermometer size={12} />
|
<Thermometer size={12} />
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wider">Temp</span>
|
<span className="text-[10px] font-bold uppercase tracking-wider">Temp</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-bold text-[var(--color-text-primary)]">
|
<p className={cn("text-2xl font-bold transition-colors", isOffline ? "text-slate-400" : "text-[var(--color-text-primary)]")}>
|
||||||
{reading.temperature.toFixed(1)}°
|
{reading.temperature.toFixed(1)}°
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -106,7 +121,7 @@ export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardPr
|
||||||
<Droplets size={12} />
|
<Droplets size={12} />
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wider">RH</span>
|
<span className="text-[10px] font-bold uppercase tracking-wider">RH</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-2xl font-bold text-[var(--color-text-primary)]">
|
<p className={cn("text-2xl font-bold transition-colors", isOffline ? "text-slate-400" : "text-[var(--color-text-primary)]")}>
|
||||||
{reading.humidity.toFixed(0)}%
|
{reading.humidity.toFixed(0)}%
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -116,17 +131,26 @@ export function PulseSensorCard({ reading, history, onClick }: PulseSensorCardPr
|
||||||
<CloudFog size={12} />
|
<CloudFog size={12} />
|
||||||
<span className="text-[10px] font-bold uppercase tracking-wider">VPD</span>
|
<span className="text-[10px] font-bold uppercase tracking-wider">VPD</span>
|
||||||
</div>
|
</div>
|
||||||
<p className={cn("text-2xl font-bold", statusColor)}>
|
<p className={cn("text-2xl font-bold transition-colors", statusColor)}>
|
||||||
{reading.vpd.toFixed(2)}
|
{reading.vpd.toFixed(2)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-end justify-end">
|
<div className="flex flex-col items-end justify-end">
|
||||||
{history && history.temperature && (
|
{history && history.temperature && !isOffline && (
|
||||||
<div className="mb-1">
|
<div className="mb-1">
|
||||||
<Sparkline data={history.temperature} color={reading.temperature > 80 ? '#f43f5e' : '#10b981'} />
|
<Sparkline data={history.temperature} color={reading.temperature > 80 ? '#f43f5e' : '#10b981'} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isOffline && (
|
||||||
|
<div className="text-right">
|
||||||
|
<p className="text-[10px] font-medium text-rose-500">Last Updated</p>
|
||||||
|
<p className="text-[10px] text-[var(--color-text-tertiary)]">
|
||||||
|
{readingTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue