115 lines
5.4 KiB
JavaScript
115 lines
5.4 KiB
JavaScript
import React, { useEffect, useState } from 'react';
|
|
import { propertiesAPI, equipmentAPI, mowingAPI } from '../../services/api';
|
|
import toast from 'react-hot-toast';
|
|
|
|
const directionOptions = [
|
|
{ value: 'N_S', label: 'North to South' },
|
|
{ value: 'E_W', label: 'East to West' },
|
|
{ value: 'NE_SW', label: 'NE to SW' },
|
|
{ value: 'NW_SE', label: 'NW to SE' },
|
|
{ value: 'CIRCULAR', label: 'Circular' },
|
|
];
|
|
|
|
const MowingPlanModal = ({ onClose, onCreated }) => {
|
|
const [properties, setProperties] = useState([]);
|
|
const [mowers, setMowers] = useState([]);
|
|
const [sections, setSections] = useState([]);
|
|
const [propertyId, setPropertyId] = useState('');
|
|
const [lawnSectionIds, setLawnSectionIds] = useState([]);
|
|
const [equipmentId, setEquipmentId] = useState('');
|
|
const [plannedDate, setPlannedDate] = useState(new Date().toISOString().slice(0,10));
|
|
const [cutHeightInches, setCutHeightInches] = useState(3.0);
|
|
const [direction, setDirection] = useState('N_S');
|
|
const [notes, setNotes] = useState('');
|
|
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
try {
|
|
const [props, eq] = await Promise.all([propertiesAPI.getAll(), equipmentAPI.getAll()]);
|
|
setProperties(props.data.data.properties || []);
|
|
const m = (eq.data.data.equipment || []).filter(e => (e.categoryName || '').toLowerCase().includes('mower'));
|
|
setMowers(m);
|
|
} catch (e) { toast.error('Failed to load data'); }
|
|
};
|
|
load();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const loadSections = async () => {
|
|
if (!propertyId) { setSections([]); return; }
|
|
try { const r = await propertiesAPI.getById(propertyId); setSections(r.data.data.property.sections || []);} catch {
|
|
setSections([]);
|
|
}
|
|
};
|
|
loadSections();
|
|
}, [propertyId]);
|
|
|
|
const create = async () => {
|
|
try {
|
|
if (!propertyId || lawnSectionIds.length === 0 || !equipmentId) { toast.error('Missing fields'); return; }
|
|
await mowingAPI.createPlan({ propertyId: Number(propertyId), lawnSectionIds: lawnSectionIds.map(Number), equipmentId: Number(equipmentId), plannedDate, cutHeightInches: Number(cutHeightInches), direction, notes });
|
|
toast.success('Mowing plan created');
|
|
onCreated?.();
|
|
onClose();
|
|
} catch (e) { toast.error(e.response?.data?.message || 'Failed to create plan'); }
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
|
<div className="bg-white rounded-lg p-6 w-full max-w-3xl max-h-[90vh] overflow-y-auto">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<h3 className="text-xl font-semibold">New Mowing Plan</h3>
|
|
<button onClick={onClose} className="text-gray-500 hover:text-gray-700">✕</button>
|
|
</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label className="block text-sm mb-1">Property</label>
|
|
<select className="w-full border rounded px-2 py-2" value={propertyId} onChange={(e)=>{setPropertyId(e.target.value); setLawnSectionIds([]);}}>
|
|
<option value="">Select…</option>
|
|
{properties.map(p=> <option key={p.id} value={p.id}>{p.name}</option>)}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">Planned Date</label>
|
|
<input type="date" value={plannedDate} onChange={(e)=> setPlannedDate(e.target.value)} className="w-full border rounded px-2 py-2" />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">Areas</label>
|
|
<select multiple className="w-full border rounded px-2 py-2 h-32" value={lawnSectionIds} onChange={(e)=> setLawnSectionIds(Array.from(e.target.selectedOptions).map(o=>o.value))}>
|
|
{sections.map(s=> <option key={s.id} value={s.id}>{s.name}</option>)}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">Mower</label>
|
|
<select className="w-full border rounded px-2 py-2" value={equipmentId} onChange={(e)=> setEquipmentId(e.target.value)}>
|
|
<option value="">Select…</option>
|
|
{mowers.map(m=> <option key={m.id} value={m.id}>{m.customName || m.manufacturer} {m.model}</option>)}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">Cut Height (in)</label>
|
|
<input type="number" step="0.25" className="w-full border rounded px-2 py-2" value={cutHeightInches} onChange={(e)=> setCutHeightInches(e.target.value)} />
|
|
</div>
|
|
<div>
|
|
<label className="block text-sm mb-1">Direction</label>
|
|
<select className="w-full border rounded px-2 py-2" value={direction} onChange={(e)=> setDirection(e.target.value)}>
|
|
{directionOptions.map(d=> <option key={d.value} value={d.value}>{d.label}</option>)}
|
|
</select>
|
|
</div>
|
|
<div className="md:col-span-2">
|
|
<label className="block text-sm mb-1">Notes</label>
|
|
<textarea className="w-full border rounded px-2 py-2" rows={3} value={notes} onChange={(e)=> setNotes(e.target.value)} />
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 flex justify-end gap-2">
|
|
<button className="btn-secondary" onClick={onClose}>Cancel</button>
|
|
<button className="btn-primary" onClick={create}>Create Plan</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MowingPlanModal;
|
|
|