Added PlantTouchPoint and IPMSchedule models. Implemented touch-points and IPM controllers/routes. Updated frontend with Dashboard feed and IPM widgets.
154 lines
7.8 KiB
TypeScript
154 lines
7.8 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Save, CheckSquare, Settings } from 'lucide-react';
|
|
import { settingsApi, WalkthroughSettings, PhotoRequirement } from '../lib/settingsApi';
|
|
|
|
const PHOTO_OPTIONS: { label: string; value: PhotoRequirement }[] = [
|
|
{ label: 'Always Required', value: 'REQUIRED' },
|
|
{ label: 'Optional', value: 'OPTIONAL' },
|
|
{ label: 'Weekly Only', value: 'WEEKLY' },
|
|
{ label: 'On Demand', value: 'ON_DEMAND' },
|
|
];
|
|
|
|
export default function WalkthroughSettingsPage() {
|
|
const [settings, setSettings] = useState<WalkthroughSettings | null>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
loadSettings();
|
|
}, []);
|
|
|
|
const loadSettings = async () => {
|
|
try {
|
|
const data = await settingsApi.getWalkthrough();
|
|
setSettings(data);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
};
|
|
|
|
const handleUpdate = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!settings) return;
|
|
|
|
setIsLoading(true);
|
|
try {
|
|
await settingsApi.updateWalkthrough(settings);
|
|
alert('Settings saved!');
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert('Failed to save settings.');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
if (!settings) return <div>Loading...</div>;
|
|
|
|
return (
|
|
<div className="max-w-4xl mx-auto space-y-6 pb-20">
|
|
<header>
|
|
<h1 className="text-2xl font-bold text-slate-900 dark:text-white flex items-center gap-2">
|
|
<Settings className="text-emerald-600" />
|
|
Walkthrough Settings
|
|
</h1>
|
|
<p className="text-slate-500 dark:text-slate-400 text-sm">
|
|
Configure daily checklist requirements and photo rules.
|
|
</p>
|
|
</header>
|
|
|
|
<form onSubmit={handleUpdate} className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
|
|
{/* Enabled Sections */}
|
|
<div className="bg-white dark:bg-slate-800 p-6 rounded-xl border border-slate-200 dark:border-slate-700 space-y-4">
|
|
<h3 className="text-lg font-bold text-slate-900 dark:text-white flex items-center gap-2">
|
|
<CheckSquare size={18} /> Enabled Modules
|
|
</h3>
|
|
<div className="space-y-2">
|
|
<label className="flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={settings.enableReservoirs}
|
|
onChange={e => setSettings({ ...settings, enableReservoirs: e.target.checked })}
|
|
className="w-5 h-5 rounded border-slate-300 text-emerald-600 focus:ring-emerald-500"
|
|
/>
|
|
<span className="font-medium text-slate-700 dark:text-slate-300">Reservoir Checks</span>
|
|
</label>
|
|
<label className="flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={settings.enableIrrigation}
|
|
onChange={e => setSettings({ ...settings, enableIrrigation: e.target.checked })}
|
|
className="w-5 h-5 rounded border-slate-300 text-emerald-600 focus:ring-emerald-500"
|
|
/>
|
|
<span className="font-medium text-slate-700 dark:text-slate-300">Irrigation Checks</span>
|
|
</label>
|
|
<label className="flex items-center gap-3 p-3 bg-slate-50 dark:bg-slate-700/50 rounded-lg cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={settings.enablePlantHealth}
|
|
onChange={e => setSettings({ ...settings, enablePlantHealth: e.target.checked })}
|
|
className="w-5 h-5 rounded border-slate-300 text-emerald-600 focus:ring-emerald-500"
|
|
/>
|
|
<span className="font-medium text-slate-700 dark:text-slate-300">Plant Health Checks</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Photo Requirements */}
|
|
<div className="bg-white dark:bg-slate-800 p-6 rounded-xl border border-slate-200 dark:border-slate-700 space-y-4">
|
|
<h3 className="text-lg font-bold text-slate-900 dark:text-white flex items-center gap-2">
|
|
<Save size={18} /> Photo Rules
|
|
</h3>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-sm font-bold text-slate-700 dark:text-slate-300 mb-1">Reservoirs</label>
|
|
<select
|
|
value={settings.reservoirPhotos}
|
|
onChange={e => setSettings({ ...settings, reservoirPhotos: e.target.value as PhotoRequirement })}
|
|
className="w-full p-2 bg-slate-50 dark:bg-slate-700 rounded-lg border-none"
|
|
>
|
|
{PHOTO_OPTIONS.map(opt => (
|
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-bold text-slate-700 dark:text-slate-300 mb-1">Irrigation</label>
|
|
<select
|
|
value={settings.irrigationPhotos}
|
|
onChange={e => setSettings({ ...settings, irrigationPhotos: e.target.value as PhotoRequirement })}
|
|
className="w-full p-2 bg-slate-50 dark:bg-slate-700 rounded-lg border-none"
|
|
>
|
|
{PHOTO_OPTIONS.map(opt => (
|
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm font-bold text-slate-700 dark:text-slate-300 mb-1">Plant Health</label>
|
|
<select
|
|
value={settings.plantHealthPhotos}
|
|
onChange={e => setSettings({ ...settings, plantHealthPhotos: e.target.value as PhotoRequirement })}
|
|
className="w-full p-2 bg-slate-50 dark:bg-slate-700 rounded-lg border-none"
|
|
>
|
|
{PHOTO_OPTIONS.map(opt => (
|
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="md:col-span-2">
|
|
<button
|
|
type="submit"
|
|
disabled={isLoading}
|
|
className="w-full md:w-auto px-8 py-3 bg-emerald-600 hover:bg-emerald-700 text-white font-bold rounded-xl flex items-center justify-center gap-2 transition-colors disabled:opacity-50"
|
|
>
|
|
{isLoading ? 'Saving...' : 'Save Configuration'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|