Skip to content

Commit

Permalink
Merge bf289e6 into d810c45
Browse files Browse the repository at this point in the history
  • Loading branch information
ViniciusAlmeidaSilva committed Jan 22, 2019
2 parents d810c45 + bf289e6 commit ff6a0c6
Show file tree
Hide file tree
Showing 18 changed files with 580 additions and 18 deletions.
3 changes: 3 additions & 0 deletions app/assets/javascripts/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export default keyMirror({
EDIT_STORY: null,
UPDATE_STORY: null,
UPDATE_STORY_SUCCESS: null,
ADD_TASK: null,
REMOVE_TASK: null,
TOGGLE_TASK: null,
DELETE_STORY_SUCCESS: null,
ADD_NOTE: null,
DELETE_NOTE: null
Expand Down
46 changes: 46 additions & 0 deletions app/assets/javascripts/actions/task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import actionTypes from './actionTypes';

export const createTaskSuccess = (task, storyId) => ({
type: actionTypes.ADD_TASK,
task,
storyId
});

export const deleteTaskSuccess = (task, storyId) => ({
type: actionTypes.REMOVE_TASK,
task,
storyId
});

export const toggleTaskSuccess = (task, story) => ({
type: actionTypes.TOGGLE_TASK,
task,
story
});

export const createTask = (projectId, storyId, task) => {
return (dispatch, getState, { Task }) => {
return Task.post(projectId, storyId, task)
.then(task => {
dispatch(createTaskSuccess(task, storyId))
});
};
};

export const deleteTask = (projectId, storyId, taskId) => {
return (dispatch, getState, { Task }) => {
return Task.destroy(projectId, storyId, taskId)
.then(() => {
dispatch(deleteTaskSuccess(taskId, storyId))
});
};
};

export const toggleTask = (projectId, story, task, status) => {
return (dispatch, getState, { Task }) => {
return Task.toggle(projectId, story.id, task.id, status)
.then((task) => {
dispatch(toggleTaskSuccess(task, story))
});
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { Component } from 'react';
import TasksList from '../task/TasksList';
import PropTypes from 'prop-types';

class ExpandedStoryTask extends Component {
constructor(props) {
super(props);
this.state = {
task: ''
};
this.onInputChange = this.onInputChange.bind(this);
this.onHandleSubmit = this.onHandleSubmit.bind(this);
}

onInputChange(e) {
this.setState({
task: e.target.value
});
}

onHandleSubmit() {
const { onSave } = this.props;

const task = this.state.task;
onSave(task);
this.setState({
task: ''
});
}

render() {
const { story, onToggle, onDelete } = this.props;

return (
<div>
<div className="Story__section">
<div className="Story__section-title">
<div className="Story__section-title">
{I18n.translate('story.tasks')}
</div>
</div>
</div>
<div className="Story__list-task" >
<TasksList
tasks={story.tasks}
onDelete={onDelete}
onToggle={onToggle}
/>
</div>
<div className="Story__task-form">
<input
value={this.state.task}
className="form-control input-sm"
onChange={this.onInputChange}
/>
<button
type='submit'
className='Story__add-task-button'
onClick={this.onHandleSubmit}
>
{I18n.t('add task')}
</button>
</div>
</div>
);
}
};

ExpandedStoryTask.propTypes = {
story: PropTypes.object.isRequired,
story: PropTypes.shape({
tasks: PropTypes.array.isRequired
}),
onToggle: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired
};

export default ExpandedStoryTask
21 changes: 18 additions & 3 deletions app/assets/javascripts/components/story/ExpandedStory/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import ExpandedStoryControls from './ExpandedStoryControls';
import ExpandedStoryEstimate from './ExpandedStoryEstimate';
import ExpandedStoryType from './ExpandedStoryType';
import ExpandedStoryDescription from './ExpandedStoryDescription';
import { createTask, deleteTask, toggleTask } from '../../../actions/task';
import ExpandedStoryNotes from './ExpandedStoryNotes';
import ExpandedStoryState from './ExpandedStoryState';
import ExpandedStoryTitle from './ExpandedStoryTitle';
import ExpandedStoryLabels from './ExpandedStoryLabels';
import { deleteNote, createNote } from '../../../actions/note';
import { editStory, updateStory, deleteStory } from '../../../actions/story';
import ExpandedStoryTask from './ExpandedStoryTask';
import { connect } from 'react-redux';
import * as Story from '../../../models/beta/story';

Expand All @@ -19,12 +21,13 @@ export const ExpandedStory = ({
onToggle,
editStory,
updateStory,
deleteStory,
project,
createTask,
deleteTask,
toggleTask,
deleteNote,
createNote
}) => {

return (
<div className="Story Story--expanded">
<ExpandedStoryControls
Expand Down Expand Up @@ -62,7 +65,9 @@ export const ExpandedStory = ({

<ExpandedStoryDescription
story={story}
onEdit={(newAttributes) => editStory(story.id, newAttributes)}
onToggle={(task, status) => toggleTask(project.id, story, task, status)}
onDelete={(taskId) => deleteTask(project.id, story.id, taskId)}
onSave={(task) => createTask(project.id, story.id, task)}
/>

<ExpandedStoryNotes
Expand All @@ -71,6 +76,13 @@ export const ExpandedStory = ({
onDelete={(noteId) => deleteNote(project.id, story.id, noteId)}
onCreate={(note) => createNote(project.id, story.id, { note })}
/>

<ExpandedStoryTask
story={story}
onToggle={ (task, status) => toggleTask(project.id, story, task, status)}
onDelete={(taskId) => deleteTask(project.id, story.id, taskId)}
onSave={(task) => createTask(project.id, story.id, task)}
/>
</div>
);
};
Expand All @@ -86,6 +98,9 @@ export default connect(
{
editStory,
updateStory,
createTask,
deleteTask,
toggleTask,
deleteStory,
deleteNote,
createNote
Expand Down
34 changes: 34 additions & 0 deletions app/assets/javascripts/components/story/task/Task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';

const Task = ({ task, onDelete, onToggle }) => (
<div className="Story__task">
<label>
<input
type='checkbox'
defaultChecked={task.done}
onClick={onToggle}
/>
{task.name}
</label>
<span
title={I18n.t('delete')}
className='delete-btn'
onClick={onDelete}
>
{ I18n.t('delete') }
</span>
</div>
);

Task.propTypes = {
task: PropTypes.shape({
id: PropTypes.number,
done: PropTypes.bool,
name: PropTypes.string
}),
onToggle: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired
};

export default Task;
26 changes: 26 additions & 0 deletions app/assets/javascripts/components/story/task/TasksList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import Task from './Task'
import PropTypes from 'prop-types';

const TasksList = ({ tasks, onDelete, onToggle }) => (
<div>
{
tasks.map((task) => (
<Task
key={task.id}
task={task}
onDelete={() => onDelete(task.id)}
onToggle={() => onToggle(task, { done: !task.done })}
/>
))
}
</div>
)

TasksList.propTypes = {
tasks: PropTypes.array.isRequired,
onDelete: PropTypes.func.isRequired,
onToggle: PropTypes.func.isRequired
};

export default TasksList;
63 changes: 63 additions & 0 deletions app/assets/javascripts/models/beta/task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import httpService from '../../services/httpService';
import changeCase from 'change-object-case';
import { updateIfSameId } from '../../services/updateIfSameId';

export const post = (projectId, storyId, task) => {
const newTask = {
name: task,
done: false
}
return httpService
.post(`/projects/${projectId}/stories/${storyId}/tasks`, newTask)
.then(({ data }) => changeCase.camelKeys(data, { recursive: true, arrayRecursive: true }))
.catch((error) => {
console.log(error);
});
};

export const toggle = (projectId, storyId, taskId, status) => {
const taskStatus = changeCase.snakeKeys(status);
return httpService
.put(`/projects/${projectId}/stories/${storyId}/tasks/${taskId}/`, taskStatus)
.then(({ data }) => changeCase.camelKeys(data, { recursive: true, arrayRecursive: true }))
.catch((error) => {
console.log(error);
});
};

export const destroy = (projectId, storyId, taskId) => {
return httpService
.delete(`/projects/${projectId}/stories/${storyId}/tasks/${taskId}`)
.catch((error) => {
console.log(error);
});
};

export const addTask = (story, task) => (
{
...story,
tasks: [
...story.tasks,
task
]
}
);

export const toggleTask = (story, changedTask) => {
return {
...story,
tasks: story.tasks.map(
updateIfSameId(changedTask.id, task => {
return {
...task,
...changedTask
};
}
))
};
};

export const deleteTask = (taskId, story) => ({
...story,
tasks: story.tasks.filter((task) => task.id !== taskId)
});
43 changes: 33 additions & 10 deletions app/assets/javascripts/reducers/stories.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import actionTypes from 'actions/actionTypes';
import { toggleStory, editStory, updateStory } from 'models/beta/story';
import { toggleStory, editStory, updateStory } from 'models/beta/story'
import * as Note from 'models/beta/note';
import { updateIfSameId } from '../services/updateIfSameId';
import * as Task from 'models/beta/task';

const initialState = [];

Expand All @@ -21,6 +23,21 @@ const storiesReducer = (state = initialState, action) => {
updateIfSameId(action.story.id, (story) => {
return updateStory(story, action.story);
}));
case actionTypes.ADD_TASK:
return state.map(
updateIfSameId(action.storyId, (story) => {
return Task.addTask(story, action.task);
}));
case actionTypes.REMOVE_TASK:
return state.map(
updateIfSameId(action.storyId, (story) => {
return Task.deleteTask(action.task, story);
}));
case actionTypes.TOGGLE_TASK:
return state.map(
updateIfSameId(action.story.id, (story) => {
return Task.toggleTask(story, action.task);
}));
case actionTypes.DELETE_STORY_SUCCESS:
return state.filter(
story => story.id !== action.id
Expand All @@ -35,18 +52,24 @@ const storiesReducer = (state = initialState, action) => {
updateIfSameId(action.storyId, (story) => {
return Note.deleteNote(story, action.noteId)
}));
case actionTypes.ADD_TASK:
return state.map(
updateIfSameId(action.story.id, (story) => {
return Task.addTask(story, action.task);
}));
case actionTypes.REMOVE_TASK:
return state.map(
updateIfSameId(action.story.id, (story) => {
return Task.deleteTask(action.task, story);
}));
case actionTypes.UPDATE_TASK:
return state.map(
updateIfSameId(action.story.id, (story) => {
return Task.updateTask(story, action.task);
}));
default:
return state;
};
};

const updateIfSameId = (id, update) => {
return (model) => {
if (model.id !== id) {
return model;
};
return update(model);
};
};

export default storiesReducer;

0 comments on commit ff6a0c6

Please sign in to comment.