Added PlantTouchPoint and IPMSchedule models. Implemented touch-points and IPM controllers/routes. Updated frontend with Dashboard feed and IPM widgets.
133 lines
5.8 KiB
TypeScript
133 lines
5.8 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { batchesApi, Batch } from '../lib/batchesApi';
|
|
import { touchPointsApi, PlantTouchPoint, IPMSchedule } from '../lib/touchPointsApi';
|
|
import { Loader2, Droplets, Utensils, Scissors, Dumbbell, Search, ShieldCheck, Shovel, Sprout } from 'lucide-react';
|
|
|
|
export default function TouchPointPage() {
|
|
const navigate = useNavigate();
|
|
const [batches, setBatches] = useState<Batch[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [selectedBatch, setSelectedBatch] = useState<Batch | null>(null);
|
|
const [actionType, setActionType] = useState<string | null>(null);
|
|
const [notes, setNotes] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
// Load active batches
|
|
useEffect(() => {
|
|
batchesApi.getAll().then(data => {
|
|
setBatches(data.filter(b => b.status === 'ACTIVE'));
|
|
setIsLoading(false);
|
|
}).catch(err => {
|
|
console.error(err);
|
|
setIsLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
const actions = [
|
|
{ id: 'WATER', label: 'Water', icon: Droplets, color: 'text-blue-500 bg-blue-50 border-blue-200' },
|
|
{ id: 'FEED', label: 'Feed', icon: Utensils, color: 'text-green-500 bg-green-50 border-green-200' },
|
|
{ id: 'PRUNE', label: 'Prune', icon: Scissors, color: 'text-amber-500 bg-amber-50 border-amber-200' },
|
|
{ id: 'TRAIN', label: 'Train', icon: Dumbbell, color: 'text-purple-500 bg-purple-50 border-purple-200' },
|
|
{ id: 'INSPECT', label: 'Inspect', icon: Search, color: 'text-slate-500 bg-slate-50 border-slate-200' },
|
|
{ id: 'IPM', label: 'IPM', icon: ShieldCheck, color: 'text-red-500 bg-red-50 border-red-200' },
|
|
{ id: 'TRANSPLANT', label: 'Transplant', icon: Shovel, color: 'text-orange-500 bg-orange-50 border-orange-200' },
|
|
{ id: 'HARVEST', label: 'Harvest', icon: Sprout, color: 'text-emerald-500 bg-emerald-50 border-emerald-200' },
|
|
];
|
|
|
|
const handleSubmit = async () => {
|
|
if (!selectedBatch || !actionType) return;
|
|
setSubmitting(true);
|
|
try {
|
|
await touchPointsApi.create({
|
|
batchId: selectedBatch.id,
|
|
type: actionType as any,
|
|
notes,
|
|
});
|
|
// Reset
|
|
setActionType(null);
|
|
setNotes('');
|
|
alert('Touch point recorded!');
|
|
} catch (err) {
|
|
alert('Failed to record');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
if (isLoading) return <div className="flex justify-center p-8"><Loader2 className="animate-spin" /></div>;
|
|
|
|
if (!selectedBatch) {
|
|
return (
|
|
<div className="space-y-4">
|
|
<h1 className="text-2xl font-bold">Select Batch</h1>
|
|
<div className="grid gap-4">
|
|
{batches.map(batch => (
|
|
<button
|
|
key={batch.id}
|
|
onClick={() => setSelectedBatch(batch)}
|
|
className="p-4 bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 text-left hover:border-emerald-500 transition-colors"
|
|
>
|
|
<div className="font-bold text-lg">{batch.name}</div>
|
|
<div className="text-sm text-slate-500">{batch.strain}</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!actionType) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<button onClick={() => setSelectedBatch(null)} className="text-sm text-slate-500">← Back to Batches</button>
|
|
<div className="bg-white dark:bg-slate-800 p-4 rounded-xl border border-emerald-500/20">
|
|
<h2 className="text-xl font-bold">{selectedBatch.name}</h2>
|
|
<p className="text-sm text-slate-500">Record Interaction</p>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{actions.map(action => (
|
|
<button
|
|
key={action.id}
|
|
onClick={() => setActionType(action.id)}
|
|
className={`p-6 rounded-xl border-2 flex flex-col items-center gap-3 transition-all active:scale-95 ${action.color}`}
|
|
>
|
|
<action.icon className="w-8 h-8" />
|
|
<span className="font-bold">{action.label}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<button onClick={() => setActionType(null)} className="text-sm text-slate-500">← Back to Actions</button>
|
|
|
|
<h1 className="text-2xl font-bold flex items-center gap-2">
|
|
{actions.find(a => a.id === actionType)?.label}
|
|
<span className="text-slate-400">for {selectedBatch.name}</span>
|
|
</h1>
|
|
|
|
<div className="space-y-4">
|
|
<label className="block text-sm font-medium">Notes (Optional)</label>
|
|
<textarea
|
|
value={notes}
|
|
onChange={e => setNotes(e.target.value)}
|
|
className="w-full p-4 rounded-xl border bg-white dark:bg-slate-800 shadow-sm h-32"
|
|
placeholder="Add details..."
|
|
/>
|
|
|
|
<button
|
|
onClick={handleSubmit}
|
|
disabled={submitting}
|
|
className="w-full py-4 bg-emerald-600 text-white font-bold rounded-xl shadow-lg hover:bg-emerald-700 disabled:opacity-50"
|
|
>
|
|
{submitting ? 'Saving...' : 'Save Record'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|