├── src ├── app.css ├── vite-env.d.ts ├── parts │ ├── Aside.svelte │ └── Header.svelte ├── scripts │ ├── dayjs.js │ ├── utils.js │ ├── db.js │ ├── project.js │ └── calculate.js ├── main.js ├── home │ ├── Home.svelte │ ├── Main.svelte │ ├── Card.svelte │ └── Header.svelte ├── profile │ ├── Profile.svelte │ └── Main.svelte ├── App.svelte ├── store.js └── project │ ├── Project.svelte │ └── Main.svelte ├── .vscode └── extensions.json ├── postcss.config.js ├── public └── images │ ├── favicon.png │ ├── stop.svg │ ├── play.svg │ ├── plus-24.svg │ ├── back.svg │ ├── plus-orange.svg │ ├── alert-octagon.svg │ ├── trash-24.svg │ ├── trash-48.svg │ ├── edit-24.svg │ ├── money-gray.svg │ ├── money-color.svg │ └── logo.svg ├── vite.config.js ├── svelte.config.js ├── .gitignore ├── tailwind.config.js ├── package.json ├── .github └── workflows │ └── deploy.yml ├── index.html ├── jsconfig.json └── README.md /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maykbrito/jobscalc-svelte-tailwindcss/HEAD/public/images/favicon.png -------------------------------------------------------------------------------- /src/parts/Aside.svelte: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/scripts/dayjs.js: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | // import ptBr from 'dayjs/locale/pt-br' 3 | 4 | // dayjs.locale(ptBr) 5 | 6 | export { dayjs } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import App from './App.svelte' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app'), 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /public/images/stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /public/images/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/home/Home.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |
9 |
-------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { svelte } from '@sveltejs/vite-plugin-svelte' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | base: './' 8 | }) 9 | -------------------------------------------------------------------------------- /src/profile/Profile.svelte: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 |
8 |
9 |
-------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /public/images/plus-24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/scripts/utils.js: -------------------------------------------------------------------------------- 1 | import { DB } from "./db"; 2 | 3 | function debounce(func, delay = 500) { 4 | let timeout; 5 | return function (...args) { 6 | clearTimeout(timeout) 7 | timeout = setTimeout(() => func(...args), delay) 8 | } 9 | } 10 | 11 | function autosave(data) { 12 | DB.set(data) 13 | } 14 | 15 | export const debouncedAutosave = debounce(autosave, 700) -------------------------------------------------------------------------------- /src/App.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | {#if $app.page == "home"} 10 | 11 | {:else if $app.page == "profile"} 12 | 13 | {:else} 14 | 15 | {/if} 16 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | const defaultTheme = require('tailwindcss/defaultTheme') 4 | 5 | export default { 6 | content: ['./src/**/*.{html,js,svelte,ts}'], 7 | theme: { 8 | extend: { 9 | fontFamily: { 10 | 'sans': ['"IBM Plex Sans"', ...defaultTheme.fontFamily.sans], 11 | } 12 | }, 13 | }, 14 | plugins: [], 15 | } 16 | 17 | -------------------------------------------------------------------------------- /public/images/plus-orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/scripts/db.js: -------------------------------------------------------------------------------- 1 | export const DB = { 2 | init(initialData) { 3 | const data = DB.get() 4 | 5 | if (Object.keys(data).length === 0) { 6 | return DB.set(initialData) 7 | } 8 | 9 | return data 10 | }, 11 | 12 | get() { 13 | return JSON.parse(localStorage.getItem("@jobscalc:app")) || {} 14 | }, 15 | set(app = {}) { 16 | localStorage.setItem('@jobscalc:app', 17 | JSON.stringify(app)) 18 | 19 | return DB.get() 20 | } 21 | } -------------------------------------------------------------------------------- /src/parts/Header.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
12 | 15 |

{title}

16 |
17 |
18 | -------------------------------------------------------------------------------- /src/home/Main.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
12 |

Trabalhos

13 | 14 |
15 | {#each $app.projects as project (project.id)} 16 | 17 | {/each} 18 |
19 | 20 |
21 |
22 | 23 | -------------------------------------------------------------------------------- /public/images/alert-octagon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jobscalc-svelte-tailwindcss", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^3.1.1", 13 | "autoprefixer": "^10.4.20", 14 | "postcss": "^8.4.41", 15 | "svelte": "^4.2.18", 16 | "tailwindcss": "^3.4.8", 17 | "vite": "^5.4.0" 18 | }, 19 | "dependencies": { 20 | "dayjs": "^1.11.12" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/images/trash-24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/trash-48.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/images/edit-24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20' 19 | 20 | - name: Install dependencies 21 | run: npm ci 22 | 23 | - name: Build 24 | run: npm run build 25 | 26 | - name: Deploy to GitHub Pages 27 | uses: JamesIves/github-pages-deploy-action@v4 28 | with: 29 | folder: dist # or your Svelte output folder -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | JobsCalc 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/scripts/project.js: -------------------------------------------------------------------------------- 1 | import { dayjs } from './dayjs.js' 2 | 3 | export class Project { 4 | constructor(name, dailyHours, totalHours) { 5 | this.id = crypto.randomUUID() 6 | this.name = name || 'Novo projeto' 7 | this.dailyHours = dailyHours || 1 8 | this.totalHours = totalHours || 2 9 | this.createdAt = new Date() 10 | } 11 | 12 | get remainingDays() { 13 | const totalDays = Math.ceil(this.totalHours / this.dailyHours) 14 | const daysPassed = dayjs().diff(this.createdAt, 'day') 15 | return totalDays - daysPassed 16 | } 17 | 18 | get status() { 19 | return this.remainingDays < 0 ? 'encerrado' : 'em andamento' 20 | } 21 | 22 | get deadline() { 23 | return dayjs() 24 | .add(this.remainingDays, 'day') 25 | .diff(this.createdAt, 'day') 26 | } 27 | } -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { writable } from "svelte/store"; 2 | import { Project } from "./scripts/project"; 3 | import { DB } from "./scripts/db"; 4 | 5 | const createApp = (initialData) => { 6 | const data = DB.init(initialData) 7 | const app = writable(data) 8 | 9 | if (data.projects?.length > 0) { 10 | data.projects = data.projects.map(p => new Project(p.name, p.dailyHours, p.totalHours)) 11 | } 12 | 13 | return app 14 | } 15 | 16 | export const app = createApp({ 17 | user: { 18 | name: "Mayk Brito", 19 | avatar: "https://github.com/maykbrito.png" 20 | }, 21 | planning: { 22 | monthlyIncome: 50000, 23 | hoursPerDay: 12, 24 | daysAWeek: 6, 25 | vacationWeeks: 4, 26 | }, 27 | projects: [ 28 | new Project('Youtube Video', 1, 2), 29 | new Project('Site', 1, 2) 30 | ], 31 | page: "home", 32 | currentProject: new Project() 33 | }) -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "moduleResolution": "bundler", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | /** 7 | * svelte-preprocess cannot figure out whether you have 8 | * a value or a type, so tell TypeScript to enforce using 9 | * `import type` instead of `import` for Types. 10 | */ 11 | "verbatimModuleSyntax": true, 12 | "isolatedModules": true, 13 | "resolveJsonModule": true, 14 | /** 15 | * To have warnings / errors of the Svelte compiler at the 16 | * correct position, enable source maps by default. 17 | */ 18 | "sourceMap": true, 19 | "esModuleInterop": true, 20 | "skipLibCheck": true, 21 | /** 22 | * Typecheck JS in `.svelte` and `.js` files by default. 23 | * Disable this if you'd like to use dynamic types. 24 | */ 25 | "checkJs": true 26 | }, 27 | /** 28 | * Use global.d.ts instead of compilerOptions.types 29 | * to avoid limiting type declarations. 30 | */ 31 | "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] 32 | } 33 | -------------------------------------------------------------------------------- /src/project/Project.svelte: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 |
22 | 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /src/scripts/calculate.js: -------------------------------------------------------------------------------- 1 | export class Calculate { 2 | constructor(app) { 3 | this.project = app.currentProject 4 | this.planning = app.planning 5 | this.projects = app.projects 6 | } 7 | 8 | get valueHour() { 9 | const weeksPerYear = 52 10 | const weeksPerMonth = (weeksPerYear - this.planning.vacationWeeks) / 12 11 | 12 | const weekTotalHours = this.planning.hoursPerDay * this.planning.daysAWeek 13 | 14 | const monthlyTotalHours = weekTotalHours * weeksPerMonth 15 | 16 | return +this.planning.monthlyIncome / +monthlyTotalHours 17 | } 18 | 19 | get formattedValueHour() { 20 | return Number(this.valueHour).toLocaleString('pt-br', { 21 | currency: 'BRL', style: 'currency' 22 | }) 23 | } 24 | 25 | get projectValue() { 26 | return Number(this.valueHour) * this.project.totalHours 27 | } 28 | 29 | get formattedProjectValue() { 30 | return Number(this.projectValue).toLocaleString('pt-br', { currency: 'BRL', style: 'currency' }) 31 | } 32 | 33 | get projectsTotalHours() { 34 | return this.projects.reduce((acc, project) => { 35 | return project.status === 'em andamento' 36 | ? acc + Number(project.dailyHours) 37 | : acc 38 | }, 0) 39 | } 40 | 41 | get freeHours() { 42 | return this.planning.hoursPerDay - this.projectsTotalHours 43 | } 44 | 45 | get projectsStatus() { 46 | 47 | return { 48 | total: this.projects.length, 49 | progress: this.projects.filter(project => project.status == 'em andamento').length, 50 | done: this.projects.filter(project => project.status == 'encerrado').length, 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/project/Main.svelte: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 |

9 | Dados do projeto 10 |

11 | 12 |
13 |
14 | 17 | 24 |
25 | 26 |
27 |
28 | 31 | 39 |
40 | 41 |
42 | 45 | 52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | JobsCalc 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 | - Svelte 32 | - Tailwind 33 | - Vite 34 | 35 | ## 💻 Projeto 36 | 37 | O JobsCalc é uma aplicação de estimativa de cálculo para projetos freelancer, onde é possível cadastrar e excluir jobs (projetos), obtendo uma estimativa de custo de cada job. Além disso, é possível traçar o valor da hora da pessoa que estará usando o sistema 💰 38 | 39 | ## 🔖 Layout 40 | 41 | Você pode visualizar o layout do projeto através [desse link](https://www.figma.com/file/s4fytPFbDiSkv4GPSfKaLE/Jobs-Planning). É necessário ter conta no [Figma](https://figma.com) para acessá-lo. 42 | 43 | ## :memo: Licença 44 | 45 | Esse projeto está sob a licença MIT. Veja o arquivo [LICENSE](.github/LICENSE.md) para mais detalhes. 46 | 47 | --- 48 | 49 | Feito com ♥ by Rocketseat :wave: [Participe da nossa comunidade!](https://discordapp.com/invite/gCRAFhc) 50 | 51 | 52 | 53 |
54 |
55 | 56 |

57 | 58 | banner 59 | 60 |

61 | 62 | 63 | -------------------------------------------------------------------------------- /src/home/Card.svelte: -------------------------------------------------------------------------------- 1 | 25 | 26 |
47 |
48 | {project.name} 49 |
50 |
51 | Prazo 52 | 53 | {#if project.status == "em andamento"} 54 | 55 | {project.remainingDays} 56 | {project.remainingDays == "1" ? "dia" : "dias"} 57 | 58 | {:else} 59 | Esgotado 60 | {/if} 61 |
62 |
63 | Valor 64 | {projectValue} 65 |
66 |
69 | {#if project.status === "encerrado"} 70 |
Encerrado
71 | {:else} 72 |
Em andamento
73 | {/if} 74 |
75 |
76 |

Ações

77 | 84 | 91 |
92 |
93 | 94 | -------------------------------------------------------------------------------- /src/home/Header.svelte: -------------------------------------------------------------------------------- 1 | 20 | 21 | 90 | -------------------------------------------------------------------------------- /src/profile/Main.svelte: -------------------------------------------------------------------------------- 1 | 11 | 12 |
15 | 29 | 30 |
31 |

32 | Dados do perfil 33 |

34 | 35 |
36 |
37 | 38 | 45 |
46 | 47 |
48 | 51 | 59 |
60 |
61 | 62 |

63 | Planejamento 64 |

65 | 66 |
67 |
68 | 71 | 80 |
81 | 82 |
83 | 87 | 94 |
95 |
96 | 97 |
98 |
99 | 102 | 109 |
110 | 111 |
112 | 116 | 123 |
124 |
125 |
126 |
127 | 128 | -------------------------------------------------------------------------------- /public/images/money-gray.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/images/money-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /public/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------