again
This commit is contained in:
@@ -4,7 +4,9 @@ import {
|
||||
MapPinIcon,
|
||||
BeakerIcon,
|
||||
WrenchScrewdriverIcon,
|
||||
CalculatorIcon
|
||||
CalculatorIcon,
|
||||
PencilIcon,
|
||||
TrashIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { propertiesAPI, productsAPI, equipmentAPI, applicationsAPI, nozzlesAPI } from '../../services/api';
|
||||
import LoadingSpinner from '../../components/UI/LoadingSpinner';
|
||||
@@ -20,6 +22,7 @@ const Applications = () => {
|
||||
const [equipment, setEquipment] = useState([]);
|
||||
const [nozzles, setNozzles] = useState([]);
|
||||
const [selectedPropertyDetails, setSelectedPropertyDetails] = useState(null);
|
||||
const [editingPlan, setEditingPlan] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApplications();
|
||||
@@ -86,6 +89,34 @@ const Applications = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeletePlan = async (planId, planName) => {
|
||||
if (window.confirm(`Are you sure you want to delete the plan for "${planName}"?`)) {
|
||||
try {
|
||||
await applicationsAPI.deletePlan(planId);
|
||||
toast.success('Application plan deleted successfully');
|
||||
fetchApplications(); // Refresh the list
|
||||
} catch (error) {
|
||||
console.error('Failed to delete plan:', error);
|
||||
toast.error('Failed to delete application plan');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditPlan = async (planId) => {
|
||||
try {
|
||||
// Fetch the full plan details
|
||||
const response = await applicationsAPI.getPlan(planId);
|
||||
const plan = response.data.data.plan;
|
||||
|
||||
// Set up the editing plan data
|
||||
setEditingPlan(plan);
|
||||
setShowPlanForm(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch plan details:', error);
|
||||
toast.error('Failed to load plan details');
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
@@ -185,6 +216,22 @@ const Applications = () => {
|
||||
<p className="text-xs text-gray-500">
|
||||
Created {new Date(application.createdAt).toLocaleDateString()}
|
||||
</p>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<button
|
||||
onClick={() => handleEditPlan(application.id)}
|
||||
className="p-1 text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded"
|
||||
title="Edit plan"
|
||||
>
|
||||
<PencilIcon className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeletePlan(application.id, `${application.propertyName} - ${application.sectionName}`)}
|
||||
className="p-1 text-red-600 hover:text-red-800 hover:bg-red-50 rounded"
|
||||
title="Delete plan"
|
||||
>
|
||||
<TrashIcon className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,34 +242,33 @@ const Applications = () => {
|
||||
{/* Plan Application Modal */}
|
||||
{showPlanForm && (
|
||||
<ApplicationPlanModal
|
||||
onClose={() => setShowPlanForm(false)}
|
||||
onClose={() => {
|
||||
setShowPlanForm(false);
|
||||
setEditingPlan(null);
|
||||
}}
|
||||
properties={properties}
|
||||
products={products}
|
||||
equipment={equipment}
|
||||
nozzles={nozzles}
|
||||
selectedPropertyDetails={selectedPropertyDetails}
|
||||
onPropertySelect={fetchPropertyDetails}
|
||||
editingPlan={editingPlan}
|
||||
onSubmit={async (planData) => {
|
||||
try {
|
||||
// Create a plan for each selected area
|
||||
const planPromises = planData.selectedAreas.map(async (areaId) => {
|
||||
// Get the area details for calculations
|
||||
const selectedArea = selectedPropertyDetails.sections.find(s => s.id === areaId);
|
||||
if (editingPlan) {
|
||||
// Edit existing plan
|
||||
const selectedArea = selectedPropertyDetails.sections.find(s => s.id === planData.selectedAreas[0]);
|
||||
const areaSquareFeet = selectedArea?.area || 0;
|
||||
|
||||
// Get equipment details for calculations
|
||||
const selectedEquipment = equipment.find(eq => eq.id === parseInt(planData.equipmentId));
|
||||
|
||||
// Get nozzle details if selected
|
||||
const selectedNozzle = planData.nozzleId ? nozzles.find(n => n.id === parseInt(planData.nozzleId)) : null;
|
||||
|
||||
const planPayload = {
|
||||
lawnSectionId: parseInt(areaId),
|
||||
lawnSectionId: parseInt(planData.selectedAreas[0]),
|
||||
equipmentId: parseInt(planData.equipmentId),
|
||||
nozzleId: planData.nozzleId ? parseInt(planData.nozzleId) : null,
|
||||
plannedDate: new Date().toISOString().split('T')[0], // Default to today
|
||||
plannedDate: planData.plannedDate || new Date().toISOString().split('T')[0],
|
||||
notes: planData.notes || '',
|
||||
areaSquareFeet: areaSquareFeet, // Pass area for calculations
|
||||
areaSquareFeet: areaSquareFeet,
|
||||
equipment: {
|
||||
id: selectedEquipment?.id,
|
||||
categoryName: selectedEquipment?.categoryName,
|
||||
@@ -248,16 +294,61 @@ const Applications = () => {
|
||||
}]
|
||||
};
|
||||
|
||||
return applicationsAPI.createPlan(planPayload);
|
||||
});
|
||||
await applicationsAPI.updatePlan(editingPlan.id, planPayload);
|
||||
toast.success('Application plan updated successfully');
|
||||
} else {
|
||||
// Create new plan(s)
|
||||
const planPromises = planData.selectedAreas.map(async (areaId) => {
|
||||
const selectedArea = selectedPropertyDetails.sections.find(s => s.id === areaId);
|
||||
const areaSquareFeet = selectedArea?.area || 0;
|
||||
const selectedEquipment = equipment.find(eq => eq.id === parseInt(planData.equipmentId));
|
||||
const selectedNozzle = planData.nozzleId ? nozzles.find(n => n.id === parseInt(planData.nozzleId)) : null;
|
||||
|
||||
const planPayload = {
|
||||
lawnSectionId: parseInt(areaId),
|
||||
equipmentId: parseInt(planData.equipmentId),
|
||||
nozzleId: planData.nozzleId ? parseInt(planData.nozzleId) : null,
|
||||
plannedDate: new Date().toISOString().split('T')[0],
|
||||
notes: planData.notes || '',
|
||||
areaSquareFeet: areaSquareFeet,
|
||||
equipment: {
|
||||
id: selectedEquipment?.id,
|
||||
categoryName: selectedEquipment?.categoryName,
|
||||
tankSizeGallons: selectedEquipment?.tankSizeGallons,
|
||||
pumpGpm: selectedEquipment?.pumpGpm,
|
||||
sprayWidthFeet: selectedEquipment?.sprayWidthFeet,
|
||||
capacityLbs: selectedEquipment?.capacityLbs,
|
||||
spreadWidth: selectedEquipment?.spreadWidth
|
||||
},
|
||||
nozzle: selectedNozzle ? {
|
||||
id: selectedNozzle.id,
|
||||
flowRateGpm: selectedNozzle.flowRateGpm,
|
||||
sprayAngle: selectedNozzle.sprayAngle
|
||||
} : null,
|
||||
products: [{
|
||||
...(planData.selectedProduct?.isShared
|
||||
? { productId: parseInt(planData.selectedProduct.id) }
|
||||
: { userProductId: parseInt(planData.selectedProduct.id) }
|
||||
),
|
||||
rateAmount: parseFloat(planData.selectedProduct?.customRateAmount || planData.selectedProduct?.rateAmount || 1),
|
||||
rateUnit: planData.selectedProduct?.customRateUnit || planData.selectedProduct?.rateUnit || 'per 1000sqft',
|
||||
applicationType: planData.applicationType
|
||||
}]
|
||||
};
|
||||
|
||||
return applicationsAPI.createPlan(planPayload);
|
||||
});
|
||||
|
||||
await Promise.all(planPromises);
|
||||
toast.success(`Created ${planData.selectedAreas.length} application plan(s) successfully`);
|
||||
}
|
||||
|
||||
await Promise.all(planPromises);
|
||||
toast.success(`Created ${planData.selectedAreas.length} application plan(s) successfully`);
|
||||
setShowPlanForm(false);
|
||||
setEditingPlan(null);
|
||||
fetchApplications();
|
||||
} catch (error) {
|
||||
console.error('Failed to create application plans:', error);
|
||||
toast.error('Failed to create application plans');
|
||||
console.error('Failed to save application plan:', error);
|
||||
toast.error('Failed to save application plan');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -275,7 +366,8 @@ const ApplicationPlanModal = ({
|
||||
equipment,
|
||||
nozzles,
|
||||
selectedPropertyDetails,
|
||||
onPropertySelect
|
||||
onPropertySelect,
|
||||
editingPlan
|
||||
}) => {
|
||||
const [loadingProperty, setLoadingProperty] = useState(false);
|
||||
|
||||
@@ -287,9 +379,43 @@ const ApplicationPlanModal = ({
|
||||
applicationType: '', // 'liquid' or 'granular'
|
||||
equipmentId: '',
|
||||
nozzleId: '',
|
||||
plannedDate: '',
|
||||
notes: ''
|
||||
});
|
||||
|
||||
// Initialize form with editing data
|
||||
useEffect(() => {
|
||||
if (editingPlan) {
|
||||
const propertyId = editingPlan.section?.propertyId || editingPlan.property?.id;
|
||||
if (propertyId && propertyId !== planData.propertyId) {
|
||||
onPropertySelect(propertyId);
|
||||
}
|
||||
|
||||
// Find the product from the plans products array
|
||||
const planProduct = editingPlan.products?.[0];
|
||||
let selectedProduct = null;
|
||||
if (planProduct) {
|
||||
if (planProduct.productId) {
|
||||
selectedProduct = products.find(p => p.uniqueId === `shared_${planProduct.productId}`);
|
||||
} else if (planProduct.userProductId) {
|
||||
selectedProduct = products.find(p => p.uniqueId === `user_${planProduct.userProductId}`);
|
||||
}
|
||||
}
|
||||
|
||||
setPlanData({
|
||||
propertyId: propertyId?.toString() || '',
|
||||
selectedAreas: [editingPlan.section?.id],
|
||||
productId: selectedProduct?.uniqueId || '',
|
||||
selectedProduct: selectedProduct,
|
||||
applicationType: planProduct?.applicationType || '',
|
||||
equipmentId: editingPlan.equipment?.id?.toString() || '',
|
||||
nozzleId: editingPlan.nozzle?.id?.toString() || '',
|
||||
plannedDate: editingPlan.plannedDate ? new Date(editingPlan.plannedDate).toISOString().split('T')[0] : '',
|
||||
notes: editingPlan.notes || ''
|
||||
});
|
||||
}
|
||||
}, [editingPlan, products, onPropertySelect]);
|
||||
|
||||
const handlePropertyChange = async (propertyId) => {
|
||||
setPlanData({ ...planData, propertyId, selectedAreas: [] });
|
||||
if (propertyId) {
|
||||
@@ -342,7 +468,9 @@ const ApplicationPlanModal = ({
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||
<h3 className="text-lg font-semibold mb-4">Plan Application</h3>
|
||||
<h3 className="text-lg font-semibold mb-4">
|
||||
{editingPlan ? 'Edit Application Plan' : 'Plan Application'}
|
||||
</h3>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Property Selection */}
|
||||
@@ -551,6 +679,17 @@ const ApplicationPlanModal = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Planned Date */}
|
||||
<div>
|
||||
<label className="label">Planned Date</label>
|
||||
<input
|
||||
type="date"
|
||||
className="input"
|
||||
value={planData.plannedDate}
|
||||
onChange={(e) => setPlanData({ ...planData, plannedDate: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Notes */}
|
||||
<div>
|
||||
<label className="label">Notes</label>
|
||||
@@ -565,7 +704,7 @@ const ApplicationPlanModal = ({
|
||||
|
||||
<div className="flex gap-3 pt-4">
|
||||
<button type="submit" className="btn-primary flex-1">
|
||||
Create Application Plan
|
||||
{editingPlan ? 'Update Application Plan' : 'Create Application Plan'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -152,6 +152,7 @@ export const applicationsAPI = {
|
||||
getPlan: (id) => apiClient.get(`/applications/plans/${id}`),
|
||||
createPlan: (planData) => apiClient.post('/applications/plans', planData),
|
||||
updatePlan: (id, planData) => apiClient.put(`/applications/plans/${id}`, planData),
|
||||
deletePlan: (id) => apiClient.delete(`/applications/plans/${id}`),
|
||||
updatePlanStatus: (id, status) => apiClient.put(`/applications/plans/${id}/status`, { status }),
|
||||
|
||||
// Logs
|
||||
|
||||
Reference in New Issue
Block a user