└── projeto ├── .gitignore ├── README.md ├── package.json ├── public └── index.html ├── src ├── assets │ └── img │ │ └── check-mark.svg ├── common │ └── utils │ │ └── date.ts ├── components │ ├── Cronometro │ │ ├── Relogio │ │ │ ├── index.tsx │ │ │ └── style.module.scss │ │ ├── index.tsx │ │ └── style.module.scss │ ├── Formulario │ │ ├── index.tsx │ │ └── style.module.scss │ └── Lista │ │ ├── Item │ │ ├── index.tsx │ │ └── style.module.scss │ │ ├── index.tsx │ │ └── style.module.scss ├── index.scss ├── index.tsx ├── pages │ ├── App.tsx │ └── style.module.scss ├── react-app-env.d.ts └── types │ └── Tarefa.tsx ├── tsconfig.json └── yarn.lock /projeto/.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 | -------------------------------------------------------------------------------- /projeto/README.md: -------------------------------------------------------------------------------- 1 | # Como rodar o projeto 2 | 3 | ### `yarn start` 4 | 5 | # reset 6 | 7 | * box-sizing: border-box; 8 | ### button -------------------------------------------------------------------------------- /projeto/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "projeto", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^17.0.0", 12 | "@types/react-dom": "^17.0.0", 13 | "node-sass": "^5.0.0", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "typescript": "^4.1.2", 18 | "web-vitals": "^1.0.1" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "devDependencies": { 45 | "typescript-plugin-css-modules": "^3.4.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /projeto/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | Alura Studies 12 | 13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /projeto/src/assets/img/check-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /projeto/src/common/utils/date.ts: -------------------------------------------------------------------------------- 1 | export const delay = (ms = 1000) => new Promise((resolve, _) => { 2 | setTimeout(resolve, ms); 3 | }) 4 | 5 | export const timeToSeconds = (defaultTime: string) => { 6 | const [hourString = '0', minuteString = '0', secondString = '0'] = defaultTime.split(':') 7 | const timeSeconds = (parseInt(hourString) * 3600) + (parseInt(minuteString) * 60) + parseInt(secondString) 8 | return timeSeconds 9 | } -------------------------------------------------------------------------------- /projeto/src/components/Cronometro/Relogio/index.tsx: -------------------------------------------------------------------------------- 1 | import styles from './style.module.scss' 2 | 3 | interface IRelogio { 4 | totalSegundos: number 5 | } 6 | 7 | export const Relogio:React.FC = props => { 8 | 9 | const minutos = ('0'+ Math.floor(props.totalSegundos/60)).slice(-2); 10 | const segundosRestantes = ('0'+ props.totalSegundos % 60).slice(-2); 11 | const [minutoEsquerdo, minutoDireito] = minutos.split(''); 12 | const [segundoEsquerdo, segundoDireito] = segundosRestantes.split(''); 13 | 14 | return ( 15 | <> 16 | {minutoEsquerdo} 17 | {minutoDireito} 18 | : 19 | {segundoEsquerdo} 20 | {segundoDireito} 21 | 22 | ); 23 | } -------------------------------------------------------------------------------- /projeto/src/components/Cronometro/Relogio/style.module.scss: -------------------------------------------------------------------------------- 1 | .relogioNumero { 2 | background-color: #5D677C; 3 | box-shadow: 2px 2px 4px #2B2B2B inset; 4 | height: 3.6rem; 5 | width: 3rem; 6 | padding: 8px 4px; 7 | border-radius: 10px; 8 | 9 | @media screen and (min-width:1280px) { 10 | height: 10.8rem; 11 | width: 9rem; 12 | } 13 | } 14 | 15 | .relogioDivisao { 16 | height: 4.2rem; 17 | 18 | @media screen and (min-width:1280px) { 19 | height: 12.6rem; 20 | } 21 | } -------------------------------------------------------------------------------- /projeto/src/components/Cronometro/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Relogio } from './Relogio/index' 3 | import { delay } from '../../common/utils/date' 4 | import styles from './style.module.scss' 5 | 6 | interface ICronometro { 7 | tempo: number, 8 | tempoFinalizado:()=>void 9 | } 10 | 11 | export const Cronometro:React.FC = props => { 12 | const [rodando, setRodando] = useState(false); 13 | const [tempoRestante, setTempoRestante] = useState(0); 14 | 15 | useEffect(() => { 16 | setTempoRestante(props.tempo) 17 | }, [props.tempo]) 18 | 19 | async function iniciaCronometro() { 20 | setRodando(true); 21 | let contador = tempoRestante 22 | 23 | while (contador > 0){ 24 | await delay() 25 | setTempoRestante(tempoRestante => tempoRestante - 1); 26 | contador-- 27 | } 28 | pararCronometro() 29 | } 30 | 31 | function pararCronometro(){ 32 | setRodando(false); 33 | props.tempoFinalizado(); 34 | } 35 | 36 | return ( 37 |
38 |

Escolha um card e inicie o cronômetro

39 |
40 | 41 |
42 | 43 |
44 | ) 45 | } -------------------------------------------------------------------------------- /projeto/src/components/Cronometro/style.module.scss: -------------------------------------------------------------------------------- 1 | .cronometro { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | grid-area: cronometro; 6 | 7 | .relogioWrapper { 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | width: 100%; 12 | box-sizing: border-box; 13 | border-radius: 10px; 14 | padding: 16px 12px; 15 | margin-bottom: 24px; 16 | box-shadow: 2px 4px 4px #0000009F; 17 | font-size: 5rem; 18 | background-color: #7687A1; 19 | } 20 | 21 | .titulo { 22 | font-size: 2rem; 23 | } 24 | 25 | button { 26 | width: 150px; 27 | padding: 16px; 28 | color: #272626; 29 | font-size: 1.25rem; 30 | background-color: #88bcd1; 31 | border-radius: 10px; 32 | box-shadow: 2px 4px 4px #0000009F; 33 | cursor: pointer; 34 | 35 | &:active { 36 | background-color: #7CA6B7; 37 | box-shadow: 2px 2px 4px #0000009F inset; 38 | cursor: auto; 39 | } 40 | } 41 | 42 | @media screen and (min-width:1280px) { 43 | 44 | .relogioWrapper{ 45 | font-size: 15rem; 46 | } 47 | 48 | p { 49 | font-size: 2rem; 50 | } 51 | 52 | button { 53 | grid-column-start: span 2; 54 | justify-self: center; 55 | width: 200px; 56 | font-size: 2.25rem; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /projeto/src/components/Formulario/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useState } from 'react'; 3 | import { ITarefa } from '../../types/Tarefa'; 4 | import styles from './style.module.scss' 5 | 6 | interface IFormulario { 7 | enviarTarefa: (data: ITarefa) => void; 8 | } 9 | 10 | let contador = 0 11 | 12 | export const Form:React.FC = props => { 13 | 14 | const [tarefa, setTarefa] = useState('') 15 | const [tempo, setTempo] = useState('00:00') 16 | 17 | function salvarTarefa(event: React.FormEvent){ 18 | event.preventDefault() 19 | props.enviarTarefa({tarefa, tempo, id: contador}) 20 | setTarefa('') 21 | setTempo('00:00') 22 | contador++ 23 | } 24 | 25 | return ( 26 |
27 |
28 | 29 | { setTarefa(evento.target.value) }} 36 | required/> 37 |
38 |
39 | 40 | { setTempo(evento.target.value) }} 49 | required/> 50 |
51 | 52 |
53 | ) 54 | } -------------------------------------------------------------------------------- /projeto/src/components/Formulario/style.module.scss: -------------------------------------------------------------------------------- 1 | .novaTarefa { 2 | display:flex; 3 | flex-direction: column; 4 | grid-area: nova-tarefa; 5 | background-color: #7687A1; 6 | border-radius: 10px; 7 | box-shadow: 2px 4px 4px #0000009F; 8 | padding: 12px; 9 | 10 | .inputContainer { 11 | display: flex; 12 | flex-direction: column; 13 | width: 100%; 14 | margin-bottom: 16px; 15 | 16 | label { 17 | margin-bottom: 8px; 18 | font-size: 1.25rem; 19 | } 20 | 21 | input { 22 | width: 100%; 23 | padding: 8px 12px 4px; 24 | box-sizing: border-box; 25 | border: unset; 26 | border-radius: 5px; 27 | background-color: #5D677C; 28 | box-shadow: 0px 2px 4px #2D2B2B9F inset; 29 | 30 | &::placeholder { 31 | color: #BFBFBF; 32 | } 33 | } 34 | } 35 | 36 | button { 37 | align-self: center; 38 | width: 150px; 39 | padding: 16px; 40 | color: #272626; 41 | font-size: 1.25rem; 42 | background-color: #88bcd1; 43 | border-radius: 10px; 44 | box-shadow: 2px 4px 4px #0000009F; 45 | cursor: pointer; 46 | 47 | &:active { 48 | background-color: #7CA6B7; 49 | box-shadow: 2px 2px 4px #0000009F inset; 50 | } 51 | } 52 | 53 | @media screen and (min-width: 1280px) { 54 | flex-direction: row; 55 | flex-wrap: wrap; 56 | justify-content: space-around; 57 | font-size: 2.25rem; 58 | padding: 24px; 59 | box-sizing: border-box; 60 | 61 | .inputContainer { 62 | width: calc(60% - 12px); 63 | 64 | &:last-of-type { 65 | width: 40%; 66 | } 67 | 68 | label { 69 | font-size: 2rem; 70 | } 71 | 72 | input { 73 | height: 100%; 74 | font-size: 1.75rem; 75 | } 76 | } 77 | 78 | button { 79 | grid-column-start: span 2; 80 | justify-self: center; 81 | width: 200px; 82 | font-size: 2.25rem; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /projeto/src/components/Lista/Item/index.tsx: -------------------------------------------------------------------------------- 1 | import { ITarefa } from '../../../types/Tarefa' 2 | import styles from './style.module.scss' 3 | 4 | interface IItem { 5 | item: ITarefa, 6 | index: number, 7 | abreItem: (item: ITarefa, index: number) => void 8 | } 9 | 10 | export const Item:React.FC = props => { 11 | return ( 12 |
  • !props.item.completado && props.abreItem(props.item, props.index)}> 15 |

    {props.item.tarefa}

    16 | {props.item.tempo} 17 | {props.item.completado && } 18 |
  • 19 | ) 20 | } -------------------------------------------------------------------------------- /projeto/src/components/Lista/Item/style.module.scss: -------------------------------------------------------------------------------- 1 | .item { 2 | background-color: #4D4D4D; 3 | border-radius: 8px; 4 | box-shadow: 2px 4px 4px #0000009F; 5 | padding: 12px; 6 | margin-bottom: 8px; 7 | position: relative; 8 | cursor: pointer; 9 | 10 | h3 { 11 | margin-bottom: 8px; 12 | word-break: break-all; 13 | } 14 | 15 | span { 16 | color: #D0D0D0; 17 | } 18 | 19 | @media screen and (min-width:1280px) { 20 | font-size: 1.8rem; 21 | } 22 | } 23 | 24 | .itemSelecionado { 25 | background-color: #292929; 26 | box-shadow: 2px 4px 4px #0000009F inset; 27 | } 28 | 29 | .itemCompletado { 30 | background-color: #566F42; 31 | cursor: auto; 32 | 33 | .concluido { 34 | display: block; 35 | background-image: url('../../../assets/img/check-mark.svg'); 36 | background-repeat: no-repeat; 37 | background-size: 38px 38px; 38 | position: absolute; 39 | top: 50%; 40 | right: 12px; 41 | transform: translateY(-50%); 42 | width: 42px; 43 | height: 43px; 44 | } 45 | } -------------------------------------------------------------------------------- /projeto/src/components/Lista/index.tsx: -------------------------------------------------------------------------------- 1 | import {Item} from './Item/index' 2 | import { ITarefa } from '../../types/Tarefa' 3 | import styles from './style.module.scss' 4 | 5 | interface ILista { 6 | lista: ITarefa[], 7 | abreItem: (item: ITarefa, index: number) => void 8 | } 9 | 10 | export const Lista:React.FC = props => { 11 | return ( 12 | 26 | ) 27 | } -------------------------------------------------------------------------------- /projeto/src/components/Lista/style.module.scss: -------------------------------------------------------------------------------- 1 | .listaTarefas { 2 | grid-area: tarefas; 3 | height: 100%; 4 | 5 | h2 { 6 | font-size: 1.25rem; 7 | margin-bottom: 12px; 8 | } 9 | 10 | ul { 11 | max-height: 350px; 12 | overflow-y: scroll; 13 | scrollbar-width: thin; 14 | } 15 | 16 | @media screen and (min-width:1280px) { 17 | 18 | h2{ 19 | text-align: center; 20 | font-size: 2.25rem; 21 | margin-bottom: 24px; 22 | } 23 | 24 | ul { 25 | overflow: auto; 26 | max-height: 500px; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /projeto/src/index.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | 45 | 46 | // Aqui começa customização do reset 47 | input { 48 | font-family: inherit; 49 | font-size: inherit; 50 | font-weight: inherit; 51 | color: inherit; 52 | } 53 | 54 | button { 55 | border: unset; 56 | background-color: unset; 57 | font-family: inherit; 58 | font-size: inherit; 59 | font-weight: inherit; 60 | color: inherit; 61 | } 62 | 63 | @import url('https://fonts.googleapis.com/css2?family=Manjari&display=swap'); 64 | 65 | body { 66 | display: flex; 67 | justify-content: center; 68 | font-family: 'Manjari', sans-serif; 69 | color: #F0F0F0; 70 | background-color: #4C4C4C; 71 | height: 100%; 72 | width: 100%; 73 | padding: 16px; 74 | box-sizing: border-box; 75 | } 76 | 77 | #root { 78 | width: 100%; 79 | } 80 | 81 | ::-webkit-scrollbar { 82 | width: 8px; 83 | } 84 | 85 | ::-webkit-scrollbar-track { 86 | background: unset; 87 | } 88 | 89 | ::-webkit-scrollbar-thumb { 90 | border-radius: 4px; 91 | background: #888; 92 | } 93 | 94 | ::-webkit-scrollbar-thumb:hover { 95 | background: #555; 96 | } -------------------------------------------------------------------------------- /projeto/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.scss'; 4 | import { App } from './pages/App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); -------------------------------------------------------------------------------- /projeto/src/pages/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Form } from '../components/Formulario/index' 3 | import { Lista } from '../components/Lista/index' 4 | import { Cronometro } from '../components/Cronometro/index' 5 | import { ITarefa } from '../types/Tarefa' 6 | import { timeToSeconds } from 'common/utils/date' 7 | import styles from "./style.module.scss" 8 | 9 | export const App = () => { 10 | 11 | const [lista, setLista] = useState([]) 12 | const [selecionado, setSelecionado] = useState() 13 | const [tempo, setTempo] = useState(0) 14 | 15 | function enviarTarefa(data: ITarefa) { 16 | setLista([...lista, { ...data, completado: false, selecionado: false }]) 17 | } 18 | 19 | function selecionaItem(item: ITarefa, index: number) { 20 | item.selecionado = true 21 | setSelecionado(item) 22 | setLista((listaAnterior: ITarefa[]) => 23 | listaAnterior.map((itemAnterior: ITarefa, indexAnterior: number) => ( 24 | indexAnterior === index ? { ...itemAnterior, selecionado: true } : itemAnterior 25 | )) 26 | ) 27 | const segundos = timeToSeconds(item.tempo) 28 | setTempo(segundos) 29 | } 30 | 31 | function tarefaFinalizada() { 32 | if (selecionado) { 33 | const item = selecionado 34 | setLista((listaAnterior: ITarefa[]) => 35 | listaAnterior.map((itemAnterior: ITarefa) => ( 36 | itemAnterior.id === item.id ? { ...itemAnterior, selecionado: false, completado: true } : itemAnterior 37 | ))) 38 | setTempo(0) 39 | } 40 | } 41 | 42 | return ( 43 |
    44 |
    45 | 46 | 47 |
    48 | ); 49 | } -------------------------------------------------------------------------------- /projeto/src/pages/style.module.scss: -------------------------------------------------------------------------------- 1 | .AppStyle { 2 | display: grid; 3 | grid-template-rows: min-content min-content auto; 4 | grid-template-areas: 5 | "nova-tarefa" 6 | "cronometro" 7 | "tarefas" 8 | ; 9 | row-gap: 24px; 10 | min-width: 320px; 11 | min-height: calc(100vh - 32px); 12 | width: 100%; 13 | padding: 32px; 14 | box-sizing: border-box; 15 | border-radius: 10px; 16 | background-color: #171717; 17 | 18 | @media screen and (min-width:1280px) { 19 | grid-template-areas: 20 | "nova-tarefa tarefas" 21 | "cronometro tarefas" 22 | ; 23 | column-gap: 64px; 24 | grid-template-rows: min-content min-content; 25 | grid-template-columns: 750px 300px; 26 | justify-content: center; 27 | align-content: center; 28 | padding: 64px; 29 | } 30 | } -------------------------------------------------------------------------------- /projeto/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /projeto/src/types/Tarefa.tsx: -------------------------------------------------------------------------------- 1 | export interface ITarefa { 2 | id: number, 3 | tempo: string, 4 | tarefa: string, 5 | selecionado?: boolean; 6 | completado?: boolean, 7 | } -------------------------------------------------------------------------------- /projeto/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "baseUrl": "src", 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "plugins": [{ "name": "typescript-plugin-css-modules" }] 24 | }, 25 | "include": [ 26 | "src" 27 | ] 28 | } 29 | --------------------------------------------------------------------------------