├── .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 |
3 |
4 |
5 |
6 | Tecnologias |
7 | Projeto |
8 | Layout |
9 | Licença
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
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 |
15 |
16 |
17 |
18 |
19 | Balanço
20 |
21 |
22 |
23 |
24 | Entradas
25 |
26 |
27 |
28 |
R$ 0,00
29 |
30 |
31 |
32 |
33 |
34 | Saídas
35 |
36 |
37 |
38 |
R$ 0,00
39 |
40 |
41 |
42 |
43 |
44 | Total
45 |
46 |
47 |
48 |
R$ 0,00
49 |
50 |
51 |
52 |
53 |
54 | Transações
55 |
56 | + Nova Transação
59 |
60 |
61 |
62 |
63 | Descrição
64 | Valor
65 | Data
66 |
67 |
68 |
69 |
70 |
71 |
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 |
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 |
--------------------------------------------------------------------------------