diff --git a/frontend/src/components/Maps/PropertyMap.js b/frontend/src/components/Maps/PropertyMap.js index 41da970..8fbb233 100644 --- a/frontend/src/components/Maps/PropertyMap.js +++ b/frontend/src/components/Maps/PropertyMap.js @@ -429,14 +429,14 @@ const PropertyMap = ({ })} {/* GPS Tracking Elements */} - {mode === "execution" && ( + {(mode === "execution" || mode === "view") && ( <> {/* GPS Track Polyline */} {gpsTrack.length > 1 && ( [point.lat, point.lng])} pathOptions={{ - color: '#10B981', + color: '#EF4444', weight: 4, opacity: 0.8, }} @@ -454,8 +454,8 @@ const PropertyMap = ({ ) ))} - {/* Current Location */} - {currentLocation && ( + {/* Current Location - only for execution mode */} + {currentLocation && mode === "execution" && ( { const [showViewModal, setShowViewModal] = useState(false); const [viewingApplication, setViewingApplication] = useState(null); + // Filtering and sorting state + const [showFilters, setShowFilters] = useState(false); + const [filters, setFilters] = useState({ + status: 'all', + dateRange: 'all', + product: 'all', + minArea: '', + maxArea: '', + property: 'all' + }); + const [sortBy, setSortBy] = useState('date'); + const [sortOrder, setSortOrder] = useState('desc'); useEffect(() => { fetchApplications(); @@ -195,6 +210,119 @@ const Applications = () => { } }; + const handleArchiveApplication = async (applicationId) => { + if (!confirm('Are you sure you want to archive this application? It will be moved to the archive and hidden from the main list.')) { + return; + } + + try { + await applicationsAPI.archivePlan(applicationId); + toast.success('Application archived successfully'); + fetchApplications(); // Refresh the list + } catch (error) { + console.error('Failed to archive application:', error); + toast.error('Failed to archive application'); + } + }; + + // Filter and sort applications + const filteredAndSortedApplications = React.useMemo(() => { + let filtered = applications.filter(app => { + // Status filter + if (filters.status !== 'all' && app.status !== filters.status) { + return false; + } + + // Date range filter + if (filters.dateRange !== 'all') { + const appDate = new Date(app.plannedDate); + const now = new Date(); + switch (filters.dateRange) { + case 'today': + if (appDate.toDateString() !== now.toDateString()) return false; + break; + case 'week': + const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + if (appDate < weekAgo) return false; + break; + case 'month': + const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + if (appDate < monthAgo) return false; + break; + } + } + + // Area filter + const area = app.totalSectionArea || app.sectionArea || 0; + if (filters.minArea && area < parseInt(filters.minArea)) { + return false; + } + if (filters.maxArea && area > parseInt(filters.maxArea)) { + return false; + } + + // Property filter + if (filters.property !== 'all' && app.propertyName !== filters.property) { + return false; + } + + // Product filter + if (filters.product !== 'all') { + const hasProduct = app.productDetails?.some(p => p.name.toLowerCase().includes(filters.product.toLowerCase())) || + app.products?.some(p => p.productName?.toLowerCase().includes(filters.product.toLowerCase())); + if (!hasProduct) return false; + } + + return true; + }); + + // Sort + return filtered.sort((a, b) => { + let aVal, bVal; + switch (sortBy) { + case 'date': + aVal = new Date(a.plannedDate); + bVal = new Date(b.plannedDate); + break; + case 'area': + aVal = a.totalSectionArea || a.sectionArea || 0; + bVal = b.totalSectionArea || b.sectionArea || 0; + break; + case 'property': + aVal = a.propertyName || ''; + bVal = b.propertyName || ''; + break; + case 'status': + aVal = a.status || ''; + bVal = b.status || ''; + break; + default: + return 0; + } + + if (sortOrder === 'desc') { + return bVal > aVal ? 1 : bVal < aVal ? -1 : 0; + } else { + return aVal > bVal ? 1 : aVal < bVal ? -1 : 0; + } + }); + }, [applications, filters, sortBy, sortOrder]); + + // Get unique values for filter dropdowns + const uniqueProperties = React.useMemo(() => { + const props = [...new Set(applications.map(app => app.propertyName).filter(Boolean))]; + return props.sort(); + }, [applications]); + + const uniqueProducts = React.useMemo(() => { + const products = new Set(); + applications.forEach(app => { + app.productDetails?.forEach(p => products.add(p.name)); + app.products?.forEach(p => p.productName && products.add(p.productName)); + }); + return [...products].sort(); + }, [applications]); + if (loading) { return (
@@ -322,6 +450,164 @@ const Applications = () => {
+ {/* Filter Controls */} + {applications.length > 0 && ( +
+
+
+ +
+ {filteredAndSortedApplications.length} of {applications.length} applications +
+
+ + {showFilters && ( +
+ {/* Status Filter */} +
+ + +
+ + {/* Date Range Filter */} +
+ + +
+ + {/* Property Filter */} +
+ + +
+ + {/* Sort By */} +
+ +
+ + +
+
+ + {/* Area Range Filter */} +
+ +
+ setFilters(prev => ({ ...prev, minArea: e.target.value }))} + placeholder="Min" + className="flex-1 border border-gray-300 rounded px-3 py-2 text-sm" + /> + to + setFilters(prev => ({ ...prev, maxArea: e.target.value }))} + placeholder="Max" + className="flex-1 border border-gray-300 rounded px-3 py-2 text-sm" + /> +
+
+ + {/* Product Filter */} +
+ +
+ setFilters(prev => ({ ...prev, product: e.target.value }))} + placeholder="Search products..." + className="flex-1 border border-gray-300 rounded px-3 py-2 text-sm" + /> + {filters.product !== 'all' && filters.product !== '' && ( + + )} +
+
+ + {/* Clear Filters Button */} +
+ +
+
+ )} +
+
+ )} + {/* Applications List */} {applications.length === 0 ? (
@@ -342,7 +628,7 @@ const Applications = () => {
) : (
- {applications.map((application) => ( + {filteredAndSortedApplications.map((application) => (
@@ -456,13 +742,22 @@ const Applications = () => { )} {application.status === 'completed' && ( - + <> + + + )}