From 83ee49b966b7056a2033bc19d71faf75a7a2bb35 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 28 Jun 2024 12:55:13 -0700 Subject: [PATCH 1/4] allow for captures without locations --- .../captures/AnimalCaptureContainer.tsx | 23 ++++++++++++++- .../location/CaptureLocationMapControl.tsx | 9 ++++-- .../capture-form/edit/EditCapturePage.tsx | 2 +- .../components/AnimalCaptureCardContainer.tsx | 6 ++-- .../captures/components/AnimalCapturesMap.tsx | 29 ++++++++++++------- .../components/CaptureDetails.tsx | 12 ++++---- .../components/ReleaseDetails.tsx | 4 --- .../profile/components/MeasurementDetails.tsx | 7 +++-- .../components/AnimalAttributeItem.tsx | 2 +- .../components/AnimalProfileHeader.tsx | 21 +++++++------- .../animals/profile/markings/MarkingCard.tsx | 2 +- .../form-sections/CaptureAnimalForm.tsx | 2 +- app/src/interfaces/useCritterApi.interface.ts | 4 +-- 13 files changed, 77 insertions(+), 46 deletions(-) diff --git a/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx b/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx index 397f25b629..778f689ad2 100644 --- a/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx +++ b/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx @@ -1,5 +1,10 @@ +import { mdiAlertRhombusOutline } from '@mdi/js'; +import Icon from '@mdi/react'; import Box from '@mui/material/Box'; +import { orange } from '@mui/material/colors'; import Skeleton from '@mui/material/Skeleton'; +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; import { SkeletonHorizontalStack } from 'components/loading/SkeletonLoaders'; import { AnimalCaptureCardContainer } from 'features/surveys/animals/profile/captures/components/AnimalCaptureCardContainer'; import { AnimalCapturesToolbar } from 'features/surveys/animals/profile/captures/components/AnimalCapturesToolbar'; @@ -100,6 +105,8 @@ export const AnimalCaptureContainer = () => { animalPageContext.critterDataLoader.refresh(critterbase_critter_id); }; + const capturesWithLocation = captures.filter((capture) => capture.capture_location); + return ( <> { ); }} /> - {captures.length > 0 && } + {capturesWithLocation.length < captures.length && ( + + + + + Missing Capture Location + + + Not all captures are visible on the map due to missing location data. Please update these captures with + location information. + + + + )} + {captures.length > 0 && } ); diff --git a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx index c3c3f35f30..2744d711d9 100644 --- a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx +++ b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx @@ -83,8 +83,10 @@ export const CaptureLocationMapControl = (coordinates ? String(coordinates.latitude) : ''); - const [longitudeInput, setLongitudeInput] = useState(coordinates ? String(coordinates.longitude) : ''); + const [latitudeInput, setLatitudeInput] = useState(coordinates?.latitude ? String(coordinates.latitude) : ''); + const [longitudeInput, setLongitudeInput] = useState( + coordinates?.longitude ? String(coordinates.longitude) : '' + ); const [updatedBounds, setUpdatedBounds] = useState(undefined); @@ -120,9 +122,10 @@ export const CaptureLocationMapControl = { type: 'Feature', geometry: { type: 'Point', - coordinates: [capture.capture_location.longitude ?? 0, capture.capture_location.latitude ?? 0] + coordinates: [capture?.capture_location?.longitude ?? 0, capture?.capture_location?.latitude ?? 0] }, properties: {} }, diff --git a/app/src/features/surveys/animals/profile/captures/components/AnimalCaptureCardContainer.tsx b/app/src/features/surveys/animals/profile/captures/components/AnimalCaptureCardContainer.tsx index c9cba48d94..5843757a69 100644 --- a/app/src/features/surveys/animals/profile/captures/components/AnimalCaptureCardContainer.tsx +++ b/app/src/features/surveys/animals/profile/captures/components/AnimalCaptureCardContainer.tsx @@ -166,11 +166,11 @@ export const AnimalCaptureCardContainer = (props: IAnimalCaptureCardContainer) = )}   - {capture.capture_location.latitude && capture.capture_location.longitude && ( + {capture.capture_location?.latitude && capture.capture_location?.longitude && ( - {capture.capture_location.latitude},  - {capture.capture_location.longitude} + {capture.capture_location?.latitude},  + {capture.capture_location?.longitude} )} diff --git a/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx b/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx index 0b585d42da..4ba2480551 100644 --- a/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx +++ b/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx @@ -19,16 +19,25 @@ interface IAnimalCapturesMapProps { export const AnimalCapturesMap = (props: IAnimalCapturesMapProps) => { const { captures, isLoading } = props; - const captureMapFeatures = captures - .filter((capture) => isDefined(capture.capture_location?.latitude) && isDefined(capture.capture_location.longitude)) - .map((capture) => ({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [capture.capture_location.longitude, capture.capture_location.latitude] - }, - properties: { captureId: capture.capture_id, date: capture.capture_date } - })) as Feature[]; + // Only include captures with valid locations + const captureMapFeatures: Feature[] = captures + .filter( + (capture) => isDefined(capture.capture_location?.latitude) && isDefined(capture.capture_location?.longitude) + ) + .map( + (capture) => + capture?.capture_location + ? ({ + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [capture.capture_location.longitude, capture.capture_location.latitude] + }, + properties: { captureId: capture.capture_id, date: capture.capture_date } + } as Feature) + : null // Return null instead of undefined + ) + .filter((feature): feature is Feature => isDefined(feature)); const staticLayers: IStaticLayer[] = captureMapFeatures.map((feature, index) => ({ layerName: 'Captures', diff --git a/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/CaptureDetails.tsx b/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/CaptureDetails.tsx index 9fab2b7d3e..ec9aa1ac02 100644 --- a/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/CaptureDetails.tsx +++ b/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/CaptureDetails.tsx @@ -24,10 +24,6 @@ export const CaptureDetails = (props: ICaptureDetailsProps) => { const captureLocation = capture.capture_location; const captureComment = capture.capture_comment; - if (!captureDate && (!captureLocation.latitude || !captureLocation.longitude) && !captureComment) { - return null; - } - return ( @@ -52,9 +48,11 @@ export const CaptureDetails = (props: ICaptureDetailsProps) => { sx={{ textTransform: 'uppercase', mb: 0.5 }}> Capture location - - {captureLocation.latitude}, {captureLocation.longitude} - + {captureLocation && ( + + {captureLocation.latitude}, {captureLocation.longitude} + + )} diff --git a/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/ReleaseDetails.tsx b/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/ReleaseDetails.tsx index 8314b8040c..232c5898c8 100644 --- a/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/ReleaseDetails.tsx +++ b/app/src/features/surveys/animals/profile/captures/components/capture-card-details/components/ReleaseDetails.tsx @@ -24,10 +24,6 @@ export const ReleaseDetails = (props: IReleaseDetailsProps) => { const releaseLocation = capture.release_location; const releaseComment = capture.release_comment; - if (!releaseDate && !releaseLocation && !releaseComment) { - return null; - } - return ( diff --git a/app/src/features/surveys/animals/profile/components/MeasurementDetails.tsx b/app/src/features/surveys/animals/profile/components/MeasurementDetails.tsx index b67f05daaf..9479c17416 100644 --- a/app/src/features/surveys/animals/profile/components/MeasurementDetails.tsx +++ b/app/src/features/surveys/animals/profile/components/MeasurementDetails.tsx @@ -33,8 +33,11 @@ export const MeasurementDetails = (props: IMeasurementDetailsProps) => { {allMeasurements.map((measurement) => ( - - {startCase(measurement.measurement_name)}: {measurement.value} + + {startCase(measurement.measurement_name)}:{' '} + + {measurement.value} + ))} diff --git a/app/src/features/surveys/animals/profile/details/components/AnimalAttributeItem.tsx b/app/src/features/surveys/animals/profile/details/components/AnimalAttributeItem.tsx index 4cd987f5cc..db5f9544da 100644 --- a/app/src/features/surveys/animals/profile/details/components/AnimalAttributeItem.tsx +++ b/app/src/features/surveys/animals/profile/details/components/AnimalAttributeItem.tsx @@ -18,7 +18,7 @@ export const AnimalAttributeItem = (props: IAnimalAttributeItemProps) => { return ( {props.startIcon && } - + {props.text} diff --git a/app/src/features/surveys/animals/profile/details/components/AnimalProfileHeader.tsx b/app/src/features/surveys/animals/profile/details/components/AnimalProfileHeader.tsx index 48e99e2e6e..265ec04b66 100644 --- a/app/src/features/surveys/animals/profile/details/components/AnimalProfileHeader.tsx +++ b/app/src/features/surveys/animals/profile/details/components/AnimalProfileHeader.tsx @@ -1,4 +1,4 @@ -import { mdiCheckboxMultipleBlankOutline, mdiInformationOutline, mdiPlusBoxOutline } from '@mdi/js'; +import { mdiCheckboxMultipleBlankOutline, mdiInformationOutline } from '@mdi/js'; import { Icon } from '@mdi/react'; import Box from '@mui/material/Box'; import green from '@mui/material/colors/green'; @@ -60,7 +60,6 @@ export const AnimalProfileHeader = (props: IAnimalProfileHeaderProps) => { } startIcon={mdiInformationOutline} /> - {critter.wlh_id && } { - - - Sex - - - {critter.sex} - - + {critter.wlh_id && ( + + + Wildlife Health ID + + + {critter.wlh_id} + + + )} {critter.collection_units.map((unit, index) => ( diff --git a/app/src/features/surveys/animals/profile/markings/MarkingCard.tsx b/app/src/features/surveys/animals/profile/markings/MarkingCard.tsx index 3c8a7809e5..e3ac1c6f40 100644 --- a/app/src/features/surveys/animals/profile/markings/MarkingCard.tsx +++ b/app/src/features/surveys/animals/profile/markings/MarkingCard.tsx @@ -40,7 +40,7 @@ export const MarkingCard = (props: IMarkingCardProps) => { return ( - + {startCase(marking_type_label)} {editable && ( diff --git a/app/src/features/surveys/view/survey-animals/form-sections/CaptureAnimalForm.tsx b/app/src/features/surveys/view/survey-animals/form-sections/CaptureAnimalForm.tsx index 056bc1a51b..2f7b683b05 100644 --- a/app/src/features/surveys/view/survey-animals/form-sections/CaptureAnimalForm.tsx +++ b/app/src/features/surveys/view/survey-animals/form-sections/CaptureAnimalForm.tsx @@ -87,7 +87,7 @@ export const CaptureAnimalForm = (props: AnimalFormProps) => { capture_id: props?.formObject?.capture_id, critter_id: props.critter.critter_id, capture_location: { - location_id: props?.formObject?.capture_location.location_id, + location_id: props?.formObject?.capture_location?.location_id, latitude: props?.formObject?.capture_location?.latitude ?? ('' as unknown as number), longitude: props?.formObject?.capture_location?.longitude ?? ('' as unknown as number), coordinate_uncertainty: diff --git a/app/src/interfaces/useCritterApi.interface.ts b/app/src/interfaces/useCritterApi.interface.ts index 5e704aac64..43a11fbc00 100644 --- a/app/src/interfaces/useCritterApi.interface.ts +++ b/app/src/interfaces/useCritterApi.interface.ts @@ -155,8 +155,8 @@ export type ICaptureResponse = { release_time: string | null; capture_comment: string | null; release_comment: string | null; - capture_location: ILocationResponse; - release_location: ILocationResponse | null | undefined; + capture_location?: ILocationResponse | null | undefined; + release_location?: ILocationResponse | null | undefined; }; export type IMarkingResponse = { From 98211ab37864e4853d838a349fbf2be5290ebcef Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 5 Jul 2024 12:25:21 -0700 Subject: [PATCH 2/4] reset release collection on yes & remove unnecessary filter --- .../captures/AnimalCaptureContainer.tsx | 15 +++----- .../location/ReleaseLocationForm.tsx | 9 ++++- .../captures/components/AnimalCapturesMap.tsx | 37 ++++++++++--------- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx b/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx index 778f689ad2..aed46f7446 100644 --- a/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx +++ b/app/src/features/surveys/animals/profile/captures/AnimalCaptureContainer.tsx @@ -118,17 +118,12 @@ export const AnimalCaptureContainer = () => { }} /> {capturesWithLocation.length < captures.length && ( - + - - - Missing Capture Location - - - Not all captures are visible on the map due to missing location data. Please update these captures with - location information. - - + + Not all captures are visible on the map due to missing location data. Please update these captures with + location information. + )} {captures.length > 0 && } diff --git a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx index b430969d39..6a38d76667 100644 --- a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx +++ b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx @@ -15,7 +15,7 @@ import { CaptureLocationMapControl } from './CaptureLocationMapControl'; * @return {*} */ export const ReleaseLocationForm = () => { - const { values } = useFormikContext(); + const { values, setFieldValue } = useFormikContext(); const [isReleaseSameAsCapture, setIsReleaseSameAsCapture] = useState( !(values.capture.release_location && values.capture.capture_location) || @@ -30,7 +30,12 @@ export const ReleaseLocationForm = setIsReleaseSameAsCapture(event.target.value === 'true')}> + onChange={(event) => { + if (event.target.value) { + setFieldValue('capture.release_location', values.capture.capture_location); + } + setIsReleaseSameAsCapture(event.target.value === 'true'); + }}> } label="Yes" /> } label="No" /> diff --git a/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx b/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx index 4ba2480551..602774f9fc 100644 --- a/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx +++ b/app/src/features/surveys/animals/profile/captures/components/AnimalCapturesMap.tsx @@ -20,24 +20,25 @@ export const AnimalCapturesMap = (props: IAnimalCapturesMapProps) => { const { captures, isLoading } = props; // Only include captures with valid locations - const captureMapFeatures: Feature[] = captures - .filter( - (capture) => isDefined(capture.capture_location?.latitude) && isDefined(capture.capture_location?.longitude) - ) - .map( - (capture) => - capture?.capture_location - ? ({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [capture.capture_location.longitude, capture.capture_location.latitude] - }, - properties: { captureId: capture.capture_id, date: capture.capture_date } - } as Feature) - : null // Return null instead of undefined - ) - .filter((feature): feature is Feature => isDefined(feature)); + const captureMapFeatures: Feature[] = []; + + for (const capture of captures) { + if ( + capture.capture_location && + isDefined(capture.capture_location.latitude) && + isDefined(capture.capture_location.longitude) + ) { + const feature: Feature = { + type: 'Feature', + geometry: { + type: 'Point', + coordinates: [capture.capture_location.longitude, capture.capture_location.latitude] + }, + properties: { captureId: capture.capture_id, date: capture.capture_date } + }; + captureMapFeatures.push(feature); + } + } const staticLayers: IStaticLayer[] = captureMapFeatures.map((feature, index) => ({ layerName: 'Captures', From 57e4913ddf5af73681cd1d1ce65bcb4a5013e375 Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Fri, 5 Jul 2024 13:07:37 -0700 Subject: [PATCH 3/4] Minor update to release_location control. Updated comments to explain capture vs release location business logic. --- .../components/location/CaptureLocationMapControl.tsx | 2 ++ .../capture-form/components/location/ReleaseLocationForm.tsx | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx index 2744d711d9..fb775a470a 100644 --- a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx +++ b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/CaptureLocationMapControl.tsx @@ -39,6 +39,8 @@ export interface ICaptureLocationMapControlProps { /** * Capture location map control component. * + * This component can be used to record a Point location on a map, for either a capture or release location. + * * @param {ICaptureLocationMapControlProps} props * @return {*} */ diff --git a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx index 6a38d76667..15d3855b22 100644 --- a/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx +++ b/app/src/features/surveys/animals/profile/captures/capture-form/components/location/ReleaseLocationForm.tsx @@ -17,6 +17,8 @@ import { CaptureLocationMapControl } from './CaptureLocationMapControl'; export const ReleaseLocationForm = () => { const { values, setFieldValue } = useFormikContext(); + // Determine if the release location is the same as the capture location + // If the release location is the same as the capture location, we don't need to show the release location map control const [isReleaseSameAsCapture, setIsReleaseSameAsCapture] = useState( !(values.capture.release_location && values.capture.capture_location) || booleanEqual(values.capture.release_location, values.capture.capture_location) @@ -32,7 +34,8 @@ export const ReleaseLocationForm = { if (event.target.value) { - setFieldValue('capture.release_location', values.capture.capture_location); + // Clear the release location if it is the same as the capture location + setFieldValue('capture.release_location', null); } setIsReleaseSameAsCapture(event.target.value === 'true'); }}> From 5d89e049e37250f78b672501fd55dc5d489cf51a Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Fri, 5 Jul 2024 13:10:09 -0700 Subject: [PATCH 4/4] fix code smell --- app/src/interfaces/useCritterApi.interface.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/interfaces/useCritterApi.interface.ts b/app/src/interfaces/useCritterApi.interface.ts index 43a11fbc00..9ad15e16e4 100644 --- a/app/src/interfaces/useCritterApi.interface.ts +++ b/app/src/interfaces/useCritterApi.interface.ts @@ -155,8 +155,8 @@ export type ICaptureResponse = { release_time: string | null; capture_comment: string | null; release_comment: string | null; - capture_location?: ILocationResponse | null | undefined; - release_location?: ILocationResponse | null | undefined; + capture_location?: ILocationResponse | null; + release_location?: ILocationResponse | null; }; export type IMarkingResponse = {