asdfasdf
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { MapContainer, TileLayer, Polygon, Marker, Circle, Rectangle, useMapEvents, useMap } from 'react-leaflet';
|
||||
import { MapContainer, TileLayer, Polygon, Marker, Circle, Rectangle, Popup, useMapEvents, useMap } from 'react-leaflet';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { propertiesAPI, wateringAPI, equipmentAPI } from '../../services/api';
|
||||
import toast from 'react-hot-toast';
|
||||
@@ -50,6 +50,62 @@ const Watering = () => {
|
||||
const [editForm, setEditForm] = useState(null);
|
||||
const [satellite, setSatellite] = useState(false);
|
||||
|
||||
// Select a point helper (for popup+state sync)
|
||||
const onSelectPoint = (pt) => {
|
||||
setSelectedPointId(pt.id);
|
||||
setEditForm({
|
||||
durationMinutes: pt.duration_minutes || 0,
|
||||
mountType: pt.sprinkler_mount,
|
||||
sprinklerHeadType: pt.sprinkler_head_type,
|
||||
gpm: Number(pt.sprinkler_gpm||0),
|
||||
throwFeet: Number(pt.sprinkler_throw_feet||0),
|
||||
degrees: Number(pt.sprinkler_degrees||360),
|
||||
lengthFeet: Number(pt.sprinkler_length_feet||0),
|
||||
widthFeet: Number(pt.sprinkler_width_feet||0),
|
||||
headingDegrees: Number(pt.sprinkler_heading_degrees||0)
|
||||
});
|
||||
};
|
||||
|
||||
// Optimistic local patch + save to server
|
||||
const applyLocalPatch = (id, patch) => {
|
||||
setPoints(prev => prev.map(p => p.id === id ? { ...p,
|
||||
sprinkler_degrees: patch.degrees !== undefined ? patch.degrees : p.sprinkler_degrees,
|
||||
sprinkler_heading_degrees: patch.headingDegrees !== undefined ? patch.headingDegrees : p.sprinkler_heading_degrees,
|
||||
sprinkler_throw_feet: patch.throwFeet !== undefined ? patch.throwFeet : p.sprinkler_throw_feet,
|
||||
sprinkler_length_feet: patch.lengthFeet !== undefined ? patch.lengthFeet : p.sprinkler_length_feet,
|
||||
sprinkler_width_feet: patch.widthFeet !== undefined ? patch.widthFeet : p.sprinkler_width_feet,
|
||||
duration_minutes: patch.durationMinutes !== undefined ? patch.durationMinutes : p.duration_minutes,
|
||||
lat: patch.lat !== undefined ? patch.lat : p.lat,
|
||||
lng: patch.lng !== undefined ? patch.lng : p.lng
|
||||
} : p));
|
||||
};
|
||||
|
||||
const updatePointField = async (id, patch) => {
|
||||
try {
|
||||
applyLocalPatch(id, patch);
|
||||
const r = await wateringAPI.updatePoint(id, patch);
|
||||
const np = r.data?.data?.point;
|
||||
setPoints(prev => prev.map(p=> p.id===id? np: p));
|
||||
} catch(e){ toast.error('Failed to update point'); }
|
||||
};
|
||||
|
||||
const nudgePointLocation = (pt, dxFeet, dyFeet) => {
|
||||
const lat = Number(pt.lat), lng = Number(pt.lng);
|
||||
const Rlat = 111320; // meters per degree lat
|
||||
const Rlng = Math.cos(lat*Math.PI/180)*111320; // meters per degree lng
|
||||
const dxm = dxFeet*0.3048; // east-west
|
||||
const dym = dyFeet*0.3048; // north-south
|
||||
const newLat = lat + (dym / Rlat);
|
||||
const newLng = lng + (dxm / Rlng);
|
||||
updatePointField(pt.id, { lat: newLat, lng: newLng });
|
||||
};
|
||||
|
||||
const adjustHeading = (pt, delta) => {
|
||||
const cur = Number(pt.sprinkler_heading_degrees || 0);
|
||||
let next = (cur + delta) % 360; if (next < 0) next += 360;
|
||||
updatePointField(pt.id, { headingDegrees: next });
|
||||
};
|
||||
|
||||
useEffect(() => { (async () => {
|
||||
try {
|
||||
const [pr, eq] = await Promise.all([propertiesAPI.getAll(), equipmentAPI.getAll()]);
|
||||
@@ -388,6 +444,33 @@ const Watering = () => {
|
||||
click: ()=> onSelectPoint(pt)
|
||||
}}
|
||||
/>
|
||||
<Popup>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="font-medium">Adjust Sprinkler</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs">Heading:</span>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> adjustHeading(pt, -10)}>-10°</button>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> adjustHeading(pt, -1)}>-1°</button>
|
||||
<span className="px-2">{Number(pt.sprinkler_heading_degrees||0)}°</span>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> adjustHeading(pt, +1)}>+1°</button>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> adjustHeading(pt, +10)}>+10°</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 items-center">
|
||||
<div></div>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> nudgePointLocation(pt, 0, +1)}>▲</button>
|
||||
<div></div>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> nudgePointLocation(pt, -1, 0)}>◀</button>
|
||||
<div className="text-center text-xs">Move 1 ft</div>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> nudgePointLocation(pt, +1, 0)}>▶</button>
|
||||
<div></div>
|
||||
<button className="px-2 py-1 border rounded" onClick={()=> nudgePointLocation(pt, 0, -1)}>▼</button>
|
||||
<div></div>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-1">
|
||||
<button className="btn-secondary" onClick={async ()=>{ await wateringAPI.deletePoint(pt.id); setPoints(prev=> prev.filter(p=> p.id!==pt.id)); }}>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</Popup>
|
||||
{cov?.kind==='circle' && (
|
||||
cov.degrees && cov.degrees < 360 ? (
|
||||
<Polygon positions={sectorPolygon({lat:Number(pt.lat),lng:Number(pt.lng)}, cov.radius, (pt.sprinkler_heading_degrees||0), (pt.sprinkler_heading_degrees||0)+cov.degrees)} pathOptions={{ color:'#2563eb', fillOpacity:0.2 }} />
|
||||
|
||||
Reference in New Issue
Block a user