This commit is contained in:
Jake Kasper
2025-09-05 11:11:53 -04:00
parent 7789eadb61
commit c517b28f51
2 changed files with 61 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState } from 'react';
import { MapContainer, TileLayer, Polygon, Marker, Circle, Rectangle, Popup, useMapEvents, useMap } from 'react-leaflet';
import { MapContainer, TileLayer, Polygon, Marker, Circle, Rectangle, Popup, useMapEvents, useMap, Polyline } from 'react-leaflet';
import { Icon } from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { propertiesAPI, wateringAPI, equipmentAPI } from '../../services/api';
@@ -271,6 +271,15 @@ const Watering = () => {
return (2*R*Math.atan2(Math.sqrt(A),Math.sqrt(1-A)))*3.28084;
};
const bearingDegrees = (a,b) => {
const toRad = d=> d*Math.PI/180, toDeg = r=> (r*180/Math.PI+360)%360;
const lat1 = toRad(a.lat), lat2 = toRad(b.lat);
const dLng = toRad(b.lng - a.lng);
const y = Math.sin(dLng) * Math.cos(lat2);
const x = Math.cos(lat1)*Math.sin(lat2) - Math.sin(lat1)*Math.cos(lat2)*Math.cos(dLng);
return Math.round(toDeg(Math.atan2(y,x)));
};
// Build sector polygon (approx) for Leaflet
const sectorPolygon = (center, radiusFeet, startDeg, endDeg, steps=60) => {
const [clat, clng] = [Number(center.lat), Number(center.lng)];
@@ -560,7 +569,12 @@ const Watering = () => {
}}>Save</button>
<button className="btn-secondary" onClick={async ()=>{
await wateringAPI.deletePoint(selectedPointId);
setPoints(prev=> prev.filter(p=> p.id!==selectedPointId));
if (plan) {
const rs = await wateringAPI.getPlanPoints(plan.id);
setPoints(rs.data?.data?.points || []);
} else {
setPoints(prev=> prev.filter(p=> p.id!==selectedPointId));
}
setSelectedPointId(null); setEditForm(null);
}}>Delete</button>
</div>
@@ -572,9 +586,15 @@ const Watering = () => {
<div className="lg:col-span-3">
<div className="card p-0" style={{height:'70vh', position:'relative'}}>
<div className="flex items-center justify-between p-3 border-b">
<div className="text-sm text-gray-700">{guiding && currentPos && points[guideIndex] ? (
<>Go to point #{points[guideIndex].sequence}: {distanceFeet(currentPos, {lat: Number(points[guideIndex].lat), lng: Number(points[guideIndex].lng)}).toFixed(0)} ft away</>
) : 'Map'}</div>
<div className="text-sm text-gray-700">{guiding && currentPos && points[guideIndex] ? (()=>{
const tgt = {lat:Number(points[guideIndex].lat), lng:Number(points[guideIndex].lng)};
const dist = distanceFeet(currentPos, tgt);
const brg = bearingDegrees(currentPos, tgt);
if (dist <= 15) {
return <span className="text-green-700 font-semibold">Arrived at #{points[guideIndex].sequence} {dist.toFixed(0)} ft</span>;
}
return <> {brg}° {dist.toFixed(0)} ft Point #{points[guideIndex].sequence}</>;
})() : 'Map'}</div>
<div className="flex gap-2 items-center">
<label className="text-xs flex items-center gap-1">
<input type="checkbox" checked={satellite} onChange={(e)=> setSatellite(e.target.checked)} /> Satellite
@@ -601,6 +621,16 @@ const Watering = () => {
<Polygon key={s.id} positions={s.polygonData?.coordinates?.[0]||[]} pathOptions={{ color:'#16a34a', weight:2, fillOpacity:0.1 }} />
))}
{placing && <SprinklerPlacement onPlace={onPlace} />}
{guiding && currentPos && points[guideIndex] && (
<>
{/* Current location marker */}
<Marker position={[currentPos.lat, currentPos.lng]} icon={markerIcon('#0ea5e9')} />
{/* Line to target */}
<Polyline positions={[[currentPos.lat, currentPos.lng], [Number(points[guideIndex].lat), Number(points[guideIndex].lng)]]} pathOptions={{ color:'#0ea5e9', dashArray:'6 6' }} />
{/* Arrival ring */}
<Circle center={[Number(points[guideIndex].lat), Number(points[guideIndex].lng)]} radius={15*0.3048} pathOptions={{ color:'#22c55e', fillOpacity:0.15, weight:2 }} />
</>
)}
{/* Legend overlay */}
{points.length > 0 && (
<div style={{ position:'absolute', right:10, bottom:10, zIndex:1000 }}>
@@ -703,7 +733,15 @@ const Watering = () => {
<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>
<button className="btn-secondary" onClick={async ()=>{
await wateringAPI.deletePoint(pt.id);
if (plan) {
const rs = await wateringAPI.getPlanPoints(plan.id);
setPoints(rs.data?.data?.points || []);
} else {
setPoints(prev=> prev.filter(p=> p.id!==pt.id));
}
}}>Delete</button>
</div>
</div>
</Popup>