Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed code/README.md
Empty file.
17 changes: 0 additions & 17 deletions code/backend/useful_commands.txt
Original file line number Diff line number Diff line change
@@ -1,20 +1,3 @@
For running the docker image: docker run --rm -p 8001:8001 -e RESULTS_DIR=/app/results -v "/home/logan/repos/DBD/code/backend/results:/app/results" dbd-backend:local
For building the docker image: docker build -t dbd-backend:local ./code/backend

For running the frontend: docker run --rm -p 8000:8000 dbd-frontend:local
For building the frontend: docker build -t dbd-frontend:local ./code/frontend

Backend test, no file mount:
For running the docker image: docker run --rm -p 8001:8001 dbd-backend:local


Backend run
docker run --rm --network="host" dbd-backend:local

Frontend run:
docker run --rm --network="host" dbd-frontend:local


####################### START HERE ######################################
Build frontend using:
docker build -t dbd-frontend:local ./code/frontend
Expand Down
45 changes: 38 additions & 7 deletions code/frontend/src/components/PathGenerationOptions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import type { UploadSystemSettings } from '../types/upload';

interface PathGenerationOptionsProps {
Expand Down Expand Up @@ -28,15 +28,36 @@ const PathGenerationOptions: React.FC<PathGenerationOptionsProps> = ({
onCoverageWidthOverrideChange,
settings,
}) => {
const [headingDraft, setHeadingDraft] = useState(String(pathHeading));
const [robotWidthDraft, setRobotWidthDraft] = useState(String(robotWidthOverride));
const [coverageWidthDraft, setCoverageWidthDraft] = useState(String(coverageWidthOverride));

useEffect(() => {
setHeadingDraft(String(pathHeading));
}, [pathHeading]);

useEffect(() => {
setRobotWidthDraft(String(robotWidthOverride));
}, [robotWidthOverride]);

useEffect(() => {
setCoverageWidthDraft(String(coverageWidthOverride));
}, [coverageWidthOverride]);

return (
<div className="grid grid-cols-1 gap-3 md:grid-cols-3">
<label className="text-sm text-dark-300">
Heading (degrees)
<input
className="mt-1 w-full rounded border border-dark-600 bg-dark-700 px-3 py-2 text-dark-100"
type="number"
value={pathHeading}
onChange={(e) => onPathHeadingChange(parseFloat(e.target.value) || 0)}
value={headingDraft}
onChange={(e) => {
const { value } = e.target;
setHeadingDraft(value);
const parsed = Number.parseFloat(value);
if (Number.isFinite(parsed)) onPathHeadingChange(parsed);
}}
/>
</label>
<label className="text-sm text-dark-300">
Expand All @@ -52,8 +73,13 @@ const PathGenerationOptions: React.FC<PathGenerationOptionsProps> = ({
className="mt-1 w-full rounded border border-dark-600 bg-dark-700 px-3 py-2 text-dark-100 disabled:opacity-50"
type="number"
disabled={useDefaultRobotWidth}
value={robotWidthOverride}
onChange={(e) => onRobotWidthOverrideChange(parseFloat(e.target.value) || 0.1)}
value={robotWidthDraft}
onChange={(e) => {
const { value } = e.target;
setRobotWidthDraft(value);
const parsed = Number.parseFloat(value);
if (Number.isFinite(parsed)) onRobotWidthOverrideChange(parsed);
}}
/>
</label>
<label className="text-sm text-dark-300">
Expand All @@ -69,8 +95,13 @@ const PathGenerationOptions: React.FC<PathGenerationOptionsProps> = ({
className="mt-1 w-full rounded border border-dark-600 bg-dark-700 px-3 py-2 text-dark-100 disabled:opacity-50"
type="number"
disabled={useDefaultCoverageWidth}
value={coverageWidthOverride}
onChange={(e) => onCoverageWidthOverrideChange(parseFloat(e.target.value) || 0.1)}
value={coverageWidthDraft}
onChange={(e) => {
const { value } = e.target;
setCoverageWidthDraft(value);
const parsed = Number.parseFloat(value);
if (Number.isFinite(parsed)) onCoverageWidthOverrideChange(parsed);
}}
/>
</label>
</div>
Expand Down
16 changes: 15 additions & 1 deletion code/frontend/src/components/PesticidePrescriptionsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,17 @@ const PesticidePrescriptionsView: React.FC = () => {
return computePrescriptionTotalGallons(detailGeojson, getSprayForFeature, override);
}, [detailGeojson, getSprayForFeature, detailConfig, draftGpaNone, draftGpaLow, draftGpaHigh]);

const parsedDraftGpaNone = draftGpaNone.trim() === '' ? Number.NaN : Number.parseFloat(draftGpaNone);
const parsedDraftGpaLow = draftGpaLow.trim() === '' ? Number.NaN : Number.parseFloat(draftGpaLow);
const parsedDraftGpaHigh = draftGpaHigh.trim() === '' ? Number.NaN : Number.parseFloat(draftGpaHigh);
const thresholdsAreValid =
Number.isFinite(parsedDraftGpaNone) &&
parsedDraftGpaNone >= 0 &&
Number.isFinite(parsedDraftGpaLow) &&
parsedDraftGpaLow >= 0 &&
Number.isFinite(parsedDraftGpaHigh) &&
parsedDraftGpaHigh >= 0;
Comment on lines +287 to +296

Copilot AI Apr 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thresholdsAreValid currently requires all three draft fields to be non-empty finite numbers. However openThresholdsModal initializes missing values as empty strings and saveThresholds explicitly treats empty strings as “keep current saved value”. As a result, users can no longer leave any threshold blank (even to keep the current value), and the Save button stays disabled. Consider treating empty strings as valid (e.g., validate each field as either blank or a finite >= 0 number) to match the save/merge behavior.

Suggested change
const parsedDraftGpaNone = draftGpaNone.trim() === '' ? Number.NaN : Number.parseFloat(draftGpaNone);
const parsedDraftGpaLow = draftGpaLow.trim() === '' ? Number.NaN : Number.parseFloat(draftGpaLow);
const parsedDraftGpaHigh = draftGpaHigh.trim() === '' ? Number.NaN : Number.parseFloat(draftGpaHigh);
const thresholdsAreValid =
Number.isFinite(parsedDraftGpaNone) &&
parsedDraftGpaNone >= 0 &&
Number.isFinite(parsedDraftGpaLow) &&
parsedDraftGpaLow >= 0 &&
Number.isFinite(parsedDraftGpaHigh) &&
parsedDraftGpaHigh >= 0;
const isValidThresholdDraft = (value: string) => {
if (value.trim() === '') return true;
const parsedValue = Number.parseFloat(value);
return Number.isFinite(parsedValue) && parsedValue >= 0;
};
const thresholdsAreValid =
isValidThresholdDraft(draftGpaNone) &&
isValidThresholdDraft(draftGpaLow) &&
isValidThresholdDraft(draftGpaHigh);

Copilot uses AI. Check for mistakes.

const saveSprayUpdates = useCallback(async () => {
if (!selectedTaskId) return;
const updates = Object.entries(sprayUpdates).map(([featureId, spray]) => ({ featureId, spray }));
Expand Down Expand Up @@ -921,6 +932,9 @@ const PesticidePrescriptionsView: React.FC = () => {
/>
</label>
</div>
{!thresholdsAreValid && (
<p className="mt-3 text-sm text-red-400">All spray thresholds must be numeric values greater than or equal to 0.</p>
)}
{previewGallonsInModal != null && (
<p className="text-dark-200 text-sm mt-4">
<span className="font-medium text-primary-400">Preview total spray:</span>{' '}
Expand All @@ -938,7 +952,7 @@ const PesticidePrescriptionsView: React.FC = () => {
<button
type="button"
onClick={saveThresholds}
disabled={savingThresholds}
disabled={savingThresholds || !thresholdsAreValid}
className="px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 disabled:opacity-50"
>
{savingThresholds ? 'Saving…' : 'Save thresholds'}
Expand Down
Loading
Loading