From 12e99723bd10ee878749139e7fecc5533515a6fb Mon Sep 17 00:00:00 2001 From: Martin Rifon Date: Tue, 13 Dec 2016 20:22:34 -0300 Subject: [PATCH 1/2] Add edit and delete options for Time Entries --- package.json | 1 + src/api/Foogl.js | 31 ++++ src/layout/utils.scss | 5 + .../Components/TimeEntry/TimeEntry.js | 151 +++++++++++++++--- src/reducers/index.js | 32 ++++ src/store/actions.js | 60 ++++++- 6 files changed, 252 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index 7eb0f0f..1fee002 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react-addons-transition-group": "^15.4.1", "react-block-ui": "^0.2.2", "react-bootstrap": "^0.30.7", + "react-date-picker": "^5.3.28", "react-dom": "^15.3.2", "react-fa": "^4.1.2", "react-fontawesome": "^1.4.0", diff --git a/src/api/Foogl.js b/src/api/Foogl.js index 705e147..7f7f6e1 100644 --- a/src/api/Foogl.js +++ b/src/api/Foogl.js @@ -69,6 +69,37 @@ class FooglApi { return error; }); } + + static updateTimeEntry(timeEntry) { + const body = JSON.stringify({ + id: timeEntry.id, + title: timeEntry.title, + user_id: timeEntry.user_id, + project_id: timeEntry.project_id, + time_start: timeEntry.time_start, + time_end: timeEntry.time_end + }) + + return fetch(`${API_URL}/time_entries/${timeEntry.id}`, { + method: 'patch', + body + }) + .then(response => { + return response.json(); + }) + .catch(error => { + return error; + }); + } + + static deleteTimeEntry(timeEntryId) { + return fetch(`${API_URL}/time_entries/${timeEntryId}`, { method: 'delete' }) + .then(response => { + return response.json(); + }).catch(error => { + return error; + }); + } } export default FooglApi; diff --git a/src/layout/utils.scss b/src/layout/utils.scss index 1c91e00..ca614c2 100644 --- a/src/layout/utils.scss +++ b/src/layout/utils.scss @@ -25,3 +25,8 @@ a.nostyle:visited { color: inherit; cursor: auto; } + +.hover-pointer { + cursor: pointer; + cursor: hand; +} diff --git a/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js b/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js index 69c61e0..f082e8e 100644 --- a/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js +++ b/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js @@ -1,30 +1,134 @@ -import React, { Component } from 'react'; +import React, { Component } from 'react' +import { connect } from 'react-redux' +import { Col, Popover, OverlayTrigger, FormGroup, + ControlLabel, Modal, Button } from 'react-bootstrap' +import Icon from 'react-fa' +import moment from 'moment' +import { DateField, Calendar, TransitionView } from 'react-date-picker' +import 'react-date-picker/index.css' + +import { deleteTimeEntry, updateTimeEntry } from '~/src/store/actions'; -import { connect } from 'react-redux'; -import {Col} from 'react-bootstrap'; import './TimeEntry.scss'; class TimeEntry extends Component { + constructor(props) { + super(props); + + this.state = { + showModal: false + } + } + + toggleShowModal() { + this.setState({ showModal: !this.state.showModal }); + } + + deleteTimeEntry() { + this.props.deleteTimeEntry(this.props.entry.id); + this.toggleShowModal(); + } + render () { + const { showModal } = this.state; + const toggleShowModal = ::this.toggleShowModal + const deleteTimeEntry = ::this.deleteTimeEntry + const onChange = (dateString, attribute) => { + let timeEntry = this.props.entry + timeEntry[attribute] = moment(dateString, 'DD-MM-YYYY HH:mm:ss').format() + this.props.updateTimeEntry(timeEntry) + } + + const editPopover = ( + +
+ + Start: + { onChange(dateString, 'time_start') }} + /> + + + + End: + { onChange(dateString, 'time_end') }} + /> + +
+
+ ); + + const deleteModal = ( +
+ + + Delete Time Entry + + + + Are you sure you want to delete this Time Entry? + + + + + + + + +
+ ); + return ( -
- - {this.props.entry.title} - - - - {this.props.project.name} - - - - {this.props.entry.time_start} - - - - {this.props.entry.time_end} - -
+
+ + {this.props.entry.title} + + + + {this.props.project.name} + + + + {this.props.entry.time_start} + + + + {this.props.entry.time_end} + + + + + + + + + + + + + + + { deleteModal } +
); } }; @@ -37,4 +141,9 @@ const mapStateToProps = (state, ownProps) => { }; } -export default connect(mapStateToProps)(TimeEntry); +const mapDispatchToProps = { + deleteTimeEntry, + updateTimeEntry +} + +export default connect(mapStateToProps, mapDispatchToProps)(TimeEntry); diff --git a/src/reducers/index.js b/src/reducers/index.js index 176874a..4e3ef2c 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -45,6 +45,38 @@ const ACTION_HANDLERS = { }; }, + 'ALTER_TIME_ENTRY': (state, action) => { + const timeEntry = action.timeEntry; + const timeEntryIndex = state.timeEntries + .findIndex( + (elem) => elem.id == timeEntry.id + ) + const timeEntries = timeEntryIndex >= 0 + ? state.timeEntries.slice(0).splice(timeEntryIndex + 1, 1, timeEntry) + : state.timeEntries + + return { + ...state, + timeEntries + }; + }, + + 'REMOVE_TIME_ENTRY': (state, action) => { + const timeEntryId = action.timeEntryId; + const timeEntryIndex = state.timeEntries + .findIndex( + (timeEntry) => timeEntry.id == timeEntryId + ) + const timeEntries = timeEntryIndex >= 0 + ? state.timeEntries.slice(0).splice(timeEntryIndex + 1, 1) + : state.timeEntries + + return { + ...state, + timeEntries + }; + }, + 'LOADING': (state, action) => { const component = action.component let newLoading = {} diff --git a/src/store/actions.js b/src/store/actions.js index a3059d9..c50c75a 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -71,22 +71,52 @@ export const loadTimeEntries = (userId) => { } } -export const loadProjects = (userId) => { +export const updateTimeEntry = (timeEntry) => { return function(dispatch, getState) { - dispatch(loading({ name: 'projectList' })) + dispatch(loading({ name: 'timeEntryList' })); - return FooglApi.projects(userId) + return FooglApi.updateTimeEntry(timeEntry) .then((response) => { - dispatch(finishedLoading({ name: 'projectList' })) - dispatch(setProjects(response.projects)) + dispatch(alterTimeEntry(timeEntry)) + dispatch(finishedLoading({ name: 'timeEntryList' })) }) .catch((error) => { - dispatch(finishedLoading({ name: 'projectList' })) + dispatch(finishedLoading({ name: 'timeEntryList' })) dispatch(displayError(error)) }); } } +export const alterTimeEntry = (timeEntry) => { + return { + type: 'ALTER_TIME_ENTRY', + timeEntry + } +} + +export const deleteTimeEntry = (timeEntryId) => { + return function(dispatch, getState) { + dispatch(loading({ name: 'timeEntryList' })); + + return FooglApi.deleteTimeEntry(timeEntryId) + .then((response) => { + dispatch(removeTimeEntry(timeEntryId)) + dispatch(finishedLoading({ name: 'timeEntryList' })) + }) + .catch((error) => { + dispatch(finishedLoading({ name: 'timeEntryList' })) + dispatch(displayError(error)) + }); + } +} + +export const removeTimeEntry = (timeEntryId) => { + return { + type: 'REMOVE_TIME_ENTRY', + timeEntryId + } +} + // Login. export const attemptLogin = (credentials) => { return function(dispatch, getState) { @@ -97,8 +127,8 @@ export const attemptLogin = (credentials) => { dispatch(finishedLoading({ name: 'loginForm' })) dispatch(setLoggedUser(response.user)) - dispatch(loadTimeEntries(response.user.id)) dispatch(loadProjects(response.user.id)) + dispatch(loadTimeEntries(response.user.id)) browserHistory.push('/dashboard') }) @@ -144,6 +174,22 @@ export const addProjectToStore = (project) => { } } +export const loadProjects = (userId) => { + return function(dispatch, getState) { + dispatch(loading({ name: 'projectList' })) + + return FooglApi.projects(userId) + .then((response) => { + dispatch(finishedLoading({ name: 'projectList' })) + dispatch(setProjects(response.projects)) + }) + .catch((error) => { + dispatch(finishedLoading({ name: 'projectList' })) + dispatch(displayError(error)) + }); + } +} + export const setProjects = (projects) => { return { type: 'SET_PROJECTS', From c5afb4ccdab452f0e6e4daf3dcda41db7a527e9c Mon Sep 17 00:00:00 2001 From: Martin Rifon Date: Thu, 22 Dec 2016 23:07:20 -0300 Subject: [PATCH 2/2] Code Review fixes --- .../Dashboard/Components/TimeEntry/TimeEntry.js | 16 +++++++++++----- src/reducers/index.js | 7 +++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js b/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js index f082e8e..e8dbe26 100644 --- a/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js +++ b/src/pages/Dashboard/Components/TimeEntry/TimeEntry.js @@ -30,15 +30,21 @@ class TimeEntry extends Component { this.toggleShowModal(); } + onChange(dateString, attribute) { + let timeEntry = this.props.entry + timeEntry[attribute] = moment(dateString, 'DD-MM-YYYY HH:mm:ss').format() + this.props.updateTimeEntry(timeEntry) + } + render () { const { showModal } = this.state; const toggleShowModal = ::this.toggleShowModal const deleteTimeEntry = ::this.deleteTimeEntry - const onChange = (dateString, attribute) => { - let timeEntry = this.props.entry - timeEntry[attribute] = moment(dateString, 'DD-MM-YYYY HH:mm:ss').format() - this.props.updateTimeEntry(timeEntry) - } + // const onChange = (dateString, attribute) => { + // let timeEntry = this.props.entry + // timeEntry[attribute] = moment(dateString, 'DD-MM-YYYY HH:mm:ss').format() + // this.props.updateTimeEntry(timeEntry) + // } const editPopover = ( diff --git a/src/reducers/index.js b/src/reducers/index.js index 4e3ef2c..28d5a0e 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -47,10 +47,9 @@ const ACTION_HANDLERS = { 'ALTER_TIME_ENTRY': (state, action) => { const timeEntry = action.timeEntry; - const timeEntryIndex = state.timeEntries - .findIndex( - (elem) => elem.id == timeEntry.id - ) + const timeEntryIndex = state.timeEntries.findIndex((elem) => ( + elem.id == timeEntry.id + )); const timeEntries = timeEntryIndex >= 0 ? state.timeEntries.slice(0).splice(timeEntryIndex + 1, 1, timeEntry) : state.timeEntries