refactor: consolidate Daily Walkthrough to single page
Some checks are pending
Deploy to Production / deploy (push) Waiting to run
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

- All sections (reservoirs, irrigation, plant health) on one scrollable page
- Collapsible sections with progress indicators
- Compact inline check rows (no oversized tank visualizations)
- Thin level indicator bars instead of large tank graphics
- Fixed submit button at bottom
- Edit mode per row for quick adjustments
This commit is contained in:
fullsizemalt 2025-12-12 18:53:08 -08:00
parent 20e8f994a1
commit 817abb732d

View file

@ -1,337 +1,470 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import ReservoirChecklist from '../components/walkthrough/ReservoirChecklist';
import IrrigationChecklist from '../components/walkthrough/IrrigationChecklist';
import PlantHealthChecklist from '../components/walkthrough/PlantHealthChecklist';
import { settingsApi, WalkthroughSettings, PhotoRequirement } from '../lib/settingsApi';
import { settingsApi, WalkthroughSettings } from '../lib/settingsApi';
import { walkthroughApi, ReservoirCheckData, IrrigationCheckData, PlantHealthCheckData } from '../lib/walkthroughApi';
import {
walkthroughApi,
ReservoirCheckData,
IrrigationCheckData,
PlantHealthCheckData
} from '../lib/walkthroughApi';
import { ArrowLeft, Check, Loader2, AlertCircle, Droplets, Sprout, Bug, ChevronRight, Clock } from 'lucide-react';
ArrowLeft, Check, Loader2, AlertCircle, Droplets, Sprout, Bug,
Camera, X, Minus, Plus, ChevronDown, ChevronUp
} from 'lucide-react';
import { useToast } from '../context/ToastContext';
type Step = 'start' | 'reservoir' | 'irrigation' | 'plant-health' | 'summary';
// Tank/Zone configs
const TANKS = [
{ name: 'Veg Tank 1', type: 'VEG' as const },
{ name: 'Veg Tank 2', type: 'VEG' as const },
{ name: 'Flower Tank 1', type: 'FLOWER' as const },
{ name: 'Flower Tank 2', type: 'FLOWER' as const },
];
const ZONES = [
{ name: 'Veg Upstairs', drippers: 48 },
{ name: 'Veg Downstairs', drippers: 48 },
{ name: 'Flower Upstairs', drippers: 64 },
{ name: 'Flower Downstairs', drippers: 64 },
];
const HEALTH_ZONES = ['Veg Upstairs', 'Veg Downstairs', 'Flower Upstairs', 'Flower Downstairs'];
export default function DailyWalkthroughPage() {
const navigate = useNavigate();
const { addToast } = useToast();
const [currentStep, setCurrentStep] = useState<Step>('start');
const [walkthroughId, setWalkthroughId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [reservoirChecks, setReservoirChecks] = useState<ReservoirCheckData[]>([]);
const [irrigationChecks, setIrrigationChecks] = useState<IrrigationCheckData[]>([]);
const [plantHealthChecks, setPlantHealthChecks] = useState<PlantHealthCheckData[]>([]);
const [isStarting, setIsStarting] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [settings, setSettings] = useState<WalkthroughSettings | null>(null);
// All check states
const [reservoirChecks, setReservoirChecks] = useState<Record<string, ReservoirCheckData>>({});
const [irrigationChecks, setIrrigationChecks] = useState<Record<string, IrrigationCheckData>>({});
const [plantHealthChecks, setPlantHealthChecks] = useState<Record<string, PlantHealthCheckData>>({});
// Section expansion
const [expandedSections, setExpandedSections] = useState<Record<string, boolean>>({
reservoirs: true,
irrigation: true,
plantHealth: true,
});
useEffect(() => {
settingsApi.getWalkthrough().then(setSettings).catch(console.error);
}, []);
const isPhotoRequired = (type: 'reservoir' | 'irrigation' | 'plantHealth') => {
if (!settings) return false;
const req = settings[`${type}Photos` as keyof WalkthroughSettings] as PhotoRequirement;
if (req === 'REQUIRED') return true;
if (req === 'WEEKLY') return new Date().getDay() === 1;
return false;
const toggleSection = (section: string) => {
setExpandedSections(prev => ({ ...prev, [section]: !prev[section] }));
};
const getNextStep = (current: Step): Step => {
if (!settings) return 'summary';
const sequence: Step[] = ['reservoir', 'irrigation', 'plant-health', 'summary'];
const isEnabled = (s: Step) => {
if (s === 'reservoir') return settings.enableReservoirs;
if (s === 'irrigation') return settings.enableIrrigation;
if (s === 'plant-health') return settings.enablePlantHealth;
return true;
};
if (current === 'start') return sequence.find(s => isEnabled(s)) || 'summary';
const idx = sequence.indexOf(current);
if (idx === -1) return 'summary';
for (let i = idx + 1; i < sequence.length; i++) {
if (isEnabled(sequence[i])) return sequence[i];
}
return 'summary';
};
const handleStartWalkthrough = async () => {
setIsLoading(true);
setError(null);
const handleStart = async () => {
setIsStarting(true);
try {
const walkthrough = await walkthroughApi.create();
setWalkthroughId(walkthrough.id);
setCurrentStep(getNextStep('start'));
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to start walkthrough');
addToast('Failed to start walkthrough', 'error');
} finally {
setIsLoading(false);
setIsStarting(false);
}
};
const handleReservoirComplete = async (checks: ReservoirCheckData[]) => {
const handleSubmit = async () => {
if (!walkthroughId) return;
setIsLoading(true);
setError(null);
setIsSubmitting(true);
try {
for (const check of checks) {
// Submit all checks
for (const check of Object.values(reservoirChecks)) {
await walkthroughApi.addReservoirCheck(walkthroughId, check);
}
setReservoirChecks(checks);
setCurrentStep(getNextStep('reservoir'));
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to save reservoir checks');
} finally {
setIsLoading(false);
}
};
const handleIrrigationComplete = async (checks: IrrigationCheckData[]) => {
if (!walkthroughId) return;
setIsLoading(true);
setError(null);
try {
for (const check of checks) {
for (const check of Object.values(irrigationChecks)) {
await walkthroughApi.addIrrigationCheck(walkthroughId, check);
}
setIrrigationChecks(checks);
setCurrentStep(getNextStep('irrigation'));
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to save irrigation checks');
} finally {
setIsLoading(false);
}
};
const handlePlantHealthComplete = async (checks: PlantHealthCheckData[]) => {
if (!walkthroughId) return;
setIsLoading(true);
setError(null);
try {
for (const check of checks) {
for (const check of Object.values(plantHealthChecks)) {
await walkthroughApi.addPlantHealthCheck(walkthroughId, check);
}
setPlantHealthChecks(checks);
setCurrentStep(getNextStep('plant-health'));
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to save plant health checks');
} finally {
setIsLoading(false);
}
};
const handleSubmitWalkthrough = async () => {
if (!walkthroughId) return;
setIsLoading(true);
setError(null);
try {
await walkthroughApi.complete(walkthroughId);
addToast('Walkthrough completed!', 'success');
navigate('/', { state: { message: 'Daily walkthrough completed!' } });
navigate('/');
} catch (err: any) {
setError(err.response?.data?.message || 'Failed to complete walkthrough');
addToast('Failed to submit walkthrough', 'error');
} finally {
setIsLoading(false);
setIsSubmitting(false);
}
};
// Render current step
if (currentStep === 'reservoir') {
return (
<ReservoirChecklist
onComplete={handleReservoirComplete}
onBack={() => setCurrentStep('start')}
isPhotoRequired={isPhotoRequired('reservoir')}
/>
);
}
if (currentStep === 'irrigation') {
return (
<IrrigationChecklist
onComplete={handleIrrigationComplete}
onBack={() => setCurrentStep('reservoir')}
isPhotoRequired={isPhotoRequired('irrigation')}
/>
);
}
if (currentStep === 'plant-health') {
return (
<PlantHealthChecklist
onComplete={handlePlantHealthComplete}
onBack={() => setCurrentStep('irrigation')}
isPhotoRequired={isPhotoRequired('plantHealth')}
/>
);
}
// Summary Step
if (currentStep === 'summary') {
const totalChecks = reservoirChecks.length + irrigationChecks.length + plantHealthChecks.length;
const issues = [
...reservoirChecks.filter(c => c.status !== 'OK'),
...irrigationChecks.filter(c => !c.waterFlow || !c.nutrientsMixed || c.drippersWorking < c.drippersTotal),
...plantHealthChecks.filter(c => c.healthStatus !== 'GOOD' || c.pestsObserved)
].length;
const totalChecks = Object.keys(reservoirChecks).length + Object.keys(irrigationChecks).length + Object.keys(plantHealthChecks).length;
const requiredChecks = TANKS.length + ZONES.length + HEALTH_ZONES.length;
const isComplete = totalChecks === requiredChecks;
// Pre-start view
if (!walkthroughId) {
return (
<div className="min-h-screen bg-primary flex items-center justify-center p-4">
<div className="w-full max-w-3xl">
{/* Compact header */}
<div className="text-center mb-8">
<h1 className="text-xl font-semibold text-primary">Review & Submit</h1>
</div>
{/* Summary stats */}
<div className="flex justify-center gap-6 mb-6">
<div className="text-center">
<div className="text-3xl font-semibold text-primary">{totalChecks}</div>
<div className="text-xs text-tertiary uppercase tracking-wider">Checks</div>
</div>
<div className="w-px bg-subtle" />
<div className="text-center">
<div className={`text-3xl font-semibold ${issues > 0 ? 'text-warning' : 'text-success'}`}>{issues}</div>
<div className="text-xs text-tertiary uppercase tracking-wider">Issues</div>
</div>
</div>
{/* Compact summary cards */}
<div className="grid grid-cols-3 gap-3 mb-6">
<SummaryCard
icon={Droplets}
label="Reservoirs"
count={reservoirChecks.length}
status={reservoirChecks.every(c => c.status === 'OK') ? 'good' : 'warning'}
/>
<SummaryCard
icon={Sprout}
label="Irrigation"
count={irrigationChecks.length}
status={irrigationChecks.every(c => c.waterFlow && c.nutrientsMixed) ? 'good' : 'warning'}
/>
<SummaryCard
icon={Bug}
label="Plant Health"
count={plantHealthChecks.length}
status={plantHealthChecks.every(c => c.healthStatus === 'GOOD' && !c.pestsObserved) ? 'good' : 'warning'}
/>
</div>
{error && (
<div className="mb-4 p-3 bg-destructive-muted text-destructive rounded-md text-sm flex items-center gap-2">
<AlertCircle size={14} />
{error}
</div>
)}
{/* Actions */}
<div className="flex gap-3">
<button
onClick={() => setCurrentStep('plant-health')}
disabled={isLoading}
className="btn btn-secondary px-6"
>
<ArrowLeft size={14} />
Back
</button>
<button
onClick={handleSubmitWalkthrough}
disabled={isLoading}
className="btn btn-primary flex-1"
>
{isLoading ? <Loader2 size={14} className="animate-spin" /> : <Check size={14} />}
{isLoading ? 'Submitting...' : 'Complete Walkthrough'}
</button>
</div>
</div>
</div>
);
}
// Start Screen - Refined
const today = new Date();
const greeting = today.getHours() < 12 ? 'Good morning' : today.getHours() < 17 ? 'Good afternoon' : 'Good evening';
return (
<div className="min-h-screen bg-primary flex items-center justify-center p-4">
<div className="w-full max-w-md">
{/* Minimal header */}
<div className="text-center mb-8">
<p className="text-sm text-tertiary mb-1">{today.toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })}</p>
<h1 className="text-xl font-semibold text-primary">{greeting}</h1>
</div>
{/* Task list - tight and intentional */}
<div className="card overflow-hidden">
<div className="p-4 border-b border-subtle">
<h2 className="text-sm font-medium text-primary">Daily Walkthrough</h2>
<p className="text-xs text-tertiary flex items-center gap-1 mt-0.5">
<Clock size={10} />
~15 min
<div className="w-full max-w-sm text-center">
<h1 className="text-xl font-semibold text-primary mb-2">Daily Walkthrough</h1>
<p className="text-sm text-tertiary mb-6">
{new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })}
</p>
</div>
<div className="divide-y divide-subtle">
<StepRow icon={Droplets} label="Reservoir Checks" />
<StepRow icon={Sprout} label="Irrigation System" />
<StepRow icon={Bug} label="Plant Health" />
</div>
{error && (
<div className="p-3 bg-destructive-muted text-destructive text-xs flex items-center gap-2">
<AlertCircle size={12} />
{error}
</div>
)}
<div className="p-4">
<button
onClick={handleStartWalkthrough}
disabled={isLoading}
onClick={handleStart}
disabled={isStarting}
className="btn btn-primary w-full"
>
{isLoading ? <Loader2 size={14} className="animate-spin" /> : 'Begin'}
{isStarting ? <Loader2 size={14} className="animate-spin" /> : 'Begin Walkthrough'}
</button>
</div>
</div>
);
}
return (
<div className="max-w-2xl mx-auto pb-24 space-y-4 animate-in">
{/* Header */}
<div className="flex items-center gap-3 mb-6">
<button onClick={() => navigate('/')} className="p-2 -ml-2 hover:bg-tertiary rounded-md">
<ArrowLeft size={16} className="text-secondary" />
</button>
<div>
<h1 className="text-lg font-semibold text-primary">Daily Walkthrough</h1>
<p className="text-xs text-tertiary">{totalChecks}/{requiredChecks} checks completed</p>
</div>
</div>
{/* Reservoirs Section */}
{settings?.enableReservoirs !== false && (
<CollapsibleSection
title="Reservoirs"
icon={Droplets}
count={Object.keys(reservoirChecks).length}
total={TANKS.length}
expanded={expandedSections.reservoirs}
onToggle={() => toggleSection('reservoirs')}
>
<div className="space-y-2">
{TANKS.map(tank => (
<ReservoirRow
key={tank.name}
tank={tank}
data={reservoirChecks[tank.name]}
onChange={(data) => setReservoirChecks(prev => ({ ...prev, [tank.name]: data }))}
/>
))}
</div>
</CollapsibleSection>
)}
{/* Irrigation Section */}
{settings?.enableIrrigation !== false && (
<CollapsibleSection
title="Irrigation"
icon={Sprout}
count={Object.keys(irrigationChecks).length}
total={ZONES.length}
expanded={expandedSections.irrigation}
onToggle={() => toggleSection('irrigation')}
>
<div className="space-y-2">
{ZONES.map(zone => (
<IrrigationRow
key={zone.name}
zone={zone}
data={irrigationChecks[zone.name]}
onChange={(data) => setIrrigationChecks(prev => ({ ...prev, [zone.name]: data }))}
/>
))}
</div>
</CollapsibleSection>
)}
{/* Plant Health Section */}
{settings?.enablePlantHealth !== false && (
<CollapsibleSection
title="Plant Health"
icon={Bug}
count={Object.keys(plantHealthChecks).length}
total={HEALTH_ZONES.length}
expanded={expandedSections.plantHealth}
onToggle={() => toggleSection('plantHealth')}
>
<div className="space-y-2">
{HEALTH_ZONES.map(zone => (
<PlantHealthRow
key={zone}
zoneName={zone}
data={plantHealthChecks[zone]}
onChange={(data) => setPlantHealthChecks(prev => ({ ...prev, [zone]: data }))}
/>
))}
</div>
</CollapsibleSection>
)}
{/* Fixed Submit Button */}
<div className="fixed bottom-0 left-0 right-0 p-4 bg-primary border-t border-default">
<div className="max-w-2xl mx-auto">
<button
onClick={handleSubmit}
disabled={!isComplete || isSubmitting}
className="btn btn-primary w-full"
>
{isSubmitting ? (
<><Loader2 size={14} className="animate-spin" /> Submitting...</>
) : isComplete ? (
<><Check size={14} /> Submit Walkthrough</>
) : (
`Complete all checks (${totalChecks}/${requiredChecks})`
)}
</button>
</div>
</div>
</div>
</div>
);
}
// Compact step row
function StepRow({ icon: Icon, label }: { icon: typeof Droplets; label: string }) {
return (
<div className="flex items-center gap-3 px-4 py-3">
<div className="w-7 h-7 rounded-md bg-tertiary flex items-center justify-center">
<Icon size={14} className="text-secondary" />
</div>
<span className="text-sm text-primary flex-1">{label}</span>
<ChevronRight size={14} className="text-tertiary" />
</div>
);
}
// Summary card
function SummaryCard({ icon: Icon, label, count, status }: {
// Collapsible Section
function CollapsibleSection({
title, icon: Icon, count, total, expanded, onToggle, children
}: {
title: string;
icon: typeof Droplets;
label: string;
count: number;
status: 'good' | 'warning';
total: number;
expanded: boolean;
onToggle: () => void;
children: React.ReactNode;
}) {
const isComplete = count === total;
return (
<div className="card p-4 text-center">
<div className={`w-8 h-8 rounded-md mx-auto mb-2 flex items-center justify-center ${status === 'good' ? 'bg-success-muted text-success' : 'bg-warning-muted text-warning'
<div className="card overflow-hidden">
<button
onClick={onToggle}
className="w-full flex items-center justify-between p-4 hover:bg-tertiary transition-colors"
>
<div className="flex items-center gap-3">
<div className={`w-8 h-8 rounded-md flex items-center justify-center ${isComplete ? 'bg-success-muted text-success' : 'bg-accent-muted text-accent'
}`}>
<Icon size={14} />
{isComplete ? <Check size={14} /> : <Icon size={14} />}
</div>
<div className="text-left">
<h3 className="text-sm font-medium text-primary">{title}</h3>
<p className="text-xs text-tertiary">{count}/{total} complete</p>
</div>
</div>
{expanded ? <ChevronUp size={16} className="text-tertiary" /> : <ChevronDown size={16} className="text-tertiary" />}
</button>
{expanded && <div className="p-4 pt-0 border-t border-subtle">{children}</div>}
</div>
);
}
// Reservoir Row - Compact inline
function ReservoirRow({
tank, data, onChange
}: {
tank: { name: string; type: 'VEG' | 'FLOWER' };
data?: ReservoirCheckData;
onChange: (data: ReservoirCheckData) => void;
}) {
const [level, setLevel] = useState(data?.levelPercent ?? 100);
const [editing, setEditing] = useState(!data);
const getStatus = (l: number) => l >= 70 ? 'OK' : l >= 30 ? 'LOW' : 'CRITICAL';
const handleSave = () => {
onChange({
tankName: tank.name,
tankType: tank.type,
levelPercent: level,
status: getStatus(level),
});
setEditing(false);
};
if (!editing && data) {
return (
<div className="flex items-center justify-between p-3 bg-tertiary rounded-md">
<div className="flex items-center gap-3">
<div className={`w-2 h-8 rounded-full ${data.status === 'OK' ? 'bg-success' : data.status === 'LOW' ? 'bg-warning' : 'bg-destructive'
}`} />
<div>
<span className="text-sm text-primary">{tank.name}</span>
<span className="text-xs text-tertiary ml-2">{data.levelPercent}%</span>
</div>
</div>
<button onClick={() => setEditing(true)} className="text-xs text-accent">Edit</button>
</div>
);
}
return (
<div className="p-3 bg-tertiary rounded-md">
<div className="flex items-center justify-between mb-2">
<span className="text-sm font-medium text-primary">{tank.name}</span>
<span className="text-xs text-tertiary">{tank.type}</span>
</div>
<div className="flex items-center gap-3">
<div className={`w-3 h-12 rounded-full overflow-hidden bg-subtle relative`}>
<div
className={`absolute bottom-0 left-0 right-0 transition-all ${getStatus(level) === 'OK' ? 'bg-success' : getStatus(level) === 'LOW' ? 'bg-warning' : 'bg-destructive'
}`}
style={{ height: `${level}%` }}
/>
</div>
<input
type="range"
min="0"
max="100"
value={level}
onChange={(e) => setLevel(parseInt(e.target.value))}
className="flex-1 h-1.5 bg-subtle rounded-full appearance-none cursor-pointer accent-accent"
/>
<span className="text-sm font-medium text-primary w-12 text-right">{level}%</span>
<button onClick={handleSave} className="btn btn-primary h-8 px-3 text-xs">Save</button>
</div>
</div>
);
}
// Irrigation Row - Compact inline
function IrrigationRow({
zone, data, onChange
}: {
zone: { name: string; drippers: number };
data?: IrrigationCheckData;
onChange: (data: IrrigationCheckData) => void;
}) {
const [working, setWorking] = useState(data?.drippersWorking ?? zone.drippers);
const [waterFlow, setWaterFlow] = useState(data?.waterFlow ?? true);
const [nutrients, setNutrients] = useState(data?.nutrientsMixed ?? true);
const [editing, setEditing] = useState(!data);
const handleSave = () => {
onChange({
zoneName: zone.name,
drippersTotal: zone.drippers,
drippersWorking: working,
drippersFailed: [],
waterFlow,
nutrientsMixed: nutrients,
scheduleActive: true,
});
setEditing(false);
};
if (!editing && data) {
const issues = !data.waterFlow || !data.nutrientsMixed || data.drippersWorking < data.drippersTotal;
return (
<div className="flex items-center justify-between p-3 bg-tertiary rounded-md">
<div className="flex items-center gap-3">
<div className={`w-2 h-8 rounded-full ${issues ? 'bg-warning' : 'bg-success'}`} />
<div>
<span className="text-sm text-primary">{zone.name}</span>
<span className="text-xs text-tertiary ml-2">{data.drippersWorking}/{data.drippersTotal}</span>
</div>
</div>
<button onClick={() => setEditing(true)} className="text-xs text-accent">Edit</button>
</div>
);
}
return (
<div className="p-3 bg-tertiary rounded-md space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-primary">{zone.name}</span>
<div className="flex items-center gap-2">
<button onClick={() => setWorking(Math.max(0, working - 1))} className="w-6 h-6 bg-subtle rounded flex items-center justify-center">
<Minus size={12} />
</button>
<span className="text-sm w-16 text-center">{working}/{zone.drippers}</span>
<button onClick={() => setWorking(Math.min(zone.drippers, working + 1))} className="w-6 h-6 bg-subtle rounded flex items-center justify-center">
<Plus size={12} />
</button>
</div>
</div>
<div className="flex items-center gap-4">
<label className="flex items-center gap-2 text-xs">
<input type="checkbox" checked={waterFlow} onChange={() => setWaterFlow(!waterFlow)} className="w-4 h-4 rounded" />
Water
</label>
<label className="flex items-center gap-2 text-xs">
<input type="checkbox" checked={nutrients} onChange={() => setNutrients(!nutrients)} className="w-4 h-4 rounded" />
Nutrients
</label>
<div className="flex-1" />
<button onClick={handleSave} className="btn btn-primary h-8 px-3 text-xs">Save</button>
</div>
</div>
);
}
// Plant Health Row - Compact inline
function PlantHealthRow({
zoneName, data, onChange
}: {
zoneName: string;
data?: PlantHealthCheckData;
onChange: (data: PlantHealthCheckData) => void;
}) {
const [health, setHealth] = useState<'GOOD' | 'FAIR' | 'NEEDS_ATTENTION'>(data?.healthStatus ?? 'GOOD');
const [pests, setPests] = useState(data?.pestsObserved ?? false);
const [editing, setEditing] = useState(!data);
const handleSave = () => {
onChange({
zoneName,
healthStatus: health,
pestsObserved: pests,
waterAccess: 'OK',
foodAccess: 'OK',
flaggedForAttention: health !== 'GOOD' || pests,
});
setEditing(false);
};
if (!editing && data) {
const issues = data.healthStatus !== 'GOOD' || data.pestsObserved;
return (
<div className="flex items-center justify-between p-3 bg-tertiary rounded-md">
<div className="flex items-center gap-3">
<div className={`w-2 h-8 rounded-full ${data.healthStatus === 'GOOD' && !data.pestsObserved ? 'bg-success' :
data.healthStatus === 'FAIR' ? 'bg-warning' : 'bg-destructive'
}`} />
<div>
<span className="text-sm text-primary">{zoneName}</span>
<span className="text-xs text-tertiary ml-2">{data.healthStatus}</span>
{data.pestsObserved && <span className="text-xs text-destructive ml-2">🐛 Pests</span>}
</div>
</div>
<button onClick={() => setEditing(true)} className="text-xs text-accent">Edit</button>
</div>
);
}
return (
<div className="p-3 bg-tertiary rounded-md space-y-3">
<div className="flex items-center justify-between">
<span className="text-sm font-medium text-primary">{zoneName}</span>
</div>
<div className="flex items-center gap-2">
{(['GOOD', 'FAIR', 'NEEDS_ATTENTION'] as const).map(s => (
<button
key={s}
onClick={() => setHealth(s)}
className={`flex-1 py-1.5 rounded text-xs font-medium transition-colors ${health === s
? s === 'GOOD' ? 'bg-success text-white'
: s === 'FAIR' ? 'bg-warning text-white'
: 'bg-destructive text-white'
: 'bg-subtle text-secondary hover:bg-secondary'
}`}
>
{s === 'NEEDS_ATTENTION' ? 'Attention' : s}
</button>
))}
</div>
<div className="flex items-center justify-between">
<label className="flex items-center gap-2 text-xs">
<input type="checkbox" checked={pests} onChange={() => setPests(!pests)} className="w-4 h-4 rounded" />
Pests observed
</label>
<button onClick={handleSave} className="btn btn-primary h-8 px-3 text-xs">Save</button>
</div>
<div className="text-lg font-semibold text-primary">{count}</div>
<div className="text-[10px] text-tertiary uppercase tracking-wider">{label}</div>
</div>
);
}