Skip to content

Commit

Permalink
Merge pull request #4655 from beyondessential/waitp-1287-dashboard-dr…
Browse files Browse the repository at this point in the history
…opdown

WAITP-1287: Dashboard dropdown
  • Loading branch information
tcaiger authored Jun 19, 2023
2 parents c2e9c67 + 65afea4 commit 94489d5
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 49 deletions.
1 change: 1 addition & 0 deletions packages/tupaia-web-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@tupaia/api-client": "3.1.0",
"@tupaia/database": "1.0.0",
"@tupaia/server-boilerplate": "1.0.0",
"camelcase-keys": "^6.2.2",
"dotenv": "^8.2.0",
"express": "^4.16.2",
"winston": "^3.2.1"
Expand Down
44 changes: 28 additions & 16 deletions packages/tupaia-web-server/src/routes/DashboardsRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 { entityCode, 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'],
entityCode,
);

const dashboards = await ctx.services.central.fetchResources('dashboards', {
filter: { root_entity_code: baseEntity.code },
});

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 });
}
}
1 change: 1 addition & 0 deletions packages/tupaia-web/src/api/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { useProjects } from './useProjects';
export { useUser } from './useUser';
export { useEntity } from './useEntity';
export { useEmailVerification } from './useEmailVerification';
export { useDashboards } from './useDashboards';
24 changes: 24 additions & 0 deletions packages/tupaia-web/src/api/queries/useDashboards.ts
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: { entityCode, projectCode },
});
},
{ enabled: !!entityCode && !!projectCode },
);
};
1 change: 1 addition & 0 deletions packages/tupaia-web/src/constants/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

export const TRANSPARENT_BLACK = 'rgba(43, 45, 56, 0.94)';

export const FORM_COLORS = {
BORDER: '#d9d9d9',
};
1 change: 1 addition & 0 deletions packages/tupaia-web/src/layout/Sidebar/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export const Breadcrumbs = styled.div`
max-width: 220px;
background: #efefefaa;
height: 15px;
z-index: 1;
`;
82 changes: 82 additions & 0 deletions packages/tupaia-web/src/layout/Sidebar/DashboardMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* 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 { useDashboards } from '../../api/queries';
import { DashboardCode } from '../../types';

const MenuButton = styled(ButtonBase)`
display: flex;
justify-content: space-between;
align-items: center;
background-color: ${({ theme }) => theme.panel.secondaryBackground};
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>
</>
);
};
3 changes: 1 addition & 2 deletions packages/tupaia-web/src/layout/Sidebar/ExpandButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import React from 'react';
import styled from 'styled-components';
import { KeyboardArrowLeft, KeyboardArrowRight } from '@material-ui/icons';
import { Button } from '@tupaia/ui-components';
import { TRANSPARENT_BLACK } from '../../constants';

const SemiCircle = styled(Button)`
position: absolute;
Expand All @@ -16,7 +15,7 @@ const SemiCircle = styled(Button)`
display: flex;
justify-content: center;
align-items: center;
background-color: ${TRANSPARENT_BLACK};
background-color: ${({ theme }) => theme.panel.background};
min-height: 60px;
min-width: 30px;
border-top-left-radius: 60px;
Expand Down
46 changes: 18 additions & 28 deletions packages/tupaia-web/src/layout/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,22 @@ import styled from 'styled-components';
import { useParams } from 'react-router-dom';
import { Typography, Button } from '@material-ui/core';
import GetAppIcon from '@material-ui/icons/GetApp';
import { TRANSPARENT_BLACK } from '../../constants';
import { ExpandButton } from './ExpandButton';
import { Photo } from './Photo';
import { Breadcrumbs } from './Breadcrumbs';
import { StaticMap } from './StaticMap';
import { useEntity } from '../../api/queries';
import { useDashboards, useEntity } from '../../api/queries';
import { DashboardMenu } from './DashboardMenu';

const MAX_SIDEBAR_EXPANDED_WIDTH = 1000;
const MAX_SIDEBAR_COLLAPSED_WIDTH = 500;
const MIN_SIDEBAR_WIDTH = 335;
const PANEL_GREY = '#4a4b55';

const Panel = styled.div<{
$isExpanded: boolean;
}>`
position: relative;
background-color: ${TRANSPARENT_BLACK};
background-color: ${({ theme }) => theme.panel.background};
transition: width 0.5s ease, max-width 0.5s ease;
width: ${({ $isExpanded }) => ($isExpanded ? 55 : 30)}%;
min-width: ${MIN_SIDEBAR_WIDTH}px;
Expand All @@ -46,7 +45,7 @@ const TitleBar = styled.div`
justify-content: space-between;
align-items: center;
padding: 1rem;
background-color: ${TRANSPARENT_BLACK};
background-color: ${({ theme }) => theme.panel.background};
z-index: 1;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
`;
Expand All @@ -64,18 +63,11 @@ const Title = styled(Typography)`
line-height: 1.4;
`;

const Dropdown = styled.div`
background: ${PANEL_GREY};
width: 100%;
padding: 1rem;
font-size: 1rem;
`;

const ChartsContainer = styled.div<{
$isExpanded: boolean;
}>`
display: grid;
background-color: ${PANEL_GREY};
background-color: ${({ theme }) => theme.panel.secondaryBackground};
grid-template-columns: repeat(auto-fill, minmax(${MIN_SIDEBAR_WIDTH}px, auto));
column-gap: 0.5rem;
row-gap: 0.5rem;
Expand All @@ -84,16 +76,21 @@ const ChartsContainer = styled.div<{

const Chart = styled.div`
position: relative;
background-color: ${TRANSPARENT_BLACK};
text-align: center;
background-color: ${({ theme }) => theme.panel.background};
// Use padding to maintain aspect ratio
padding-bottom: 80%;
padding: 1rem 1rem 75%;
`;

export const Sidebar = () => {
const { projectCode, entityCode, '*': dashboardCode } = useParams();
const [isExpanded, setIsExpanded] = useState(false);
const { entityCode } = useParams();
const { data: entityData } = useEntity(entityCode);
const bounds = entityData?.location?.bounds;

const { data: dashboardData } = useDashboards(projectCode, entityCode);
const activeDashboard = dashboardData?.find(dashboard => dashboard.code === dashboardCode);

const toggleExpanded = () => {
setIsExpanded(!isExpanded);
};
Expand All @@ -109,21 +106,14 @@ export const Sidebar = () => {
<Photo title={entityData?.name} photoUrl={entityData?.photoUrl} />
)}
<TitleBar>
<Title variant="h3">Northern</Title>
<Title variant="h3">{entityData?.name}</Title>
<ExportButton startIcon={<GetAppIcon />}>Export</ExportButton>
</TitleBar>
<Dropdown>General</Dropdown>
<DashboardMenu />
<ChartsContainer $isExpanded={isExpanded}>
<Chart />
<Chart />
<Chart />
<Chart />
<Chart />
<Chart />
<Chart />
<Chart />
<Chart />
<Chart />
{activeDashboard?.items.map(({ childId }) => {
return <Chart key={childId}>DashboardId: {childId}</Chart>;
})}
</ChartsContainer>
</ScrollBody>
</Panel>
Expand Down
2 changes: 1 addition & 1 deletion packages/tupaia-web/src/layout/UserMenu/MenuList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import React, { ReactNode } from 'react';
import styled from 'styled-components';
import { Button, ListItem, ListItemProps } from '@material-ui/core';
import { RouterLink } from '../../components/RouterButton';
import { RouterLink } from '../../components';

/**
* Menulist is a component that displays a list of menu items for the hamburger menu
Expand Down
3 changes: 2 additions & 1 deletion packages/tupaia-web/src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ export const Login = () => {
Log in
</AuthModalButton>
<LinkText align="center">
Don't have an account? <RouterLink to={MODAL_ROUTES.REGISTER}>Register here</RouterLink>
Don't have an account?{' '}
<RouterLink modal={MODAL_ROUTES.REGISTER}>Register here</RouterLink>
</LinkText>
</StyledForm>
</ModalBody>
Expand Down
5 changes: 5 additions & 0 deletions packages/tupaia-web/src/theme/theme.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createMuiTheme } from '@material-ui/core';
import { TRANSPARENT_BLACK } from '../constants';

export const theme = createMuiTheme(
{
Expand All @@ -19,6 +20,10 @@ export const theme = createMuiTheme(
},
},
{
panel: {
background: TRANSPARENT_BLACK,
secondaryBackground: '#4a4b55',
},
projectCard: {
background: '#2e2f33',
fallBack: '#EFEFF0',
Expand Down
14 changes: 13 additions & 1 deletion packages/tupaia-web/src/types/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LandingPage, Project } from '@tupaia/types';
import { LandingPage, Project, Entity, Dashboard } from '@tupaia/types';
import { KeysToCamelCase } from './helpers';

export type SingleProject = KeysToCamelCase<Project> & {
Expand All @@ -13,3 +13,15 @@ export type SingleProject = KeysToCamelCase<Project> & {
export type SingleLandingPage = KeysToCamelCase<Omit<LandingPage, 'project_codes'>> & {
projects: SingleProject[];
};

export type ProjectCode = Project['code'];

export type EntityCode = Entity['code'];

export type DashboardCode = Dashboard['code'];

export type TupaiaUrlParams = {
projectCode?: ProjectCode;
entityCode?: EntityCode;
dashboardCode?: DashboardCode;
};
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10070,6 +10070,7 @@ __metadata:
"@tupaia/api-client": 3.1.0
"@tupaia/database": 1.0.0
"@tupaia/server-boilerplate": 1.0.0
camelcase-keys: ^6.2.2
dotenv: ^8.2.0
express: ^4.16.2
winston: ^3.2.1
Expand Down

0 comments on commit 94489d5

Please sign in to comment.