import { useState, useEffect } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import {
ArrowLeft, Calendar, Droplets, Thermometer, Wind, Zap,
Bug, Camera, Activity, TrendingUp, Clock, CheckCircle,
AlertTriangle, Leaf, Sun, Moon
} from 'lucide-react';
import { batchesApi, Batch } from '../lib/batchesApi';
// Mock sensor data generator
function generateMockData(days: number, min: number, max: number, variance: number = 5) {
return Array.from({ length: days }, (_, i) => ({
day: i + 1,
value: min + Math.random() * (max - min) + (Math.random() - 0.5) * variance,
timestamp: new Date(Date.now() - (days - i) * 24 * 60 * 60 * 1000).toISOString()
}));
}
// SVG Sparkline Component
function Sparkline({ data, color = '#3B82F6', height = 40 }: {
data: { value: number }[];
color?: string;
height?: number;
}) {
if (data.length === 0) return null;
const values = data.map(d => d.value);
const min = Math.min(...values);
const max = Math.max(...values);
const range = max - min || 1;
const width = 120;
const padding = 2;
const points = data.map((d, i) => {
const x = padding + (i / (data.length - 1)) * (width - padding * 2);
const y = height - padding - ((d.value - min) / range) * (height - padding * 2);
return `${x},${y}`;
}).join(' ');
return (
);
}
// Stage Progress Component
function StageProgress({ currentStage }: { currentStage: string }) {
const stages = [
{ id: 'CLONE_IN', label: 'Clone', icon: '🌱', days: 7 },
{ id: 'VEGETATIVE', label: 'Veg', icon: '🌿', days: 28 },
{ id: 'FLOWERING', label: 'Flower', icon: '🌸', days: 63 },
{ id: 'HARVEST', label: 'Harvest', icon: '✂️', days: 1 },
{ id: 'DRYING', label: 'Dry', icon: '🍂', days: 14 },
{ id: 'CURING', label: 'Cure', icon: '🫙', days: 30 },
];
const currentIndex = stages.findIndex(s => s.id === currentStage);
return (
{stages.map((stage, i) => {
const isPast = i < currentIndex;
const isCurrent = i === currentIndex;
const isFuture = i > currentIndex;
return (
{stage.icon}
{stage.label}
{i < stages.length - 1 && (
)}
);
})}
);
}
// Metric Card with Sparkline
function MetricCard({ icon: Icon, label, value, unit, trend, data, color }: {
icon: typeof Thermometer;
label: string;
value: string;
unit: string;
trend?: 'up' | 'down' | 'stable';
data: { value: number }[];
color: string;
}) {
return (
{trend && (
{trend === 'up' ? '↑' : trend === 'down' ? '↓' : '→'}
)}
);
}
// Touch Point Item
function TouchPoint({ type, date, user, notes }: {
type: string;
date: string;
user: string;
notes?: string;
}) {
const typeConfig: Record = {
WATER: { icon: Droplets, color: 'text-blue-500' },
FEED: { icon: Leaf, color: 'text-green-500' },
INSPECT: { icon: Bug, color: 'text-amber-500' },
PHOTO: { icon: Camera, color: 'text-purple-500' },
DEFOLIATE: { icon: Leaf, color: 'text-orange-500' },
TRANSPLANT: { icon: Activity, color: 'text-teal-500' },
};
const config = typeConfig[type] || { icon: Activity, color: 'text-secondary' };
const Icon = config.icon;
return (
{type}
{new Date(date).toLocaleDateString()}
{notes &&
{notes}
}
{user}
);
}
export default function BatchDetailPage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const [batch, setBatch] = useState(null);
const [loading, setLoading] = useState(true);
// Mock sensor data (will be replaced with real sensor API)
const [sensorData] = useState(() => ({
temperature: generateMockData(14, 72, 78),
humidity: generateMockData(14, 50, 65),
vpd: generateMockData(14, 0.8, 1.2),
co2: generateMockData(14, 800, 1200),
lightPPFD: generateMockData(14, 600, 900),
}));
useEffect(() => {
if (id) {
batchesApi.getById(id)
.then(setBatch)
.catch((err) => {
console.error('Failed to load batch:', err);
navigate('/batches');
})
.finally(() => setLoading(false));
}
}, [id, navigate]);
if (loading) {
return (
);
}
if (!batch) {
return (
Batch not found
← Back to batches
);
}
// Calculate days in stage
const startDate = new Date(batch.startDate);
const daysInCycle = Math.floor((Date.now() - startDate.getTime()) / (1000 * 60 * 60 * 24));
return (
{/* Header */}
{batch.name}
{batch.stage}
{batch.strain}
•
{batch.plantCount} plants
•
Day {daysInCycle}
{/* Stage Journey */}
Lifecycle
{/* Sensor Grid */}
{/* Two Column Layout */}
{/* Touch Points */}
Recent Activity
{batch.touchPoints?.length || 0} entries
{batch.touchPoints && batch.touchPoints.length > 0 ? (
batch.touchPoints.map((tp) => (
))
) : (
No activity yet
)}
{/* Stats & IPM */}
{/* Quick Stats */}
Statistics
{batch.touchPoints?.length || 0}
Touch Points
{/* IPM Schedule */}
{batch.ipmSchedule ? (
IPM Schedule
{batch.ipmSchedule.product || 'Preventative'}
Next: {new Date(batch.ipmSchedule.nextTreatment).toLocaleDateString()}
) : (
IPM Schedule
No IPM schedule configured
)}
{/* Health Score */}
Health Score
Excellent
Based on 14 day average
);
}