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 [selectedNozzleId, setSelectedNozzleId] = useState('');
const [selectedProducts, setSelectedProducts] = useState([]);
const [applicationType, setApplicationType] = useState('');
const [plannedDate, setPlannedDate] = useState('');
const [notes, setNotes] = useState('');
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 = () => {
setSelectedProducts(prev => [...prev, {
uniqueId: '',
productId: null,
userProductId: null,
productName: '',
productBrand: '',
productType: applicationType,
rateAmount: '',
rateUnit: 'oz/1000sqft',
rateUnit: applicationType === 'granular' ? 'lb/1000sqft' : 'oz/1000sqft',
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
const removeProduct = (index) => {
setSelectedProducts(prev => prev.filter((_, i) => i !== index));
@@ -245,7 +271,7 @@ const ApplicationPlanModal = ({
<option value="">Select equipment</option>
{equipment.map(eq => (
<option key={eq.id} value={eq.id}>
{eq.name} ({eq.type})
{eq.name || 'Unnamed Equipment'} {eq.type ? `(${eq.type})` : ''}
</option>
))}
</select>
@@ -286,78 +312,145 @@ const ApplicationPlanModal = ({
/>
</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 */}
<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
Products to Apply
</label>
{applicationType === 'liquid' && (
<button
type="button"
onClick={addProduct}
className="text-blue-600 hover:text-blue-800 text-sm"
>
+ Add Product
+ Add Product (Tank Mix)
</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">
<div key={index} className="border rounded p-3 mb-3">
{/* Product Selection */}
<div className="grid grid-cols-1 gap-3">
<select
value={product.isUserProduct ? `user_${product.userProductId}` : `shared_${product.productId}`}
value={product.uniqueId || ''}
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);
const selectedProduct = products.find(p => p.uniqueId === e.target.value);
if (selectedProduct) {
updateProduct(index, 'uniqueId', e.target.value);
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
>
<option value="">Select product</option>
{products.map(prod => (
{products
.filter(prod => !applicationType || prod.productType === applicationType)
.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">
{/* Rate Input */}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="block text-xs text-gray-600 mb-1">Application Rate</label>
<input
type="number"
step="0.01"
placeholder="Rate"
value={product.rateAmount}
value={product.rateAmount || ''}
onChange={(e) => updateProduct(index, 'rateAmount', e.target.value)}
className="border border-gray-300 rounded px-2 py-1 flex-1"
className="w-full border border-gray-300 rounded px-3 py-2"
required
/>
</div>
<div>
<label className="block text-xs text-gray-600 mb-1">Rate Unit</label>
<select
value={product.rateUnit}
value={product.rateUnit || (applicationType === 'granular' ? 'lb/1000sqft' : 'oz/1000sqft')}
onChange={(e) => updateProduct(index, 'rateUnit', e.target.value)}
className="border border-gray-300 rounded px-2 py-1"
className="w-full border border-gray-300 rounded px-3 py-2"
>
<option value="oz/1000sqft">oz/1000sqft</option>
{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>
{/* Remove Button */}
{applicationType === 'liquid' && selectedProducts.length > 1 && (
<div className="flex justify-end">
<button
type="button"
onClick={() => removeProduct(index)}
className="text-red-600 hover:text-red-800 px-2"
className="text-red-600 hover:text-red-800 text-sm"
>
Remove
Remove Product
</button>
</div>
)}
</div>
</div>
))}
{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>
@@ -376,12 +469,49 @@ const ApplicationPlanModal = ({
</div>
</div>
{/* Right Column - Map */}
{/* Right Column - Areas & Map */}
<div>
{/* Area Selection with Checkboxes */}
{selectedPropertyDetails?.sections?.length > 0 && (
<div className="mb-4">
<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">
<div className="max-h-32 overflow-y-auto border rounded p-2 bg-gray-50">
{selectedPropertyDetails.sections.map(section => (
<label key={section.id} className="flex items-center py-1 cursor-pointer hover:bg-gray-100 rounded px-2">
<input
type="checkbox"
checked={selectedAreas.includes(section.id)}
onChange={() => handleAreaClick(section)}
className="mr-2"
/>
<span className="text-sm">
{section.name} ({Math.round(section.area || 0).toLocaleString()} sq ft)
</span>
</label>
))}
</div>
{selectedAreas.length > 0 && (
<div className="mt-2 p-2 bg-blue-50 rounded">
<p className="text-sm font-medium text-blue-800">
Total Selected: {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>
)}
{/* 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}
@@ -389,8 +519,7 @@ const ApplicationPlanModal = ({
property={selectedPropertyDetails}
sections={selectedPropertyDetails.sections || []}
selectedSections={selectedAreas}
onSectionClick={handleAreaClick}
mode="select"
mode="view"
editable={false}
className="h-full w-full"
/>
@@ -400,28 +529,7 @@ const ApplicationPlanModal = ({
</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>