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 all 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
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 },
});
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: { 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