This commit is contained in:
Jake Kasper
2025-09-02 13:06:02 -05:00
parent 5a16f42e1c
commit f37b43bf4f
4 changed files with 243 additions and 2 deletions

View File

@@ -8,18 +8,21 @@ import {
CalendarIcon,
ChartBarIcon
} from '@heroicons/react/24/outline';
import { applicationsAPI } from '../../services/api';
import { applicationsAPI, mowingAPI } from '../../services/api';
import LoadingSpinner from '../../components/UI/LoadingSpinner';
import ApplicationViewModal from '../../components/Applications/ApplicationViewModal';
import MowingSessionViewModal from '../../components/Mowing/MowingSessionViewModal';
import toast from 'react-hot-toast';
const History = () => {
// State for applications and UI
const [completedApplications, setCompletedApplications] = useState([]);
const [applicationLogs, setApplicationLogs] = useState([]);
const [mowingLogs, setMowingLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [showViewModal, setShowViewModal] = useState(false);
const [viewingApplication, setViewingApplication] = useState(null);
const [viewingMowingSession, setViewingMowingSession] = useState(null);
const [selectedPropertyDetails, setSelectedPropertyDetails] = useState(null);
const [dateFilter, setDateFilter] = useState('all'); // all, today, week, month, custom
const [dateRangeStart, setDateRangeStart] = useState('');
@@ -113,6 +116,15 @@ const History = () => {
setCompletedApplications(allHistoryApplications);
setApplicationLogs(logs);
// Fetch mowing sessions/logs
try {
const mowingRes = await mowingAPI.getLogs();
setMowingLogs(mowingRes.data?.data?.logs || []);
} catch (e) {
console.warn('Failed to load mowing logs', e?.response?.data || e.message);
setMowingLogs([]);
}
} catch (error) {
console.error('Failed to fetch history data:', error);
@@ -262,6 +274,7 @@ const History = () => {
const totalApplications = completedApplications.length;
const totalAreaTreated = completedApplications.reduce((sum, app) => sum + (app.totalSectionArea || 0), 0);
const totalDuration = applicationLogs.reduce((sum, log) => sum + (log.gpsTrack?.duration || 0), 0);
const totalMowingSessions = mowingLogs.length;
if (loading) {
return (
@@ -665,6 +678,37 @@ const History = () => {
</div>
)}
{/* Mowing History */}
{mowingLogs.length > 0 && (
<div className="bg-white rounded-lg shadow mb-8">
<div className="p-4 border-b border-gray-200">
<h3 className="text-lg font-medium text-gray-900">Mowing History</h3>
<p className="text-sm text-gray-500">{totalMowingSessions} session{totalMowingSessions!==1?'s':''}</p>
</div>
<div className="divide-y">
{mowingLogs.map((log) => {
const durationMin = Math.round((log.duration_seconds || log.durationSeconds || log.gpsTrack?.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);
return (
<div key={log.id} className="flex items-center justify-between p-4">
<div>
<div className="font-medium text-gray-900">{log.property_name || 'Property'}</div>
<div className="text-sm text-gray-600">{log.equipment_name || 'Mower'} {avg} mph {distFeet} ft {durationMin} min</div>
</div>
<button
onClick={() => setViewingMowingSession(log)}
className="p-2 text-indigo-600 hover:text-indigo-800 hover:bg-indigo-50 rounded"
>
<EyeIcon className="h-5 w-5" />
</button>
</div>
);
})}
</div>
</div>
)}
{/* Application View Modal */}
{showViewModal && viewingApplication && (
<ApplicationViewModal
@@ -676,6 +720,14 @@ const History = () => {
}}
/>
)}
{/* Mowing Session View Modal */}
{viewingMowingSession && (
<MowingSessionViewModal
session={viewingMowingSession}
onClose={() => setViewingMowingSession(null)}
/>
)}
</div>
);
};

View File

@@ -3,6 +3,7 @@ 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 = () => {
@@ -10,12 +11,19 @@ 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 () => {
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 {
@@ -70,9 +78,41 @@ const Mowing = () => {
{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;