├── .eslintignore ├── public ├── robots.txt ├── img │ └── icons │ │ ├── nutrisicon.png │ │ ├── nutrisicon144.png │ │ └── nutrisicon512.png ├── manifest.json └── index.html ├── .browserslistrc ├── .jest └── setup.js ├── src ├── layouts │ ├── Index.vue │ ├── default │ │ ├── Default-variables.scss │ │ ├── components │ │ │ ├── footer │ │ │ │ ├── footer.i18n.ts │ │ │ │ └── Footer.vue │ │ │ └── header │ │ │ │ ├── components │ │ │ │ ├── user-options │ │ │ │ │ ├── userOptions.i18n.ts │ │ │ │ │ ├── resize.ts │ │ │ │ │ └── UserOptions.vue │ │ │ │ └── nav │ │ │ │ │ ├── nav.i18n.ts │ │ │ │ │ ├── navMenu copy.ts │ │ │ │ │ ├── navMenu.ts │ │ │ │ │ └── Nav.vue │ │ │ │ └── Header.vue │ │ └── Default.vue │ └── alt │ │ ├── Alt.vue │ │ └── header │ │ ├── Header.vue │ │ └── components │ │ └── social-login │ │ └── SocialLogin.vue ├── views │ ├── alt │ │ ├── Alt.vue │ │ ├── not-found │ │ │ └── NotFound.vue │ │ ├── SomeAlt.vue │ │ └── home │ │ │ ├── homei18n.ts │ │ │ └── Home.vue │ └── default │ │ ├── About.vue │ │ ├── welcome │ │ └── Welcome.vue │ │ ├── new-patient │ │ └── NewPatient.vue │ │ ├── manage-diets │ │ ├── ManageDiets.i18n.ts │ │ ├── ManageDiets.vue │ │ └── components │ │ │ ├── diet │ │ │ └── Diet.vue │ │ │ └── form │ │ │ └── Form.vue │ │ └── patients │ │ ├── Patients.vue │ │ └── components │ │ ├── patient │ │ └── Patient.vue │ │ ├── history │ │ ├── History.vue │ │ └── components │ │ │ └── Card.vue │ │ └── form │ │ └── Form.vue ├── enums │ ├── i18n.ts │ ├── storageKeys.ts │ ├── firebaseQuerys.ts │ ├── alerts.ts │ ├── label.ts │ ├── statics.ts │ ├── chunkName.ts │ └── collections │ │ └── firebase.ts ├── assets │ ├── icons │ │ ├── nutris.eot │ │ ├── nutris.ttf │ │ ├── nutris.woff │ │ ├── nutris.woff2 │ │ ├── nutris.scss │ │ └── nutris.svg │ └── styles │ │ ├── base.css │ │ ├── variables.scss │ │ ├── index.scss │ │ ├── screen.scss │ │ ├── transition.scss │ │ ├── class.scss │ │ ├── reset.css │ │ └── variables-root.css ├── router │ ├── alt │ │ ├── enums.ts │ │ ├── home.ts │ │ ├── notFound.ts │ │ └── index.ts │ ├── enums.ts │ ├── default │ │ ├── history.ts │ │ ├── calculate.ts │ │ ├── welcome.ts │ │ ├── newPatient.ts │ │ ├── index.ts │ │ ├── enums.ts │ │ ├── manageDiets.ts │ │ └── patients.ts │ └── index.ts ├── models │ ├── firebase.ts │ ├── User.ts │ ├── Diet.ts │ ├── Patient.ts │ └── CalculationHistory.ts ├── helpers │ ├── localStorage │ │ ├── __tests__ │ │ │ ├── mocks.ts │ │ │ └── localStorage.spec.ts │ │ └── localStorage.ts │ ├── styles.ts │ ├── parenteral │ │ └── index.ts │ ├── form │ │ ├── form.ts │ │ └── __tests__ │ │ │ ├── mocks.ts │ │ │ └── form.spec.ts │ ├── router │ │ └── index.ts │ ├── date │ │ ├── date.spec.ts │ │ └── date.ts │ └── window-listeners │ │ └── scroll.ts ├── shims-vue.d.ts ├── App.vue ├── components │ ├── form │ │ ├── button │ │ │ ├── FixedBtn.i18n.ts │ │ │ ├── FixedBtn.vue │ │ │ └── Button.vue │ │ ├── box │ │ │ ├── BoxStyles.scss │ │ │ └── Box.vue │ │ ├── select │ │ │ └── Select.vue │ │ └── input │ │ │ └── Input.vue │ ├── TestI18n.vue │ ├── logo │ │ └── Logo.vue │ ├── loading-page │ │ └── LoadingPage.vue │ ├── paper │ │ └── Paper.vue │ ├── HelloWorld.vue │ ├── empty-state │ │ └── EmptyState.vue │ ├── modal │ │ └── Modal.vue │ ├── alert │ │ └── Alert.vue │ ├── popup-message │ │ └── PopupMessage.vue │ ├── spinner │ │ └── Spinner.vue │ └── calculator │ │ └── Calculator.vue ├── store │ ├── loading-page │ │ ├── types.ts │ │ └── index.ts │ ├── index.ts │ ├── user │ │ ├── types.ts │ │ └── index.ts │ ├── popup-message │ │ ├── types.ts │ │ └── index.ts │ ├── i18n │ │ ├── types.ts │ │ └── index.ts │ ├── patients │ │ ├── types.ts │ │ └── index.ts │ └── manage-diets │ │ ├── types.ts │ │ └── index.ts ├── services │ ├── auth.service.ts │ └── calculate.service.ts ├── registerServiceWorker.ts ├── test │ └── setup.ts ├── i18n │ ├── test-ignore.ts │ └── index.ts └── main.ts ├── .firebaserc ├── babel.config.js ├── vue.config.js ├── .env.example ├── .npmrc.example ├── .github └── pull_request_template.md ├── jest.config.js ├── .editorconfig ├── firebase.json ├── .prettierrc ├── firebaseApp.ts ├── .eslintrc.js ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md └── .gitignore /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.jest/setup.js: -------------------------------------------------------------------------------- 1 | // initials setup to Jest, as .env variables. 2 | -------------------------------------------------------------------------------- /src/layouts/Index.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/views/alt/Alt.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/views/alt/not-found/NotFound.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/enums/i18n.ts: -------------------------------------------------------------------------------- 1 | export enum I18n { 2 | en = 'en', 3 | pt = 'pt', 4 | } 5 | -------------------------------------------------------------------------------- /src/enums/storageKeys.ts: -------------------------------------------------------------------------------- 1 | export enum storageKey { 2 | diets = 'diets', 3 | } 4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "dietcount-dev" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/enums/firebaseQuerys.ts: -------------------------------------------------------------------------------- 1 | export enum firebaseQuerys { 2 | desc = 'desc', 3 | } 4 | -------------------------------------------------------------------------------- /src/views/alt/SomeAlt.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/views/default/About.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | } 4 | -------------------------------------------------------------------------------- /src/enums/alerts.ts: -------------------------------------------------------------------------------- 1 | export enum Alerts { 2 | DayjsInvalidDate = 'Invalid Date', 3 | } 4 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | devServer: { 3 | port: 1993, 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | VUE_APP_FIREBASE_PROJECT_ID= 2 | VUE_APP_FIREBASE_API_KEY= 3 | VUE_APP_FIREBASE_ID= 4 | -------------------------------------------------------------------------------- /src/assets/icons/nutris.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ish/nutris/HEAD/src/assets/icons/nutris.eot -------------------------------------------------------------------------------- /src/assets/icons/nutris.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ish/nutris/HEAD/src/assets/icons/nutris.ttf -------------------------------------------------------------------------------- /src/assets/icons/nutris.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ish/nutris/HEAD/src/assets/icons/nutris.woff -------------------------------------------------------------------------------- /public/img/icons/nutrisicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ish/nutris/HEAD/public/img/icons/nutrisicon.png -------------------------------------------------------------------------------- /src/assets/icons/nutris.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ish/nutris/HEAD/src/assets/icons/nutris.woff2 -------------------------------------------------------------------------------- /src/assets/styles/base.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: var(--text-color); 3 | font-family: var(--font-family); 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc.example: -------------------------------------------------------------------------------- 1 | //npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN 2 | @open-ish:registry=https://npm.pkg.github.com 3 | -------------------------------------------------------------------------------- /public/img/icons/nutrisicon144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ish/nutris/HEAD/public/img/icons/nutrisicon144.png -------------------------------------------------------------------------------- /public/img/icons/nutrisicon512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-ish/nutris/HEAD/public/img/icons/nutrisicon512.png -------------------------------------------------------------------------------- /src/enums/label.ts: -------------------------------------------------------------------------------- 1 | export enum Label { 2 | fullDate = 'DD/MM/YYYY', 3 | fullDateAndHour = 'DD/MM/YYYY HH:mm', 4 | } 5 | -------------------------------------------------------------------------------- /src/router/alt/enums.ts: -------------------------------------------------------------------------------- 1 | export enum Paths { 2 | home = 'home', 3 | } 4 | 5 | export enum Names { 6 | home = 'Home', 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $header-height-small: 58px; 2 | $header-height: 70px; 3 | $padding-small: 10px; 4 | $padding: 20px; 5 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | #### Description 2 | 3 | Describe here all the changes that will be made and what the expected result is. 4 | -------------------------------------------------------------------------------- /src/models/firebase.ts: -------------------------------------------------------------------------------- 1 | import { Diet } from '@/models/Diet' 2 | 3 | export interface Snapshot { 4 | id: string 5 | data: () => any 6 | } 7 | -------------------------------------------------------------------------------- /src/models/User.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | displayName: string 3 | email: string 4 | uid: string //firebase`s id 5 | photoURL: string 6 | } 7 | -------------------------------------------------------------------------------- /src/models/Diet.ts: -------------------------------------------------------------------------------- 1 | export interface Diet { 2 | id: string 3 | name: string 4 | calAmount?: number 5 | proteinAmount: number 6 | createdAt: number | Date 7 | } 8 | -------------------------------------------------------------------------------- /src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | @import './variables-root.css'; 3 | @import './reset.css'; 4 | @import './class.scss'; 5 | @import './transition.scss'; 6 | -------------------------------------------------------------------------------- /src/enums/statics.ts: -------------------------------------------------------------------------------- 1 | export enum Statics { 2 | grape = 'https://img.icons8.com/material-rounded/55/006064/grapes.png', 3 | openIshRepo = 'https://github.com/open-ish', 4 | } 5 | -------------------------------------------------------------------------------- /src/helpers/localStorage/__tests__/mocks.ts: -------------------------------------------------------------------------------- 1 | export const SOME_DATA = 'some value' 2 | export const ARRAY_DATA = ['changed'] 3 | export const COMPLEX_DATA = [{ data: 'complex' }] 4 | -------------------------------------------------------------------------------- /src/enums/chunkName.ts: -------------------------------------------------------------------------------- 1 | export enum chunkName { 2 | loadingPage = 'LoadingPage', 3 | popupMessage = 'PopupMessage', 4 | calculator = 'Calculator', 5 | modal = 'Modal', 6 | } 7 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { defineComponent } from 'vue' 3 | const component: ReturnType 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /src/helpers/styles.ts: -------------------------------------------------------------------------------- 1 | export const getCssVariableValue = (variableName: string) => 2 | getComputedStyle(document.documentElement) 3 | .getPropertyValue(variableName) 4 | .trim() 5 | -------------------------------------------------------------------------------- /src/assets/styles/screen.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Here there global variables to use at all layouts 3 | */ 4 | $screen-xs: 360px; 5 | $screen-sm: 768px; 6 | $screen-md: 992px; 7 | $screen-lg: 1280px; 8 | $screen-xlg: 1440px; 9 | -------------------------------------------------------------------------------- /src/enums/collections/firebase.ts: -------------------------------------------------------------------------------- 1 | export enum names { 2 | users = 'users', 3 | diets = 'diets', 4 | history = 'history', 5 | patients = 'patients', 6 | calculationHistory = 'calculationHistory', 7 | } 8 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /src/layouts/default/Default-variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Usually, it can have other values in diffs layouts. 3 | Here there global variables to use at Default layout 4 | */ 5 | // utils 6 | $nav-height: 48px; 7 | $footer-height-small: 68px; 8 | -------------------------------------------------------------------------------- /src/router/enums.ts: -------------------------------------------------------------------------------- 1 | //we may do not need of this enum anymore. 2 | 3 | export enum LayoutsName { 4 | default = 'LDefault', 5 | alt = 'LAlt', 6 | } 7 | 8 | export enum LayoutsPaths { 9 | default = '/', 10 | alt = '/', 11 | } 12 | -------------------------------------------------------------------------------- /src/assets/styles/transition.scss: -------------------------------------------------------------------------------- 1 | .slide-left-enter, 2 | .slide-left-leave-to { 3 | transform: translateX(-10%); 4 | opacity: 0; 5 | } 6 | 7 | .slide-left-enter-active, 8 | .slide-left-leave-active { 9 | transition: all 0.3s ease-in-out; 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', 3 | transform: { 4 | '^.+\\.vue$': 'vue-jest', 5 | }, 6 | setupFiles: ['/.jest/setup.js'], 7 | testMatch: ['/**/*.(spec).(ts|js)'], 8 | } 9 | -------------------------------------------------------------------------------- /src/router/alt/home.ts: -------------------------------------------------------------------------------- 1 | const Home = () => 2 | import(/* webpackChunkName: "Home" */ '@/views/alt/home/Home.vue') 3 | import { Paths, Names } from '@/router/alt/enums' 4 | 5 | export default { 6 | path: Paths.home, 7 | name: Names.home, 8 | component: Home, 9 | } 10 | -------------------------------------------------------------------------------- /src/views/default/welcome/Welcome.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | -------------------------------------------------------------------------------- /src/router/default/history.ts: -------------------------------------------------------------------------------- 1 | import { Names, Paths } from '@/router/default/enums' 2 | import Home from '@/views/alt/home/Home.vue' 3 | 4 | export default { 5 | path: Paths.history, 6 | name: Names.history, 7 | component: Home, 8 | meta: { 9 | requiresAuth: true, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /src/router/default/calculate.ts: -------------------------------------------------------------------------------- 1 | import { Names, Paths } from '@/router/default/enums' 2 | import Home from '@/views/alt/home/Home.vue' 3 | 4 | export default { 5 | path: Paths.calculate, 6 | name: Names.calculate, 7 | component: Home, 8 | meta: { 9 | requiresAuth: true, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /src/router/default/welcome.ts: -------------------------------------------------------------------------------- 1 | import { Names, Paths } from '@/router/default/enums' 2 | import Welcome from '@/views/default/welcome/Welcome.vue' 3 | 4 | export default { 5 | path: Paths.welcome, 6 | name: Names.welcome, 7 | component: Welcome, 8 | meta: { 9 | requiresAuth: true, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "./dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/layouts/default/components/footer/footer.i18n.ts: -------------------------------------------------------------------------------- 1 | export enum defaultFooterI18nPath { 2 | madeWithlove = 'footer.madeWithlove', 3 | } 4 | 5 | export const defaultFooterPt = { 6 | madeWithlove: 'Feito com ❤️ por', 7 | } 8 | 9 | export const defaultFooterEn = { 10 | madeWithlove: 'Made with ❤️ by', 11 | } 12 | -------------------------------------------------------------------------------- /src/router/alt/notFound.ts: -------------------------------------------------------------------------------- 1 | const NotFound = () => 2 | import( 3 | /* webpackChunkName: "fail-over" */ '@/views/alt/not-found/NotFound.vue' 4 | ) 5 | 6 | export const notFoundFallback = { path: '/404', component: NotFound } 7 | export const notFound = { 8 | path: '/:pathMatch(.*)*', 9 | redirect: '/404', 10 | } 11 | -------------------------------------------------------------------------------- /src/helpers/parenteral/index.ts: -------------------------------------------------------------------------------- 1 | import { parenteral } from '@open-ish/nutris-roles' 2 | 3 | // example of use 4 | export const data = { 5 | patient: { 6 | body: 60, 7 | calGoal: 0.3, 8 | proteinGoal: 0.4, 9 | volumeReceived: 10, 10 | }, 11 | diet: { cal: 3, protein: 1 }, 12 | } 13 | console.log('paraenteral', parenteral(data)) 14 | -------------------------------------------------------------------------------- /src/helpers/form/form.ts: -------------------------------------------------------------------------------- 1 | export const removeAllEmptySpace = (string: string) => { 2 | return string?.replace(/\s+/g, '') 3 | } 4 | 5 | export const maxLength = (value: string, max: number) => { 6 | const hasAmount = value.length < max 7 | return hasAmount ? value : value.slice(0, max) 8 | } 9 | 10 | export const changeToDot = (value: string) => { 11 | return value.toString().replace(/,/g, '.') 12 | } 13 | -------------------------------------------------------------------------------- /src/helpers/router/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'vue-router' 2 | 3 | const paramsQuerysFactory = (router: Router) => router.currentRoute?.value 4 | export function querys(router: Router) { 5 | const { query } = paramsQuerysFactory(router) 6 | 7 | return query 8 | } 9 | 10 | export function params(router: Router) { 11 | const { params } = paramsQuerysFactory(router) 12 | 13 | return params 14 | } 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "htmlWhitespaceSensitivity": "css", 6 | "insertPragma": false, 7 | "printWidth": 80, 8 | "proseWrap": "preserve", 9 | "requirePragma": false, 10 | "semi": false, 11 | "semicolons": false, 12 | "singleQuote": true, 13 | "tabWidth": 2, 14 | "trailingComma": "es5", 15 | "useTabs": false 16 | } 17 | -------------------------------------------------------------------------------- /src/components/form/button/FixedBtn.i18n.ts: -------------------------------------------------------------------------------- 1 | export enum fixedBtnI18nPath { 2 | ariaLabelBtn = 'fixedBtn.ariaLabelBtn', 3 | ariaLabelIcon = 'fixedBtn.ariaLabelIcon', 4 | } 5 | 6 | export const fixedBtnPt = { 7 | ariaLabelBtn: 'Voltar pro topo.', 8 | ariaLabelIcon: 'Seta apontando pra cima.', 9 | } 10 | 11 | export const fixedBtnEn = { 12 | ariaLabelBtn: 'Back to top.', 13 | ariaLabelIcon: 'Up arrow up.', 14 | } 15 | -------------------------------------------------------------------------------- /src/router/alt/index.ts: -------------------------------------------------------------------------------- 1 | import { LayoutsName, LayoutsPaths } from '@/router/enums' 2 | const Alt = () => import(/* webpackChunkName: "alt" */ '@/layouts/alt/Alt.vue') 3 | 4 | import home from './home' 5 | import { notFound, notFoundFallback } from './notFound' 6 | 7 | export const alt = { 8 | path: LayoutsPaths.alt, 9 | name: LayoutsName.alt, 10 | component: Alt, 11 | children: [home, notFoundFallback, notFound], 12 | } 13 | -------------------------------------------------------------------------------- /src/store/loading-page/types.ts: -------------------------------------------------------------------------------- 1 | export const LOADING_PAGE_NAMESPACE = 'loadingPage' 2 | 3 | export interface LoadingPageState { 4 | loading: boolean 5 | } 6 | 7 | // getters 8 | export const LoadingPageGetters = { 9 | LOADING: 'LOADING', 10 | } 11 | 12 | // actions 13 | export const LoadingPageActions = { 14 | TOGGLE: 'TOGGLE', 15 | } 16 | 17 | // mutations 18 | export const LoadingPageMutations = { 19 | TOGGLE: 'TOGGLE', 20 | } 21 | -------------------------------------------------------------------------------- /src/helpers/date/date.spec.ts: -------------------------------------------------------------------------------- 1 | import { formatDate, getAge } from './date' 2 | 3 | const DATE_RESULT = '31/10/2020' 4 | 5 | describe('Date', () => { 6 | it('Should return valid dates', () => { 7 | expect(formatDate(1604194938654)).toBe(DATE_RESULT) 8 | expect(formatDate('Sat Oct 31 2020 22:34:11 GMT-0300')).toBe(DATE_RESULT) 9 | }) 10 | 11 | it('Should return date in ages', () => { 12 | expect(getAge('1994/09/14')).toBe('26') 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/components/TestI18n.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /src/models/Patient.ts: -------------------------------------------------------------------------------- 1 | import { CalculationHistory } from './CalculationHistory' 2 | 3 | export interface Patient { 4 | id: string 5 | anonymousIdentifier: string //can not have empty space. The user will fill it. 6 | anonymousText: '' 7 | anonymousNumber: '' 8 | currentBody: string 9 | born: string //date 10 | gender: 'F' | 'M' | null 11 | calculationHistory: CalculationHistory[] | null 12 | lastUpdated: number | Date 13 | createdAt: number | Date 14 | } 15 | -------------------------------------------------------------------------------- /src/router/default/newPatient.ts: -------------------------------------------------------------------------------- 1 | import Index from '@/layouts/Index.vue' 2 | import NewPatient from '@/views/default/new-patient/NewPatient.vue' 3 | import { Names, Paths } from '@/router/default/enums' 4 | 5 | export default { 6 | path: Paths.newPatient, 7 | name: Names.newPatient, 8 | component: Index, 9 | meta: { 10 | requiresAuth: true, 11 | }, 12 | children: [ 13 | { 14 | path: '', 15 | component: NewPatient, 16 | }, 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/components/user-options/userOptions.i18n.ts: -------------------------------------------------------------------------------- 1 | export enum userOptionsI18nPath { 2 | close = 'userOptions.close', 3 | profile = 'userOptions.profile', 4 | logout = 'userOptions.logout', 5 | } 6 | 7 | export const userOptionsPt = { 8 | close: 'Fechar menu', 9 | profile: 'Perfil', 10 | logout: 'Sair', 11 | } 12 | 13 | export const userOptionsEn = { 14 | close: 'Close menu', 15 | profile: 'Profile', 16 | logout: 'Log out', 17 | } 18 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | import i18n from './i18n' 3 | import manageDiets from './manage-diets' 4 | import patients from './patients' 5 | import popupMessage from './popup-message' 6 | import loadingPage from './loading-page' 7 | import user from './user' 8 | 9 | const modules = { 10 | i18n, 11 | manageDiets, 12 | patients, 13 | user, 14 | loadingPage, 15 | popupMessage, 16 | } 17 | 18 | export default createStore({ 19 | modules, 20 | }) 21 | -------------------------------------------------------------------------------- /src/store/user/types.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@/models/User.ts' 2 | 3 | export const USER_NAMESPACE = 'user' 4 | 5 | export interface UserState { 6 | user?: User | null 7 | } 8 | 9 | // getters 10 | export const UserGetters = { 11 | IS_AUTH: 'IS_AUTH', 12 | USER: 'USER', 13 | } 14 | 15 | // // actions 16 | export const UserActions = { 17 | LOGIN_GOOGLE: 'LOGIN_GOOGLE', 18 | } 19 | 20 | // mutations 21 | export const UserMutations = { 22 | CHANGE_USER: 'CHANGE_USER', 23 | } 24 | -------------------------------------------------------------------------------- /src/store/popup-message/types.ts: -------------------------------------------------------------------------------- 1 | export const POPUP_MESSAGE_NAMESPACE = 'popupMessage' 2 | 3 | export interface PopupMessageState { 4 | message: string 5 | mode: 'default' | 'success' | 'danger' 6 | } 7 | 8 | // getters 9 | export const PopupMessageGetters = { 10 | INFOS: 'INFOS', 11 | } 12 | 13 | // actions 14 | export const PopupMessageActions = { 15 | SHOW_MESSAGE: 'SHOW_MESSAGE', 16 | } 17 | 18 | // mutations 19 | export const PopupMessageMutations = { 20 | SHOW_MESSAGE: 'SHOW_MESSAGE', 21 | } 22 | -------------------------------------------------------------------------------- /src/components/form/box/BoxStyles.scss: -------------------------------------------------------------------------------- 1 | .box { 2 | &-description { 3 | font: var(--typography-body2-font); 4 | margin: var(--space-xs) 0; 5 | letter-spacing: var(--typography-body2-letter); 6 | line-height: 20px; 7 | } 8 | 9 | &-input { 10 | margin: var(--space-sm) 0; 11 | 12 | @media screen and (min-width: $screen-sm) { 13 | margin: var(--space) 0; 14 | } 15 | } 16 | 17 | &-btn-submit { 18 | margin: var(--space) auto; 19 | margin-top: var(--space-lg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/store/i18n/types.ts: -------------------------------------------------------------------------------- 1 | import { I18n } from '@/enums/i18n' 2 | 3 | export const I18N_NAMESPACE = 'i18n' 4 | 5 | export interface I18nState { 6 | language?: I18n.en | I18n.pt 7 | } 8 | 9 | // getters 10 | export const I18nGetters = { 11 | LANGUAGE: 'LANGUAGE', 12 | LABEL_TOGGLE: 'LABEL_TOGGLE', 13 | } 14 | 15 | // actions 16 | export const I18nActions = { 17 | CHANGE_LANGUAGE: 'CHANGE_LANGUAGE', 18 | } 19 | 20 | // mutations 21 | export const I18nMutations = { 22 | CHANGE_LANGUAGE: 'CHANGE_LANGUAGE', 23 | } 24 | -------------------------------------------------------------------------------- /src/router/default/index.ts: -------------------------------------------------------------------------------- 1 | import welcome from './welcome' 2 | import history from './history' 3 | import calculate from './calculate' 4 | import manageDiets from './manageDiets' 5 | import patients from './patients' 6 | const LDefault = () => 7 | import(/* webpackChunkName: "Default" */ '@/layouts/default/Default.vue') 8 | import { LayoutsName, LayoutsPaths } from '@/router/enums' 9 | 10 | export default { 11 | path: LayoutsPaths.default, 12 | name: LayoutsName.default, 13 | component: LDefault, 14 | children: [welcome, history, calculate, manageDiets, patients], 15 | } 16 | -------------------------------------------------------------------------------- /src/helpers/form/__tests__/mocks.ts: -------------------------------------------------------------------------------- 1 | export const STRING_WITHOUT_SPACE = 'nospace' 2 | export const STRING_WITH_SPACE = 'space inside' 3 | export const STRING_WITH_SPACE_RESULT = 'spaceinside' 4 | export const STRING_WITH_SIDE_SPACE = ' side space ' 5 | export const STRING_WITH_SIDE_SPACE_RESULT = 'sidespace' 6 | 7 | export const NUMBER_OVERFLOW = `123456789` 8 | export const NUMBER_OVERFLOW_RESULT = `123456` 9 | export const TEXT_OVERFLOW = `some text` 10 | export const TEXT_OVERFLOW_RESULT = `some t` 11 | 12 | export const WITH_COMMA = '0,5' 13 | export const WITHOUT_COMMA = '0.5' 14 | -------------------------------------------------------------------------------- /src/helpers/localStorage/localStorage.ts: -------------------------------------------------------------------------------- 1 | const storage = { 2 | get: (key: string): null | string => { 3 | const rawData: string | null = localStorage.getItem(key) 4 | 5 | if (!rawData) return null 6 | 7 | try { 8 | return JSON.parse(rawData) 9 | } catch (error) { 10 | return rawData 11 | } 12 | }, 13 | set: (key: string, data: T): void => { 14 | if (!data) return 15 | 16 | const dataHandled = typeof data !== 'string' ? JSON.stringify(data) : data 17 | 18 | localStorage.setItem(key, dataHandled) 19 | }, 20 | } 21 | 22 | export default storage 23 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router' 2 | import lDefault from './default' 3 | import { alt } from './alt' 4 | import firebase from 'firebase' 5 | 6 | const routes: Array = [lDefault, alt] 7 | 8 | const router = createRouter({ 9 | history: createWebHistory(process.env.BASE_URL), 10 | routes, 11 | }) 12 | 13 | router.beforeEach((to, from, next) => { 14 | const currentUser = firebase.auth().currentUser 15 | const requiresAuth = to.matched.some((record) => record.meta.requiresAuth) 16 | requiresAuth && !currentUser ? next('/') : next() 17 | }) 18 | 19 | export default router 20 | -------------------------------------------------------------------------------- /src/helpers/window-listeners/scroll.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, ref, onUnmounted } from 'vue' 2 | 3 | export function scroll() { 4 | const isShowElement = ref(false) 5 | const downScrollToShow = 450 6 | const handleScroll = () => 7 | (isShowElement.value = window.scrollY > downScrollToShow) 8 | const toTop = () => { 9 | document.body.scrollTop = 0 10 | document.documentElement.scrollTop = 0 11 | } 12 | const windowHandle = (state: string) => window[state]('scroll', handleScroll) 13 | 14 | onMounted(() => windowHandle('addEventListener')) 15 | onUnmounted(() => windowHandle('removeEventListener')) 16 | 17 | return { isShowElement, toTop } 18 | } 19 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Nutris", 3 | "short_name": "Nutris", 4 | "icons": [ 5 | { 6 | "src": "./img/icons/nutrisicon.png", 7 | "sizes": "64x64", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./img/icons/nutrisicon144.png", 12 | "sizes": "144x144", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "./index.html", 17 | "display": "standalone", 18 | "background_color": "#006064", 19 | "theme_color": "#006064", 20 | "scope": ".", 21 | "orientation": "portrait-primary", 22 | "description": "App para cálculos de dietas enterais e parenterais.", 23 | "dir": "ltr", 24 | "lang": "pt-BR" 25 | } 26 | -------------------------------------------------------------------------------- /src/models/CalculationHistory.ts: -------------------------------------------------------------------------------- 1 | import { Diet } from './Diet' 2 | 3 | export interface CalculationHistory { 4 | patient: { 5 | currentBody: number 6 | calGoal: number 7 | proteinGoal: number 8 | volumeReceived: number 9 | } 10 | diet: Diet 11 | createdAt: number | Date 12 | } 13 | 14 | export interface CalculatorIntermediate { 15 | parenteralResult: null 16 | patient: { 17 | body?: number 18 | currentBody: number 19 | calGoal: number 20 | proteinGoal: Number 21 | volumeReceived: Number 22 | } 23 | diet: { 24 | cal?: Number 25 | calAmount: Number 26 | protein?: Number 27 | proteinAmount: Number 28 | } 29 | createdAt?: number 30 | } 31 | -------------------------------------------------------------------------------- /src/store/patients/types.ts: -------------------------------------------------------------------------------- 1 | import { Patient } from '@/models/Patient' 2 | export const PATIENTS_NAMESPACE = 'patients' 3 | 4 | export interface PatientsState { 5 | patients: Patient[] 6 | } 7 | 8 | // getters 9 | export const PatientsGetters = { 10 | PATIENTS: 'PATIENTS', 11 | FIND_PATIENT: 'FIND_PATIENT', 12 | } 13 | 14 | // actions 15 | export const PatientsActions = { 16 | GET_PATIENTS: 'GET_PATIENTS', 17 | POST_PATIENTS: 'POST_PATIENTS', 18 | UPDATE_HISTORY_LOCALLY: 'UPDATE_HISTORY_LOCALLY', 19 | } 20 | 21 | // mutations 22 | export const PatientsMutations = { 23 | GET_PATIENTS: 'GET_PATIENTS', 24 | POST_PATIENTS: 'POST_PATIENTS', 25 | UPDATE_HISTORY_LOCALLY: 'UPDATE_HISTORY_LOCALLY', 26 | } 27 | -------------------------------------------------------------------------------- /src/store/manage-diets/types.ts: -------------------------------------------------------------------------------- 1 | import { Diet } from '@/models/Diet' 2 | 3 | export const MANAGE_DIETS_NAMESPACE = 'manageDiets' 4 | 5 | export interface ManageDietsState { 6 | diets: Diet[] 7 | } 8 | 9 | // getters 10 | export const ManageDietsGetters = { 11 | DIETS: 'DIETS', 12 | FIND_DIET: 'FIND_DIET', 13 | } 14 | 15 | // actions 16 | export const ManageDietsActions = { 17 | GET_DIETS: 'GET_DIETS', 18 | POST_DIETS: 'POST_DIETS', 19 | UPDATE_DIET: 'UPDATE_DIET', 20 | DELETE_DIET: 'DELETE_DIET', 21 | } 22 | 23 | // mutations 24 | export const ManageDietsMutations = { 25 | GET_DIETS: 'GET_DIETS', 26 | POST_DIETS: 'POST_DIETS', 27 | UPDATE_DIET: 'UPDATE_DIET', 28 | DELETE_DIET: 'DELETE_DIET', 29 | } 30 | -------------------------------------------------------------------------------- /src/components/logo/Logo.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 23 | 24 | 37 | -------------------------------------------------------------------------------- /src/helpers/date/date.ts: -------------------------------------------------------------------------------- 1 | import { Alerts } from '@/enums/alerts' 2 | import { Label } from '@/enums/label' 3 | import dayjs from 'dayjs' 4 | import relativeTime from 'dayjs/plugin/relativeTime' 5 | 6 | export function formatDate(date: string | Date | number) { 7 | const formatedDate = dayjs(date).format(Label.fullDate) 8 | const isInvalidDate = Alerts.DayjsInvalidDate 9 | 10 | return formatedDate !== isInvalidDate && formatedDate ? formatedDate : '' 11 | } 12 | 13 | export function timestamp(): number { 14 | return new Date().getTime() 15 | } 16 | 17 | export function getAge(date: string | number) { 18 | dayjs.extend(relativeTime) 19 | const isOnlyValue = true 20 | return dayjs(date) 21 | .fromNow(isOnlyValue) 22 | .split(' ')[0] 23 | } 24 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/components/nav/nav.i18n.ts: -------------------------------------------------------------------------------- 1 | export enum navI18nPath { 2 | // calculate = 'nav.calculate', 3 | patients = 'nav.patients', 4 | newPacient = 'nav.newPacient', 5 | manageDiets = 'nav.manageDiets', 6 | fallBackManageDiets = 'nav.fallBackManageDiets', 7 | } 8 | 9 | export const defaultNavPt = { 10 | // calculate: 'Cálculo avulso', 11 | patients: 'Meus Pacientes', 12 | newPacient: 'Novo Paciente', 13 | manageDiets: 'Minhas Dietas', 14 | fallBackManageDiets: 'Carregando Dietas', 15 | } 16 | 17 | export const defaultNavEn = { 18 | // calculate: 'Loose calculate', 19 | patients: 'My Patients', 20 | newPacient: 'New Pacient', 21 | manageDiets: 'My diets', 22 | fallBackManageDiets: 'Loading Dietas', 23 | } 24 | -------------------------------------------------------------------------------- /src/views/default/new-patient/NewPatient.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | 28 | -------------------------------------------------------------------------------- /src/router/default/enums.ts: -------------------------------------------------------------------------------- 1 | export enum Paths { 2 | welcome = '', 3 | calculate = '/calculate', 4 | history = '/history', 5 | patients = '/patients', 6 | newPatient = '/new-patient', 7 | 8 | // Manage Diets Routes 9 | manageDiets = '/manage-diets', 10 | 11 | // Standardization 12 | new = 'new', 13 | edit = 'edit/', 14 | details = 'details/', 15 | } 16 | 17 | export enum Names { 18 | welcome = 'Welcome', 19 | calculate = 'Calculate', 20 | history = 'History', 21 | newPatient = 'NewPatient', 22 | 23 | // Manage Diets Routes 24 | manageDiets = 'ManageDiets', 25 | manageDietsNew = 'ManageDietsNew', 26 | manageDietsEdit = 'ManageDietsEdit', 27 | 28 | // Patients Routes 29 | patients = 'Patients', 30 | patientsNew = 'PatientsNew', 31 | patientsHistory = 'PatientsHistory', 32 | } 33 | -------------------------------------------------------------------------------- /firebaseApp.ts: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app' 2 | import 'firebase/firestore' 3 | require('firebase/auth') 4 | 5 | const FIREBASE_URL = '.firebaseapp.com' 6 | const config = { 7 | apiKey: process.env.VUE_APP_FIREBASE_API_KEY, 8 | authDomain: process.env.VUE_APP_FIREBASE_PROJECT_ID + FIREBASE_URL, 9 | databaseURL: 10 | 'https://' + process.env.VUE_APP_FIREBASE_PROJECT_ID + FIREBASE_URL, 11 | projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID, 12 | storageBucket: process.env.VUE_APP_FIREBASE_PROJECT_ID + '.appspot.com', 13 | appId: process.env.VUE_APP_FIREBASE_ID, 14 | } 15 | 16 | const FirebaseApp: any = firebase.initializeApp(config) 17 | const firestore = firebase.firestore() 18 | 19 | FirebaseApp.db = firestore 20 | FirebaseApp.db.enablePersistence({ synchronizeTabs: true }) 21 | 22 | export default FirebaseApp 23 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/components/nav/navMenu copy.ts: -------------------------------------------------------------------------------- 1 | import { navI18nPath } from '@/layouts/default/components/header/components/nav/nav.i18n.ts' 2 | import { Paths } from '@/router/default/enums' 3 | 4 | export const navOptions = [ 5 | // I will think if it is necessary 6 | // { 7 | // path: Paths.calculate, 8 | // i18n: navI18nPath.calculate, 9 | // icon: 'nutris-ticket', 10 | // }, 11 | { 12 | path: Paths.patients, 13 | i18n: navI18nPath.patients, 14 | icon: 'nutris-users', 15 | }, 16 | { 17 | path: Paths.patients + '/new', 18 | i18n: navI18nPath.newPacient, 19 | icon: 'nutris-user', 20 | }, 21 | { 22 | path: Paths.manageDiets, 23 | i18n: navI18nPath.manageDiets, 24 | icon: 'nutris-checklist', 25 | callBack: 'diets', 26 | loading: false, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /src/components/loading-page/LoadingPage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 35 | -------------------------------------------------------------------------------- /src/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase' 2 | import user from '@/store/user/index.ts' 3 | import { LayoutsPaths } from '@/router/enums' 4 | import { Paths } from '@/router/alt/enums' 5 | 6 | export const signInWithGoogle = () => { 7 | const provider = new firebase.auth.GoogleAuthProvider() 8 | return firebase 9 | .auth() 10 | .signInWithPopup(provider) 11 | .then(() => { 12 | return firebase.auth().currentUser 13 | }) 14 | .catch((error: Error) => error) 15 | } 16 | 17 | export const logout = () => { 18 | firebase 19 | .auth() 20 | .signOut() 21 | .then(() => { 22 | firebase.auth().currentUser 23 | user.mutations.CHANGE_USER({}) 24 | const homePath = LayoutsPaths.alt + Paths.home 25 | window.location.replace(homePath) 26 | }) 27 | .catch((error: Error) => error) 28 | } 29 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | plugins: ['prettier'], 7 | extends: [ 8 | 'plugin:vue/vue3-essential', 9 | 'eslint:recommended', 10 | '@vue/typescript/recommended', 11 | '@vue/prettier', 12 | '@vue/prettier/@typescript-eslint', 13 | 'prettier', 14 | ], 15 | parserOptions: { 16 | ecmaVersion: 2020, 17 | }, 18 | rules: { 19 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 21 | '@typescript-eslint/ban-ts-ignore': 'off', 22 | }, 23 | overrides: [ 24 | { 25 | files: [ 26 | '**/__tests__/*.{j,t}s?(x)', 27 | '**/tests/unit/**/*.spec.{j,t}s?(x)', 28 | ], 29 | env: { 30 | jest: true, 31 | }, 32 | }, 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /src/components/paper/Paper.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 38 | -------------------------------------------------------------------------------- /src/store/loading-page/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LoadingPageState, 3 | LoadingPageGetters, 4 | LoadingPageActions, 5 | LoadingPageMutations, 6 | } from './types' 7 | import { ActionTree, MutationTree } from 'vuex' 8 | 9 | const state: LoadingPageState = { 10 | loading: false, 11 | } 12 | 13 | const getters = { 14 | [LoadingPageGetters.LOADING](state: LoadingPageState) { 15 | return state.loading 16 | }, 17 | } 18 | 19 | const actions: ActionTree = { 20 | [LoadingPageActions.TOGGLE]({ commit }, value) { 21 | commit(LoadingPageMutations.TOGGLE, value) 22 | }, 23 | } 24 | 25 | const mutations: MutationTree = { 26 | [LoadingPageMutations.TOGGLE](state, value) { 27 | state.loading = value 28 | }, 29 | } 30 | 31 | export default { 32 | namespaced: true, 33 | state, 34 | getters, 35 | actions, 36 | mutations, 37 | } 38 | -------------------------------------------------------------------------------- /src/views/default/manage-diets/ManageDiets.i18n.ts: -------------------------------------------------------------------------------- 1 | export enum manageDietsI18nPath { 2 | ariaLabelAddNewDietBtn = 'manageDiets.ariaLabelAddNewDietBtn', 3 | ariaLabelAddNewDietIcon = 'manageDiets.ariaLabelAddNewDietIcon', 4 | emptyStateTitle = 'manageDiets.emptyStateTitle', 5 | emptyStateAction = 'manageDiets.emptyStateAction', 6 | } 7 | 8 | export const manageDietsPt = { 9 | // fixed btn 10 | ariaLabelAddNewDietBtn: 'Adicionar nova dieta.', 11 | ariaLabelAddNewDietIcon: 'Sinal de mais.', 12 | // empty state 13 | emptyStateTitle: 'Gostaria de cadastrar sua primeira dieta?', 14 | emptyStateAction: 'Quero Cadastrar!!', 15 | } 16 | 17 | export const manageDietsEn = { 18 | // fixed btn 19 | ariaLabelAddNewDietBtn: 'Add new diet.', 20 | ariaLabelAddNewDietIcon: 'Plus icon.', 21 | // empty state 22 | emptyStateTitle: 'Would like to add your first diet?', 23 | emptyStateAction: 'I wanna add it!!', 24 | } 25 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 27 | 43 | -------------------------------------------------------------------------------- /src/assets/styles/class.scss: -------------------------------------------------------------------------------- 1 | @import './screen.scss'; 2 | @import './variables.scss'; 3 | 4 | /* 5 | Global class starts with g- prefix 6 | 7 | 1. elements position 8 | 2. screen 9 | */ 10 | 11 | /* 1. elements position */ 12 | .g-center--x, 13 | .g-center--y { 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | } 18 | 19 | .g-center--y { 20 | flex-direction: column; 21 | } 22 | 23 | .g-cursor { 24 | cursor: pointer; 25 | } 26 | 27 | /* 2. screen */ 28 | .g-container { 29 | padding-right: $padding-small; 30 | padding-left: $padding-small; 31 | margin: 0 auto; 32 | width: 100%; 33 | 34 | @media screen and (min-width: $screen-sm) { 35 | padding-right: $padding; 36 | padding-left: $padding; 37 | } 38 | 39 | &-small { 40 | max-width: $screen-sm; 41 | } 42 | 43 | &-medium { 44 | max-width: $screen-md; 45 | } 46 | 47 | &-large { 48 | max-width: $screen-xlg; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "sourceMap": true, 15 | "baseUrl": ".", 16 | "types": [ 17 | "webpack-env", 18 | "jest" 19 | ], 20 | "paths": { 21 | "@/*": [ 22 | "src/*" 23 | ] 24 | }, 25 | "lib": [ 26 | "esnext", 27 | "dom", 28 | "dom.iterable", 29 | "scripthost" 30 | ] 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.tsx", 35 | "src/**/*.vue", 36 | "tests/**/*.ts", 37 | "tests/**/*.tsx" 38 | ], 39 | "exclude": [ 40 | "node_modules" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/components/nav/navMenu.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | import { navI18nPath } from '@/layouts/default/components/header/components/nav/nav.i18n.ts' 3 | import { Paths } from '@/router/default/enums' 4 | 5 | export function navMenu() { 6 | const navOptions = reactive([ 7 | // I will think if it is necessary 8 | // { 9 | // path: Paths.calculate, 10 | // i18n: navI18nPath.calculate, 11 | // icon: 'nutris-ticket', 12 | // }, 13 | { 14 | path: Paths.patients, 15 | i18n: navI18nPath.patients, 16 | icon: 'nutris-users', 17 | }, 18 | { 19 | path: Paths.patients + '/new', 20 | i18n: navI18nPath.newPacient, 21 | icon: 'nutris-user', 22 | }, 23 | { 24 | path: Paths.manageDiets, 25 | i18n: navI18nPath.manageDiets, 26 | fallback: navI18nPath.fallBackManageDiets, 27 | icon: 'nutris-checklist', 28 | loading: false, 29 | }, 30 | ]) 31 | return { navOptions } 32 | } 33 | -------------------------------------------------------------------------------- /src/layouts/alt/Alt.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 46 | -------------------------------------------------------------------------------- /src/layouts/alt/header/Header.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | 42 | -------------------------------------------------------------------------------- /src/registerServiceWorker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker' 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | 'App is being served from cache by a service worker.\n' + 10 | 'For more details, visit https://goo.gl/AFskqB' 11 | ) 12 | }, 13 | registered() { 14 | console.log('Service worker has been registered.') 15 | }, 16 | cached() { 17 | console.log('Content has been cached for offline use.') 18 | }, 19 | updatefound() { 20 | console.log('New content is downloading.') 21 | }, 22 | updated() { 23 | console.log('New content is available; please refresh.') 24 | }, 25 | offline() { 26 | console.log( 27 | 'No internet connection found. App is running in offline mode.' 28 | ) 29 | }, 30 | error(error) { 31 | console.error('Error during service worker registration:', error) 32 | }, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /src/store/user/index.ts: -------------------------------------------------------------------------------- 1 | import { UserState, UserGetters, UserMutations, UserActions } from './types' 2 | import { MutationTree, ActionTree } from 'vuex' 3 | import { signInWithGoogle } from '@/services/auth.service.ts' 4 | import { User } from '@/models/User' 5 | 6 | const state: UserState = { 7 | user: null, 8 | } 9 | 10 | const getters = { 11 | [UserGetters.IS_AUTH](state: UserState) { 12 | return state.user?.uid 13 | }, 14 | [UserGetters.USER](state: UserState) { 15 | return state.user 16 | }, 17 | } 18 | 19 | const actions: ActionTree = { 20 | async [UserActions.LOGIN_GOOGLE]({ commit }) { 21 | const user = await signInWithGoogle() 22 | 23 | !user 24 | ? console.error('error de login') 25 | : commit(UserMutations.CHANGE_USER, user) 26 | }, 27 | } 28 | 29 | const mutations: MutationTree = { 30 | [UserMutations.CHANGE_USER](state, user: User) { 31 | state.user = user 32 | }, 33 | } 34 | 35 | export default { 36 | namespaced: true, 37 | state, 38 | getters, 39 | actions, 40 | mutations, 41 | } 42 | -------------------------------------------------------------------------------- /src/components/empty-state/EmptyState.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | 24 | 46 | -------------------------------------------------------------------------------- /src/helpers/localStorage/__tests__/localStorage.spec.ts: -------------------------------------------------------------------------------- 1 | import storage from '../localStorage' 2 | import * as mocks from './mocks' 3 | 4 | const actionMock = { 5 | someData: mocks.SOME_DATA, 6 | } 7 | 8 | const localStorageMock = (() => { 9 | return { 10 | getItem(key: string): T | null { 11 | return actionMock[key] || null 12 | }, 13 | setItem(key: string, value: any) { 14 | actionMock[key] = value 15 | }, 16 | } 17 | })() 18 | 19 | Object.defineProperty(window, 'localStorage', { 20 | value: localStorageMock, 21 | }) 22 | 23 | describe('LocalStorage helper', () => { 24 | it('Should get item from local storage', () => { 25 | expect(storage.get('someData')).toBe(mocks.SOME_DATA) 26 | expect(storage.get('notExist')).toBeNull() 27 | expect(typeof storage.get('someData')).toBe('string') 28 | 29 | storage.set('someData', mocks.ARRAY_DATA) 30 | expect(storage.get('someData')).toEqual(mocks.ARRAY_DATA) 31 | 32 | storage.set('someData', mocks.COMPLEX_DATA) 33 | expect(storage.get('someData')).toEqual(mocks.COMPLEX_DATA) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /src/helpers/form/__tests__/form.spec.ts: -------------------------------------------------------------------------------- 1 | import { changeToDot, maxLength, removeAllEmptySpace } from '../form' 2 | import * as mocks from './mocks' 3 | 4 | describe('Date', () => { 5 | it('Should return string without empty space', () => { 6 | expect(removeAllEmptySpace(mocks.STRING_WITHOUT_SPACE)).toBe( 7 | mocks.STRING_WITHOUT_SPACE 8 | ) 9 | expect(removeAllEmptySpace(mocks.STRING_WITH_SIDE_SPACE)).toBe( 10 | mocks.STRING_WITH_SIDE_SPACE_RESULT 11 | ) 12 | expect(removeAllEmptySpace(mocks.STRING_WITH_SPACE)).toBe( 13 | mocks.STRING_WITH_SPACE_RESULT 14 | ) 15 | }) 16 | it('Should validate max length', () => { 17 | expect(maxLength(mocks.NUMBER_OVERFLOW, 6)).toBe( 18 | mocks.NUMBER_OVERFLOW_RESULT 19 | ) 20 | expect(maxLength('1354', 6)).toBe('1354') 21 | expect(maxLength(mocks.TEXT_OVERFLOW, 6)).toBe(mocks.TEXT_OVERFLOW_RESULT) 22 | }) 23 | 24 | it('Should change comma to dot', () => { 25 | expect(changeToDot(mocks.WITH_COMMA)).toBe(mocks.WITHOUT_COMMA) 26 | expect(changeToDot(mocks.WITHOUT_COMMA)).toBe(mocks.WITHOUT_COMMA) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 open-ish 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/store/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { I18nState, I18nGetters, I18nActions, I18nMutations } from './types' 2 | import { I18n } from '@/enums/i18n' 3 | import { ActionTree, MutationTree } from 'vuex' 4 | 5 | const state: I18nState = { 6 | language: I18n.pt, 7 | } 8 | 9 | const getters = { 10 | [I18nGetters.LANGUAGE](state: I18nState) { 11 | return state.language 12 | }, 13 | [I18nGetters.LABEL_TOGGLE](state: I18nState) { 14 | const isPt = state.language === I18n.pt 15 | return { 16 | label: isPt ? 'Inglês' : 'Portuguese', 17 | short: isPt ? I18n.en : I18n.pt, 18 | } 19 | }, 20 | } 21 | 22 | const actions: ActionTree = { 23 | [I18nActions.CHANGE_LANGUAGE]( 24 | { commit }, 25 | newLanguage: I18nState['language'] 26 | ) { 27 | commit(I18nMutations.CHANGE_LANGUAGE, newLanguage) 28 | }, 29 | } 30 | 31 | const mutations: MutationTree = { 32 | [I18nMutations.CHANGE_LANGUAGE](state, newLanguage: I18nState['language']) { 33 | state.language = newLanguage 34 | }, 35 | } 36 | 37 | export default { 38 | namespaced: true, 39 | state, 40 | getters, 41 | actions, 42 | mutations, 43 | } 44 | -------------------------------------------------------------------------------- /src/components/form/box/Box.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | 46 | -------------------------------------------------------------------------------- /src/test/setup.ts: -------------------------------------------------------------------------------- 1 | import { mount, shallowMount, VueWrapper } from '@vue/test-utils' 2 | import { GlobalMountOptions } from '@vue/test-utils/dist/types' 3 | import store from '@/store' 4 | import router from '@/router' 5 | import { DefineComponent } from 'vue' 6 | import { Vue } from 'vue-class-component' 7 | import { i18n } from '@/i18n/index.ts' 8 | 9 | interface TestHelper extends GlobalMountOptions { 10 | shallow?: boolean 11 | props?: { [key: string]: unknown } 12 | } 13 | 14 | type setupFunction = ( 15 | component: DefineComponent, 16 | config?: TestHelper 17 | ) => VueWrapper 18 | 19 | export const setup: setupFunction = ( 20 | component, 21 | { shallow, props, stubs, components } = { shallow: false } 22 | ) => { 23 | router.isReady() 24 | 25 | const wrapper = !shallow 26 | ? mount(component, { 27 | props: props, 28 | global: { 29 | plugins: [store, router, i18n], 30 | stubs, 31 | components, 32 | }, 33 | }) 34 | : shallowMount(component, { 35 | props: props, 36 | global: { 37 | plugins: [store, router, i18n], 38 | stubs, 39 | components, 40 | }, 41 | }) 42 | 43 | return wrapper 44 | } 45 | -------------------------------------------------------------------------------- /src/i18n/test-ignore.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n' 2 | 3 | export const i18n = createI18n({ 4 | locale: 'en-US', 5 | datetimeFormats: { 6 | 'en-US': { 7 | long: { 8 | year: 'numeric', 9 | month: '2-digit', 10 | day: '2-digit', 11 | hour: '2-digit', 12 | minute: '2-digit', 13 | second: '2-digit', 14 | }, 15 | }, 16 | 'ja-JP-u-ca-japanese': { 17 | long: { 18 | era: 'long', 19 | year: 'numeric', 20 | month: 'numeric', 21 | day: 'numeric', 22 | hour: 'numeric', 23 | minute: 'numeric', 24 | second: 'numeric', 25 | weekday: 'long', 26 | hour12: true, 27 | timeZoneName: 'long', 28 | }, 29 | }, 30 | }, 31 | messages: { 32 | en: { 33 | message: { 34 | language: 'English', 35 | quantity: 'Quantity', 36 | list: 'hello, {0}!', 37 | named: 'hello, {name}!', 38 | linked: '@:message.named How are you?', 39 | plural: 'no bananas | {n} banana | {n} bananas', 40 | }, 41 | }, 42 | ja: { 43 | message: { 44 | language: '日本語', 45 | list: 'こんにちは、{0}!', 46 | named: 'こんにちは、{name}!', 47 | linked: '@:message.named ごきげんいかが?', 48 | }, 49 | }, 50 | }, 51 | }) 52 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | 17 | 18 | 19 | 20 | 24 | 28 | Nutris 29 | 30 | 31 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /src/components/modal/Modal.vue: -------------------------------------------------------------------------------- 1 | 8 | 21 | 60 | -------------------------------------------------------------------------------- /src/store/popup-message/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PopupMessageState, 3 | PopupMessageGetters, 4 | PopupMessageActions, 5 | PopupMessageMutations, 6 | } from './types' 7 | import { ActionTree, MutationTree } from 'vuex' 8 | 9 | const state: PopupMessageState = { 10 | message: '', 11 | mode: 'default', 12 | } 13 | 14 | const getters = { 15 | [PopupMessageGetters.INFOS](state: PopupMessageState) { 16 | return state 17 | }, 18 | } 19 | 20 | const actions: ActionTree = { 21 | [PopupMessageActions.SHOW_MESSAGE]( 22 | { commit }, 23 | { 24 | message, 25 | time, 26 | mode, 27 | }: { 28 | message: PopupMessageState['message'] 29 | time: number 30 | mode: PopupMessageState['mode'] 31 | } 32 | ) { 33 | commit(PopupMessageMutations.SHOW_MESSAGE, { message, mode }) 34 | setTimeout(() => { 35 | const resetState = '' 36 | commit(PopupMessageMutations.SHOW_MESSAGE, { 37 | message: resetState, 38 | mode: 'default', 39 | }) 40 | }, time) 41 | }, 42 | } 43 | 44 | const mutations: MutationTree = { 45 | [PopupMessageMutations.SHOW_MESSAGE](state, { message, mode }) { 46 | state.message = message 47 | state.mode = mode 48 | }, 49 | } 50 | 51 | export default { 52 | namespaced: true, 53 | state, 54 | getters, 55 | actions, 56 | mutations, 57 | } 58 | -------------------------------------------------------------------------------- /src/components/alert/Alert.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 31 | 32 | 54 | -------------------------------------------------------------------------------- /src/views/alt/home/homei18n.ts: -------------------------------------------------------------------------------- 1 | export enum defaultHomeI18nPath { 2 | title = 'home.title', 3 | textOne = 'home.textOne', 4 | textTwo = 'home.textTwo', 5 | purpose = 'home.purpose', 6 | purposeText = 'home.purposeText', 7 | purposeButton = 'home.purposeButton', 8 | } 9 | 10 | export const defaultHomePt = { 11 | title: 'Cálculos de dietas parenteral e enteral', 12 | textOne: 13 | ' é um Web App para cálculos de dietas parenteral e enteral. Agora ficará facil quantificar a quantidade protéica e calórica de seus pacientes', 14 | textTwo: 15 | 'E como é um Web App, você não precisa baixa-lo e pode economizar a mémoriade seu celular.', 16 | purpose: 'Fins didáticos', 17 | purposeText: 18 | 'é um projeto open-source criado para apoiar a comunidade de tecnologia. Você pode conhecer mais sobre o', 19 | purposeButton: ' projeto no repositório da open-ish.', 20 | } 21 | 22 | export const defaultHomeEn = { 23 | title: 'Calculations of parenteral and enteral diets', 24 | textOne: 25 | ' is a Web App for calculating parenteral and enteral diets. Now it will be easy to quantify the protein and caloric amount of your patients', 26 | textTwo: 27 | "As it is a Web App, you do not need to download it and you can save your phone's memory.", 28 | purpose: 'Didactic purposes', 29 | purposeText: 30 | 'is an open-source project created to support the community of technology. You can learn more about the', 31 | purposeButton: ' project at open-ish repository.', 32 | } 33 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/Header.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 35 | 36 | 55 | -------------------------------------------------------------------------------- /src/components/popup-message/PopupMessage.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | 40 | 67 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/components/user-options/resize.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from 'vue' 2 | import { getCssVariableValue } from '@/helpers/styles.ts' 3 | 4 | type displayCreated = (value?: string | undefined) => string 5 | type displayFactory = ( 6 | element: ElementCSSInlineStyle['style'] 7 | ) => displayCreated 8 | 9 | export function resize() { 10 | const none = 'none' 11 | const initial = 'initial' 12 | const flex = 'flex' 13 | const menu = ref() 14 | const mask = ref() 15 | const createDisplay: displayFactory = (element) => (value) => 16 | (element.display = value ?? element.display) 17 | 18 | const isSmallScreen = (): boolean => 19 | Number(`${window.innerWidth}`) < 20 | Number(getCssVariableValue('--screen-sm').replace('px', '')) 21 | 22 | let menuDisplay: displayCreated = () => '' 23 | let maskDisplay: displayCreated = () => '' 24 | 25 | onMounted(() => { 26 | menuDisplay = createDisplay((menu.value as HTMLElement).style) 27 | maskDisplay = createDisplay((mask.value as HTMLElement).style) 28 | 29 | window.addEventListener('resize', () => { 30 | const isSmallScreenAndMaskNone = isSmallScreen() && maskDisplay() === none 31 | 32 | isSmallScreenAndMaskNone && menuDisplay(none) 33 | !isSmallScreen() && maskDisplay(none) && menuDisplay(flex) 34 | }) 35 | }) 36 | 37 | const toggleMenu = () => { 38 | if (!isSmallScreen()) return 39 | 40 | const isMenuNone = menuDisplay() !== none && menuDisplay() !== '' 41 | isMenuNone 42 | ? menuDisplay(none) && maskDisplay(none) 43 | : menuDisplay(flex) && maskDisplay(initial) 44 | } 45 | return { toggleMenu, menu, mask, isSmallScreen } 46 | } 47 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | // setup 2 | import { createApp } from 'vue' 3 | import App from './App.vue' 4 | import './registerServiceWorker' 5 | import router from './router' 6 | import store from './store' 7 | import FirebaseApp from '@/../firebaseApp' 8 | 9 | import { getCssVariableValue } from '@/helpers/styles.ts' 10 | import { User } from '@/models/User.ts' 11 | import '@/assets/styles/index.scss' 12 | import { i18n } from '@/i18n/index.ts' 13 | import { UserMutations, USER_NAMESPACE } from './store/user/types' 14 | import storage from './helpers/localStorage/localStorage' 15 | import { storageKey } from './enums/storageKeys' 16 | import { 17 | ManageDietsMutations, 18 | MANAGE_DIETS_NAMESPACE, 19 | } from './store/manage-diets/types' 20 | 21 | const init = () => 22 | createApp(App) 23 | .use(store) 24 | .use(router) 25 | .use(i18n) 26 | .mount('#app') 27 | 28 | const setUser = USER_NAMESPACE + '/' + UserMutations.CHANGE_USER 29 | const setDiets = MANAGE_DIETS_NAMESPACE + '/' + ManageDietsMutations.GET_DIETS 30 | 31 | FirebaseApp.auth().onAuthStateChanged(async (user: User) => { 32 | const diets = storage.get(storageKey.diets) 33 | diets && store.commit(setDiets, diets) 34 | await store.commit(setUser, user) 35 | init() 36 | }) 37 | 38 | const BRAND_COLOR = getCssVariableValue('--brand-color') 39 | const BRAND_COLOR_LIGHTEN = getCssVariableValue('--brand-color-lighten') 40 | const WELCOME_DEV = '%c Hi, Nutris Dev!! ​😁🍉​' 41 | const WELCOME_DEV_STYLES = `font-size: 14px; color: ${BRAND_COLOR_LIGHTEN}; border: 0.5px solid ${BRAND_COLOR}; border-radius: 2px` 42 | 43 | console.log( 44 | process.env.VUE_APP_FIREBASE_PROJECT_ID?.includes('dev') && WELCOME_DEV, 45 | WELCOME_DEV_STYLES 46 | ) 47 | -------------------------------------------------------------------------------- /src/services/calculate.service.ts: -------------------------------------------------------------------------------- 1 | import FirebaseApp from '@/../firebaseApp' 2 | import { names } from '@/enums/collections/firebase' 3 | import { 4 | CalculationHistory, 5 | CalculatorIntermediate, 6 | } from '@/models/CalculationHistory' 7 | import { Snapshot } from '@/models/firebase' 8 | import store from '@/store/' 9 | import { UserGetters, USER_NAMESPACE } from '@/store/user/types' 10 | 11 | const userNamespace = USER_NAMESPACE + '/' + UserGetters.USER 12 | 13 | interface GetCalculationHistoryTypes { 14 | historyId: string 15 | } 16 | export const getCalculationHistory = ({ 17 | historyId, 18 | }: GetCalculationHistoryTypes) => { 19 | const userId = store.getters[userNamespace]?.uid 20 | 21 | return FirebaseApp.db 22 | .collection(names.users) 23 | .doc(userId) 24 | .collection(names.history) 25 | .doc(historyId) 26 | .collection(names.calculationHistory) 27 | .get() 28 | .then((querySnapshot: Snapshot[]) => { 29 | const history: CalculationHistory[] = [] 30 | querySnapshot.forEach((doc) => { 31 | history.push({ ...doc.data() }) 32 | }) 33 | 34 | return history 35 | }) 36 | .catch((error: Error) => error.message) 37 | } 38 | 39 | interface PostCalculationHistoryTypes extends GetCalculationHistoryTypes { 40 | data: CalculationHistory | CalculatorIntermediate 41 | } 42 | export const postCalculationHistory = ({ 43 | historyId, 44 | data, 45 | }: PostCalculationHistoryTypes) => { 46 | const userId = store.getters[userNamespace]?.uid 47 | 48 | return FirebaseApp.db 49 | .collection(names.users) 50 | .doc(userId) 51 | .collection(names.history) 52 | .doc(historyId) 53 | .collection(names.calculationHistory) 54 | .add(data) 55 | .then((doc: Snapshot) => { 56 | return doc.id 57 | }) 58 | .catch((error: Error) => error.message) 59 | } 60 | -------------------------------------------------------------------------------- /src/assets/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* https://dev.to/hankchizljaw/a-modern-css-reset-6p3 */ 2 | 3 | /* Box sizing rules */ 4 | *, 5 | *::before, 6 | *::after { 7 | box-sizing: border-box; 8 | } 9 | 10 | /* Remove default padding */ 11 | ul[class], 12 | ol[class] { 13 | padding: 0; 14 | } 15 | 16 | /* Remove default margin */ 17 | body, 18 | h1, 19 | h2, 20 | h3, 21 | h4, 22 | p, 23 | ul[class], 24 | ol[class], 25 | li, 26 | figure, 27 | figcaption, 28 | blockquote, 29 | dl, 30 | dd { 31 | margin: 0; 32 | } 33 | 34 | html { 35 | scroll-behavior: smooth; 36 | } 37 | 38 | /* Set core body defaults */ 39 | body { 40 | min-height: 100vh; 41 | scroll-behavior: smooth; 42 | text-rendering: optimizeSpeed; 43 | -webkit-font-smoothing: antialiased; 44 | line-height: 1.5; 45 | } 46 | 47 | /* Remove list styles on ul, ol elements with a class attribute */ 48 | ul[class], 49 | ol[class] { 50 | list-style: none; 51 | } 52 | 53 | /* A elements that don't have a class get default styles */ 54 | a:not([class]) { 55 | text-decoration-skip-ink: auto; 56 | } 57 | 58 | /* Make images easier to work with */ 59 | img { 60 | max-width: 100%; 61 | display: block; 62 | } 63 | 64 | /* Inherit fonts for inputs and buttons */ 65 | input, 66 | button, 67 | textarea, 68 | select { 69 | font: inherit; 70 | } 71 | 72 | /* Remove all animations and transitions for people that prefer not to see them */ 73 | @media (prefers-reduced-motion: reduce) { 74 | * { 75 | animation-duration: 0.01ms !important; 76 | animation-iteration-count: 1 !important; 77 | transition-duration: 0.01ms !important; 78 | scroll-behavior: auto !important; 79 | } 80 | } 81 | 82 | ol, 83 | ul { 84 | list-style: none; 85 | } 86 | 87 | a { 88 | color: var(--text-color); 89 | cursor: pointer; 90 | text-decoration-line: none; 91 | } 92 | 93 | label { 94 | font-size: 12px; 95 | } 96 | -------------------------------------------------------------------------------- /src/views/default/patients/Patients.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 61 | 69 | -------------------------------------------------------------------------------- /src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | // All i18n configuration will be imported here 2 | 3 | /* 4 | ================== 5 | 1. Global 6 | 2. Default Layout 7 | 3. Components 8 | 4. Pages 9 | ================== 10 | */ 11 | import { createI18n } from 'vue-i18n' 12 | 13 | /* 14 | ================== 15 | 2. Default Layout 16 | ================== 17 | */ 18 | import { 19 | defaultNavEn, 20 | defaultNavPt, 21 | } from '@/layouts/default/components/header/components/nav/nav.i18n.ts' 22 | import { 23 | userOptionsEn, 24 | userOptionsPt, 25 | } from '@/layouts/default/components/header/components/user-options/userOptions.i18n.ts' 26 | import { 27 | defaultFooterEn, 28 | defaultFooterPt, 29 | } from '@/layouts/default/components/footer/footer.i18n.ts' 30 | import { defaultHomeEn, defaultHomePt } from '@/views/alt/home/homei18n.ts' 31 | 32 | /* 33 | ================== 34 | 3. Components 35 | ================== 36 | */ 37 | import { 38 | fixedBtnEn, 39 | fixedBtnPt, 40 | } from '@/components/form/button/FixedBtn.i18n.ts' 41 | 42 | /* 43 | ================== 44 | 4. Pages 45 | ================== 46 | */ 47 | import { 48 | manageDietsEn, 49 | manageDietsPt, 50 | } from '@/views/default/manage-diets/ManageDiets.i18n.ts' 51 | export const i18n = createI18n({ 52 | legacy: true, 53 | locale: 'pt', 54 | // texts to translate 55 | messages: { 56 | en: { 57 | // 2. Default Layout 58 | nav: defaultNavEn, 59 | userOptions: userOptionsEn, 60 | home: defaultHomeEn, 61 | footer: defaultFooterEn, 62 | // 3. Components 63 | fixedBtn: fixedBtnEn, 64 | // 4. Pages 65 | manageDiets: manageDietsEn, 66 | }, 67 | pt: { 68 | // 2. Default Layout 69 | nav: defaultNavPt, 70 | userOptions: userOptionsPt, 71 | home: defaultHomePt, 72 | footer: defaultFooterPt, 73 | // 3. Components 74 | fixedBtn: fixedBtnPt, 75 | // 4. Pages 76 | manageDiets: manageDietsPt, 77 | }, 78 | }, 79 | }) 80 | -------------------------------------------------------------------------------- /src/router/default/manageDiets.ts: -------------------------------------------------------------------------------- 1 | import { RouteLocationNormalized } from 'vue-router' 2 | 3 | import { Names, Paths } from '@/router/default/enums' 4 | const Index = () => 5 | import(/* webpackChunkName: "ManageDiets" */ '@/layouts/Index.vue') 6 | const ManageDiets = () => 7 | import( 8 | /* webpackChunkName: "ManageDiets" */ '@/views/default/manage-diets/ManageDiets.vue' 9 | ) 10 | const Form = () => 11 | import( 12 | /* webpackChunkName: "Form" */ '@/views/default/manage-diets/components/form/Form.vue' 13 | ) 14 | import store from '@/store' 15 | import { 16 | ManageDietsActions, 17 | // ManageDietsGetters, 18 | MANAGE_DIETS_NAMESPACE, 19 | } from '@/store/manage-diets/types' 20 | import { 21 | LOADING_PAGE_NAMESPACE, 22 | LoadingPageActions, 23 | } from '@/store/loading-page/types' 24 | 25 | // const diets = MANAGE_DIETS_NAMESPACE + '/' + ManageDietsGetters.DIETS 26 | const getDiets = MANAGE_DIETS_NAMESPACE + '/' + ManageDietsActions.GET_DIETS 27 | const loadingSpace = LOADING_PAGE_NAMESPACE + '/' + LoadingPageActions.TOGGLE 28 | 29 | export default { 30 | path: Paths.manageDiets, 31 | name: Names.manageDiets, 32 | component: Index, 33 | meta: { 34 | requiresAuth: true, 35 | }, 36 | beforeEnter: ( 37 | to: RouteLocationNormalized, 38 | from: RouteLocationNormalized, 39 | next: Function 40 | ) => { 41 | // !store.getters[diets].length && store.dispatch(getDiets) 42 | store.dispatch(loadingSpace, true) 43 | store.dispatch(getDiets).then(() => { 44 | store.dispatch(loadingSpace, false) 45 | next() 46 | }) 47 | }, 48 | children: [ 49 | { 50 | path: '', 51 | component: ManageDiets, 52 | }, 53 | { 54 | path: Paths.new, 55 | name: Names.manageDietsNew, 56 | component: Form, 57 | }, 58 | { 59 | path: Paths.edit + ':id', 60 | name: Names.manageDietsEdit, 61 | component: Form, 62 | props: true, 63 | }, 64 | ], 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nutris", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "test:unit": "vue-cli-service test:unit", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "dependencies": { 12 | "@open-ish/nutris-roles": "^1.0.0", 13 | "core-js": "^3.6.5", 14 | "dayjs": "^1.9.4", 15 | "firebase": "^6.6.1", 16 | "firebase-admin": "^9.2.0", 17 | "register-service-worker": "^1.7.1", 18 | "vue": "^3.0.0-0", 19 | "vue-class-component": "^8.0.0-0", 20 | "vue-i18n": "^9.0.0-beta.3", 21 | "vue-property-decorator": "^9.0.0", 22 | "vue-router": "^4.0.0-0", 23 | "vuex": "^4.0.0-0" 24 | }, 25 | "devDependencies": { 26 | "@types/jest": "^24.0.19", 27 | "@typescript-eslint/eslint-plugin": "^2.33.0", 28 | "@typescript-eslint/parser": "^2.33.0", 29 | "@vue/cli-plugin-babel": "~4.5.0", 30 | "@vue/cli-plugin-eslint": "~4.5.0", 31 | "@vue/cli-plugin-pwa": "~4.5.0", 32 | "@vue/cli-plugin-router": "~4.5.0", 33 | "@vue/cli-plugin-typescript": "~4.5.0", 34 | "@vue/cli-plugin-unit-jest": "~4.5.0", 35 | "@vue/cli-plugin-vuex": "~4.5.0", 36 | "@vue/cli-service": "~4.5.0", 37 | "@vue/compiler-sfc": "^3.0.0-0", 38 | "@vue/eslint-config-prettier": "^6.0.0", 39 | "@vue/eslint-config-typescript": "^5.0.2", 40 | "@vue/test-utils": "^2.0.0-0", 41 | "eslint": "^6.7.2", 42 | "eslint-plugin-prettier": "^3.1.3", 43 | "eslint-plugin-vue": "^7.0.0-0", 44 | "lint-staged": "^9.5.0", 45 | "node-sass": "^4.12.0", 46 | "prettier": "^1.19.1", 47 | "sass-loader": "^8.0.2", 48 | "typescript": "~3.9.3", 49 | "vue-jest": "^5.0.0-0" 50 | }, 51 | "gitHooks": { 52 | "pre-commit": "lint-staged" 53 | }, 54 | "lint-staged": { 55 | "*.{js,jsx,vue,ts,tsx}": [ 56 | "npx prettier --write", 57 | "eslint **/*.js --fix-dry-run", 58 | "jest --bail --findRelatedTests" 59 | ], 60 | "*.scss": [ 61 | "stylelint --fix" 62 | ] 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | Nutris 4 |

5 |

Nutris

6 | 7 | Web App to calculate parenteral diets. [See old version running here!👀](https://nutris.web.app/) 8 | 9 | ## Stack 10 | 11 | This project uses: 12 | 13 | - [Vue ❤️](https://vuejs.org/) - Awesome JS Framework! 14 | - [TypeScript](https://www.typescriptlang.org/) - To build a secure code and avoid bugs. 15 | - [Sass](https://sass-lang.com/) - CSS Pre-processor with nice approach. 16 | - [Jest](https://jestjs.io/docs/en/getting-started) - To create unit test and avoid bugs. 17 | - [Venice](https://github.com/juntossomosmais/venice) - We have been using [Juntos Somos Mais](https://github.com/juntossomosmais) Design System as inspiration. 18 | - [Vue-i18n](https://github.com/intlify/vue-i18n-next) - To create a multi idiom project. 19 | 20 | This project also uses [Husky](https://github.com/typicode/husky) to prevent commit and push messy and wrong code. 21 | 22 | ## Commands 23 | 24 | #### Install (*WITH* Open-ish's access) 25 | 26 | - Generate a token at GitHub 27 | - Create a `.npmrc` file, using `.npmrc.example` as example 28 | 29 | ```sh 30 | npm install 31 | ``` 32 | 33 | #### Install (*WITHOUT* Open-is's access) 34 | 35 | - Remove `@open-ish/nutris-roles` dependency from `package.json` file (at the future, we will create a mock function to it). 36 | 37 | ```sh 38 | npm install 39 | ``` 40 | 41 | #### Usage 42 | 43 | ```sh 44 | npm start 45 | ``` 46 | 47 | #### Build 48 | 49 | ```sh 50 | npm run build 51 | ``` 52 | 53 | It'll be running at: http://localhost:1993/ 54 | 55 | ## Authors 56 | 57 | 👤 [@tassioFront](https://github.com/tassioFront) 58 | 👤 [@lupy100](https://github.com/lupy100) 59 | 👤 [@pehlse](https://github.com/pehlse) 60 | 61 | - Github: [@open-ish](https://github.com/open-ish) 62 | 63 | ## 🤝 Contributing 64 | 65 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/open-ish/nutris/issues). 66 | 67 | ## 📝 License 68 | 69 | Copyright © 2020 [@open-ish](https://github.com/open-ish) 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | .firebase 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.production 75 | .env.development 76 | .env.test 77 | 78 | # parcel-bundler cache (https://parceljs.org/) 79 | .cache 80 | 81 | # Next.js build output 82 | .next 83 | 84 | # Nuxt.js build / generate output 85 | .nuxt 86 | dist 87 | 88 | # Gatsby files 89 | .cache/ 90 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 91 | # https://nextjs.org/blog/next-9-1#public-directory-support 92 | # public 93 | 94 | # vuepress build output 95 | .vuepress/dist 96 | 97 | # Serverless directories 98 | .serverless/ 99 | 100 | # FuseBox cache 101 | .fusebox/ 102 | 103 | # DynamoDB Local files 104 | .dynamodb/ 105 | 106 | # Credentials 107 | .\npmrc 108 | .\yarnrc 109 | 110 | # TernJS port file 111 | .tern-port 112 | -------------------------------------------------------------------------------- /src/components/spinner/Spinner.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 36 | 37 | 109 | -------------------------------------------------------------------------------- /src/layouts/default/Default.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 60 | 61 | 88 | -------------------------------------------------------------------------------- /src/layouts/default/components/footer/Footer.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 59 | 60 | 97 | -------------------------------------------------------------------------------- /src/router/default/patients.ts: -------------------------------------------------------------------------------- 1 | import { RouteLocationNormalized } from 'vue-router' 2 | 3 | import Index from '@/layouts/Index.vue' 4 | import Patients from '@/views/default/patients/Patients.vue' 5 | import History from '@/views/default/patients/components/history/History.vue' 6 | import Form from '@/views/default/patients/components/form/Form.vue' 7 | import { Names, Paths } from '@/router/default/enums' 8 | import { 9 | LOADING_PAGE_NAMESPACE, 10 | LoadingPageActions, 11 | } from '@/store/loading-page/types' 12 | import { 13 | PatientsActions, 14 | PatientsGetters, 15 | PATIENTS_NAMESPACE, 16 | } from '@/store/patients/types' 17 | const getPatients = PATIENTS_NAMESPACE + '/' + PatientsActions.GET_PATIENTS 18 | import store from '@/store' 19 | 20 | const loadingSpace = LOADING_PAGE_NAMESPACE + '/' + LoadingPageActions.TOGGLE 21 | const patients = PATIENTS_NAMESPACE + '/' + PatientsGetters.PATIENTS 22 | 23 | export default { 24 | path: Paths.patients, 25 | name: Names.patients, 26 | component: Index, 27 | meta: { 28 | requiresAuth: true, 29 | }, 30 | children: [ 31 | { 32 | path: '', 33 | component: Patients, 34 | beforeEnter: ( 35 | to: RouteLocationNormalized, 36 | from: RouteLocationNormalized, 37 | next: Function 38 | ) => { 39 | store.dispatch(loadingSpace, true) 40 | !store.getters[patients].length 41 | ? store.dispatch(getPatients).then(() => { 42 | store.dispatch(loadingSpace, false) 43 | next() 44 | }) 45 | : setTimeout(() => { 46 | store.dispatch(loadingSpace, false) 47 | next() 48 | }, 500) 49 | }, 50 | }, 51 | { 52 | path: Paths.new, 53 | name: Names.patientsNew, 54 | component: Form, 55 | }, 56 | { 57 | path: Paths.details + ':id', 58 | name: Names.patientsHistory, 59 | component: History, 60 | props: true, 61 | beforeEnter: ( 62 | to: RouteLocationNormalized, 63 | from: RouteLocationNormalized, 64 | next: Function 65 | ) => { 66 | store.dispatch(loadingSpace, true) 67 | !store.getters[patients].length 68 | ? store.dispatch(getPatients).then(() => { 69 | store.dispatch(loadingSpace, false) 70 | next() 71 | }) 72 | : setTimeout(() => { 73 | store.dispatch(loadingSpace, false) 74 | next() 75 | }, 500) 76 | }, 77 | }, 78 | ], 79 | } 80 | -------------------------------------------------------------------------------- /src/layouts/alt/header/components/social-login/SocialLogin.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 62 | 63 | 99 | -------------------------------------------------------------------------------- /src/views/default/manage-diets/ManageDiets.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 69 | 91 | -------------------------------------------------------------------------------- /src/views/alt/home/Home.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 56 | 102 | -------------------------------------------------------------------------------- /src/components/form/select/Select.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 60 | 126 | -------------------------------------------------------------------------------- /src/components/form/input/Input.vue: -------------------------------------------------------------------------------- 1 | 18 | 70 | 125 | -------------------------------------------------------------------------------- /src/store/patients/index.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree, ActionTree } from 'vuex' 2 | import { 3 | PatientsState, 4 | PatientsGetters, 5 | PatientsMutations, 6 | PatientsActions, 7 | } from './types' 8 | import FirebaseApp from '@/../firebaseApp' 9 | 10 | import { names } from '@/enums/collections/firebase' 11 | import { UserGetters, USER_NAMESPACE } from '../user/types' 12 | import { Snapshot } from '@/models/firebase' 13 | import { Patient } from '@/models/Patient' 14 | import { firebaseQuerys } from '@/enums/firebaseQuerys' 15 | 16 | const user = USER_NAMESPACE + '/' + UserGetters.USER 17 | 18 | const state: PatientsState = { 19 | patients: [], 20 | } 21 | 22 | const getters = { 23 | [PatientsGetters.PATIENTS](state: PatientsState) { 24 | return state.patients 25 | }, 26 | [PatientsGetters.FIND_PATIENT](state: PatientsState) { 27 | return (id: string) => 28 | state.patients.find((patient) => { 29 | return patient.id === id 30 | }) 31 | }, 32 | } 33 | 34 | const actions: ActionTree = { 35 | [PatientsActions.GET_PATIENTS]({ commit, rootGetters }) { 36 | return FirebaseApp.db 37 | .collection(names.users) 38 | .doc(rootGetters[user].uid) 39 | .collection(names.patients) 40 | .orderBy('createdAt', firebaseQuerys.desc) 41 | .get() 42 | .then((querySnapshot: Snapshot[]) => { 43 | const patients: Patient[] = [] 44 | querySnapshot.forEach((doc) => { 45 | patients.push({ ...doc.data(), id: doc.id } as Patient) 46 | }) 47 | commit(PatientsMutations.GET_PATIENTS, patients) 48 | }) 49 | .catch((error: Error) => error.message) 50 | }, 51 | [PatientsActions.POST_PATIENTS]({ commit, rootGetters }, patient) { 52 | return FirebaseApp.db 53 | .collection(names.users) 54 | .doc(rootGetters[user].uid) 55 | .collection(names.patients) 56 | .add(patient) 57 | .then((doc: Snapshot) => { 58 | commit(PatientsMutations.POST_PATIENTS, { ...patient, id: doc.id }) 59 | }) 60 | .catch((error: Error) => error.message) 61 | }, 62 | [PatientsActions.UPDATE_HISTORY_LOCALLY]( 63 | { commit, getters }, 64 | { history, historyId } 65 | ) { 66 | const index = getters[PatientsGetters.PATIENTS].findIndex( 67 | (currentPatient: Patient) => currentPatient.id === historyId 68 | ) 69 | const noIndex = index === -1 70 | return !noIndex 71 | ? commit(PatientsMutations.UPDATE_HISTORY_LOCALLY, { 72 | history, 73 | index, 74 | }) 75 | : new Error() 76 | }, 77 | } 78 | 79 | const mutations: MutationTree = { 80 | [PatientsMutations.GET_PATIENTS](state, patients: Patient[]) { 81 | state.patients = patients 82 | }, 83 | [PatientsMutations.POST_PATIENTS](state, patient: Patient) { 84 | state.patients.unshift(patient) 85 | }, 86 | [PatientsMutations.UPDATE_HISTORY_LOCALLY](state, { history, index }) { 87 | state.patients[index].calculationHistory?.unshift(history) 88 | }, 89 | } 90 | 91 | export default { 92 | namespaced: true, 93 | state, 94 | getters, 95 | actions, 96 | mutations, 97 | } 98 | -------------------------------------------------------------------------------- /src/components/form/button/FixedBtn.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 71 | 72 | 116 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/components/nav/Nav.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 82 | 83 | 142 | -------------------------------------------------------------------------------- /src/views/default/patients/components/patient/Patient.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 76 | 157 | -------------------------------------------------------------------------------- /src/assets/styles/variables-root.css: -------------------------------------------------------------------------------- 1 | /* ================================================== 2 | 1. Spaces 3 | 2. Colors 4 | 2.1. Color Name by context 5 | 3. Utils 6 | // ================================================== */ 7 | 8 | /* These have been using Venice (Juntos Somos Mais's Design System) as inpirations. 9 | You can learn about here: https://github.com/juntossomosmais/venice */ 10 | 11 | :root { 12 | /* 1. Spaces */ 13 | --space-xxs: 4px; 14 | --space-xs: 8px; 15 | --space-sm: 16px; 16 | --space: 24px; 17 | --space-md: 32px; 18 | --space-lg: 48px; 19 | --space-xlg: 64px; 20 | --screen-xs: 360px; 21 | --screen-sm: 768px; 22 | --screen-md: 992px; 23 | --screen-lg: 1280px; 24 | --screen-xlg: 1440px; 25 | --screen-max-container: 1140px; 26 | --font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 27 | 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 28 | sans-serif; 29 | --typography-panel-font: 900 38px/40px var(--font-family); 30 | --typography-panel-letter: 0; 31 | --typography-title-font: 500 24px/28px var(--font-family); 32 | --typography-title-letter: -0.357143px; 33 | --typography-subheader-font: 400 20px/24px var(--font-family); 34 | --typography-subheader-letter: -0.288572px; 35 | --typography-body-font: 400 16px/19px var(--font-family); 36 | --typography-body-letter: -0.228572px; 37 | --typography-body2-font: 400 14px/16px var(--font-family); 38 | --typography-body2-letter: -0.2px; 39 | --typography-caption-font: 400 12px/14px var(--font-family); 40 | --typography-caption-letter: -0.171429px; 41 | --typography-button-font: 500 16px/19px var(--font-family); 42 | --typography-button-letter: -0.228572px; 43 | 44 | /* 2. Colors */ 45 | --black: #111; 46 | --white: #fff; 47 | --gray-1: #f5f5f5; 48 | --gray-2: #d8d8d8; 49 | --gray-3: #9b9b9b; 50 | --gray-4: #4a4a4a; 51 | --gray-5: #333; 52 | --blue-1: #e3f2fd; /*5*/ 53 | --blue-2: #90caf9; /*3*/ 54 | --blue-3: #2196f3; 55 | --blue-4: #1976d2; /*2*/ 56 | --blue-5: #0d47a1; /*4*/ 57 | --green-1: #e8f5e9; 58 | --green-2: #a5d6a7; 59 | --green-3: #4caf50; 60 | --green-4: #388e3c; 61 | --green-5: #1b5e20; 62 | --orange-1: #fff3e0; 63 | --orange-2: #ffcc80; 64 | --orange-3: #ff9800; 65 | --orange-4: #f57c00; 66 | --orange-5: #e65100; 67 | --red-1: #ffebee; 68 | --red-2: #ef9a9a; 69 | --red-3: #f44336; 70 | --red-4: #d32f2f; 71 | --red-5: #b71c1c; 72 | --purple-1: #ce93d8; 73 | --purple-2: #9c27b0; 74 | --purple-3: #6a1b9a; 75 | --cyan-1: #80deea; 76 | --cyan-2: #006064de; 77 | --cyan-3: #006064; 78 | /*2.1 Color name by context */ 79 | --brand-color: var(--cyan-3); 80 | --brand-color-lighten: var(--cyan-2); 81 | --brand-color-contrast: var(--white); 82 | --default-color-lighten: var(--gray-2); 83 | --default-color: var(--gray-3); 84 | --default-color-darken: var(--gray-4); 85 | --default-color-contrast: var(--white); 86 | --disabled-color: var(--gray-3); 87 | --disabled-color-contrast: var(--gray-1); 88 | --primary-color-lighten: var(--orange-2); 89 | --primary-color: var(--orange-3); 90 | --primary-color-darken: var(--orange-4); 91 | --primary-color-contrast: var(--white); 92 | --secondary-color-lighten: var(--blue-2); 93 | --secondary-color: var(--blue-3); 94 | --secondary-color-darken: var(--blue-4); 95 | --secondary-color-contrast: var(--white); 96 | --success-color-lighten: var(--green-2); 97 | --success-color: var(--green-3); 98 | --success-color-darken: var(--green-4); 99 | --success-color-contrast: var(--white); 100 | --danger-color-lighten: var(--red-2); 101 | --danger-color: var(--red-3); 102 | --danger-color-darken: var(--red-4); 103 | --danger-color-contrast: var(--white); 104 | --text-color: var(--gray-5); 105 | --text-color-lighten: var(--gray-3); 106 | --bg-darken: rgba(0, 0, 0, 0.7); 107 | --bg-lighten: var(--white); 108 | 109 | /* 3. Utils */ 110 | --border-radius-lighten: 2px; 111 | --border-radius: 4px; 112 | --zindex-1: 1; 113 | --zindex-5: 5; 114 | --zindex-10: 10; 115 | --zindex-100: 100; 116 | --zindex-1000: 1000; 117 | --zindex-overlay: 10000; 118 | --transition-duration-speed: 0.25s; 119 | --transition-duration: 0.5s; 120 | --transition-duration-slow: 0.75s; 121 | } 122 | -------------------------------------------------------------------------------- /src/views/default/manage-diets/components/diet/Diet.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 109 | 182 | -------------------------------------------------------------------------------- /src/layouts/default/components/header/components/user-options/UserOptions.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 86 | 87 | 180 | -------------------------------------------------------------------------------- /src/views/default/patients/components/history/History.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 131 | 151 | -------------------------------------------------------------------------------- /src/views/default/manage-diets/components/form/Form.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 153 | 171 | -------------------------------------------------------------------------------- /src/store/manage-diets/index.ts: -------------------------------------------------------------------------------- 1 | import { MutationTree, ActionTree } from 'vuex' 2 | import { 3 | ManageDietsState, 4 | ManageDietsGetters, 5 | ManageDietsMutations, 6 | ManageDietsActions, 7 | } from './types' 8 | import FirebaseApp from '@/../firebaseApp' 9 | 10 | import { Diet } from '@/models/Diet' 11 | import { names } from '@/enums/collections/firebase' 12 | import { UserGetters, USER_NAMESPACE } from '../user/types' 13 | import { Snapshot } from '@/models/firebase' 14 | import { firebaseQuerys } from '@/enums/firebaseQuerys' 15 | import storage from '@/helpers/localStorage/localStorage' 16 | import { storageKey } from '@/enums/storageKeys' 17 | 18 | const user = USER_NAMESPACE + '/' + UserGetters.USER 19 | 20 | const state: ManageDietsState = { 21 | diets: [], 22 | } 23 | 24 | const getters = { 25 | [ManageDietsGetters.DIETS](state: ManageDietsState) { 26 | return state.diets 27 | }, 28 | [ManageDietsGetters.FIND_DIET](state: ManageDietsState) { 29 | return (id: string) => 30 | state.diets.find((diet) => { 31 | return diet.id === id 32 | }) 33 | }, 34 | } 35 | 36 | const actions: ActionTree = { 37 | [ManageDietsActions.GET_DIETS]({ commit, rootGetters }) { 38 | return FirebaseApp.db 39 | .collection(names.users) 40 | .doc(rootGetters[user].uid) 41 | .collection(names.diets) 42 | .orderBy('createdAt', firebaseQuerys.desc) 43 | .get() 44 | .then((querySnapshot: Snapshot[]) => { 45 | const diets: Diet[] = [] 46 | querySnapshot.forEach((doc) => { 47 | diets.push({ ...doc.data(), id: doc.id } as Diet) 48 | }) 49 | commit(ManageDietsMutations.GET_DIETS, diets) 50 | }) 51 | .catch((error: Error) => error.message) 52 | }, 53 | [ManageDietsActions.POST_DIETS]({ commit, rootGetters }, diet) { 54 | return FirebaseApp.db 55 | .collection(names.users) 56 | .doc(rootGetters[user].uid) 57 | .collection(names.diets) 58 | .add(diet) 59 | .then((doc: Snapshot) => { 60 | commit(ManageDietsMutations.POST_DIETS, { ...diet, id: doc.id }) 61 | }) 62 | .catch((error: Error) => error.message) 63 | }, 64 | [ManageDietsActions.UPDATE_DIET]( 65 | { commit, rootGetters, getters }, 66 | { diet, id } 67 | ) { 68 | const index = getters[ManageDietsGetters.DIETS].findIndex( 69 | (currentDiet: Diet) => currentDiet.id === id 70 | ) 71 | const noIndex = index === -1 72 | return !noIndex 73 | ? FirebaseApp.db 74 | .collection(names.users) 75 | .doc(rootGetters[user].uid) 76 | .collection(names.diets) 77 | .doc(id) 78 | .set(diet) 79 | .then(() => { 80 | commit(ManageDietsMutations.UPDATE_DIET, { 81 | diet, 82 | index, 83 | }) 84 | }) 85 | .catch((error: Error) => error.message) 86 | : new Error() 87 | }, 88 | [ManageDietsActions.DELETE_DIET]({ commit, rootGetters, getters }, id) { 89 | const index = getters[ManageDietsGetters.DIETS].findIndex( 90 | (diet: Diet) => diet.id === id 91 | ) 92 | const noIndex = index === -1 93 | return !noIndex 94 | ? FirebaseApp.db 95 | .collection(names.users) 96 | .doc(rootGetters[user].uid) 97 | .collection(names.diets) 98 | .doc(id) 99 | .delete() 100 | .then(() => { 101 | commit(ManageDietsMutations.DELETE_DIET, index) 102 | }) 103 | .catch((error: Error) => error.message) 104 | : new Error() 105 | }, 106 | // saveLegacy() { 107 | // CHANGE DIETAS TO DIETS 108 | // return ( 109 | // FirebaseApp.db 110 | // .collection(names.users) 111 | // .doc(rootGetters[user].uid) 112 | // // .collection(names.diets) 113 | // .collection('dietas') 114 | // .get() 115 | // .then((querySnapshot: Snapshot[]) => { 116 | // const diets: any = [] 117 | // querySnapshot.forEach((doc: any) => { 118 | // const { cal, protein, title } = doc.data().diet 119 | // console.log('cal', cal) 120 | // FirebaseApp.db 121 | // .collection(names.users) 122 | // .doc(rootGetters[user].uid) 123 | // .collection(names.diets) 124 | // .add({ name: title, calAmount: cal, proteinAmount: protein }) 125 | // .then((test: Snapshot) => { 126 | // console.log('doc', test) 127 | // }) 128 | // .catch((error: Error) => console.log(error.message)) 129 | // }) 130 | // commit(ManageDietsMutations.GET_DIETS, diets) 131 | // }) 132 | // .catch((error: Error) => error) 133 | // ) 134 | // } 135 | } 136 | 137 | const mutations: MutationTree = { 138 | [ManageDietsMutations.GET_DIETS](state, diets: Diet[]) { 139 | storage.set(storageKey.diets, diets) 140 | state.diets = diets 141 | }, 142 | [ManageDietsMutations.POST_DIETS](state, diet: Diet) { 143 | state.diets.unshift(diet) 144 | storage.set(storageKey.diets, state.diets) 145 | }, 146 | [ManageDietsMutations.UPDATE_DIET](state, { diet, index }) { 147 | state.diets[index] = diet 148 | storage.set(storageKey.diets, state.diets) 149 | }, 150 | [ManageDietsMutations.DELETE_DIET](state, index) { 151 | state.diets.splice(index, 1) 152 | storage.set(storageKey.diets, state.diets) 153 | }, 154 | } 155 | 156 | export default { 157 | namespaced: true, 158 | state, 159 | getters, 160 | actions, 161 | mutations, 162 | } 163 | -------------------------------------------------------------------------------- /src/components/calculator/Calculator.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 203 | 240 | -------------------------------------------------------------------------------- /src/views/default/patients/components/form/Form.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 183 | 229 | -------------------------------------------------------------------------------- /src/assets/icons/nutris.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'nutris'; 3 | src: url('./assets/icons/nutris.eot?21886223'); 4 | src: url('./assets/icons/nutris.eot?21886223#iefix') 5 | format('embedded-opentype'), 6 | url('./assets/icons/nutris.woff2?21886223') format('woff2'), 7 | url('./assets/icons/nutris.woff?21886223') format('woff'), 8 | url('./assets/icons/nutris.ttf?21886223') format('truetype'), 9 | url('./assets/icons/nutris.svg?21886223#nutris') format('svg'); 10 | font-weight: normal; 11 | font-style: normal; 12 | } 13 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 14 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 15 | /* 16 | @media screen and (-webkit-min-device-pixel-ratio:0) { 17 | @font-face { 18 | font-family: 'nutris'; 19 | src: url('../font/nutris.svg?21886223#nutris') format('svg'); 20 | } 21 | } 22 | */ 23 | 24 | [class^='nutris-']:before, 25 | [class*=' nutris-']:before { 26 | font-family: 'nutris'; 27 | font-style: normal; 28 | font-weight: normal; 29 | speak: never; 30 | 31 | display: inline-block; 32 | text-decoration: inherit; 33 | width: 1em; 34 | margin-right: 0.2em; 35 | text-align: center; 36 | /* opacity: .8; */ 37 | 38 | /* For safety - reset parent styles, that can break glyph codes*/ 39 | font-variant: normal; 40 | text-transform: none; 41 | 42 | /* fix buttons height, for twitter bootstrap */ 43 | line-height: 1em; 44 | 45 | /* Animation center compensation - margins should be symmetric */ 46 | /* remove if not needed */ 47 | margin-left: 0.2em; 48 | 49 | /* you can be more comfortable with increased icons size */ 50 | /* font-size: 120%; */ 51 | 52 | /* Font smoothing. That was taken from TWBS */ 53 | -webkit-font-smoothing: antialiased; 54 | -moz-osx-font-smoothing: grayscale; 55 | 56 | /* Uncomment for 3D effect */ 57 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 58 | } 59 | 60 | .nutris-alert:before { 61 | content: '\e800'; 62 | } /* '' */ 63 | .nutris-dot-3:before { 64 | content: '\e800'; 65 | } /* '' */ 66 | .nutris-arrow-left:before { 67 | content: '\e801'; 68 | } /* '' */ 69 | .nutris-plus:before { 70 | content: '\e802'; 71 | } /* '' */ 72 | .nutris-minus:before { 73 | content: '\e803'; 74 | } /* '' */ 75 | .nutris-spin:before { 76 | content: '\e804'; 77 | } /* '' */ 78 | .nutris-arrow-thin-right:before { 79 | content: '\e805'; 80 | } /* '' */ 81 | .nutris-flag:before { 82 | content: '\e806'; 83 | } /* '' */ 84 | .nutris-search:before { 85 | content: '\e807'; 86 | } /* '' */ 87 | .nutris-star:before { 88 | content: '\e808'; 89 | } /* '' */ 90 | .nutris-outline:before { 91 | content: '\e809'; 92 | } /* '' */ 93 | .nutris-whatsapp:before { 94 | content: '\e80a'; 95 | } /* '' */ 96 | .nutris-whatsapp-negative:before { 97 | content: '\e80b'; 98 | } /* '' */ 99 | .nutris-emo-coffee:before { 100 | content: '\e80c'; 101 | } /* '' */ 102 | .nutris-folder-open:before { 103 | content: '\e80d'; 104 | } /* '' */ 105 | .nutris-emo-unhappy:before { 106 | content: '\e80e'; 107 | } /* '' */ 108 | .nutris-female:before { 109 | content: '\e810'; 110 | } /* '' */ 111 | .nutris-male:before { 112 | content: '\e811'; 113 | } /* '' */ 114 | .nutris-print:before { 115 | content: '\e831'; 116 | } /* '' */ 117 | .nutris-up-dir:before { 118 | content: '\e832'; 119 | } /* '' */ 120 | .nutris-home:before { 121 | content: '\e833'; 122 | } /* '' */ 123 | .nutris-cancel:before { 124 | content: '\e834'; 125 | } /* '' */ 126 | .nutris-business:before { 127 | content: '\e837'; 128 | } /* '' */ 129 | .nutris-calendar:before { 130 | content: '\e838'; 131 | } /* '' */ 132 | .nutris-cart:before { 133 | content: '\e839'; 134 | } /* '' */ 135 | .nutris-chat:before { 136 | content: '\e83a'; 137 | } /* '' */ 138 | .nutris-check:before { 139 | content: '\e83b'; 140 | } /* '' */ 141 | .nutris-checklist:before { 142 | content: '\e83c'; 143 | } /* '' */ 144 | .nutris-coins:before { 145 | content: '\e83d'; 146 | } /* '' */ 147 | .nutris-denied:before { 148 | content: '\e83e'; 149 | } /* '' */ 150 | .nutris-doc:before { 151 | content: '\e83f'; 152 | } /* '' */ 153 | .nutris-dollar:before { 154 | content: '\e840'; 155 | } /* '' */ 156 | .nutris-down-dir:before { 157 | content: '\e841'; 158 | } /* '' */ 159 | .nutris-edit:before { 160 | content: '\e842'; 161 | } /* '' */ 162 | .nutris-filter:before { 163 | content: '\e843'; 164 | } /* '' */ 165 | .nutris-heart:before { 166 | content: '\e845'; 167 | } /* '' */ 168 | .nutris-icon-warning:before { 169 | content: '\e846'; 170 | } /* '' */ 171 | .nutris-list:before { 172 | content: '\e848'; 173 | } /* '' */ 174 | .nutris-mail:before { 175 | content: '\e849'; 176 | } /* '' */ 177 | .nutris-money:before { 178 | content: '\e84c'; 179 | } /* '' */ 180 | .nutris-phone:before { 181 | content: '\e84d'; 182 | } /* '' */ 183 | .nutris-pin:before { 184 | content: '\e84e'; 185 | } /* '' */ 186 | .nutris-professional:before { 187 | content: '\e84f'; 188 | } /* '' */ 189 | .nutris-radio-off:before { 190 | content: '\e850'; 191 | } /* '' */ 192 | .nutris-radio-on:before { 193 | content: '\e851'; 194 | } /* '' */ 195 | .nutris-standby:before { 196 | content: '\e852'; 197 | } /* '' */ 198 | .nutris-store:before { 199 | content: '\e853'; 200 | } /* '' */ 201 | .nutris-ticket:before { 202 | content: '\e854'; 203 | } /* '' */ 204 | .nutris-timeline-up:before { 205 | content: '\e855'; 206 | } /* '' */ 207 | .nutris-timeline:before { 208 | content: '\e856'; 209 | } /* '' */ 210 | .nutris-trash:before { 211 | content: '\e857'; 212 | } /* '' */ 213 | .nutris-user-block:before { 214 | content: '\e858'; 215 | } /* '' */ 216 | .nutris-user-check:before { 217 | content: '\e859'; 218 | } /* '' */ 219 | .nutris-user-circle:before { 220 | content: '\e85a'; 221 | } /* '' */ 222 | .nutris-user-divide:before { 223 | content: '\e85b'; 224 | } /* '' */ 225 | .nutris-user-heart:before { 226 | content: '\e85c'; 227 | } /* '' */ 228 | .nutris-user:before { 229 | content: '\e85d'; 230 | } /* '' */ 231 | .nutris-users:before { 232 | content: '\e85e'; 233 | } /* '' */ 234 | .nutris-video:before { 235 | content: '\e85f'; 236 | } /* '' */ 237 | .nutris-visibility-off:before { 238 | content: '\e860'; 239 | } /* '' */ 240 | .nutris-visibility-on:before { 241 | content: '\e861'; 242 | } /* '' */ 243 | .nutris-check-empty:before { 244 | content: '\f096'; 245 | } /* '' */ 246 | .nutris-facebook:before { 247 | content: '\f09a'; 248 | } /* '' */ 249 | .nutris-menu:before { 250 | content: '\f0c9'; 251 | } /* '' */ 252 | .nutris-angle-left:before { 253 | content: '\f104'; 254 | } /* '' */ 255 | .nutris-angle-right:before { 256 | content: '\f105'; 257 | } /* '' */ 258 | .nutris-angle-up:before { 259 | content: '\f106'; 260 | } /* '' */ 261 | .nutris-angle-down:before { 262 | content: '\f107'; 263 | } /* '' */ 264 | .nutris-ok-squared:before { 265 | content: '\f14a'; 266 | } /* '' */ 267 | .nutris-instagram:before { 268 | content: '\f16d'; 269 | } /* '' */ 270 | .nutris-google:before { 271 | content: '\f1a0'; 272 | } /* '' */ 273 | .nutris-calc:before { 274 | content: '\f1ec'; 275 | } /* '' */ 276 | -------------------------------------------------------------------------------- /src/components/form/button/Button.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 62 | 63 | 337 | -------------------------------------------------------------------------------- /src/views/default/patients/components/history/components/Card.vue: -------------------------------------------------------------------------------- 1 | 128 | 129 | 165 | 304 | -------------------------------------------------------------------------------- /src/assets/icons/nutris.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2020 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | --------------------------------------------------------------------------------