diff options
| author | Armand Philippot <git@armandphilippot.com> | 2022-02-20 16:11:50 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2022-02-20 16:15:08 +0100 |
| commit | 73a5c7fae9ffbe9ada721148c8c454a643aceebe (patch) | |
| tree | c8fad013ed9b5dd589add87f8d45cf02bbfc6e91 /public/projects/react-small-apps/apps/todos/src/views/TodoList | |
| parent | b01239fbdcc5bbc5921f73ec0e8fee7bedd5c8e8 (diff) | |
chore!: restructure repo
I separated public files from the config/dev files. It improves repo
readability.
I also moved dotenv helper to public/inc directory and extract the
Matomo tracker in the same directory.
Diffstat (limited to 'public/projects/react-small-apps/apps/todos/src/views/TodoList')
4 files changed, 249 insertions, 0 deletions
diff --git a/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoList.js b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoList.js new file mode 100644 index 0000000..c671459 --- /dev/null +++ b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoList.js @@ -0,0 +1,84 @@ +import { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { Button } from "../../components/forms"; +import TodoForm from "../TodoForm/TodoForm"; +import TodoListItem from "./TodoListItem"; +import "./TodoList.scss"; +import { deleteAllTodos } from "../../store/todos/todos.slice"; +import { LocalStorage } from "../../services/LocalStorage.service"; +import TodoListFilters from "./TodoListFilters"; + +function TodoList() { + const [todosList, setTodosList] = useState([]); + const [currentView, setCurrentView] = useState("all"); + const dispatch = useDispatch(); + const [isToggled, setIsToggled] = useState(false); + const currentUser = useSelector((state) => state.auth.currentUser); + const allTodos = useSelector((state) => state.todos); + + useEffect(() => { + const userTodos = allTodos.filter((todo) => todo.userId === currentUser.id); + + setTodosList(() => { + let list; + + switch (currentView) { + case "completed": + list = userTodos.filter((todo) => todo.done); + break; + case "ongoing": + list = userTodos.filter((todo) => !todo.done); + break; + default: + list = userTodos; + break; + } + + return list; + }); + }, [currentView, allTodos, currentUser.id]); + + useEffect(() => { + LocalStorage.set("todoList", allTodos); + }); + + const userTodosList = todosList.map((todo) => ( + <TodoListItem key={todo.id} todo={todo} /> + )); + + return ( + <div> + <h2>Welcome back {currentUser.username}!</h2> + <div className="todos-actions"> + <Button + modifiers={["action"]} + onClickHandler={() => setIsToggled(!isToggled)} + > + New todo + </Button> + <Button + modifiers={["action", "delete"]} + onClickHandler={() => dispatch(deleteAllTodos())} + > + Delete all + </Button> + </div> + {isToggled ? ( + <TodoForm userId={currentUser.id} closeForm={setIsToggled} /> + ) : ( + "" + )} + <TodoListFilters + currentView={currentView} + setCurrentView={setCurrentView} + /> + {userTodosList.length > 0 ? ( + <ul className="todos-list">{userTodosList}</ul> + ) : ( + "" + )} + </div> + ); +} + +export default TodoList; diff --git a/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoList.scss b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoList.scss new file mode 100644 index 0000000..024fe3e --- /dev/null +++ b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoList.scss @@ -0,0 +1,63 @@ +@use "../../sass/abstracts/placeholders"; +@use "../../sass/abstracts/variables" as var; + +.todos-actions { + display: flex; + gap: 1rem; +} + +.todos-filters { + display: flex; + flex-flow: row wrap; + align-items: center; + gap: clamp(0.2rem, 1vw, 0.5rem); + margin-top: 1rem +} + +.todos-list { + @extend %list-reset; + border: 1px solid #000; + margin-top: 1.5rem; + + &__item { + display: flex; + flex-flow: row wrap; + align-items: center; + gap: 1rem; + padding: 1rem; + + &:not(:first-child) { + border-top: 1px solid #000; + } + + .form__label { + margin: 0; + letter-spacing: 0; + text-transform: none; + } + + .todo__title { + background-image: linear-gradient( + to top, + transparent calc(50% - 3px), + var.$done-color calc(50% - 3px), + var.$done-color 50%, + transparent 50% + ); + background-size: 0 100%; + background-repeat: no-repeat; + margin-right: auto; + transition: background-size 0.3s ease-in-out 0s; + } + + &--done { + .todo__title { + background-size: 100% 100%; + } + } + + .btn { + padding: 0.4rem 0.7rem; + } + } +} diff --git a/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoListFilters.js b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoListFilters.js new file mode 100644 index 0000000..338492a --- /dev/null +++ b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoListFilters.js @@ -0,0 +1,47 @@ +import { Button } from "../../components/forms"; + +function TodoListFilters({ currentView, setCurrentView }) { + let allModifiers = ["filters"]; + let ongoingModifiers = ["filters"]; + let completedModifiers = ["filters"]; + + switch (currentView) { + case "all": + allModifiers.push("current"); + break; + case "ongoing": + ongoingModifiers.push("current"); + break; + case "completed": + completedModifiers.push("current"); + break; + default: + break; + } + + return ( + <div className="todos-filters"> + Show: + <Button + modifiers={allModifiers} + onClickHandler={() => setCurrentView("all")} + > + All + </Button> + <Button + modifiers={ongoingModifiers} + onClickHandler={() => setCurrentView("ongoing")} + > + Ongoing + </Button> + <Button + modifiers={completedModifiers} + onClickHandler={() => setCurrentView("completed")} + > + Completed + </Button> + </div> + ); +} + +export default TodoListFilters; diff --git a/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoListItem.js b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoListItem.js new file mode 100644 index 0000000..f76c1a1 --- /dev/null +++ b/public/projects/react-small-apps/apps/todos/src/views/TodoList/TodoListItem.js @@ -0,0 +1,55 @@ +import { useEffect, useState } from "react"; +import { useDispatch } from "react-redux"; +import { Link } from "react-router-dom"; +import { Button, Input } from "../../components/forms"; +import { deleteTodo, toggleTodo } from "../../store/todos/todos.slice"; +import { slugify } from "../../utilities/helpers"; + +function TodoListItem({ todo }) { + const { id, createdAt, title, done } = todo; + const [isChecked, setIsChecked] = useState(false); + const dispatch = useDispatch(); + + useEffect(() => { + if (done) setIsChecked(true); + }, [done]); + + const handleTodoDone = (checkboxState) => { + setIsChecked(checkboxState); + dispatch(toggleTodo(id)); + }; + + const todoSlug = slugify(title); + + const classNames = `todos-list__item ${ + isChecked ? "todos-list__item--done" : "" + }`; + + return ( + <li className={classNames}> + <span className="todo__date"> + {new Date(createdAt).toLocaleDateString()} + </span> + <span className="todo__title"> + <Link to={`/todo/${todoSlug}`} state={{ todoId: todo.id }}> + {title} + </Link> + </span> + <Input + type="checkbox" + label="Done?" + id={id} + value={isChecked} + updateValue={handleTodoDone} + /> + <Button + modifiers={["action", "delete"]} + onClickHandler={() => dispatch(deleteTodo(id))} + > + Delete + </Button> + </li> + ); +} + +export default TodoListItem; |
