608 lines
26 KiB
JavaScript
608 lines
26 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
ClockIcon,
|
|
MapPinIcon,
|
|
WrenchScrewdriverIcon,
|
|
BeakerIcon,
|
|
EyeIcon,
|
|
CalendarIcon,
|
|
ChartBarIcon
|
|
} from '@heroicons/react/24/outline';
|
|
import { applicationsAPI } from '../../services/api';
|
|
import LoadingSpinner from '../../components/UI/LoadingSpinner';
|
|
import ApplicationViewModal from '../../components/Applications/ApplicationViewModal';
|
|
import toast from 'react-hot-toast';
|
|
|
|
const History = () => {
|
|
const [completedApplications, setCompletedApplications] = useState([]);
|
|
const [applicationLogs, setApplicationLogs] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [showViewModal, setShowViewModal] = useState(false);
|
|
const [viewingApplication, setViewingApplication] = useState(null);
|
|
const [selectedPropertyDetails, setSelectedPropertyDetails] = useState(null);
|
|
const [dateFilter, setDateFilter] = useState('all'); // all, today, week, month, custom
|
|
const [dateRangeStart, setDateRangeStart] = useState('');
|
|
const [dateRangeEnd, setDateRangeEnd] = useState('');
|
|
const [sortBy, setSortBy] = useState('date'); // date, area, duration
|
|
const [statusFilter, setStatusFilter] = useState('all'); // all, completed, archived
|
|
const [propertyFilter, setPropertyFilter] = useState('all');
|
|
const [selectedProducts, setSelectedProducts] = useState([]);
|
|
const [applicationTypeFilter, setApplicationTypeFilter] = useState('all'); // all, granular, liquid
|
|
const [showFilters, setShowFilters] = useState(false);
|
|
const [showProductDropdown, setShowProductDropdown] = useState(false);
|
|
|
|
// Calculate coverage percentage based on GPS tracking and equipment specifications
|
|
const calculateCoverage = (application, log) => {
|
|
if (!log?.gpsTrack?.points || log.gpsTrack.points.length < 2) {
|
|
return 0; // No movement = no coverage
|
|
}
|
|
|
|
const totalDistance = log.gpsTrack.totalDistance || 0;
|
|
const plannedArea = application.totalSectionArea || 0;
|
|
|
|
if (totalDistance === 0 || plannedArea === 0) {
|
|
return 0;
|
|
}
|
|
|
|
// Estimate equipment width based on type
|
|
let equipmentWidth = 4; // Default 4 feet for unknown equipment
|
|
|
|
const equipmentName = application.equipmentName?.toLowerCase() || '';
|
|
if (equipmentName.includes('spreader')) {
|
|
equipmentWidth = 12; // Spreaders typically 8-16 feet wide
|
|
} else if (equipmentName.includes('sprayer')) {
|
|
equipmentWidth = 20; // Sprayers typically 15-30 feet wide
|
|
} else if (equipmentName.includes('mower')) {
|
|
equipmentWidth = 6; // Mowers typically 4-8 feet wide
|
|
}
|
|
|
|
// Calculate theoretical coverage area
|
|
// Distance (feet) * Width (feet) = Coverage area (sq ft)
|
|
const theoreticalCoverageArea = totalDistance * equipmentWidth;
|
|
|
|
// Calculate coverage percentage (capped at 100%)
|
|
const coveragePercentage = Math.min((theoreticalCoverageArea / plannedArea) * 100, 100);
|
|
|
|
return Math.round(coveragePercentage);
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchHistoryData();
|
|
}, []);
|
|
|
|
// Close dropdown when clicking outside
|
|
useEffect(() => {
|
|
const handleClickOutside = (event) => {
|
|
if (showProductDropdown && !event.target.closest('.relative')) {
|
|
setShowProductDropdown(false);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, [showProductDropdown]);
|
|
|
|
const fetchHistoryData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
|
|
// Fetch completed and archived applications
|
|
const [completedResponse, archivedResponse] = await Promise.all([
|
|
applicationsAPI.getPlans({ status: 'completed' }),
|
|
applicationsAPI.getPlans({ status: 'archived' })
|
|
]);
|
|
|
|
const completedPlans = completedResponse.data.data.plans || [];
|
|
const archivedPlans = archivedResponse.data.data.plans || [];
|
|
const allHistoryApplications = [...completedPlans, ...archivedPlans];
|
|
|
|
// Fetch application logs for additional details
|
|
const logsResponse = await applicationsAPI.getLogs();
|
|
const logs = logsResponse.data.data.logs || [];
|
|
|
|
setCompletedApplications(allHistoryApplications);
|
|
setApplicationLogs(logs);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to fetch history data:', error);
|
|
toast.error('Failed to load application history');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleViewApplication = async (application) => {
|
|
try {
|
|
setViewingApplication(application);
|
|
setShowViewModal(true);
|
|
} catch (error) {
|
|
console.error('Failed to load application details:', error);
|
|
toast.error('Failed to load application details');
|
|
}
|
|
};
|
|
|
|
// Get unique values for filter options
|
|
const uniqueProperties = [...new Set(completedApplications.map(app => app.propertyName))].filter(Boolean);
|
|
const uniqueProducts = [...new Set(
|
|
completedApplications.flatMap(app =>
|
|
app.products ? app.products.map(p => p.productName) : []
|
|
)
|
|
)].filter(Boolean);
|
|
|
|
// Filter applications based on all filters
|
|
const filteredApplications = completedApplications.filter(app => {
|
|
// Date filter
|
|
if (dateFilter !== 'all') {
|
|
const appDate = new Date(app.plannedDate);
|
|
const now = new Date();
|
|
|
|
switch (dateFilter) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
// Status filter
|
|
if (statusFilter !== 'all' && app.status !== statusFilter) return false;
|
|
|
|
// Property filter
|
|
if (propertyFilter !== 'all' && app.propertyName !== propertyFilter) return false;
|
|
|
|
// Product filter
|
|
if (productFilter !== 'all') {
|
|
if (!app.products || !app.products.some(p => p.productName === productFilter)) return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Sort applications
|
|
const sortedApplications = [...filteredApplications].sort((a, b) => {
|
|
switch (sortBy) {
|
|
case 'date':
|
|
return new Date(b.plannedDate) - new Date(a.plannedDate);
|
|
case 'area':
|
|
return (b.totalSectionArea || 0) - (a.totalSectionArea || 0);
|
|
case 'duration':
|
|
const logA = applicationLogs.find(log => log.planId === a.id);
|
|
const logB = applicationLogs.find(log => log.planId === b.id);
|
|
return (logB?.gpsTrack?.duration || 0) - (logA?.gpsTrack?.duration || 0);
|
|
default:
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
// Calculate summary statistics
|
|
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);
|
|
|
|
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">Application History</h1>
|
|
<button
|
|
onClick={fetchHistoryData}
|
|
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
|
|
>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
|
|
{/* Summary Statistics */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
<div className="bg-blue-50 p-4 rounded-lg">
|
|
<div className="flex items-center">
|
|
<ChartBarIcon className="h-8 w-8 text-blue-600" />
|
|
<div className="ml-3">
|
|
<p className="text-sm text-blue-600 font-medium">Total Applications</p>
|
|
<p className="text-2xl font-bold text-blue-900">{totalApplications}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-green-50 p-4 rounded-lg">
|
|
<div className="flex items-center">
|
|
<MapPinIcon className="h-8 w-8 text-green-600" />
|
|
<div className="ml-3">
|
|
<p className="text-sm text-green-600 font-medium">Total Area Treated</p>
|
|
<p className="text-2xl font-bold text-green-900">
|
|
{Math.round(totalAreaTreated / 1000)}k sq ft
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-purple-50 p-4 rounded-lg">
|
|
<div className="flex items-center">
|
|
<ClockIcon className="h-8 w-8 text-purple-600" />
|
|
<div className="ml-3">
|
|
<p className="text-sm text-purple-600 font-medium">Total Time</p>
|
|
<p className="text-2xl font-bold text-purple-900">
|
|
{Math.round(totalDuration / 3600)}h
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Filters and Sort */}
|
|
<div className="bg-white rounded-lg shadow mb-6">
|
|
<div className="p-4 border-b border-gray-200">
|
|
<button
|
|
onClick={() => setShowFilters(!showFilters)}
|
|
className="flex items-center justify-between w-full text-left"
|
|
>
|
|
<h3 className="text-lg font-medium text-gray-900">Filters & Sorting</h3>
|
|
<svg
|
|
className={`h-5 w-5 transform transition-transform ${showFilters ? 'rotate-180' : ''}`}
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
{showFilters && (
|
|
<div className="p-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6 gap-4">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Time Period
|
|
</label>
|
|
<select
|
|
value={dateFilter}
|
|
onChange={(e) => setDateFilter(e.target.value)}
|
|
className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
>
|
|
<option value="all">All Time</option>
|
|
<option value="today">Today</option>
|
|
<option value="week">Last Week</option>
|
|
<option value="month">Last Month</option>
|
|
<option value="custom">Custom Range</option>
|
|
</select>
|
|
|
|
{/* Date Range Inputs */}
|
|
{dateFilter === 'custom' && (
|
|
<div className="mt-2 grid grid-cols-2 gap-2">
|
|
<div>
|
|
<label className="block text-xs text-gray-600 mb-1">From</label>
|
|
<input
|
|
type="date"
|
|
value={dateRangeStart}
|
|
onChange={(e) => setDateRangeStart(e.target.value)}
|
|
className="w-full border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-xs text-gray-600 mb-1">To</label>
|
|
<input
|
|
type="date"
|
|
value={dateRangeEnd}
|
|
onChange={(e) => setDateRangeEnd(e.target.value)}
|
|
className="w-full border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Status
|
|
</label>
|
|
<select
|
|
value={statusFilter}
|
|
onChange={(e) => setStatusFilter(e.target.value)}
|
|
className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
>
|
|
<option value="all">All Status</option>
|
|
<option value="completed">Completed</option>
|
|
<option value="archived">Archived</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Property
|
|
</label>
|
|
<select
|
|
value={propertyFilter}
|
|
onChange={(e) => setPropertyFilter(e.target.value)}
|
|
className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
>
|
|
<option value="all">All Properties</option>
|
|
{uniqueProperties.map(property => (
|
|
<option key={property} value={property}>{property}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Products
|
|
</label>
|
|
<div className="relative">
|
|
<button
|
|
onClick={() => setShowProductDropdown(!showProductDropdown)}
|
|
className="w-full border border-gray-300 rounded px-3 py-2 text-sm text-left focus:outline-none focus:ring-2 focus:ring-blue-500 flex items-center justify-between"
|
|
>
|
|
<span>
|
|
{selectedProducts.length === 0
|
|
? 'All Products'
|
|
: `${selectedProducts.length} selected`
|
|
}
|
|
</span>
|
|
<svg className={`h-4 w-4 transform transition-transform ${showProductDropdown ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
|
|
{showProductDropdown && (
|
|
<div className="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-auto">
|
|
{uniqueProducts.length === 0 ? (
|
|
<div className="px-3 py-2 text-sm text-gray-500">No products available</div>
|
|
) : (
|
|
<>
|
|
<div className="px-3 py-2 border-b">
|
|
<button
|
|
onClick={() => {
|
|
if (selectedProducts.length === uniqueProducts.length) {
|
|
setSelectedProducts([]);
|
|
} else {
|
|
setSelectedProducts([...uniqueProducts]);
|
|
}
|
|
}}
|
|
className="text-xs text-blue-600 hover:text-blue-800"
|
|
>
|
|
{selectedProducts.length === uniqueProducts.length ? 'Deselect All' : 'Select All'}
|
|
</button>
|
|
</div>
|
|
{uniqueProducts.map(product => (
|
|
<label key={product} className="flex items-center px-3 py-2 hover:bg-gray-50 cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
checked={selectedProducts.includes(product)}
|
|
onChange={(e) => {
|
|
if (e.target.checked) {
|
|
setSelectedProducts([...selectedProducts, product]);
|
|
} else {
|
|
setSelectedProducts(selectedProducts.filter(p => p !== product));
|
|
}
|
|
}}
|
|
className="mr-2"
|
|
/>
|
|
<span className="text-sm">{product}</span>
|
|
</label>
|
|
))}
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Application Type
|
|
</label>
|
|
<select
|
|
value={applicationTypeFilter}
|
|
onChange={(e) => setApplicationTypeFilter(e.target.value)}
|
|
className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
>
|
|
<option value="all">All Types</option>
|
|
<option value="granular">Granular</option>
|
|
<option value="liquid">Liquid</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Sort By
|
|
</label>
|
|
<select
|
|
value={sortBy}
|
|
onChange={(e) => setSortBy(e.target.value)}
|
|
className="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
>
|
|
<option value="date">Date</option>
|
|
<option value="area">Area Size</option>
|
|
<option value="duration">Duration</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-4 pt-4 border-t border-gray-200">
|
|
<button
|
|
onClick={() => {
|
|
setDateFilter('all');
|
|
setDateRangeStart('');
|
|
setDateRangeEnd('');
|
|
setStatusFilter('all');
|
|
setPropertyFilter('all');
|
|
setSelectedProducts([]);
|
|
setApplicationTypeFilter('all');
|
|
setSortBy('date');
|
|
setShowProductDropdown(false);
|
|
}}
|
|
className="px-4 py-2 text-sm text-gray-600 hover:text-gray-800 border border-gray-300 rounded hover:bg-gray-50"
|
|
>
|
|
Clear All Filters
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Applications List */}
|
|
{sortedApplications.length === 0 ? (
|
|
<div className="text-center py-12">
|
|
<CalendarIcon className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">
|
|
{statusFilter === 'completed' ? 'No completed applications' :
|
|
statusFilter === 'archived' ? 'No archived applications' :
|
|
'No completed or archived applications'}
|
|
</h3>
|
|
<p className="text-gray-500">
|
|
{statusFilter === 'completed' ? 'Complete some applications to see them here.' :
|
|
statusFilter === 'archived' ? 'Archive some completed applications to see them here.' :
|
|
'Complete and archive applications to see them here.'}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="grid gap-4">
|
|
{sortedApplications.map((application) => {
|
|
const log = applicationLogs.find(log => log.planId === application.id);
|
|
|
|
return (
|
|
<div key={application.id} className="bg-white p-6 rounded-lg shadow hover:shadow-lg transition-shadow">
|
|
<div className="flex justify-between items-start">
|
|
<div className="flex-1">
|
|
<div className="flex items-center justify-between mb-4">
|
|
<h3 className="text-lg font-semibold text-gray-900">
|
|
{application.propertyName} - {application.sectionNames}
|
|
</h3>
|
|
<span className={`px-3 py-1 text-sm font-medium rounded-full ${
|
|
application.status === 'archived'
|
|
? 'bg-gray-100 text-gray-800'
|
|
: 'bg-green-100 text-green-800'
|
|
}`}>
|
|
{application.status === 'archived' ? 'Archived' : 'Completed'}
|
|
</span>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
|
|
<div className="flex items-center text-sm text-gray-600">
|
|
<CalendarIcon className="h-4 w-4 mr-2" />
|
|
{new Date(application.plannedDate).toLocaleDateString()}
|
|
</div>
|
|
|
|
<div className="flex items-center text-sm text-gray-600">
|
|
<MapPinIcon className="h-4 w-4 mr-2" />
|
|
{(application.totalSectionArea || 0).toLocaleString()} sq ft
|
|
</div>
|
|
|
|
<div className="flex items-center text-sm text-gray-600">
|
|
<WrenchScrewdriverIcon className="h-4 w-4 mr-2" />
|
|
{application.equipmentName}
|
|
</div>
|
|
|
|
{log && (
|
|
<div className="flex items-center text-sm text-gray-600">
|
|
<ClockIcon className="h-4 w-4 mr-2" />
|
|
{Math.round((log.gpsTrack?.duration || 0) / 60)} min
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* GPS Tracking Stats */}
|
|
{log && (
|
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 mb-4">
|
|
<div className="bg-blue-50 p-2 rounded text-center">
|
|
<div className="text-xs text-blue-600 font-medium">Avg Speed</div>
|
|
<div className="text-sm font-bold text-blue-900">
|
|
{log.averageSpeed?.toFixed(1) || 0} mph
|
|
</div>
|
|
</div>
|
|
<div className="bg-green-50 p-2 rounded text-center">
|
|
<div className="text-xs text-green-600 font-medium">GPS Points</div>
|
|
<div className="text-sm font-bold text-green-900">
|
|
{log.gpsTrack?.points?.length || 0}
|
|
</div>
|
|
</div>
|
|
<div className="bg-purple-50 p-2 rounded text-center">
|
|
<div className="text-xs text-purple-600 font-medium">Distance</div>
|
|
<div className="text-sm font-bold text-purple-900">
|
|
{Math.round(log.gpsTrack?.totalDistance || 0)} ft
|
|
</div>
|
|
</div>
|
|
<div className="bg-orange-50 p-2 rounded text-center">
|
|
<div className="text-xs text-orange-600 font-medium">Coverage</div>
|
|
<div className="text-sm font-bold text-orange-900">
|
|
{calculateCoverage(application, log)}%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Products */}
|
|
{application.products && application.products.length > 0 && (
|
|
<div className="mb-4">
|
|
<div className="flex items-center text-sm text-gray-600 mb-2">
|
|
<BeakerIcon className="h-4 w-4 mr-2" />
|
|
Products Applied:
|
|
</div>
|
|
<div className="flex flex-wrap gap-2">
|
|
{application.products.map((product, index) => (
|
|
<span
|
|
key={index}
|
|
className="px-2 py-1 bg-gray-100 text-gray-700 rounded text-xs"
|
|
>
|
|
{product.productName} ({product.rateAmount} {product.rateUnit})
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{application.notes && (
|
|
<p className="text-sm text-gray-600 mb-4">
|
|
<strong>Notes:</strong> {application.notes}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="ml-4">
|
|
<button
|
|
onClick={() => handleViewApplication(application)}
|
|
className="p-2 text-indigo-600 hover:text-indigo-800 hover:bg-indigo-50 rounded"
|
|
title="View details"
|
|
>
|
|
<EyeIcon className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
|
|
{/* Application View Modal */}
|
|
{showViewModal && viewingApplication && (
|
|
<ApplicationViewModal
|
|
application={viewingApplication}
|
|
propertyDetails={selectedPropertyDetails}
|
|
onClose={() => {
|
|
setShowViewModal(false);
|
|
setViewingApplication(null);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default History; |