- DailyWalkthroughPage: Centered, compact start screen with minimal chrome - Summary: Statistical overview with compact cards - ReservoirChecklist: Single column centered, tighter spacing - IrrigationChecklist: Compact status rows, inline toggles - PlantHealthChecklist: Segmented health control, minimal layout - Layout: Remove theme toggle from desktop sidebar (cleaner)
192 lines
7.7 KiB
TypeScript
192 lines
7.7 KiB
TypeScript
import { useState } from 'react';
|
|
import { ArrowLeft, ChevronRight, Camera, X } from 'lucide-react';
|
|
|
|
interface Tank {
|
|
name: string;
|
|
type: 'VEG' | 'FLOWER';
|
|
}
|
|
|
|
interface ReservoirCheckData {
|
|
tankName: string;
|
|
tankType: 'VEG' | 'FLOWER';
|
|
levelPercent: number;
|
|
status: 'OK' | 'LOW' | 'CRITICAL';
|
|
photoUrl?: string;
|
|
notes?: string;
|
|
}
|
|
|
|
interface ReservoirChecklistProps {
|
|
onComplete: (checks: ReservoirCheckData[]) => void;
|
|
onBack: () => void;
|
|
isPhotoRequired: boolean;
|
|
}
|
|
|
|
export default function ReservoirChecklist({ onComplete, onBack, isPhotoRequired }: ReservoirChecklistProps) {
|
|
const tanks: Tank[] = [
|
|
{ name: 'Veg Tank 1', type: 'VEG' },
|
|
{ name: 'Veg Tank 2', type: 'VEG' },
|
|
{ name: 'Flower Tank 1', type: 'FLOWER' },
|
|
{ name: 'Flower Tank 2', type: 'FLOWER' },
|
|
];
|
|
|
|
const [checks, setChecks] = useState<Map<string, ReservoirCheckData>>(new Map());
|
|
const [currentTankIndex, setCurrentTankIndex] = useState(0);
|
|
const [levelPercent, setLevelPercent] = useState(100);
|
|
const [notes, setNotes] = useState('');
|
|
const [photo, setPhoto] = useState<string | null>(null);
|
|
|
|
const currentTank = tanks[currentTankIndex];
|
|
const isLastTank = currentTankIndex === tanks.length - 1;
|
|
|
|
const getStatus = (level: number): 'OK' | 'LOW' | 'CRITICAL' => {
|
|
if (level >= 70) return 'OK';
|
|
if (level >= 30) return 'LOW';
|
|
return 'CRITICAL';
|
|
};
|
|
|
|
const handleNext = () => {
|
|
if (isPhotoRequired && !photo) {
|
|
alert('Photo is required for this step.');
|
|
return;
|
|
}
|
|
|
|
const checkData: ReservoirCheckData = {
|
|
tankName: currentTank.name,
|
|
tankType: currentTank.type,
|
|
levelPercent,
|
|
status: getStatus(levelPercent),
|
|
notes: notes || undefined,
|
|
photoUrl: photo || undefined
|
|
};
|
|
|
|
const newChecks = new Map(checks);
|
|
newChecks.set(currentTank.name, checkData);
|
|
setChecks(newChecks);
|
|
|
|
if (isLastTank) {
|
|
onComplete(Array.from(newChecks.values()));
|
|
} else {
|
|
setCurrentTankIndex(currentTankIndex + 1);
|
|
setLevelPercent(100);
|
|
setNotes('');
|
|
setPhoto(null);
|
|
}
|
|
};
|
|
|
|
const status = getStatus(levelPercent);
|
|
const statusConfig = {
|
|
OK: { color: 'bg-success', text: 'Good' },
|
|
LOW: { color: 'bg-warning', text: 'Low' },
|
|
CRITICAL: { color: 'bg-destructive', text: 'Critical' },
|
|
}[status];
|
|
|
|
return (
|
|
<div className="min-h-screen bg-primary flex items-center justify-center p-4">
|
|
<div className="w-full max-w-sm">
|
|
{/* Header */}
|
|
<div className="flex items-center gap-3 mb-6">
|
|
<button onClick={onBack} className="p-2 -ml-2 hover:bg-tertiary rounded-md transition-colors">
|
|
<ArrowLeft size={16} className="text-secondary" />
|
|
</button>
|
|
<div className="flex-1">
|
|
<p className="text-xs text-tertiary">Reservoir {currentTankIndex + 1}/{tanks.length}</p>
|
|
<h1 className="text-lg font-semibold text-primary">{currentTank.name}</h1>
|
|
</div>
|
|
<span className="text-xs text-tertiary">{currentTank.type}</span>
|
|
</div>
|
|
|
|
{/* Progress */}
|
|
<div className="flex gap-1 mb-6">
|
|
{tanks.map((_, i) => (
|
|
<div
|
|
key={i}
|
|
className={`h-1 flex-1 rounded-full ${i <= currentTankIndex ? 'bg-accent' : 'bg-tertiary'}`}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Level Indicator */}
|
|
<div className="card p-4 mb-4">
|
|
<div className="relative h-32 bg-tertiary rounded-md overflow-hidden mb-3">
|
|
<div
|
|
className={`absolute bottom-0 left-0 right-0 ${statusConfig.color} transition-all duration-200`}
|
|
style={{ height: `${levelPercent}%` }}
|
|
/>
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<span className="text-3xl font-semibold text-white drop-shadow-sm">{levelPercent}%</span>
|
|
</div>
|
|
</div>
|
|
|
|
<input
|
|
type="range"
|
|
min="0"
|
|
max="100"
|
|
value={levelPercent}
|
|
onChange={(e) => setLevelPercent(parseInt(e.target.value))}
|
|
className="w-full h-1.5 bg-tertiary rounded-full appearance-none cursor-pointer accent-accent"
|
|
/>
|
|
|
|
<div className={`mt-3 text-center text-sm font-medium ${status === 'OK' ? 'text-success' : status === 'LOW' ? 'text-warning' : 'text-destructive'
|
|
}`}>
|
|
{statusConfig.text}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Notes - compact */}
|
|
<div className="mb-4">
|
|
<input
|
|
type="text"
|
|
value={notes}
|
|
onChange={(e) => setNotes(e.target.value)}
|
|
placeholder="Notes (optional)"
|
|
className="input w-full text-sm"
|
|
/>
|
|
</div>
|
|
|
|
{/* Photo - only show when needed */}
|
|
{(isPhotoRequired || status !== 'OK') && (
|
|
<div className="mb-4">
|
|
<input
|
|
type="file"
|
|
id="photo-upload"
|
|
className="hidden"
|
|
accept="image/*"
|
|
capture="environment"
|
|
onChange={(e) => {
|
|
if (e.target.files?.[0]) {
|
|
setPhoto(URL.createObjectURL(e.target.files[0]));
|
|
}
|
|
}}
|
|
/>
|
|
{photo ? (
|
|
<div className="relative">
|
|
<img src={photo} alt="Preview" className="w-full h-24 object-cover rounded-md" />
|
|
<button
|
|
onClick={() => setPhoto(null)}
|
|
className="absolute top-1 right-1 p-1 bg-black/50 rounded-full"
|
|
>
|
|
<X size={12} className="text-white" />
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<label
|
|
htmlFor="photo-upload"
|
|
className={`flex items-center justify-center gap-2 p-3 border border-dashed rounded-md cursor-pointer text-sm ${isPhotoRequired ? 'border-destructive/50 text-destructive' : 'border-subtle text-tertiary'
|
|
}`}
|
|
>
|
|
<Camera size={14} />
|
|
{isPhotoRequired ? 'Photo required' : 'Add photo'}
|
|
</label>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Actions */}
|
|
<button onClick={handleNext} className="btn btn-primary w-full">
|
|
{isLastTank ? 'Complete' : 'Next'}
|
|
{!isLastTank && <ChevronRight size={14} />}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|