Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30,325 changes: 16,983 additions & 13,342 deletions CodingChallenge.UI/TodoChallenge/package-lock.json

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions CodingChallenge.UI/TodoChallenge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,25 @@
"version": "0.0.1",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"node-sass": "^5.0.0",
"node-sass": "^9.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-donut-chart": "^1.3.3",
"react-redux": "^7.2.4",
"react-scripts": "4.0.3",
"react-scripts": "^5.0.1",
"redux": "^4.1.0",
"redux-thunk": "2.3.0",
"web-vitals": "^1.0.1"
},
"scripts": {
"start": "react-scripts start",
"start": "react-scripts --openssl-legacy-provider start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
Expand All @@ -41,6 +46,7 @@
},
"devDependencies": {
"@babel/plugin-syntax-jsx": "^7.12.13",
"node-gyp": "9.4.0",
"prop-types": "^15.7.2",
"redux-devtools": "^3.7.0"
}
Expand Down
13 changes: 9 additions & 4 deletions CodingChallenge.UI/TodoChallenge/src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {Component} from 'react';
import TodoList from "./components/todo/TodoList";
import NewTodo from './components/todo/NewTodo';
import TodoChart from './components/todo/TodoChart';
import "./App.scss";
import './button.scss';

Expand All @@ -14,17 +16,20 @@ class App extends Component {

textInputChange = (e) => {
this.setState({...this.state, newTodo: e.target.value});

}

addNewTodo = () => {
console.warn('not implemented');
addNewTodo = (e) => {
console.log('add new todo');

}

render() {
return (
<div className="App">
<input type="text" value={this.state.newTodo} onChange={this.textInputChange}></input>
<button className={"btn--default"} onClick={this.addNewTodo}>Add</button>
<h1 className='App-header'>TODO APP</h1>
<TodoChart />
<NewTodo />
<TodoList />
</div>
)}
Expand Down
2 changes: 1 addition & 1 deletion CodingChallenge.UI/TodoChallenge/src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

.App-header {
background-color: #282c34;
min-height: 100vh;
min-height: 10vh;
display: flex;
flex-direction: column;
align-items: center;
Expand Down
5 changes: 2 additions & 3 deletions CodingChallenge.UI/TodoChallenge/src/TodoModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ export const TodoModel = {
id: PropTypes.number.isRequired,
text: PropTypes.string.isRequired,
isComplete: PropTypes.bool.isRequired,
dueDate: PropTypes.string.isRequired,
type: PropTypes.oneOf(['MustDo', 'Optional'])
};

export const TodoListModel = {
todos: PropTypes.arrayOf(PropTypes.shape(TodoModel)).isRequired
};


};
26 changes: 13 additions & 13 deletions CodingChallenge.UI/TodoChallenge/src/TodoService.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ const svc = {
return Promise.resolve(todo);
},

addTodo: async text => {
addTodo: async (text, dueDate) => {
const todos = (await svc.getTodos());
const nextId = Number(new Date());
const todo = { id: nextId, text, isComplete: false };
localStorage.setItem(KEY, JSON.stringify([...todos, todo]));
const todo = { id: nextId, text, dueDate, isComplete: false };
localStorage.setItem(KEY, JSON.stringify([...todos, todo]));
return Promise.resolve(todo);
},

Expand All @@ -27,16 +27,16 @@ const svc = {
const firstId = Number(new Date());
let counter = 0;
const todos = [
{id: firstId, text: 'Run the todo app', isComplete: true},
{id: ++counter+firstId, text: 'Implement addNewTodo in App.js', isComplete: false},
{id: ++counter+firstId, text: 'Fix the bug where text changes to a todo item are lost on browser refresh', isComplete: false},
{id: ++counter+firstId, text: 'Match the provided design', isComplete: false},
{id: ++counter+firstId, text: 'Add a chart for complete and incomplete todo items', isComplete: false},
{id: ++counter+firstId, text: 'Replace the Edit/Save/Complete buttons with icons', isComplete: false},
{id: ++counter+firstId, text: 'Add a due date to each todo', isComplete: false},
{id: ++counter+firstId, text: 'Sort the todo list by due date', isComplete: false},
{id: ++counter+firstId, text: 'Make all of the tests pass', isComplete: false},
{id: ++counter+firstId, text: 'Refactor anything in this app to your liking', isComplete: false},
{id: firstId, text: 'Run the todo app', isComplete: true, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Implement addNewTodo in App.js', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Fix the bug where text changes to a todo item are lost on browser refresh', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Match the provided design', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Add a chart for complete and incomplete todo items', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Replace the Edit/Save/Complete buttons with icons', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Add a due date to each todo', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Sort the todo list by due date', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Make all of the tests pass', isComplete: false, dueDate: '12/31/2023'},
{id: ++counter+firstId, text: 'Refactor anything in this app to your liking', isComplete: false, dueDate: '12/31/2023'},
];
localStorage.setItem(KEY, JSON.stringify(todos))
}
Expand Down
41 changes: 41 additions & 0 deletions CodingChallenge.UI/TodoChallenge/src/components/todo/NewTodo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import PropTypes from "prop-types";
import { TodoModel } from "../../TodoModel";
import { addTodo } from "../../todoActions";
import './todo.scss';

const NewTodo = () => {
const getCurrentDate = () => {
const d = new Date();
return (d.getMonth() + 1) + '/' + (d.getDate()) + '/' + (d.getFullYear());
}

const [newTodo, textInputChange] = useState('');
const [newTodoDueDate, dateInputChange] = useState(getCurrentDate());
const dispatch = useDispatch();

const onAddTodo = () => {
if (newTodo.trim() !== '' && !isNaN(Date.parse(newTodoDueDate))) {
textInputChange('');
dateInputChange(getCurrentDate());
dispatch(addTodo(newTodo, newTodoDueDate));
}
}

return (
<div>
<div className='new-todo'>
<input type="text" className={"new-todo-text"} value={newTodo} onChange={(e) => textInputChange(e.target.value)}></input>
<input type="text" className={"new-todo-text new-todo-date"} value={newTodoDueDate} onChange={(e) => dateInputChange(e.target.value)}></input>
<button className={"btn--default"} onClick={() => onAddTodo()}>Add</button>
</div>
</div>
)
}

NewTodo.propTypes = {
newTodo: PropTypes.shape(TodoModel)
};

export default NewTodo;
43 changes: 32 additions & 11 deletions CodingChallenge.UI/TodoChallenge/src/components/todo/Todo.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, {useState} from 'react';
import {TodoModel} from "../../TodoModel";
import PropTypes from "prop-types";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPen, faFloppyDisk, faCheck, faX } from '@fortawesome/free-solid-svg-icons'
import './todo.scss';

const Todo = (props) => {
const [editing, setStateEditing] = useState(false);
const [editingText, setStateEditText] = useState(props.todo.text);


const toggleComplete = () => {
props.onCompleteChange({...props.todo, isComplete: !props.todo.isComplete});
}
Expand All @@ -27,27 +28,47 @@ const Todo = (props) => {
}

const displayText = () => {
if (editing)
{
return <input onChange={onChangeEditText} value={editingText}></input>
if (editing) {
return (<div><input onChange={onChangeEditText} value={editingText}></input></div>)
}
else
{
return props.todo.text;
else {
return (<div>{props.todo.text}</div>);
}
}

const displayDueDate = () => {
return (<div>{props.todo.dueDate}</div>);
}

const getClassName = () => {
const {isComplete} = props.todo;
return `todo-item ${isComplete ? 'complete' : 'incomplete'}`;
}

return (
<div className={getClassName()}>
{displayText()}
<button onClick={toggleComplete} className={"btn--default btn--destructive"}>Toggle Complete</button>
<div className='todo-text'>
{displayText()}
{displayDueDate()}
</div>
{editing
? <button onClick={saveText} className={"btn--default btn--base"}>Save</button>
: <button onClick={toggleEditText} className={"btn--default btn--base"}>Edit</button>
?
<div>
<button onClick={toggleEditText} className={"btn--default btn--base"}>
<FontAwesomeIcon icon={faX} />
</button>
<button onClick={saveText} className={"btn--default btn--base"}>
<FontAwesomeIcon icon={faFloppyDisk} />
</button>
</div>
: <div>
<button onClick={toggleComplete} className={"btn--default btn--base"}>
<FontAwesomeIcon icon={faCheck} />
</button>
<button onClick={toggleEditText} className={"btn--default btn--base"}>
<FontAwesomeIcon icon={faPen} />
</button>
</div>
}
</div>
)
Expand Down
34 changes: 34 additions & 0 deletions CodingChallenge.UI/TodoChallenge/src/components/todo/TodoChart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import DonutChart from 'react-donut-chart';
import { getTodos } from "../../todoActions";

const TodoChart = ({todos, getTodos}) => {
useEffect(() => {
getTodos();
}, []);

return (
<div>
<DonutChart data={[{
label: 'Not Done',
value: todos.filter(todo => !todo.isComplete).length
},
{
label: 'Done',
value: todos.filter(todo => todo.isComplete).length
},]}
/>
</div>
)
}

const mapStateToProps = (state) => ({
todos: state.todos ?? []
});

const mapDispatchToProps = (dispatch) => ({
getTodos: () => dispatch(getTodos())
});

export default connect(mapStateToProps, mapDispatchToProps)(TodoChart);
32 changes: 21 additions & 11 deletions CodingChallenge.UI/TodoChallenge/src/components/todo/TodoList.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import React, {useState, useEffect} from 'react';
import Todo from "./Todo";
import {TodoListModel} from "../../TodoModel";
import {connect} from "react-redux";
import {completeTodo, getTodos, TODO_TEXT_CHANGE} from "../../todoActions";
import {completeTodo, getTodos, updateTodoText} from "../../todoActions";
import { useDispatch } from 'react-redux'

const TodoList = ({todos, getTodos, onTodoTextChange, onTodoCompleteChange}) => {
const TodoList = ({todos, getTodos, onTodoCompleteChange}) => {
const [filtered, setFiltered] = useState(true);

const dispatch = useDispatch();

const filterByOnChange = () => {
setFiltered(!filtered);
}
Expand All @@ -16,22 +19,29 @@ const TodoList = ({todos, getTodos, onTodoTextChange, onTodoCompleteChange}) =>
}, [getTodos]);

const renderTodoList = (todos) => {
todos.sort((a, b) => Date.parse(a.dueDate) - Date.parse(b.dueDate));
return todos.filter(filterTodos).map(mapTodoObjectToComponent);
}

const filterTodos = (todo) => filtered ? !todo.isComplete : true;
const mapTodoObjectToComponent = (todo, i) => (<Todo key={i}
todo={todo}
onTextChange={onTodoTextChange}
onCompleteChange={onTodoCompleteChange} />);

const mapTodoObjectToComponent = (todo, i) => {
return (<Todo key={i}
todo={todo}
onTextChange={(text) => {dispatch(updateTodoText(todo, text))}}
onCompleteChange={onTodoCompleteChange}
/>);
}

return (
<div className="todo-list">
<h2>List of todos</h2>
<div>
<div className="todo-list">
<div className='todo-filter'>
<span>Filter by complete</span>
<input type="checkbox" defaultChecked={filtered} onChange={filterByOnChange} />
</div>
<div className='todo-list-content'>
{renderTodoList(todos)}
</div>
{renderTodoList(todos)}
</div>
);
}
Expand All @@ -41,8 +51,8 @@ TodoList.propTypes = TodoListModel;
const mapStateToProps = (state) => ({
todos: state.todos ?? []
});

const mapDispatchToProps = (dispatch) => ({
onTodoTextChange: (text, id) => dispatch({type: TODO_TEXT_CHANGE, text, id}),
onTodoCompleteChange: (todo) => dispatch(completeTodo(todo)),
getTodos: () => dispatch(getTodos())
});
Expand Down
Loading