Skip to content

Commit

Permalink
feat(tasks): added timer
Browse files Browse the repository at this point in the history
  • Loading branch information
munkhorgil committed Aug 28, 2020
1 parent ce60a75 commit f7d80a4
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 5 deletions.
2 changes: 2 additions & 0 deletions ui/src/index.tsx
Expand Up @@ -3,6 +3,7 @@ import 'abortcontroller-polyfill/dist/polyfill-patch-fetch';
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';
import 'erxes-icon/css/erxes.min.css';
import OwnerSetup from 'modules/auth/containers/OwnerSetup';
// global style
Expand All @@ -16,6 +17,7 @@ import Routes from './routes';

dayjs.extend(localizedFormat);
dayjs.extend(relativeTime);
dayjs.extend(utc, { parseLocal: true });

const target = document.querySelector('#root');

Expand Down
23 changes: 23 additions & 0 deletions ui/src/modules/boards/containers/editForm/EditForm.tsx
@@ -1,3 +1,4 @@
import client from 'apolloClient';
import gql from 'graphql-tag';
import * as compose from 'lodash.flowright';
import Spinner from 'modules/common/components/Spinner';
Expand Down Expand Up @@ -179,6 +180,27 @@ class EditFormContainer extends React.Component<FinalProps> {
);
};

updateTimeTrack = (
doc: { _id: string; status: string; timeSpent: number },
callback?
) => {
const { options } = this.props;

client
.mutate({
variables: doc,
mutation: gql(options.mutations.updateTimeTrackMutation)
})
.then(() => {
if (callback) {
callback();
}
})
.catch(error => {
Alert.error(error.message);
});
};

render() {
const { usersQuery, detailQuery, options } = this.props;

Expand All @@ -204,6 +226,7 @@ class EditFormContainer extends React.Component<FinalProps> {
removeItem: this.removeItem,
saveItem: this.saveItem,
copyItem: this.copyItem,
updateTimeTrack: this.updateTimeTrack,
users
};

Expand Down
17 changes: 17 additions & 0 deletions ui/src/modules/boards/types.ts
Expand Up @@ -38,6 +38,7 @@ export interface IOptions {
watchMutation: string;
archiveMutation: string;
copyMutation: string;
updateTimeTrackMutation?: string;
};
texts: {
addText: string;
Expand Down Expand Up @@ -170,6 +171,11 @@ export interface IItem {
labelIds: string[];
status?: string;
createdAt: Date;
timeTrack: {
status: string;
timeSpent: number;
startDate?: string;
};
}

export interface IDraggableLocation {
Expand Down Expand Up @@ -267,8 +273,19 @@ export type RemoveVariables = {
_id: string;
};

export type UpdateTimeVariables = {
_id: string;
status: string;
timeSpent: number;
startDate?: string;
};

export type RemoveMutation = ({ variables: RemoveVariables }) => Promise<any>;

export type UpdateTimeTrackMutation = (
{ variables: UpdateTimeVariables }
) => Promise<any>;

export type CopyVariables = {
_id: string;
proccessId: string;
Expand Down
267 changes: 267 additions & 0 deletions ui/src/modules/common/components/Timer.tsx
@@ -0,0 +1,267 @@
import Box from 'modules/common/components/Box';
import React from 'react';
import styled from 'styled-components';
import { Alert } from '../utils';
import Button from './Button';
import { ControlLabel } from './form';

const Container = styled.div`
align-items: center;
padding: 10px;
`;

const TimeWrapper = styled.div`
text-align: center;
padding: 5px;
justify-content: center;
margin-bottom: 5px;
h1 {
margin: 0;
}
`;

const ButtonWrapper = styled.div`
display: flex;
justify-content: center;
margin-bottom: 10px;
`;

const Time = styled.h1`
text-align: center;
`;

export const STATUS_TYPES = {
COMPLETED: 'completed',
STOPPED: 'stopped',
STARTED: 'started',
PAUSED: 'paused'
};

function getSpentTime(seconds: number): string {
const days = Math.floor(seconds / (3600 * 24));

seconds -= days * 3600 * 24;

const hours = Math.floor(seconds / 3600);

seconds -= hours * 3600;

const minutes = Math.floor(seconds / 60);

seconds -= minutes * 60;

return `
${days}.
${hours}.
${minutes}.
${seconds}
`;
}

type Props = {
taskId: string;
status: string;
timeSpent: number;
startDate?: string;
update: (
{
_id,
status,
timeSpent,
startDate
}: { _id: string; status: string; timeSpent: number; startDate?: string },
callback?: () => void
) => void;
};

type State = {
status: string;
timeSpent: number;
};

class TaskTimer extends React.Component<Props, State> {
timer: NodeJS.Timeout;

constructor(props) {
super(props);

this.timer = {} as NodeJS.Timeout;

const { status, startDate, timeSpent } = props;

const absentTime =
status === STATUS_TYPES.STARTED &&
startDate &&
Math.floor((new Date().getTime() - new Date(startDate).getTime()) / 1000);

this.state = {
status,
timeSpent: absentTime ? timeSpent + absentTime : timeSpent
};
}

componentDidMount() {
const { status } = this.props;

if (status === STATUS_TYPES.STARTED) {
this.startTimer();
}
}

onSubmit = () => {
const { taskId, update } = this.props;
const { status, timeSpent } = this.state;

const doc: any = {
_id: taskId,
status,
timeSpent
};

if (status === STATUS_TYPES.STARTED && timeSpent === 0) {
doc.startDate = new Date();
}

return update(doc);
};

onChangeStatus = (status: string, callback) => {
this.setState({ status }, callback);
};

handleReset = () => {
Alert.info('Task reset!');

this.stopTimer();

return this.setState({ status: STATUS_TYPES.STOPPED, timeSpent: 0 }, () => {
return this.onSubmit();
});
};

startTimer() {
this.timer = setInterval(() => {
this.setState(prevState => ({
timeSpent: prevState.timeSpent + 1
}));
}, 1000);
}

stopTimer() {
clearInterval(this.timer);
}

renderButton() {
const { status } = this.state;

const isComplete = status === STATUS_TYPES.COMPLETED;

const handleComplete = () => {
Alert.info('Task completed!');

this.stopTimer();

return this.onChangeStatus(STATUS_TYPES.COMPLETED, () => {
return this.onSubmit();
});
};

return (
<Button
block={true}
disabled={isComplete}
btnStyle={isComplete ? 'success' : 'simple'}
onClick={handleComplete}
size="small"
>
{isComplete ? 'Completed' : 'Complete'}
</Button>
);
}

renderActions() {
const { status } = this.state;

const isComplete = status === STATUS_TYPES.COMPLETED;

const handleClick = () => {
if ([STATUS_TYPES.STOPPED, STATUS_TYPES.PAUSED].includes(status)) {
this.startTimer();

return this.onChangeStatus(STATUS_TYPES.STARTED, () => {
return this.onSubmit();
});
}

this.stopTimer();

return this.onChangeStatus(STATUS_TYPES.PAUSED, () => {
return this.onSubmit();
});
};

return (
<ButtonWrapper>
{[
STATUS_TYPES.COMPLETED,
STATUS_TYPES.PAUSED,
STATUS_TYPES.STOPPED
].includes(status) ? (
<Button
disabled={isComplete}
btnStyle="success"
size="large"
icon="play"
onClick={handleClick}
>
Play
</Button>
) : (
<Button
btnStyle="danger"
size="large"
icon="pause-1"
onClick={handleClick}
>
Pause
</Button>
)}

<Button
btnStyle="warning"
icon="checked-1"
size="large"
onClick={this.handleReset}
>
Reset
</Button>
</ButtonWrapper>
);
}

renderTime() {
const { timeSpent } = this.state;

return (
<TimeWrapper>
<ControlLabel>Time spent on this task</ControlLabel>
<Time>{getSpentTime(timeSpent)}</Time>
</TimeWrapper>
);
}

render() {
return (
<Box title="Time tracking" isOpen={true} name="showCustomers">
<Container>
{this.renderTime()}
{this.renderActions()}
{this.renderButton()}
</Container>
</Box>
);
}
}

export default TaskTimer;
6 changes: 4 additions & 2 deletions ui/src/modules/deals/options.ts
Expand Up @@ -21,7 +21,8 @@ const options = {
changeMutation: 'dealsChange',
watchMutation: 'dealsWatch',
archiveMutation: 'dealsArchive',
copyMutation: 'dealsCopy'
copyMutation: 'dealsCopy',
updateTimeTrackMutation: 'updateTimeTrack'
},
queries: {
itemsQuery: queries.deals,
Expand All @@ -36,7 +37,8 @@ const options = {
changeMutation: mutations.dealsChange,
watchMutation: mutations.dealsWatch,
archiveMutation: mutations.dealsArchive,
copyMutation: mutations.dealsCopy
copyMutation: mutations.dealsCopy,
updateTimeTrackMutation: ``
},
texts: {
addText: 'Add a deal',
Expand Down

0 comments on commit f7d80a4

Please sign in to comment.