Skip to content

Commit e10e61b

Browse files
authored
Merge pull request #445 from developmentseed/fix/org-map
Org Map fixes
2 parents 8a5d760 + 44d13f7 commit e10e61b

File tree

6 files changed

+168
-30
lines changed

6 files changed

+168
-30
lines changed

src/components/tables/teams.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,24 @@ import qs from 'qs'
1010

1111
const APP_URL = process.env.APP_URL
1212

13-
function TeamsTable({ type, orgId }) {
13+
function TeamsTable({ type, orgId, bbox }) {
1414
const [page, setPage] = useState(1)
1515
const [search, setSearch] = useState(null)
1616
const [sort, setSort] = useState({
1717
key: 'name',
1818
direction: 'asc',
1919
})
2020

21-
const querystring = qs.stringify({
22-
search,
23-
page,
24-
sort: sort.key,
25-
order: sort.direction,
26-
})
21+
const querystring = qs.stringify(
22+
{
23+
search,
24+
page,
25+
sort: sort.key,
26+
order: sort.direction,
27+
bbox: bbox,
28+
},
29+
{ arrayFormat: 'comma' }
30+
)
2731

2832
let apiBasePath
2933
let emptyMessage

src/lib/org-api.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ export async function getOrg(id) {
6363
* @param {integer} id
6464
* @returns {response}
6565
*/
66-
export async function getOrgTeams(id) {
67-
let res = await fetch(join(ORG_URL, `${id}`, 'teams'))
66+
export async function getOrgLocations(id) {
67+
let res = await fetch(join(ORG_URL, `${id}`, 'locations'))
6868

6969
if (res.status === 200) {
7070
return res.json()

src/models/team.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,24 +349,35 @@ async function paginatedList(options = {}) {
349349
*
350350
* @param options
351351
* @param {Array[float]} options.bbox - filter for teams whose location is in bbox (xmin, ymin, xmax, ymax)
352+
* @param {int} options.organizationId - filter by whether team belongs to organization
352353
* @return {[Array]} Array of teams
353354
**/
354-
async function list({ bbox }) {
355+
async function list({ bbox, organizationId, includePrivate }) {
355356
// TODO: this method should be merged to the paginatedList() method when possible
356357
// for consistency, as they both return a list of teams.
357358

358359
const st = knexPostgis(db)
359360

360-
let query = db('team')
361-
.select(...teamAttributes, st.asGeoJSON('location'))
362-
.where('privacy', 'public')
361+
let query = db('team').select(...teamAttributes, st.asGeoJSON('location'))
363362

364363
if (bbox) {
365364
query = query.where(
366365
st.boundingBoxContained('location', st.makeEnvelope(...bbox))
367366
)
368367
}
369368

369+
if (!includePrivate) {
370+
query.where('privacy', 'public')
371+
}
372+
373+
if (organizationId) {
374+
query = query.whereIn('id', function () {
375+
this.select('team_id')
376+
.from('organization_team')
377+
.where('organization_id', organizationId)
378+
})
379+
}
380+
370381
return query
371382
}
372383

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { createBaseHandler } from '../../../../middlewares/base-handler'
2+
import { validate } from '../../../../middlewares/validation'
3+
import Team from '../../../../models/team'
4+
import * as Yup from 'yup'
5+
import canViewOrgTeams from '../../../../middlewares/can/view-org-teams'
6+
7+
const handler = createBaseHandler()
8+
9+
/**
10+
* @swagger
11+
* /organizations/{id}/teams:
12+
* get:
13+
* summary: Get locations of teams of an organization
14+
* tags:
15+
* - organizations
16+
* parameters:
17+
* - in: path
18+
* name: id
19+
* required: true
20+
* description: Numeric ID of the organization the teams are part of.
21+
* schema:
22+
* type: integer
23+
* responses:
24+
* 200:
25+
* description: A list of teams.
26+
* content:
27+
* application/json:
28+
* schema:
29+
* type: object
30+
* properties:
31+
* data:
32+
* $ref: '#/components/schemas/ArrayOfTeams'
33+
*/
34+
handler.get(
35+
canViewOrgTeams,
36+
validate({
37+
query: Yup.object({
38+
orgId: Yup.number().required().positive().integer(),
39+
}).required(),
40+
}),
41+
async function (req, res) {
42+
const { orgId } = req.query
43+
const {
44+
org: { isMember, isOwner, isManager },
45+
} = req
46+
const teamList = await Team.list({
47+
organizationId: orgId,
48+
includePrivate: isMember || isManager || isOwner,
49+
})
50+
51+
return res.send({ data: teamList })
52+
}
53+
)
54+
55+
export default handler

src/pages/api/organizations/[orgId]/teams.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createBaseHandler } from '../../../../middlewares/base-handler'
22
import { validate } from '../../../../middlewares/validation'
33
import Organization from '../../../../models/organization'
44
import Team from '../../../../models/team'
5+
import Boom from '@hapi/boom'
56
import * as Yup from 'yup'
67
import canCreateOrgTeam from '../../../../middlewares/can/create-org-team'
78
import canViewOrgTeams from '../../../../middlewares/can/view-org-teams'
@@ -103,15 +104,25 @@ handler.get(
103104
}).required(),
104105
}),
105106
async function (req, res) {
106-
const { orgId, page, perPage, search, sort, order } = req.query
107+
const { orgId, page, perPage, search, sort, order, bbox } = req.query
107108
const {
108109
org: { isMember, isOwner, isManager },
109110
} = req
111+
112+
let bounds = bbox || null
113+
if (bbox) {
114+
bounds = bbox.split(',').map((num) => parseFloat(num))
115+
if (bounds.length !== 4) {
116+
throw Boom.badRequest('error in bbox param')
117+
}
118+
}
119+
110120
return res.send(
111121
await Team.paginatedList({
112122
organizationId: orgId,
113123
page,
114124
perPage,
125+
bbox: bounds,
115126
search,
116127
sort,
117128
order,

src/pages/organizations/[id]/index.js

Lines changed: 73 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@ import {
77
removeManager,
88
addOwner,
99
removeOwner,
10-
getOrgTeams,
10+
getOrgLocations,
1111
getOrgStaff,
1212
} from '../../../lib/org-api'
1313
import { getUserOrgProfile } from '../../../lib/profiles-api'
14-
import { Box, Container, Heading, Button, Flex } from '@chakra-ui/react'
14+
import {
15+
Box,
16+
Checkbox,
17+
Container,
18+
Heading,
19+
Button,
20+
Flex,
21+
} from '@chakra-ui/react'
1522
import Table from '../../../components/tables/table'
1623
import { AddMemberByIdForm } from '../../../components/add-member-form'
1724
import ProfileModal from '../../../components/profile-modal'
@@ -66,6 +73,8 @@ class Organization extends Component {
6673
profileInfo: [],
6774
profileUserId: '',
6875
teams: [],
76+
searchOnMapMove: false,
77+
mapBounds: undefined,
6978
managers: [],
7079
owners: [],
7180
page: 0,
@@ -75,14 +84,15 @@ class Organization extends Component {
7584

7685
this.closeProfileModal = this.closeProfileModal.bind(this)
7786
this.renderBadges = this.renderBadges.bind(this)
87+
this.renderMap = this.renderMap.bind(this)
7888
}
7989

8090
async componentDidMount() {
8191
this.setState({ session: await getSession() })
8292
await this.getOrg()
8393
await this.getOrgStaff()
8494
await this.getBadges()
85-
await this.getOrgTeams()
95+
await this.getOrgLocations()
8696
}
8797

8898
async openProfileModal(user) {
@@ -165,10 +175,10 @@ class Organization extends Component {
165175
}
166176
}
167177

168-
async getOrgTeams() {
178+
async getOrgLocations() {
169179
const { id } = this.props
170180
try {
171-
let teams = await getOrgTeams(id)
181+
let teams = await getOrgLocations(id)
172182
this.setState({
173183
teams,
174184
})
@@ -258,6 +268,28 @@ class Organization extends Component {
258268
) : null
259269
}
260270

271+
/**
272+
* Bounds is a WESN box, refresh teams
273+
*/
274+
onMapBoundsChange(bounds) {
275+
if (this.state.searchOnMapMove) {
276+
this.setState({
277+
mapBounds: bounds,
278+
})
279+
} else {
280+
this.setState({ mapBounds: null })
281+
}
282+
}
283+
284+
setSearchOnMapMove(e) {
285+
this.setState(
286+
{
287+
searchOnMapMove: e.target.checked,
288+
},
289+
() => this.getOrgLocations()
290+
)
291+
}
292+
261293
renderMap(teams) {
262294
const { data } = teams
263295

@@ -275,20 +307,41 @@ class Organization extends Component {
275307
)
276308

277309
return (
278-
<Map
279-
markers={centers}
280-
style={{
281-
height: '360px',
282-
zIndex: '10',
283-
marginBottom: '1rem',
284-
}}
285-
onBoundsChange={() => {}}
286-
/>
310+
<>
311+
<Map
312+
markers={centers}
313+
style={{
314+
height: '360px',
315+
zIndex: '10',
316+
marginBottom: '1rem',
317+
}}
318+
onBoundsChange={this.onMapBoundsChange.bind(this)}
319+
/>
320+
<Checkbox
321+
border={'2px'}
322+
marginTop={'-5rem'}
323+
marginLeft={'1rem'}
324+
position='absolute'
325+
zIndex='2000'
326+
borderColor='brand.600'
327+
p={2}
328+
bg='white'
329+
name='map-bounds-filter'
330+
id='map-bounds-filter'
331+
type='checkbox'
332+
colorScheme={'brand'}
333+
isChecked={this.state.searchOnMapMove}
334+
onChange={(e) => this.setSearchOnMapMove(e)}
335+
>
336+
Filter teams by map
337+
</Checkbox>
338+
</>
287339
)
288340
}
289341

290342
render() {
291-
const { org, managers, owners, error, teams } = this.state
343+
const { org, managers, owners, error, teams, searchOnMapMove, mapBounds } =
344+
this.state
292345
const userId = parseInt(this.state?.session?.user_id)
293346

294347
// Handle org loading errors
@@ -367,7 +420,11 @@ class Organization extends Component {
367420
<Box layerStyle={'shadowed'} as='section'>
368421
<Heading variant='sectionHead'>Teams</Heading>
369422
{this.renderMap(teams)}
370-
<TeamsTable type='org-teams' orgId={org.data.id} />
423+
<TeamsTable
424+
type='org-teams'
425+
orgId={org.data.id}
426+
bbox={searchOnMapMove ? mapBounds : null}
427+
/>
371428
</Box>
372429

373430
{isStaff ? (

0 commit comments

Comments
 (0)