This commit is contained in:
Jake Kasper
2025-08-27 14:02:14 -04:00
parent ac6f6857b0
commit d667a7cf9d

View File

@@ -19,6 +19,7 @@ const ApplicationPlanModal = ({
const [selectedEquipmentId, setSelectedEquipmentId] = useState(''); const [selectedEquipmentId, setSelectedEquipmentId] = useState('');
const [selectedNozzleId, setSelectedNozzleId] = useState(''); const [selectedNozzleId, setSelectedNozzleId] = useState('');
const [selectedProducts, setSelectedProducts] = useState([]); const [selectedProducts, setSelectedProducts] = useState([]);
const [applicationType, setApplicationType] = useState('');
const [plannedDate, setPlannedDate] = useState(''); const [plannedDate, setPlannedDate] = useState('');
const [notes, setNotes] = useState(''); const [notes, setNotes] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -105,17 +106,42 @@ const ApplicationPlanModal = ({
}); });
}; };
// Add product to plan // Add product to plan (for liquid tank mixes)
const addProduct = () => { const addProduct = () => {
setSelectedProducts(prev => [...prev, { setSelectedProducts(prev => [...prev, {
uniqueId: '',
productId: null, productId: null,
userProductId: null, userProductId: null,
productName: '',
productBrand: '',
productType: applicationType,
rateAmount: '', rateAmount: '',
rateUnit: 'oz/1000sqft', rateUnit: applicationType === 'granular' ? 'lb/1000sqft' : 'oz/1000sqft',
isUserProduct: false isUserProduct: false
}]); }]);
}; };
// Handle application type change
const handleApplicationTypeChange = (type) => {
setApplicationType(type);
// Clear products when switching type
setSelectedProducts([]);
// Add initial product
setTimeout(() => {
setSelectedProducts([{
uniqueId: '',
productId: null,
userProductId: null,
productName: '',
productBrand: '',
productType: type,
rateAmount: '',
rateUnit: type === 'granular' ? 'lb/1000sqft' : 'oz/1000sqft',
isUserProduct: false
}]);
}, 0);
};
// Remove product from plan // Remove product from plan
const removeProduct = (index) => { const removeProduct = (index) => {
setSelectedProducts(prev => prev.filter((_, i) => i !== index)); setSelectedProducts(prev => prev.filter((_, i) => i !== index));
@@ -245,7 +271,7 @@ const ApplicationPlanModal = ({
<option value="">Select equipment</option> <option value="">Select equipment</option>
{equipment.map(eq => ( {equipment.map(eq => (
<option key={eq.id} value={eq.id}> <option key={eq.id} value={eq.id}>
{eq.name} ({eq.type}) {eq.name || 'Unnamed Equipment'} {eq.type ? `(${eq.type})` : ''}
</option> </option>
))} ))}
</select> </select>
@@ -286,78 +312,145 @@ const ApplicationPlanModal = ({
/> />
</div> </div>
{/* Application Type */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
<BeakerIcon className="h-4 w-4 inline mr-1" />
Application Type
</label>
<div className="flex gap-4 mb-3">
<label className="flex items-center">
<input
type="radio"
name="applicationType"
value="granular"
checked={applicationType === 'granular'}
onChange={(e) => handleApplicationTypeChange(e.target.value)}
className="mr-2"
/>
Granular (Spreader)
</label>
<label className="flex items-center">
<input
type="radio"
name="applicationType"
value="liquid"
checked={applicationType === 'liquid'}
onChange={(e) => handleApplicationTypeChange(e.target.value)}
className="mr-2"
/>
Liquid (Sprayer)
</label>
</div>
</div>
{/* Products */} {/* Products */}
<div> <div>
<div className="flex justify-between items-center mb-2"> <div className="flex justify-between items-center mb-2">
<label className="block text-sm font-medium text-gray-700"> <label className="block text-sm font-medium text-gray-700">
<BeakerIcon className="h-4 w-4 inline mr-1" /> Products to Apply
Products
</label> </label>
<button {applicationType === 'liquid' && (
type="button" <button
onClick={addProduct} type="button"
className="text-blue-600 hover:text-blue-800 text-sm" onClick={addProduct}
> className="text-blue-600 hover:text-blue-800 text-sm"
+ Add Product >
</button> + Add Product (Tank Mix)
</button>
)}
</div> </div>
{selectedProducts.map((product, index) => ( {selectedProducts.map((product, index) => (
<div key={index} className="border rounded p-3 mb-2"> <div key={index} className="border rounded p-3 mb-3">
<div className="grid grid-cols-1 md:grid-cols-3 gap-2"> {/* Product Selection */}
<div className="grid grid-cols-1 gap-3">
<select <select
value={product.isUserProduct ? `user_${product.userProductId}` : `shared_${product.productId}`} value={product.uniqueId || ''}
onChange={(e) => { onChange={(e) => {
const [type, id] = e.target.value.split('_'); const selectedProduct = products.find(p => p.uniqueId === e.target.value);
updateProduct(index, 'isUserProduct', type === 'user'); if (selectedProduct) {
updateProduct(index, type === 'user' ? 'userProductId' : 'productId', parseInt(id)); updateProduct(index, 'uniqueId', e.target.value);
updateProduct(index, type === 'user' ? 'productId' : 'userProductId', null); updateProduct(index, 'productId', selectedProduct.isShared ? selectedProduct.id : null);
updateProduct(index, 'userProductId', !selectedProduct.isShared ? selectedProduct.id : null);
updateProduct(index, 'isUserProduct', !selectedProduct.isShared);
updateProduct(index, 'productName', selectedProduct.name);
updateProduct(index, 'productBrand', selectedProduct.brand);
updateProduct(index, 'productType', selectedProduct.productType);
}
}} }}
className="border border-gray-300 rounded px-2 py-1" className="border border-gray-300 rounded px-3 py-2"
required required
> >
<option value="">Select product</option> <option value="">Select product</option>
{products.map(prod => ( {products
<option key={prod.uniqueId} value={prod.uniqueId}> .filter(prod => !applicationType || prod.productType === applicationType)
{prod.name} {prod.brand && `(${prod.brand})`} {prod.isShared ? '' : '(Custom)'} .map(prod => (
</option> <option key={prod.uniqueId} value={prod.uniqueId}>
))} {prod.name} {prod.brand && `(${prod.brand})`} {prod.isShared ? '' : '(Custom)'}
</option>
))}
</select> </select>
<div className="flex gap-1"> {/* Rate Input */}
<input <div className="grid grid-cols-2 gap-2">
type="number" <div>
step="0.01" <label className="block text-xs text-gray-600 mb-1">Application Rate</label>
placeholder="Rate" <input
value={product.rateAmount} type="number"
onChange={(e) => updateProduct(index, 'rateAmount', e.target.value)} step="0.01"
className="border border-gray-300 rounded px-2 py-1 flex-1" placeholder="Rate"
required value={product.rateAmount || ''}
/> onChange={(e) => updateProduct(index, 'rateAmount', e.target.value)}
<select className="w-full border border-gray-300 rounded px-3 py-2"
value={product.rateUnit} required
onChange={(e) => updateProduct(index, 'rateUnit', e.target.value)} />
className="border border-gray-300 rounded px-2 py-1" </div>
> <div>
<option value="oz/1000sqft">oz/1000sqft</option> <label className="block text-xs text-gray-600 mb-1">Rate Unit</label>
<option value="lb/1000sqft">lb/1000sqft</option> <select
<option value="fl oz/1000sqft">fl oz/1000sqft</option> value={product.rateUnit || (applicationType === 'granular' ? 'lb/1000sqft' : 'oz/1000sqft')}
</select> onChange={(e) => updateProduct(index, 'rateUnit', e.target.value)}
className="w-full border border-gray-300 rounded px-3 py-2"
>
{applicationType === 'granular' ? (
<>
<option value="lb/1000sqft">lb/1000sqft</option>
<option value="oz/1000sqft">oz/1000sqft</option>
</>
) : (
<>
<option value="oz/1000sqft">oz/1000sqft</option>
<option value="fl oz/1000sqft">fl oz/1000sqft</option>
<option value="lb/1000sqft">lb/1000sqft</option>
</>
)}
</select>
</div>
</div> </div>
<button {/* Remove Button */}
type="button" {applicationType === 'liquid' && selectedProducts.length > 1 && (
onClick={() => removeProduct(index)} <div className="flex justify-end">
className="text-red-600 hover:text-red-800 px-2" <button
> type="button"
Remove onClick={() => removeProduct(index)}
</button> className="text-red-600 hover:text-red-800 text-sm"
>
Remove Product
</button>
</div>
)}
</div> </div>
</div> </div>
))} ))}
{selectedProducts.length === 0 && ( {selectedProducts.length === 0 && (
<p className="text-gray-500 text-sm italic">No products added yet</p> <div className="text-center py-4 border-2 border-dashed border-gray-300 rounded">
<p className="text-gray-500 text-sm">
{applicationType ? `Select a ${applicationType} product to continue` : 'Select application type first'}
</p>
</div>
)} )}
</div> </div>
@@ -376,52 +469,67 @@ const ApplicationPlanModal = ({
</div> </div>
</div> </div>
{/* Right Column - Map */} {/* Right Column - Areas & Map */}
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> {/* Area Selection with Checkboxes */}
Select Areas to Treat {selectedPropertyDetails?.sections?.length > 0 && (
</label> <div className="mb-4">
<div className="h-96 border rounded-lg overflow-hidden"> <label className="block text-sm font-medium text-gray-700 mb-2">
{selectedPropertyDetails ? ( Select Areas to Treat
<PropertyMap </label>
center={mapCenter} <div className="max-h-32 overflow-y-auto border rounded p-2 bg-gray-50">
zoom={16} {selectedPropertyDetails.sections.map(section => (
property={selectedPropertyDetails} <label key={section.id} className="flex items-center py-1 cursor-pointer hover:bg-gray-100 rounded px-2">
sections={selectedPropertyDetails.sections || []} <input
selectedSections={selectedAreas} type="checkbox"
onSectionClick={handleAreaClick} checked={selectedAreas.includes(section.id)}
mode="select" onChange={() => handleAreaClick(section)}
editable={false} className="mr-2"
className="h-full w-full" />
/> <span className="text-sm">
) : ( {section.name} ({Math.round(section.area || 0).toLocaleString()} sq ft)
<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> </span>
); </label>
})} ))}
</div> </div>
<p className="text-xs text-gray-600 mt-1">
Total: {selectedAreas.reduce((total, areaId) => { {selectedAreas.length > 0 && (
const area = selectedPropertyDetails.sections?.find(s => s.id === areaId); <div className="mt-2 p-2 bg-blue-50 rounded">
return total + (area?.area || 0); <p className="text-sm font-medium text-blue-800">
}, 0).toLocaleString()} sq ft Total Selected: {selectedAreas.reduce((total, areaId) => {
</p> const area = selectedPropertyDetails.sections?.find(s => s.id === areaId);
return total + (area?.area || 0);
}, 0).toLocaleString()} sq ft
</p>
</div>
)}
</div> </div>
)} )}
{/* Map Display */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Property Map
</label>
<div className="h-64 border rounded-lg overflow-hidden">
{selectedPropertyDetails ? (
<PropertyMap
center={mapCenter}
zoom={16}
property={selectedPropertyDetails}
sections={selectedPropertyDetails.sections || []}
selectedSections={selectedAreas}
mode="view"
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>
</div>
</div> </div>
</div> </div>