Skip to content

Commit

Permalink
Merge pull request #445 from developmentseed/fix/org-map
Browse files Browse the repository at this point in the history
Org Map fixes
  • Loading branch information
kamicut authored Jun 5, 2023
2 parents 8a5d760 + 44d13f7 commit e10e61b
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 30 deletions.
18 changes: 11 additions & 7 deletions src/components/tables/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@ import qs from 'qs'

const APP_URL = process.env.APP_URL

function TeamsTable({ type, orgId }) {
function TeamsTable({ type, orgId, bbox }) {
const [page, setPage] = useState(1)
const [search, setSearch] = useState(null)
const [sort, setSort] = useState({
key: 'name',
direction: 'asc',
})

const querystring = qs.stringify({
search,
page,
sort: sort.key,
order: sort.direction,
})
const querystring = qs.stringify(
{
search,
page,
sort: sort.key,
order: sort.direction,
bbox: bbox,
},
{ arrayFormat: 'comma' }
)

let apiBasePath
let emptyMessage
Expand Down
4 changes: 2 additions & 2 deletions src/lib/org-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ export async function getOrg(id) {
* @param {integer} id
* @returns {response}
*/
export async function getOrgTeams(id) {
let res = await fetch(join(ORG_URL, `${id}`, 'teams'))
export async function getOrgLocations(id) {
let res = await fetch(join(ORG_URL, `${id}`, 'locations'))

if (res.status === 200) {
return res.json()
Expand Down
19 changes: 15 additions & 4 deletions src/models/team.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,24 +349,35 @@ async function paginatedList(options = {}) {
*
* @param options
* @param {Array[float]} options.bbox - filter for teams whose location is in bbox (xmin, ymin, xmax, ymax)
* @param {int} options.organizationId - filter by whether team belongs to organization
* @return {[Array]} Array of teams
**/
async function list({ bbox }) {
async function list({ bbox, organizationId, includePrivate }) {
// TODO: this method should be merged to the paginatedList() method when possible
// for consistency, as they both return a list of teams.

const st = knexPostgis(db)

let query = db('team')
.select(...teamAttributes, st.asGeoJSON('location'))
.where('privacy', 'public')
let query = db('team').select(...teamAttributes, st.asGeoJSON('location'))

if (bbox) {
query = query.where(
st.boundingBoxContained('location', st.makeEnvelope(...bbox))
)
}

if (!includePrivate) {
query.where('privacy', 'public')
}

if (organizationId) {
query = query.whereIn('id', function () {
this.select('team_id')
.from('organization_team')
.where('organization_id', organizationId)
})
}

return query
}

Expand Down
55 changes: 55 additions & 0 deletions src/pages/api/organizations/[orgId]/locations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { createBaseHandler } from '../../../../middlewares/base-handler'
import { validate } from '../../../../middlewares/validation'
import Team from '../../../../models/team'
import * as Yup from 'yup'
import canViewOrgTeams from '../../../../middlewares/can/view-org-teams'

const handler = createBaseHandler()

/**
* @swagger
* /organizations/{id}/teams:
* get:
* summary: Get locations of teams of an organization
* tags:
* - organizations
* parameters:
* - in: path
* name: id
* required: true
* description: Numeric ID of the organization the teams are part of.
* schema:
* type: integer
* responses:
* 200:
* description: A list of teams.
* content:
* application/json:
* schema:
* type: object
* properties:
* data:
* $ref: '#/components/schemas/ArrayOfTeams'
*/
handler.get(
canViewOrgTeams,
validate({
query: Yup.object({
orgId: Yup.number().required().positive().integer(),
}).required(),
}),
async function (req, res) {
const { orgId } = req.query
const {
org: { isMember, isOwner, isManager },
} = req
const teamList = await Team.list({
organizationId: orgId,
includePrivate: isMember || isManager || isOwner,
})

return res.send({ data: teamList })
}
)

export default handler
13 changes: 12 additions & 1 deletion src/pages/api/organizations/[orgId]/teams.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createBaseHandler } from '../../../../middlewares/base-handler'
import { validate } from '../../../../middlewares/validation'
import Organization from '../../../../models/organization'
import Team from '../../../../models/team'
import Boom from '@hapi/boom'
import * as Yup from 'yup'
import canCreateOrgTeam from '../../../../middlewares/can/create-org-team'
import canViewOrgTeams from '../../../../middlewares/can/view-org-teams'
Expand Down Expand Up @@ -103,15 +104,25 @@ handler.get(
}).required(),
}),
async function (req, res) {
const { orgId, page, perPage, search, sort, order } = req.query
const { orgId, page, perPage, search, sort, order, bbox } = req.query
const {
org: { isMember, isOwner, isManager },
} = req

let bounds = bbox || null
if (bbox) {
bounds = bbox.split(',').map((num) => parseFloat(num))
if (bounds.length !== 4) {
throw Boom.badRequest('error in bbox param')
}
}

return res.send(
await Team.paginatedList({
organizationId: orgId,
page,
perPage,
bbox: bounds,
search,
sort,
order,
Expand Down
89 changes: 73 additions & 16 deletions src/pages/organizations/[id]/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import {
removeManager,
addOwner,
removeOwner,
getOrgTeams,
getOrgLocations,
getOrgStaff,
} from '../../../lib/org-api'
import { getUserOrgProfile } from '../../../lib/profiles-api'
import { Box, Container, Heading, Button, Flex } from '@chakra-ui/react'
import {
Box,
Checkbox,
Container,
Heading,
Button,
Flex,
} from '@chakra-ui/react'
import Table from '../../../components/tables/table'
import { AddMemberByIdForm } from '../../../components/add-member-form'
import ProfileModal from '../../../components/profile-modal'
Expand Down Expand Up @@ -66,6 +73,8 @@ class Organization extends Component {
profileInfo: [],
profileUserId: '',
teams: [],
searchOnMapMove: false,
mapBounds: undefined,
managers: [],
owners: [],
page: 0,
Expand All @@ -75,14 +84,15 @@ class Organization extends Component {

this.closeProfileModal = this.closeProfileModal.bind(this)
this.renderBadges = this.renderBadges.bind(this)
this.renderMap = this.renderMap.bind(this)
}

async componentDidMount() {
this.setState({ session: await getSession() })
await this.getOrg()
await this.getOrgStaff()
await this.getBadges()
await this.getOrgTeams()
await this.getOrgLocations()
}

async openProfileModal(user) {
Expand Down Expand Up @@ -165,10 +175,10 @@ class Organization extends Component {
}
}

async getOrgTeams() {
async getOrgLocations() {
const { id } = this.props
try {
let teams = await getOrgTeams(id)
let teams = await getOrgLocations(id)
this.setState({
teams,
})
Expand Down Expand Up @@ -258,6 +268,28 @@ class Organization extends Component {
) : null
}

/**
* Bounds is a WESN box, refresh teams
*/
onMapBoundsChange(bounds) {
if (this.state.searchOnMapMove) {
this.setState({
mapBounds: bounds,
})
} else {
this.setState({ mapBounds: null })
}
}

setSearchOnMapMove(e) {
this.setState(
{
searchOnMapMove: e.target.checked,
},
() => this.getOrgLocations()
)
}

renderMap(teams) {
const { data } = teams

Expand All @@ -275,20 +307,41 @@ class Organization extends Component {
)

return (
<Map
markers={centers}
style={{
height: '360px',
zIndex: '10',
marginBottom: '1rem',
}}
onBoundsChange={() => {}}
/>
<>
<Map
markers={centers}
style={{
height: '360px',
zIndex: '10',
marginBottom: '1rem',
}}
onBoundsChange={this.onMapBoundsChange.bind(this)}
/>
<Checkbox
border={'2px'}
marginTop={'-5rem'}
marginLeft={'1rem'}
position='absolute'
zIndex='2000'
borderColor='brand.600'
p={2}
bg='white'
name='map-bounds-filter'
id='map-bounds-filter'
type='checkbox'
colorScheme={'brand'}
isChecked={this.state.searchOnMapMove}
onChange={(e) => this.setSearchOnMapMove(e)}
>
Filter teams by map
</Checkbox>
</>
)
}

render() {
const { org, managers, owners, error, teams } = this.state
const { org, managers, owners, error, teams, searchOnMapMove, mapBounds } =
this.state
const userId = parseInt(this.state?.session?.user_id)

// Handle org loading errors
Expand Down Expand Up @@ -367,7 +420,11 @@ class Organization extends Component {
<Box layerStyle={'shadowed'} as='section'>
<Heading variant='sectionHead'>Teams</Heading>
{this.renderMap(teams)}
<TeamsTable type='org-teams' orgId={org.data.id} />
<TeamsTable
type='org-teams'
orgId={org.data.id}
bbox={searchOnMapMove ? mapBounds : null}
/>
</Box>

{isStaff ? (
Expand Down

0 comments on commit e10e61b

Please sign in to comment.