Skip to content

ERA-11237: Conditional display of event detail location markers on map #1258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 24, 2025
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
3 changes: 2 additions & 1 deletion public/locales/en-US/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@
"doneButton": "Done"
},
"formPreview": {
"collectionHumanizedValue": "{{collectionLength}} items"
"collectionHumanizedValue": "{{collectionLength}} items",
"jumpToLocationButtonLabel": "Jump to {{field}} location"
},
"chevronButtonLabel": {
"closed": "Open the {{itemTitle}} form preview",
Expand Down
3 changes: 2 additions & 1 deletion public/locales/es/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@
"doneButton": "Hecho"
},
"formPreview": {
"collectionHumanizedValue": "{{collectionLength}} elementos"
"collectionHumanizedValue": "{{collectionLength}} elementos",
"jumpToLocationButtonLabel": "Ir a la ubicación de {{field}}"
},
"chevronButtonLabel": {
"closed": "Abrir la vista previa del formulario {{itemTitle}}",
Expand Down
3 changes: 2 additions & 1 deletion public/locales/fr/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@
"doneButton": "Terminé"
},
"formPreview": {
"collectionHumanizedValue": "{{collectionLength}} éléments"
"collectionHumanizedValue": "{{collectionLength}} éléments",
"jumpToLocationButtonLabel": "Aller à l'emplacement de {{field}}"
},
"chevronButtonLabel": {
"closed": "Ouvrir l'aperçu du formulaire {{itemTitle}}",
Expand Down
5 changes: 3 additions & 2 deletions public/locales/ne-NP/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,9 @@
"doneButton": "सम्पन्न"
},
"formPreview": {
"collectionHumanizedValue": "{{collectionLength}} वस्तुहरू"
},
"collectionHumanizedValue": "{{collectionLength}} वस्तुहरू",
"jumpToLocationButtonLabel": "{{field}} स्थानमा जानुहोस्"
},
"chevronButtonLabel": {
"closed": "{{itemTitle}} फारमको पूर्वावलोकन खोल्नुहोस्",
"open": "{{itemTitle}} फारमको पूर्वावलोकन बन्द गर्नुहोस्"
Expand Down
3 changes: 2 additions & 1 deletion public/locales/pt/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@
"doneButton": "Concluído"
},
"formPreview": {
"collectionHumanizedValue": "{{collectionLength}} itens"
"collectionHumanizedValue": "{{collectionLength}} itens",
"jumpToLocationButtonLabel": "Ir para a localização de {{field}}"
},
"chevronButtonLabel": {
"closed": "Abrir a visualização do formulário {{itemTitle}}",
Expand Down
3 changes: 2 additions & 1 deletion public/locales/sw/reports.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@
"doneButton": "Imekamilika"
},
"formPreview": {
"collectionHumanizedValue": "{{collectionLength}} vitu"
"collectionHumanizedValue": "{{collectionLength}} vitu",
"jumpToLocationButtonLabel": "Ruka kwenye eneo la {{field}}"
},
"chevronButtonLabel": {
"closed": "Fungua muhtasari wa fomu ya {{itemTitle}}",
Expand Down
8 changes: 7 additions & 1 deletion src/CursorGpsDisplay/MenuPopover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ const MenuPopover = ({ buttonRef, className, onClose, ...otherProps }, ref) => {
}
};

const onGpsInputButtonClick = (event) => {
event.stopPropagation();

onJumpToCoordinates();
};

useEffect(() => {
// Select the GPS input on mount so user can type away or navigate.
gpsInputRef.current.select();
Expand Down Expand Up @@ -115,7 +121,7 @@ const MenuPopover = ({ buttonRef, className, onClose, ...otherProps }, ref) => {
aria-label={t('gpsInputButtonLabel')}
className={styles.gpsInputButton}
disabled={!gpsInputValue}
onClick={() => onJumpToCoordinates()}
onClick={onGpsInputButtonClick}
ref={gpsInputButtonRef}
title={t('gpsInputButtonLabel')}
type="button"
Expand Down
29 changes: 25 additions & 4 deletions src/LocationPicker/MenuPopover/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const eventReportTracker = trackEventFactory(EVENT_REPORT_CATEGORY);
const MenuPopover = ({
className,
id,
onBlur,
onChange,
onClose,
setLocationButtonRef,
Expand Down Expand Up @@ -104,14 +105,34 @@ const MenuPopover = ({
}, []);

useEffect(() => {
const onMouseDown = (event) => !wrapperRef.current.contains(event.target)
&& !setLocationButtonRef.current.contains(event.target)
&& onClose();
const onMouseDown = (event) => {
if (!wrapperRef.current.contains(event.target) && !setLocationButtonRef.current.contains(event.target)) {
onClose(event);

if (!target.current.contains(event.target)) {
// Clicking away from our picker when the menu is open doesn't trigger the wrapper's blur event, so we need
// to trigger the onBlur callback manually.
const blurEvent = new FocusEvent('blur', {
bubbles: true,
cancelable: false,
relatedTarget: event.target,
});

Object.defineProperties(blurEvent, {
target: {
value: target.current,
},
});

onBlur(blurEvent);
}
}
};

document.addEventListener('mousedown', onMouseDown);

return () => document.removeEventListener('mousedown', onMouseDown);
}, [onClose, setLocationButtonRef]);
}, [onBlur, onClose, setLocationButtonRef, target]);

return <Popover
className={`${className} ${styles.menuPopover}`}
Expand Down
48 changes: 47 additions & 1 deletion src/LocationPicker/MenuPopover/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jest.mock('mapbox-gl', () => ({
}));

describe('LocationPicker - MenuPopover', () => {
const onBlur = jest.fn();
const onChange = jest.fn();
const onClose = jest.fn();
const setLocationButtonRefFocus = jest.fn();
Expand All @@ -48,6 +49,7 @@ describe('LocationPicker - MenuPopover', () => {
<MenuPopover
className="className"
id="locationPicker"
onBlur={onBlur}
onChange={onChange}
onClose={onClose}
setLocationButtonRef={{
Expand All @@ -59,6 +61,7 @@ describe('LocationPicker - MenuPopover', () => {
style={{}}
target={{
current: {
contains: () => false,
offsetWidth: 100,
},
}}
Expand Down Expand Up @@ -166,7 +169,47 @@ describe('LocationPicker - MenuPopover', () => {
expect(setLocationButtonRefFocus).toHaveBeenCalledTimes(1);
});

test('closes the menu if the user clicks outside', () => {
test('closes the menu if the user clicks outside and triggers the blur callback if the click was outside of the picker', () => {
render(<>
<div data-testid="outside" />

<Provider store={mockStore(store)}>
<MapContext.Provider value={map}>
<MenuPopover
className="className"
id="locationPicker"
onBlur={onBlur}
onChange={onChange}
onClose={onClose}
setLocationButtonRef={{
current: {
contains: () => false,
focus: setLocationButtonRefFocus,
},
}}
style={{}}
target={{
current: {
contains: () => false,
offsetWidth: 100,
},
}}
value={null}
/>
</MapContext.Provider>
</Provider>
</>);

expect(onClose).not.toHaveBeenCalled();
expect(onBlur).not.toHaveBeenCalled();

userEvent.click(screen.getByTestId('outside'));

expect(onClose).toHaveBeenCalledTimes(1);
expect(onBlur).toHaveBeenCalledTimes(1);
});

test('closes the menu if the user clicks outside but does not trigger the blur callback if the click was inside the picker', () => {
render(<>
<div data-testid="outside" />

Expand All @@ -175,6 +218,7 @@ describe('LocationPicker - MenuPopover', () => {
<MenuPopover
className="className"
id="locationPicker"
onBlur={onBlur}
onChange={onChange}
onClose={onClose}
setLocationButtonRef={{
Expand All @@ -186,6 +230,7 @@ describe('LocationPicker - MenuPopover', () => {
style={{}}
target={{
current: {
contains: () => true,
offsetWidth: 100,
},
}}
Expand All @@ -200,5 +245,6 @@ describe('LocationPicker - MenuPopover', () => {
userEvent.click(screen.getByTestId('outside'));

expect(onClose).toHaveBeenCalledTimes(1);
expect(onBlur).not.toHaveBeenCalled();
});
});
5 changes: 4 additions & 1 deletion src/LocationPicker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const LocationPicker = ({
disabled = false,
id,
inputProps = {},
jumpToLocationButtonZoom = undefined,
name = '',
onBlur = null,
onChange,
Expand Down Expand Up @@ -71,6 +72,7 @@ const LocationPicker = ({
className={`${styles.input} ${readOnly ? styles.readOnly : ''}`}
disabled={disabled}
id={id}
onFocus={() => setLocationButtonRef.current.focus()}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For form data management, the id must be appended to the <input> but we forward the focusing to the button so when we programmatically focus the element by the id (like when we click a location marker) we see the location picker button being focused.

placeholder={placeholder || t('defaultPlaceholder')}
readOnly
required={required}
Expand All @@ -97,7 +99,7 @@ const LocationPicker = ({
aria-label={t('jumpToLocationButtonLabel')}
className={styles.jumpToLocationButton}
disabled={!value || disabled}
onClick={() => jumpToLocation([value.longitude, value.latitude])}
onClick={() => jumpToLocation([value.longitude, value.latitude], jumpToLocationButtonZoom)}
title={t('jumpToLocationButtonLabel')}
type="button"
>
Expand All @@ -116,6 +118,7 @@ const LocationPicker = ({
<MenuPopover
id={id}
onChange={onChange}
onBlur={onBlur}
onClose={() => setIsMenuPopoverOpen(false)}
setLocationButtonRef={setLocationButtonRef}
target={innerRef}
Expand Down
27 changes: 26 additions & 1 deletion src/LocationPicker/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ describe('LocationPicker', () => {
expect(screen.getByLabelText('Location')).toBeRequired();
});

test('forwards the focusing of the input to the set location button', () => {
renderLocationPicker();

fireEvent.focus(screen.getByLabelText('Location'));

expect(screen.getByLabelText('Open the location picker menu to set a value')).toHaveFocus();
});

test('shows a display value in the input', () => {
renderLocationPicker({
value: {
Expand Down Expand Up @@ -221,7 +229,24 @@ describe('LocationPicker', () => {
userEvent.click(screen.getByLabelText('Jump to location'));

expect(jumpToLocationMock).toHaveBeenCalledTimes(1);
expect(jumpToLocationMock).toHaveBeenCalledWith([10, 15]);
expect(jumpToLocationMock).toHaveBeenCalledWith([10, 15], undefined);
});

test('jumps to the location with a custom zoom', () => {
renderLocationPicker({
jumpToLocationButtonZoom: 20,
value: {
latitude: 15,
longitude: 10,
},
});

expect(jumpToLocationMock).not.toHaveBeenCalled();

userEvent.click(screen.getByLabelText('Jump to location'));

expect(jumpToLocationMock).toHaveBeenCalledTimes(1);
expect(jumpToLocationMock).toHaveBeenCalledWith([10, 15], 20);
});

test('opens the menu popover', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/LocationPicker/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
background-color: colors.$disabled-field-gray;
}

&:focus-visible:not(:disabled) {
&:focus:not(:disabled) {
border: 2px solid colors.$bright-blue;
}

Expand Down
2 changes: 2 additions & 0 deletions src/ReportManager/DetailsSection/SchemaForm/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ export const HEADER_ELEMENT_SIZES = {
SMALL: 'SMALL',
};

export const JUMP_TO_LOCATION_BUTTON_ZOOM = 20;

export const ROOT_CANVAS_ID = 'root';

export const TEXT_ELEMENT_INPUT_TYPES = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const FormModal = ({
breadcrumbs,
columns,
errors,
focusLocationMarker,
formData,
isOpen,
itemName,
Expand Down Expand Up @@ -74,6 +75,7 @@ const FormModal = ({
formData[fieldId],
onFieldChange,
errors?.[fieldId],
focusLocationMarker,
[...breadcrumbs, { display: title, id: fieldId }]
))}
</div>
Expand All @@ -87,6 +89,7 @@ const FormModal = ({
formData[fieldId],
onFieldChange,
errors?.[fieldId],
focusLocationMarker,
[...breadcrumbs, { display: title, id: fieldId }]
))}
</div>}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { mockStore } from '../../../../../../../../__test-helpers/MockStore';
import FormModal from './';

describe('ReportManager - DetailsSection - SchemaForm - fields - Collection - SortableList - Item - FormModal', () => {
const focusLocationMarker = jest.fn();
const onCancel = jest.fn();
const onDeleteItem = jest.fn();
const onDone = jest.fn();
Expand All @@ -31,6 +32,7 @@ describe('ReportManager - DetailsSection - SchemaForm - fields - Collection - So
breadcrumbs={[{ id: '1', display: 'Item 1' }, { id: '2', display: 'Item 2' }]}
columns={1}
errors={{}}
focusLocationMarker={focusLocationMarker}
formData={{ 'field-1': 'Value 1', 'field-2': 'Value 2' }}
isOpen
itemName="Item"
Expand Down Expand Up @@ -115,13 +117,15 @@ describe('ReportManager - DetailsSection - SchemaForm - fields - Collection - So
'Value 1',
onFieldChange,
undefined,
focusLocationMarker,
[{ id: '1', display: 'Item 1' }, { id: '2', display: 'Item 2' }, { id: 'field-1', display: 'Item 3' }]
);
expect(renderField).toHaveBeenCalledWith(
'field-2',
'Value 2',
onFieldChange,
undefined,
focusLocationMarker,
[{ id: '1', display: 'Item 1' }, { id: '2', display: 'Item 2' }, { id: 'field-2', display: 'Item 3' }]
);
});
Expand Down
Loading