diff --git a/frontend/src/pages/DailyWalkthroughPage.tsx b/frontend/src/pages/DailyWalkthroughPage.tsx index 03d96ac..6935469 100644 --- a/frontend/src/pages/DailyWalkthroughPage.tsx +++ b/frontend/src/pages/DailyWalkthroughPage.tsx @@ -2,18 +2,14 @@ import { useState, useEffect, useRef } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import { settingsApi, WalkthroughSettings } from '../lib/settingsApi'; import { walkthroughApi, ReservoirCheckData, IrrigationCheckData, PlantHealthCheckData, Walkthrough } from '../lib/walkthroughApi'; -import { documentsApi, Document } from '../lib/documentsApi'; import { Check, Loader2, Droplets, Sprout, Bug, ArrowLeft, - Camera, X, Minus, Plus, ChevronDown, ChevronUp, Upload, CheckCircle2, Clock, - FileText, AlertTriangle, Thermometer, Wind, Leaf, BookOpen, CircleCheck + Camera, X, Minus, Plus, ChevronDown, ChevronUp, Upload, CheckCircle2, Clock } from 'lucide-react'; import { useToast } from '../context/ToastContext'; -import { Card } from '../components/ui/card'; import { cn } from '../lib/utils'; -import { motion, AnimatePresence } from 'framer-motion'; -// Tank/Zone configs +// Tank/Zone configs - simplified for clarity const TANKS = [ { name: 'Veg Tank 1', type: 'VEG' as const }, { name: 'Veg Tank 2', type: 'VEG' as const }, @@ -39,7 +35,6 @@ export default function DailyWalkthroughPage() { const [isLoading, setIsLoading] = useState(true); const [settings, setSettings] = useState(null); const [todaysWalkthrough, setTodaysWalkthrough] = useState(null); - const [relatedSOPs, setRelatedSOPs] = useState([]); // All check states const [reservoirChecks, setReservoirChecks] = useState>({}); @@ -49,22 +44,20 @@ export default function DailyWalkthroughPage() { // Section expansion const [expandedSections, setExpandedSections] = useState>({ reservoirs: true, - irrigation: true, - plantHealth: true, + irrigation: false, + plantHealth: false, }); useEffect(() => { const loadData = async () => { setIsLoading(true); try { - const [settingsData, todayData, sops] = await Promise.all([ + const [settingsData, todayData] = await Promise.all([ settingsApi.getWalkthrough().catch(() => null), - walkthroughApi.getToday().catch(() => null), - documentsApi.getDocuments({ type: 'CHECKLIST', status: 'APPROVED' }).catch(() => []) + walkthroughApi.getToday().catch(() => null) ]); setSettings(settingsData); setTodaysWalkthrough(todayData); - setRelatedSOPs(sops); } catch (error) { console.error('Failed to load walkthrough data:', error); } finally { @@ -94,7 +87,6 @@ export default function DailyWalkthroughPage() { if (!walkthroughId) return; setIsSubmitting(true); try { - // Submit all checks for (const check of Object.values(reservoirChecks)) { await walkthroughApi.addReservoirCheck(walkthroughId, check); } @@ -119,94 +111,78 @@ export default function DailyWalkthroughPage() { const isComplete = totalChecks === requiredChecks; const progressPercent = Math.round((totalChecks / requiredChecks) * 100); - // Determine shift based on time - const currentHour = new Date().getHours(); - const shift = currentHour < 12 ? 'AM' : currentHour < 18 ? 'PM' : 'NIGHT'; - // Loading state if (isLoading) { return ( -
+
- -

Loading walkthrough...

+ +

Loading...

); } - // Already completed today - show success state + // Already completed today if (todaysWalkthrough?.status === 'COMPLETED' && !walkthroughId) { const completedTime = new Date(todaysWalkthrough.endTime || todaysWalkthrough.startTime); return ( -
- -
- +
+
+
+
-

- Walkthrough Complete -

-

+

Walkthrough Complete

+

{new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })}

- -
- - - Completed at {completedTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })} +
+
+ + + {completedTime.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}
-

- by {todaysWalkthrough.user?.name || 'Unknown'} -

- - + {todaysWalkthrough.user?.name && ( +

by {todaysWalkthrough.user.name}

+ )} +
+ Back to Dashboard - +
); } - // In-progress walkthrough from earlier + // In-progress walkthrough if (todaysWalkthrough?.status === 'IN_PROGRESS' && !walkthroughId) { return ( -
- -
- +
+
+
+
-

- In Progress -

-

- Started at {new Date(todaysWalkthrough.startTime).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })} +

Walkthrough In Progress

+

+ Started {new Date(todaysWalkthrough.startTime).toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}

- - โ† Back to Dashboard + + โ† Back - +
); } @@ -214,148 +190,93 @@ export default function DailyWalkthroughPage() { // Pre-start view if (!walkthroughId) { return ( -
- - {/* Header */} -
- -
-

- Daily Walkthrough -

-
- - {new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })} - - - {shift} Shift - +
+
+
+
+

Daily Walkthrough

+

+ {new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })} +

- {/* Checklist Preview */} - -

Today's Checklist

-
-
- - Reservoir Checks - {TANKS.length} tanks -
-
- - Irrigation Checks - {ZONES.length} zones -
-
- - Plant Health Checks - {HEALTH_ZONES.length} zones -
+ {/* Simple checklist preview */} +
+
+ + Reservoir Levels + {TANKS.length}
- {relatedSOPs.length > 0 && ( -
-
- - {relatedSOPs.length} related SOPs available -
-
- )} - +
+ + Irrigation Zones + {ZONES.length} +
+
+ + Plant Health + {HEALTH_ZONES.length} +
+
- + โ† Back to Dashboard - +
); } - + // Active walkthrough return ( -
- {/* Sticky Header */} -
-
+
+ {/* Header - fixed with progress */} +
+
-
- +
+
-

- Daily Walkthrough -

-
- - {new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - - - {shift} - -
+

Daily Walkthrough

+

+ {new Date().toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} +

- {/* Progress Ring */} -
-
- {progressPercent}% -

{totalChecks}/{requiredChecks}

-
-
- - - - - {isComplete && ( -
- -
- )} -
+ {/* Progress indicator */} +
+ {totalChecks} + / {requiredChecks}
+ + {/* Progress bar */} +
+
+
- {/* Main Content */} -
- {/* Sections */} + {/* Sections */} +
+ {/* Reservoirs */} {settings?.enableReservoirs !== false && ( -
{TANKS.map(tank => ( - ))}
-
+ )} + {/* Irrigation */} {settings?.enableIrrigation !== false && ( -
{ZONES.map(zone => ( - ))}
-
+ )} + {/* Plant Health */} {settings?.enablePlantHealth !== false && ( -
{HEALTH_ZONES.map(zone => ( - ))}
-
+ )}
- {/* Fixed Submit Button */} -
-
+ {/* Submit button - fixed bottom */} +
+
@@ -448,135 +367,87 @@ export default function DailyWalkthroughPage() { ); } -// Collapsible Section -function CollapsibleSection({ - title, icon: Icon, iconColor, count, total, expanded, onToggle, children +// Section component +function Section({ + title, icon: Icon, count, total, expanded, onToggle, children }: { title: string; icon: typeof Droplets; - iconColor: string; count: number; total: number; expanded: boolean; onToggle: () => void; children: React.ReactNode; }) { - const isComplete = count === total; + const done = count === total; return ( - +
- - {expanded && ( - -
{children}
-
- )} -
- + {expanded &&
{children}
} +
); } -// Inline Photo Capture Component -function PhotoCapture({ - onCapture, - photoUrl -}: { - onCapture: (url: string | null) => void; - photoUrl?: string | null; -}) { - const fileInputRef = useRef(null); - const cameraInputRef = useRef(null); +// Photo capture - mobile optimized +function PhotoButton({ photoUrl, onCapture }: { photoUrl?: string | null; onCapture: (url: string | null) => void }) { + const inputRef = useRef(null); const handleFile = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; - if (file) { - const url = URL.createObjectURL(file); - onCapture(url); - } + if (file) onCapture(URL.createObjectURL(file)); }; if (photoUrl) { return ( -
- Captured +
+ Photo
); } return ( -
- - - + <> + - -
+ ); } -// Reservoir Row -function ReservoirRow({ - tank, data, onChange -}: { +// Reservoir card - large touch targets +function ReservoirCard({ tank, data, onChange }: { tank: { name: string; type: 'VEG' | 'FLOWER' }; data?: ReservoirCheckData; onChange: (data: ReservoirCheckData) => void; @@ -584,10 +455,9 @@ function ReservoirRow({ const [level, setLevel] = useState(data?.levelPercent ?? 100); const [photo, setPhoto] = useState(data?.photoUrl || null); const [notes, setNotes] = useState(data?.notes || ''); - const [editing, setEditing] = useState(!data); const getStatus = (l: number) => l >= 70 ? 'OK' : l >= 30 ? 'LOW' : 'CRITICAL'; - const statusColor = getStatus(level) === 'OK' ? 'emerald' : getStatus(level) === 'LOW' ? 'amber' : 'red'; + const statusColor = getStatus(level) === 'OK' ? 'success' : getStatus(level) === 'LOW' ? 'warning' : 'destructive'; const handleSave = () => { onChange({ @@ -598,63 +468,61 @@ function ReservoirRow({ photoUrl: photo || undefined, notes: notes || undefined, }); - setEditing(false); }; - if (!editing && data) { + // If already saved, show compact view + if (data) { return ( -
-
-
+
+
+
- {tank.name} -
- {data.levelPercent}% - {data.photoUrl && } -
+ {tank.name} +

{data.levelPercent}%

- +
); } return ( -
+
+ {/* Header */}
- {tank.name} + {tank.name} {tank.type}
- {/* Level Slider */} -
-
-
+ {/* Level - large slider for touch */} +
+
+ Level + {level}%
-
- setLevel(parseInt(e.target.value))} - className="w-full h-2 bg-slate-700 rounded-full appearance-none cursor-pointer accent-emerald-500" - /> + setLevel(parseInt(e.target.value))} + className="w-full h-3 bg-secondary rounded-full appearance-none cursor-pointer accent-accent" + /> +
+ Empty + Full
- - {level}% -
{/* Notes */} @@ -663,22 +531,22 @@ function ReservoirRow({ value={notes} onChange={(e) => setNotes(e.target.value)} placeholder="Notes (optional)" - className="w-full px-4 py-3 bg-slate-900 border border-slate-700 rounded-xl text-sm text-white placeholder:text-slate-500 focus:border-emerald-500/50 focus:outline-none" + className="input w-full py-3" /> - + {/* Photo */} + -
); } -// Irrigation Row -function IrrigationRow({ - zone, data, onChange -}: { +// Irrigation card +function IrrigationCard({ zone, data, onChange }: { zone: { name: string; drippers: number }; data?: IrrigationCheckData; onChange: (data: IrrigationCheckData) => void; @@ -687,7 +555,6 @@ function IrrigationRow({ const [waterFlow, setWaterFlow] = useState(data?.waterFlow ?? true); const [nutrients, setNutrients] = useState(data?.nutrientsMixed ?? true); const [photo, setPhoto] = useState(null); - const [editing, setEditing] = useState(!data); const handleSave = () => { onChange({ @@ -700,89 +567,85 @@ function IrrigationRow({ scheduleActive: true, photoUrl: photo || undefined, }); - setEditing(false); }; - if (!editing && data) { + if (data) { const issues = !data.waterFlow || !data.nutrientsMixed || data.drippersWorking < data.drippersTotal; return ( -
-
-
+
+
+
- {zone.name} -
- {data.drippersWorking}/{data.drippersTotal} drippers -
+ {zone.name} +

{data.drippersWorking}/{data.drippersTotal} drippers

- +
); } return ( -
-
- {zone.name} -
+
+ {zone.name} - {/* Dripper Count */} + {/* Dripper counter - large touch targets */}
- {working} - /{zone.drippers} -

drippers working

+ {working} + /{zone.drippers} +

drippers working

- {/* Checkboxes */} -
-
); } -// Plant Health Row -function PlantHealthRow({ - zoneName, data, onChange -}: { +// Health card +function HealthCard({ zoneName, data, onChange }: { zoneName: string; data?: PlantHealthCheckData; onChange: (data: PlantHealthCheckData) => void; @@ -791,7 +654,6 @@ function PlantHealthRow({ const [pests, setPests] = useState(data?.pestsObserved ?? false); const [photo, setPhoto] = useState(null); const [notes, setNotes] = useState(''); - const [editing, setEditing] = useState(!data); const handleSave = () => { onChange({ @@ -804,96 +666,81 @@ function PlantHealthRow({ issuePhotoUrl: photo || undefined, notes: notes || undefined, }); - setEditing(false); }; - if (!editing && data) { + if (data) { + const color = data.healthStatus === 'GOOD' && !data.pestsObserved ? 'success' : data.healthStatus === 'FAIR' ? 'warning' : 'destructive'; return ( -
-
-
+
+
+
- {zoneName} -
- - {data.healthStatus} - - {data.pestsObserved && ๐Ÿ›} - {data.issuePhotoUrl && } -
+ {zoneName} +

+ {data.healthStatus}{data.pestsObserved ? ' ยท ๐Ÿ› Pests' : ''} +

- +
); } return ( -
-
- {zoneName} -
+
+ {zoneName} - {/* Health Status Buttons */} + {/* Health - large touch targets */}
{(['GOOD', 'FAIR', 'NEEDS_ATTENTION'] as const).map(s => ( ))}
- {/* Pests Checkbox */} -
); diff --git a/frontend/src/pages/WalkthroughSettingsPage.tsx b/frontend/src/pages/WalkthroughSettingsPage.tsx index 63b7a51..2af9a11 100644 --- a/frontend/src/pages/WalkthroughSettingsPage.tsx +++ b/frontend/src/pages/WalkthroughSettingsPage.tsx @@ -1,12 +1,9 @@ import { useState, useEffect } from 'react'; import { Link } from 'react-router-dom'; -import { Save, CheckSquare, Settings, Camera, Loader2, ArrowLeft, BookOpen, FileText, Droplets, Sprout, Bug } from 'lucide-react'; +import { Save, CheckSquare, Camera, Loader2, ArrowLeft, Droplets, Sprout, Bug } from 'lucide-react'; import { settingsApi, WalkthroughSettings, PhotoRequirement } from '../lib/settingsApi'; -import { documentsApi, Document } from '../lib/documentsApi'; -import { Card } from '../components/ui/card'; import { useToast } from '../context/ToastContext'; import { cn } from '../lib/utils'; -import { motion } from 'framer-motion'; const PHOTO_OPTIONS: { label: string; value: PhotoRequirement }[] = [ { label: 'Always Required', value: 'REQUIRED' }, @@ -20,7 +17,6 @@ export default function WalkthroughSettingsPage() { const [settings, setSettings] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); - const [checklists, setChecklists] = useState([]); useEffect(() => { loadSettings(); @@ -29,12 +25,8 @@ export default function WalkthroughSettingsPage() { const loadSettings = async () => { setIsLoading(true); try { - const [data, docs] = await Promise.all([ - settingsApi.getWalkthrough(), - documentsApi.getDocuments({ type: 'CHECKLIST', status: 'APPROVED' }).catch(() => []) - ]); + const data = await settingsApi.getWalkthrough(); setSettings(data); - setChecklists(docs); } catch (e) { console.error(e); addToast('Failed to load settings', 'error'); @@ -61,73 +53,27 @@ export default function WalkthroughSettingsPage() { if (isLoading || !settings) { return ( -
+
- -

Loading settings...

+ +

Loading settings...

); } - // Toggle switch component - const Toggle = ({ checked, onChange, label, description, icon: Icon }: { - checked: boolean; - onChange: () => void; - label: string; - description?: string; - icon?: typeof Droplets; - }) => ( - - ); - return ( -
+
{/* Header */} -
-
-
- +
+
+
+
-

- Walkthrough Config -

-

- Configure daily checklist requirements -

+

Walkthrough Settings

+

Configure daily checklist

@@ -136,61 +82,51 @@ export default function WalkthroughSettingsPage() {
{/* Enabled Modules */} - -
-
- -
-
-

Enabled Modules

-

Choose which checks to include

-
+
+
+ + Enabled Modules
-
+
setSettings({ ...settings, enableReservoirs: !settings.enableReservoirs })} label="Reservoir Checks" - description="Tank levels and nutrient monitoring" + description="Tank levels and nutrients" icon={Droplets} /> setSettings({ ...settings, enableIrrigation: !settings.enableIrrigation })} label="Irrigation Checks" - description="Dripper counts and flow verification" + description="Dripper counts and flow" icon={Sprout} /> setSettings({ ...settings, enablePlantHealth: !settings.enablePlantHealth })} label="Plant Health Checks" - description="Visual inspections and pest monitoring" + description="Visual inspections" icon={Bug} />
- +
{/* Photo Requirements */} - -
-
- -
-
-

Photo Requirements

-

When to require photo documentation

-
+
+
+ + Photo Requirements
-
-
-
- - - {/* Linked SOPs */} - {checklists.length > 0 && ( - -
-
- -
-
-

Linked SOPs

-

Related checklist documents

-
-
-
- {checklists.map(doc => ( -
- -
- {doc.title} - v{doc.version} -
- - View - -
- ))} -
-
- )} +
@@ -282,3 +180,48 @@ export default function WalkthroughSettingsPage() {
); } + +// Toggle component +function Toggle({ checked, onChange, label, description, icon: Icon }: { + checked: boolean; + onChange: () => void; + label: string; + description?: string; + icon?: typeof Droplets; +}) { + return ( + + ); +}