application plan again

This commit is contained in:
Jake Kasper
2025-08-27 13:43:11 -04:00
parent 2075d6f121
commit 2d085c1e72

View File

@@ -1,9 +1,402 @@
// This is a placeholder - the ApplicationPlanModal component was in the main file
// We need to extract it to this separate file for better organization
// For now, the modal functionality is temporarily unavailable until we recreate it
import React, { useState, useEffect } from 'react';
import { XMarkIcon, MapPinIcon, WrenchScrewdriverIcon, BeakerIcon } from '@heroicons/react/24/outline';
import PropertyMap from '../Maps/PropertyMap';
import toast from 'react-hot-toast';
const ApplicationPlanModal = () => {
return <div>ApplicationPlanModal - To be implemented</div>;
const ApplicationPlanModal = ({
onClose,
properties,
products,
equipment,
nozzles,
selectedPropertyDetails,
onPropertySelect,
editingPlan,
onSubmit
}) => {
const [selectedPropertyId, setSelectedPropertyId] = useState('');
const [selectedAreas, setSelectedAreas] = useState([]);
const [selectedEquipmentId, setSelectedEquipmentId] = useState('');
const [selectedNozzleId, setSelectedNozzleId] = useState('');
const [selectedProducts, setSelectedProducts] = useState([]);
const [plannedDate, setPlannedDate] = useState('');
const [notes, setNotes] = useState('');
const [loading, setLoading] = useState(false);
// Initialize form with editing data if provided
useEffect(() => {
if (editingPlan) {
setSelectedPropertyId(editingPlan.propertyId || '');
setSelectedAreas(editingPlan.selectedAreas || []);
setSelectedEquipmentId(editingPlan.equipmentId || '');
setSelectedNozzleId(editingPlan.nozzleId || '');
setSelectedProducts(editingPlan.products || []);
setPlannedDate(editingPlan.plannedDate || '');
setNotes(editingPlan.notes || '');
}
}, [editingPlan]);
// Handle property selection
const handlePropertyChange = async (propertyId) => {
setSelectedPropertyId(propertyId);
setSelectedAreas([]); // Clear selected areas when property changes
if (propertyId && onPropertySelect) {
await onPropertySelect(propertyId);
}
};
// Handle area selection on map
const handleAreaClick = (area) => {
setSelectedAreas(prev => {
if (prev.includes(area.id)) {
return prev.filter(id => id !== area.id);
} else {
return [...prev, area.id];
}
});
};
// Add product to plan
const addProduct = () => {
setSelectedProducts(prev => [...prev, {
productId: null,
userProductId: null,
rateAmount: '',
rateUnit: 'oz/1000sqft',
isUserProduct: false
}]);
};
// Remove product from plan
const removeProduct = (index) => {
setSelectedProducts(prev => prev.filter((_, i) => i !== index));
};
// Update product in plan
const updateProduct = (index, field, value) => {
setSelectedProducts(prev => prev.map((product, i) =>
i === index ? { ...product, [field]: value } : product
));
};
// Handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
// Validation
if (!selectedPropertyId) {
toast.error('Please select a property');
return;
}
if (selectedAreas.length === 0) {
toast.error('Please select at least one area');
return;
}
if (!selectedEquipmentId) {
toast.error('Please select equipment');
return;
}
if (selectedProducts.length === 0) {
toast.error('Please add at least one product');
return;
}
if (!plannedDate) {
toast.error('Please select a planned date');
return;
}
// Validate products have required fields
for (const product of selectedProducts) {
if (!product.productId && !product.userProductId) {
toast.error('Please select a product for all entries');
return;
}
if (!product.rateAmount) {
toast.error('Please enter rate amount for all products');
return;
}
}
setLoading(true);
try {
const planData = {
propertyId: parseInt(selectedPropertyId),
selectedAreas,
equipmentId: selectedEquipmentId,
nozzleId: selectedNozzleId || null,
products: selectedProducts,
plannedDate,
notes
};
await onSubmit(planData);
onClose();
} catch (error) {
console.error('Failed to submit plan:', error);
toast.error('Failed to save application plan');
} finally {
setLoading(false);
}
};
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-6xl mx-4 max-h-[90vh] overflow-y-auto">
{/* Header */}
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-gray-900">
{editingPlan ? 'Edit Application Plan' : 'Plan New Application'}
</h2>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-600"
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
<form onSubmit={handleSubmit}>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Left Column - Form */}
<div className="space-y-6">
{/* Property Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<MapPinIcon className="h-4 w-4 inline mr-1" />
Property
</label>
<select
value={selectedPropertyId}
onChange={(e) => handlePropertyChange(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
required
>
<option value="">Select a property</option>
{properties.map(property => (
<option key={property.id} value={property.id}>
{property.name} - {property.address}
</option>
))}
</select>
</div>
{/* Equipment Selection */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<WrenchScrewdriverIcon className="h-4 w-4 inline mr-1" />
Equipment
</label>
<select
value={selectedEquipmentId}
onChange={(e) => setSelectedEquipmentId(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
required
>
<option value="">Select equipment</option>
{equipment.map(eq => (
<option key={eq.id} value={eq.id}>
{eq.name} ({eq.type})
</option>
))}
</select>
</div>
{/* Nozzle Selection (for sprayers) */}
{selectedEquipmentId && equipment.find(eq => eq.id == selectedEquipmentId)?.type === 'sprayer' && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Nozzle (Optional)
</label>
<select
value={selectedNozzleId}
onChange={(e) => setSelectedNozzleId(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
>
<option value="">No specific nozzle</option>
{nozzles.map(nozzle => (
<option key={nozzle.id} value={nozzle.id}>
{nozzle.name} - {nozzle.flow_rate} GPM
</option>
))}
</select>
</div>
)}
{/* Planned Date */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Planned Date
</label>
<input
type="date"
value={plannedDate}
onChange={(e) => setPlannedDate(e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
required
/>
</div>
{/* Products */}
<div>
<div className="flex justify-between items-center mb-2">
<label className="block text-sm font-medium text-gray-700">
<BeakerIcon className="h-4 w-4 inline mr-1" />
Products
</label>
<button
type="button"
onClick={addProduct}
className="text-blue-600 hover:text-blue-800 text-sm"
>
+ Add Product
</button>
</div>
{selectedProducts.map((product, index) => (
<div key={index} className="border rounded p-3 mb-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-2">
<select
value={product.isUserProduct ? `user_${product.userProductId}` : `shared_${product.productId}`}
onChange={(e) => {
const [type, id] = e.target.value.split('_');
updateProduct(index, 'isUserProduct', type === 'user');
updateProduct(index, type === 'user' ? 'userProductId' : 'productId', parseInt(id));
updateProduct(index, type === 'user' ? 'productId' : 'userProductId', null);
}}
className="border border-gray-300 rounded px-2 py-1"
required
>
<option value="">Select product</option>
{products.map(prod => (
<option key={prod.uniqueId} value={prod.uniqueId}>
{prod.name} {prod.brand && `(${prod.brand})`} {prod.isShared ? '' : '(Custom)'}
</option>
))}
</select>
<div className="flex gap-1">
<input
type="number"
step="0.01"
placeholder="Rate"
value={product.rateAmount}
onChange={(e) => updateProduct(index, 'rateAmount', e.target.value)}
className="border border-gray-300 rounded px-2 py-1 flex-1"
required
/>
<select
value={product.rateUnit}
onChange={(e) => updateProduct(index, 'rateUnit', e.target.value)}
className="border border-gray-300 rounded px-2 py-1"
>
<option value="oz/1000sqft">oz/1000sqft</option>
<option value="lb/1000sqft">lb/1000sqft</option>
<option value="fl oz/1000sqft">fl oz/1000sqft</option>
</select>
</div>
<button
type="button"
onClick={() => removeProduct(index)}
className="text-red-600 hover:text-red-800 px-2"
>
Remove
</button>
</div>
</div>
))}
{selectedProducts.length === 0 && (
<p className="text-gray-500 text-sm italic">No products added yet</p>
)}
</div>
{/* Notes */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Notes (Optional)
</label>
<textarea
value={notes}
onChange={(e) => setNotes(e.target.value)}
rows={3}
className="w-full border border-gray-300 rounded px-3 py-2"
placeholder="Add any notes or special instructions..."
/>
</div>
</div>
{/* Right Column - Map */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Select Areas to Treat
</label>
<div className="h-96 border rounded-lg overflow-hidden">
{selectedPropertyDetails ? (
<PropertyMap
property={selectedPropertyDetails}
sections={selectedPropertyDetails.sections || []}
selectedSections={selectedAreas}
onSectionClick={handleAreaClick}
mode="select"
editable={false}
className="h-full w-full"
/>
) : (
<div className="h-full w-full flex items-center justify-center bg-gray-100">
<p className="text-gray-500">Select a property to view areas</p>
</div>
)}
</div>
{selectedAreas.length > 0 && selectedPropertyDetails && (
<div className="mt-2">
<p className="text-sm font-medium text-gray-700">Selected Areas:</p>
<div className="flex flex-wrap gap-2 mt-1">
{selectedAreas.map(areaId => {
const area = selectedPropertyDetails.sections?.find(s => s.id === areaId);
return (
<span key={areaId} className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs">
{area?.name} ({Math.round(area?.area || 0).toLocaleString()} sq ft)
</span>
);
})}
</div>
<p className="text-xs text-gray-600 mt-1">
Total: {selectedAreas.reduce((total, areaId) => {
const area = selectedPropertyDetails.sections?.find(s => s.id === areaId);
return total + (area?.area || 0);
}, 0).toLocaleString()} sq ft
</p>
</div>
)}
</div>
</div>
{/* Form Actions */}
<div className="flex justify-end gap-3 mt-6 pt-6 border-t">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:text-gray-800"
disabled={loading}
>
Cancel
</button>
<button
type="submit"
className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
disabled={loading}
>
{loading ? 'Saving...' : (editingPlan ? 'Update Plan' : 'Create Plan')}
</button>
</div>
</form>
</div>
</div>
);
};
export default ApplicationPlanModal;