commit f3852c1f5bea14e72e200389759f123b880ccfec
parent eddfd72ff918d39d648278ac1637679504019384
Author: Adrián Oliva <[email protected]>
Date:   Sun, 21 May 2023 17:58:01 -0600

Heavy refactoring.

Broke `ToDo.jsx` into two separate files. Broke each function into
simpler functions. Hope this makes the whole project more readable.

Diffstat:
Msrc/App.jsx | 4++--
Asrc/ToDo-UI/ListToDo.jsx | 320+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/ToDo-UI/NewToDo.jsx | 175+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/ToDo.jsx | 476-------------------------------------------------------------------------------
4 files changed, 497 insertions(+), 478 deletions(-)

diff --git a/src/App.jsx b/src/App.jsx @@ -1,6 +1,6 @@ import React from "react"; -// import { Counter } from "./features/counter/Counter"; -import { NewToDo, ListToDos } from "./ToDo"; +import { NewToDo } from "./ToDo-UI/NewToDo"; +import { ListToDos } from "./ToDo-UI/ListToDo"; function App() { return ( diff --git a/src/ToDo-UI/ListToDo.jsx b/src/ToDo-UI/ListToDo.jsx @@ -0,0 +1,320 @@ +import React, { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { + change_done, + remove_todo, + edit_todo, + set_sort_todo, + sort_todo, + select_todos, + select_current_sorting, +} from "../features/todo/reducer"; + +function sort_table_header(prefix, current_sorting) { + if (prefix.toLowerCase().startsWith(current_sorting.substr(0, 3))) { + switch (current_sorting.substr(-1)) { + case "^": + // Write Prefix and an arrow pointing up. + return ( + <> + {prefix} <span>&#8593;</span> + </> + ); + case "v": + // Write Prefix and an arrow pointing down. + return ( + <> + {prefix} <span>&#8595;</span> + </> + ); + } + } else { + // Write Prefix and four dots. No sorting. + return ( + <> + {prefix} <span>&#8283;</span> + </> + ); + } +} + +function list_of_todos(edit_button, delete_button) { + const dispatch = useDispatch(); + const my_todos = useSelector(select_todos); + const my_sorting = useSelector(select_current_sorting); + + function handle_sort_todos(where_clicked) { + dispatch( + set_sort_todo({ + where_clicked: where_clicked, + }) + ); + dispatch(sort_todo()); + } + + // Table contents + var table_head = ( + <thead> + <tr> + <th scope="col">Done</th> + <th scope="col">Name</th> + <th scope="col" onClick={() => handle_sort_todos("priority")}> + {sort_table_header("Priority", my_sorting)} + </th> + <th + scope="col" + onClick={() => { + handle_sort_todos("due_date"); + }} + > + {sort_table_header("Due Date", my_sorting)} + </th> + <th scope="col">Actions</th> + </tr> + </thead> + ); + + var table_body = ( + <tbody className="table-group-divider"> + {my_todos.map((item) => ( + <tr key={item.id}> + <th scope="row"> + <div className="form-check"> + <input + className="form-check-input" + type="checkbox" + checked={item.done} + id={"list-todo-done-" + item.id} + onChange={(e) => + dispatch( + change_done({ + id: item.id, + done: e.target.checked, + }) + ) + } + ></input> + </div> + </th> + <td>{item.text}</td> + <td>{item.priority}</td> + <td>{item.due_date}</td> + <td> + <div className="btn-group btn-group-sm" role="group"> + {edit_button(item)} + {delete_button(item)} + </div> + </td> + </tr> + ))} + </tbody> + ); + + return ( + <div className="container"> + <table className="table align-middle"> + {table_head} + {table_body} + </table> + </div> + ); +} + +function new_modal(modal_id, modal_header, modal_body, modal_footer) { + return ( + <div className="modal fade" id={modal_id} tabIndex="-1" role="dialog"> + <div className="modal-dialog modal-dialog-centered" role="document"> + <div className="modal-content"> + {modal_header} + {modal_body} + {modal_footer} + </div> + </div> + </div> + ); +} + +export function ListToDos() { + const dispatch = useDispatch(); + + const [edit_id, set_edit_id] = useState(-1); + const [edit_text, set_edit_text] = useState(""); + const [edit_due_date, set_edit_due_date] = useState(""); + const [edit_done, set_edit_done] = useState(false); + const [edit_priority, set_edit_priority] = useState("Low"); + + function handle_open_modal(id, text, due_date, done, priority) { + set_edit_id(id); + set_edit_text(text); + set_edit_due_date(due_date); + set_edit_done(done); + set_edit_priority(priority); + } + function handle_exit_modal() { + // https://stackoverflow.com/questions/27826381/clearing-form-input-fields-in-bootstrap + $("form").get(0).reset(); // Reset form + + set_edit_id(-1); + set_edit_text(""); + set_edit_done(false); + set_edit_priority("Low"); + } + function handle_edit_todo() { + dispatch( + edit_todo({ + id: edit_id, + text: edit_text, + due_date: edit_due_date, + done: edit_done, + priority: edit_priority, + }) + ); + dispatch(sort_todo()); + handle_exit_modal(); + } + + // Define Edit and Remove buttons + function edit_button(item) { + return ( + <button + type="button" + className="btn btn-outline-dark" + data-bs-toggle="modal" + data-bs-target="#EditToDo" + onClick={() => + handle_open_modal( + item.id, + item.text, + item.due_date, + item.done, + item.priority + ) + } + > + Edit + </button> + ); + } + function delete_button(item) { + return ( + <button + type="button" + className="btn btn-outline-dark" + onClick={(e) => dispatch(remove_todo(item.id))} + > + Delete + </button> + ); + } + + // Define modal to edit a to do. + var modal_header = ( + <div className="modal-header"> + <h5 className="modal-title">Edit a to do</h5> + <button + type="button" + className="close" + data-bs-dismiss="modal" + aria-label="Close edit window" + onClick={handle_exit_modal} + > + <span>&times;</span> + </button> + </div> + ); + var modal_footer = ( + <div className="modal-footer"> + <button + type="button" + className="btn btn-secondary" + data-bs-dismiss="modal" + onClick={handle_exit_modal} + > + Cancel + </button> + <button + type="button" + className="btn btn-primary" + onClick={handle_edit_todo} + data-bs-dismiss="modal" + > + Edit + </button> + </div> + ); + var modal_body = ( + <div className="modal-body"> + <form> + <div className="form-floating mb-3"> + <input + className="form-control" + id="edit-todo-id" + value={edit_id} + disabled + /> + <label htmlFor="edit-todo-id">ID</label> + </div> + <div className="form-floating mb-3"> + <input + className="form-control" + placeholder="Text" + value={edit_text} + id="edit-todo-text" + onChange={(e) => set_edit_text(e.target.value)} + /> + <label htmlFor="edit-todo-text">Text</label> + </div> + <div className="input-group mb-3"> + <div className="form-floating"> + <input + className="form-control" + type="date" + id="edit-todo-due-date" + placeholder="Due date" + value={edit_due_date} + onChange={(e) => { + set_edit_due_date(e.target.value); + }} + /> + <label htmlFor="edit-todo-due-date">Due Date</label> + </div> + </div> + <div className="form-check mb-3"> + <input + className="form-check-input" + type="checkbox" + id="edit-todo-done" + onClick={(e) => set_edit_done(e.target.checked)} + checked={edit_done} + /> + <label + className="form-check-label" + htmlFor="edit-todo-done" + > + Completed + </label> + </div> + <div className="form-floating mb-3"> + <select + className="form-select" + id="edit-todo-priority" + value={edit_priority} + onChange={(e) => set_edit_priority(e.target.value)} + > + <option value="Low">Low</option> + <option value="Medium">Medium</option> + <option value="High">High</option> + </select> + <label htmlFor="edit-todo-priority">Priority</label> + </div> + </form> + </div> + ); + + return ( + <> + {list_of_todos(edit_button, delete_button)} + {new_modal("EditToDo", modal_header, modal_body, modal_footer)} + </> + ); +} diff --git a/src/ToDo-UI/NewToDo.jsx b/src/ToDo-UI/NewToDo.jsx @@ -0,0 +1,175 @@ +import React, { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { + add_todo, + sort_todo, + select_last_index, +} from "../features/todo/reducer"; + +function new_button(trigger_id) { + return ( + <div className="container mt-3 mb-3"> + <button + type="button" + className="btn btn-outline-dark" + data-bs-toggle="modal" + data-bs-target={trigger_id} + > + + New To Do + </button> + </div> + ); +} + +function new_modal(modal_id, modal_header, modal_body, modal_footer) { + return ( + <div className="modal fade" id={modal_id} tabIndex="-1" role="dialog"> + <div className="modal-dialog modal-dialog-centered" role="document"> + <div className="modal-content"> + {modal_header} + {modal_body} + {modal_footer} + </div> + </div> + </div> + ); +} + +export function NewToDo() { + const my_last_idx = useSelector(select_last_index); + + const dispatch = useDispatch(); + const [new_text, set_new_text] = useState(""); + const [new_due_date, set_new_due_date] = useState(""); + const [new_done, set_new_done] = useState(false); + const [new_priority, set_new_priority] = useState("Low"); + + function handle_exit_modal() { + // https://stackoverflow.com/questions/27826381/clearing-form-input-fields-in-bootstrap + $("form").get(0).reset(); // Reset form + + set_new_text(""); + set_new_due_date(""); + set_new_done(false); + set_new_priority("Low"); + } + function handle_add_todo() { + dispatch( + add_todo({ + text: new_text, + due_date: new_due_date, + done: new_done, + priority: new_priority, + creation_date: new Date().toString(), + }) + ); + dispatch(sort_todo()); + handle_exit_modal(); + } + + // Define modal to add a new to do. + var modal_header = ( + <div className="modal-header"> + <h5 className="modal-title">Add a new to do</h5> + <button + type="button" + className="close" + data-bs-dismiss="modal" + aria-label="Close new to do window" + onClick={handle_exit_modal} + > + <span>&times;</span> + </button> + </div> + ); + var modal_footer = ( + <div className="modal-footer"> + <button + type="button" + className="btn btn-secondary" + data-bs-dismiss="modal" + onClick={handle_exit_modal} + > + Cancel + </button> + <button + type="button" + className="btn btn-primary" + onClick={handle_add_todo} + data-bs-dismiss="modal" + > + Add + </button> + </div> + ); + var modal_body = ( + <div className="modal-body"> + <form> + <div className="form-floating mb-3"> + <input + className="form-control" + id="new-todo-id" + value={my_last_idx + 1} + disabled + /> + <label htmlFor="new-todo-id">ID</label> + </div> + <div className="form-floating mb-3"> + <input + className="form-control" + placeholder="Text" + id="new-todo-text" + onChange={(e) => set_new_text(e.target.value)} + /> + <label htmlFor="new-todo-text">Text</label> + </div> + <div className="input-group mb-3"> + <div className="form-floating"> + <input + className="form-control" + type="date" + id="new-todo-due-date" + placeholder="Due date" + onChange={(e) => { + set_new_due_date(e.target.value); + }} + /> + <label htmlFor="new-todo-due-date">Due Date</label> + </div> + </div> + <div className="form-check mb-3"> + <input + className="form-check-input" + type="checkbox" + id="new-todo-done" + onClick={(e) => set_new_done(e.target.checked)} + /> + <label className="form-check-label" htmlFor="new-todo-done"> + Completed + </label> + </div> + <div className="form-floating mb-3"> + <select + className="form-select" + id="new-todo-priority" + onChange={(e) => set_new_priority(e.target.value)} + > + <option value="Low" defaultValue> + Low + </option> + <option value="Medium">Medium</option> + <option value="High">High</option> + </select> + <label htmlFor="new-todo-priority">Priority</label> + </div> + </form> + </div> + ); + + return ( + <> + {new_button("#NewToDo")} + {new_modal("NewToDo", modal_header, modal_body, modal_footer)} + </> + ); +} diff --git a/src/ToDo.jsx b/src/ToDo.jsx @@ -1,476 +0,0 @@ -import React, { useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { - add_todo, - change_done, - remove_todo, - edit_todo, - set_sort_todo, - sort_todo, - select_todos, - select_last_index, - select_current_sorting, -} from "./features/todo/reducer"; - -export function NewToDo() { - const my_todos = useSelector(select_todos); - const my_last_idx = useSelector(select_last_index); - - const dispatch = useDispatch(); - const [new_text, set_new_text] = useState(""); - const [new_due_date, set_new_due_date] = useState(""); - const [new_done, set_new_done] = useState(false); - const [new_priority, set_new_priority] = useState("Low"); - - function handle_exit_modal() { - // https://stackoverflow.com/questions/27826381/clearing-form-input-fields-in-bootstrap - $("form").get(0).reset(); // Reset form - - set_new_text(""); - set_new_due_date(""); - set_new_done(false); - set_new_priority("Low"); - } - - return ( - <> - <div className="container mt-3 mb-3"> - <button - type="button" - className="btn btn-outline-dark" - data-bs-toggle="modal" - data-bs-target="#NewToDo" - > - + New To Do - </button> - </div> - - <div - className="modal fade" - id="NewToDo" - tabIndex="-1" - role="dialog" - > - <div - className="modal-dialog modal-dialog-centered" - role="document" - > - <div className="modal-content"> - <div className="modal-header"> - <h5 className="modal-title" id="new-modal-title"> - Add a new to do - </h5> - <button - type="button" - className="close" - data-bs-dismiss="modal" - aria-label="Close new to do window" - onClick={handle_exit_modal} - > - <span>&times;</span> - </button> - </div> - <div className="modal-body"> - <form> - <div className="form-floating mb-3"> - <input - className="form-control" - id="new-todo-id" - value={my_last_idx + 1} - disabled - /> - <label htmlFor="new-todo-id">ID</label> - </div> - <div className="form-floating mb-3"> - <input - className="form-control" - placeholder="Text" - id="new-todo-text" - onChange={(e) => - set_new_text(e.target.value) - } - /> - <label htmlFor="new-todo-text">Text</label> - </div> - <div className="input-group mb-3"> - <div className="form-floating"> - <input - className="form-control" - type="date" - id="new-todo-due-date" - placeholder="Due date" - onChange={(e) => { - set_new_due_date( - e.target.value - ); - }} - /> - <label htmlFor="new-todo-due-date"> - Due Date - </label> - </div> - </div> - <div className="form-check mb-3"> - <input - className="form-check-input" - type="checkbox" - id="new-todo-done" - onClick={(e) => - set_new_done(e.target.checked) - } - /> - <label - className="form-check-label" - htmlFor="new-todo-done" - > - Completed - </label> - </div> - <div className="form-floating mb-3"> - <select - className="form-select" - id="new-todo-priority" - onChange={(e) => - set_new_priority(e.target.value) - } - > - <option value="Low" defaultValue> - Low - </option> - <option value="Medium">Medium</option> - <option value="High">High</option> - </select> - <label htmlFor="new-todo-priority"> - Priority - </label> - </div> - </form> - </div> - <div className="modal-footer"> - <button - type="button" - className="btn btn-secondary" - data-bs-dismiss="modal" - onClick={handle_exit_modal} - > - Cancel - </button> - <button - type="button" - className="btn btn-primary" - onClick={() => { - dispatch( - add_todo({ - text: new_text, - due_date: new_due_date, - done: new_done, - priority: new_priority, - creation_date: - new Date().toString(), - }) - ), - dispatch(sort_todo()); - handle_exit_modal(); - }} - data-bs-dismiss="modal" - > - Add - </button> - </div> - </div> - </div> - </div> - </> - ); -} - -function sort_table_header(prefix, current_sorting) { - if (prefix.toLowerCase().startsWith(current_sorting.substr(0, 3))) { - switch (current_sorting.substr(-1)) { - case "^": - return ( - <> - {prefix} <span>&#8593;</span> - </> - ); - case "v": - return ( - <> - {prefix} <span>&#8595;</span> - </> - ); - } - } else { - return ( - <> - {prefix} <span>&#8283;</span> - </> - ); - } -} - -export function ListToDos() { - const my_todos = useSelector(select_todos); - const my_sorting = useSelector(select_current_sorting); - const dispatch = useDispatch(); - - const [edit_id, set_edit_id] = useState(-1); - const [edit_text, set_edit_text] = useState(""); - const [edit_due_date, set_edit_due_date] = useState(""); - const [edit_done, set_edit_done] = useState(false); - const [edit_priority, set_edit_priority] = useState("Low"); - - function handle_open_modal(id, text, due_date, done, priority) { - set_edit_id(id); - set_edit_text(text); - set_edit_due_date(due_date); - set_edit_done(done); - set_edit_priority(priority); - } - function handle_exit_modal() { - // https://stackoverflow.com/questions/27826381/clearing-form-input-fields-in-bootstrap - $("form").get(0).reset(); // Reset form - - set_edit_id(-1); - set_edit_text(""); - set_edit_done(false); - set_edit_priority("Low"); - } - - return ( - <> - <div className="container"> - <table className="table align-middle"> - <thead> - <tr> - <th scope="col">Done</th> - <th scope="col">Name</th> - <th - scope="col" - onClick={(e) => { - dispatch( - set_sort_todo({ - where_clicked: "priority", - }) - ), - dispatch(sort_todo()); - }} - > - {sort_table_header("Priority", my_sorting)} - </th> - <th - scope="col" - onClick={(e) => { - dispatch( - set_sort_todo({ - where_clicked: "due_date", - }) - ), - dispatch(sort_todo()); - }} - > - {sort_table_header("Due Date", my_sorting)} - </th> - <th scope="col">Actions</th> - </tr> - </thead> - <tbody className="table-group-divider"> - {my_todos.map((item) => ( - <tr key={item.id}> - <th scope="row"> - <div className="form-check"> - <input - className="form-check-input" - type="checkbox" - checked={item.done} - id={"list-todo-done-" + item.id} - onChange={(e) => - dispatch( - change_done({ - id: item.id, - done: e.target.checked, - }) - ) - } - ></input> - </div> - </th> - <td>{item.text}</td> - <td>{item.priority}</td> - <td>{item.due_date}</td> - <td> - <div - className="btn-group btn-group-sm" - role="group" - > - <button - type="button" - className="btn btn-outline-dark" - data-bs-toggle="modal" - data-bs-target="#EditToDo" - onClick={() => - handle_open_modal( - item.id, - item.text, - item.due_date, - item.done, - item.priority - ) - } - > - Edit - </button> - <button - type="button" - className="btn btn-outline-dark" - onClick={(e) => - dispatch(remove_todo(item.id)) - } - > - Delete - </button> - </div> - </td> - </tr> - ))} - </tbody> - </table> - </div> - - <div - className="modal fade" - id="EditToDo" - tabIndex="-1" - role="dialog" - > - <div - className="modal-dialog modal-dialog-centered" - role="document" - > - <div className="modal-content"> - <div className="modal-header"> - <h5 className="modal-title">Edit a to do</h5> - <button - type="button" - className="close" - data-bs-dismiss="modal" - aria-label="Close edit window" - onClick={handle_exit_modal} - > - <span>&times;</span> - </button> - </div> - <div className="modal-body"> - <form> - <div className="form-floating mb-3"> - <input - className="form-control" - id="edit-todo-id" - value={edit_id} - disabled - /> - <label htmlFor="edit-todo-id">ID</label> - </div> - <div className="form-floating mb-3"> - <input - className="form-control" - placeholder="Text" - value={edit_text} - id="edit-todo-text" - onChange={(e) => - set_edit_text(e.target.value) - } - /> - <label htmlFor="edit-todo-text">Text</label> - </div> - <div className="input-group mb-3"> - <div className="form-floating"> - <input - className="form-control" - type="date" - id="edit-todo-due-date" - placeholder="Due date" - value={edit_due_date} - onChange={(e) => { - set_edit_due_date( - e.target.value - ); - }} - /> - <label htmlFor="edit-todo-due-date"> - Due Date - </label> - </div> - </div> - <div className="form-check mb-3"> - <input - className="form-check-input" - type="checkbox" - id="edit-todo-done" - onClick={(e) => - set_edit_done(e.target.checked) - } - checked={edit_done} - /> - <label - className="form-check-label" - htmlFor="edit-todo-done" - > - Completed - </label> - </div> - <div className="form-floating mb-3"> - <select - className="form-select" - id="edit-todo-priority" - value={edit_priority} - onChange={(e) => - set_edit_priority(e.target.value) - } - > - <option value="Low">Low</option> - <option value="Medium">Medium</option> - <option value="High">High</option> - </select> - <label htmlFor="edit-todo-priority"> - Priority - </label> - </div> - </form> - </div> - <div className="modal-footer"> - <button - type="button" - className="btn btn-secondary" - data-bs-dismiss="modal" - onClick={handle_exit_modal} - > - Cancel - </button> - <button - type="button" - className="btn btn-primary" - onClick={() => { - dispatch( - edit_todo({ - id: edit_id, - text: edit_text, - due_date: edit_due_date, - done: edit_done, - priority: edit_priority, - }) - ), - dispatch(sort_todo()), - handle_exit_modal(); - }} - data-bs-dismiss="modal" - > - Edit - </button> - </div> - </div> - </div> - </div> - </> - ); -}