diff --git a/backend/src/routes/mowing.js b/backend/src/routes/mowing.js index c101ecf..6b5b78b 100644 --- a/backend/src/routes/mowing.js +++ b/backend/src/routes/mowing.js @@ -84,11 +84,16 @@ router.post('/sessions', validateRequest(mowingSessionSchema), async (req, res, router.get('/sessions', async (req, res, next) => { try { const result = await pool.query( - `SELECT ms.*, p.name as property_name, ue.custom_name as equipment_name + `SELECT ms.*, p.name as property_name, ue.custom_name as equipment_name, + STRING_AGG(ls.name, ', ') as section_names, + SUM(ls.area) as total_area FROM mowing_sessions ms JOIN properties p ON ms.property_id=p.id LEFT JOIN user_equipment ue ON ms.equipment_id=ue.id + JOIN mowing_session_sections lss ON lss.session_id = ms.id + JOIN lawn_sections ls ON lss.lawn_section_id = ls.id WHERE ms.user_id=$1 + GROUP BY ms.id, p.name, ue.custom_name ORDER BY ms.created_at DESC LIMIT 200`, [req.user.id] @@ -214,11 +219,16 @@ router.put('/plans/:id/status', async (req, res, next) => { router.get('/logs', async (req, res, next) => { try { const rs = await pool.query( - `SELECT ms.*, p.name as property_name, ue.custom_name as equipment_name + `SELECT ms.*, p.name as property_name, ue.custom_name as equipment_name, + STRING_AGG(ls.name, ', ') as section_names, + SUM(ls.area) as total_area FROM mowing_sessions ms JOIN properties p ON ms.property_id=p.id LEFT JOIN user_equipment ue ON ms.equipment_id=ue.id + JOIN mowing_session_sections lss ON lss.session_id = ms.id + JOIN lawn_sections ls ON lss.lawn_section_id = ls.id WHERE ms.user_id=$1 + GROUP BY ms.id, p.name, ue.custom_name ORDER BY ms.created_at DESC LIMIT 200`, [req.user.id]); res.json({ success: true, data: { logs: rs.rows } }); diff --git a/frontend/src/pages/History/History.js b/frontend/src/pages/History/History.js index b78c41c..38f1708 100644 --- a/frontend/src/pages/History/History.js +++ b/frontend/src/pages/History/History.js @@ -368,6 +368,19 @@ const History = () => { return items; })(); + // Coverage calculation for mowing when shown in unified list + const calculateMowingCoverage = (log) => { + const meters = (log.total_distance_meters || log.gpsTrack?.totalDistance || log.gps_track?.totalDistance || 0) || 0; + const totalDistanceFeet = meters * 3.28084; + const plannedArea = Number(log.total_area || 0); + if (totalDistanceFeet === 0 || plannedArea === 0) return 0; + let equipmentWidth = 6; // default mower deck width in ft + const name = (log.equipment_name || '').toLowerCase(); + if (name.includes('30"') || name.includes('30 in')) equipmentWidth = 2.5; + const theoreticalCoverageArea = totalDistanceFeet * equipmentWidth; + return Math.min(100, Math.round((theoreticalCoverageArea / plannedArea) * 100)); + }; + if (loading) { return (
@@ -655,9 +668,12 @@ const History = () => {
-

- {application.propertyName} - {application.sectionNames} -

+
+

+ {application.propertyName} - {application.sectionNames} +

+ Application +
{ // Mowing entry const log = item.log; - const durationMin = Math.round((log.duration_seconds || log.durationSeconds || log.gpsTrack?.duration || 0) / 60); + const durationMin = Math.round((log.duration_seconds || log.durationSeconds || log.gpsTrack?.duration || log.gps_track?.duration || 0) / 60); const avg = (log.average_speed_mph || log.averageSpeed || 0).toFixed?.(1) || Number(log.averageSpeed || 0).toFixed(1); - const distFeet = Math.round(((log.total_distance_meters || log.gpsTrack?.totalDistance || 0) * 3.28084) || 0); + const distFeet = Math.round(((log.total_distance_meters || log.gpsTrack?.totalDistance || log.gps_track?.totalDistance || 0) * 3.28084) || 0); return (
-
-
-
{log.property_name || 'Property'}
-
{new Date(item.date).toLocaleString()} • {log.equipment_name || 'Mower'} • {avg} mph • {distFeet} ft • {durationMin} min
+
+
+
+
+

+ {(log.property_name || 'Property')} - {(log.section_names || 'Areas')} +

+ Mowing +
+ Completed +
+ +
+
+ + {new Date(item.date).toLocaleString()} +
+
+ + {log.property_name} +
+
+ + {log.equipment_name || 'Mower'} +
+
+ +
+
+
Duration
+
{durationMin} min
+
+
+
GPS Points
+
{log.gpsTrack?.points?.length || log.gps_track?.points?.length || 0}
+
+
+
Distance
+
{distFeet} ft
+
+
+
Coverage
+
{calculateMowingCoverage(log)}%
+
+
+
+
+
-
); diff --git a/frontend/src/pages/Mowing/Mowing.js b/frontend/src/pages/Mowing/Mowing.js index 5f5e3d5..999e02d 100644 --- a/frontend/src/pages/Mowing/Mowing.js +++ b/frontend/src/pages/Mowing/Mowing.js @@ -12,7 +12,6 @@ const Mowing = () => { const [loading, setLoading] = useState(true); const [showPlanModal, setShowPlanModal] = useState(false); const [execPlan, setExecPlan] = useState(null); - const [sessions, setSessions] = useState([]); const [viewSession, setViewSession] = useState(null); const fetchPlans = async () => { @@ -20,11 +19,6 @@ const Mowing = () => { setLoading(true); const r = await mowingAPI.getPlans(); setPlans(r.data.data.plans || []); - // also load recent sessions - try { - const s = await mowingAPI.getLogs(); - setSessions(s.data?.data?.logs || []); - } catch {} } catch (e) { toast.error('Failed to load mowing plans'); } finally { @@ -75,29 +69,6 @@ const Mowing = () => { {p.notes && (

"{p.notes}"

)} - - {/* Recent sessions for this property */} - {sessions.filter(s => s.property_id === p.property_id).length > 0 && ( -
-

Recent Sessions

-
- {sessions.filter(s => s.property_id === p.property_id).slice(0,3).map(s => ( -
-
- {new Date(s.created_at || s.session_date).toLocaleString()} • {(s.average_speed_mph || s.averageSpeed || 0).toFixed?.(1) || Number(s.averageSpeed || 0).toFixed(1)} mph • {Math.round(((s.total_distance_meters || s.gpsTrack?.totalDistance || 0) * 3.28084) || 0)} ft -
- -
- ))} -
-
- )}

{p.planned_date ? new Date(p.planned_date).toLocaleDateString() : 'No date'}

@@ -127,40 +98,7 @@ const Mowing = () => { setExecPlan(null)} onComplete={fetchPlans} /> )} - {/* Recent Sessions */} -
-

Recent Sessions

-
-
-
Property
-
Mower
-
Cut Height
-
Avg Speed
-
Distance
-
Actions
-
- {sessions.length === 0 ? ( -
No sessions yet.
- ) : sessions.map((s) => ( -
-
{s.property_name}
-
{s.equipment_name || '—'}
-
{Number(s.cut_height_inches || 0).toFixed(2)}"
-
{(s.average_speed_mph || s.averageSpeed || 0).toFixed?.(1) || Number(s.averageSpeed || 0).toFixed(1)} mph
-
{Math.round(((s.total_distance_meters || s.gpsTrack?.totalDistance || 0) * 3.28084) || 0)} ft
-
- -
-
- ))} -
-
+ {/* Recent Sessions section removed as requested */} {viewSession && ( setViewSession(null)} />