-
Notifications
You must be signed in to change notification settings - Fork 7
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
WAITP-1287: Dashboard dropdown #4655
Changes from 7 commits
9a3135e
23b0206
16c0214
17dd14b
6a67b36
cf764eb
bfe5e8e
65afea4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,28 +4,40 @@ | |
*/ | ||
|
||
import { Request } from 'express'; | ||
import camelcaseKeys from 'camelcase-keys'; | ||
import { Route } from '@tupaia/server-boilerplate'; | ||
|
||
export type DashboardsRequest = Request< | ||
any, | ||
any, | ||
any, | ||
any | ||
>; | ||
export type DashboardsRequest = Request<any, any, any, any>; | ||
|
||
export class DashboardsRoute extends Route<DashboardsRequest> { | ||
public async buildResponse() { | ||
const { query, ctx } = this.req; | ||
const { organisationUnitCode, projectCode } = query; | ||
|
||
const project = (await ctx.services.central.fetchResources('projects', { filter: { code: projectCode }, columns: JSON.stringify(['entity.code', 'entity_hierarchy.name']) }))[0]; | ||
const baseEntity = await ctx.services.entity.getEntity(project['entity_hierarchy.name'], organisationUnitCode); | ||
// TODO: Add a better getAncestors function to the EntityApi | ||
const entities = await ctx.services.entity.getRelationshipsOfEntity(project['entity_hierarchy.name'], project['entity.code'], 'descendant', {}, {} , { filter: { type: baseEntity.type } }); | ||
const dashboards = await ctx.services.central.fetchResources('dashboards', { filter: { root_entity_code: entities.ancestors }}); | ||
return Promise.all(dashboards.map(async (dash: any) => ({ | ||
...dash, | ||
items: await ctx.services.central.fetchResources(`dashboards/${dash.id}/dashboardRelations`) | ||
}))); | ||
const project = ( | ||
await ctx.services.central.fetchResources('projects', { | ||
filter: { code: projectCode }, | ||
columns: JSON.stringify(['entity.code', 'entity_hierarchy.name']), | ||
}) | ||
)[0]; | ||
const baseEntity = await ctx.services.entity.getEntity( | ||
project['entity_hierarchy.name'], | ||
organisationUnitCode, | ||
); | ||
|
||
const dashboards = await ctx.services.central.fetchResources('dashboards', { | ||
filter: { root_entity_code: baseEntity.code }, | ||
}); | ||
Comment on lines
+28
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A dashboard attached to an entity can also appear on any of that entity's children (depending on the existence of dashboard items which are usually specific to entity types) which is why I was checking against ancestors. This route handler needs another fix-up pass anyway so I'm happy to pick that up in a separate PR |
||
|
||
const response = await Promise.all( | ||
dashboards.map(async (dash: any) => ({ | ||
...dash, | ||
items: await ctx.services.central.fetchResources( | ||
`dashboards/${dash.id}/dashboardRelations`, | ||
), | ||
})), | ||
); | ||
|
||
return camelcaseKeys(response, { deep: true }); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Tupaia | ||
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd | ||
*/ | ||
import { useQuery } from 'react-query'; | ||
import { Dashboard } from '@tupaia/types'; | ||
import { get } from '../api'; | ||
|
||
type DashboardRecord = Dashboard & { | ||
items: { childId: string }[]; | ||
}; | ||
|
||
type DashboardsResponse = DashboardRecord[]; | ||
export const useDashboards = (projectCode?: string, entityCode?: string) => { | ||
return useQuery( | ||
['dashboards', projectCode, entityCode], | ||
async (): Promise<DashboardsResponse> => { | ||
return await get('dashboards', { | ||
params: { organisationUnitCode: entityCode, projectCode }, | ||
}); | ||
}, | ||
{ enabled: !!entityCode && !!projectCode }, | ||
); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,4 +13,5 @@ export const Breadcrumbs = styled.div` | |
max-width: 220px; | ||
background: #efefefaa; | ||
height: 15px; | ||
z-index: 1; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/* | ||
* Tupaia | ||
* Copyright (c) 2017 - 2023 Beyond Essential Systems Pty Ltd | ||
*/ | ||
import React, { useState } from 'react'; | ||
import { useLocation, Link, useParams } from 'react-router-dom'; | ||
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; | ||
import { ButtonBase, Menu, MenuItem } from '@material-ui/core'; | ||
import { Dashboard } from '@tupaia/types'; | ||
import styled from 'styled-components'; | ||
import { PANEL_GREY } from '../../constants'; | ||
import { useDashboards } from '../../api/queries'; | ||
import { DashboardCode } from '../../types'; | ||
|
||
const MenuButton = styled(ButtonBase)` | ||
display: flex; | ||
justify-content: space-between; | ||
align-items: center; | ||
background: ${PANEL_GREY}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think of putting this grey in the theme? I'm guessing that if we ever themed the app for a particular organisation then this colour may potentially be a part of that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes that's a good idea. We will have to come up with a scheme to make all these theme colors make sense at some point. Perhaps it will need to evolve over time. I've added a panel section to the theme and used that through out the side bar for now. |
||
width: 100%; | ||
padding: 1rem; | ||
font-size: 1rem; | ||
`; | ||
|
||
interface DashboardMenuItemProps { | ||
dashboardName: Dashboard['name']; | ||
dashboardCode: DashboardCode; | ||
onClose: () => void; | ||
} | ||
|
||
const DashboardMenuItem = ({ dashboardName, dashboardCode, onClose }: DashboardMenuItemProps) => { | ||
const location = useLocation(); | ||
const { projectCode, entityCode } = useParams(); | ||
|
||
const link = { ...location, pathname: `/${projectCode}/${entityCode}/${dashboardCode}` }; | ||
|
||
return ( | ||
<MenuItem to={link} onClick={onClose} component={Link}> | ||
{dashboardName} | ||
</MenuItem> | ||
); | ||
}; | ||
|
||
export const DashboardMenu = () => { | ||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null); | ||
const { projectCode, entityCode, '*': dashboardCode } = useParams(); | ||
const { data: dashboards } = useDashboards(projectCode, entityCode); | ||
|
||
const handleClickListItem = (event: React.MouseEvent<HTMLElement>) => { | ||
setAnchorEl(event.currentTarget); | ||
}; | ||
|
||
const handleClose = () => { | ||
setAnchorEl(null); | ||
}; | ||
|
||
const selectedOption = dashboards?.find(({ code }) => code === dashboardCode); | ||
|
||
return ( | ||
<> | ||
<MenuButton onClick={handleClickListItem}> | ||
{selectedOption?.name} | ||
<ArrowDropDownIcon /> | ||
</MenuButton> | ||
<Menu | ||
id="dashboards-menu" | ||
anchorEl={anchorEl} | ||
open={Boolean(anchorEl)} | ||
onClose={handleClose} | ||
variant="menu" | ||
> | ||
{dashboards?.map(({ name, code }) => ( | ||
<DashboardMenuItem | ||
key={code} | ||
dashboardName={name} | ||
dashboardCode={code} | ||
onClose={handleClose} | ||
/> | ||
))} | ||
</Menu> | ||
</> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were thinking about removing
organisationUnitCode
in favour ofentityCode
, right? What do you think of doing the web-server side of it now, so that eventually it just slots right into the entity server as and when changes are made? I know it's not strictly part of this ticket, but something to consider I guess?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@acdunham The back end needs a bit of a clean up anyway so I reckon if the front end looks good let's not let the back end hold it up