From e445543ab47df4419e1007cd79e47291441513d4 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Sun, 29 Sep 2019 21:48:42 +0800 Subject: [PATCH] Userguide (#87) * very basic cards vis * better trend and bar chart in kanban overview * now is better * fix delete bug * better-looking tray * setting up userguide * UserGuide * better card UI * add sortby in overview * add a basic tutorial * add demo data * set min-height for List.Cards * add scrollbar for the overview page * better sorting * fix test issue * refine UI detail --- README.md | 4 +- package.json | 2 +- src/lang/en.ts | 14 ++ src/main/main.ts | 17 +- src/renderer/components/Analyser/Analyser.tsx | 4 + src/renderer/components/Application.tsx | 4 +- .../components/Kanban/Board/Board.tsx | 13 ++ .../components/Kanban/Board/BoardBrief.tsx | 218 ++++++++++++------ .../components/Kanban/Board/Overview.tsx | 155 ++++++++++--- .../components/Kanban/Board/action.test.ts | 2 + .../components/Kanban/Board/action.ts | 29 ++- src/renderer/components/Kanban/Card/Card.tsx | 23 +- .../components/Kanban/Card/CardInDetail.tsx | 136 +++++++---- src/renderer/components/Kanban/Kanban.tsx | 199 ++++++++++------ src/renderer/components/Kanban/List/List.tsx | 81 ++++++- src/renderer/components/Kanban/action.ts | 39 +++- src/renderer/components/Kanban/style/Form.ts | 8 + .../components/Kanban/style/Markdown.ts | 27 ++- src/renderer/components/Timer/Timer.tsx | 59 ++++- .../components/Timer/WorkRestIcon.tsx | 1 + src/renderer/components/Timer/action.ts | 6 +- src/renderer/components/Timer/iconMaker.ts | 4 - src/renderer/components/UserGuide/Dialog.tsx | 64 +++++ .../components/UserGuide/HelpIcon.tsx | 30 +++ src/renderer/components/UserGuide/Pointer.tsx | 81 +++++++ .../components/UserGuide/UserGuide.tsx | 154 +++++++++++++ src/renderer/components/UserGuide/actions.ts | 76 ++++++ src/renderer/components/UserGuide/stories.ts | 130 +++++++++++ src/renderer/components/UserGuide/type.d.ts | 33 +++ src/renderer/components/UserGuide/utils.ts | 35 +++ src/renderer/components/Visualization/Bar.tsx | 118 ++++++++++ .../components/Visualization/ProjectTrend.tsx | 69 ++++-- src/renderer/reducers/index.ts | 6 +- src/res/back.svg | 1 + src/res/pointer-left.svg | 1 + 35 files changed, 1541 insertions(+), 302 deletions(-) create mode 100644 src/lang/en.ts create mode 100644 src/renderer/components/Kanban/style/Form.ts create mode 100644 src/renderer/components/UserGuide/Dialog.tsx create mode 100644 src/renderer/components/UserGuide/HelpIcon.tsx create mode 100644 src/renderer/components/UserGuide/Pointer.tsx create mode 100644 src/renderer/components/UserGuide/UserGuide.tsx create mode 100644 src/renderer/components/UserGuide/actions.ts create mode 100644 src/renderer/components/UserGuide/stories.ts create mode 100644 src/renderer/components/UserGuide/type.d.ts create mode 100644 src/renderer/components/UserGuide/utils.ts create mode 100644 src/renderer/components/Visualization/Bar.tsx create mode 100644 src/res/back.svg create mode 100644 src/res/pointer-left.svg diff --git a/README.md b/README.md index 046a658..c551bde 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,9 @@ To download, go to [release page](https://github.com/rem2016/PomodoroLogger/rele # Development -To build the project, issue the following commands +You need to install node.js and node-gyp to build this project. + +After installing yarn, issue the following commands ``` yarn diff --git a/package.json b/package.json index d4dae1f..16dafc3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pomodoro-logger", - "version": "0.1.0", + "version": "0.1.1", "description": "pomodoro project-time-logger", "main": "./dist/main.js", "scripts": { diff --git a/src/lang/en.ts b/src/lang/en.ts new file mode 100644 index 0000000..f7766f2 --- /dev/null +++ b/src/lang/en.ts @@ -0,0 +1,14 @@ +export const lang = { + demoCardContent: + 'This is a demo Card 🎈. \n' + + '## Usage \n\n' + + '- **Draggable**\n' + + '- Support **Markdown**\n' + + '- Click to edit content\n' + + '- Ctrl+F to search card\n' + + '\n### Try it yourself!\n\n' + + '[ ] Drag this card to In Progress\n' + + '[ ] Edit me in Markdown\n' + + '[ ] Set estimated time', + welcome: 'Welcome✨' +}; diff --git a/src/main/main.ts b/src/main/main.ts index 4313a59..6783c64 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -23,18 +23,18 @@ if (process.platform === 'win32') { let win: BrowserWindow | undefined; const installExtensions = async () => { const installer = require('electron-devtools-installer'); - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; + // const forceDownload = !!process.env.UPGRADE_EXTENSIONS; + const forceDownload = true; + console.log('forcedownload', forceDownload); const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; + console.log('react', installer[extensions[0]]); + console.log('redux', installer[extensions[1]]); return Promise.all( extensions.map(name => installer.default(installer[name], forceDownload)) ).catch(console.log); }; const createWindow = async () => { - if (process.env.NODE_ENV !== 'production') { - await installExtensions(); - } - win = new BrowserWindow({ width: 1080, height: 800, @@ -47,6 +47,12 @@ const createWindow = async () => { } }); + console.log('create window'); + if (process.env.NODE_ENV !== 'production') { + await installExtensions(); + } + console.log('installed extensions'); + if (process.env.NODE_ENV === 'production') { win.removeMenu(); } @@ -65,6 +71,7 @@ const createWindow = async () => { ); } + console.log('loaded url'); win.on('close', (event: Event) => { if (win) { win.hide(); diff --git a/src/renderer/components/Analyser/Analyser.tsx b/src/renderer/components/Analyser/Analyser.tsx index 7e6381c..20a888a 100644 --- a/src/renderer/components/Analyser/Analyser.tsx +++ b/src/renderer/components/Analyser/Analyser.tsx @@ -5,6 +5,7 @@ import { Button, Card, Col, Progress, Row, Statistic } from 'antd'; import { RootState } from '../../reducers'; import { HistoryActionCreatorTypes } from '../History/action'; import { workers } from '../../workers'; +import { Bar } from '../Visualization/Bar'; const Container = styled.div` position: relative; @@ -61,6 +62,9 @@ export const Analyser: React.FC = (props: Props) => { Train +
+ +
); }; diff --git a/src/renderer/components/Application.tsx b/src/renderer/components/Application.tsx index 75d3626..45bfcf8 100644 --- a/src/renderer/components/Application.tsx +++ b/src/renderer/components/Application.tsx @@ -15,6 +15,7 @@ import { RootState } from '../reducers'; import Kanban from './Kanban'; import styled from 'styled-components'; import Timer from './Timer'; +import { UserGuide } from './UserGuide/UserGuide'; const Main = styled.div` .ant-tabs-bar { @@ -59,7 +60,7 @@ const Application = (props: Props) => { Kanban } - key="project" + key="kanban" > @@ -103,6 +104,7 @@ const Application = (props: Props) => { undefined )} + ); }; diff --git a/src/renderer/components/Kanban/Board/Board.tsx b/src/renderer/components/Kanban/Board/Board.tsx index a4997c2..e794f1f 100644 --- a/src/renderer/components/Kanban/Board/Board.tsx +++ b/src/renderer/components/Kanban/Board/Board.tsx @@ -9,6 +9,19 @@ const Container = styled.div` height: 100%; width: 100%; overflow-x: auto; + ::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: #f5f5f5; + } + ::-webkit-scrollbar-thumb { + border-radius: 8px; + background-color: rgba(50, 50, 50, 0.3); + } + ::-webkit-scrollbar-track { + border-radius: 8px; + background-color: rgba(200, 200, 200, 0.5); + } `; const ListContainer = styled.div` diff --git a/src/renderer/components/Kanban/Board/BoardBrief.tsx b/src/renderer/components/Kanban/Board/BoardBrief.tsx index 4310d63..5ca0962 100644 --- a/src/renderer/components/Kanban/Board/BoardBrief.tsx +++ b/src/renderer/components/Kanban/Board/BoardBrief.tsx @@ -1,10 +1,11 @@ -import * as React from 'react'; +import React, { useState } from 'react'; +import { actions as timerActions } from '../../Timer/action'; import { connect } from 'react-redux'; import { RootState } from '../../../reducers'; import { actions, KanbanBoard } from './action'; import { actions as kanbanActions } from '../action'; import { Dispatch } from 'redux'; -import styled from 'styled-components'; +import styled, { keyframes } from 'styled-components'; import { Card, CardsState } from '../Card/action'; import { ListsState } from '../List/action'; import { Button, Divider } from 'antd'; @@ -14,8 +15,10 @@ import formatMarkdown from '../Card/formatMarkdown'; import { IdTrend } from '../../Visualization/ProjectTrend'; import { BadgeHolder } from '../style/Badge'; import { Markdown } from '../style/Markdown'; +import { ListsCountBar } from '../../Visualization/Bar'; const BriefCard = styled.div` + position: relative; display: inline-block; padding: 6px; background-color: white; @@ -24,45 +27,98 @@ const BriefCard = styled.div` width: 260px; min-height: 100px; cursor: pointer; - transition: transform 0.3s; - + transition: box-shadow 0.5s, transform 0.5s; + :hover { - box-shadow: 2px 2px 4px 4px rgba(0, 0, 0, 0.14); - transform: translate(-3px, -3px); + box-shadow: 2px 2px 4px 4px rgba(0, 0, 0, 0.14); + transform: translate(-3px -3px); + z-index: 100; + } +`; + +const clipAnimation = keyframes` + 0% { + clip-path: inset(0 100% 0 0); + } + 20% { + clip-path: inset(0 0 0 0); + } + 85% { + clip-path: inset(0 0 0 0); + } + 100% { + clip-path: inset(0 0 0 100%); + } +`; + +const AnimTrend = styled.div` + position: absolute; + width: 100%; + height: 40px; + z-index: -1; + opacity: 0.5; + bottom: 16px; + + svg { + animation: ${clipAnimation} 3s linear infinite; + animation-delay: 0.4s; + clip-path: inset(100%); } `; const Content = styled.div` - + position: relative; + padding: 0 8px; `; const Header = styled.div` - margin-bottom: 4px; - display: flex; - justify-content: space-between; - - h1 { - max-width: 180px; - word-break: break-all; - } -`; + margin-top: 6px; + margin-bottom: 4px; + padding: 0 6px; + display: flex; + justify-content: space-between; + h1 { + max-width: 180px; + word-break: break-all; + } +`; interface InputProps { - onClick?: ()=>void + onClick?: () => void; } -interface Props extends KanbanBoard, InputProps{ - delete: ()=>void, - choose: ()=>void, - listsById: ListsState, - cardsById: CardsState, +interface Props extends KanbanBoard, InputProps { + choose: () => void; + configure: () => void; + listsById: ListsState; + cardsById: CardsState; } - type NewCard = Card & { isDone?: boolean }; const _BoardBrief: React.FC = (props: Props) => { - const { name, lists, relatedSessions, _id, listsById, doneList, cardsById, onClick } = props; + if (props._id === undefined) { + return <>; + } + + const { + name, + lists, + relatedSessions, + _id, + listsById, + doneList, + cardsById, + onClick, + spentHours + } = props; + const [hover, setHover] = useState(false); + const onMouseEnter = () => { + setHover(true); + }; + const onMouseLeave = () => { + setHover(false); + }; const cards: NewCard[] = lists.reduce((l: NewCard[], listId) => { for (const cardId of listsById[listId].cards) { const card: NewCard = cardsById[cardId]; @@ -111,78 +167,100 @@ const _BoardBrief: React.FC = (props: Props) => { accColor = '#ff350e'; } - const onSettingClick = (event: any)=>{ + const onSettingClick = (event: any) => { event.preventDefault(); event.stopPropagation(); + props.configure(); props.onSettingClick!(); }; return ( - - +

{name}

- { - props.onSettingClick? ( -
- { - props.description? ( - - ) : ( - No description provided - ) - } - { - props.relatedSessions.length? ( - - ) : undefined - } + {props.description ? ( + + ) : ( + undefined + )} + + {props.relatedSessions.length ? ( + + + + ) : ( + undefined + )} - + - { - estimatedLeftTimeSum? ( - - ) : undefined - } - - { - showErr? ( - - ) : undefined - } + {estimatedLeftTimeSum ? ( + + ) : ( + undefined + )} + + {showErr ? ( + + ) : ( + undefined + )}
- ) + ); }; - interface InputProps { - boardId: string, - onSettingClick?: ()=>void + boardId: string; + onSettingClick?: () => void; } - export const BoardBrief = connect( (state: RootState, props: InputProps) => ({ ...state.kanban.boards[props.boardId], listsById: state.kanban.lists, - cardsById: state.kanban.cards, + cardsById: state.kanban.cards }), (dispatch: Dispatch, props: InputProps) => ({ - delete: () => actions.deleteBoard(props.boardId)(dispatch), - choose: () => dispatch(kanbanActions.setChosenBoardId(props.boardId)) + choose: () => { + dispatch(timerActions.changeAppTab('timer')); + dispatch(timerActions.setBoardId(props.boardId)); + }, + configure: () => dispatch(kanbanActions.setConfiguringBoardId(props.boardId)) }) - )(_BoardBrief); diff --git a/src/renderer/components/Kanban/Board/Overview.tsx b/src/renderer/components/Kanban/Board/Overview.tsx index 902487f..42fe94b 100644 --- a/src/renderer/components/Kanban/Board/Overview.tsx +++ b/src/renderer/components/Kanban/Board/Overview.tsx @@ -2,7 +2,7 @@ import React, { FC, useEffect, useState } from 'react'; import { Table } from 'antd'; import { RootState } from '../../../reducers'; import { Dispatch } from 'redux'; -import { KanbanBoardState } from './action'; +import { KanbanBoard, KanbanBoardState } from './action'; import { ListsState } from '../List/action'; import { connect } from 'react-redux'; import { Card, CardsState } from '../Card/action'; @@ -10,11 +10,13 @@ import { IdTrend } from '../../Visualization/ProjectTrend'; import styled from 'styled-components'; import { formatTime, formatTimeWithoutZero } from '../../../utils'; import { BoardBrief } from './BoardBrief'; -import { actions } from '../action'; +import { actions, SortType } from '../action'; +import { workers } from '../../../workers'; +import { PomodoroRecord } from '../../../monitor/type'; const Container = styled.div``; -interface Props{ +interface Props { boards: KanbanBoardState; lists: ListsState; cards: CardsState; @@ -133,57 +135,144 @@ const _OverviewTable: FC = (props: Props) => { ); }; - interface InputProps { showTable?: boolean; - showConfigById?: (boardId: string)=>void; + showConfigById?: (boardId: string) => void; } -const OverviewTable = connect( - (state: RootState) => ({ - boards: state.kanban.boards, - lists: state.kanban.lists, - cards: state.kanban.cards - }), -)(_OverviewTable); +const OverviewTable = connect((state: RootState) => ({ + boards: state.kanban.boards, + lists: state.kanban.lists, + cards: state.kanban.cards +}))(_OverviewTable); const BriefContainer = styled.div` - display: flex; - align-items: flex-start; - flex-wrap: wrap; + display: flex; + align-items: flex-start; + flex-wrap: wrap; `; +const sortFunc: Map number> = new Map(); +sortFunc.set('alpha', (a, b) => { + return a.name < b.name ? -1 : 1; +}); +sortFunc.set('due', (a, b) => { + if (!a.dueTime) { + return 1; + } + + if (!b.dueTime) { + return -1; + } + + return a.dueTime - b.dueTime; +}); +sortFunc.set('spent', (a, b) => { + return -a.spentHours + b.spentHours; +}); +sortFunc.set('recent', (a, b) => { + if (!a.lastVisitTime) { + return 1; + } + + if (!b.lastVisitTime) { + return -1; + } + + return -a.lastVisitTime + b.lastVisitTime; +}); + interface OverviewCardsProps { - boards: string[]; - setId: (_id: string)=>void; - showConfigById?: (boardId: string)=>void; + boards: KanbanBoard[]; + sortedBy: SortType; + setId: (_id: string) => void; + lists: ListsState; + cards: CardsState; + showConfigById?: (boardId: string) => void; } const OverviewCards = connect( (state: RootState) => ({ - boards: Object.keys(state.kanban.boards) + boards: Object.values(state.kanban.boards), + sortedBy: state.kanban.kanban.sortedBy, + lists: state.kanban.lists, + cards: state.kanban.cards }), (dispatch: Dispatch) => ({ - setId: (_id: string) => dispatch(actions.setChosenBoardId(_id)) + setId: (_id: string) => actions.setChosenBoardId(_id)(dispatch) }) -)(((props: OverviewCardsProps)=>{ - const {boards, setId} = props; +)(((props: OverviewCardsProps) => { + const { boards, setId } = props; + const [ids, setIds] = useState([]); + useEffect(() => { + let alive = true; + if ( + props.sortedBy === 'due' || + props.sortedBy === 'alpha' || + props.sortedBy === 'spent' || + props.sortedBy === 'recent' + ) { + boards.sort(sortFunc.get(props.sortedBy)); + } else if (props.sortedBy === 'remaining') { + const boardsMap: { [_id: string]: number } = {}; + for (let i = 0; i < boards.length; i += 1) { + if (boardsMap[boards[i]._id] == null) { + boardsMap[boards[i]._id] = 0; + } + + const board = boards[i]; + let leftSum = 0; + for (const j of board.lists) { + if (j === board.doneList) { + continue; + } + + for (const card of props.lists[j].cards) { + const h = props.cards[card].spentTimeInHour; + const left = h.estimated - h.actual; + leftSum += Math.max(0, left); + } + } + + boardsMap[boards[i]._id] = leftSum; + } + + boards.sort((a, b) => { + return -boardsMap[a._id] + boardsMap[b._id]; + }); + } + setIds(boards.map(b => b._id)); + return () => { + alive = false; + }; + }, [ + props.sortedBy, + props.boards, + props.sortedBy === 'remaining' && props.cards, + boards.reduce((v, b) => (b.lastVisitTime ? b.lastVisitTime : 0) + v, 0) + ]); return ( - {boards.map(_id=>{ - const onClick = ()=>setId(_id); - const onSettingClick = props.showConfigById? (()=>{ - props.showConfigById!(_id); - }) : undefined; + {ids.map(_id => { + const onClick = () => setId(_id); + const onSettingClick = props.showConfigById + ? () => { + props.showConfigById!(_id); + } + : undefined; return ( - - ) - }) } + + ); + })} ); }) as FC); -export const Overview: FC = ({showTable = false, showConfigById}: InputProps)=> ( - showTable? : -); +export const Overview: FC = ({ showTable = false, showConfigById }: InputProps) => + showTable ? : ; diff --git a/src/renderer/components/Kanban/Board/action.test.ts b/src/renderer/components/Kanban/Board/action.test.ts index 8af7206..1aa2297 100644 --- a/src/renderer/components/Kanban/Board/action.test.ts +++ b/src/renderer/components/Kanban/Board/action.test.ts @@ -56,12 +56,14 @@ describe('board actions', () => { await actions.addBoard(_id, 'B0')(dispatch); const doc: KanbanBoard = await db.findOne({ _id }); expect(doc.lists.length).toBe(3); + delete doc.lastVisitTime; expect(doc).toStrictEqual(state[_id]); await actions.moveList(_id, 0, 2)(dispatch); const newDoc: KanbanBoard = await db.findOne({ _id }); expect(newDoc.lists[0]).toBe(doc.lists[1]); expect(newDoc.lists[1]).toBe(doc.lists[2]); expect(newDoc.lists[2]).toBe(doc.lists[0]); + delete newDoc.lastVisitTime; expect(newDoc).toStrictEqual(state[_id]); }); }); diff --git a/src/renderer/components/Kanban/Board/action.ts b/src/renderer/components/Kanban/Board/action.ts index 4904f70..45e6be6 100644 --- a/src/renderer/components/Kanban/Board/action.ts +++ b/src/renderer/components/Kanban/Board/action.ts @@ -5,6 +5,7 @@ import { actions as cardActions } from '../Card/action'; import { actions as kanbanActions } from '../action'; import { DBWorker } from '../../../workers/DBWorker'; import shortid from 'shortid'; +import { lang } from '../../../../lang/en'; const db = new DBWorker('kanbanDB'); type ListId = string; @@ -26,6 +27,8 @@ export interface KanbanBoard { focusedList: string; doneList: string; relatedSessions: SessionId[]; + dueTime?: number; // TODO: Add due time setting + lastVisitTime?: number; // TODO: Add due time setting aggInfo?: AggInfo; } @@ -80,6 +83,10 @@ const deleteList = createActionCreator('[Board]DEL_LIST', resolve => (_id, listI resolve({ _id, listId }) ); +const setLastVisitTime = createActionCreator('[Board]SET_LAST_VISIT_TIME', resolve => (_id, time) => + resolve({ _id, time }) +); + const onTimerFinished = createActionCreator( '[Board]ON_TIMER_FINISHED', resolve => (_id: string, sessionId: string, spentTime: number) => @@ -164,6 +171,16 @@ export const boardReducer = createReducer({}, handle => [ }; }), + handle(setLastVisitTime, (state, { payload: { _id, time } }) => { + return { + ...state, + [_id]: { + ...state[_id], + lastVisitTime: time + } + }; + }), + handle(editBoard, (state, { payload: { _id, name, description } }) => ({ ...state, [_id]: { @@ -209,8 +226,8 @@ export const actions = { await db.update({ _id }, { $push: { lists: listId } }); }, deleteBoard: (_id: string) => async (dispatch: Dispatch) => { - dispatch(kanbanActions.setChosenBoardId(undefined)); dispatch(deleteBoard(_id)); + await kanbanActions.setChosenBoardId(undefined)(dispatch); await db.remove({ _id }); }, deleteList: (_id: string, listId: string) => async (dispatch: Dispatch) => { @@ -230,17 +247,25 @@ export const actions = { } dispatch(addBoard(_id, name, description, lists, lists[1], lists[2])); + const cardId = shortid.generate(); + await cardActions.addCard(cardId, lists[0], lang.welcome, lang.demoCardContent)(dispatch); await db.insert({ ...defaultBoard, _id, description, name, lists, + lastVisitTime: new Date().getTime(), focusedList: lists[1], doneList: lists[2] } as KanbanBoard); }, + setLastVisitTime: (_id: string, time: number) => async (dispatch: Dispatch) => { + dispatch(setLastVisitTime(_id, time)); + await db.update({ _id }, { $set: { lastVisitTime: time } }); + }, + moveCard: (fromListId: string, toListId: string, fromIndex: number, toIndex: number) => async ( dispatch: Dispatch ) => { @@ -261,6 +286,8 @@ export const actions = { for (const cardId of cardIds) { await cardActions.onTimerFinished(cardId, sessionId, timeSpent)(dispatch); } + + await actions.setLastVisitTime(_id, new Date().getTime())(dispatch); }, editBoard: (_id: string, name: string, description: string) => async (dispatch: Dispatch) => { diff --git a/src/renderer/components/Kanban/Card/Card.tsx b/src/renderer/components/Kanban/Card/Card.tsx index b0574b7..23eafda 100644 --- a/src/renderer/components/Kanban/Card/Card.tsx +++ b/src/renderer/components/Kanban/Card/Card.tsx @@ -37,19 +37,6 @@ export interface InputProps { interface Props extends CardType, InputProps, CardActionTypes, KanbanActionTypes {} export const Card: FC = (props: Props) => { const { index, listId, title, content, _id, isDraggingOver } = props; - - const onDelete = () => { - props.deleteCard(props._id, props.listId); - }; - - const menu = ( - - - Delete - - - ); - const onClick = () => { props.setEditCard(true, props.listId, props._id); }; @@ -64,16 +51,10 @@ export const Card: FC = (props: Props) => { {...provided.draggableProps} {...provided.dragHandleProps} onClick={onClick} + className={'kanban-card'} > - - -

- -

-
-
-

{props.title}

+

{props.title}

= (props: Props) => { } as FormData); } }, [card]); + const onDelete = () => { + if (!card) { + return; + } + + props.deleteCard(card._id, listId); + onCancel(); + }; const [isEditingActualTime, setIsEditingActualTime] = useState(false); const onSwitchIsEditing = () => { @@ -94,53 +121,72 @@ const _CardInDetail: FC = (props: Props) => { onCancel={onCancel} onOk={onSave} > -
- - {getFieldDecorator('title', { - rules: [{ required: true, message: 'Please input the name of board!' }] - })()} - - - {getFieldDecorator('content')( -