Skip to content
This repository has been archived by the owner on Jan 2, 2022. It is now read-only.

Commit

Permalink
feat: stacked bar chart
Browse files Browse the repository at this point in the history
WDY-406
  • Loading branch information
jcmnunes committed Mar 11, 2021
1 parent 2a17c73 commit 66fe992
Show file tree
Hide file tree
Showing 12 changed files with 235 additions and 576 deletions.
459 changes: 0 additions & 459 deletions frontend/package-lock.json

Large diffs are not rendered by default.

6 changes: 1 addition & 5 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
"@binarycapsule/ui-capsules": "^4.2.0",
"@emotion/react": "^11.1.2",
"@emotion/styled": "^11.0.0",
"@nivo/core": "^0.67.0",
"@nivo/pie": "^0.67.0",
"@reduxjs/toolkit": "^1.5.0",
"@styled-system/should-forward-prop": "^5.1.5",
"array-move": "^3.0.1",
Expand Down Expand Up @@ -54,11 +52,9 @@
"react-sizeme": "^2.6.12",
"react-use": "^15.3.4",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-saga": "^1.1.3",
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
"styled-components": "^5.2.1",
"styled-system": "^5.1.5",
"yup": "^0.32.8"
},
Expand All @@ -74,7 +70,6 @@
"@types/react-redux": "^7.1.12",
"@types/react-router-dom": "^5.1.6",
"@types/react-select": "^4.0.13",
"@types/styled-components": "^5.1.5",
"@types/styled-system": "^5.1.10",
"@types/yup": "^0.29.10",
"babel-plugin-macros": "^3.0.0",
Expand All @@ -83,6 +78,7 @@
"lint-staged": "^10.5.3",
"prettier": "^2.2.1",
"pretty-quick": "^3.1.0",
"redux-devtools-extension": "^2.13.8",
"source-map-explorer": "^2.5.1",
"typescript": "^4.1.3"
},
Expand Down
77 changes: 77 additions & 0 deletions frontend/src/components/charts/StackedBar/StackedBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useMemo, useState } from 'react';
import { Box, Text, Tooltip } from '@binarycapsule/ui-capsules';

interface TooltipContentProps {
label: string;
value: string;
color: string;
}

const TooltipContent: React.FC<TooltipContentProps> = ({ label, value, color }) => {
return (
<Box display="flex" alignItems="center">
<Box width={8} height={8} bg={color} mr="4" />

<Text color="neutral.100" fontSize="small" mr="4">
{`${label}:`}
</Text>

<Text color="neutral.100" fontSize="small" fontWeight={600}>
{value}
</Text>
</Box>
);
};

export interface StackedBarProps {
data: {
label: string;
value: number;
color: string;
}[];
formatter?(val: number): string;
}

export const StackedBar: React.FC<StackedBarProps> = ({ data, formatter }) => {
const [hovered, setHovered] = useState<number | null>(null);

const total = useMemo(() => {
return data.reduce((acc, { value }) => acc + value, 0);
}, [data]);

return (
<Box
display="flex"
flexDirection="row"
height={24}
width="100%"
style={{ overflow: 'hidden', borderRadius: 4 }}
>
{data.map(({ label, value, color }, index) => {
return (
<Tooltip
key={label}
content={
<TooltipContent
label={label}
value={formatter ? formatter(value) : value.toString()}
color={color}
/>
}
placement="top"
delay={0}
>
<Box
bg={color}
flex={value / total}
height={32}
onMouseEnter={() => setHovered(index)}
onMouseLeave={() => setHovered(null)}
style={{ opacity: hovered !== null && hovered !== index ? 0.3 : 1 }}
/>
</Tooltip>
);
})}
</Box>
);
};
3 changes: 3 additions & 0 deletions frontend/src/features/report/Report.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface ReportRouteParams {
dayId: string;
}
21 changes: 4 additions & 17 deletions frontend/src/features/report/ReportPage/ReportPage.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React, { useMemo } from 'react';
import { useHistory } from 'react-router-dom';
import { Button, Spinner } from '@binarycapsule/ui-capsules';
import TaskPerSectionChart from './TaskPerSectionChart/TaskPerSectionChart';
import UserDropdown from '../../../components/UserDropdown/UserDropdown';
import { formatTotalTime } from '../../../helpers/timeHelpers';
import { formatDay } from '../../../helpers/dates';
import { TasksTable } from './TasksTable/TasksTable';
import {
ActionsContainer,
ActionsTop,
ChartsContainer,
EmptyStateContainer,
EmptyStateText,
LoadingReport,
Expand All @@ -25,12 +23,9 @@ import {
StatValue,
StyledReportPage,
} from './ReportPage.styles';
import {
selectTasksTableData,
selectTimePerSectionPieChartData,
useReport,
} from '../api/useReport';
import { selectTasksTableData, useReport } from '../api/useReport';
import { IllustrationBoss } from '../../../illustrations/IllustrationBoss';
import { SectionChart } from '../components/SectionChart/SectionChart';

interface Props {
dayId: string;
Expand All @@ -41,11 +36,6 @@ export const ReportPage: React.FC<Props> = ({ dayId }) => {

const history = useHistory();

const timePerSectionPieChartData = useMemo(
() => (report ? selectTimePerSectionPieChartData(report) : null),
[report],
);

const tasksTableData = useMemo(() => (report ? selectTasksTableData(report) : null), [report]);

return (
Expand Down Expand Up @@ -103,11 +93,8 @@ export const ReportPage: React.FC<Props> = ({ dayId }) => {
</EmptyStateContainer>
) : (
<>
{timePerSectionPieChartData && (
<ChartsContainer>
<TaskPerSectionChart data={timePerSectionPieChartData} />
</ChartsContainer>
)}
<SectionChart my="32" />

{report.totalTasks > 0 && tasksTableData && <TasksTable data={tasksTableData} />}
</>
)}
Expand Down

This file was deleted.

10 changes: 6 additions & 4 deletions frontend/src/features/report/api/useReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ const fetchReport = async (dayId?: string) => {
};

export const useReport = (dayId?: string) => {
return useQuery(['report', dayId], () => fetchReport(dayId), {
return useQuery<ReportDto, Error>(['report', dayId], () => fetchReport(dayId), {
enabled: !!dayId,
staleTime: 0,
});
};

export const timePerSection = ({ tasks }: ReportDto) => {
export const timePerSection = (tasks: ReportTask[]) => {
return tasks.reduce((acc, { section, time }) => {
if (section.isPlan) {
return acc;
Expand All @@ -45,19 +45,21 @@ export const timePerSection = ({ tasks }: ReportDto) => {
if (acc[section.id]) {
// eslint-disable-next-line no-param-reassign
acc[section.id].time += time;
acc[section.id].taskCount += 1;
} else {
// eslint-disable-next-line no-param-reassign
acc[section.id] = {
title: section.title,
time,
taskCount: 1,
};
}
return acc;
}, {} as Record<string, { title: string; time: number }>);
}, {} as Record<string, { title: string; time: number; taskCount: number }>);
};

export const selectTimePerSectionPieChartData = (report: ReportDto) =>
Object.entries(timePerSection(report)).map(([sectionId, section]) => ({
Object.entries(timePerSection(report.tasks)).map(([sectionId, section]) => ({
id: sectionId,
label: section.title,
value: section.time,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import styled from '@emotion/styled/macro';
import { Box } from '@binarycapsule/ui-capsules';

export const ChartWrapper = styled(Box)`
border-radius: 12px;
border: ${({ theme }) => `1px solid ${theme.colors.neutral['200']}`};
`;

export const HeadCell = styled.th`
height: 38px;
vertical-align: middle;
padding: 0 18px;
text-align: left;
white-space: initial;
border-top: ${({ theme }) => `1px solid ${theme.colors.neutral['200']}`};
border-bottom: ${({ theme }) => `1px solid ${theme.colors.neutral['200']}`};
`;

export const Cell = styled.td`
height: 38px;
vertical-align: middle;
padding: 0 18px;
text-align: left;
white-space: initial;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import useMedia from 'react-use/lib/useMedia';
import { useSectionChart } from './useSectionChart';
import { StackedBar } from '../../../../components/charts/StackedBar/StackedBar';
import { formatTotalTime } from '../../../../helpers/timeHelpers';
import { Box, Flex, Text, Wrapper, WrapperProps } from '@binarycapsule/ui-capsules';
import { Cell, ChartWrapper, HeadCell } from './SectionChart.styles';

export const SectionChart: React.FC<WrapperProps> = props => {
const { data } = useSectionChart();

const isWide = useMedia('(min-width: 720px)');

if (!data) {
return null;
}

return (
<Wrapper {...props}>
<ChartWrapper>
<Box m="16">
<StackedBar data={data} formatter={formatTotalTime} />
</Box>

<table style={{ width: '100%' }}>
<thead>
<tr>
<HeadCell>
<Text variant="smallCaps" color="neutral.700">
Sections
</Text>
</HeadCell>

{isWide && (
<HeadCell style={{ textAlign: 'center' }}>
<Text variant="smallCaps">Tasks</Text>
</HeadCell>
)}

<HeadCell>
<Text variant="smallCaps">Time</Text>
</HeadCell>
</tr>
</thead>

<tbody>
{data.map(({ value, label, color, taskCount }) => (
<tr>
<Cell>
<Flex alignItems="center">
<Box bg={color} width={12} height={12} mr="8" />
{label}
</Flex>
</Cell>

{isWide && <Cell style={{ textAlign: 'center' }}>{taskCount}</Cell>}

<Cell>{formatTotalTime(value)}</Cell>
</tr>
))}
</tbody>
</table>
</ChartWrapper>
</Wrapper>
);
};
Loading

0 comments on commit 66fe992

Please # to comment.