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

WAITP-1287: Dashboard dropdown #4655

Merged
merged 8 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
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
42 changes: 27 additions & 15 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;
Copy link
Contributor

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 of entityCode, 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?

Copy link
Contributor

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


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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 });
}
}
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: { organisationUnitCode: entityCode, projectCode },
});
},
{ enabled: !!entityCode && !!projectCode },
);
};
3 changes: 3 additions & 0 deletions packages/tupaia-web/src/constants/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
*/

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

export const PANEL_GREY = '#4a4b55';

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;
`;
83 changes: 83 additions & 0 deletions packages/tupaia-web/src/layout/Sidebar/DashboardMenu.tsx
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};
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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>
</>
);
};
39 changes: 15 additions & 24 deletions packages/tupaia-web/src/layout/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ 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 { TRANSPARENT_BLACK, PANEL_GREY } 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;
Expand Down Expand Up @@ -64,13 +64,6 @@ 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;
}>`
Expand All @@ -84,16 +77,21 @@ const ChartsContainer = styled.div<{

const Chart = styled.div`
position: relative;
text-align: center;
background-color: ${TRANSPARENT_BLACK};
// 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 +107,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
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