├── .github ├── devfinances.png └── logo.svg ├── LICENSE.md ├── README.md ├── assets ├── expense.svg ├── income.svg ├── logo.svg ├── minus.svg ├── plus.svg └── total.svg ├── index.html ├── scripts.js └── style.css /.github/devfinances.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rocketseat-education/maratona-discover-01/4ed5114e6b41e2ee4a7ea64f800a9c2f1864426e/.github/devfinances.png -------------------------------------------------------------------------------- /.github/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Rocketseat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | dev.finances 3 |

4 | 5 |

6 | Tecnologias   |    7 | Projeto   |    8 | Layout   |    9 | Licença 10 |

11 | 12 |

13 | PRs welcome! 14 | 15 | License 16 |

17 | 18 |
19 | 20 |

21 | dev.finances 22 |

23 | 24 | ## 🚀 Tecnologias 25 | 26 | Esse projeto foi desenvolvido com as seguintes tecnologias: 27 | 28 | - HTML 29 | - CSS 30 | - JavaScript 31 | 32 | ## 💻 Projeto 33 | 34 | O dev.finances é uma aplicação de controle financeiro, onde é possível cadastrar e excluir transações e ver o saldo de entrada e saída 💰 35 | 36 | ## 🔖 Layout 37 | 38 | Você pode visualizar o layout do projeto através [desse link](https://www.figma.com/file/7Vu9DzUaCZIV4nibzkjgB4/dev.finance%24-Maratona-Discover). É necessário ter conta no [Figma](https://figma.com) para acessá-lo. 39 | 40 | ## :memo: Licença 41 | 42 | Esse projeto está sob a licença MIT. Veja o arquivo [LICENSE](LICENSE.md) para mais detalhes. 43 | 44 | --- 45 | 46 | Feito com ♥ by Rocketseat :wave: [Participe da nossa comunidade!](https://discordapp.com/invite/gCRAFhc) 47 | 48 | 49 | 50 |
51 |
52 | 53 |

54 | 55 | banner 56 | 57 |

58 | 59 | 60 | -------------------------------------------------------------------------------- /assets/expense.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/income.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /assets/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/total.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | dev.finance$ 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | Logo Dev Finance 15 |
16 | 17 |
18 |
19 |

Balanço

20 | 21 |
22 |

23 | 24 | Entradas 25 | 26 | Image de entradas 27 |

28 |

R$ 0,00

29 |
30 | 31 |
32 |

33 | 34 | Saídas 35 | 36 | Image de saídas 37 |

38 |

R$ 0,00

39 |
40 | 41 |
42 |

43 | 44 | Total 45 | 46 | Image de total 47 |

48 |

R$ 0,00

49 |
50 | 51 |
52 | 53 |
54 |

Transações

55 | 56 | + Nova Transação 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
DescriçãoValorData
72 |
73 |
74 | 75 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /scripts.js: -------------------------------------------------------------------------------- 1 | const Modal = { 2 | open(){ 3 | // Abrir modal 4 | // Adicionar a class active ao modal 5 | document 6 | .querySelector('.modal-overlay') 7 | .classList 8 | .add('active') 9 | 10 | }, 11 | close(){ 12 | // fechar o modal 13 | // remover a class active do modal 14 | document 15 | .querySelector('.modal-overlay') 16 | .classList 17 | .remove('active') 18 | } 19 | } 20 | 21 | const Storage = { 22 | get() { 23 | return JSON.parse(localStorage.getItem("dev.finances:transactions")) || [] 24 | }, 25 | 26 | set(transactions) { 27 | localStorage.setItem("dev.finances:transactions", JSON.stringify(transactions)) 28 | } 29 | } 30 | 31 | const Transaction = { 32 | all: Storage.get(), 33 | 34 | add(transaction){ 35 | Transaction.all.push(transaction) 36 | 37 | App.reload() 38 | }, 39 | 40 | remove(index) { 41 | Transaction.all.splice(index, 1) 42 | 43 | App.reload() 44 | }, 45 | 46 | incomes() { 47 | let income = 0; 48 | Transaction.all.forEach(transaction => { 49 | if( transaction.amount > 0 ) { 50 | income += transaction.amount; 51 | } 52 | }) 53 | return income; 54 | }, 55 | 56 | expenses() { 57 | let expense = 0; 58 | Transaction.all.forEach(transaction => { 59 | if( transaction.amount < 0 ) { 60 | expense += transaction.amount; 61 | } 62 | }) 63 | return expense; 64 | }, 65 | 66 | total() { 67 | return Transaction.incomes() + Transaction.expenses(); 68 | } 69 | } 70 | 71 | const DOM = { 72 | transactionsContainer: document.querySelector('#data-table tbody'), 73 | 74 | addTransaction(transaction, index) { 75 | const tr = document.createElement('tr') 76 | tr.innerHTML = DOM.innerHTMLTransaction(transaction, index) 77 | tr.dataset.index = index 78 | 79 | DOM.transactionsContainer.appendChild(tr) 80 | }, 81 | 82 | innerHTMLTransaction(transaction, index) { 83 | const CSSclass = transaction.amount > 0 ? "income" : "expense" 84 | 85 | const amount = Utils.formatCurrency(transaction.amount) 86 | 87 | const html = ` 88 | ${transaction.description} 89 | ${amount} 90 | ${transaction.date} 91 | 92 | Remover transação 93 | 94 | ` 95 | 96 | return html 97 | }, 98 | 99 | updateBalance() { 100 | document 101 | .getElementById('incomeDisplay') 102 | .innerHTML = Utils.formatCurrency(Transaction.incomes()) 103 | document 104 | .getElementById('expenseDisplay') 105 | .innerHTML = Utils.formatCurrency(Transaction.expenses()) 106 | document 107 | .getElementById('totalDisplay') 108 | .innerHTML = Utils.formatCurrency(Transaction.total()) 109 | }, 110 | 111 | clearTransactions() { 112 | DOM.transactionsContainer.innerHTML = "" 113 | } 114 | } 115 | 116 | const Utils = { 117 | formatAmount(value){ 118 | value = Number(value.replace(/\,\./g, "")) * 100 119 | 120 | return value 121 | }, 122 | 123 | formatDate(date) { 124 | const splittedDate = date.split("-") 125 | return `${splittedDate[2]}/${splittedDate[1]}/${splittedDate[0]}` 126 | }, 127 | 128 | formatCurrency(value) { 129 | const signal = Number(value) < 0 ? "-" : "" 130 | 131 | value = String(value).replace(/\D/g, "") 132 | 133 | value = Number(value) / 100 134 | 135 | value = value.toLocaleString("pt-BR", { 136 | style: "currency", 137 | currency: "BRL" 138 | }) 139 | 140 | return signal + value 141 | } 142 | } 143 | 144 | const Form = { 145 | description: document.querySelector('input#description'), 146 | amount: document.querySelector('input#amount'), 147 | date: document.querySelector('input#date'), 148 | 149 | getValues() { 150 | return { 151 | description: Form.description.value, 152 | amount: Form.amount.value, 153 | date: Form.date.value 154 | } 155 | }, 156 | 157 | validateFields() { 158 | const { description, amount, date } = Form.getValues() 159 | 160 | if( description.trim() === "" || 161 | amount.trim() === "" || 162 | date.trim() === "" ) { 163 | throw new Error("Por favor, preencha todos os campos") 164 | } 165 | }, 166 | 167 | formatValues() { 168 | let { description, amount, date } = Form.getValues() 169 | 170 | amount = Utils.formatAmount(amount) 171 | 172 | date = Utils.formatDate(date) 173 | 174 | return { 175 | description, 176 | amount, 177 | date 178 | } 179 | }, 180 | 181 | clearFields() { 182 | Form.description.value = "" 183 | Form.amount.value = "" 184 | Form.date.value = "" 185 | }, 186 | 187 | submit(event) { 188 | event.preventDefault() 189 | 190 | try { 191 | Form.validateFields() 192 | const transaction = Form.formatValues() 193 | Transaction.add(transaction) 194 | Form.clearFields() 195 | Modal.close() 196 | } catch (error) { 197 | alert(error.message) 198 | } 199 | } 200 | } 201 | 202 | const App = { 203 | init() { 204 | Transaction.all.forEach(DOM.addTransaction) 205 | 206 | DOM.updateBalance() 207 | 208 | Storage.set(Transaction.all) 209 | }, 210 | reload() { 211 | DOM.clearTransactions() 212 | App.init() 213 | }, 214 | } 215 | 216 | App.init() -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* Global ===================== */ 2 | :root { 3 | --dark-blue: #363f5f; 4 | --green: #49AA26; 5 | --light-green: #3dd705; 6 | --red: #e92929; 7 | 8 | } 9 | 10 | * { 11 | margin: 0; 12 | padding: 0; 13 | box-sizing: border-box; 14 | } 15 | 16 | html { 17 | font-size: 93.75%; /* 15px */ 18 | } 19 | 20 | body { 21 | background: #f0f2f5; 22 | font-family:'Poppins', sans-serif 23 | } 24 | 25 | .sr-only { 26 | position: absolute; 27 | width: 1px; 28 | height: 1px; 29 | padding: 0; 30 | margin: -1px; 31 | overflow: hidden; 32 | clip: rect(0, 0, 0, 0); 33 | white-space: nowrap; 34 | border-width: 0; 35 | } 36 | 37 | .container { 38 | width: min(90vw, 800px); 39 | margin: auto; 40 | } 41 | 42 | /* Titles ===================== */ 43 | h2 { 44 | margin-top: 3.2rem; 45 | margin-bottom: 0.8rem; 46 | color: var(--dark-blue); 47 | 48 | font-weight: normal; 49 | } 50 | 51 | /* Links & Buttons ===================== */ 52 | a { 53 | color: var(--green); 54 | text-decoration: none; 55 | } 56 | 57 | a:hover { 58 | color: var(--light-green); 59 | } 60 | 61 | button { 62 | width: 100%; 63 | height: 50px; 64 | 65 | border: none; 66 | 67 | color: white; 68 | background: var(--green); 69 | 70 | padding: 0; 71 | 72 | border-radius: 0.25rem; 73 | 74 | cursor: pointer; 75 | } 76 | 77 | button:hover { 78 | background: var(--light-green); 79 | } 80 | 81 | .button.new { 82 | display: inline-block; 83 | margin-bottom: .8rem; 84 | } 85 | 86 | .button.cancel { 87 | color: var(--red); 88 | border: 2px var(--red) solid; 89 | border-radius: 0.25rem; 90 | 91 | height: 50px; 92 | 93 | display: flex; 94 | align-items: center; 95 | justify-content: center; 96 | 97 | opacity: 0.6; 98 | } 99 | 100 | .button.cancel:hover { 101 | opacity: 1; 102 | } 103 | 104 | /* Header ===================== */ 105 | header { 106 | background: #2D4A22; 107 | padding: 2rem 0 10rem; 108 | text-align: center; 109 | } 110 | 111 | #logo { 112 | color: #fff; 113 | font-weight: 100; 114 | } 115 | 116 | /* Balance ===================== */ 117 | #balance { 118 | margin-top: -8rem; 119 | } 120 | 121 | #balance h2 { 122 | color:white; 123 | margin-top:0; 124 | } 125 | 126 | /* Cards ===================== */ 127 | .card { 128 | background: white; 129 | padding: 1.5rem 2rem; 130 | border-radius: 0.25rem; 131 | 132 | margin-bottom: 2rem; 133 | 134 | color: var(--dark-blue); 135 | } 136 | 137 | .card h3 { 138 | font-weight: normal; 139 | font-size: 1rem; 140 | 141 | display: flex; 142 | align-items: center; 143 | justify-content: space-between; 144 | } 145 | 146 | .card p { 147 | font-size: 2rem; 148 | line-height: 3rem; 149 | white-space:nowrap; 150 | margin-top: 1rem; 151 | } 152 | 153 | .card.total { 154 | background: var(--green); 155 | color: white; 156 | } 157 | 158 | /* Table ===================== */ 159 | #transaction { 160 | display: block; 161 | width: 100%; 162 | overflow-x: auto; 163 | } 164 | #data-table { 165 | width: 100%; 166 | border-spacing: 0 0.5rem; 167 | color: #969cb3; 168 | } 169 | 170 | table thead tr th:first-child, 171 | table tbody tr td:first-child 172 | { 173 | border-radius: 0.25rem 0 0 0.25rem; 174 | } 175 | 176 | table thead tr th:last-child, 177 | table tbody tr td:last-child{ 178 | border-radius: 0 0.25rem 0.25rem 0; 179 | } 180 | 181 | table thead th { 182 | background: white; 183 | 184 | font-weight: normal; 185 | padding: 1rem 2rem; 186 | 187 | text-align: left; 188 | } 189 | 190 | table tbody tr { 191 | opacity: 0.7 192 | } 193 | 194 | table tbody tr:hover { 195 | opacity: 1 196 | } 197 | 198 | table tbody td { 199 | background:white; 200 | padding: 1rem 2rem; 201 | } 202 | 203 | td.description { 204 | color: var(--dark-blue); 205 | } 206 | 207 | td.income { 208 | color: #12a454; 209 | } 210 | 211 | td.expense { 212 | color: #e92929; 213 | } 214 | 215 | /* Modal ===================== */ 216 | .modal-overlay { 217 | width: 100%; 218 | height: 100%; 219 | 220 | background-color: rgba(0,0,0,0.7); 221 | 222 | position: fixed; 223 | top: 0; 224 | 225 | display:flex; 226 | align-items: center; 227 | justify-content: center; 228 | 229 | opacity: 0; 230 | visibility: hidden; 231 | 232 | z-index: 999; 233 | } 234 | 235 | .modal-overlay.active { 236 | opacity: 1; 237 | visibility: visible; 238 | } 239 | 240 | .modal { 241 | background: #F0F2f5; 242 | padding: 2.4rem; 243 | 244 | position: relative; 245 | z-index: 1; 246 | } 247 | 248 | 249 | /* Form ===================== */ 250 | #form { 251 | max-width: 500px; 252 | } 253 | 254 | #form h2 { 255 | margin-top: 0; 256 | } 257 | 258 | input { 259 | border: none; 260 | border-radius: 0.2rem; 261 | 262 | padding: 0.8rem; 263 | 264 | width: 100%; 265 | } 266 | 267 | .input-group { 268 | margin-top: 0.8rem; 269 | } 270 | 271 | .input-group .help{ 272 | opacity: 0.4; 273 | } 274 | 275 | .input-group.actions { 276 | display: flex; 277 | justify-content: space-between; 278 | align-items: center; 279 | } 280 | 281 | .input-group.actions .button, 282 | .input-group.actions button { 283 | width: 48%; 284 | } 285 | 286 | 287 | /* Footer ===================== */ 288 | footer { 289 | text-align: center; 290 | padding: 4rem 0 2rem; 291 | color: var(--dark-blue); 292 | 293 | opacity: 0.6; 294 | } 295 | 296 | 297 | /* Responsive ===================== */ 298 | @media (min-width: 800px) { 299 | html { 300 | font-size: 87.5%; 301 | } 302 | 303 | #balance { 304 | display: grid; 305 | grid-template-columns: repeat(3, 1fr); 306 | gap: 2rem; 307 | } 308 | } 309 | --------------------------------------------------------------------------------