Components: - FarmCard: Farm summary display with status and metrics - ZoneGrid: Multi-level zone layout visualization - ZoneDetailCard: Individual zone details with environment readings - EnvironmentGauge: Real-time environmental parameter display - BatchProgress: Crop batch progress tracking with health scores - RecipeSelector: Growing recipe browser and selector - AlertPanel: Environment alerts display and management - GrowthStageIndicator: Visual growth stage progress tracker - ResourceUsageChart: Energy/water usage analytics visualization Pages: - /vertical-farm: Dashboard with farm listing and stats - /vertical-farm/register: Multi-step farm registration form - /vertical-farm/[farmId]: Farm detail view with zones and alerts - /vertical-farm/[farmId]/zones: Zone management with batch starting - /vertical-farm/[farmId]/batches: Batch management and harvesting - /vertical-farm/[farmId]/analytics: Farm analytics and performance metrics
117 lines
4.5 KiB
TypeScript
117 lines
4.5 KiB
TypeScript
import { EnvironmentAlert } from '../../lib/vertical-farming/types';
|
|
|
|
interface AlertPanelProps {
|
|
alerts: EnvironmentAlert[];
|
|
onAcknowledge?: (alert: EnvironmentAlert) => void;
|
|
title?: string;
|
|
}
|
|
|
|
export default function AlertPanel({ alerts, onAcknowledge, title = 'Environment Alerts' }: AlertPanelProps) {
|
|
const activeAlerts = alerts.filter(a => !a.acknowledged);
|
|
const acknowledgedAlerts = alerts.filter(a => a.acknowledged);
|
|
|
|
const alertStyles: Record<string, { bg: string; border: string; icon: string }> = {
|
|
low: { bg: 'bg-blue-50', border: 'border-blue-400', icon: 'arrow-down' },
|
|
high: { bg: 'bg-orange-50', border: 'border-orange-400', icon: 'arrow-up' },
|
|
critical_low: { bg: 'bg-red-50', border: 'border-red-500', icon: 'exclamation' },
|
|
critical_high: { bg: 'bg-red-50', border: 'border-red-500', icon: 'exclamation' },
|
|
sensor_fault: { bg: 'bg-gray-50', border: 'border-gray-500', icon: 'warning' },
|
|
};
|
|
|
|
const formatParameter = (param: string) => {
|
|
const names: Record<string, string> = {
|
|
temperature: 'Temperature',
|
|
humidity: 'Humidity',
|
|
co2: 'CO2 Level',
|
|
ec: 'EC (Nutrient)',
|
|
ph: 'pH Level',
|
|
ppfd: 'Light (PPFD)',
|
|
};
|
|
return names[param] || param;
|
|
};
|
|
|
|
if (alerts.length === 0) {
|
|
return (
|
|
<div className="bg-green-50 rounded-lg p-4 text-center">
|
|
<p className="text-green-700 font-medium">All systems operating within normal parameters</p>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<div className="flex justify-between items-center">
|
|
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
|
|
<span className={`px-3 py-1 rounded-full text-sm font-medium ${
|
|
activeAlerts.some(a => a.type.includes('critical'))
|
|
? 'bg-red-100 text-red-800'
|
|
: activeAlerts.length > 0
|
|
? 'bg-yellow-100 text-yellow-800'
|
|
: 'bg-green-100 text-green-800'
|
|
}`}>
|
|
{activeAlerts.length} Active
|
|
</span>
|
|
</div>
|
|
|
|
{activeAlerts.length > 0 && (
|
|
<div className="space-y-2">
|
|
{activeAlerts.map((alert, idx) => {
|
|
const style = alertStyles[alert.type] || alertStyles.low;
|
|
return (
|
|
<div
|
|
key={`${alert.parameter}-${alert.timestamp}-${idx}`}
|
|
className={`rounded-lg p-4 border-l-4 ${style.bg} ${style.border}`}
|
|
>
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<span className={`font-semibold ${
|
|
alert.type.includes('critical') ? 'text-red-700' : 'text-gray-900'
|
|
}`}>
|
|
{alert.type.includes('critical') && '!! '}
|
|
{formatParameter(alert.parameter)}
|
|
{alert.type.includes('low') ? ' LOW' : ' HIGH'}
|
|
</span>
|
|
</div>
|
|
<p className="text-sm text-gray-700 mt-1">
|
|
Current: <span className="font-medium">{alert.value}</span>
|
|
{' | '}
|
|
Threshold: <span className="font-medium">{alert.threshold}</span>
|
|
</p>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
{new Date(alert.timestamp).toLocaleString()}
|
|
</p>
|
|
</div>
|
|
{onAcknowledge && (
|
|
<button
|
|
onClick={() => onAcknowledge(alert)}
|
|
className="px-3 py-1 text-sm bg-white border border-gray-300 rounded hover:bg-gray-50 transition"
|
|
>
|
|
Acknowledge
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{acknowledgedAlerts.length > 0 && (
|
|
<details className="text-sm">
|
|
<summary className="cursor-pointer text-gray-600 hover:text-gray-900">
|
|
{acknowledgedAlerts.length} acknowledged alert(s)
|
|
</summary>
|
|
<div className="mt-2 space-y-1 pl-4">
|
|
{acknowledgedAlerts.map((alert, idx) => (
|
|
<p key={idx} className="text-gray-500">
|
|
{formatParameter(alert.parameter)} {alert.type}: {alert.value}
|
|
<span className="text-gray-400"> - {new Date(alert.timestamp).toLocaleTimeString()}</span>
|
|
</p>
|
|
))}
|
|
</div>
|
|
</details>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|