Skip to content
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

feat: Polygon and Point search #190

Merged
merged 4 commits into from
Jul 27, 2024
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
4 changes: 4 additions & 0 deletions frontend/src/assets/svgs/crosshairs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions frontend/src/assets/svgs/fa-trash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 2 additions & 4 deletions frontend/src/components/DataLayersCheckboxGroup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,10 @@ describe('Test suite for DataLayersCheckboxGroup', () => {
DATA_LAYER_GROUPS.forEach(({ name, layers }) => {
screen.getByRole('button', { name, pressed: true })
layers.forEach((layer) => {
const cb = screen.getByLabelText(layer.name)
expect(cb).not.toBeChecked()
if (layer.url) {
const cb = screen.getByLabelText(layer.name)
expect(cb).not.toBeChecked()
expect(cb).toBeEnabled()
} else {
expect(cb).toBeDisabled()
}
})
})
Expand Down
23 changes: 12 additions & 11 deletions frontend/src/components/DataLayersToggleGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,18 @@ export function DataLayersToggleGroup({
</Button>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<FormGroup className="data-layers-checkbox-list">
{group.layers.map((layer: DataLayer) => (
<FormControlLabel
key={`dataLayerCheckBox-${layer.name}`}
control={<Checkbox />}
checked={isDataLayerChecked(layer)}
label={layer.name}
disabled={!layer.url}
className="data-layers-checkbox-item"
onChange={() => onLayerToggle(layer)}
/>
))}
{group.layers.map((layer: DataLayer) =>
layer.url ? (
<FormControlLabel
key={`dataLayerCheckBox-${layer.name}`}
control={<Checkbox />}
checked={isDataLayerChecked(layer)}
label={layer.name}
className="data-layers-checkbox-item"
onChange={() => onLayerToggle(layer)}
/>
) : null,
)}
</FormGroup>
</Collapse>
</Stack>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/DropdownButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Button, ButtonProps, MenuProps } from '@mui/material'
import Menu from '@mui/material/Menu'
import clsx from 'clsx'

import downArrow from '@/assets/svgs/fa-caret-down.svg'
import DownArrow from '@/assets/svgs/fa-caret-down.svg?react'

import './DropdownButton.css'

Expand Down Expand Up @@ -68,7 +68,7 @@ export function DropdownButton({
aria-label={typeof children === 'string' ? children : undefined}
endIcon={
showArrow ? (
<img src={downArrow} alt="Down arrow" {...arrowProps} />
<DownArrow title="Down arrow" {...arrowProps} />
) : undefined
}
{...buttonProps}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,20 @@ export const DEFAULT_AUTHORIZATION_ZOOM = 13
* in its initial state. It can be expanded to full height.
*/
export const MAP_BOTTOM_DRAWER_HEIGHT = 320
export const MAP_BOTTOM_DRAWER_HEIGHT_SEARCH_BY = 160
export const MAP_BOTTOM_DRAWER_HEIGHT_SMALL = 160

export const MAP_CONTROLS_RIGHT_XL = 64
export const MAP_CONTROLS_RIGHT_LG = 48
export const MAP_CONTROLS_RIGHT_SM = 24
export const MAP_CONTROLS_BOTTOM_LG = 40
export const MAP_CONTROLS_BOTTOM_SM = 24

export enum BottomDrawerContentEnum {
export enum ActiveToolEnum {
dataLayers = 'dataLayers',
pointSearch = 'pointSearch',
polygonSearch = 'polygonSearch',
searchBy = 'searchBy',
filterBy = 'filterBy',
}

export const MIN_CIRCLE_RADIUS = 500
23 changes: 23 additions & 0 deletions frontend/src/constants/marker-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import markerIcon1x from '@/assets/marker-icon-1x-blue.png'
import shadowIcon1x from '@/assets/marker-shadow-1x.png'
import markerIcon2x from '@/assets/marker-icon-2x-blue.png'
import shadowIcon2x from '@/assets/marker-shadow-2x.png'
import crosshairsSvg from '@/assets/svgs/crosshairs.svg'

export const blueIcon2x = new L.Icon({
iconUrl: markerIcon2x,
Expand All @@ -28,3 +29,25 @@ export const blueIcon1x = new L.Icon({
shadowSize: [16, 8],
shadowAnchor: [8, 4],
})

export const crosshairsIcon = new L.Icon({
iconUrl: crosshairsSvg,
iconSize: [32, 32],
iconAnchor: [16, 16],
// relative to iconAnchor
popupAnchor: [32, 0],
tooltipAnchor: [32, 0],
className: 'crosshairs-icon',
})

export const emptyIcon = new L.DivIcon({
html: '<span/>',
className: 'empty-icon',
iconSize: [0, 0],
})

export const blackSquareIcon = new L.DivIcon({
html: '<div />',
className: 'black-square-icon',
iconSize: [18, 18],
})
26 changes: 13 additions & 13 deletions frontend/src/features/map/map-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { RootState } from '@/app/store'
import OmrrData from '@/interfaces/omrr'
import { DataLayer } from '@/interfaces/data-layers'
import { useCallback } from 'react'
import { BottomDrawerContentEnum } from '@/constants/constants'
import { ActiveToolEnum } from '@/constants/constants'

export interface ZoomPosition {
position: LatLngTuple
Expand All @@ -25,8 +25,9 @@ export interface MapSliceState {
selectedItem?: OmrrData
// Selected data layers
dataLayers: DataLayer[]
// On small screens - defines which type of content is shown in the bottom drawer
bottomDrawerContentType?: BottomDrawerContentEnum
// Keeps track of the active tool - point/polygon search (all screen sizes), and
// data layers, search by and filter by (small screens only - shown in bottom drawer)
activeTool?: ActiveToolEnum
}

export const initialState: MapSliceState = {
Expand All @@ -37,7 +38,7 @@ export const initialState: MapSliceState = {
sidebarWidth: 0,
selectedItem: undefined,
dataLayers: [],
bottomDrawerContentType: undefined,
activeTool: undefined,
}

export const mapSlice = createSlice({
Expand Down Expand Up @@ -86,12 +87,13 @@ export const mapSlice = createSlice({
resetDataLayers: (state) => {
state.dataLayers = []
},
setBottomDrawerContent: (
setActiveTool: (
state,
action: PayloadAction<BottomDrawerContentEnum | undefined>,
action: PayloadAction<ActiveToolEnum | undefined>,
) => {
state.bottomDrawerContentType = action.payload
state.isDrawerExpanded = Boolean(action.payload)
// Toggle the tool
const tool = action.payload
state.activeTool = state.activeTool === tool ? undefined : tool
},
},
})
Expand All @@ -106,7 +108,7 @@ export const {
clearSelectedItem,
toggleDataLayer,
resetDataLayers,
setBottomDrawerContent,
setActiveTool,
} = mapSlice.actions

// Selectors
Expand Down Expand Up @@ -141,7 +143,5 @@ export const useIsDataLayerOn = () => {
}
export const useHasDataLayersOn = () => useSelector(selectDataLayers).length > 0

const selectBottomDrawerContentType = (state: RootState) =>
state.map.bottomDrawerContentType
export const useBottomDrawerContentType = () =>
useSelector(selectBottomDrawerContentType)
const selectActiveTool = (state: RootState) => state.map.activeTool
export const useActiveTool = () => useSelector(selectActiveTool)
54 changes: 41 additions & 13 deletions frontend/src/features/omrr/omrr-slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@ import {
createSlice,
PayloadAction,
} from '@reduxjs/toolkit'
import { LatLngTuple } from 'leaflet'
import { useLocation } from 'react-router-dom'
import rfdc from 'rfdc'

import { RootState } from '@/app/store'
import { LoadingStatusType } from '@/interfaces/loading-status'
import OmrrData from '@/interfaces/omrr'
import { facilityTypeFilters, OmrrFilter } from '@/interfaces/omrr-filter'
import {
facilityTypeFilters,
OmrrFilter,
CircleFilter,
PolygonFilter,
} from '@/interfaces/omrr-filter'
import { SEARCH_BY_ACTIVE } from '@/interfaces/types'
import OmrrResponse from '@/interfaces/omrr-response'
import apiService from '@/service/api-service'
Expand All @@ -22,12 +30,17 @@ import {
sortDataByPosition,
} from './omrr-utils'
import { MIN_SEARCH_LENGTH } from '@/constants/constants'
import { LatLngLiteral } from 'leaflet'
import { useLocation } from 'react-router-dom'
import { LoadingStatusType } from '@/interfaces/loading-status'

const deepClone = rfdc({ circles: true })

export const fetchOMRRData = createAsyncThunk(
'data/fetchOMRRRecords',
async () => {
const result = await apiService.getAxiosInstance().get('/omrr')
return result?.data
},
)

export interface OmrrSliceState {
lastModified: string
status: LoadingStatusType
Expand All @@ -41,16 +54,10 @@ export interface OmrrSliceState {
searchTextFilter: string
// The timestamp when the user last performed a search or filter
lastSearchTime?: number
polygonFilter?: PolygonFilter
circleFilter?: CircleFilter
}

export const fetchOMRRData = createAsyncThunk(
'data/fetchOMRRRecords',
async () => {
const result = await apiService.getAxiosInstance().get('/omrr')
return result?.data
},
)

export const initialState: OmrrSliceState = {
lastModified: '',
// The status of the API call
Expand Down Expand Up @@ -125,7 +132,7 @@ export const omrrSlice = createSlice({
},
sortFilteredResultsByPosition: (
state,
action: PayloadAction<LatLngLiteral>,
action: PayloadAction<LatLngTuple>,
) => {
state.filteredResults = sortDataByPosition(
state.filteredResults,
Expand All @@ -141,6 +148,20 @@ export const omrrSlice = createSlice({
state.searchTextFilter = action.payload
performSearch(state)
},
setPolygonFilter: (
state,
action: PayloadAction<PolygonFilter | undefined>,
) => {
state.polygonFilter = action.payload
performSearch(state)
},
setCircleFilter: (
state,
action: PayloadAction<CircleFilter | undefined>,
) => {
state.circleFilter = action.payload
performSearch(state)
},
},
extraReducers: (builder: ActionReducerMapBuilder<OmrrSliceState>) => {
// Handle the pending action
Expand Down Expand Up @@ -185,6 +206,8 @@ export const {
sortFilteredResultsByPosition,
setPage,
setSearchTextFilter,
setPolygonFilter,
setCircleFilter,
} = omrrSlice.actions

export default omrrSlice.reducer
Expand Down Expand Up @@ -245,3 +268,8 @@ export const useAllResultsShowing = () => {

const selectLastSearchTime = (state: RootState) => state.omrr.lastSearchTime
export const useLastSearchTime = () => useSelector(selectLastSearchTime)

const selectPolygonFilter = (state: RootState) => state.omrr.polygonFilter
export const usePolygonFilter = () => useSelector(selectPolygonFilter)
const selectCircleFilter = (state: RootState) => state.omrr.circleFilter
export const useCircleFilter = () => useSelector(selectCircleFilter)
Loading