└── 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 |
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 |
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 |
--------------------------------------------------------------------------------