├── src ├── types │ └── react-reveal.d.ts ├── react-app-env.d.ts ├── assets │ ├── icons │ │ ├── pin.png │ │ ├── close.png │ │ ├── edit.png │ │ ├── remove.png │ │ └── index.tsx │ └── images │ │ ├── loading.gif │ │ ├── toDoList.png │ │ └── index.tsx ├── views │ ├── index.tsx │ ├── About │ │ ├── styled.tsx │ │ └── index.tsx │ ├── Home │ │ ├── styled.tsx │ │ └── index.tsx │ └── ToDoListId │ │ ├── styled.tsx │ │ └── index.tsx ├── components │ ├── Loading │ │ ├── index.tsx │ │ └── styled.tsx │ ├── TextLink │ │ ├── index.tsx │ │ └── styled.tsx │ ├── index.tsx │ ├── NotebookSheet │ │ ├── index.tsx │ │ └── styled.tsx │ ├── Buttom │ │ ├── index.tsx │ │ └── styled.tsx │ ├── Header │ │ ├── index.tsx │ │ └── styled.tsx │ ├── Modal │ │ ├── styled.tsx │ │ └── index.tsx │ └── Note │ │ ├── styled.tsx │ │ └── index.tsx ├── styles │ ├── GlobalStyle.tsx │ └── Theme.tsx ├── App.tsx ├── index.tsx ├── routes.tsx └── server │ └── api.tsx ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── .gitignore ├── tsconfig.json ├── package.json └── README.md /src/types/react-reveal.d.ts: -------------------------------------------------------------------------------- 1 | declare module "react-reveal"; 2 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/assets/icons/pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/src/assets/icons/pin.png -------------------------------------------------------------------------------- /src/assets/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/src/assets/icons/close.png -------------------------------------------------------------------------------- /src/assets/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/src/assets/icons/edit.png -------------------------------------------------------------------------------- /src/assets/icons/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/src/assets/icons/remove.png -------------------------------------------------------------------------------- /src/assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/src/assets/images/loading.gif -------------------------------------------------------------------------------- /src/assets/images/toDoList.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HMontarroyos/to_do_list/HEAD/src/assets/images/toDoList.png -------------------------------------------------------------------------------- /src/assets/images/index.tsx: -------------------------------------------------------------------------------- 1 | import toDoList from "../images/toDoList.png"; 2 | import loading from "../images/loading.gif"; 3 | 4 | export { toDoList, loading }; 5 | -------------------------------------------------------------------------------- /src/assets/icons/index.tsx: -------------------------------------------------------------------------------- 1 | import edit from "../icons/edit.png"; 2 | import remove from "../icons/remove.png"; 3 | import pin from "../icons/pin.png"; 4 | import close from "../icons/close.png"; 5 | 6 | export { edit, remove, pin, close }; 7 | -------------------------------------------------------------------------------- /src/views/index.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from "react"; 2 | 3 | const Home = lazy(async () => await import("./Home")); 4 | const About = lazy(async () => await import("./About")); 5 | const ToDoListId = lazy(async () => await import("./ToDoListId")); 6 | 7 | export { Home, About, ToDoListId }; 8 | -------------------------------------------------------------------------------- /src/components/Loading/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from "./styled"; 2 | import { loading } from "../../assets/images"; 3 | 4 | function Loading(): JSX.Element { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | 12 | export default Loading; 13 | -------------------------------------------------------------------------------- /src/styles/GlobalStyle.tsx: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | export const GlobalStyle = createGlobalStyle` 3 | * { 4 | border: 0; 5 | padding: 0; 6 | margin: 0; 7 | font-family: 'Oswald', sans-serif; 8 | } 9 | 10 | body{ 11 | background-color: #F2E9e6; 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import Routes from "./routes"; 2 | import { ThemeProvider } from "styled-components"; 3 | import { Theme } from "../src/styles/Theme"; 4 | 5 | function App(): JSX.Element { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /src/components/TextLink/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from "./styled"; 2 | 3 | interface TextLinkProps { 4 | name: string; 5 | color: string; 6 | onClick: () => void; 7 | } 8 | 9 | function TextLink({ name, color, onClick }: TextLinkProps): JSX.Element { 10 | return ( 11 | 12 | {name} 13 | 14 | ); 15 | } 16 | 17 | export default TextLink; 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from "react"; 2 | import Buttom from "./Buttom"; 3 | import Note from "./Note"; 4 | import Modal from "./Modal"; 5 | import TextLink from "./TextLink"; 6 | import NotebookSheet from "./NotebookSheet"; 7 | const Header = lazy(async () => await import("./Header")); 8 | const Loading = lazy(async () => await import("./Loading")); 9 | 10 | export { Buttom, Note, Modal, TextLink, NotebookSheet, Header, Loading }; 11 | -------------------------------------------------------------------------------- /src/components/TextLink/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { lighten } from "polished"; 3 | 4 | interface Props { 5 | color: string; 6 | } 7 | 8 | export const Text = styled.p` 9 | padding-left: 20px; 10 | color: ${(props) => props.color}; 11 | font-size: ${(props) => props.theme.fontSizes.small}; 12 | cursor: pointer; 13 | 14 | &:hover { 15 | color: ${(props) => lighten(0.1, props.color)}; 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /src/components/NotebookSheet/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import * as S from "./styled"; 3 | 4 | interface NotebookSheetProps 5 | extends React.ButtonHTMLAttributes { 6 | children: React.ReactNode; 7 | } 8 | 9 | function NotebookSheet({ children }: NotebookSheetProps): JSX.Element { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | 17 | export default NotebookSheet; 18 | -------------------------------------------------------------------------------- /src/components/NotebookSheet/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const Wrapper = styled.div` 4 | padding-left: 7%; 5 | padding-right: 5%; 6 | margin: 0 auto; 7 | background-color: #fcf9ff; 8 | `; 9 | 10 | export const Container = styled.div` 11 | width: 100%; 12 | height: 100%; 13 | position: relative; 14 | padding-top: 10px; 15 | padding-bottom: 10px; 16 | margin: 20px 20px 0 20px; 17 | border-left: solid 1px red; 18 | border-right: solid 1px red; 19 | `; 20 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { ToastContainer } from "react-toastify"; 4 | import "react-toastify/dist/ReactToastify.css"; 5 | import App from "./App"; 6 | import { GlobalStyle } from "../src/styles/GlobalStyle"; 7 | 8 | const root = ReactDOM.createRoot( 9 | document.getElementById("root") as HTMLElement 10 | ); 11 | root.render( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "ToDo List", 3 | "name": "ToDo List", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Loading/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from "styled-components"; 2 | 3 | const animationLoading = keyframes` 4 | 0% { 5 | transform: translateZ(0); 6 | opacity: 1; 7 | } 8 | 60% { 9 | opacity: 0.8; 10 | } 11 | 100% { 12 | letter-spacing: 1em; 13 | transform: translateZ(300px); 14 | opacity: 0; 15 | } 16 | `; 17 | 18 | export const Container = styled.div` 19 | animation: ${animationLoading} 5s ease-out; 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | height: 100vh; 24 | `; 25 | 26 | export const Image = styled.img` 27 | width: 300px; 28 | height: 300px; 29 | `; 30 | -------------------------------------------------------------------------------- /src/components/Buttom/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import * as S from "./styled"; 3 | 4 | interface ButtonProps extends React.ButtonHTMLAttributes { 5 | width?: string; 6 | height?: string; 7 | colorWarning?: boolean; 8 | children?: React.ReactNode; 9 | } 10 | 11 | function Button({ 12 | width, 13 | height, 14 | colorWarning, 15 | children, 16 | ...props 17 | }: ButtonProps): JSX.Element { 18 | return ( 19 | 25 | {children} 26 | 27 | ); 28 | } 29 | 30 | export default Button; 31 | -------------------------------------------------------------------------------- /src/styles/Theme.tsx: -------------------------------------------------------------------------------- 1 | export const Theme = { 2 | colors: { 3 | primary: "#fbeec2", 4 | secondary: "#accec0", 5 | background: "#F2E9e6", 6 | backgroundConfirm: "#95de90", 7 | text: "#605951", 8 | light: "#c1b398", 9 | confirm: "#61a6ab", 10 | remove: "#ff6161", 11 | note: "#FDFDC9", 12 | disabledText: "#a1a1ad", 13 | }, 14 | fonts: { 15 | title: "Satisfy, cursive", 16 | text: "Titillium Web, sans-serif", 17 | }, 18 | fontSizes: { 19 | small: "18px", 20 | medium: "24px", 21 | large: "50px", 22 | big: "128px", 23 | }, 24 | spacing: { 25 | small: "8px", 26 | medium: "16px", 27 | large: "24px", 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import * as S from "./styled"; 2 | import { pin } from "../../assets/icons"; 3 | 4 | function Header(): JSX.Element { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
  • Inicio
  • 15 |
    16 | 17 |
  • Sobre
  • 18 |
    19 |
    20 |
    21 | 22 | ); 23 | } 24 | 25 | export default Header; 26 | -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { BrowserRouter, Routes as Router, Route } from "react-router-dom"; 3 | import { Home, About, ToDoListId } from "./views"; 4 | import { Header, Loading } from "./components"; 5 | 6 | const Routes: React.FC = () => { 7 | return ( 8 | 9 | }> 10 |
    11 | 12 | } /> 13 | } /> 14 | } /> 15 | } /> 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default Routes; 23 | -------------------------------------------------------------------------------- /src/components/Modal/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const ModalOverlay = styled.div` 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: 100%; 8 | height: 100%; 9 | background-color: rgba(0, 0, 0, 0.5); 10 | z-index: 999; 11 | justify-content: center; 12 | display: flex; 13 | align-items: center; 14 | `; 15 | export const Input = styled.input` 16 | background-color: #e8e8a2; 17 | background-color: ${(props) => props.theme.fonts.title}; 18 | color: #333; 19 | width: 160px; 20 | height: 30px; 21 | margin-bottom: 10px; 22 | border-radius: 5px; 23 | padding: 5px; 24 | `; 25 | 26 | export const ContainerInput = styled.div` 27 | margin-top: 20px; 28 | display: flex; 29 | flex-direction: column; 30 | justify-content: center; 31 | align-items: center; 32 | `; 33 | 34 | export const TextMaxCharacter = styled.p` 35 | font-size: 13px; 36 | color: ${(props) => props.theme.colors.remove}; 37 | font-family: ${(props) => props.theme.fonts.title}; 38 | padding-bottom: 10px; 39 | `; 40 | -------------------------------------------------------------------------------- /src/views/About/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const Container = styled.div` 4 | display: flex; 5 | flex-direction: row; 6 | justify-content: center; 7 | align-items: center; 8 | width: 800px; 9 | margin: 20px auto; 10 | padding-left: 10px; 11 | padding-right: 10px; 12 | 13 | @media (max-width: 820px) { 14 | width: 600px; 15 | flex-direction: column; 16 | } 17 | 18 | @media (max-width: 500px) { 19 | width: 300px; 20 | } 21 | 22 | h1 { 23 | color: ${(props) => props.theme.colors.text}; 24 | font-size: ${(props) => props.theme.fontSizes.large}; 25 | font-family: ${(props) => props.theme.fonts.title}; 26 | margin-top: 20px; 27 | } 28 | p { 29 | color: ${(props) => props.theme.colors.text}; 30 | font-size: ${(props) => props.theme.fontSizes.small}; 31 | font-family: ${(props) => props.theme.fonts.title}; 32 | margin-top: 20px; 33 | white-space: pre-wrap; 34 | } 35 | `; 36 | export const Image = styled.img` 37 | width: 400px; 38 | 39 | @media (max-width: 500px) { 40 | width: 300px; 41 | } 42 | `; 43 | -------------------------------------------------------------------------------- /src/components/Buttom/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { lighten } from "polished"; 3 | 4 | interface Props { 5 | width?: string; 6 | height?: string; 7 | colorWarning?: boolean; 8 | disabled?: boolean; 9 | } 10 | 11 | export const Button = styled.button` 12 | width: ${(props) => props.width}; 13 | height: ${(props) => props.height}; 14 | color: ${(props) => props.theme.colors.text}; 15 | background-color: ${(props) => 16 | props.disabled 17 | ? props.theme.colors.disabledBackground 18 | : props.colorWarning 19 | ? props.theme.colors.remove 20 | : props.theme.colors.backgroundConfirm}; 21 | font-size: ${(props) => props.theme.fontSizes.small}; 22 | border-radius: 10px; 23 | cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; 24 | margin-left: 20px; 25 | 26 | &:hover { 27 | background-color: ${(props) => 28 | props.disabled 29 | ? props.theme.colors.disabledBackground 30 | : props.colorWarning 31 | ? lighten(0.1, props.theme.colors.remove) 32 | : lighten(0.1, props.theme.colors.backgroundConfirm)}; 33 | box-shadow: ${(props) => 34 | props.disabled ? "none" : "0px 2px 4px rgba(0, 0, 0, 0.25)"}; 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | ToDo List 18 | 19 | 20 | 21 |
    22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo_list", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.18.32", 11 | "@types/react": "^18.2.6", 12 | "@types/react-dom": "^18.2.4", 13 | "@types/uuid": "^9.0.1", 14 | "axios": "^1.4.0", 15 | "polished": "^4.2.2", 16 | "query-string": "^8.1.0", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-reveal": "^1.2.2", 20 | "react-router-dom": "^6.11.2", 21 | "react-scripts": "5.0.1", 22 | "react-toastify": "^9.1.3", 23 | "styled-components": "^6.0.0-rc.1", 24 | "sweetalert2": "^11.7.5", 25 | "typescript": "^4.9.5", 26 | "uuid": "^13.0.0", 27 | "web-vitals": "^2.1.4" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "test": "react-scripts test", 33 | "eject": "react-scripts eject" 34 | }, 35 | "eslintConfig": { 36 | "extends": [ 37 | "react-app", 38 | "react-app/jest" 39 | ] 40 | }, 41 | "browserslist": { 42 | "production": [ 43 | ">0.2%", 44 | "not dead", 45 | "not op_mini all" 46 | ], 47 | "development": [ 48 | "last 1 chrome version", 49 | "last 1 firefox version", 50 | "last 1 safari version" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/views/Home/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Link } from "react-router-dom"; 3 | 4 | export const Container = styled.div` 5 | margin: 20px; 6 | `; 7 | 8 | export const LinkRedirect = styled(Link)` 9 | text-decoration: none; 10 | `; 11 | 12 | export const Title = styled.h1` 13 | color: ${(props) => props.theme.colors.text}; 14 | font-size: ${(props) => props.theme.fontSizes.large}; 15 | font-family: ${(props) => props.theme.fonts.title}; 16 | `; 17 | 18 | export const Paragraph = styled.p` 19 | color: ${(props) => props.theme.colors.text}; 20 | font-size: ${(props) => props.theme.fontSizes.medium}; 21 | font-family: ${(props) => props.theme.fonts.title}; 22 | white-space: pre-wrap; 23 | margin-top: 30px; 24 | `; 25 | 26 | export const TextToDo = styled.p` 27 | color: ${(props) => props.theme.colors.text}; 28 | font-size: ${(props) => props.theme.fontSizes.small}; 29 | white-space: pre-wrap; 30 | text-align: center; 31 | padding: 10px; 32 | `; 33 | 34 | export const ContainerParagraph = styled.div` 35 | display: flex; 36 | flex-direction: column; 37 | justify-content: center; 38 | align-items: center; 39 | width: 700px; 40 | margin: 0 auto; 41 | margin-bottom: 30px; 42 | 43 | @media (max-width: 820px) { 44 | width: 500px; 45 | } 46 | 47 | @media (max-width: 500px) { 48 | width: 300px; 49 | } 50 | `; 51 | 52 | export const ContainerNote = styled.div` 53 | width: 100%; 54 | display: flex; 55 | flex-direction: row; 56 | align-items: center; 57 | flex-wrap: wrap; 58 | `; 59 | -------------------------------------------------------------------------------- /src/components/Header/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Link } from "react-router-dom"; 3 | import { lighten } from "polished"; 4 | 5 | interface Props { 6 | height: string; 7 | } 8 | 9 | export const Menu = styled.div` 10 | width: 100%; 11 | height: ${(props) => props.height}; 12 | background-color: ${(props) => props.theme.colors.primary}; 13 | position: relative; 14 | `; 15 | 16 | export const NavItems = styled.ul` 17 | z-index: 9999; 18 | list-style: none; 19 | display: flex; 20 | margin: 0; 21 | padding: 0; 22 | justify-content: space-around; 23 | text-align: center; 24 | position: relative; 25 | z-index: 1; 26 | 27 | 28 | li { 29 | color: ${(props) => props.theme.colors.text}; 30 | font-family: ${(props) => props.theme.fonts.title} 31 | font-weight: bold; 32 | display: inline-block; 33 | padding: 10px; 34 | margin-right: 20px; 35 | font-size: ${(props) => props.theme.fontSizes.small}; 36 | cursor: pointer; 37 | transition: color 0.3s ease; 38 | 39 | &:hover { 40 | color: ${(props) => lighten(0.1, props.theme.colors.text)}; 41 | } 42 | } 43 | `; 44 | 45 | export const LinkRedirect = styled(Link)` 46 | text-decoration: none; 47 | `; 48 | 49 | export const ImageContainer = styled.div` 50 | position: absolute; 51 | display: flex; 52 | flex-direction: row; 53 | align-items: center; 54 | justify-content: space-between; 55 | width: 100%; 56 | top: 50%; 57 | transform: translateY(-50%); 58 | z-index: 0; 59 | `; 60 | 61 | export const Image = styled.img` 62 | width: 40px; 63 | height: 40px; 64 | margin-right: 10px; 65 | margin-left: 10px; 66 | `; 67 | -------------------------------------------------------------------------------- /src/views/About/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Fade } from "react-reveal"; 3 | import * as S from "./styled"; 4 | import { toDoList } from "../../assets/images"; 5 | 6 | const About: React.FC = () => { 7 | return ( 8 | <> 9 | 10 | 11 |
    12 |

    Sobre Nós

    13 |

    14 | Bem-vindo(a) ao nosso sistema de gerenciamento de tarefas! Sabemos 15 | o quão essencial é ser organizado(a) e eficiente em suas 16 | atividades diárias. Para ajudá-lo(a) a alcançar esse objetivo, 17 | criamos um recurso poderoso que permite que você crie, personalize 18 | e acompanhe suas listas de tarefas com facilidade. 19 |

    20 |

    21 | Além disso, nosso sistema permite que você edite e gerencie suas 22 | tarefas. E se alguma tarefa não for mais relevante, você pode 23 | excluí-la sem problemas. 24 |

    25 |

    26 | Então, não espere mais! começe logo a criar sua primeira lista de 27 | tarefas agora mesmo. Com recursos abrangentes e uma interface 28 | intuitiva, estamos aqui para ajudá-lo(a) a atingir níveis elevados 29 | de produtividade e alcançar seus objetivos. Seja organizado(a), 30 | seja eficiente e conquiste o sucesso em suas tarefas diárias! 31 |

    32 |
    33 |
    34 | 35 | 36 | 37 |
    38 | 39 | ); 40 | }; 41 | 42 | export default About; 43 | -------------------------------------------------------------------------------- /src/components/Note/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const StickyContainer = styled.div` 4 | width: 230px; 5 | position: relative; 6 | `; 7 | 8 | export const StickyOuter = styled.div` 9 | display: flex; 10 | padding-top: 92%; 11 | position: relative; 12 | width: 100%; 13 | `; 14 | 15 | export const Sticky = styled.div` 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | right: 0; 20 | bottom: 0; 21 | 22 | &::before { 23 | box-shadow: -2px 2px 15px 0 rgba(0, 0, 0, 0.5); 24 | background-color: rgba(0, 0, 0, 0.25); 25 | content: ""; 26 | width: 90%; 27 | left: 5px; 28 | height: 83%; 29 | position: absolute; 30 | top: 30%; 31 | } 32 | `; 33 | 34 | export const StickyBefore = styled.div` 35 | box-shadow: -2px 2px 15px 0 rgba(0, 0, 0, 0.5); 36 | background-color: rgba(0, 0, 0, 0.25); 37 | content: ""; 38 | width: 90%; 39 | left: 5px; 40 | height: 83%; 41 | position: absolute; 42 | top: 30%; 43 | `; 44 | 45 | export const StickyContent = styled.div` 46 | background: ${(props) => props.theme.colors.note}; 47 | width: 100%; 48 | height: 100%; 49 | display: flex; 50 | justify-content: center; 51 | align-items: center; 52 | color: ${(props) => props.theme.colors.text}; 53 | font-size: ${(props) => props.theme.fontSizes.medium}; 54 | clip-path: url(#stickyClip); 55 | cursor: pointer; 56 | position: relative; 57 | `; 58 | export const Icon = styled.img` 59 | width: 25px; 60 | height: 25px; 61 | margin: 10px; 62 | `; 63 | 64 | export const ContainerIcon = styled.div` 65 | position: absolute; 66 | top: 10px; 67 | right: 10px; 68 | display: flex; 69 | justify-content: space-between; 70 | align-items: center; 71 | `; 72 | 73 | export const TextContainer = styled.div` 74 | word-wrap: break-word; 75 | hyphens: auto; 76 | `; 77 | -------------------------------------------------------------------------------- /src/components/Note/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import * as S from "./styled"; 4 | import { edit, remove, close } from "../../assets/icons"; 5 | 6 | interface NoteProps extends React.ButtonHTMLAttributes { 7 | children?: React.ReactNode; 8 | closeIcon?: boolean; 9 | onClose?: () => void; 10 | onRemove?: () => void; 11 | onEdit?: () => void; 12 | id?: string; 13 | } 14 | 15 | function Note({ 16 | children, 17 | closeIcon, 18 | onClose, 19 | onRemove, 20 | onEdit, 21 | id, 22 | }: NoteProps): JSX.Element { 23 | const navigate = useNavigate(); 24 | 25 | const navigateToDoListById = () => { 26 | id && navigate(`/todo/${id}`); 27 | }; 28 | 29 | const stopIconClickPropagation = (event: React.MouseEvent) => { 30 | event.stopPropagation(); 31 | }; 32 | 33 | return ( 34 | <> 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | 49 | 50 | {children} 51 | 52 | {closeIcon ? ( 53 | 54 | ) : ( 55 | <> 56 | { 59 | stopIconClickPropagation(event); 60 | onEdit && onEdit(); 61 | }} 62 | /> 63 | { 66 | stopIconClickPropagation(event); 67 | onRemove && onRemove(); 68 | }} 69 | /> 70 | 71 | )} 72 | 73 | 74 | 75 | 76 | 77 | 78 | ); 79 | } 80 | 81 | export default Note; 82 | -------------------------------------------------------------------------------- /src/views/ToDoListId/styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | interface ItemProps { 4 | checked: boolean; 5 | } 6 | 7 | interface ContainerInputProps { 8 | taskChildren?: boolean; 9 | } 10 | 11 | export const Container = styled.div` 12 | margin: 20px; 13 | display: flex; 14 | flex-direction: row; 15 | justify-content: space-between; 16 | align-items: center; 17 | `; 18 | 19 | export const ContainerTitle = styled.div` 20 | width: 50%; 21 | word-wrap: break-word; 22 | hyphens: auto; 23 | `; 24 | 25 | export const Title = styled.h1` 26 | color: ${(props) => props.theme.colors.text}; 27 | font-size: ${(props) => props.theme.fontSizes.large}; 28 | font-family: ${(props) => props.theme.fonts.title}; 29 | `; 30 | 31 | export const Paragraph = styled.p` 32 | text-align: center; 33 | margin-top: 50px; 34 | color: ${(props) => props.theme.colors.text}; 35 | font-size: ${(props) => props.theme.fontSizes.medium}; 36 | font-family: ${(props) => props.theme.fonts.title}; 37 | `; 38 | 39 | export const ContainerTable = styled.div` 40 | margin: 20px; 41 | margin-top: 150px; 42 | 43 | div { 44 | display: flex; 45 | flex-direction: row; 46 | justify-content: space-between; 47 | } 48 | 49 | @media (max-width: 768px) { 50 | flex-wrap: wrap; 51 | } 52 | `; 53 | 54 | export const ContainerButtons = styled.div` 55 | display: flex; 56 | flex-direction: row; 57 | `; 58 | 59 | export const ContainerInput = styled.div` 60 | margin: 20px; 61 | align-items: center; 62 | display: flex; 63 | flex-direction: row; 64 | justify-content: space-between; 65 | 66 | div { 67 | margin-left: ${(props) => (props.taskChildren ? "30px" : 0)}; 68 | display: flex; 69 | flex-direction: row; 70 | align-items: center; 71 | } 72 | `; 73 | 74 | export const Item = styled.p` 75 | padding: 10px; 76 | color: ${(props) => 77 | props.checked ? props.theme.colors.disabledText : props.theme.colors.text}; 78 | font-size: ${(props) => props.theme.fontSizes.small}; 79 | font-family: ${(props) => props.theme.fonts.title}; 80 | font-weight: bold; 81 | text-decoration: ${(props) => (props.checked ? "line-through" : "none")}; 82 | `; 83 | 84 | export const Input = styled.input` 85 | width: 20px; 86 | height: 20px; 87 | border-radius: 5px; 88 | margin-right: 30px; 89 | 90 | input:checked + Item { 91 | color: ${(props) => props.theme.colors.disabledText}; 92 | opacity: 0.5; 93 | text-decoration: line-through; 94 | } 95 | `; 96 | 97 | export const WrapperTextButton = styled.div` 98 | overflow: hidden; 99 | white-space: wrap; 100 | padding: 5px; 101 | p { 102 | width: 100%; 103 | text-align: center; 104 | 105 | @media (max-width: 400px) { 106 | font-size: 12px; 107 | } 108 | 109 | @media (max-width: 500px) and (min-width: 401px) { 110 | font-size: 14px; 111 | } 112 | } 113 | `; 114 | -------------------------------------------------------------------------------- /src/components/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { toast } from "react-toastify"; 4 | import * as S from "./styled"; 5 | import { createToDo, updateToDo } from "../../server/api"; 6 | import { Buttom, Note } from "../../components"; 7 | 8 | interface ModalProps { 9 | value: string; 10 | onClose: () => void; 11 | onCreate: () => void; 12 | idToDo?: string; 13 | } 14 | 15 | const MAX_CHARACTERS = 50; 16 | 17 | function Modal({ value, onClose, onCreate, idToDo }: ModalProps): JSX.Element { 18 | interface toDo { 19 | name: string; 20 | } 21 | 22 | const [newToDo, setNewToDo] = useState({ name: value }); 23 | const [maxAllowedCharactersInput, setMaxAllowedCharactersInput] = 24 | useState(false); 25 | const navigate = useNavigate(); 26 | 27 | const handleChange = (event: ChangeEvent) => { 28 | const { name, value } = event.target; 29 | if (value.length === MAX_CHARACTERS) { 30 | setMaxAllowedCharactersInput(true); 31 | } else { 32 | setMaxAllowedCharactersInput(false); 33 | } 34 | setNewToDo((prevState) => ({ 35 | ...prevState, 36 | [name]: value, 37 | })); 38 | }; 39 | 40 | const createNewToDo = async (): Promise => { 41 | if (idToDo) { 42 | try { 43 | updateToDo(idToDo, newToDo.name); 44 | toast.success("toDo List Editado com sucesso!"); 45 | onCreate(); 46 | onClose(); 47 | } catch (error) { 48 | toast.error("Erro ao editar toDo List!"); 49 | console.error(error); 50 | } 51 | } else { 52 | try { 53 | toast.success("toDo List Criado com sucesso!"); 54 | const response = await createToDo(newToDo); 55 | onCreate(); 56 | onClose(); 57 | navigate(`/todo/${response.id}`); 58 | } catch (error) { 59 | toast.error("Erro ao criar toDo List!"); 60 | console.error(error); 61 | } 62 | } 63 | }; 64 | 65 | return ( 66 | 67 | 68 | 69 | 79 | {maxAllowedCharactersInput && ( 80 | 81 | Limite máximo de caracteres excedido! 82 | 83 | )} 84 | 90 | Salvar 91 | 92 | 93 | 94 | 95 | ); 96 | } 97 | 98 | export default Modal; 99 | -------------------------------------------------------------------------------- /src/views/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useLocation } from "react-router-dom"; 3 | import queryString from "query-string"; 4 | import { Fade } from "react-reveal"; 5 | import Swal from "sweetalert2"; 6 | import { toast } from "react-toastify"; 7 | import "react-toastify/dist/ReactToastify.css"; 8 | import * as S from "./styled"; 9 | import { Buttom, Note, Modal } from "../../components"; 10 | import { getToDos, deleteToDo } from "../../server/api"; 11 | 12 | interface todoList { 13 | id: string; 14 | name: string; 15 | } 16 | 17 | type HomeProps = { 18 | modal?: boolean; 19 | }; 20 | 21 | const Home: React.FC = ({ modal }) => { 22 | const location = useLocation(); 23 | const [todoLists, setTodoLists] = useState([]); 24 | const [showModal, setShowModal] = useState(false); 25 | const [editToDo, setEditToDo] = useState(""); 26 | const [editToDoName, setEditToDoName] = useState(""); 27 | 28 | useEffect(() => { 29 | const queryParams = queryString.parse(location.search); 30 | const value = queryParams.value === "true"; 31 | if (value || modal) { 32 | const hasModal = value || modal; 33 | setShowModal(hasModal); 34 | } 35 | }, [location.search]); 36 | 37 | const fetchToDos = async (): Promise => { 38 | try { 39 | const toDos = await getToDos(); 40 | setTodoLists(toDos); 41 | } catch (error) { 42 | console.error(error); 43 | } 44 | }; 45 | 46 | useEffect(() => { 47 | fetchToDos(); 48 | }, []); 49 | 50 | const closeModalToDo = () => { 51 | setShowModal(false); 52 | clearEditToDo(); 53 | }; 54 | 55 | const onRemove = (id: string) => { 56 | Swal.fire({ 57 | title: "Você deseja deletar este toDo List?", 58 | text: "Lembre-se essa ação não tem volta!", 59 | icon: "warning", 60 | color: "#605951", 61 | background: "#F2E9e6", 62 | iconColor: "#ff6161", 63 | showCancelButton: true, 64 | confirmButtonColor: "#95de90", 65 | cancelButtonColor: "#ff6161", 66 | cancelButtonText: "Cancelar", 67 | confirmButtonText: "OK, deletar toDo list", 68 | }).then((result) => { 69 | if (result.isConfirmed) { 70 | try { 71 | deleteToDo(id); 72 | fetchToDos(); 73 | toast.success("toDo List Deletado com sucesso!"); 74 | } catch (e) { 75 | toast.error("Ocorreu um erro ao deletar toDo List!"); 76 | console.error(e); 77 | } 78 | } 79 | }); 80 | }; 81 | 82 | const onEdit = (id: string) => { 83 | const todo = todoLists.find((todo) => todo.id === id); 84 | setShowModal(true); 85 | setEditToDo(id); 86 | if (todo) { 87 | setEditToDoName(todo.name); 88 | } 89 | }; 90 | 91 | const clearEditToDo = () => { 92 | setEditToDo(""); 93 | setEditToDoName(""); 94 | }; 95 | 96 | const createNewToDo = () => { 97 | clearEditToDo(); 98 | setShowModal(true); 99 | }; 100 | 101 | return ( 102 | <> 103 | 104 | 105 | 106 | ToDo List 107 | 108 | Bem-vindo(a) ao nosso sistema de gerenciamento de tarefas! Sabemos 109 | o quão importante é ser produtivo e organizar suas atividades 110 | diárias de maneira eficiente. Para ajudá-lo(a) nessa jornada, 111 | criamos um recurso que permite a criação de uma nova lista de 112 | tarefas com um simples clique no botão "Criar Novo ToDo List". 113 | 114 | 115 | Caso tenha necessidade também pode editar o titulo desse seu toDo 116 | e também excluir ele caso não seja mais útil. 117 | 118 | 119 | 120 | { 124 | createNewToDo(); 125 | }} 126 | > 127 | Criar Novo ToDo List 128 | 129 | {todoLists.length === 0 ? ( 130 | 131 | 132 | Não existe listas criadas ainda ;( Não deixe o vazio dominar sua 133 | produtividade ! Crie agora mesmo sua primeira lista de tarefas e 134 | dê um passo rumo ao sucesso 135 | 136 | 137 | ) : ( 138 | <> 139 | 140 | {todoLists.map((todo) => ( 141 | onRemove(todo.id)} 145 | onEdit={() => onEdit(todo.id)} 146 | > 147 | {todo.name} 148 | 149 | ))} 150 | 151 | 152 | )} 153 | 154 | {showModal && ( 155 | 161 | )} 162 | 163 | ); 164 | }; 165 | 166 | export default Home; 167 | -------------------------------------------------------------------------------- /src/server/api.tsx: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from "uuid"; 2 | 3 | const generateUniqueId = () => { 4 | return uuidv4(); 5 | }; 6 | 7 | const todosData = localStorage.getItem("todos"); 8 | let todos: toDo[] = todosData ? JSON.parse(todosData) : []; 9 | 10 | interface toDo { 11 | id?: string; 12 | name: string; 13 | permalink?: string; 14 | itens?: Item[]; 15 | } 16 | 17 | interface Item { 18 | id: string; 19 | item: string; 20 | order?: number; 21 | } 22 | 23 | export const createToDo = async (todo: toDo): Promise => { 24 | try { 25 | const newTodo: toDo = { 26 | id: generateUniqueId(), 27 | name: todo.name, 28 | permalink: `/list/${todo.name}`, 29 | }; 30 | 31 | todos.push(newTodo); 32 | localStorage.setItem("todos", JSON.stringify(todos)); 33 | return newTodo; 34 | } catch (error: any) { 35 | console.error(error); 36 | throw new Error("Error creating new toDo", error); 37 | } 38 | }; 39 | 40 | export const getToDos = async (): Promise => { 41 | try { 42 | const todosData = localStorage.getItem("todos"); 43 | const todos = todosData ? JSON.parse(todosData) : []; 44 | return todos; 45 | } catch (error: any) { 46 | console.error(error); 47 | throw new Error("Error getting todos", error); 48 | } 49 | }; 50 | 51 | export const getToDoListById = (id: string): any | undefined => { 52 | const todos = JSON.parse(localStorage.getItem("todos") || "[]"); 53 | const todoList = todos.find((todo: any) => todo.id === id); 54 | return todoList; 55 | }; 56 | 57 | export const deleteToDo = async (id: string): Promise => { 58 | try { 59 | todos = todos.filter((todo) => todo.id !== id); 60 | localStorage.setItem("todos", JSON.stringify(todos)); 61 | } catch (error: any) { 62 | console.error(error); 63 | throw new Error("Error deleting toDo", error); 64 | } 65 | }; 66 | 67 | export const updateToDo = async (id: string, newName: string): Promise => { 68 | try { 69 | const todosData = localStorage.getItem("todos"); 70 | const todos = todosData ? JSON.parse(todosData) : []; 71 | const updatedTodoIndex = todos.findIndex((todo: toDo) => todo.id === id); 72 | if (updatedTodoIndex !== -1) { 73 | todos[updatedTodoIndex].name = newName; 74 | todos[updatedTodoIndex].permalink = `/list/${newName}`; 75 | } else { 76 | throw new Error(`ToDo with id ${id} not found.`); 77 | } 78 | localStorage.setItem("todos", JSON.stringify(todos)); 79 | return todos[updatedTodoIndex]; 80 | } catch (error: any) { 81 | console.error(error); 82 | throw new Error("Error updating toDo", error); 83 | } 84 | }; 85 | 86 | export const addItemToTodo = async ( 87 | todoId: string, 88 | newItem: string 89 | ): Promise => { 90 | try { 91 | const todosData = localStorage.getItem("todos"); 92 | const todos: toDo[] = todosData ? JSON.parse(todosData) : []; 93 | const todoIndex = todos.findIndex((todo) => todo.id === todoId); 94 | 95 | if (todoIndex !== -1) { 96 | const todo = todos[todoIndex]; 97 | 98 | if (!todo.itens) { 99 | todo.itens = []; 100 | } 101 | 102 | const newItemObj = { 103 | id: generateUniqueId(), 104 | item: newItem, 105 | order: 0, 106 | }; 107 | 108 | let maxOrder = 0; 109 | 110 | if (todo.itens.length > 0) { 111 | todo.itens.forEach((item: any) => { 112 | if (item.order > maxOrder) { 113 | maxOrder = item.order; 114 | } 115 | }); 116 | } 117 | 118 | newItemObj.order = maxOrder + 1; 119 | todo.itens.push(newItemObj); 120 | localStorage.setItem("todos", JSON.stringify(todos)); 121 | return newItemObj; 122 | } else { 123 | throw new Error(`ToDo with id ${todoId} not found.`); 124 | } 125 | } catch (error: any) { 126 | console.error(error); 127 | throw new Error("Error adding item to ToDo", error); 128 | } 129 | }; 130 | 131 | export const editItemInTodo = async ( 132 | todoId: string, 133 | itemId: string, 134 | newName: string 135 | ): Promise => { 136 | try { 137 | const todosData = localStorage.getItem("todos"); 138 | const todos: toDo[] = todosData ? JSON.parse(todosData) : []; 139 | 140 | const todoIndex = todos.findIndex((todo) => todo.id === todoId); 141 | 142 | if (todoIndex !== -1) { 143 | const todo = todos[todoIndex]; 144 | 145 | if (todo.itens) { 146 | const itemIndex = todo.itens.findIndex((item) => item.id === itemId); 147 | 148 | if (itemIndex !== -1) { 149 | todo.itens[itemIndex].item = newName; 150 | localStorage.setItem("todos", JSON.stringify(todos)); 151 | return todo.itens[itemIndex]; 152 | } else { 153 | throw new Error( 154 | `Item with id ${itemId} not found in ToDo ${todoId}.` 155 | ); 156 | } 157 | } else { 158 | throw new Error(`ToDo with id ${todoId} does not have any items.`); 159 | } 160 | } else { 161 | throw new Error(`ToDo with id ${todoId} not found.`); 162 | } 163 | } catch (error: any) { 164 | console.error(error); 165 | throw new Error("Error editing item in ToDo", error); 166 | } 167 | }; 168 | 169 | export const removeItemFromTodo = async ( 170 | todoId: string, 171 | itemId: string 172 | ): Promise => { 173 | try { 174 | const todosData = localStorage.getItem("todos"); 175 | const todos: toDo[] = todosData ? JSON.parse(todosData) : []; 176 | 177 | const todoIndex = todos.findIndex((todo) => todo.id === todoId); 178 | 179 | if (todoIndex !== -1) { 180 | const todo = todos[todoIndex]; 181 | 182 | if (todo.itens) { 183 | const itemIndex = todo.itens.findIndex((item) => item.id === itemId); 184 | 185 | if (itemIndex !== -1) { 186 | todo.itens.splice(itemIndex, 1); 187 | localStorage.setItem("todos", JSON.stringify(todos)); 188 | } else { 189 | throw new Error( 190 | `Item with id ${itemId} not found in ToDo ${todoId}.` 191 | ); 192 | } 193 | } else { 194 | throw new Error(`ToDo with id ${todoId} does not have any items.`); 195 | } 196 | } else { 197 | throw new Error(`ToDo with id ${todoId} not found.`); 198 | } 199 | } catch (error: any) { 200 | console.error(error); 201 | throw new Error("Error removing item from ToDo", error); 202 | } 203 | }; 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | [![Contributors][contributors-shield]][contributors-url] 4 | [![Forks][forks-shield]][forks-url] 5 | [![Stargazers][stars-shield]][stars-url] 6 | [![Issues][issues-shield]][issues-url] 7 | [![LinkedIn][linkedin-shield]][linkedin-url] 8 | 9 |
    10 | 11 | Projeto ToDo List   |    12 | Tecnologias Utilizadas   |    13 | Como Instalar o Projeto   |    14 | Como usar   |    15 | Deploy da aplicação 16 | 17 |
    18 | 19 | ## Projeto ToDo List 20 | 21 |
    22 | 23 | #### Desktop 24 | 25 |
    26 | 27 | home sem todo 28 | 29 | home sem todo 30 | 31 | home sem todo 32 | 33 | home sem todo 34 | 35 | home sem todo 36 | 37 |
    38 | 39 | #### Mobile 40 | 41 |
    42 | 43 | mobile home 44 | 45 | mobile home 46 | 47 | mobile home 48 | 49 | mobile home 50 | 51 |
    52 | 53 | O projeto toDo List é um sistema de gerenciamento de tarefas, onde o usuário pode gerenciar suas tarefas em um esquema de toDo List, podendo criar seus toDos e gerenciar cada um deles individualmente. 54 | 55 | Na Pagina Inicial da aplicação o usuário já consegue criar um novo toDo List onde é redirecionado para a página desse toDo podendo então criar suas tarefas e atribuir a ele, podendo editar ou excluir caso tenha necessidade, pode também ir marcando as tarefas como concluidas para uma facil visualização. 56 | 57 | O usuário ainda pode compartilhar via-email cada tarefa com seus amigos para facilitar sua produtividade . 58 | 59 |
    60 |
    61 | 62 | ### Algumas features futuras 63 | 64 |
    65 | 66 | - [ ] Organizar uma task como sub-task de uma task já existente 67 | 68 |
    69 | 70 | - [ ] Mover uma task para fora da task pai, transformando-a em um outra task pai ou uma task de outra task ; 71 | 72 |
    73 | 74 |

    (de volta ao topo)

    75 | 76 | ### Tecnologias Utilizadas 77 | 78 |
    79 | 80 | [![React][React]][React-url] 81 | [![Typescript][Typescript]][Typescript-url] 82 | [![Styled_Components][Styled_Components]][Styled_Components-url] 83 | [![Polished][Polished]][Polished-url] 84 | [![React_Reveal][React_Reveal]][React_Reveal-url] 85 | [![React_Toastify][React_Toastify]][React_Toastify-url] 86 | [![Sweet_Alert2][Sweet_Alert2]][Sweet_Alert2-url] 87 | 88 |
    89 | 90 | ### Utilitários 91 | 92 |
    93 | 94 | [![Vercel][Vercel]][Vercel-url] 95 | 96 |

    (de volta ao topo)

    97 | 98 | ### Como Instalar o Projeto 99 | 100 |
    101 | 102 | ```sh 103 | yarn 104 | ``` 105 | 106 | Após baixar todas as Dependências do Projeto dentro da pasta Raiz, inicie o Servidor com o Comando: 107 | 108 | ```sh 109 | yarn start 110 | ``` 111 | 112 | ### Como usar 113 | 114 |
    115 | 116 | Para Inicializar o Projeto 117 | Abrir [http://localhost:3000](http://localhost:3000) vizualizar no Navegador. 118 | 119 |
    120 | 121 | ### Deploy da aplicação 122 | 123 |
    124 | 125 | [![Deploy][Deploy]][Deploy-url] 126 | 127 |
    128 | 129 | ### 🚀 Let's code! 🚀 130 | 131 | 132 | 133 | 134 | [contributors-shield]: https://img.shields.io/github/contributors/HMontarroyos/to_do_list.svg?style=for-the-badge 135 | [contributors-url]: https://github.com/HMontarroyos/to_do_list/graphs/contributors 136 | [forks-shield]: https://img.shields.io/github/forks/HMontarroyos/to_do_list.svg?style=for-the-badge 137 | [forks-url]: https://github.com/HMontarroyos/to_do_list/fork 138 | [stars-shield]: https://img.shields.io/github/stars/HMontarroyos/to_do_list.svg?style=for-the-badge 139 | [stars-url]: https://github.com/HMontarroyos/to_do_list/stargazers 140 | [issues-shield]: https://img.shields.io/github/issues/HMontarroyos/to_do_list.svg?style=for-the-badge 141 | [issues-url]: https://github.com/HMontarroyos/to_do_list/issues 142 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 143 | [linkedin-url]: https://www.linkedin.com/in/hebertmontarroyos-developer/ 144 | [React]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB 145 | [React-url]: https://pt-br.react.dev/ 146 | [Typescript]: https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white 147 | [Typescript-url]: https://www.typescriptlang.org/ 148 | [Styled_Components]: https://img.shields.io/badge/styled--components-DB7093?style=for-the-badge&logo=styled-components&logoColor=white 149 | [Styled_Components-url]: https://styled-components.com/ 150 | [Polished]: https://img.shields.io/badge/Polished-107C10?style=for-the-badge 151 | [Polished-url]: https://polished.js.org/ 152 | [React_Reveal]: https://img.shields.io/badge/React%20Reveal-593D88?style=for-the-badge 153 | [React_Reveal-url]: https://www.react-reveal.com/ 154 | [React_Toastify]: https://img.shields.io/badge/React%20Toastify-092E20?style=for-the-badge 155 | [React_Toastify-url]: https://fkhadra.github.io/react-toastify/introduction 156 | [Sweet_Alert2]: https://img.shields.io/badge/Sweet%20Alert%202-FA5C5C?style=for-the-badge 157 | [Sweet_Alert2-url]: https://sweetalert2.github.io/ 158 | [Vercel]: https://img.shields.io/badge/Vercel-000000?style=for-the-badge&logo=vercel&logoColor=white 159 | [Vercel-url]: https://vercel.com/ 160 | [Deploy]: https://img.shields.io/badge/Vercel-000000?style=for-the-badge&logo=vercel&logoColor=white 161 | [Deploy-url]: https://to-do-list-mu-brown.vercel.app/ 162 | -------------------------------------------------------------------------------- /src/views/ToDoListId/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useNavigate, useParams } from "react-router-dom"; 3 | import queryString from "query-string"; 4 | import Swal from "sweetalert2"; 5 | import { toast } from "react-toastify"; 6 | import * as S from "./styled"; 7 | import { 8 | getToDos, 9 | getToDoListById, 10 | deleteToDo, 11 | addItemToTodo, 12 | editItemInTodo, 13 | removeItemFromTodo, 14 | } from "../../server/api"; 15 | import { Buttom, TextLink, NotebookSheet } from "../../components"; 16 | 17 | const ToDoListId: React.FC = () => { 18 | const navigate = useNavigate(); 19 | const [toDoLists, setToDoLists] = useState([]); 20 | const [toDo, setToDo] = useState(); 21 | const { id } = useParams<{ id: string }>(); 22 | 23 | const fetchToDo = async (): Promise => { 24 | try { 25 | const todos = await getToDos(); 26 | setToDoLists(todos); 27 | const hasId = todos.some((toDo: any) => toDo.id === id); 28 | if (id && hasId) { 29 | const toDoById = await getToDoListById(id); 30 | setToDo(toDoById); 31 | } else { 32 | const queryParams = queryString.stringify({ value: true }); 33 | navigate(`/?${queryParams}`); 34 | } 35 | } catch (error) { 36 | console.error(error); 37 | } 38 | }; 39 | 40 | useEffect(() => { 41 | fetchToDo(); 42 | }, []); 43 | 44 | const handleEmail = () => { 45 | const currentURL = window.location.href; 46 | const emailSubject = 47 | "Venha conferir minha Lista de Tarefas e crie a sua! ✅"; 48 | const emailBody = `Maximize sua produtividade com minha incrível Lista de Tarefas personalizada! Clique no link abaixo para conhecer minha Lista de Tarefas e fique a vontade caso queira adicionar novas tarefas ou excluir e atualizar alguma existente, aproveite e crie a sua também 😀\n\n${currentURL}`; 49 | 50 | const mailtoLink = `mailto:?subject=${encodeURIComponent( 51 | emailSubject 52 | )}&body=${encodeURIComponent(emailBody)}`; 53 | 54 | window.location.href = mailtoLink; 55 | 56 | window.open(mailtoLink); 57 | }; 58 | 59 | const deleteToDoList = () => { 60 | Swal.fire({ 61 | title: 62 | "Você deseja deletar este toDo List? Isso irá deletar todos seus itens atribuidos a este toDo.", 63 | text: "Lembre-se essa ação não tem volta, após isso você sera redirecionado para a Home onde poderá criar novos toDo List", 64 | icon: "warning", 65 | color: "#605951", 66 | background: "#F2E9e6", 67 | iconColor: "#ff6161", 68 | showCancelButton: true, 69 | confirmButtonColor: "#95de90", 70 | cancelButtonColor: "#ff6161", 71 | cancelButtonText: "Cancelar", 72 | confirmButtonText: "OK, deletar toDo list", 73 | }).then((result) => { 74 | if (result.isConfirmed) { 75 | try { 76 | id && deleteToDo(id); 77 | navigate(`/`); 78 | toast.success("toDo List Deletado com sucesso!"); 79 | } catch (e) { 80 | toast.error("Ocorreu um erro ao deletar toDo List!"); 81 | console.error(e); 82 | } 83 | } 84 | }); 85 | }; 86 | 87 | const createToDoList = async () => { 88 | Swal.fire({ 89 | text: "Digite o nome da tarefa que deseja criar.", 90 | color: "#605951", 91 | input: "text", 92 | inputAttributes: { 93 | autocapitalize: "off", 94 | maxlength: "50", 95 | }, 96 | background: "#F2E9e6", 97 | showCancelButton: true, 98 | confirmButtonColor: "#95de90", 99 | cancelButtonColor: "#ff6161", 100 | cancelButtonText: "Cancelar", 101 | confirmButtonText: "OK, criar nova tarefa", 102 | }).then(async (result) => { 103 | if (result.isConfirmed) { 104 | try { 105 | id && (await addItemToTodo(id, result.value)); 106 | fetchToDo(); 107 | toast.success("Tarefa Criada com sucesso !"); 108 | } catch (e) { 109 | toast.error("Ocorreu um erro ao criar nova tarefa"); 110 | console.error(e); 111 | } 112 | } 113 | }); 114 | }; 115 | 116 | const editTask = (idTask: string, nameTask: string) => { 117 | Swal.fire({ 118 | icon: "info", 119 | iconColor: "#61a6ab", 120 | text: "Digite o novo nome da tarefa para ser editado", 121 | color: "#605951", 122 | input: "text", 123 | inputValue: nameTask, 124 | inputAttributes: { 125 | autocapitalize: "off", 126 | maxlength: "50", 127 | }, 128 | background: "#F2E9e6", 129 | showCancelButton: true, 130 | confirmButtonColor: "#95de90", 131 | cancelButtonColor: "#ff6161", 132 | cancelButtonText: "Cancelar", 133 | confirmButtonText: "OK, editar e salvar nova tarefa", 134 | }).then(async (result) => { 135 | if (result.isConfirmed) { 136 | try { 137 | id && (await editItemInTodo(id, idTask, result.value)); 138 | fetchToDo(); 139 | toast.success("Tarefa Editada com sucesso !"); 140 | } catch (e) { 141 | toast.error("Ocorreu um erro ao editar sua tarefa"); 142 | console.error(e); 143 | } 144 | } 145 | }); 146 | }; 147 | 148 | const deleteTask = (idTask: string) => { 149 | Swal.fire({ 150 | title: "Você deseja deletar esta tarefa ?", 151 | text: "Lembre-se essa ação não tem volta!", 152 | icon: "warning", 153 | color: "#605951", 154 | background: "#F2E9e6", 155 | iconColor: "#ff6161", 156 | showCancelButton: true, 157 | confirmButtonColor: "#95de90", 158 | cancelButtonColor: "#ff6161", 159 | cancelButtonText: "Cancelar", 160 | confirmButtonText: "OK, deletar tarefa", 161 | }).then((result) => { 162 | if (result.isConfirmed) { 163 | try { 164 | id && removeItemFromTodo(id, idTask); 165 | fetchToDo(); 166 | toast.success("Tarefa deletada com sucesso!"); 167 | } catch (e) { 168 | toast.error("Ocorreu um erro ao deletar tarefa!"); 169 | console.error(e); 170 | } 171 | } 172 | }); 173 | }; 174 | 175 | const handleTaskChange = async ( 176 | e: React.ChangeEvent, 177 | taskId: number 178 | ) => { 179 | const task = toDo.itens[taskId]; 180 | if (toDo && toDo.itens && task) { 181 | const updatedToDoTask = [...toDo.itens]; 182 | if (!updatedToDoTask[taskId].hasOwnProperty("checked")) { 183 | updatedToDoTask[taskId].checked = false; 184 | } 185 | updatedToDoTask[taskId].checked = e.target.checked; 186 | setToDo( 187 | (prevToDo: any) => ({ ...prevToDo, itens: updatedToDoTask } as any) 188 | ); 189 | if (e.target.checked) { 190 | Swal.fire({ 191 | title: 192 | "Você marcou sua tarefa como finalizada, deseja excluir ela ?", 193 | text: " Caso não exclua ao sair da pagina terá que refazer o processo de checked manualmente", 194 | icon: "question", 195 | color: "#605951", 196 | background: "#F2E9e6", 197 | iconColor: "#61a6ab", 198 | showCancelButton: true, 199 | confirmButtonColor: "#95de90", 200 | cancelButtonColor: "#ff6161", 201 | cancelButtonText: "Cancelar", 202 | confirmButtonText: "OK, desejo prosseguir e deletar a tarefa", 203 | }).then((result) => { 204 | if (result.isConfirmed) { 205 | try { 206 | id && removeItemFromTodo(id, task.id); 207 | fetchToDo(); 208 | toast.success("Tarefa deletada com sucesso!"); 209 | } catch (e) { 210 | toast.error("Ocorreu um erro ao deletar tarefa!"); 211 | console.error(e); 212 | } 213 | } 214 | }); 215 | } 216 | } else { 217 | return; 218 | } 219 | }; 220 | 221 | return ( 222 | <> 223 | {toDo && ( 224 | <> 225 | 226 | {toDo?.name && ( 227 | 228 | {toDo.name} 229 | 230 | )} 231 | 232 | 233 |

    Compartilhar via-email

    234 |
    235 |
    236 |
    237 | 238 |
    239 | 245 | 246 |

    Deletar lista

    247 |
    248 |
    249 | 250 | 251 |

    Novo item

    252 |
    253 |
    254 |
    255 |
    256 | {toDo?.itens && toDo.itens.length > 0 ? ( 257 | 258 | {toDo.itens.map((_item: any, idx: number) => ( 259 | <> 260 | 262 |
    263 | handleTaskChange(e, idx)} 267 | /> 268 | 269 | {_item.item} 270 | 271 |
    272 | 273 | editTask(_item.id, _item.item)} 277 | /> 278 | deleteTask(_item.id)} 282 | /> 283 | {/* {_item.item && ( 284 | console.log(taskChildren={toDo.itens[idx].checked)} 288 | /> 289 | )} */} 290 | 291 |
    292 | 293 | ))} 294 |
    295 | ) : ( 296 | 297 | Não existe itens atribuidos a este toDo, para criar uma nova 298 | tarefa clique no botão "Novo item" 299 | 300 | )} 301 | 302 | )} 303 | 304 | ); 305 | }; 306 | 307 | export default ToDoListId; 308 | --------------------------------------------------------------------------------