Files
turftracker/frontend/src/pages/Mowing/Mowing.js
Jake Kasper f37b43bf4f zsdfv
2025-09-02 13:06:02 -05:00

119 lines
4.9 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import { mowingAPI } from '../../services/api';
import LoadingSpinner from '../../components/UI/LoadingSpinner';
import MowingPlanModal from '../../components/Mowing/MowingPlanModal';
import MowingExecutionModal from '../../components/Mowing/MowingExecutionModal';
import MowingSessionViewModal from '../../components/Mowing/MowingSessionViewModal';
import toast from 'react-hot-toast';
const Mowing = () => {
const [plans, setPlans] = useState([]);
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 () => {
try {
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 {
setLoading(false);
}
};
useEffect(() => { fetchPlans(); }, []);
if (loading) return (<div className="p-6"><LoadingSpinner /></div>);
return (
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-gray-900">Mowing</h1>
<button className="btn-primary" onClick={()=> setShowPlanModal(true)}>New Plan</button>
</div>
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="grid grid-cols-7 gap-4 p-4 border-b text-sm font-medium text-gray-600">
<div>Property</div>
<div>Areas</div>
<div>Date</div>
<div>Mower</div>
<div>Cut Height</div>
<div>Status</div>
<div>Actions</div>
</div>
{plans.length === 0 ? (
<div className="p-4 text-gray-600">No mowing plans yet.</div>
) : plans.map((p) => (
<div key={p.id} className="grid grid-cols-7 gap-4 p-4 border-b text-sm items-center">
<div className="font-medium">{p.property_name}</div>
<div className="truncate" title={p.section_names}>{p.section_names}</div>
<div>{new Date(p.planned_date).toLocaleDateString()}</div>
<div>{p.equipment_name || '—'}</div>
<div>{p.cut_height_inches}"</div>
<div><span className={`px-2 py-1 rounded text-xs ${p.status==='completed'?'bg-green-100 text-green-800': p.status==='planned'?'bg-blue-100 text-blue-800': p.status==='archived'?'bg-gray-100 text-gray-800':'bg-yellow-100 text-yellow-800'}`}>{p.status}</span></div>
<div className="flex gap-2">
<button className="btn-secondary" onClick={()=> setExecPlan(p)}>Execute</button>
{p.status !== 'archived' && (
<button className="px-3 py-1 text-xs rounded border" onClick={async ()=>{ await mowingAPI.updatePlanStatus(p.id, 'archived'); fetchPlans(); }}>Archive</button>
)}
</div>
</div>
))}
</div>
{showPlanModal && (
<MowingPlanModal onClose={()=> setShowPlanModal(false)} onCreated={fetchPlans} />
)}
{execPlan && (
<MowingExecutionModal plan={execPlan} onClose={()=> setExecPlan(null)} onComplete={fetchPlans} />
)}
{/* Recent Sessions */}
<div className="mt-8">
<h2 className="text-lg font-semibold mb-3">Recent Sessions</h2>
<div className="bg-white rounded-lg shadow overflow-hidden">
<div className="grid grid-cols-6 gap-4 p-4 border-b text-sm font-medium text-gray-600">
<div>Property</div>
<div>Mower</div>
<div>Cut Height</div>
<div>Avg Speed</div>
<div>Distance</div>
<div>Actions</div>
</div>
{sessions.length === 0 ? (
<div className="p-4 text-gray-600">No sessions yet.</div>
) : sessions.map((s) => (
<div key={s.id} className="grid grid-cols-6 gap-4 p-4 border-b text-sm items-center">
<div className="font-medium">{s.property_name}</div>
<div>{s.equipment_name || '—'}</div>
<div>{Number(s.cut_height_inches || 0).toFixed(2)}"</div>
<div>{(s.average_speed_mph || s.averageSpeed || 0).toFixed?.(1) || Number(s.averageSpeed || 0).toFixed(1)} mph</div>
<div>{Math.round(((s.total_distance_meters || s.gpsTrack?.totalDistance || 0) * 3.28084) || 0)} ft</div>
<div>
<button className="btn-secondary" onClick={()=> setViewSession(s)}>View</button>
</div>
</div>
))}
</div>
</div>
{viewSession && (
<MowingSessionViewModal session={viewSession} onClose={()=> setViewSession(null)} />
)}
</div>
);
};
export default Mowing;