Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
Boards overview (#68)
Browse files Browse the repository at this point in the history
* basic table

* add done prop

* board overview table
  • Loading branch information
zxch3n authored Sep 5, 2019
1 parent e410b01 commit 5d6c998
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 17 deletions.
1 change: 1 addition & 0 deletions src/renderer/components/Kanban/Board/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export const Board: FC<Props> = (props: Props) => {
key={listId}
boardId={props.boardId}
focused={listId === props.focusedList}
done={listId === props.doneList}
/>
))}
{provided.placeholder}
Expand Down
141 changes: 141 additions & 0 deletions src/renderer/components/Kanban/Board/Overview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { FC, useEffect, useState } from 'react';
import { Table } from 'antd';
import { RootState } from '../../../reducers';
import { Dispatch } from 'redux';
import { KanbanBoardState } from './action';
import { ListsState } from '../List/action';
import { connect } from 'react-redux';
import { Card, CardsState } from '../Card/action';
import { IdTrend } from '../../Visualization/ProjectTrend';
import styled from 'styled-components';
import { formatTime, formatTimeWithoutZero } from '../../../utils';

const Container = styled.div``;

interface Props {
boards: KanbanBoardState;
lists: ListsState;
cards: CardsState;
}

interface AggBoardInfo {
_id: string;
name: string;
estimatedLeftTimeSum: number;
actualTimeSum: number;
pomodoroCount: number;
meanPercentageError?: number;
}

const columns = [
{
title: 'Board Name',
dataIndex: 'name',
editable: true,
key: 'name'
},
{
title: 'Estimated Left Time',
dataIndex: 'estimatedLeftTimeSum',
key: 'estimatedLeftTimeSum',
render: formatTimeWithoutZero,
sorter: (a: AggBoardInfo, b: AggBoardInfo) =>
a.estimatedLeftTimeSum - b.estimatedLeftTimeSum
},
{
title: 'Spent Time',
dataIndex: 'actualTimeSum',
key: 'actualTimeSum',
render: formatTimeWithoutZero,
sorter: (a: AggBoardInfo, b: AggBoardInfo) => a.actualTimeSum - b.actualTimeSum
},
{
title: 'Pomodoros',
dataIndex: 'pomodoroCount',
key: 'pomodoroCount',
sorter: (a: AggBoardInfo, b: AggBoardInfo) => a.pomodoroCount - b.pomodoroCount
},
{
title: 'Mean Estimate Error',
dataIndex: 'meanPercentageError',
key: 'meanPercentageError',
render: (text?: number) => {
if (text === undefined) {
return ``;
}

return `${text.toFixed(2)}%`;
},
sorter: (a: AggBoardInfo, b: AggBoardInfo) => {
const va = a.meanPercentageError === undefined ? 1e8 : a.meanPercentageError;
const vb = b.meanPercentageError === undefined ? 1e8 : b.meanPercentageError;
return va - vb;
}
},
{
title: 'Trend',
dataIndex: 'trend',
key: 'trend',
render: (text: any, record: AggBoardInfo) => {
return <IdTrend boardId={record._id} />;
}
}
];

type NewCard = Card & { isDone?: boolean };
const _Overview: FC<Props> = (props: Props) => {
const { boards, lists: listsById, cards: cardsById } = props;
const boardRows = Object.values(boards);

const aggInfo: AggBoardInfo[] = boardRows.map(board => {
const { name, lists, relatedSessions, _id } = board;
const cards: NewCard[] = lists.reduce((l: NewCard[], listId) => {
for (const cardId of listsById[listId].cards) {
const card: NewCard = cardsById[cardId];
card.isDone = listId === board.doneList;
l.push(card);
}
return l;
}, []);
const [estimatedLeftTimeSum, actualTimeSum, errorSum, n] = cards.reduce(
(l: number[], r: NewCard) => {
let err = 0;
const { actual, estimated } = r.spentTimeInHour;
if (r.isDone && actual !== 0 && estimated !== 0) {
err = (Math.abs(estimated - actual) / actual) * 100;
}

return [
l[0] + (r.isDone ? 0 : Math.max(0, estimated - actual)),
l[1] + actual,
l[2] + err,
l[3] + (r.isDone ? 1 : 0)
];
},
[0, 0, 0, 0]
);
return {
_id,
name,
estimatedLeftTimeSum,
actualTimeSum,
meanPercentageError: n ? errorSum / n : undefined,
pomodoroCount: relatedSessions.length
};
});

return (
<Container>
<Table rowKey={'name'} columns={columns} dataSource={aggInfo} />
</Container>
);
};

export const Overview = connect(
(state: RootState) => ({
boards: state.kanban.boards,
lists: state.kanban.lists,
cards: state.kanban.cards
}),
(dispatch: Dispatch) => ({})
)(_Overview);
38 changes: 23 additions & 15 deletions src/renderer/components/Kanban/Board/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface KanbanBoard {
description: string;
lists: ListId[]; // lists id in order
focusedList: string;
doneList: string;
relatedSessions: SessionId[];
aggInfo?: AggInfo;
}
Expand All @@ -33,6 +34,7 @@ const defaultBoard: KanbanBoard = {
lists: [],
name: '',
focusedList: '',
doneList: '',
description: '',

relatedSessions: [],
Expand All @@ -48,8 +50,9 @@ const addBoard = createActionCreator(
name: string,
description: string,
lists: string[],
focusedList: string
) => resolve({ _id, name, description, lists, focusedList })
focusedList: string,
doneList: string
) => resolve({ _id, name, description, lists, focusedList, doneList })
);

const setBoardMap = createActionCreator(
Expand Down Expand Up @@ -95,17 +98,21 @@ const updateAggInfo = createActionCreator(
);

export const boardReducer = createReducer<KanbanBoardState, any>({}, handle => [
handle(addBoard, (state, { payload: { _id, name, description, lists, focusedList } }) => ({
...state,
[_id]: {
...defaultBoard,
_id,
description,
name,
lists,
focusedList
}
})),
handle(
addBoard,
(state, { payload: { _id, name, description, lists, focusedList, doneList } }) => ({
...state,
[_id]: {
...defaultBoard,
_id,
description,
name,
lists,
focusedList,
doneList
}
})
),

handle(setBoardMap, (state, { payload }) => payload),
handle(moveList, (state, { payload: { _id, fromIndex, toIndex } }) => {
Expand Down Expand Up @@ -221,15 +228,16 @@ export const actions = {
await listActions.addList(listId, name)(dispatch);
lists.push(listId);
}
dispatch(addBoard(_id, name, description, lists, lists[1]));
dispatch(addBoard(_id, name, description, lists, lists[1], lists[2]));

await db.insert({
...defaultBoard,
_id,
description,
name,
lists,
focusedList: lists[1]
focusedList: lists[1],
doneList: lists[2]
} as KanbanBoard);
},

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/components/Kanban/Kanban.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import styled from 'styled-components';
import { SelectParam } from 'antd/lib/menu';
import TextArea from 'antd/es/input/TextArea';
import { SearchBar } from './SearchBar';
import { Overview } from './Board/Overview';

const { Option } = Select;
const { Sider, Content } = Layout;
Expand Down Expand Up @@ -205,7 +206,7 @@ export const Kanban: FunctionComponent<Props> = (props: Props) => {
}}
>
{props.kanban.chosenBoardId === undefined ? (
undefined
<Overview />
) : (
<Board boardId={props.kanban.chosenBoardId} key={props.kanban.chosenBoardId} />
)}
Expand Down
13 changes: 12 additions & 1 deletion src/renderer/components/Kanban/List/List.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Draggable, Droppable } from 'react-beautiful-dnd';
import FocusIcon from '../../../../res/Focus.svg';
import DoneIcon from '../../../../res/done.svg';
import React, { FC, useRef, useState } from 'react';
import { List as ListType, ListActionTypes } from './action';
import styled from 'styled-components';
Expand Down Expand Up @@ -72,6 +73,7 @@ export interface InputProps {
index: number;
boardId: string;
focused?: boolean;
done?: boolean;
}

interface Props extends ListType, InputProps, ListActionTypes, KanbanActionTypes {
Expand All @@ -80,7 +82,7 @@ interface Props extends ListType, InputProps, ListActionTypes, KanbanActionTypes
}

export const List: FC<Props> = (props: Props) => {
const { focused = false, searchReg, cards, cardsState } = props;
const { focused = false, searchReg, cards, cardsState, done = false } = props;
const [estimatedTimeSum, actualTimeSum] = props.cards.reduce(
(l: [number, number], r: string) => {
return [
Expand Down Expand Up @@ -182,6 +184,15 @@ export const List: FC<Props> = (props: Props) => {
) : (
undefined
)}
{done ? (
<Tooltip title={'Done column'}>
<span style={{ color: 'green', marginRight: 8 }}>
<Icon component={DoneIcon} />
</span>
</Tooltip>
) : (
undefined
)}
<Dropdown overlay={menu} trigger={['click']}>
<Icon type="menu" />
</Dropdown>
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ export function formatTime(timeInHour: number) {
return `${to2digits(hour)}h ${to2digits(minute)}m`;
}

export function formatTimeWithoutZero(timeInHour: number) {
const hour = Math.floor(timeInHour);
const minute = Math.floor((timeInHour - hour) * 60 + 0.5);
return `${hour}h ${minute}m`;
}

export function parseTime(formattedTime: string) {
const matchedH = formattedTime.match(/(\d+)h/);
const matchedM = formattedTime.match(/(\d+)m/);
Expand Down
1 change: 1 addition & 0 deletions src/res/done.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5d6c998

Please sign in to comment.