├── .gitignore
├── Documentos
├── Documentos.md
└── images
│ └── login.png
├── Frontend
├── Procfile
├── src
│ ├── containers
│ │ ├── Pagination.jsx
│ │ ├── NotFound.jsx
│ │ ├── PatientInfo.jsx
│ │ ├── Exam.jsx
│ │ ├── User.jsx
│ │ ├── Patient.jsx
│ │ ├── Doctor.jsx
│ │ ├── AddExamsResults.jsx
│ │ ├── Bacteriologist.jsx
│ │ ├── Administrator.jsx
│ │ ├── AddUser.jsx
│ │ └── HomeLogin.jsx
│ ├── assets
│ │ ├── styles
│ │ │ ├── containers
│ │ │ │ ├── AddUser.scss
│ │ │ │ ├── AddExams.scss
│ │ │ │ ├── PatientList.scss
│ │ │ │ ├── Login.scss
│ │ │ │ ├── Patient.scss
│ │ │ │ └── Administrator.scss
│ │ │ ├── components
│ │ │ │ ├── Useritem.scss
│ │ │ │ ├── PatientData.scss
│ │ │ │ ├── NavbarDoctor.scss
│ │ │ │ ├── User.scss
│ │ │ │ ├── ExamItem.scss
│ │ │ │ ├── SheduleExams.scss
│ │ │ │ ├── RememberInfo.scss
│ │ │ │ ├── Modal.scss
│ │ │ │ └── Header.scss
│ │ │ ├── Media.scss
│ │ │ └── App.scss
│ │ └── static
│ │ │ ├── edit.png
│ │ │ ├── logo.png
│ │ │ ├── csv-file.png
│ │ │ ├── logotipo.png
│ │ │ ├── logotype.png
│ │ │ ├── delete-user.png
│ │ │ ├── edit-user.png
│ │ │ ├── iconotipo.png
│ │ │ ├── circle-regular.png
│ │ │ ├── logo-horizontal.png
│ │ │ └── delete-user-black.png
│ ├── components
│ │ ├── Layout.jsx
│ │ ├── Modal.jsx
│ │ ├── ExamList.jsx
│ │ ├── PatientList.jsx
│ │ ├── PersonalInfo.jsx
│ │ ├── AddResultsModal.jsx
│ │ ├── MedicalHistory.jsx
│ │ ├── SheduleExams.jsx
│ │ ├── NavbarDoctor.jsx
│ │ ├── ExamItem.jsx
│ │ ├── RememberInfo.jsx
│ │ ├── UserItem.jsx
│ │ └── Header.jsx
│ ├── utils
│ │ └── gravatar.jsx
│ ├── conf
│ │ ├── config-firebase.json
│ │ └── firebase.js
│ ├── actions
│ │ └── index.js
│ ├── hooks
│ │ └── useInitialState.js
│ ├── reducers
│ │ └── index.js
│ ├── sw
│ │ └── firebase-messaging-sw.js
│ ├── routes
│ │ └── App.jsx
│ ├── mocks
│ │ ├── ExamMock.js
│ │ └── UsersMock.json
│ └── index.js
├── static.json
├── .prettierrc
├── babel.config.js
├── .env.example
├── public
│ └── index.html
├── README.md
├── webpack.config.js
├── package.json
├── .gitignore
└── .eslintrc
├── Backend
├── Procfile
├── jest.config.js
├── config
│ ├── database.js
│ └── config.js
├── api
│ ├── components
│ │ ├── exams
│ │ │ ├── index.js
│ │ │ ├── controller.js
│ │ │ └── network.js
│ │ ├── notification
│ │ │ ├── index.js
│ │ │ ├── network.js
│ │ │ └── controller.js
│ │ ├── auth
│ │ │ ├── index.js
│ │ │ ├── network.js
│ │ │ └── controller.js
│ │ ├── user
│ │ │ ├── index.js
│ │ │ └── controller.js
│ │ ├── email
│ │ │ ├── index.js
│ │ │ ├── templates
│ │ │ │ └── sendNewUser.js
│ │ │ └── controller.js
│ │ ├── auth_roles
│ │ │ ├── index.js
│ │ │ └── controller.js
│ │ └── welcome
│ │ │ └── network.js
│ └── index.js
├── .sequelizerc
├── auth
│ ├── index.js
│ ├── roles.js
│ └── strategies
│ │ ├── basic.js
│ │ ├── cookie.js
│ │ └── jwt.js
├── utils
│ ├── testServer.js
│ └── userUtils.js
├── .eslintrc.js
├── .env_example
├── store
│ ├── mocks
│ │ ├── mockDB.js
│ │ ├── storeMock.js
│ │ ├── RolesMock.js
│ │ ├── exams
│ │ │ └── BoodExam.js
│ │ ├── AuthMock.js
│ │ └── UsersMock.json
│ ├── dummy.js
│ └── sequelize.js
├── network
│ ├── response.js
│ ├── errors.js
│ └── __test__
│ │ └── errors.spec.js
├── db
│ ├── models
│ │ ├── exam.js
│ │ ├── auth.js
│ │ ├── examstatus.js
│ │ ├── rolepermission.js
│ │ ├── examtype.js
│ │ ├── permission.js
│ │ ├── authRole.js
│ │ ├── role.js
│ │ ├── firebasenotificationtoken.js
│ │ ├── user.js
│ │ ├── medicalrecord.js
│ │ └── index.js
│ ├── seeders
│ │ └── 20200531065934-roles.js
│ └── migrations
│ │ ├── 20200526161357-create-auth.js
│ │ ├── 20200527002438-create-role.js
│ │ ├── 20200527010113-create-auth-role.js
│ │ ├── 20200527002503-create-permission.js
│ │ ├── 20200528063216-create-examStatus.js
│ │ ├── 20200527002633-create-role-permission.js
│ │ ├── 20200531013304-create-firebase-notification-token.js
│ │ ├── 20200528062121-create-examtype.js
│ │ ├── 20200528063918-create-exam.js
│ │ ├── 20200527024840-create-user.js
│ │ └── 20200528043643-create-medicalrecord.js
├── __test__
│ ├── api.components.welcome.spec.js
│ ├── api.components.user.spec.js
│ └── api.components.auth.spec.js
├── package.json
└── .gitignore
├── LICENSE
├── .travis.yml
├── CODE_OF_CONDUCT.md
└── Readme.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/Documentos/Documentos.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Frontend/Procfile:
--------------------------------------------------------------------------------
1 | web: bin/boot
--------------------------------------------------------------------------------
/Backend/Procfile:
--------------------------------------------------------------------------------
1 | web: node api/index.js
--------------------------------------------------------------------------------
/Frontend/src/containers/Pagination.jsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/containers/AddUser.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Backend/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true
3 | }
4 |
--------------------------------------------------------------------------------
/Documentos/images/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Documentos/images/login.png
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/Useritem.scss:
--------------------------------------------------------------------------------
1 | .user-item__details--img {
2 | width: 20px;
3 | }
--------------------------------------------------------------------------------
/Frontend/static.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": "dist/",
3 | "routes": {
4 | "/**": "index.html"
5 | }
6 | }
--------------------------------------------------------------------------------
/Frontend/src/assets/static/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/edit.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logo.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/csv-file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/csv-file.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/logotipo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logotipo.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/logotype.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logotype.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/delete-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/delete-user.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/edit-user.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/edit-user.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/iconotipo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/iconotipo.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/circle-regular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/circle-regular.png
--------------------------------------------------------------------------------
/Frontend/src/assets/static/logo-horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/logo-horizontal.png
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/Media.scss:
--------------------------------------------------------------------------------
1 | @media screen and(max-width: 736px) {
2 |
3 | }
4 |
5 | @media screen and(max-width: 480px) {
6 |
7 | }
--------------------------------------------------------------------------------
/Frontend/src/assets/static/delete-user-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leshz/megaCat/HEAD/Frontend/src/assets/static/delete-user-black.png
--------------------------------------------------------------------------------
/Frontend/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 85,
3 | "arrowParens": "always",
4 | "tabWidth": 2,
5 | "semi": true,
6 | "singleQuote": true
7 | }
--------------------------------------------------------------------------------
/Backend/config/database.js:
--------------------------------------------------------------------------------
1 | const config = require('./config')
2 | const database = {
3 | ...config.sequelize
4 | }
5 |
6 | module.exports = database
7 |
--------------------------------------------------------------------------------
/Backend/api/components/exams/index.js:
--------------------------------------------------------------------------------
1 | const store = require('../../../store/dummy')
2 | const ctrl = require('./controller')
3 |
4 | module.exports = ctrl(store)
5 |
--------------------------------------------------------------------------------
/Backend/api/components/notification/index.js:
--------------------------------------------------------------------------------
1 | const store = require('../../../store/sequelize')
2 | const ctrl = require('./controller')
3 |
4 | module.exports = ctrl(store)
5 |
--------------------------------------------------------------------------------
/Frontend/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-env', '@babel/preset-react'],
3 | plugins: ['@babel/plugin-proposal-class-properties'],
4 | };
5 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/PatientData.scss:
--------------------------------------------------------------------------------
1 | .Patient__personal_data, .Patient__medical_history {
2 | display: grid;
3 | grid-template-columns: 30% 35%;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/Backend/api/components/auth/index.js:
--------------------------------------------------------------------------------
1 | const config = require('../../../config/config')
2 | const store = require(`../../../store/${config.storeMotor}`)
3 | const ctrl = require('./controller')
4 |
5 | module.exports = ctrl(store)
6 |
--------------------------------------------------------------------------------
/Backend/api/components/user/index.js:
--------------------------------------------------------------------------------
1 | const config = require('../../../config/config')
2 | const store = require(`../../../store/${config.storeMotor}`)
3 | const ctrl = require('./controller')
4 |
5 | module.exports = ctrl(store)
6 |
--------------------------------------------------------------------------------
/Frontend/src/containers/NotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFound = () => (
4 | <>
5 |
No encontrado
6 | Regresa al Home
7 | >
8 | );
9 | export default NotFound;
10 |
--------------------------------------------------------------------------------
/Backend/api/components/email/index.js:
--------------------------------------------------------------------------------
1 | const config = require('../../../config/config')
2 | const store = require(`../../../store/${config.storeMotor}`)
3 | const ctrl = require('./controller')
4 |
5 | module.exports = ctrl(store)
6 |
--------------------------------------------------------------------------------
/Backend/api/components/auth_roles/index.js:
--------------------------------------------------------------------------------
1 | const config = require('../../../config/config')
2 | const store = require(`../../../store/${config.storeMotor}`)
3 | const ctrl = require('./controller')
4 |
5 | module.exports = ctrl(store)
6 |
--------------------------------------------------------------------------------
/Backend/.sequelizerc:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | 'config': path.resolve('config', 'database.js'),
5 | 'models-path': path.resolve('db', 'models'),
6 | 'seeders-path': path.resolve('db', 'seeders'),
7 | 'migrations-path': path.resolve('db', 'migrations')
8 | };
--------------------------------------------------------------------------------
/Backend/auth/index.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 | const config = require('../config/config')
3 | const secret = config.jwt.secret
4 |
5 | async function sign (data) {
6 | return jwt.sign(data, secret, {
7 | expiresIn: '90m'
8 | })
9 | }
10 |
11 | module.exports = {
12 | sign
13 | }
14 |
--------------------------------------------------------------------------------
/Frontend/.env.example:
--------------------------------------------------------------------------------
1 | FIREBASE_CONFIG_API_KEY=
2 |
3 | FIREBASE_CONFIG_AUTH_DOMAIN=
4 |
5 | FIREBASE_CONFIG_DATABASE_URL=
6 |
7 | FIREBASE_CONFIG_PROJECT_ID=
8 |
9 | FIREBASE_CONFIG_STORAGE_BUCKET=
10 |
11 | FIREBASE_CONFIG_MESSAGING_SENDER_ID=
12 |
13 | FIREBASE_CONFIG_WEB_APP_ID=
14 |
15 | FIREBASE_CONFIG_MEASUREMENT_ID=
--------------------------------------------------------------------------------
/Frontend/src/components/Layout.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Header from './Header';
3 | // import Footer from './Footer';
4 |
5 | const Layout = ({ children }) => (
6 |
7 |
8 | {children}
9 | {/* */}
10 |
11 | );
12 |
13 | export default Layout;
14 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/NavbarDoctor.scss:
--------------------------------------------------------------------------------
1 | .Patient__menu {
2 | padding: 0;
3 | display: flex;
4 | justify-content: space-evenly;
5 | align-items: center;
6 | border-bottom: 2px solid gray;
7 | > li {
8 | list-style: none;
9 | }
10 | a {
11 | text-decoration: none;
12 | color: #19243b;
13 | }
14 | }
--------------------------------------------------------------------------------
/Backend/utils/testServer.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const supertest = require('supertest')
3 | const errors = require('../network/errors')
4 |
5 | function testServer (route) {
6 | const app = express()
7 | app.use('/', route)
8 | app.use(errors)
9 | return supertest(app)
10 | }
11 |
12 | module.exports = testServer
13 |
--------------------------------------------------------------------------------
/Frontend/src/utils/gravatar.jsx:
--------------------------------------------------------------------------------
1 | import md5 from 'md5';
2 |
3 | const gravatar = (email) => {
4 | const base = 'https://gravatar.com/avatar/';
5 | const formattedEmail = (email).trim().toLowerCase();
6 | const hash = md5(formattedEmail, { encoding: 'binary' });
7 |
8 | return `${base}${hash}`;
9 | };
10 |
11 | export default gravatar;
12 |
--------------------------------------------------------------------------------
/Frontend/src/containers/PatientInfo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavbarDoctor from '../components/NavbarDoctor';
3 |
4 | const PatientInfo = () => {
5 | return (
6 | <>
7 |
10 | >
11 | );
12 | };
13 |
14 | export default PatientInfo;
15 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/User.scss:
--------------------------------------------------------------------------------
1 | $color__board--content: rgb(250, 221, 209);
2 | $color__board--board: rgb(219, 155, 36);
3 |
4 | .margen {
5 | background-color: $color__board--content;
6 | margin: 1em;
7 | }
8 |
9 | .tabla thead {
10 | background-color: $color__board--board;
11 | }
12 |
13 | .tabla th {
14 | height: 2em;
15 | }
--------------------------------------------------------------------------------
/Backend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | commonjs: true,
4 | es6: true,
5 | node: true,
6 | jest: true
7 | },
8 | extends: [
9 | 'standard'
10 | ],
11 | globals: {
12 | Atomics: 'readonly',
13 | SharedArrayBuffer: 'readonly'
14 | },
15 | parserOptions: {
16 | ecmaVersion: 2018
17 | },
18 | rules: {
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Backend/.env_example:
--------------------------------------------------------------------------------
1 | PORT=
2 | JWT_SECRET=
3 | KNEX_HOST=
4 | KNEX_USER=
5 | KNEX_PASSWORD=
6 | KNEX_DATABASE=
7 | KNEX_PORT=
8 | KNEX_CLIENT=
9 | DB_MOTOR=
10 | FB_APP_NAME=
11 | FB_CERT=
12 | DB_USERNAME=
13 | DB_PASSWORD=
14 | DB_DATABASE=
15 | DB_HOST=
16 | DB_PORT=
17 | DB_DIALECT=
18 | STORE_MOTOR=
19 |
20 | // Email
21 | EMAIL_HOST=
22 | EMAIL_PORT=
23 | EMAIL_USER=
24 | EMAIL_PASSWORD=
25 |
--------------------------------------------------------------------------------
/Backend/api/components/exams/controller.js:
--------------------------------------------------------------------------------
1 |
2 | const bloodExamn = require('../../../store/mocks/exams/BoodExam')
3 | // const TABLE = 'exam_types'
4 |
5 | module.exports = (store) => {
6 | const get = async () => {
7 | // const q = 'get all types of exams'
8 | // const types = await store.query(TABLE, q)
9 | const types = bloodExamn
10 | return types
11 | }
12 | return { get }
13 | }
14 |
--------------------------------------------------------------------------------
/Backend/api/components/auth_roles/controller.js:
--------------------------------------------------------------------------------
1 | const boom = require('@hapi/boom')
2 | const TABLE = 'auth_roles'
3 | module.exports = (store) => {
4 | async function insert (authId, roleId) {
5 | try {
6 | await store.insert(TABLE, { authId, roleId })
7 | return true
8 | } catch (error) {
9 | throw boom.internal()
10 | }
11 | }
12 |
13 | return {
14 | insert
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Backend/api/components/welcome/network.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const response = require('../../../network/response')
4 |
5 | router.get('/', welcome)
6 |
7 | function welcome (req, res) {
8 | const message = '🔬 Welcome to Exam Management System for Clinical Laboratories API'
9 | response.success(req, res, message)
10 | }
11 |
12 | module.exports = router
13 |
--------------------------------------------------------------------------------
/Backend/store/mocks/mockDB.js:
--------------------------------------------------------------------------------
1 | const users = require('./UsersMock')
2 | const auths = require('./AuthMock')
3 |
4 | const db = {
5 | users,
6 | auths,
7 | roles: [],
8 | permissions: [],
9 | medicalrecords: [],
10 | exams: [],
11 | examStatuses: [],
12 | examTypes: [],
13 | role_permissions: [],
14 | auth_roles: [],
15 | firebase_notification_tokens: []
16 | }
17 |
18 | module.exports = db
19 |
--------------------------------------------------------------------------------
/Backend/network/response.js:
--------------------------------------------------------------------------------
1 | exports.success = (req, res, message, status) => {
2 | const statusCode = status || 200
3 | res.status(statusCode).json({
4 | error: false,
5 | status: statusCode,
6 | body: message
7 | })
8 | }
9 |
10 | exports.error = (req, res, error, status = 500) => {
11 | res.status(status).json({
12 | error: true,
13 | status: status,
14 | body: error
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/ExamItem.scss:
--------------------------------------------------------------------------------
1 | .dashboard {
2 | margin: 0;
3 | font-weight: bold;
4 | text-align: center;
5 | }
6 |
7 | .exam__status {
8 | color: rgb(201, 201, 201);
9 | background-color: rgb(86, 46, 139);
10 | font-weight: bold;
11 | font-size: 15px;
12 | text-align: center
13 | }
14 |
15 | .fas.fa-check-square {
16 | color: #0C6B21;
17 | }
18 |
19 | .fas.fa-eye {
20 | color: #162188;
21 | }
--------------------------------------------------------------------------------
/Backend/api/components/user/controller.js:
--------------------------------------------------------------------------------
1 | const boom = require('@hapi/boom')
2 |
3 | const TABLE = 'users'
4 |
5 | module.exports = (store) => {
6 | async function insert (user) {
7 | if (!user) throw boom.badData()
8 | const userSaved = await store.insert(TABLE, user)
9 | return userSaved
10 | }
11 |
12 | async function get (id) {
13 | return store.get(TABLE, id)
14 | }
15 |
16 | return {
17 | insert,
18 | get
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Frontend/src/conf/config-firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiKey": "AIzaSyB2xfqqIkJ_mFuG0YpSjnqjWcJtXAfyAFI",
3 | "authDomain": "nextep-notifications.firebaseapp.com",
4 | "databaseURL": "https://nextep-notifications.firebaseio.com",
5 | "projectId": "nextep-notifications",
6 | "storageBucket": "nextep-notifications.appspot.com",
7 | "messagingSenderId": "53070194869",
8 | "appId": "1:53070194869:web:ebeedbca3e48efb5b3f6fc",
9 | "measurementId": "G-HHLTD6RZK6"
10 | }
--------------------------------------------------------------------------------
/Frontend/src/conf/firebase.js:
--------------------------------------------------------------------------------
1 | import * as firebase from "firebase/app";
2 | import "firebase/messaging";
3 | import firebaseConfig from "./config-firebase.json"
4 |
5 | // eslint-disable-next-line import/no-mutable-exports
6 | let messaging
7 | try {
8 | const initializedFirebaseApp = firebase.initializeApp(firebaseConfig);
9 | messaging = initializedFirebaseApp.messaging();
10 | } catch (err) {
11 | console.warn(err.message)
12 | messaging = null
13 | }
14 |
15 | export default messaging ;
16 |
--------------------------------------------------------------------------------
/Backend/db/models/exam.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const exam = sequelize.define('exam', {
4 | content: DataTypes.JSON,
5 | reservationDate: DataTypes.DATE,
6 | statusId: DataTypes.UUID,
7 | medicId: DataTypes.UUID,
8 | bacteriologistId: DataTypes.UUID,
9 | typeId: DataTypes.UUID,
10 | isDeleted: DataTypes.BOOLEAN
11 | }, {})
12 | exam.associate = function (models) {
13 | // associations can be defined here
14 | }
15 | return exam
16 | }
17 |
--------------------------------------------------------------------------------
/Backend/utils/userUtils.js:
--------------------------------------------------------------------------------
1 | const normalizeText = (text) => text
2 | .trim()
3 | .normalize('NFD')
4 | .replace(/[\u0300-\u036f]/g, '')
5 | .toLocaleLowerCase()
6 | .split(' ')[0]
7 |
8 | const generateUsername = (idNumber, firstName, lastName) => {
9 | const name = normalizeText(firstName)
10 | const surname = normalizeText(lastName)
11 | const identifier = idNumber.toString().slice(-4)
12 |
13 | return `${name}.${surname}.${identifier}`
14 | }
15 |
16 | module.exports = {
17 | generateUsername
18 | }
19 |
--------------------------------------------------------------------------------
/Frontend/src/actions/index.js:
--------------------------------------------------------------------------------
1 | export const editUser = (payload) => ({
2 | type: "EDIT_USER",
3 | payload,
4 | });
5 |
6 | export const addUser = (payload) => ({
7 | type: "ADD_USER",
8 | payload,
9 | });
10 |
11 | export const deleteUser = payload => ({
12 | type: "DELETE_USER",
13 | payload,
14 | });
15 |
16 | export const loginRequest = (payload) => ({
17 | type: "LOGIN_REQUEST",
18 | payload,
19 | });
20 |
21 | export const logoutRequest = (payload) => ({
22 | type: "LOGOUT_REQUEST",
23 | payload,
24 | });
25 |
--------------------------------------------------------------------------------
/Frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Nextep
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Frontend/src/hooks/useInitialState.js:
--------------------------------------------------------------------------------
1 | // This is a Custom Hooks, separating the logic of the components to this function
2 | import { useState, useEffect } from "react";
3 |
4 | const useInitialState = (API) => {
5 | const [dataInfo, setDataInfo] = useState({
6 | users: [],
7 | exams: [],
8 | });
9 |
10 | useEffect(() => {
11 | fetch(API)
12 | .then((response) => response.json())
13 | .then((data) => setDataInfo(data));
14 | }, []);
15 | console.log(dataInfo);
16 | return dataInfo;
17 | };
18 |
19 | export default useInitialState;
20 |
--------------------------------------------------------------------------------
/Backend/network/errors.js:
--------------------------------------------------------------------------------
1 | const response = require('./response')
2 |
3 | function errors (err, req, res, next) {
4 | let message = 'Internal error'
5 | let statusCode = 500
6 |
7 | if (err.output && err.output.payload) {
8 | message = err.output.payload.message
9 | statusCode = err.output.payload.statusCode
10 | } else if (err.message || err.statusCode) {
11 | message = err.message || message
12 | statusCode = err.statusCode || statusCode
13 | }
14 |
15 | return response.error(req, res, message, statusCode)
16 | }
17 |
18 | module.exports = errors
19 |
--------------------------------------------------------------------------------
/Backend/api/components/exams/network.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const router = express.Router()
3 | const Controller = require('./index')
4 | const response = require('../../../network/response')
5 |
6 | function typeOfExman (req, res) {
7 | return Controller.get()
8 | .then(data => {
9 | response.success(req, res, data, 200)
10 | })
11 | .catch(() =>
12 | response.error(req, res, 'Invalid information', 401)
13 | )
14 | }
15 |
16 | router.get('/', typeOfExman)
17 | const network = module.exports = router
18 | network.typeOfExman = typeOfExman
19 |
--------------------------------------------------------------------------------
/Backend/db/models/auth.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const auth = sequelize.define('auth', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | username: DataTypes.STRING,
10 | password: DataTypes.STRING,
11 | isDeleted: {
12 | type: DataTypes.BOOLEAN,
13 | defaultValue: false
14 | }
15 | }, {})
16 | auth.associate = function (models) {
17 | auth.belongsToMany(models.role, { through: models.auth_role })
18 | }
19 | return auth
20 | }
21 |
--------------------------------------------------------------------------------
/Backend/db/models/examstatus.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const examStatus = sequelize.define('examStatus', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | name: DataTypes.STRING,
10 | description: DataTypes.TEXT,
11 | isDeleted: {
12 | type: DataTypes.BOOLEAN,
13 | defaultValue: false
14 | }
15 | }, {})
16 | examStatus.associate = function (models) {
17 | // associations can be defined here
18 | }
19 | return examStatus
20 | }
21 |
--------------------------------------------------------------------------------
/Frontend/src/containers/Exam.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Exam = ({ children }) => (
4 |
5 |
6 |
7 |
8 | | Paciente-userId |
9 | Fecha |
10 | Nombre del exámen |
11 | Status |
12 | Disponibilidad |
13 | Descargar |
14 | Visualizar |
15 |
16 |
17 | {children}
18 |
19 |
20 | );
21 |
22 | export default Exam;
23 |
--------------------------------------------------------------------------------
/Backend/api/components/email/templates/sendNewUser.js:
--------------------------------------------------------------------------------
1 | const sendNewUser = (data) => `
2 |
8 | Hola ${data.firstName} ${data.lastName}
9 | Le damos la bienvenida a Nextep Labs, use estas credenciales en nuestra plataforma para acceder a todas las características creadas para usted:
10 |
11 |
12 | - Username: ${data.username}
13 | - Password: ${data.password}
14 |
15 | `
16 |
17 | module.exports = sendNewUser
18 |
--------------------------------------------------------------------------------
/Backend/db/models/rolepermission.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const rolePermission = sequelize.define('role_permission', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | roleId: DataTypes.UUID,
10 | permissionId: DataTypes.UUID,
11 | isDeleted: {
12 | type: DataTypes.BOOLEAN,
13 | defaultValue: false
14 | }
15 | }, {})
16 | rolePermission.associate = function (models) {
17 | // associations can be defined here
18 | }
19 | return rolePermission
20 | }
21 |
--------------------------------------------------------------------------------
/Backend/db/models/examtype.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const examType = sequelize.define('examType', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | name: DataTypes.STRING,
10 | description: DataTypes.TEXT,
11 | templateContent: DataTypes.JSON,
12 | isDeleted: {
13 | type: DataTypes.BOOLEAN,
14 | defaultValue: false
15 | }
16 | }, {})
17 | examType.associate = function (models) {
18 | // associations can be defined here
19 | }
20 | return examType
21 | }
22 |
--------------------------------------------------------------------------------
/Backend/db/models/permission.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const permission = sequelize.define('permission', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | name: DataTypes.STRING,
10 | description: DataTypes.TEXT,
11 | isDeleted: {
12 | type: DataTypes.BOOLEAN,
13 | defaultValue: false
14 | }
15 | }, {})
16 | permission.associate = function (models) {
17 | permission.belongsToMany(models.role, { through: models.role_permission })
18 | }
19 | return permission
20 | }
21 |
--------------------------------------------------------------------------------
/Frontend/src/components/Modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import '../assets/styles/components/Modal.scss';
4 |
5 | const Modal = (props) => {
6 | if (!props.isOpen) {
7 | return null;
8 | }
9 |
10 | return ReactDOM.createPortal(
11 |
12 |
13 |
16 | {props.children}
17 |
18 |
,
19 | document.getElementById('modal')
20 | );
21 | };
22 |
23 | export default Modal;
24 |
--------------------------------------------------------------------------------
/Backend/auth/roles.js:
--------------------------------------------------------------------------------
1 | const boom = require('@hapi/boom')
2 |
3 | function roleValidationHandler (allowedRoles) {
4 | return function (req, res, next) {
5 | if (!req.user || (!req.user && !req.user.roles)) {
6 | next(boom.unauthorized())
7 | }
8 |
9 | const userRoles = req.user.roles.map(role => role.name)
10 |
11 | const hasAccess = allowedRoles
12 | .map(allowedRole => userRoles.includes(allowedRole))
13 | .find(allowed => Boolean(allowed))
14 |
15 | if (hasAccess) {
16 | next()
17 | } else {
18 | next(boom.unauthorized())
19 | }
20 | }
21 | }
22 |
23 | module.exports = roleValidationHandler
24 |
--------------------------------------------------------------------------------
/Backend/db/models/authRole.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const authRole = sequelize.define('auth_role', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | authId: DataTypes.UUID,
10 | roleId: DataTypes.UUID,
11 | isDeleted: {
12 | type: DataTypes.BOOLEAN,
13 | defaultValue: false
14 | }
15 | }, {})
16 | authRole.associate = function (models) {
17 | authRole.belongsTo(models.auth, { foreignKey: 'authId' })
18 | authRole.belongsTo(models.role, { foreignKey: 'roleId' })
19 | }
20 | return authRole
21 | }
22 |
--------------------------------------------------------------------------------
/Frontend/src/containers/User.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../assets/styles/components/User.scss';
3 |
4 | const User = ({ children }) => (
5 |
6 |
7 |
8 |
9 | | No. Identificación |
10 | Nombre y Apellidos |
11 | Username |
12 | Email |
13 | Teléfono |
14 | Domicilio |
15 | Editar |
16 | Eliminar |
17 |
18 |
19 | {children}
20 |
21 |
22 | );
23 |
24 | export default User;
25 |
--------------------------------------------------------------------------------
/Backend/db/seeders/20200531065934-roles.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const { Op } = require('sequelize')
3 | const { roles, rolesNameEnum } = require('../../store/mocks/RolesMock.js')
4 |
5 | module.exports = {
6 | up: (queryInterface, Sequelize) => {
7 | return queryInterface.bulkInsert('roles', [...roles], {})
8 | },
9 |
10 | down: (queryInterface, Sequelize) => {
11 | return queryInterface.bulkDelete('roles', {
12 | name: {
13 | [Op.in]: [
14 | rolesNameEnum.ADMINISTRATOR,
15 | rolesNameEnum.MEDIC,
16 | rolesNameEnum.BACTERIOLOGIST,
17 | rolesNameEnum.PACIENT
18 | ]
19 | }
20 | }, {})
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Backend/db/models/role.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const role = sequelize.define('role', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | name: DataTypes.STRING,
10 | description: DataTypes.TEXT,
11 | isDeleted: {
12 | type: DataTypes.BOOLEAN,
13 | defaultValue: false
14 | }
15 | }, {})
16 | role.associate = function (models) {
17 | role.belongsToMany(models.auth, { through: models.auth_role })
18 | role.belongsToMany(models.permission, { through: models.role_permission, as: 'permissions' })
19 | }
20 | return role
21 | }
22 |
--------------------------------------------------------------------------------
/Backend/db/models/firebasenotificationtoken.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const firebaseNotificationToken = sequelize.define('firebaseNotificationToken', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | userId: DataTypes.UUID,
10 | token: DataTypes.JSON,
11 | isDeleted: {
12 | type: DataTypes.BOOLEAN,
13 | defaultValue: false
14 | }
15 | }, {})
16 | firebaseNotificationToken.associate = function (models) {
17 | firebaseNotificationToken.belongsTo(models.user, { foreignKey: 'userId' })
18 | }
19 | return firebaseNotificationToken
20 | }
21 |
--------------------------------------------------------------------------------
/Backend/store/mocks/storeMock.js:
--------------------------------------------------------------------------------
1 | const db = require('./mockDB')
2 | const dummy = require('../dummy')
3 |
4 | const get = jest.fn((table, id) => Promise.resolve(db[table][0]))
5 | const list = jest.fn((table) => Promise.resolve(db[table]))
6 | const insert = jest.fn((table, data, trx = null) => Promise.resolve(db[table][0]))
7 | const update = jest.fn((table, id, data) => Promise.resolve(db[table][1]))
8 | const remove = jest.fn((table, id) => Promise.resolve(true))
9 | const query = jest.fn((table, query, join = null) => {
10 | return dummy.query(table, query)
11 | })
12 | const model = jest.fn((table) => table)
13 |
14 | module.exports = {
15 | get,
16 | list,
17 | insert,
18 | update,
19 | remove,
20 | query,
21 | model
22 | }
23 |
--------------------------------------------------------------------------------
/Frontend/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | const reducer = (state, action) => {
2 |
3 | switch (action.type) {
4 |
5 | case "ADD_USER":
6 | return {
7 | ...state,
8 | users: [...state.users, action.payload],
9 | };
10 |
11 | case "DELETE_USER":
12 | return {
13 | ...state,
14 | users: state.users.filter((items) => items.id !== action.payload),
15 | };
16 |
17 | case "LOGIN_REQUEST":
18 | return {
19 | ...state,
20 | user: action.payload,
21 | };
22 |
23 | case "LOGOUT_REQUEST":
24 | return {
25 | ...state,
26 | user: action.payload,
27 | };
28 |
29 | default:
30 | return state;
31 | }
32 | };
33 |
34 | export default reducer;
35 |
--------------------------------------------------------------------------------
/Backend/auth/strategies/basic.js:
--------------------------------------------------------------------------------
1 | const passport = require('passport')
2 | const { BasicStrategy } = require('passport-http')
3 | const boom = require('@hapi/boom')
4 | const bcrypt = require('bcrypt')
5 |
6 | const authService = require('../../api/components/auth')
7 |
8 | passport.use(new BasicStrategy(async (username, password, cb) => {
9 | try {
10 | const user = await authService.get({ username })
11 |
12 | if (!user || user.isDeleted) {
13 | return cb(boom.unauthorized(), false)
14 | }
15 |
16 | const isValidLogin = await bcrypt.compare(password, user.password)
17 |
18 | if (!isValidLogin) {
19 | return cb(boom.unauthorized(), false)
20 | }
21 | return cb(null, user)
22 | } catch (error) {
23 | return cb(error)
24 | }
25 | }))
26 |
--------------------------------------------------------------------------------
/Backend/db/models/user.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const user = sequelize.define('user', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | idNumber: DataTypes.INTEGER,
10 | username: DataTypes.STRING,
11 | firstName: DataTypes.STRING,
12 | lastName: DataTypes.STRING,
13 | email: DataTypes.STRING,
14 | contactNumber: DataTypes.STRING,
15 | address: DataTypes.STRING,
16 | medicalRecordId: DataTypes.UUID,
17 | isDeleted: {
18 | type: DataTypes.BOOLEAN,
19 | defaultValue: false
20 | }
21 | }, {})
22 | user.associate = function (models) {
23 | // associations can be defined here
24 | }
25 | return user
26 | }
27 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/containers/AddExams.scss:
--------------------------------------------------------------------------------
1 | @import '../App.scss';
2 |
3 | .Add__exam {
4 | h1 {
5 | color: #FF5C00;
6 | text-align: center;
7 | }
8 | }
9 |
10 | .Add__exam--options {
11 | display: grid;
12 | grid-template-columns: 25% 50% 25%;
13 | justify-items: center;
14 | border-bottom: 2px solid #A1A1A1;
15 | }
16 |
17 | .Add__exam--list--cards {
18 | margin: 0;
19 | padding: 1.5em 0 0 0;
20 | }
21 |
22 | .Exam__card {
23 | @include cardList;
24 | .Exam__watch--icon {
25 | @include cardIcon;
26 | background: 0;
27 | border: none;
28 | }
29 | }
30 |
31 | .Add__exam--name--patient {
32 | width: 30%;
33 | margin-left: auto;
34 | background: #E96216;
35 | border-radius: 15px;
36 | h3 {
37 | color: #fff;
38 | text-align: center;
39 | }
40 | }
--------------------------------------------------------------------------------
/Backend/auth/strategies/cookie.js:
--------------------------------------------------------------------------------
1 | const passport = require('passport')
2 | const boom = require('@hapi/boom')
3 | const CookieStrategy = require('passport-cookie')
4 | const config = require('../../config/config')
5 | const authService = require('../../api/components/auth')
6 | const { verify } = require('jsonwebtoken')
7 |
8 | passport.use(new CookieStrategy(
9 | async function (token, cb) {
10 | try {
11 | const userData = await verify(token, config.jwt.secret)
12 |
13 | const user = await authService.get({ id: userData.id })
14 |
15 | if (!user || user.isBloqued) {
16 | return (boom.unauthorized(), null)
17 | }
18 | delete user.password
19 |
20 | return cb(null, user)
21 | } catch (error) {
22 | return cb(boom.unauthorized(), null)
23 | }
24 | }
25 | ))
26 |
--------------------------------------------------------------------------------
/Backend/__test__/api.components.welcome.spec.js:
--------------------------------------------------------------------------------
1 | const testServer = require('../utils/testServer')
2 |
3 | describe('API | Components | Welcome', () => {
4 | const route = require('../api/components/welcome/network')
5 | const request = testServer(route)
6 | const message = '🔬 Welcome to Exam Management System for Clinical Laboratories API'
7 | describe('Network', () => {
8 | it('should respond with status 201', (done) => {
9 | request.get('/').expect(200, done)
10 | })
11 |
12 | it('should respond with a response.success structure', (done) => {
13 | const expected = {
14 | status: 200,
15 | error: false,
16 | body: message
17 | }
18 | request.get('/').end((_, res) => {
19 | expect(res.body).toEqual(expected)
20 | done()
21 | })
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/Backend/auth/strategies/jwt.js:
--------------------------------------------------------------------------------
1 | const passport = require('passport')
2 | const { ExtractJwt, Strategy } = require('passport-jwt')
3 | const boom = require('@hapi/boom')
4 |
5 | const config = require('../../config/config')
6 | const authService = require('../../api/components/auth')
7 |
8 | passport.use(
9 | new Strategy({
10 | secretOrKey: config.jwt.secret,
11 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
12 | },
13 | async (tokenPayload, cb) => {
14 | try {
15 | const user = await authService.get({ id: tokenPayload.id })
16 |
17 | if (!user || user.isDeleted) {
18 | return cb(boom.unauthorized(), false)
19 | }
20 |
21 | delete user.password
22 |
23 | return cb(null, user)
24 | } catch (error) {
25 | return cb(boom.unauthorized(), false)
26 | }
27 | })
28 | )
29 |
--------------------------------------------------------------------------------
/Backend/db/models/medicalrecord.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = (sequelize, DataTypes) => {
3 | const medicalRecord = sequelize.define('medicalRecord', {
4 | id: {
5 | type: DataTypes.UUID,
6 | defaultValue: DataTypes.UUIDV4,
7 | primaryKey: true
8 | },
9 | birthdate: DataTypes.DATE,
10 | blodType: DataTypes.STRING,
11 | allergies: DataTypes.TEXT,
12 | weight: DataTypes.STRING,
13 | heigth: DataTypes.STRING,
14 | medicationNotes: DataTypes.TEXT,
15 | diabetes: DataTypes.BOOLEAN,
16 | smoking: DataTypes.BOOLEAN,
17 | alcoholism: DataTypes.BOOLEAN,
18 | isDeleted: {
19 | type: DataTypes.BOOLEAN,
20 | defaultValue: false
21 | }
22 | }, {})
23 | medicalRecord.associate = function (models) {
24 | // associations can be defined here
25 | }
26 | return medicalRecord
27 | }
28 |
--------------------------------------------------------------------------------
/Frontend/src/components/ExamList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 |
5 | const ExamList = (props) => {
6 | const { id, examName, examDescription } = props;
7 |
8 | return (
9 | <>
10 |
11 | Exámen
12 | {examName}
13 | Descripción
14 | {examDescription}
15 |
19 |
20 | >
21 | );
22 | };
23 |
24 | ExamList.propTypes = {
25 | id: PropTypes.number,
26 | examName: PropTypes.string,
27 | examDescription: PropTypes.string,
28 | };
29 |
30 | export default connect(null, null)(ExamList);
31 |
--------------------------------------------------------------------------------
/Backend/api/components/email/controller.js:
--------------------------------------------------------------------------------
1 | const boom = require('@hapi/boom')
2 | const nodemailer = require('nodemailer')
3 | const config = require('../../../config/config')
4 | const sendNewUserTemplate = require('./templates/sendNewUser')
5 |
6 | const transport = nodemailer.createTransport({
7 | host: config.email.emailHost,
8 | port: config.email.emailPort,
9 | auth: {
10 | user: config.email.emailUser,
11 | pass: config.email.emailPassword
12 | }
13 | })
14 | module.exports = () => {
15 | async function sendNewUser (data) {
16 | try {
17 | const mailOptions = {
18 | from: 'not-reply@nextsteplabs.com',
19 | to: data.email,
20 | subject: 'Bienvenido a Next Step Labs',
21 | html: sendNewUserTemplate(data)
22 | }
23 |
24 | await transport.sendMail(mailOptions)
25 |
26 | return true
27 | } catch (error) {
28 | throw boom.internal()
29 | }
30 | }
31 |
32 | return {
33 | sendNewUser
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Backend/api/components/notification/network.js:
--------------------------------------------------------------------------------
1 | const Controller = require('./index')
2 |
3 | const express = require('express')
4 | const response = require('../../../network/response')
5 | const passport = require('passport')
6 | const router = express.Router()
7 |
8 | router.get('/', testNotification)
9 | router.post('/setToken', passport.authenticate('basic', { session: true }), addTokentoUser)
10 |
11 | function testNotification (req, res) {
12 | return Controller.sendMessageMulticast(req.body)
13 | .then(data => {
14 | response.success(req, res, data, 200)
15 | })
16 | .catch((err) => {
17 | response.error(req, res, err, 401)
18 | })
19 | }
20 |
21 | function addTokentoUser (req, res) {
22 | return Controller.saveTokenToUser((req && req.user) || null, req.body)
23 | .then(data => {
24 | response.success(req, res, data, 200)
25 | })
26 | .catch(err => {
27 | response.error(req, res, err, 401)
28 | })
29 | }
30 |
31 | module.exports = router
32 |
--------------------------------------------------------------------------------
/Backend/network/__test__/errors.spec.js:
--------------------------------------------------------------------------------
1 | const errors = require('../errors')
2 | jest.mock('../response.js', () => ({
3 | error: (req, res, message, statusCode) => ({ error: true, status: statusCode, body: message })
4 | }))
5 | const boom = require('@hapi/boom')
6 | describe('Error Middleware', () => {
7 | describe('Boom error', () => {
8 | it('should manage boom error ', () => {
9 | const expectedResult = {
10 | error: true,
11 | status: 401,
12 | body: 'Unauthorized'
13 | }
14 | expect(errors(boom.unauthorized())).toEqual(expectedResult)
15 | })
16 | })
17 |
18 | describe('Regular error', () => {
19 | it('should manage regular error', () => {
20 | const expectedResult = {
21 | error: true,
22 | status: 500,
23 | body: 'Regular error'
24 | }
25 |
26 | const regularError = new Error(expectedResult.body)
27 |
28 | expect(errors(regularError)).toEqual(expectedResult)
29 | })
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200526161357-create-auth.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('auths', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | username: {
11 | type: Sequelize.STRING
12 | },
13 | password: {
14 | type: Sequelize.STRING
15 | },
16 | isDeleted: {
17 | type: Sequelize.BOOLEAN,
18 | defaultValue: false
19 | },
20 | createdAt: {
21 | allowNull: false,
22 | type: Sequelize.DATE,
23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
24 | },
25 | updatedAt: {
26 | allowNull: false,
27 | type: Sequelize.DATE,
28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
29 | }
30 | })
31 | },
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.dropTable('auths')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200527002438-create-role.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('roles', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | name: {
11 | type: Sequelize.STRING
12 | },
13 | description: {
14 | type: Sequelize.TEXT
15 | },
16 | isDeleted: {
17 | type: Sequelize.BOOLEAN,
18 | defaultValue: false
19 | },
20 | createdAt: {
21 | allowNull: false,
22 | type: Sequelize.DATE,
23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
24 | },
25 | updatedAt: {
26 | allowNull: false,
27 | type: Sequelize.DATE,
28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
29 | }
30 | })
31 | },
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.dropTable('roles')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200527010113-create-auth-role.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('auth_roles', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | authId: {
11 | type: Sequelize.UUID
12 | },
13 | roleId: {
14 | type: Sequelize.UUID
15 | },
16 | isDeleted: {
17 | type: Sequelize.BOOLEAN,
18 | defaultValue: false
19 | },
20 | createdAt: {
21 | allowNull: false,
22 | type: Sequelize.DATE,
23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
24 | },
25 | updatedAt: {
26 | allowNull: false,
27 | type: Sequelize.DATE,
28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
29 | }
30 | })
31 | },
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.dropTable('auth_roles')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/SheduleExams.scss:
--------------------------------------------------------------------------------
1 | .Shedule__exam--list {
2 | margin-top: 2.5em;
3 | .Shedule__exam--cards{
4 | padding: 0;
5 | margin: 0;
6 | display: grid;
7 | grid-template-columns: repeat(3, 30%);
8 | justify-content: space-between;
9 | grid-gap: 20px 0;
10 | }
11 | .Shedule__exam--item {
12 | padding: 1em;
13 | list-style: none;
14 | background: #fff;
15 | border: 1px solid #e0e0e0;
16 | border-radius: 15px;
17 | h3 {
18 | margin-top: 0;
19 | }
20 | }
21 | .Shedule__add--button {
22 | width: 60%;
23 | margin: auto;
24 | color: #fff;
25 | background: #19243B;
26 | padding: 0.8em;
27 | border: none;
28 | border-radius: 5px;
29 | display: flex;
30 | align-items: center;
31 | justify-content: center;
32 | transition: all 0.3s;
33 | &:hover {
34 | transform: scale(0.9);
35 | }
36 | > i {
37 | margin-left: 10px;
38 | font-size: 20px;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Frontend/src/components/PatientList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | import PropTypes from 'prop-types';
5 |
6 | const PatientList = (props) => {
7 | const { id, idNumber, firstName, lastName } = props;
8 |
9 | return (
10 | <>
11 |
12 | {idNumber}
13 |
14 | {firstName} {lastName}
15 |
16 |
21 |
22 |
23 |
24 |
25 |
26 | >
27 | );
28 | };
29 |
30 | PatientList.propTypes = {
31 | // id: PropTypes.number,
32 | idNumber: PropTypes.number,
33 | firstName: PropTypes.string,
34 | lastName: PropTypes.string,
35 | };
36 |
37 | export default connect(null, null)(PatientList);
38 |
--------------------------------------------------------------------------------
/Frontend/src/components/PersonalInfo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import UsersMock from '../mocks/UsersMock.json'
3 | import '../assets/styles/components/PatientData.scss'
4 |
5 | const PersonalInfo = () => {
6 | return (
7 |
8 | {UsersMock.map((item) => {
9 | return (
10 |
11 |
Nombres:
12 |
{item.first_name}
13 |
Apellidos:
14 |
{item.last_name}
15 |
Número de Identificación:
16 |
{item.id_number}
17 |
Usuario:
18 |
{item.username}
19 |
Correo electrónico:
20 |
{item.email}
21 |
Dirección:
22 |
{item.address}
23 |
Teléfono
24 |
{item.contact_number}
25 |
26 | );
27 | })}
28 |
29 | );
30 | }
31 |
32 | export default PersonalInfo
--------------------------------------------------------------------------------
/Backend/db/migrations/20200527002503-create-permission.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('permissions', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | name: {
11 | type: Sequelize.STRING
12 | },
13 | description: {
14 | type: Sequelize.TEXT
15 | },
16 | isDeleted: {
17 | type: Sequelize.BOOLEAN,
18 | defaultValue: false
19 | },
20 | createdAt: {
21 | allowNull: false,
22 | type: Sequelize.DATE,
23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
24 | },
25 | updatedAt: {
26 | allowNull: false,
27 | type: Sequelize.DATE,
28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
29 | }
30 | })
31 | },
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.dropTable('permissions')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200528063216-create-examStatus.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('examStatuses', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | name: {
11 | type: Sequelize.STRING
12 | },
13 | description: {
14 | type: Sequelize.TEXT
15 | },
16 | isDeleted: {
17 | type: Sequelize.BOOLEAN,
18 | defaultValue: false
19 | },
20 | createdAt: {
21 | allowNull: false,
22 | type: Sequelize.DATE,
23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
24 | },
25 | updatedAt: {
26 | allowNull: false,
27 | type: Sequelize.DATE,
28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
29 | }
30 | })
31 | },
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.dropTable('examStatuses')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Frontend/src/components/AddResultsModal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from './Modal';
3 |
4 | const AddResultsModal = (props) => {
5 | return (
6 |
7 |
8 |
Glucosa
9 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default AddResultsModal;
--------------------------------------------------------------------------------
/Backend/db/migrations/20200527002633-create-role-permission.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('role_permissions', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | roleId: {
11 | type: Sequelize.UUID
12 | },
13 | permissionId: {
14 | type: Sequelize.UUID
15 | },
16 | isDeleted: {
17 | type: Sequelize.BOOLEAN,
18 | defaultValue: false
19 | },
20 | createdAt: {
21 | allowNull: false,
22 | type: Sequelize.DATE,
23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
24 | },
25 | updatedAt: {
26 | allowNull: false,
27 | type: Sequelize.DATE,
28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
29 | }
30 | })
31 | },
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.dropTable('role_permissions')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Frontend/src/components/MedicalHistory.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import '../assets/styles/components/PatientData.scss';
3 |
4 | const MedicalHistory = (props) => {
5 |
6 | return (
7 |
8 |
9 |
Fecha de Nacimiento:
10 |
{}
11 |
Género:
12 |
13 |
Tipo de sangre:
14 |
15 |
Peso:
16 |
17 |
Altura:
18 |
19 |
Enfermedades crónicas propias:
20 |
21 |
Enfermedades crónicas heredadas:
22 |
23 |
Consume medicamentos:
24 |
25 |
Alergías:
26 |
27 |
Diabetes:
28 |
29 |
Tabaquismo:
30 |
31 |
Alcoholismo:
32 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default MedicalHistory;
39 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200531013304-create-firebase-notification-token.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('firebaseNotificationTokens', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | userId: {
11 | type: Sequelize.UUID
12 | },
13 | token: {
14 | type: Sequelize.JSON
15 | },
16 | isDeleted: {
17 | type: Sequelize.BOOLEAN,
18 | defaultValue: false
19 | },
20 | createdAt: {
21 | allowNull: false,
22 | type: Sequelize.DATE,
23 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
24 | },
25 | updatedAt: {
26 | allowNull: false,
27 | type: Sequelize.DATE,
28 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
29 | }
30 | })
31 | },
32 | down: (queryInterface, Sequelize) => {
33 | return queryInterface.dropTable('firebaseNotificationTokens')
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Frontend/README.md:
--------------------------------------------------------------------------------
1 | # FRONTEND - MegaCat
2 |
3 | Final Project PlatziMaster
4 |
5 | ## Converting to React Components, Frontend
6 |
7 | WIP
8 | Working adding React JS
9 |
10 | ---
11 |
12 | ### Clone this repo
13 |
14 | You can use and change 'my_folder' on this instruction to create a new folder
15 |
16 | ```bash
17 | git clone git@github.com:FernandoTorresL/Curso_ReactJS.git my_folder
18 | ```
19 |
20 | ### Change to working directory and install
21 |
22 | ```bash
23 | cd my_folder
24 | npm install
25 | ```
26 |
27 | ### Start fake API
28 | Start the json server to simulate an API for this project.
29 | ```
30 | json-server initialState.json
31 | ```
32 | You can see the data [here](http://localhost:3000/initialState)
33 |
34 | ### Execute
35 |
36 | Now, open a new terminal, in the same folder, and execute with
37 |
38 | ```bash
39 | npm run start
40 | ```
41 |
42 | ---
43 |
44 | ### Follow me
45 |
46 | [fertorresmx.dev](http://fertorresmx.dev/)
47 | [fertorresmx.com](http://fertorresmx.com/)
48 |
49 | #### :globe_with_meridians: Twitter, Instagram: @fertorresmx
50 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200528062121-create-examtype.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('examTypes', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | name: {
11 | type: Sequelize.STRING
12 | },
13 | description: {
14 | type: Sequelize.TEXT
15 | },
16 | templateContent: {
17 | type: Sequelize.JSON
18 | },
19 | isDeleted: {
20 | type: Sequelize.BOOLEAN,
21 | defaultValue: false
22 | },
23 | createdAt: {
24 | allowNull: false,
25 | type: Sequelize.DATE,
26 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
27 | },
28 | updatedAt: {
29 | allowNull: false,
30 | type: Sequelize.DATE,
31 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
32 | }
33 | })
34 | },
35 | down: (queryInterface, Sequelize) => {
36 | return queryInterface.dropTable('examTypes')
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/RememberInfo.scss:
--------------------------------------------------------------------------------
1 | @import '../App.scss';
2 |
3 | .RememberInfo {
4 | @include home-themes;
5 | height: 100vh;;
6 | .Logo {
7 | margin: 0;
8 | padding-top: 5%;
9 | }
10 | &__container {
11 | width: 50%;
12 | margin: auto;
13 | &--form--options {
14 | input {
15 | width: 45%;
16 | margin: 1em auto;
17 | line-height: 1.2;
18 | color: #333333;
19 | display: block;
20 | background: #fff;
21 | border-radius: 2em;
22 | padding: 1em;
23 | border: none;
24 | text-align: center;
25 | }
26 | span {
27 | font-size: 15px;
28 | color: #999999;
29 | display: flex;
30 | align-items: center;
31 | position: absolute;
32 | bottom: 0;
33 | left: 36%;
34 | width: inherit;
35 | height: 100%;
36 | padding-left: 40px;
37 | transition: all 0.4s;
38 | }
39 | }
40 | &--form {
41 | button {
42 | @include home-button-send;
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Backend/db/models/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs')
4 | const path = require('path')
5 | const Sequelize = require('sequelize')
6 | const basename = path.basename(__filename)
7 | const env = process.env.NODE_ENV || 'development'
8 | const config = require(path.join(__dirname, '/../../config/database'))[env]
9 | const db = {}
10 |
11 | let sequelize
12 | if (config.use_env_variable) {
13 | sequelize = new Sequelize(process.env[config.use_env_variable], config)
14 | } else {
15 | sequelize = new Sequelize(config.database, config.username, config.password, config)
16 | }
17 |
18 | fs
19 | .readdirSync(__dirname)
20 | .filter(file => {
21 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js')
22 | })
23 | .forEach(file => {
24 | const model = sequelize.import(path.join(__dirname, file))
25 | db[model.name] = model
26 | })
27 |
28 | Object.keys(db).forEach(modelName => {
29 | if (db[modelName].associate) {
30 | db[modelName].associate(db)
31 | }
32 | })
33 |
34 | db.sequelize = sequelize
35 | db.Sequelize = Sequelize
36 |
37 | module.exports = db
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Jeffer Barragán
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 |
--------------------------------------------------------------------------------
/Backend/__test__/api.components.user.spec.js:
--------------------------------------------------------------------------------
1 | const Controller = require('../api/components/user/controller.js')
2 | const storeMock = require('../store/mocks/storeMock.js')
3 | const userMock = require('../store/mocks/UsersMock.json')
4 |
5 | describe('API | Components | User', () => {
6 | const controller = Controller(storeMock)
7 | it('should call insert user', async (done) => {
8 | const userData = {
9 | idNumber: userMock[0].idNumber,
10 | firstName: userMock[0].firstName,
11 | lastName: userMock[0].lastName,
12 | email: userMock[0].email,
13 | contactNumber: userMock[0].contactNumber,
14 | address: userMock[0].address
15 | }
16 | const expectedCall = ['users', userData]
17 | await controller.insert(userData)
18 | expect(storeMock.insert.mock.calls.length).toBe(1)
19 | expect(storeMock.insert.mock.calls[0]).toEqual(expectedCall)
20 | await storeMock.insert.mockReset()
21 | done()
22 | })
23 |
24 | it('should return an error if not pass data', async (done) => {
25 | const response = controller.insert()
26 | expect(response).rejects.toThrow()
27 | done()
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/Frontend/src/components/SheduleExams.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import ExamList from '../components/ExamList';
4 | // import { addExam } from '../actions'
5 | import '../assets/styles/components/SheduleExams.scss';
6 | const SheduleExams = ({ exams }) => {
7 | // onHandleSubmit = (e) => {
8 | // e.preventDefault();
9 | // };
10 | return (
11 |
12 | {/*
13 |
/
14 | */}
15 |
16 |
17 | {exams.map((item) => {
18 | return (
19 |
24 | );
25 | })}
26 |
27 |
28 |
29 | );
30 | };
31 |
32 | const mapStateToProps = (state) => {
33 | return {
34 | exams: state.exams,
35 | };
36 | };
37 |
38 | export default connect(mapStateToProps, null)(SheduleExams);
39 |
--------------------------------------------------------------------------------
/Frontend/src/components/NavbarDoctor.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 | import '../assets/styles/components/NavbarDoctor.scss';
4 |
5 | const NavbarDoctor = () => {
6 | return (
7 |
41 | );
42 | };
43 |
44 | export default NavbarDoctor;
45 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/containers/PatientList.scss:
--------------------------------------------------------------------------------
1 | @import '../App.scss';
2 |
3 | .Patient--search {
4 | position: absolute;
5 | top: 18%;
6 | width: 30%;
7 | > input {
8 | padding: 0 30px 0 53px;
9 | border: 1px solid #A1A1A1;
10 | width: 100%;
11 | font-size: 15px;
12 | line-height: 1.2;
13 | color: #333333;
14 | display: block;
15 | height: 35px;
16 | border-radius: 25px;
17 | box-sizing: border-box;
18 | }
19 | > span {
20 | font-size: 15px;
21 | color: #999999;
22 | display: flex;
23 | align-items: center;
24 | position: absolute;
25 | border-radius: 25px;
26 | bottom: 0;
27 | left: 0;
28 | height: 100%;
29 | padding-left: 20px;
30 | }
31 | }
32 |
33 | .Patient__list--options {
34 | display: grid;
35 | grid-template-columns: 25% 50% 25%;
36 | justify-items: center;
37 | border-bottom: 2px solid #A1A1A1;
38 | text-align: center;
39 | h3 {
40 | color: #19243B;
41 | }
42 | }
43 |
44 | .Patient__list--cards {
45 | margin: 0;
46 | padding: 1.5em 0 0 0;
47 | }
48 |
49 | .Patient__card {
50 | @include cardList;
51 | .Patient__watch--icon {
52 | @include cardIcon;
53 | }
54 | }
--------------------------------------------------------------------------------
/Frontend/src/components/ExamItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import '../assets/styles/components/ExamItem.scss'
5 |
6 | const ExamItem = (props) => {
7 |
8 | const {
9 | id,
10 | userId,
11 | examDate,
12 | examName,
13 | examStatus,
14 | } = props;
15 |
16 | return (
17 |
18 | | {userId} |
19 | {examDate} |
20 | {examName} |
21 | {examStatus} |
22 |
23 |
24 | |
25 |
26 |
27 | |
28 |
29 |
30 | |
31 |
32 | );
33 | };
34 |
35 | ExamItem.propTypes = {
36 | id: PropTypes.number,
37 | userId: PropTypes.string,
38 | examDate: PropTypes.string,
39 | examName: PropTypes.string,
40 | examStatus: PropTypes.string
41 | };
42 |
43 | export default connect(null, null)(ExamItem);
44 |
--------------------------------------------------------------------------------
/Frontend/src/sw/firebase-messaging-sw.js:
--------------------------------------------------------------------------------
1 | importScripts("https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js");
2 | importScripts("https://www.gstatic.com/firebasejs/6.2.0/firebase-messaging.js");
3 |
4 | const firebaseConfig = {
5 | "apiKey": "AIzaSyB2xfqqIkJ_mFuG0YpSjnqjWcJtXAfyAFI",
6 | "authDomain": "nextep-notifications.firebaseapp.com",
7 | "databaseURL": "https://nextep-notifications.firebaseio.com",
8 | "projectId": "nextep-notifications",
9 | "storageBucket": "nextep-notifications.appspot.com",
10 | "messagingSenderId": "53070194869",
11 | "appId": "1:53070194869:web:ebeedbca3e48efb5b3f6fc",
12 | "measurementId": "G-HHLTD6RZK6"
13 | };
14 |
15 | console.log(process.env.FIREBASE_CONFIG_MEASUREMENT_ID);
16 |
17 | // Initialize Firebase
18 | firebase.initializeApp(firebaseConfig);
19 |
20 | const messaging = firebase.messaging();
21 |
22 | messaging.setBackgroundMessageHandler((payload) => {
23 | console.log(
24 | "[firebase-messaging-sw.js] Received background message ",
25 | payload
26 | );
27 | const { data } = payload;
28 | const options = {
29 | body: data.body,
30 | };
31 | // eslint-disable-next-line no-restricted-globals
32 | return self.registration.showNotification(data.title, options);
33 | });
34 |
--------------------------------------------------------------------------------
/Frontend/src/components/RememberInfo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import logo from '../assets/static/logo.png';
4 | import '../assets/styles/components/RememberInfo.scss';
5 |
6 | const RememberInfo = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Digite su usuario, se enviará un link a su correo asociado para
17 | reestablecer la contraseña
18 |
19 |
33 |
34 |
35 | );
36 | };
37 |
38 | export default RememberInfo;
39 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/App.scss:
--------------------------------------------------------------------------------
1 | @import './Media.scss';
2 |
3 | body {
4 | margin: 0;
5 | padding: 0;
6 | box-sizing: border-box;
7 | font-family: 'Quicksand', sans-serif;
8 | }
9 |
10 | a,
11 | button {
12 | cursor: pointer;
13 | }
14 |
15 | input:focus,
16 | button:focus {
17 | outline: none !important;
18 | box-shadow: none;
19 | }
20 |
21 | @mixin home-themes{
22 | background-color: #19243b;
23 | color: #fff;
24 | text-align: center;
25 | }
26 |
27 | @mixin home-button-send{
28 | width: 20%;
29 | margin: 2em auto;
30 | padding: .7em;
31 | display: block;
32 | background: rgba(255, 255, 255, 0.5);
33 | border: none;
34 | border-radius: 2em;
35 | transition: all 0.5s;
36 | text-decoration: none;
37 | &:hover {
38 | transform: scale(0.9);
39 | }
40 | }
41 | @mixin cardList {
42 | margin: .5em 0;
43 | display: grid;
44 | grid-template-columns: 25% 50% 25%;
45 | justify-items: center;
46 | align-items: center;
47 | border: 1px solid #efefef;
48 | border-radius: 15px;
49 | transition: all .3s;
50 | &:hover {
51 | background: #f5f5f5;
52 | }
53 | }
54 |
55 | @mixin cardIcon {
56 | color: #19243B;
57 | cursor: pointer;
58 | }
59 |
60 | .Container {
61 | max-width: 85%;
62 | margin: auto;
63 | }
--------------------------------------------------------------------------------
/Backend/db/migrations/20200528063918-create-exam.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('exams', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | content: {
11 | type: Sequelize.JSON
12 | },
13 | reservationDate: {
14 | type: Sequelize.DATE
15 | },
16 | statusId: {
17 | type: Sequelize.UUID
18 | },
19 | medicId: {
20 | type: Sequelize.UUID
21 | },
22 | bacteriologistId: {
23 | type: Sequelize.UUID
24 | },
25 | typeId: {
26 | type: Sequelize.UUID
27 | },
28 | isDeleted: {
29 | type: Sequelize.BOOLEAN,
30 | defaultValue: false
31 | },
32 | createdAt: {
33 | allowNull: false,
34 | type: Sequelize.DATE,
35 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
36 | },
37 | updatedAt: {
38 | allowNull: false,
39 | type: Sequelize.DATE,
40 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
41 | }
42 | })
43 | },
44 | down: (queryInterface, Sequelize) => {
45 | return queryInterface.dropTable('exams')
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Backend/api/components/auth/network.js:
--------------------------------------------------------------------------------
1 | const express = require('express')
2 | const response = require('../../../network/response')
3 | const Controller = require('./index')
4 | const passport = require('passport')
5 | const roleValidationHandler = require('../../../auth/roles')
6 | const { rolesNameEnum } = require('../../../store/mocks/RolesMock')
7 | const router = express.Router()
8 |
9 | // Passport Strategies
10 | require('../../../auth/strategies/basic')
11 | require('../../../auth/strategies/cookie')
12 | require('../../../auth/strategies/jwt')
13 |
14 | router.post('/login', passport.authenticate('basic', { session: false }), login)
15 | router.post(
16 | '/add_user',
17 | passport.authenticate(['jwt', 'cookie'], { session: false }),
18 | roleValidationHandler([rolesNameEnum.ADMINISTRATOR]),
19 | addUser
20 | )
21 |
22 | function login (req, res, next) {
23 | return Controller.login((req && req.user) || null)
24 | .then(data => {
25 | res.cookie('token', data.token, { maxAge: 5400000, httpOnly: true })
26 | response.success(req, res, data, 200)
27 | })
28 | .catch(next)
29 | }
30 |
31 | function addUser (req, res, next) {
32 | return Controller.addUser(req.body)
33 | .then(user => response.success(req, res, user, 201))
34 | .catch(next)
35 | }
36 |
37 | const network = module.exports = router
38 | network.login = login
39 |
--------------------------------------------------------------------------------
/Backend/api/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 | const express = require('express')
3 | const bodyParser = require('body-parser')
4 | const passport = require('passport')
5 | const cookieParser = require('cookie-parser')
6 | const cors = require('cors')
7 |
8 | const config = require('../config/config')
9 | const errors = require('../network/errors')
10 | const welcome = require('./components/welcome/network')
11 | const auth = require('./components/auth/network')
12 | const examTypes = require('./components/exams/network')
13 | const notification = require('./components/notification/network')
14 |
15 | const app = express()
16 |
17 | const whitelist = ['http://localhost:8080', 'https://nextep-lab.herokuapp.com']
18 | app.use(cors({
19 | credentials: true,
20 | origin: function (origin, callback) {
21 | if (whitelist.indexOf(origin) !== -1) {
22 | callback(null, true)
23 | } else {
24 | callback(new Error('Not allowed by CORS'))
25 | }
26 | }
27 | }))
28 | app.use(bodyParser.json())
29 | app.use(cookieParser())
30 | app.use(passport.initialize())
31 |
32 | app.use('/', welcome)
33 | app.use('/api/auth', auth)
34 | app.use('/api/types', examTypes)
35 | app.use('/api/notification', notification)
36 |
37 | app.use(errors)
38 |
39 | app.listen(config.api.port || 5000, () => {
40 | console.log(`🔬API listen on port: http://localhost:${config.api.port}`)
41 | })
42 |
--------------------------------------------------------------------------------
/Backend/config/config.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config()
2 |
3 | const dbConfig = {
4 | username: process.env.DB_USERNAME || null,
5 | password: process.env.DB_PASSWORD || null,
6 | database: process.env.DB_DATABASE || 'DBDemo',
7 | host: process.env.DB_HOST || 'localhost',
8 | port: process.env.DB_PORT || '5432',
9 | dialect: process.env.DB_DIALECT || 'sqlite'
10 | }
11 |
12 | module.exports = {
13 | api: {
14 | port: process.env.PORT || 5000
15 | },
16 | jwt: {
17 | secret: process.env.JWT_SECRET || 'secret'
18 | },
19 | email: {
20 | emailHost: process.env.EMAIL_HOST || '',
21 | emailPort: process.env.EMAIL_PORT || '',
22 | emailUser: process.env.EMAIL_USER || '',
23 | emailPassword: process.env.EMAIL_PASSWORD || ''
24 | },
25 | knex: {
26 | host: process.env.KNEX_HOST || '',
27 | user: process.env.KNEX_USER || '',
28 | password: process.env.KNEX_PASSWORD || '',
29 | database: process.env.KNEX_DATABASE || '',
30 | port: process.env.KNEX_PORT || '',
31 | client: process.env.KNEX_CLIENT || ''
32 | },
33 | fbase: {
34 | name: process.env.FB_APP_NAME || '',
35 | cert: process.env.FB_CERT || {}
36 | },
37 | storeMotor: process.env.STORE_MOTOR || 'dummy',
38 | sequelize: {
39 | development: {
40 | ...dbConfig
41 | },
42 | test: {
43 | ...dbConfig
44 | },
45 | production: { ...dbConfig, logging: false }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200527024840-create-user.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('users', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | idNumber: {
11 | type: Sequelize.INTEGER
12 | },
13 | username: {
14 | type: Sequelize.STRING
15 | },
16 | firstName: {
17 | type: Sequelize.STRING
18 | },
19 | lastName: {
20 | type: Sequelize.STRING
21 | },
22 | email: {
23 | type: Sequelize.STRING
24 | },
25 | contactNumber: {
26 | type: Sequelize.STRING
27 | },
28 | address: {
29 | type: Sequelize.STRING
30 | },
31 | medicalRecordId: {
32 | type: Sequelize.UUID
33 | },
34 | isDeleted: {
35 | type: Sequelize.BOOLEAN,
36 | defaultValue: false
37 | },
38 | createdAt: {
39 | allowNull: false,
40 | type: Sequelize.DATE,
41 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
42 | },
43 | updatedAt: {
44 | allowNull: false,
45 | type: Sequelize.DATE,
46 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
47 | }
48 | })
49 | },
50 | down: (queryInterface, Sequelize) => {
51 | return queryInterface.dropTable('users')
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/containers/Login.scss:
--------------------------------------------------------------------------------
1 | @import '../App.scss';
2 |
3 | .Login {
4 | @include home-themes;
5 | height: 100vh;
6 | .Logo {
7 | margin: 0;
8 | padding-top: 5%;
9 | }
10 | &__container--form {
11 | .button--send {
12 | @include home-button-send;
13 | }
14 | > a {
15 | color: #fff;
16 | }
17 | }
18 | &__container--form--options {
19 | position: relative;
20 | input {
21 | width: 25%;
22 | margin: 1em auto;
23 | line-height: 1.2;
24 | color: #333333;
25 | display: block;
26 | background: #fff;
27 | border-radius: 2em;
28 | padding: 1em;
29 | border: none;
30 | text-align: center;
31 | }
32 | > span {
33 | font-size: 15px;
34 | color: #999999;
35 | display: flex;
36 | align-items: center;
37 | position: absolute;
38 | bottom: 0;
39 | left: 35%;
40 | width: inherit;
41 | height: 100%;
42 | padding-left: 35px;
43 | transition: all 0.4s;
44 | }
45 | }
46 | &__container--info {
47 | padding: 1em 0;
48 | display: flex;
49 | justify-content: space-evenly;
50 | align-items: center;
51 | a {
52 | color: #fff;
53 | }
54 | span {
55 | background: #3083c9;
56 | padding: 1em;
57 | border-radius: 50%;
58 | > i {
59 | font-size: 25px;
60 | vertical-align: middle;
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Backend/db/migrations/20200528043643-create-medicalrecord.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | up: (queryInterface, Sequelize) => {
4 | return queryInterface.createTable('medicalrecords', {
5 | id: {
6 | type: Sequelize.UUID,
7 | primaryKey: true,
8 | unique: true
9 | },
10 | birthdate: {
11 | type: Sequelize.DATE
12 | },
13 | blodType: {
14 | type: Sequelize.STRING
15 | },
16 | allergies: {
17 | type: Sequelize.TEXT
18 | },
19 | weight: {
20 | type: Sequelize.STRING
21 | },
22 | heigth: {
23 | type: Sequelize.STRING
24 | },
25 | medicationNotes: {
26 | type: Sequelize.TEXT
27 | },
28 | diabetes: {
29 | type: Sequelize.BOOLEAN
30 | },
31 | smoking: {
32 | type: Sequelize.BOOLEAN
33 | },
34 | alcoholism: {
35 | type: Sequelize.BOOLEAN
36 | },
37 | isDeleted: {
38 | type: Sequelize.BOOLEAN,
39 | defaultValue: false
40 | },
41 | createdAt: {
42 | allowNull: false,
43 | type: Sequelize.DATE,
44 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
45 | },
46 | updatedAt: {
47 | allowNull: false,
48 | type: Sequelize.DATE,
49 | defaultValue: Sequelize.literal('CURRENT_TIMESTAMP')
50 | }
51 | })
52 | },
53 | down: (queryInterface, Sequelize) => {
54 | return queryInterface.dropTable('medicalrecords')
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Backend/store/mocks/RolesMock.js:
--------------------------------------------------------------------------------
1 | const rolesEnum = {
2 | ADMINISTRATOR: {
3 | id: 'd436e99b-afea-44cf-a31c-ff35b7740c67',
4 | name: 'administrator',
5 | description: 'An administrator Role',
6 | isDeleted: false,
7 | createdAt: '2020-05-15 00:00:00',
8 | updatedAt: '2020-05-15 00:00:00'
9 | },
10 | MEDIC: {
11 | id: 'a81bd60a-b09e-4936-b74a-22b823e39464',
12 | name: 'medic',
13 | description: 'A medic Role',
14 | isDeleted: false,
15 | createdAt: '2020-05-15 00:00:00',
16 | updatedAt: '2020-05-15 00:00:00'
17 | },
18 | BACTERIOLOGIST: {
19 | id: 'bf90b55d-0e69-4f31-8211-514049a42625',
20 | name: 'bacteriologist',
21 | description: 'A bacteriologist Role',
22 | isDeleted: false,
23 | createdAt: '2020-05-15 00:00:00',
24 | updatedAt: '2020-05-15 00:00:00'
25 | },
26 | PACIENT: {
27 | id: '9511dfc8-e17e-4c8e-927e-cb933f9b77f6',
28 | name: 'pacient',
29 | description: 'A pacient Role',
30 | isDeleted: false,
31 | createdAt: '2020-05-15 00:00:00',
32 | updatedAt: '2020-05-15 00:00:00'
33 | }
34 | }
35 |
36 | const rolesNameEnum = {
37 | ADMINISTRATOR: rolesEnum.ADMINISTRATOR.name,
38 | MEDIC: rolesEnum.MEDIC.name,
39 | BACTERIOLOGIST: rolesEnum.BACTERIOLOGIST.name,
40 | PACIENT: rolesEnum.PACIENT.name
41 | }
42 |
43 | const roles = [
44 | rolesEnum.ADMINISTRATOR,
45 | rolesEnum.MEDIC,
46 | rolesEnum.BACTERIOLOGIST,
47 | rolesEnum.PACIENT
48 | ]
49 |
50 | module.exports = {
51 | rolesEnum,
52 | rolesNameEnum,
53 | roles
54 | }
55 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/Modal.scss:
--------------------------------------------------------------------------------
1 | .Modal {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | bottom: 0;
6 | right: 0;
7 | background-color: rgba(0, 0, 0, 0.5);
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .Modal__container {
14 | position: relative;
15 | top: 10rem;
16 | background-color: #ffffff;
17 | padding: 1rem;
18 | width: 50%;
19 | border-radius: 0.5em;
20 | text-align: center;
21 | h3 {
22 | text-align: center;
23 | }
24 | }
25 |
26 | .Modal__close-button {
27 | position: absolute;
28 | top: -25px;
29 | right: -20px;
30 | border: 0;
31 | background-color: #fff;
32 | padding: 10px;
33 | border-radius: 50%;
34 | width: 40px;
35 | font-weight: 700;
36 | color: #cac4c4;
37 | font-size: 18px;
38 | &:hover {
39 | cursor: pointer;
40 | color: #000;
41 | }
42 | }
43 |
44 | .Modal__content {
45 | display: grid;
46 | grid-template-columns: 50% 50%;
47 | grid-gap: 10px;
48 | align-items: center;
49 | justify-content: center;
50 | input {
51 | border-radius: 0.5em;
52 | padding: 10px;
53 | border: 1px solid #cac4c4;
54 | }
55 | }
56 |
57 | .Modal__button--save {
58 | width: 20%;
59 | padding: 10px;
60 | margin: auto;
61 | margin-top: 1em;
62 | background: #19243B;
63 | color: #ffffff;
64 | text-align: center;
65 | font-size: 18px;
66 | border: none;
67 | border-radius: 5px;
68 | transition: all 0.3s;
69 | grid-column: 1/3;
70 | &:hover {
71 | transform: scale(0.9);
72 | cursor: pointer;
73 | }
74 | }
--------------------------------------------------------------------------------
/Frontend/src/containers/Patient.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { connect } from 'react-redux';
4 | import '../assets/styles/containers/Patient.scss';
5 | import Exam from './Exam';
6 | import ExamItem from '../components/ExamItem';
7 |
8 | const Patient = ({ exams }) => {
9 | return (
10 |
11 |
12 |
13 | Mis Resultados de Laboratorio
14 |
15 |
16 | Seleccionar registro
17 |
18 |
19 | Ordenar por
20 |
24 |
25 |
26 |
27 | 1,2,3,4,5
28 |
29 |
30 |
34 |
35 |
36 |
37 | {exams.map((item) => (
38 |
39 | ))}
40 |
41 |
42 |
43 |
44 | );
45 | };
46 |
47 | const mapStateToProps = (state) => {
48 | return {
49 | exams: state.exams,
50 | };
51 | };
52 |
53 | export default connect(mapStateToProps, null)(Patient);
54 |
--------------------------------------------------------------------------------
/Backend/store/dummy.js:
--------------------------------------------------------------------------------
1 | const { nanoid } = require('nanoid')
2 | const db = require('./mocks/mockDB')
3 |
4 | async function list (table) {
5 | return db[table]
6 | }
7 | async function get (table, id) {
8 | const collection = await list(table)
9 | const item = new Promise((resolve, reject) => {
10 | const itemRecord = collection.find(item => item.id === id) || null
11 | if (itemRecord) {
12 | return resolve(itemRecord)
13 | } else {
14 | return reject(new Error(`${table} not found`))
15 | }
16 | })
17 |
18 | return { ...item }
19 | }
20 | async function insert (table, data) {
21 | if (!db[table]) {
22 | db[table] = []
23 | }
24 | const id = data.id || nanoid()
25 |
26 | db[table].push({ ...data, id })
27 | return { ...db[table].find(item => item.id === id) }
28 | }
29 | async function update (table, id, data) {
30 | if (!db[table]) {
31 | db[table] = []
32 | }
33 |
34 | const itemIndex = db[table].findIndex(item => item.id === id)
35 |
36 | if (itemIndex >= 0) {
37 | db[table][itemIndex] = { ...db[table][itemIndex], ...data }
38 | }
39 |
40 | return { ...db[table][itemIndex] }
41 | }
42 |
43 | async function remove (table, id) {
44 | return true
45 | }
46 |
47 | async function query (table, q) {
48 | const collection = await list(table)
49 | const keys = Object.keys(q)
50 | const key = keys[0]
51 | const results = collection.filter(item => item[key] === q[key]) || null
52 | return results && results.length > 0 ? [...results] : []
53 | }
54 |
55 | module.exports = {
56 | list,
57 | get,
58 | insert,
59 | update,
60 | remove,
61 | query
62 | }
63 |
--------------------------------------------------------------------------------
/Backend/api/components/notification/controller.js:
--------------------------------------------------------------------------------
1 | const config = require('../../../config/config')
2 | const admin = require('firebase-admin')
3 | const boom = require('@hapi/boom')
4 |
5 | const FB_APP_NAME = config.fbase.name
6 | const FB_CERT = config.fbase.cert
7 |
8 | const TABLE = 'firebaseNotificationTokens'
9 |
10 | const appFirebase = admin.initializeApp({
11 | credential: admin.credential.cert(JSON.parse(FB_CERT)),
12 | databaseURL: FB_APP_NAME
13 | })
14 |
15 | module.exports = (store) => {
16 | function sendMessageMulticast (information) {
17 | return new Promise((resolve, reject) => {
18 | const { token, message, title } = information
19 | const notification = {
20 | token: token,
21 | data: {
22 | title: title,
23 | body: message
24 | }
25 | }
26 | appFirebase
27 | .messaging()
28 | .send(notification)
29 | .then((e) => resolve(e))
30 | .catch((err) => reject(err))
31 | })
32 | }
33 | function saveTokenToUser (profile, { token = null }) {
34 | return new Promise((resolve, reject) => {
35 | if (profile === null || token === null) { reject(boom.internal) }
36 | store.insert(TABLE, { userId: profile.id, token: token })
37 | .then(response => {
38 | if (response.id !== '') {
39 | resolve(true)
40 | } else {
41 | reject(new Error(false))
42 | }
43 | }).catch(e => {
44 | console.log(e, 'ERROR')
45 | reject(new Error(false))
46 | })
47 | })
48 | }
49 |
50 | return {
51 | sendMessageMulticast,
52 | saveTokenToUser
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/containers/Patient.scss:
--------------------------------------------------------------------------------
1 | $fuente-principal: 'Quicksand', sans-serif;
2 | $fuente-secundaria: Helvetica;
3 | $color-font:#FFB015;
4 |
5 | .mainContainer {
6 | display: grid;
7 | grid-template-columns: repeat(4, 1fr);
8 | grid-template-rows: 10% 15% 1fr;
9 | }
10 |
11 | .tittle__tipeuser {
12 | display: grid;
13 | // justify-self: center;
14 | grid-column: 2/4;
15 | margin: -60px auto auto auto;
16 | width: 300px;
17 | height: 75px;
18 | h2 {
19 | width: 350px;
20 | color: $color-font;
21 | font-family: $fuente-principal;
22 | font-weight: 600;
23 | }
24 | }
25 |
26 | .selectTypeUser {
27 | grid-column: 1;
28 | }
29 |
30 | .selectrOderList {
31 | grid-column: 2;
32 | justify-content: center;
33 | text-align: center;
34 | display: block;
35 | }
36 |
37 | .selectrOderList select {
38 | margin-left: 15px;
39 | }
40 |
41 | .searchIcon {
42 | color: #999999;
43 | }
44 |
45 | .check-item {
46 | grid-column: 1;
47 | grid-row: 2;
48 | margin: 15px
49 | }
50 |
51 | .pagination {
52 | grid-column: 3;
53 | margin: auto;
54 | }
55 |
56 | #button-download {
57 | width: 280px;
58 | height: 35px;
59 | border-radius: 15px;
60 | grid-column: 4;
61 | margin: auto;
62 | font-family: Helvetica;
63 | font-weight: bold;
64 | }
65 |
66 | .fas.fa-file-pdf {
67 | color: #c41212;
68 | font-size: medium;
69 | margin-right: 10px;
70 | }
71 |
72 | .exam-container {
73 | display: grid;
74 | grid-column: span 3;
75 | }
76 |
77 | .exam-container Exam {
78 | text-align: center;
79 | justify-content: center;
80 | }
81 |
82 | @media screen and(max-width: 736px) {
83 | Exam {
84 | visibility: collapse
85 | }
86 | }
--------------------------------------------------------------------------------
/Frontend/src/components/UserItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PropTypes from 'prop-types';
4 | import deleteIcon from '../assets/static/delete-user.png';
5 | import editIcon from '../assets/static/edit-user.png';
6 | import { editUser, deleteUser } from '../actions/index';
7 |
8 | const UserItem = (props) => {
9 |
10 | const {
11 | id,
12 | idNumber,
13 | firstName,
14 | lastName,
15 | username,
16 | email,
17 | contactNumber,
18 | address
19 | } = props;
20 |
21 | const handleDeleteUser = (itemId) => {
22 | props.deleteUser(itemId);
23 | };
24 |
25 | return (
26 |
27 | | {idNumber} |
28 |
29 | {firstName}
30 | {lastName}
31 | |
32 | {username} |
33 | {email} |
34 | {contactNumber} |
35 | {address} |
36 |
37 |
42 | |
43 |
44 | handleDeleteUser(id)}
49 | />
50 | |
51 |
52 | );
53 | };
54 |
55 | UserItem.propTypes = {
56 | id: PropTypes.string,
57 | idNumber: PropTypes.number,
58 | firstName: PropTypes.string,
59 | lastName: PropTypes.string,
60 | username: PropTypes.string,
61 | email: PropTypes.string,
62 | contactNumber: PropTypes.string,
63 | address: PropTypes.string,
64 | };
65 |
66 | const mapDispatchToProps = {
67 | editUser,
68 | deleteUser,
69 | };
70 |
71 | export default connect(null, mapDispatchToProps)(UserItem);
72 |
--------------------------------------------------------------------------------
/Backend/store/mocks/exams/BoodExam.js:
--------------------------------------------------------------------------------
1 | const templateBloodExam = [
2 | {
3 | name: 'Glucosa',
4 | max: 106,
5 | min: 17,
6 | value: null,
7 | unit: 'mg/dL'
8 | },
9 | {
10 | name: 'Urea',
11 | max: 49,
12 | min: 17,
13 | value: null,
14 | unit: 'mg/dL'
15 | },
16 | {
17 | name: 'Ácido úrico',
18 | max: 8,
19 | min: 4.2,
20 | value: null,
21 | unit: 'mg/dl'
22 | },
23 | {
24 | name: 'Creatinina',
25 | max: 1.3,
26 | min: 0.7,
27 | value: null,
28 | unit: 'mg/dl'
29 | },
30 | {
31 | name: 'Colesterol',
32 | max: 130,
33 | min: 0,
34 | value: null,
35 | unit: 'mg/dL'
36 | },
37 | {
38 | name: 'Triglicéridos',
39 | max: 150,
40 | min: 0,
41 | value: null,
42 | unit: 'mg/dl'
43 | },
44 | {
45 | name: 'Transaminasas',
46 | max: 55,
47 | min: 0,
48 | value: null,
49 | unit: 'unidades/litro'
50 | },
51 | {
52 | name: 'Hierro',
53 | max: 170,
54 | min: 65,
55 | value: null,
56 | unit: 'mg/dL'
57 | },
58 | {
59 | name: 'Calcio',
60 | max: 10.2,
61 | min: 8.6,
62 | value: null,
63 | unit: 'mg/dL'
64 | },
65 | {
66 | name: 'Potasio',
67 | max: 3.5,
68 | min: 5.1,
69 | value: null,
70 | unit: 'mEq/litro'
71 | },
72 | {
73 | name: 'Sodio',
74 | max: 146,
75 | min: 136,
76 | value: null,
77 | unit: 'mEq/litro'
78 | },
79 | {
80 | name: 'Bilirrubina',
81 | max: 1.2,
82 | min: 0.8,
83 | value: null,
84 | unit: 'mg/dL'
85 | }
86 | ]
87 | const BloodExam = {
88 | types: [
89 | {
90 | id: 121,
91 | name: 'Examen de general saguineo',
92 | desc: 'Examen para el analisis general de la sangre',
93 | template_cont: templateBloodExam
94 | }
95 | ]
96 | }
97 |
98 | module.exports = BloodExam
99 |
--------------------------------------------------------------------------------
/Frontend/src/containers/Doctor.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import PatientList from '../components/PatientList';
4 | import '../assets/styles/containers/PatientList.scss';
5 |
6 | const Doctor = ({ users }) => {
7 | // state = {
8 | // search: '',
9 | // };
10 |
11 | // onChange = (e) => {
12 | // this.setState({ search: e.target.value });
13 | // };
14 | // const { search } = this.state;
15 |
16 | // const filteredUsers = usersMock.filter((item) => {
17 | // return (
18 | // `${item.id_number} ${item.first_name} ${item.last_name}`
19 | // .toLowerCase()
20 | // .indexOf(search.toLowerCase()) !== -1
21 | // );
22 | // });
23 |
24 | return (
25 | <>
26 |
27 |
28 |
Listado de Pacientes
29 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
No. Identificación
44 | Nombres y Apellidos
45 | Ver registro de paciente
46 |
47 |
48 | {users.map((item) => {
49 | return ;
50 | })}
51 |
52 |
53 |
54 | >
55 | );
56 | };
57 |
58 | const mapStateToProps = (state) => {
59 | return {
60 | users: state.users,
61 | };
62 | };
63 |
64 | export default connect(mapStateToProps, null)(Doctor);
65 |
--------------------------------------------------------------------------------
/Frontend/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path")
2 | const HtmlWebpackPlugin = require("html-webpack-plugin")
3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin")
4 | const ServiceWorkerWebpackPlugin = require("serviceworker-webpack-plugin")
5 | const Dotenv = require("dotenv-webpack")
6 |
7 | module.exports = {
8 | entry: "./src/index.js",
9 | output: {
10 | path: path.resolve(__dirname, "dist"),
11 | filename: "bundle.js",
12 | },
13 | resolve: {
14 | extensions: [".js", ".jsx"],
15 | },
16 | module: {
17 | rules: [
18 | {
19 | test: /\.(js|jsx)$/,
20 | exclude: /node_modules/,
21 | use: {
22 | loader: "babel-loader",
23 | },
24 | },
25 | {
26 | test: /\.html$/,
27 | use: [
28 | {
29 | loader: "html-loader",
30 | },
31 | ],
32 | },
33 | {
34 | test: /\.(s*)css$/,
35 | use: [
36 | {
37 | loader: MiniCssExtractPlugin.loader,
38 | },
39 | "css-loader",
40 | "sass-loader",
41 | ],
42 | },
43 | {
44 | test: /\.(png|gif|jpg|jpeg)$/,
45 | use: [
46 | {
47 | loader: "file-loader",
48 | options: {
49 | name: "assets/[hash].[ext]",
50 | },
51 | },
52 | ],
53 | },
54 | ],
55 | },
56 | devServer: {
57 | historyApiFallback: true,
58 | open: "Google Chrome"
59 | },
60 | plugins: [
61 | new Dotenv(),
62 | new HtmlWebpackPlugin({
63 | template: "./public/index.html",
64 | filename: "./index.html",
65 | }),
66 | new MiniCssExtractPlugin({
67 | filename: "assets/[name].css"
68 | }),
69 | new ServiceWorkerWebpackPlugin({
70 | entry: path.join(__dirname, "./src/sw/firebase-messaging-sw.js"),
71 | filename: "firebase-messaging-sw.js"
72 | })
73 | ],
74 | }
75 |
--------------------------------------------------------------------------------
/Frontend/src/containers/AddExamsResults.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { examTypes } from '../mocks/ExamMock'
3 | import AddResultsModal from '../components/AddResultsModal'
4 | import '../assets/styles/containers/AddExams.scss';
5 |
6 | class AddExamsResults extends React.Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | modalIsOpen: false,
11 | };
12 | }
13 |
14 | //State Modal
15 | handleOpenModal = () => {
16 | this.setState({ modalIsOpen: true });
17 | };
18 |
19 | handleCloseModal = () => {
20 | this.setState({ modalIsOpen: false });
21 | };
22 |
23 | handleSubmit = (e) => {
24 | e.preventDefault();
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
31 | Agregar resultados a exámenes
32 |
33 |
Exámen
34 | Descripción
35 | Añadir resultado
36 |
37 |
38 | {examTypes.map(({ id, name, description }) => {
39 | return (
40 | -
41 |
{name}
42 | {description}
43 |
50 |
51 | );
52 | })}
53 |
54 |
60 |
61 |
62 | );
63 | }
64 | }
65 |
66 | export default AddExamsResults;
67 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | cache:
4 | directories:
5 | - node_modules
6 | - ~/.npm
7 |
8 | node_js:
9 | - '12'
10 |
11 | git:
12 | depth: 3
13 |
14 | branches:
15 | only:
16 | - master
17 | - develop
18 |
19 | jobs:
20 | include:
21 | - stage: backend deploy
22 | before_script: cd Backend
23 | script: yarn install && yarn test
24 | deploy:
25 | provider: heroku
26 | skip_cleanup: true
27 | strategy: git
28 | api_key:
29 | secure: "w/l6OsYnLxjoMSX+VSOuIQz7TIdblvPfAI1s+Ags/wLgsRZJiv2v5TNg/2DdrItnQzv45FfstOKA8iiz4aMpNmdTyXDmHABuY+CA8PeA9YEuhFMDDMXTkZVoj1TtdnhSScPGQUBlvUawUZjp/T1kEHH0kO8QHJD5J1UdkdjNXMh+cojm5kFQqBl/ZthHjobPDUk9mVkRE6lfgSfe8GoqkT05e1c5YIlipk7rffVswS7GGcfr3OB5tNiHaD4r0qto68JqhtPEosoRjahlhfdt41byFgDuuDE+KIqM1Kro3sLU6jLjyoRhgGpmqeVSM0PaP5A09z6umuHNyYmQnUmClYCsEdsztH+hSQJjd6Vhzq7XtzXEfpelatcho2Enk/r6s3xQuUAVnzpm8iqksypNLQftV0zRuXvLMXom4q5+Ua05zw45SiEmkI5ZoajpNuAyjGzjRwEhIZ0LHFQM3TFd8s1qEZq7psZBHGFav45I2sgH1CSVF+/px6eSe2d2h8IZHH8k3Bmf1hoWc+EQ2i6c/O2jTKjAGjoR4NUW2TkObWcm6itpltgvde9FWYFP6p2wlD40zE5/LpIwUM7qPhLRoSdEVww4xvE8kGu8nM5ZeUb2ofYl0Swj/KzMZSi8DbkGo6NhR+TwjB4HXYbZjocN9PkTYwcPhyiDTzptzyd0ivE="
30 | app: megacat-backend
31 | on: master
32 | run:
33 | - "npm run migration:run"
34 | - stage : frontend deploy
35 | before_script: cd Frontend
36 | script: yarn install
37 | deploy:
38 | provider: heroku
39 | skip_cleanup: true
40 | strategy: git
41 | api_key:
42 | secure: "w/l6OsYnLxjoMSX+VSOuIQz7TIdblvPfAI1s+Ags/wLgsRZJiv2v5TNg/2DdrItnQzv45FfstOKA8iiz4aMpNmdTyXDmHABuY+CA8PeA9YEuhFMDDMXTkZVoj1TtdnhSScPGQUBlvUawUZjp/T1kEHH0kO8QHJD5J1UdkdjNXMh+cojm5kFQqBl/ZthHjobPDUk9mVkRE6lfgSfe8GoqkT05e1c5YIlipk7rffVswS7GGcfr3OB5tNiHaD4r0qto68JqhtPEosoRjahlhfdt41byFgDuuDE+KIqM1Kro3sLU6jLjyoRhgGpmqeVSM0PaP5A09z6umuHNyYmQnUmClYCsEdsztH+hSQJjd6Vhzq7XtzXEfpelatcho2Enk/r6s3xQuUAVnzpm8iqksypNLQftV0zRuXvLMXom4q5+Ua05zw45SiEmkI5ZoajpNuAyjGzjRwEhIZ0LHFQM3TFd8s1qEZq7psZBHGFav45I2sgH1CSVF+/px6eSe2d2h8IZHH8k3Bmf1hoWc+EQ2i6c/O2jTKjAGjoR4NUW2TkObWcm6itpltgvde9FWYFP6p2wlD40zE5/LpIwUM7qPhLRoSdEVww4xvE8kGu8nM5ZeUb2ofYl0Swj/KzMZSi8DbkGo6NhR+TwjB4HXYbZjocN9PkTYwcPhyiDTzptzyd0ivE="
43 | app: nextep-lab
44 | on: master
45 |
--------------------------------------------------------------------------------
/Frontend/src/containers/Bacteriologist.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import usersMock from '../mocks/UsersMock.json';
4 | import '../assets/styles/containers/PatientList.scss';
5 |
6 | class Bacteriologist extends React.Component {
7 | state = {
8 | search: '',
9 | };
10 |
11 | onChange = (e) => {
12 | this.setState({ search: e.target.value });
13 | };
14 |
15 | render() {
16 | const { search } = this.state;
17 |
18 | const filteredUsersMock = usersMock.filter(item => {
19 | return (
20 | `${item.id_number} ${item.first_name} ${item.last_name}`
21 | .toLowerCase()
22 | .indexOf(search.toLowerCase()) !== -1
23 | );
24 | });
25 |
26 | return (
27 | <>
28 |
67 | >
68 | );
69 | }
70 | }
71 |
72 | export default Bacteriologist;
73 |
--------------------------------------------------------------------------------
/Backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "laboratory_backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "standard:fix": "standard --fix",
8 | "standard:validation": "standard",
9 | "start:api": "nodemon api/index.js",
10 | "test": "jest",
11 | "test:watch": "jest --watch ",
12 | "coverage": "jest --coverage",
13 | "start": "node api/index.js",
14 | "migration:run": "sequelize db:migrate",
15 | "migration:undo": "sequelize db:migrate:undo",
16 | "migration:undo:all": "sequelize db:migrate:undo:all"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/yoshuadiaz/megaCat.git"
21 | },
22 | "keywords": [],
23 | "author": "MegaCat Team",
24 | "license": "ISC",
25 | "bugs": {
26 | "url": "https://github.com/yoshuadiaz/megaCat/issues"
27 | },
28 | "homepage": "https://github.com/yoshuadiaz/megaCat#readme",
29 | "devDependencies": {
30 | "eslint": "^7.0.0",
31 | "eslint-config-standard": "^14.1.1",
32 | "eslint-plugin-import": "^2.20.2",
33 | "eslint-plugin-node": "^11.1.0",
34 | "eslint-plugin-promise": "^4.2.1",
35 | "eslint-plugin-standard": "^4.0.1",
36 | "husky": "^4.2.5",
37 | "jest": "^26.0.1",
38 | "nodemon": "^2.0.4",
39 | "standard": "^14.3.4"
40 | },
41 | "dependencies": {
42 | "@hapi/boom": "^9.1.0",
43 | "bcrypt": "^4.0.1",
44 | "body-parser": "^1.19.0",
45 | "cookie-parser": "^1.4.5",
46 | "cors": "^2.8.5",
47 | "crypto-random-string": "^3.2.0",
48 | "dotenv": "^8.2.0",
49 | "express": "^4.17.1",
50 | "firebase-admin": "^8.12.1",
51 | "jsonwebtoken": "^8.5.1",
52 | "mysql2": "^2.1.0",
53 | "nanoid": "^3.1.9",
54 | "nodemailer": "^6.4.8",
55 | "passport": "^0.4.1",
56 | "passport-cookie": "^1.0.6",
57 | "passport-http": "^0.3.0",
58 | "passport-jwt": "^4.0.0",
59 | "pluralize": "^8.0.0",
60 | "proxyquire": "^2.1.3",
61 | "sequelize": "^5.21.11",
62 | "sequelize-cli": "^5.5.1",
63 | "supertest": "^4.0.2"
64 | },
65 | "engines": {
66 | "node": "^12.16.2"
67 | },
68 | "standard": {
69 | "env": "jest"
70 | },
71 | "husky": {
72 | "hooks": {
73 | "pre-commit": "npm test && npm run standard:validation",
74 | "pre-push": "npm run standard:validation"
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "megacat",
3 | "version": "1.0.0",
4 | "description": "Sistema Gestor de Exámenes para Laboratorios Clínicos",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack --mode production",
8 | "start": "webpack-dev-server --open --mode development",
9 | "format": "prettier --write '{*.js,src/**/*.{js,jsx}}'",
10 | "lint": "eslint src --fix",
11 | "test": "jest"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/FernandoTorresL/megaCat.git"
16 | },
17 | "author": "Fernando Torres ",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/FernandoTorresL/megaCat/issues"
21 | },
22 | "homepage": "https://github.com/FernandoTorresL/megaCat#readme",
23 | "dependencies": {
24 | "axios": "^0.19.2",
25 | "babel-eslint": "^10.1.0",
26 | "eslint-config-airbnb": "^18.1.0",
27 | "eslint-plugin-import": "^2.20.2",
28 | "eslint-plugin-jsx-a11y": "^6.2.3",
29 | "eslint-plugin-react": "^7.20.0",
30 | "firebase": "^7.14.6",
31 | "md5": "^2.2.1",
32 | "prop-types": "^15.7.2",
33 | "react": "^16.13.1",
34 | "react-dom": "^16.13.1",
35 | "react-redux": "^7.2.0",
36 | "react-router-dom": "^5.2.0",
37 | "redux": "^4.0.5",
38 | "serviceworker-webpack-plugin": "^1.0.1"
39 | },
40 | "devDependencies": {
41 | "@babel/core": "^7.10.1",
42 | "@babel/preset-env": "^7.10.1",
43 | "@babel/preset-react": "^7.10.1",
44 | "babel-loader": "^8.1.0",
45 | "css-loader": "^3.5.3",
46 | "dotenv-webpack": "^1.8.0",
47 | "eslint": "^7.1.0",
48 | "eslint-config-prettier": "^6.11.0",
49 | "eslint-loader": "^4.0.2",
50 | "eslint-plugin-prettier": "^3.1.3",
51 | "file-loader": "^6.0.0",
52 | "html-loader": "^1.1.0",
53 | "html-webpack-plugin": "^4.3.0",
54 | "husky": "^4.2.5",
55 | "lint-staged": "^10.2.6",
56 | "mini-css-extract-plugin": "^0.9.0",
57 | "node-sass": "^4.14.1",
58 | "prettier": "^2.0.5",
59 | "sass-loader": "^8.0.2",
60 | "webpack": "^4.43.0",
61 | "webpack-cli": "^3.3.11",
62 | "webpack-dev-server": "^3.11.0"
63 | },
64 | "husky": {
65 | "hooks": {
66 | "pre-commit": "lint-staged"
67 | }
68 | },
69 | "lint-staged": {
70 | "*.{js,jsx}": [
71 | "npm run lint"
72 | ]
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/components/Header.scss:
--------------------------------------------------------------------------------
1 | $fuente-principal: "Quicksand", sans-serif;
2 | $background-color: linear-gradient(to left bottom, #1b2470 50%, #293585 50%);
3 | $color-font: #ffb015;
4 | $color-box: #ff5c00;
5 |
6 | body {
7 | margin: 0;
8 | padding: 0;
9 | box-sizing: border-box;
10 | }
11 |
12 | #header-container {
13 | margin-block-start: 0em;
14 | display: grid;
15 | grid-template-columns: 350px 700px 1fr;
16 | grid-template-rows: 100px 90px;
17 | }
18 |
19 | #header-container {
20 | width: 100vw;
21 | height: 130px;
22 | padding-top: 10px;
23 | background: $background-color;
24 | }
25 |
26 | .imagotipo {
27 | margin: 0;
28 | width: 260px;
29 | height: 75px;
30 | &__iconotipo {
31 | width: 100px;
32 | margin-left: 20px;
33 | }
34 | &__logotipo {
35 | width: 140px;
36 | margin-bottom: 5px;
37 | }
38 | }
39 |
40 | .user-container {
41 | display: grid;
42 | justify-self: flex-end;
43 | align-items: flex-end;
44 | margin-right: 40px;
45 | }
46 |
47 | .userIcon {
48 | display: grid;
49 | align-items: end;
50 | padding-right: 50px;
51 | width: 40px;
52 | }
53 |
54 | .tittle-indication p {
55 | font-family: "Quicksand", sans-serif;
56 | font-weight: 600;
57 | font-size: medium;
58 | color: $color-font;
59 | }
60 |
61 | .tittle-indication {
62 | margin-left: 25px;
63 | margin-top: -30px;
64 | grid-column: 1;
65 | grid-row: 2;
66 | justify-content: center;
67 | }
68 |
69 | .namebar {
70 | background: $color-box;
71 | display: grid;
72 | grid-column: 3;
73 | width: 350px;
74 | height: 30px;
75 | margin-top: 25px;
76 | border: 4px solid red;
77 | box-sizing: border-box;
78 | border-radius: 30px;
79 | p {
80 | display: grid;
81 | grid-column: 2;
82 | grid-row: 1;
83 | width: 350px;
84 | height: 30px;
85 | margin: auto;
86 | box-sizing: border-box;
87 | border-radius: 30px;
88 | p {
89 | display: grid;
90 | justify-items: center;
91 | align-items: center;
92 | margin: 0;
93 | color: white;
94 | font-weight: 600;
95 | }
96 | .header__menu {
97 | grid-column: end;
98 | }
99 | .header__menu p {
100 | margin: 0;
101 | }
102 | .header__menu li a {
103 | color: white;
104 | text-decoration: none;
105 | }
106 | .header__menu li a:hover {
107 | text-decoration: underline;
108 | // display: block;
109 | }
110 | .header__menu--profile {
111 | align-items: center;
112 | display: grid;
113 | cursor: pointer;
114 | grid-column: 3;
115 | }
116 | }
117 | }
--------------------------------------------------------------------------------
/Frontend/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | import iconotipo from '../assets/static/iconotipo.png'
5 | import logotipo from '../assets/static/logotype.png'
6 | import userIcon from '../assets/static/circle-regular.png'
7 | import '../assets/styles/components/Header.scss'
8 | import gravatar from '../utils/gravatar';
9 | import { logoutRequest } from '../actions';
10 |
11 | const Header = (props) => {
12 |
13 | const { user, setRoleType } = props;
14 | const hasUser = Object.keys(user).length > 0;
15 |
16 | const handleLogout = () => {
17 | props.logoutRequest({});
18 | };
19 |
20 | return (
21 |
92 | );
93 | };
94 |
95 | const mapStateToProps = (state) => {
96 | return {
97 | user: state.user,
98 | };
99 | };
100 |
101 | const mapDispatchToProps = {
102 | logoutRequest,
103 | };
104 |
105 | export default connect(mapStateToProps, mapDispatchToProps)(Header);
106 |
--------------------------------------------------------------------------------
/Frontend/src/assets/styles/containers/Administrator.scss:
--------------------------------------------------------------------------------
1 | $fuente-principal: 'Quicksand', sans-serif;
2 | $fuente-secundaria: Helvetica;
3 |
4 | .Container-Admin {
5 | display: grid;
6 | grid-template-columns: 25% 25% 25% 25%;
7 | grid-template-rows: 10% 10% 10% 1fr;
8 | background-color: #EFF1FF;
9 | }
10 |
11 | .searchBox{
12 | display: flex;
13 | grid-column: 1 / 4;
14 | justify-content: center;
15 | align-items: center;
16 | }
17 |
18 | .searchbar{
19 | background-color: #FFFFFF;
20 | border-radius: 25px;
21 | padding-left: 20px;
22 | width: 290px;
23 | border: 1px solid #A1A1A1 ;
24 | }
25 |
26 | .searchBusqueda {
27 | text-align: center;
28 | width: 80%;
29 | padding-left: 5%;
30 | border: none;
31 | }
32 |
33 | .searchBusqueda {
34 | font-size: 15px;
35 | line-height: 1.2;
36 | height: 35px;
37 | }
38 |
39 | .selectTypeUser {
40 | grid-column: 1;
41 | justify-content: center;
42 | text-align: center;
43 | display: block;
44 | }
45 |
46 | .selectrOderList p {
47 | grid-column: 2;
48 | text-align: start;
49 | display: block;
50 | }
51 |
52 | .selectrOderList select {
53 | margin-left: 15px;
54 | }
55 |
56 | .searchIcon {
57 | color: #999999;
58 | }
59 |
60 | .check-card p {
61 | grid-column: 1;
62 | text-align: start;
63 | margin-left: 15px;
64 | }
65 |
66 | #checkbox1 {
67 | margin-left: 25px;
68 | }
69 |
70 | .cvsIcon__container{
71 | display: grid;
72 | grid-column: 4;
73 | grid-row: 2;
74 | margin: auto;
75 | }
76 |
77 | .addUser {
78 | display: grid;
79 | grid-column: 3;
80 | display: inline;
81 | text-align: start;
82 | }
83 |
84 | .addUser p {
85 | color: #00c2ff;
86 | font-size: 15px;
87 | font-weight: bold;
88 | text-align: start;
89 | display: inline-block;
90 | }
91 |
92 | .fas.fa-plus-circle {
93 | font-size: 40px;
94 | color: #00c2ff;
95 | margin-left: 10px;
96 | }
97 |
98 | .pagination p {
99 | text-align: start;
100 | }
101 |
102 | .tittle-SideBar {
103 | margin-top: 15px;
104 | text-align: center;
105 | font-family: $fuente-secundaria;
106 | font-weight: bold;
107 | }
108 |
109 | .csvIcon {
110 | width: 70px;
111 | margin: 0;
112 | }
113 |
114 | #button-csv {
115 | width: 155px;
116 | height: 35px;
117 | border-radius: 15px;
118 | grid-column: 4;
119 | margin: 0 auto auto auto;
120 | }
121 |
122 | .user__section {
123 | grid-row: 4;
124 | grid-column: span 4;
125 | }
126 |
127 | @media screen and(max-width: 736px) {
128 | .user__section {
129 | grid-column: span 2;
130 | }
131 | #button-csv {
132 | visibility: hidden;
133 | }
134 | .pagination {
135 | display: block;
136 | }
137 | }
138 |
139 | @media screen and(max-width: 480px) {
140 | .user__section {
141 | visibility: collapse;
142 | }}
--------------------------------------------------------------------------------
/Frontend/src/routes/App.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter, Switch, Route } from "react-router-dom";
3 | import { connect } from "react-redux";
4 | import Layout from "../components/Layout";
5 | import Login from "../containers/HomeLogin";
6 | import RememberInfo from "../components/RememberInfo";
7 | import Bacteriologist from "../containers/Bacteriologist";
8 | import AddExamsResults from "../containers/AddExamsResults";
9 | import Doctor from "../containers/Doctor";
10 | import PatientInfo from "../containers/PatientInfo";
11 | import PersonalInfo from "../components/PersonalInfo";
12 | import MedicalHistory from "../components/MedicalHistory";
13 | import SheduleExams from "../components/SheduleExams";
14 | import Administrator from "../containers/Administrator";
15 | import Patient from "../containers/Patient";
16 | import NotFound from "../containers/NotFound";
17 | import "../assets/styles/App.scss";
18 | import AddUser from "../containers/AddUser";
19 |
20 | const App = ({ user }) => {
21 | return (
22 |
23 |
24 |
25 |
26 |
27 | {user &&
28 | user.roles &&
29 | user.roles.find((role) => role.name === "bacteriologist") && (
30 | <>
31 |
32 |
33 | >
34 | )}
35 | {user &&
36 | user.roles &&
37 | user.roles.find((role) => role.name === "doctor") && (
38 | <>
39 |
40 |
44 |
45 |
46 |
47 | >
48 | )}
49 | {user &&
50 | user.roles &&
51 | user.roles.find((role) => role.name === "administrator") && (
52 | <>
53 |
54 |
55 | >
56 | )}
57 | {user &&
58 | user.roles &&
59 | user.roles.find((role) => role.name === "patient") && (
60 | <>
61 |
62 | >
63 | )}
64 |
65 |
66 |
67 |
68 | );
69 | };
70 |
71 | const mapStateToProps = state => ({
72 | user: state.user
73 | })
74 |
75 | export default connect(mapStateToProps)(App);
76 |
--------------------------------------------------------------------------------
/Backend/api/components/auth/controller.js:
--------------------------------------------------------------------------------
1 | const boom = require('@hapi/boom')
2 | const cryptoRandomString = require('crypto-random-string')
3 | const bcrypt = require('bcrypt')
4 | const auth = require('../../../auth/')
5 | const { generateUsername } = require('../../../utils/userUtils')
6 | const userCtrl = require('../user/index')
7 | const emailCtrl = require('../email/index')
8 | const authRolesCtrl = require('../auth_roles/index')
9 |
10 | const TABLE = 'auths'
11 | const saltRounds = 10
12 |
13 | module.exports = (store) => {
14 | async function get (q) {
15 | if (!q) throw boom.badData()
16 | const user = await store.query(TABLE, q, null, [
17 | {
18 | as: 'roles',
19 | model: store.model('role'),
20 | attributes: ['id', 'name'],
21 | through: {
22 | attributes: []
23 | }
24 | }
25 | ])
26 |
27 | return user[0]
28 | }
29 |
30 | async function addUser (userData) {
31 | try {
32 | if (!userData) throw new Error('no data')
33 |
34 | const { idNumber, firstName, lastName, email, contactNumber, address, roleId } = userData
35 |
36 | let username = generateUsername(idNumber, firstName, lastName)
37 | const password = cryptoRandomString({ length: 16, type: 'base64' })
38 |
39 | const hasUsername = await store.query(TABLE, { username })
40 |
41 | if (hasUsername.length > 0) {
42 | const numberRange = Array.from({ length: 100 }, (_, idx) => idx < 10 ? `0${idx}` : `${idx}`)
43 | const numberRangeFiltered = numberRange.filter(nr => nr !== username.slice(-2))
44 | const index = Math.ceil(Math.random() * numberRangeFiltered.length)
45 | const lastTwoRandomNumbers = numberRangeFiltered[index]
46 | username = `${username.slice(0, -2)}${lastTwoRandomNumbers}`
47 | }
48 |
49 | const authData = {
50 | username,
51 | password: await bcrypt.hash(password, saltRounds)
52 | }
53 |
54 | const user = await userCtrl.insert({
55 | idNumber,
56 | firstName,
57 | lastName,
58 | email,
59 | contactNumber,
60 | address,
61 | username
62 | })
63 |
64 | await authRolesCtrl.insert(user.id, roleId)
65 |
66 | await store.insert(TABLE, { ...authData, id: user.id })
67 |
68 | const emailInfo = {
69 | ...JSON.parse(JSON.stringify(user)),
70 | password: password
71 | }
72 | await emailCtrl.sendNewUser(emailInfo)
73 |
74 | return true
75 | } catch (error) {
76 | throw boom.badRequest()
77 | }
78 | }
79 | async function login (User) {
80 | try {
81 | if (!User) {
82 | throw boom.unauthorized()
83 | }
84 |
85 | const user = {
86 | id: User.id,
87 | idNumber: User.idNumber,
88 | username: User.username,
89 | roles: User.roles
90 | }
91 |
92 | const token = await auth.sign({ ...user })
93 |
94 | const userData = await userCtrl.get(User.id)
95 | return {
96 | token,
97 | user: { ...user, ...userData }
98 | }
99 | } catch (error) {
100 | throw boom.unauthorized()
101 | }
102 | }
103 |
104 | return {
105 | login,
106 | get,
107 | addUser
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Frontend/src/containers/Administrator.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { Link } from 'react-router-dom';
4 | import { connect } from 'react-redux';
5 | import '../assets/styles/containers/Administrator.scss';
6 | import User from './User';
7 | import UserItem from '../components/UserItem';
8 | import csvIcon from '../assets/static/csv-file.png';
9 |
10 | const Administrator = ({ users }) => {
11 | return (
12 |
13 |
14 |
26 |
27 |
28 | Cargar información de grupo
29 |
30 |
31 |
32 |
33 | Seleccionar tipo de usuario
34 |
41 |
42 |
43 |
44 |
45 |
46 | Ordenar por:
47 |
51 |
52 |
53 |
54 |
55 | Paginación
56 |
57 |
58 |
59 | Seleccionar registro
60 |
61 |
62 |
63 |
64 | Usted esta viendo la lista de
65 |
66 |
67 |
68 |
69 |
Agregar nuevo usuario
70 |
71 |
72 |
73 |
74 |
75 |

76 |
77 |
78 |
81 |
82 |
83 |
84 | {users.map((item) => (
85 |
86 | ))}
87 |
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | const mapStateToProps = (state) => {
95 | return {
96 | users: state.users,
97 | };
98 | };
99 |
100 | export default connect(mapStateToProps, null)(Administrator);
101 |
--------------------------------------------------------------------------------
/Backend/store/sequelize.js:
--------------------------------------------------------------------------------
1 | const pluralize = require('pluralize')
2 | const store = require('../db/models')
3 |
4 | const DEFAULTS = {
5 | ITEMS_PER_PAGE: 5,
6 | CURRENT_PAGE: 1
7 | }
8 | const toJSON = (data) => JSON
9 | .parse(JSON.stringify(data))
10 |
11 | const pagination = (count, rows, itemsPerPage, currentPage) => ({
12 | total: count,
13 | itemsPerPage,
14 | lastPage: Math.ceil(count / itemsPerPage),
15 | page: currentPage,
16 | rows: [...rows]
17 | })
18 | async function list (table, paginationConfig) {
19 | const model = pluralize.singular(table)
20 | const defaultWhere = { isDeleted: false }
21 | const config = { where: { ...defaultWhere } }
22 |
23 | if (paginationConfig) {
24 | const itemsPerPage = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE
25 | const currentPage = paginationConfig.currentPage || DEFAULTS.CURRENT_PAGE
26 | config.limit = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE
27 | config.offset = currentPage <= 1 ? 0 : (currentPage - 1) * config.limit
28 |
29 | const results = await store[model].findAndCountAll({
30 | ...config,
31 | distinct: true,
32 | raw: true
33 | }).then(({ count, rows }) => pagination(count, rows, itemsPerPage, currentPage))
34 |
35 | return toJSON(results)
36 | }
37 |
38 | return store[model].findAll({
39 | ...config,
40 | where: { isDeleted: false }
41 | })
42 | }
43 | async function get (table, id) {
44 | const model = pluralize.singular(table)
45 | return store[model].findByPk(id, { raw: true })
46 | }
47 | async function insert (table, data) {
48 | const model = pluralize.singular(table)
49 | return store[model].create(data, { raw: true })
50 | }
51 | async function update (table, id, data) {
52 | const model = pluralize.singular(table)
53 | return store[model].update({ ...data }, { where: { id }, raw: true })
54 | }
55 | async function remove (table, id) {
56 | const model = pluralize.singular(table)
57 | await store[model].update({ isDeleted: true }, { where: { id } })
58 | return true
59 | }
60 | async function query (table, where, paginationConfig = null, include = null) {
61 | const model = pluralize.singular(table)
62 | const defaultWhere = { isDeleted: false }
63 | const config = { where: { ...defaultWhere } }
64 | if (where) {
65 | config.where = { ...config.where, ...where }
66 | }
67 |
68 | if (include) {
69 | config.include = include
70 | }
71 |
72 | if (paginationConfig) {
73 | const itemsPerPage = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE
74 | const currentPage = paginationConfig.currentPage || DEFAULTS.CURRENT_PAGE
75 | config.limit = paginationConfig.itemsPerPage || DEFAULTS.ITEMS_PER_PAGE
76 | config.offset = currentPage <= 1 ? 0 : (currentPage - 1) * config.limit
77 |
78 | const results = await store[model].findAndCountAll({
79 | ...config,
80 | distinct: true,
81 | raw: true
82 | }).then(({ count, rows }) => pagination(count, rows, itemsPerPage, currentPage))
83 |
84 | return toJSON(results)
85 | }
86 |
87 | const results = await store[model].findAll({
88 | ...config
89 | })
90 | return toJSON(results)
91 | }
92 |
93 | function model (model) {
94 | return store[model]
95 | }
96 |
97 | module.exports = {
98 | list,
99 | get,
100 | insert,
101 | update,
102 | remove,
103 | query,
104 | model
105 | }
106 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at leshz@me.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/Backend/store/mocks/AuthMock.js:
--------------------------------------------------------------------------------
1 | const { rolesEnum } = require('./RolesMock')
2 | const auths = [
3 | {
4 | id: '018a5472-bf8e-4889-a3a2-ebdd4722d6b2',
5 | idNumber: 1767,
6 | username: 'leodora.crouch.1767',
7 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
8 | roleId: rolesEnum.ADMINISTRATOR.id,
9 | isDeleted: false,
10 | createdAt: '2020-01-22 14:00:18',
11 | updatedAt: '2020-02-27 20:11:39'
12 | },
13 | {
14 | id: '832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c',
15 | idNumber: 3469,
16 | username: 'hayden.haseman.3469',
17 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
18 | roleId: rolesEnum.MEDIC.id,
19 | isDeleted: false,
20 | createdAt: '2020-02-03 08:45:34',
21 | updatedAt: '2019-12-31 18:44:32'
22 | },
23 | {
24 | id: '795b5699-7ca9-4344-8fe5-4a4f501678a9',
25 | idNumber: 5582,
26 | username: 'basile.matteini.5582',
27 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
28 | roleId: rolesEnum.BACTERIOLOGIST.id,
29 | isDeleted: true,
30 | createdAt: '2019-10-10 02:05:12',
31 | updatedAt: '2020-02-03 00:16:37'
32 | },
33 | {
34 | id: '1cffa615-e1d8-4113-b585-60603f0854fb',
35 | idNumber: 3123,
36 | username: 'hersh.oloman.3123',
37 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
38 | roleId: rolesEnum.PACIENT.id,
39 | isDeleted: false,
40 | createdAt: '2019-10-18 22:22:15',
41 | updatedAt: '2019-10-27 06:28:39'
42 | },
43 | {
44 | id: '0a96b420-ba2b-4741-b290-c916d92abdc0',
45 | idNumber: 1011,
46 | username: 'ursula.cowen.1011',
47 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
48 | roleId: rolesEnum.ADMINISTRATOR.id,
49 | isDeleted: true,
50 | createdAt: '2019-05-23 01:10:13',
51 | updatedAt: '2019-11-11 16:03:54'
52 | },
53 | {
54 | id: 'b9df98a3-f23d-4711-b710-59efe5387921',
55 | idNumber: 7897,
56 | username: 'norry.vockins.7897',
57 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
58 | roleId: rolesEnum.MEDIC.id,
59 | isDeleted: false,
60 | createdAt: '2020-02-02 06:11:16',
61 | updatedAt: '2019-08-20 00:41:38'
62 | },
63 | {
64 | id: 'ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc',
65 | idNumber: 5839,
66 | username: 'hort.mantram.5839',
67 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
68 | roleId: rolesEnum.BACTERIOLOGIST.id,
69 | isDeleted: true,
70 | createdAt: '2019-10-12 12:44:17',
71 | updatedAt: '2020-04-14 12:24:05'
72 | },
73 | {
74 | id: '9d7f71ff-49fe-4819-859c-727ce7f44be3',
75 | idNumber: 6214,
76 | username: 'christie.baelde.6214',
77 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
78 | roleId: rolesEnum.PACIENT.id,
79 | isDeleted: false,
80 | createdAt: '2019-12-08 08:22:59',
81 | updatedAt: '2020-05-12 06:13:23'
82 | },
83 | {
84 | id: 'c0f557a2-da71-4974-930f-17e52935bc7a',
85 | idNumber: 1325,
86 | username: 'byram.doblin.1325',
87 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
88 | roleId: rolesEnum.PACIENT.id,
89 | isDeleted: true,
90 | createdAt: '2020-01-02 01:26:33',
91 | updatedAt: '2020-02-27 09:26:02'
92 | },
93 | {
94 | id: '5a12fd8d-24f5-4327-a40a-3b1ff02c8d55',
95 | idNumber: 1382,
96 | username: 'dniren.pidwell.1382',
97 | password: '$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G',
98 | roleId: rolesEnum.PACIENT.id,
99 | isDeleted: false,
100 | createdAt: '2019-09-16 09:43:44',
101 | updatedAt: '2019-06-21 00:51:37'
102 | }
103 | ]
104 |
105 | module.exports = auths
106 |
--------------------------------------------------------------------------------
/Backend/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/node,linux,macos,windows
3 | # Edit at https://www.gitignore.io/?templates=node,linux,macos,windows
4 |
5 | ### Linux ###
6 | *~
7 |
8 | # temporary files which can be created if a process still has a handle open of a deleted file
9 | .fuse_hidden*
10 |
11 | # KDE directory preferences
12 | .directory
13 |
14 | # Linux trash folder which might appear on any partition or disk
15 | .Trash-*
16 |
17 | # .nfs files are created when an open file is removed but is still being accessed
18 | .nfs*
19 |
20 | ### macOS ###
21 | # General
22 | .DS_Store
23 | .AppleDouble
24 | .LSOverride
25 |
26 | # Icon must end with two \r
27 | Icon
28 |
29 | # Thumbnails
30 | ._*
31 |
32 | # Files that might appear in the root of a volume
33 | .DocumentRevisions-V100
34 | .fseventsd
35 | .Spotlight-V100
36 | .TemporaryItems
37 | .Trashes
38 | .VolumeIcon.icns
39 | .com.apple.timemachine.donotpresent
40 |
41 | # Directories potentially created on remote AFP share
42 | .AppleDB
43 | .AppleDesktop
44 | Network Trash Folder
45 | Temporary Items
46 | .apdisk
47 |
48 | ### Node ###
49 | # Logs
50 | logs
51 | *.log
52 | npm-debug.log*
53 | yarn-debug.log*
54 | yarn-error.log*
55 | lerna-debug.log*
56 |
57 | # Diagnostic reports (https://nodejs.org/api/report.html)
58 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
59 |
60 | # Runtime data
61 | pids
62 | *.pid
63 | *.seed
64 | *.pid.lock
65 |
66 | # Directory for instrumented libs generated by jscoverage/JSCover
67 | lib-cov
68 |
69 | # Coverage directory used by tools like istanbul
70 | coverage
71 | *.lcov
72 |
73 | # nyc test coverage
74 | .nyc_output
75 |
76 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
77 | .grunt
78 |
79 | # Bower dependency directory (https://bower.io/)
80 | bower_components
81 |
82 | # node-waf configuration
83 | .lock-wscript
84 |
85 | # Compiled binary addons (https://nodejs.org/api/addons.html)
86 | build/Release
87 |
88 | # Dependency directories
89 | node_modules/
90 | jspm_packages/
91 |
92 | # TypeScript v1 declaration files
93 | typings/
94 |
95 | # TypeScript cache
96 | *.tsbuildinfo
97 |
98 | # Optional npm cache directory
99 | .npm
100 |
101 | # Optional eslint cache
102 | .eslintcache
103 |
104 | # Optional REPL history
105 | .node_repl_history
106 |
107 | # Output of 'npm pack'
108 | *.tgz
109 |
110 | # Yarn Integrity file
111 | .yarn-integrity
112 |
113 | # dotenv environment variables file
114 | .env
115 | .env.test
116 |
117 | # parcel-bundler cache (https://parceljs.org/)
118 | .cache
119 |
120 | # next.js build output
121 | .next
122 |
123 | # nuxt.js build output
124 | .nuxt
125 |
126 | # rollup.js default build output
127 | dist/
128 |
129 | # Uncomment the public line if your project uses Gatsby
130 | # https://nextjs.org/blog/next-9-1#public-directory-support
131 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav
132 | # public
133 |
134 | # Storybook build outputs
135 | .out
136 | .storybook-out
137 |
138 | # vuepress build output
139 | .vuepress/dist
140 |
141 | # Serverless directories
142 | .serverless/
143 |
144 | # FuseBox cache
145 | .fusebox/
146 |
147 | # DynamoDB Local files
148 | .dynamodb/
149 |
150 | # Temporary folders
151 | tmp/
152 | temp/
153 |
154 | ### Windows ###
155 | # Windows thumbnail cache files
156 | Thumbs.db
157 | Thumbs.db:encryptable
158 | ehthumbs.db
159 | ehthumbs_vista.db
160 |
161 | # Dump file
162 | *.stackdump
163 |
164 | # Folder config file
165 | [Dd]esktop.ini
166 |
167 | # Recycle Bin used on file shares
168 | $RECYCLE.BIN/
169 |
170 | # Windows Installer files
171 | *.cab
172 | *.msi
173 | *.msix
174 | *.msm
175 | *.msp
176 |
177 | # Windows shortcuts
178 | *.lnk
179 |
180 | # End of https://www.gitignore.io/api/node,linux,macos,windows
181 | .env
182 | coverage
--------------------------------------------------------------------------------
/Frontend/src/mocks/ExamMock.js:
--------------------------------------------------------------------------------
1 | // Exams
2 | const Exams = [
3 | {
4 | id: "a0108392-00af-4557-865d-fa378789f4cc",
5 | content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: 6.1 }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: 3.5 }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: 10.2 }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: 31.5 }],
6 | reservation_date: "",
7 | status_id: "72a88d3f-933a-4ab5-96ed-70e5bcb0dbbd",
8 | medic_id: "bff20d66-b0e7-4eb5-ae2a-da907914a76e",
9 | pacient_id: "7b07945e-e59f-4261-8b56-d2d80adb3cec",
10 | bacteriologist_id: "be910562-e713-43a0-bed6-dd8d9fe2072f",
11 | type_id: "10b9be0a-db18-448b-98aa-002d53339805",
12 | is_deleted: false,
13 | created_at: "2020-05-25 03:48:19.437-05",
14 | updated_at: "2020-05-25 03:48:19.437-05"
15 | },
16 | {
17 | id: "a0108392-00af-4557-865d-fa378789f4ca",
18 | content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: 6.1 }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: 3.5 }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: 10.2 }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: 31.5 }],
19 | reservation_date: "2020-05-25 03:48:19.437-05",
20 | status_id: "c47400b7-9b11-43c4-93ff-c72652e42903",
21 | medic_id: "bff20d66-b0e7-4eb5-ae2a-da907914a76e",
22 | pacient_id: "7b07945e-e59f-4261-8b56-d2d80adb3cec",
23 | bacteriologist_id: "be910562-e713-43a0-bed6-dd8d9fe2072f",
24 | type_id: "10b9be0a-db18-448b-98aa-002d53339805",
25 | is_deleted: false,
26 | created_at: "2020-05-25 03:48:19.437-05",
27 | updated_at: "2020-05-25 03:48:19.437-05"
28 | },
29 | {
30 | id: "a0108392-00af-4557-865d-fa378789f4cd",
31 | content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: 6.1 }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: 3.5 }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: 10.2 }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: 31.5 }],
32 | reservation_date: "2020-05-25 03:48:19.437-05",
33 | status_id: "ed7efc9d-1be8-4fc9-b761-4debfb4d547f",
34 | medic_id: "bff20d66-b0e7-4eb5-ae2a-da907914a76e",
35 | pacient_id: "7b07945e-e59f-4261-8b56-d2d80adb3cec",
36 | bacteriologist_id: "be910562-e713-43a0-bed6-dd8d9fe2072f",
37 | type_id: "10b9be0a-db18-448b-98aa-002d53339805",
38 | is_deleted: false,
39 | created_at: "2020-05-25 03:48:19.437-05",
40 | updated_at: "2020-05-25 03:48:19.437-05"
41 | },
42 | ]
43 |
44 | // exam_types
45 |
46 | const examTypes = [
47 | {
48 | id: "",
49 | name: "Biometría hemática",
50 | description: "lorem ipsum dolor sit amet",
51 | template_content: [{ name: "Leucocitos", reference: "3.5-10.5^3/uL", value: null }, { name: "Eritrocitos", reference: "3.5-10.5^3/uL", value: null }, { name: "Hemoglobina", reference: "3.5-10.5^3/uL", value: null }, { name: "Hematrocrito", reference: "3.5-10.5^3/uL", value: null }],
52 | is_deleted: false,
53 | created_at: "2020-05-25 03:48:19.437-05",
54 | updated_at: "2020-05-25 03:48:19.437-05"
55 | }
56 | ]
57 |
58 | // exam_statuses
59 | const examStatus = [
60 | {
61 | id: "72a88d3f-933a-4ab5-96ed-70e5bcb0dbbd",
62 | name: "Pendiente",
63 | description: "Lorem ipsum",
64 | is_deleted: false,
65 | created_at: "2020-05-25 03:48:19.437-05",
66 | updated_at: "2020-05-25 03:48:19.437-05"
67 | },
68 | {
69 | id: "c47400b7-9b11-43c4-93ff-c72652e42903",
70 | name: "Asignado",
71 | description: "Lorem ipsum",
72 | is_deleted: false,
73 | created_at: "2020-05-25 03:48:19.437-05",
74 | updated_at: "2020-05-25 03:48:19.437-05"
75 | },
76 | {
77 | id: "ed7efc9d-1be8-4fc9-b761-4debfb4d547f",
78 | name: "Completado",
79 | description: "Lorem ipsum",
80 | is_deleted: false,
81 | created_at: "2020-05-25 03:48:19.437-05",
82 | updated_at: "2020-05-25 03:48:19.437-05"
83 | }
84 | ]
85 |
86 | module.exports = {
87 | Exams,
88 | examTypes,
89 | examStatus
90 | }
91 |
--------------------------------------------------------------------------------
/Frontend/src/containers/AddUser.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { connect } from 'react-redux';
4 | import { addUser } from '../actions/index';
5 |
6 | import Header from '../components/Header';
7 | import '../assets/styles/containers/AddUser.scss';
8 |
9 | const AddUser = (props) => {
10 |
11 | const [form, setForm] = useState({
12 | rolName: '',
13 | idNumber: '',
14 | firstName: '',
15 | lastName: '',
16 | contactNumber: '',
17 | address: '',
18 | email: '',
19 | });
20 |
21 | const handleInput = (event) => {
22 | setForm({
23 | ...form,
24 | [event.target.name]: event.target.value,
25 | });
26 | };
27 |
28 | const handleSubmit = (event) => {
29 | event.preventDefault();
30 | console.log(form);
31 | props.addUser(form);
32 | props.history.push('/administrator');
33 | };
34 |
35 | const handleChange = (event) => {
36 | setForm({ rolName: event.target.value });
37 | }
38 |
39 | return (
40 |
115 | );
116 | };
117 | const mapDispatchToProps = {
118 | addUser,
119 | };
120 |
121 | export default connect(null, mapDispatchToProps)(AddUser);
122 |
--------------------------------------------------------------------------------
/Frontend/src/containers/HomeLogin.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/button-has-type */
2 | import React, { useState } from "react";
3 | import { Link } from "react-router-dom";
4 | import { connect } from "react-redux";
5 | import axios from "axios"
6 | import { loginRequest } from "../actions";
7 | import messaging from "../conf/firebase";
8 |
9 | import logo from "../assets/static/logo.png";
10 | import "../assets/styles/containers/Login.scss";
11 |
12 | const Login = (props) => {
13 |
14 | const [form, setValues] = useState({
15 | username: "",
16 | password: ""
17 | });
18 |
19 | const handleInput = (event) => {
20 | setValues({
21 | ...form,
22 | [event.target.name]: event.target.value,
23 | });
24 | };
25 |
26 | const handleSubmit = (event) => {
27 | event.preventDefault();
28 |
29 | axios.post("https://megacat-backend.herokuapp.com/api/auth/login", {}, {
30 |
31 | auth: {
32 | username: form.username,
33 | password: form.password
34 | }
35 | }).then(response => {
36 | const roles = response.data.body.user.roles || [{ name: "None" }]
37 | props.loginRequest({ ...response.data.body.user, token: response.data.body.token });
38 | if (roles[0].name !== "None") {
39 | props.history.push(`/${roles[0].name}`)
40 | }
41 | })
42 | };
43 | messaging.requestPermission()
44 | .then(() =>{
45 | return messaging.getToken();
46 | })
47 | .then(e => {
48 | console.log(e);
49 | })
50 | .catch(function (err) {
51 | console.log("Unable to get permission to notify.", err);
52 | });
53 | navigator.serviceWorker.addEventListener("message", (message) => console.log(message));
54 |
55 | return (
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
Bienvenidos a Nextep
64 |
65 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Aviso de privacidad
109 |
110 |
111 |
112 |
113 | );
114 | };
115 |
116 | const mapDispatchToProps = {
117 | loginRequest,
118 | };
119 |
120 | export default connect(null, mapDispatchToProps)(Login);
121 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Welcome to Nextep Lab 👋
2 |
3 | 
4 | [](https://github.com/leshz/megaCat#readme)
5 | [](https://github.com/leshz/megaCat/graphs/commit-activity)
6 |
7 | > Nextep born to make easier and faster manage your clinical laboratory with a friendly way for your patients.
8 |
9 | 
10 |
11 | # Take A Look
12 |
13 | - [Login](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=4%3A0&scaling=scale-down)
14 | - [Patient](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=25%3A2&scaling=scale-down)
15 | - [Admin](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=255%3A1&scaling=scale-down)
16 | - [Bacteriologist](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=388%3A37&scaling=scale-down)
17 | - [Doctor](https://www.figma.com/proto/3aPltYqUg94Up8V6r5nHFn/MegaCat?node-id=37%3A99&scaling=scale-down)
18 |
19 | We build two wonderful projects: **Backend** and **Frontend**
20 |
21 | ## **Backend APP**
22 |
23 | You can read the [API REST Docs here](https://documenter.getpostman.com/view/1023966/Szt8c97m?version=latest)
24 |
25 | ### ✨ [API DEMO](https://megacat-backend.herokuapp.com)
26 |
27 | ### Install
28 |
29 | ```sh
30 | npm install
31 | ```
32 |
33 | ### Usage
34 |
35 | ```sh
36 | npm run start:api
37 | ```
38 |
39 | ### Run Tests
40 |
41 | ```sh
42 | npm run test
43 | ```
44 |
45 | ### Build Model DB
46 |
47 | ```sh
48 | npm run migration:run
49 | ```
50 |
51 | ### Sequelize Usefull Commands
52 |
53 | If you want create a Sequelize model with a migration you need to run:
54 |
55 | ```sh
56 | npx sequelize model:generate --name --attributes :,:
57 |
58 | Example:
59 |
60 | npx sequelize model:generate user --attributes username:string,password:string
61 | ```
62 |
63 | For more information [read this docs](https://sequelize.org/master/manual/migrations.html)
64 |
65 | ### Stack
66 |
67 | - NodeJS
68 | - Express
69 | - Sequelize
70 | - MySQL
71 | - Jest
72 | - JWT
73 | - Passport
74 | - Standard
75 | - Hapi/Boom
76 | - Firebase
77 |
78 | ---
79 |
80 | ## Frontend APP
81 |
82 | ### ✨ [Demo](https://nextep-lab.herokuapp.com)
83 |
84 | ## Install
85 |
86 | ```sh
87 | npm install
88 | ```
89 |
90 | ## Usage
91 |
92 | ```sh
93 | npm run start
94 | ```
95 |
96 | ## Run tests
97 |
98 | ```sh
99 | npm run test
100 | ```
101 |
102 | ### Stack
103 |
104 | - ReactJs
105 | - Redux
106 | - Firebase
107 | - SCSS
108 | - Standard
109 |
110 | ## Authors
111 |
112 | 👤 **Guiselle Mejía **
113 |
114 | - Github: [@guissmejia](github.com/guissmejia)
115 | - LinkedIn: [in/guiselle-mejía-561a61191](https://www.linkedin.com/in/guiselle-mejía-561a61191/)
116 |
117 | 👤 **Yoshua Díaz**
118 |
119 | - Website: [yoshuadiaz.com](https://yoshuadiaz.com)
120 | - Github: [@yoshuadiaz](https://github.com/yoshuadiaz)
121 | - LinkedIn: [in/yoshuadiaz](https://www.linkedin.com/in/yoshua-diaz)
122 |
123 | 👤 **Omar Betanzos **
124 |
125 | - Github: [@dashdancing](https://github.com/dashdancing)
126 | - LinkedIn: [in/omar.gbet](https://www.linkedin.com/in/omar.gbet)
127 |
128 | 👤 **Jeffer Barragan **
129 |
130 | - Website: [gitlab.com/leshz](https://gitlab.com/leshz)
131 | - Github: [@leshz](https://gitlab.com/leshz)
132 | - LinkedIn: [in/jeffbarragan/](https://www.linkedin.com/in/jeffbarragan/)
133 |
134 | 👤**Fernando Torres **
135 |
136 | - Website: [fertorresmx.com](https://www.fertorresmx.com)
137 | - Github: [@FernandoTorresL](https://github.com/FernandoTorresL)
138 | - LinkedIn: [@fertorresmx](https://www.linkedin.com/in/fertorresmx/)
139 |
140 | ## 🤝 Contributing :)
141 |
142 | Contributions, issues and feature requests are welcome!
143 |
144 | Feel free to check [issues page](https://github.com/leshz/megaCat/issues). You can also take a look at the [contributing guide](https://github.com/leshz/megaCat/blob/master/CONTRIBUTING.md).
145 |
146 | ## Documentation
147 |
148 | You can find the Nextep App documentation [On Notion](https://www.notion.so/MegaCat-Documentaci-n-26f9cb69824d4fd5bbb43898ca7e9ea6)
149 |
150 |
151 | ## Show your support
152 |
153 | Give a ⭐️ if this project helped you!
154 |
155 | ## 📝 License
156 |
157 | Copyright © 2020 [Guiselle Mejía ](https://github.com/leshz).
158 |
159 | This project is [MIT](https://github.com/leshz/megaCat/blob/master/LICENSE) licensed.
160 |
--------------------------------------------------------------------------------
/Backend/__test__/api.components.auth.spec.js:
--------------------------------------------------------------------------------
1 | const auths = require('../store/mocks/AuthMock')
2 | const users = require('../store/mocks/UsersMock')
3 | const storeMock = require('../store/mocks/storeMock')
4 | const userCtrl = require('../api/components/user/index')
5 | const emailCtrl = require('../api/components/email/index')
6 | const authRolesCtrl = require('../api/components/auth_roles/index')
7 | jest.mock('../api/components/user/index.js', () => ({
8 | insert: jest.fn((user) => ({ ...user, id: '1234567890' })),
9 | get: jest.fn((id) => ({ id }))
10 | }))
11 |
12 | jest.mock('../api/components/email/index.js', () => ({
13 | sendNewUser: jest.fn((data) => true)
14 | }))
15 |
16 | jest.mock('../api/components/auth_roles/index', () => ({
17 | insert: jest.fn(() => true)
18 | }))
19 |
20 | describe('API | Components | Auth', () => {
21 | const { rolesEnum } = require('../store/mocks/RolesMock')
22 |
23 | describe('Controllers', () => {
24 | const ctrl = require('../api/components/auth/controller')
25 |
26 | const controller = ctrl(storeMock)
27 |
28 | describe('login', () => {
29 | it('should receive a user', async () => {
30 | const result = await controller.login(auths[0])
31 | const expected = {
32 | id: users[0].id,
33 | idNumber: users[0].idNumber,
34 | username: users[0].username,
35 | roles: undefined
36 | }
37 |
38 | expect(result.user).toEqual(expected)
39 | })
40 |
41 | it('should respond with a user and token data', () => {
42 | expect().toBeFalsy()
43 | })
44 |
45 | it('should respond with an error if not pass a user', async () => {
46 | const response = controller.login()
47 | expect(response).rejects.toThrow()
48 | })
49 | })
50 |
51 | describe('get', () => {
52 | it('should call store.query', async (done) => {
53 | const user = users[0]
54 | const userAuth = auths.find(u => u.id === user.id)
55 | const response = await controller.get({ id: user.id })
56 | expect(response).toEqual(userAuth)
57 | done()
58 | })
59 |
60 | it('should fail without query data', () => {
61 | const response = controller.get()
62 | expect(response).rejects.toThrow()
63 | })
64 |
65 | it('should return an user object', () => {
66 | })
67 | })
68 |
69 | describe('addUser', () => {
70 | const { generateUsername } = require('../utils/userUtils')
71 | const userDataMock = {
72 | idNumber: users[1].idNumber,
73 | firstName: users[1].firstName,
74 | lastName: users[1].lastName,
75 | email: users[1].email,
76 | contactNumber: users[1].contactNumber,
77 | address: users[1].address,
78 | roleId: rolesEnum.ADMINISTRATOR.id
79 | }
80 |
81 | beforeEach(() => {
82 | userCtrl.insert.mockClear()
83 | emailCtrl.sendNewUser.mockClear()
84 | })
85 | it('should insert a user', async (done) => {
86 | const userData = {
87 | ...userDataMock,
88 | idNumber: 1010101010101010
89 | }
90 | const userExpected = {
91 | id: '1234567890',
92 | idNumber: userData.idNumber,
93 | firstName: userData.firstName,
94 | lastName: userData.lastName,
95 | email: userData.email,
96 | contactNumber: userData.contactNumber,
97 | address: userData.address,
98 | username: generateUsername(userData.idNumber, userData.firstName, userData.lastName)
99 | }
100 |
101 | const response = await controller.addUser(userData)
102 | const userInserted = userCtrl.insert.mock.results[0].value
103 | expect(response).toBeTruthy()
104 | expect(userInserted).toEqual(userExpected)
105 | done()
106 | })
107 |
108 | it('should generate a random two numbers different if hasUsername', async () => {
109 | const userData = {
110 | ...userDataMock
111 | }
112 | const response = await controller.addUser(userData)
113 | const username = generateUsername(userData.idNumber, userData.firstName, userData.lastName)
114 | const userInserted = userCtrl.insert.mock.results[0].value
115 | const authRoleInserted = authRolesCtrl.insert.mock.calls[0]
116 | expect(response).toBeTruthy()
117 | expect(username.slice(0, -2))
118 | .toBe(userInserted.username.slice(0, -2))
119 | expect(authRoleInserted).toEqual([userInserted.id, userData.roleId])
120 | expect(username)
121 | .not
122 | .toBe(userInserted.username)
123 | })
124 |
125 | it('should return an error if the process fails', () => {
126 | const response = controller.addUser()
127 | expect(response).rejects.toThrow()
128 | })
129 | })
130 | })
131 | })
132 |
--------------------------------------------------------------------------------
/Backend/store/mocks/UsersMock.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
4 | "idNumber": 1767,
5 | "firstName": "Leodora",
6 | "lastName": "Crouch",
7 | "username": "leodora.crouch.1767",
8 | "email": "lcrouch0@wix.com",
9 | "contactNumber": "715-982-0519",
10 | "address": "26 Quincy Pass",
11 | "medicalRecord": "03254d70-723b-4bed-8adb-477dffe8f092",
12 | "isDeleted": false,
13 | "createdAt": "2020-01-22 14:00:18",
14 | "updatedAt": "2020-02-27 20:11:39"
15 | },
16 | {
17 | "id": "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c",
18 | "idNumber": 3469,
19 | "firstName": "Hayden",
20 | "lastName": "Haseman",
21 | "username": "hayden.haseman.3469",
22 | "email": "hhaseman1@apple.com",
23 | "contactNumber": "552-913-5574",
24 | "address": "5761 Melvin Drive",
25 | "medicalRecord": "088b8665-d176-4021-9416-ac787339c568",
26 | "isDeleted": false,
27 | "createdAt": "2020-02-03 08:45:34",
28 | "updatedAt": "2019-12-31 18:44:32"
29 | },
30 | {
31 | "id": "795b5699-7ca9-4344-8fe5-4a4f501678a9",
32 | "idNumber": 5582,
33 | "firstName": "Basile",
34 | "lastName": "Matteini",
35 | "username": "basile.matteini.5582",
36 | "email": "bmatteini2@squarespace.com",
37 | "contactNumber": "978-756-5028",
38 | "address": "939 7th Place",
39 | "medicalRecord": "56021793-eed0-44c1-b2ab-72b245f1bb17",
40 | "isDeleted": true,
41 | "createdAt": "2019-10-10 02:05:12",
42 | "updatedAt": "2020-02-03 00:16:37"
43 | },
44 | {
45 | "id": "1cffa615-e1d8-4113-b585-60603f0854fb",
46 | "idNumber": 3123,
47 | "firstName": "Hersh",
48 | "lastName": "Oloman",
49 | "username": "hersh.oloman.3123",
50 | "email": "holoman3@themeforest.net",
51 | "contactNumber": "746-918-2444",
52 | "address": "05898 Southridge Alley",
53 | "medicalRecord": "5823cf51-b27c-4cec-a0ac-c42b249ddc7c",
54 | "isDeleted": false,
55 | "createdAt": "2019-10-18 22:22:15",
56 | "updatedAt": "2019-10-27 06:28:39"
57 | },
58 | {
59 | "id": "0a96b420-ba2b-4741-b290-c916d92abdc0",
60 | "idNumber": 1011,
61 | "firstName": "Ursula",
62 | "lastName": "Cowen",
63 | "username": "ursula.cowen.1011",
64 | "email": "ucowen4@oakley.com",
65 | "contactNumber": "376-721-4543",
66 | "address": "1 Amoth Place",
67 | "medicalRecord": "bb881424-e27e-4c27-be49-843b7febd7e7",
68 | "isDeleted": true,
69 | "createdAt": "2019-05-23 01:10:13",
70 | "updatedAt": "2019-11-11 16:03:54"
71 | },
72 | {
73 | "id": "b9df98a3-f23d-4711-b710-59efe5387921",
74 | "idNumber": 7897,
75 | "firstName": "Norry",
76 | "lastName": "Vockins",
77 | "username": "norry.vockins.7897",
78 | "email": "nvockins5@github.io",
79 | "contactNumber": "148-183-3666",
80 | "address": "084 Hoard Terrace",
81 | "medicalRecord": "635fb5b6-1eae-4c51-9b84-971b238d75fe",
82 | "isDeleted": false,
83 | "createdAt": "2020-02-02 06:11:16",
84 | "updatedAt": "2019-08-20 00:41:38"
85 | },
86 | {
87 | "id": "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc",
88 | "idNumber": 5839,
89 | "firstName": "Hort",
90 | "lastName": "Mantram",
91 | "username": "hort.mantram.5839",
92 | "email": "hmantram6@state.gov",
93 | "contactNumber": "273-256-7690",
94 | "address": "8791 Twin Pines Avenue",
95 | "medicalRecord": "a7dd94eb-f2e6-411f-baa7-60761c03ccb3",
96 | "isDeleted": true,
97 | "createdAt": "2019-10-12 12:44:17",
98 | "updatedAt": "2020-04-14 12:24:05"
99 | },
100 | {
101 | "id": "9d7f71ff-49fe-4819-859c-727ce7f44be3",
102 | "idNumber": 6214,
103 | "firstName": "Christie",
104 | "lastName": "Baelde",
105 | "username": "christie.baelde.6214",
106 | "email": "cbaelde7@icio.us",
107 | "contactNumber": "715-754-7698",
108 | "address": "246 Delaware Avenue",
109 | "medicalRecord": "672f2993-4366-4b16-9aec-f12830494d33",
110 | "isDeleted": false,
111 | "createdAt": "2019-12-08 08:22:59",
112 | "updatedAt": "2020-05-12 06:13:23"
113 | },
114 | {
115 | "id": "c0f557a2-da71-4974-930f-17e52935bc7a",
116 | "idNumber": 1325,
117 | "firstName": "Byram",
118 | "lastName": "Doblin",
119 | "username": "byram.doblin.1325",
120 | "email": "bdoblin8@storify.com",
121 | "contactNumber": "148-633-0195",
122 | "address": "44 Kinsman Alley",
123 | "medicalRecord": "527d5cc2-0eb0-484d-9756-90a99feb650d",
124 | "isDeleted": true,
125 | "createdAt": "2020-01-02 01:26:33",
126 | "updatedAt": "2020-02-27 09:26:02"
127 | },
128 | {
129 | "id": "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55",
130 | "idNumber": 1382,
131 | "firstName": "Dniren",
132 | "lastName": "Pidwell",
133 | "username": "dniren.pidwell.1382",
134 | "email": "dpidwell9@hexun.com",
135 | "contactNumber": "311-548-0007",
136 | "address": "39 Meadow Ridge Alley",
137 | "medicalRecord": "da16d247-a252-4abc-9692-82167f84c9e2",
138 | "isDeleted": false,
139 | "createdAt": "2019-09-16 09:43:44",
140 | "updatedAt": "2019-06-21 00:51:37"
141 | }
142 | ]
143 |
--------------------------------------------------------------------------------
/Frontend/src/mocks/UsersMock.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
4 | "id_number": 1767,
5 | "first_name": "Leodora",
6 | "last_name": "Crouch",
7 | "username": "leodora.crouch.1767",
8 | "email": "lcrouch0@wix.com",
9 | "contact_number": "715-982-0519",
10 | "address": "26 Quincy Pass",
11 | "medical_record": "03254d70-723b-4bed-8adb-477dffe8f092",
12 | "is_deleted": false,
13 | "created_at": "2020-01-22 14:00:18",
14 | "updated_at": "2020-02-27 20:11:39"
15 | },
16 | {
17 | "id": "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c",
18 | "id_number": 3469,
19 | "first_name": "Hayden",
20 | "last_name": "Haseman",
21 | "username": "hayden.haseman.3469",
22 | "email": "hhaseman1@apple.com",
23 | "contact_number": "552-913-5574",
24 | "address": "5761 Melvin Drive",
25 | "medical_record": "088b8665-d176-4021-9416-ac787339c568",
26 | "is_deleted": false,
27 | "created_at": "2020-02-03 08:45:34",
28 | "updated_at": "2019-12-31 18:44:32"
29 | },
30 | {
31 | "id": "795b5699-7ca9-4344-8fe5-4a4f501678a9",
32 | "id_number": 5582,
33 | "first_name": "Basile",
34 | "last_name": "Matteini",
35 | "username": "basile.matteini.5582",
36 | "email": "bmatteini2@squarespace.com",
37 | "contact_number": "978-756-5028",
38 | "address": "939 7th Place",
39 | "medical_record": "56021793-eed0-44c1-b2ab-72b245f1bb17",
40 | "is_deleted": true,
41 | "created_at": "2019-10-10 02:05:12",
42 | "updated_at": "2020-02-03 00:16:37"
43 | },
44 | {
45 | "id": "1cffa615-e1d8-4113-b585-60603f0854fb",
46 | "id_number": 3123,
47 | "first_name": "Hersh",
48 | "last_name": "Oloman",
49 | "username": "hersh.oloman.3123",
50 | "email": "holoman3@themeforest.net",
51 | "contact_number": "746-918-2444",
52 | "address": "05898 Southridge Alley",
53 | "medical_record": "5823cf51-b27c-4cec-a0ac-c42b249ddc7c",
54 | "is_deleted": false,
55 | "created_at": "2019-10-18 22:22:15",
56 | "updated_at": "2019-10-27 06:28:39"
57 | },
58 | {
59 | "id": "0a96b420-ba2b-4741-b290-c916d92abdc0",
60 | "id_number": 1011,
61 | "first_name": "Ursula",
62 | "last_name": "Cowen",
63 | "username": "ursula.cowen.1011",
64 | "email": "ucowen4@oakley.com",
65 | "contact_number": "376-721-4543",
66 | "address": "1 Amoth Place",
67 | "medical_record": "bb881424-e27e-4c27-be49-843b7febd7e7",
68 | "is_deleted": true,
69 | "created_at": "2019-05-23 01:10:13",
70 | "updated_at": "2019-11-11 16:03:54"
71 | },
72 | {
73 | "id": "b9df98a3-f23d-4711-b710-59efe5387921",
74 | "id_number": 7897,
75 | "first_name": "Norry",
76 | "last_name": "Vockins",
77 | "username": "norry.vockins.7897",
78 | "email": "nvockins5@github.io",
79 | "contact_number": "148-183-3666",
80 | "address": "084 Hoard Terrace",
81 | "medical_record": "635fb5b6-1eae-4c51-9b84-971b238d75fe",
82 | "is_deleted": false,
83 | "created_at": "2020-02-02 06:11:16",
84 | "updated_at": "2019-08-20 00:41:38"
85 | },
86 | {
87 | "id": "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc",
88 | "id_number": 5839,
89 | "first_name": "Hort",
90 | "last_name": "Mantram",
91 | "username": "hort.mantram.5839",
92 | "email": "hmantram6@state.gov",
93 | "contact_number": "273-256-7690",
94 | "address": "8791 Twin Pines Avenue",
95 | "medical_record": "a7dd94eb-f2e6-411f-baa7-60761c03ccb3",
96 | "is_deleted": true,
97 | "created_at": "2019-10-12 12:44:17",
98 | "updated_at": "2020-04-14 12:24:05"
99 | },
100 | {
101 | "id": "9d7f71ff-49fe-4819-859c-727ce7f44be3",
102 | "id_number": 6214,
103 | "first_name": "Christie",
104 | "last_name": "Baelde",
105 | "username": "christie.baelde.6214",
106 | "email": "cbaelde7@icio.us",
107 | "contact_number": "715-754-7698",
108 | "address": "246 Delaware Avenue",
109 | "medical_record": "672f2993-4366-4b16-9aec-f12830494d33",
110 | "is_deleted": false,
111 | "created_at": "2019-12-08 08:22:59",
112 | "updated_at": "2020-05-12 06:13:23"
113 | },
114 | {
115 | "id": "c0f557a2-da71-4974-930f-17e52935bc7a",
116 | "id_number": 1325,
117 | "first_name": "Byram",
118 | "last_name": "Doblin",
119 | "username": "byram.doblin.1325",
120 | "email": "bdoblin8@storify.com",
121 | "contact_number": "148-633-0195",
122 | "address": "44 Kinsman Alley",
123 | "medical_record": "527d5cc2-0eb0-484d-9756-90a99feb650d",
124 | "is_deleted": true,
125 | "created_at": "2020-01-02 01:26:33",
126 | "updated_at": "2020-02-27 09:26:02"
127 | },
128 | {
129 | "id": "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55",
130 | "id_number": 1382,
131 | "first_name": "Dniren",
132 | "last_name": "Pidwell",
133 | "username": "dniren.pidwell.1382",
134 | "email": "dpidwell9@hexun.com",
135 | "contact_number": "311-548-0007",
136 | "address": "39 Meadow Ridge Alley",
137 | "medical_record": "da16d247-a252-4abc-9692-82167f84c9e2",
138 | "is_deleted": false,
139 | "created_at": "2019-09-16 09:43:44",
140 | "updated_at": "2019-06-21 00:51:37"
141 | }
142 | ]
143 |
--------------------------------------------------------------------------------
/Frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/test,node,linux,macos,windows,visualstudiocode
2 | # Edit at https://www.gitignore.io/?templates=test,node,linux,macos,windows,visualstudiocode
3 |
4 | ### Linux ###
5 | *~
6 |
7 | # temporary files which can be created if a process still has a handle open of a deleted file
8 | .fuse_hidden*
9 |
10 | # KDE directory preferences
11 | .directory
12 |
13 | # Linux trash folder which might appear on any partition or disk
14 | .Trash-*
15 |
16 | # .nfs files are created when an open file is removed but is still being accessed
17 | .nfs*
18 |
19 | ### macOS ###
20 | # General
21 | .DS_Store
22 | .AppleDouble
23 | .LSOverride
24 |
25 | # Icon must end with two \r
26 | Icon
27 |
28 | # Thumbnails
29 | ._*
30 |
31 | # Files that might appear in the root of a volume
32 | .DocumentRevisions-V100
33 | .fseventsd
34 | .Spotlight-V100
35 | .TemporaryItems
36 | .Trashes
37 | .VolumeIcon.icns
38 | .com.apple.timemachine.donotpresent
39 |
40 | # Directories potentially created on remote AFP share
41 | .AppleDB
42 | .AppleDesktop
43 | Network Trash Folder
44 | Temporary Items
45 | .apdisk
46 |
47 | ### Node ###
48 | # Logs
49 | logs
50 | *.log
51 | npm-debug.log*
52 | yarn-debug.log*
53 | yarn-error.log*
54 | lerna-debug.log*
55 |
56 | # Diagnostic reports (https://nodejs.org/api/report.html)
57 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
58 |
59 | # Runtime data
60 | pids
61 | *.pid
62 | *.seed
63 | *.pid.lock
64 |
65 | # Directory for instrumented libs generated by jscoverage/JSCover
66 | lib-cov
67 |
68 | # Coverage directory used by tools like istanbul
69 | coverage
70 | *.lcov
71 |
72 | # nyc test coverage
73 | .nyc_output
74 |
75 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
76 | .grunt
77 |
78 | # Bower dependency directory (https://bower.io/)
79 | bower_components
80 |
81 | # node-waf configuration
82 | .lock-wscript
83 |
84 | # Compiled binary addons (https://nodejs.org/api/addons.html)
85 | build/Release
86 |
87 | # Dependency directories
88 | node_modules/
89 | jspm_packages/
90 |
91 | # TypeScript v1 declaration files
92 | typings/
93 |
94 | # TypeScript cache
95 | *.tsbuildinfo
96 |
97 | # Optional npm cache directory
98 | .npm
99 |
100 | # Optional eslint cache
101 | .eslintcache
102 |
103 | # Optional REPL history
104 | .node_repl_history
105 |
106 | # Output of 'npm pack'
107 | *.tgz
108 |
109 | # Yarn Integrity file
110 | .yarn-integrity
111 |
112 | # dotenv environment variables file
113 | .env
114 | .env.test
115 |
116 | # parcel-bundler cache (https://parceljs.org/)
117 | .cache
118 |
119 | # next.js build output
120 | .next
121 |
122 | # nuxt.js build output
123 | .nuxt
124 |
125 | # rollup.js default build output
126 | dist/
127 |
128 | # Uncomment the public line if your project uses Gatsby
129 | # https://nextjs.org/blog/next-9-1#public-directory-support
130 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav
131 | # public
132 |
133 | # Storybook build outputs
134 | .out
135 | .storybook-out
136 |
137 | # vuepress build output
138 | .vuepress/dist
139 |
140 | # Serverless directories
141 | .serverless/
142 |
143 | # FuseBox cache
144 | .fusebox/
145 |
146 | # DynamoDB Local files
147 | .dynamodb/
148 |
149 | # Temporary folders
150 | tmp/
151 | temp/
152 |
153 | ### Test ###
154 | ### Ignore all files that could be used to test your code and
155 | ### you wouldn't want to push
156 |
157 | # Reference https://en.wikipedia.org/wiki/Metasyntactic_variable
158 |
159 | # Most common
160 | *foo
161 | *bar
162 | *fubar
163 | *foobar
164 | *baz
165 |
166 | # Less common
167 | *qux
168 | *quux
169 | *bongo
170 | *bazola
171 | *ztesch
172 |
173 | # UK, Australia
174 | *wibble
175 | *wobble
176 | *wubble
177 | *flob
178 | *blep
179 | *blah
180 | *boop
181 | *beep
182 |
183 | # Japanese
184 | *hoge
185 | *piyo
186 | *fuga
187 | *hogera
188 | *hogehoge
189 |
190 | # Portugal, Spain
191 | *fulano
192 | *sicrano
193 | *beltrano
194 | *mengano
195 | *perengano
196 | *zutano
197 |
198 | # France, Italy, the Netherlands
199 | *toto
200 | *titi
201 | *tata
202 | *tutu
203 | *pipppo
204 | *pluto
205 | *paperino
206 | *aap
207 | *noot
208 | *mies
209 |
210 | # Other names that would make sense
211 | *tests
212 | *testsdir
213 | *testsfile
214 | *testsfiles
215 | *test
216 | *testdir
217 | *testfile
218 | *testfiles
219 | *testing
220 | *testingdir
221 | *testingfile
222 | *testingfiles
223 | *temp
224 | *tempdir
225 | *tempfile
226 | *tempfiles
227 | *tmp
228 | *tmpdir
229 | *tmpfile
230 | *tmpfiles
231 | *lol
232 |
233 | ### VisualStudioCode ###
234 | .vscode/*
235 | !.vscode/settings.json
236 | !.vscode/tasks.json
237 | !.vscode/launch.json
238 | !.vscode/extensions.json
239 |
240 | ### VisualStudioCode Patch ###
241 | # Ignore all local history of files
242 | .history
243 |
244 | ### Windows ###
245 | # Windows thumbnail cache files
246 | Thumbs.db
247 | Thumbs.db:encryptable
248 | ehthumbs.db
249 | ehthumbs_vista.db
250 |
251 | # Dump file
252 | *.stackdump
253 |
254 | # Folder config file
255 | [Dd]esktop.ini
256 |
257 | # Recycle Bin used on file shares
258 | $RECYCLE.BIN/
259 |
260 | # Windows Installer files
261 | *.cab
262 | *.msi
263 | *.msix
264 | *.msm
265 | *.msp
266 |
267 | # Windows shortcuts
268 | *.lnk
269 |
270 | # End of https://www.gitignore.io/api/test,node,linux,macos,windows,visualstudiocode
271 |
272 |
--------------------------------------------------------------------------------
/Frontend/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "airbnb",
9 | "prettier"
10 | ],
11 | "globals": {
12 | "document": false,
13 | "escape": false,
14 | "navigator": false,
15 | "unescape": false,
16 | "window": false,
17 | "describe": true,
18 | "before": true,
19 | "it": true,
20 | "expect": true,
21 | "sinon": true
22 | },
23 | "parser": "babel-eslint",
24 | "plugins": [
25 | "prettier",
26 | "react",
27 | "jsx-a11y",
28 | "import"
29 | ],
30 | "rules": {
31 | "react/jsx-filename-extension": 0,
32 | "array-bracket-spacing": 2,
33 | "arrow-body-style": 0,
34 | "block-scoped-var": 2,
35 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
36 | "comma-spacing": [2, { "before": false, "after": true }],
37 | "comma-style": [2, "last"],
38 | "complexity": 0,
39 | "consistent-return": 1,
40 | "consistent-this": 0,
41 | "curly": [2, "multi-line"],
42 | "default-case": 0,
43 | "dot-location": [2, "property"],
44 | "dot-notation": 0,
45 | "eol-last": 2,
46 | "eqeqeq": [2, "allow-null"],
47 | "func-names": 0,
48 | "func-style": 0,
49 | "generator-star-spacing": [2, "both"],
50 | "guard-for-in": 0,
51 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ],
52 | "indent": [2, 2, { "SwitchCase": 1 }],
53 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
54 | "linebreak-style": 0,
55 | "max-depth": 0,
56 | "max-len": [2, 1550, 4],
57 | "max-nested-callbacks": 0,
58 | "max-params": 0,
59 | "max-statements": 0,
60 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
61 | "newline-after-var": [0, "never"],
62 | "new-parens": 2,
63 | "no-alert": 0,
64 | "no-array-constructor": 2,
65 | "no-bitwise": 0,
66 | "no-caller": 2,
67 | "no-catch-shadow": 0,
68 | "no-cond-assign": 2,
69 | "no-console": 0,
70 | "no-constant-condition": 0,
71 | "no-continue": 0,
72 | "no-control-regex": 2,
73 | "no-debugger": 0,
74 | "no-delete-var": 2,
75 | "no-div-regex": 0,
76 | "no-dupe-args": 2,
77 | "no-dupe-keys": 2,
78 | "no-duplicate-case": 2,
79 | "no-else-return": 2,
80 | "no-empty": 0,
81 | "no-empty-character-class": 2,
82 | "no-labels": 2,
83 | "no-eq-null": 0,
84 | "no-eval": 2,
85 | "no-ex-assign": 2,
86 | "no-extend-native": 2,
87 | "no-extra-bind": 2,
88 | "no-extra-boolean-cast": 2,
89 | "no-extra-parens": 0,
90 | "no-extra-semi": 0,
91 | "no-extra-strict": 0,
92 | "no-fallthrough": 2,
93 | "no-floating-decimal": 2,
94 | "no-func-assign": 2,
95 | "no-implied-eval": 2,
96 | "no-inline-comments": 0,
97 | "no-inner-declarations": [2, "functions"],
98 | "no-invalid-regexp": 2,
99 | "no-irregular-whitespace": 2,
100 | "no-iterator": 2,
101 | "no-label-var": 2,
102 | "no-lone-blocks": 0,
103 | "no-lonely-if": 0,
104 | "no-loop-func": 0,
105 | "no-mixed-requires": 0,
106 | "no-mixed-spaces-and-tabs": [2, false],
107 | "no-multi-spaces": 2,
108 | "no-multi-str": 0,
109 | "no-multiple-empty-lines": [2, { "max": 1 }],
110 | "no-native-reassign": 2,
111 | "no-negated-in-lhs": 2,
112 | "no-nested-ternary": 0,
113 | "no-new": 2,
114 | "no-new-func": 2,
115 | "no-new-object": 2,
116 | "no-new-require": 2,
117 | "no-new-wrappers": 2,
118 | "no-obj-calls": 2,
119 | "no-octal": 2,
120 | "no-octal-escape": 2,
121 | "no-path-concat": 0,
122 | "no-plusplus": 0,
123 | "no-process-env": 0,
124 | "no-process-exit": 0,
125 | "no-proto": 2,
126 | "no-redeclare": 2,
127 | "no-regex-spaces": 2,
128 | "no-reserved-keys": 0,
129 | "no-restricted-modules": 0,
130 | "no-script-url": 0,
131 | "no-self-compare": 2,
132 | "no-sequences": 2,
133 | "no-shadow": 0,
134 | "no-shadow-restricted-names": 2,
135 | "no-spaced-func": 2,
136 | "no-sparse-arrays": 2,
137 | "no-sync": 0,
138 | "no-ternary": 0,
139 | "no-throw-literal": 2,
140 | "no-trailing-spaces": 2,
141 | "no-undef": 0,
142 | "no-undef-init": 2,
143 | "no-undefined": 0,
144 | "no-underscore-dangle": 0,
145 | "no-unneeded-ternary": 2,
146 | "no-unreachable": 2,
147 | "no-unused-expressions": 0,
148 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
149 | "no-var": 2,
150 | "no-void": 0,
151 | "no-warning-comments": 0,
152 | "no-with": 2,
153 | "object-curly-newline": 0,
154 | "object-curly-spacing": [2, "always"],
155 | "one-var": 0,
156 | "operator-assignment": 0,
157 | "operator-linebreak": [2, "after"],
158 | "padded-blocks": 0,
159 | "prefer-const": 2,
160 | "quote-props": 0,
161 | "quotes": [2, "double", "avoid-escape"],
162 | "radix": 2,
163 | "jsx-quotes": [2, "prefer-double"],
164 | "jsx-a11y/click-events-have-key-events": 0,
165 | "jsx-a11y/no-noninteractive-element-interactions": 0,
166 | "jsx-a11y/media-has-caption": 0,
167 | "react/display-name": 0,
168 | "react/jsx-boolean-value": 0,
169 | "react/jsx-closing-bracket-location": 2,
170 | "react/jsx-curly-spacing": [2, "never"],
171 | "react/jsx-equals-spacing": [2, "never"],
172 | "react/jsx-indent-props": [2, 2],
173 | "react/jsx-no-bind": [2, { "allowArrowFunctions": true }],
174 | "react/jsx-no-undef": 2,
175 | "react/jsx-pascal-case": 2,
176 | "react/jsx-sort-prop-types": 0,
177 | "react/jsx-sort-props": 0,
178 | "react/jsx-uses-react": 2,
179 | "react/jsx-uses-vars": 2,
180 | "react/no-did-mount-set-state": 2,
181 | "react/no-did-update-set-state": 2,
182 | "react/no-multi-comp": 0,
183 | "react/no-unknown-property": 2,
184 | "react/prop-types": 0,
185 | "react/forbid-prop-types": 0,
186 | "react/prefer-stateless-function": 0,
187 | "react/require-default-props": 0,
188 | "react/react-in-jsx-scope": 2,
189 | "react/self-closing-comp": 2,
190 | "react/sort-comp": 0,
191 | "react/jsx-wrap-multilines": 2,
192 | "semi-spacing": 0,
193 | "sort-vars": 0,
194 | "space-before-blocks": [2, "always"],
195 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
196 | "space-in-parens": [2, "never"],
197 | "space-infix-ops": 2,
198 | "keyword-spacing": 2,
199 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
200 | "spaced-comment": [0, "always"],
201 | "strict": 0,
202 | "use-isnan": 2,
203 | "valid-jsdoc": 0,
204 | "valid-typeof": 2,
205 | "vars-on-top": 2,
206 | "wrap-iife": [2, "any"],
207 | "wrap-regex": 0,
208 | "yoda": [2, "never"]
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/Frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Provider } from "react-redux";
4 | import { createStore, compose } from "redux";
5 | import runtime from "serviceworker-webpack-plugin/lib/runtime";
6 | import App from "./routes/App";
7 | import reducer from "./reducers";
8 |
9 | if ("serviceWorker" in navigator) {
10 | runtime.register();
11 | }
12 |
13 | const initialState = {
14 | user: {},
15 | titles: [
16 | {
17 | name: "Panel del Administrador",
18 | titlePatient: "Panel del Paciente",
19 | },
20 | {
21 | name: "Panel del Administrador",
22 | titlePatient: "Panel del Paciente",
23 | },
24 | ],
25 | users: [
26 | {
27 | id: "97588236-ft73-1234-c4n5-ebdd2689h4j7",
28 | idNumber: 5252,
29 | firstName: "Fernando",
30 | lastName: "Torres",
31 | username: "fernando.torres.5252",
32 | email: "fertorresmx@gmail.com",
33 | contactNumber: "55 1234 5678",
34 | address: "Toledo 21, Juárez, CDMX",
35 | medicalRecord: "03254d70-723b-4bed-8adb-477dffe8f092",
36 | isDeleted: false,
37 | roleId: "d436e99b-afea-44cf-a31c-ff35b7740c67",
38 | createdAt: "2020-01-16 13:05:18",
39 | updatedAt: "2020-02-17 19:16:39",
40 | },
41 | {
42 | id: "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c",
43 | idNumber: 3469,
44 | firstName: "Hayden",
45 | lastName: "Haseman",
46 | username: "hayden.haseman.3469",
47 | email: "hhaseman1@apple.com",
48 | contactNumber: "552-913-5574",
49 | address: "5761 Melvin Drive",
50 | medicalRecord: "088b8665-d176-4021-9416-ac787339c568",
51 | isDeleted: false,
52 | createdAt: "2020-02-03 08:45:34",
53 | updatedAt: "2019-12-31 18:44:32",
54 | },
55 | {
56 | id: "795b5699-7ca9-4344-8fe5-4a4f501678a9",
57 | idNumber: 5582,
58 | firstName: "Basile",
59 | lastName: "Matteini",
60 | username: "basile.matteini.5582",
61 | email: "bmatteini2@squarespace.com",
62 | contactNumber: "978-756-5028",
63 | address: "939 7th Place",
64 | medicalRecord: "56021793-eed0-44c1-b2ab-72b245f1bb17",
65 | isDeleted: true,
66 | createdAt: "2019-10-10 02:05:12",
67 | updatedAt: "2020-02-03 00:16:37",
68 | },
69 | {
70 | id: "1cffa615-e1d8-4113-b585-60603f0854fb",
71 | idNumber: 3123,
72 | firstName: "Hersh",
73 | lastName: "Oloman",
74 | username: "hersh.oloman.3123",
75 | email: "holoman3@themeforest.net",
76 | contactNumber: "746-918-2444",
77 | address: "05898 Southridge Alley",
78 | medicalRecord: "5823cf51-b27c-4cec-a0ac-c42b249ddc7c",
79 | isDeleted: false,
80 | createdAt: "2019-10-18 22:22:15",
81 | updatedAt: "2019-10-27 06:28:39",
82 | },
83 | {
84 | id: "0a96b420-ba2b-4741-b290-c916d92abdc0",
85 | idNumber: 1011,
86 | firstName: "Ursula",
87 | lastName: "Cowen",
88 | username: "ursula.cowen.1011",
89 | email: "ucowen4@oakley.com",
90 | contactNumber: "376-721-4543",
91 | address: "1 Amoth Place",
92 | medicalRecord: "bb881424-e27e-4c27-be49-843b7febd7e7",
93 | isDeleted: true,
94 | createdAt: "2019-05-23 01:10:13",
95 | updatedAt: "2019-11-11 16:03:54",
96 | },
97 | {
98 | id: "b9df98a3-f23d-4711-b710-59efe5387921",
99 | idNumber: 7897,
100 | firstName: "Norry",
101 | lastName: "Vockins",
102 | username: "norry.vockins.7897",
103 | email: "nvockins5@github.io",
104 | contactNumber: "148-183-3666",
105 | address: "084 Hoard Terrace",
106 | medicalRecord: "635fb5b6-1eae-4c51-9b84-971b238d75fe",
107 | isDeleted: false,
108 | createdAt: "2020-02-02 06:11:16",
109 | updatedAt: "2019-08-20 00:41:38",
110 | },
111 | {
112 | id: "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc",
113 | idNumber: 5839,
114 | firstName: "Hort",
115 | lastName: "Mantram",
116 | username: "hort.mantram.5839",
117 | email: "hmantram6@state.gov",
118 | contactNumber: "273-256-7690",
119 | address: "8791 Twin Pines Avenue",
120 | medicalRecord: "a7dd94eb-f2e6-411f-baa7-60761c03ccb3",
121 | isDeleted: true,
122 | createdAt: "2019-10-12 12:44:17",
123 | updatedAt: "2020-04-14 12:24:05",
124 | },
125 | {
126 | id: "9d7f71ff-49fe-4819-859c-727ce7f44be3",
127 | idNumber: 6214,
128 | firstName: "Christie",
129 | lastName: "Baelde",
130 | username: "christie.baelde.6214",
131 | email: "cbaelde7@icio.us",
132 | contactNumber: "715-754-7698",
133 | address: "246 Delaware Avenue",
134 | medicalRecord: "672f2993-4366-4b16-9aec-f12830494d33",
135 | isDeleted: false,
136 | createdAt: "2019-12-08 08:22:59",
137 | updatedAt: "2020-05-12 06:13:23",
138 | },
139 | {
140 | id: "c0f557a2-da71-4974-930f-17e52935bc7a",
141 | idNumber: 1325,
142 | firstName: "Byram",
143 | lastName: "Doblin",
144 | username: "byram.doblin.1325",
145 | email: "bdoblin8@storify.com",
146 | contactNumber: "148-633-0195",
147 | address: "44 Kinsman Alley",
148 | medicalRecord: "527d5cc2-0eb0-484d-9756-90a99feb650d",
149 | isDeleted: true,
150 | createdAt: "2020-01-02 01:26:33",
151 | updatedAt: "2020-02-27 09:26:02",
152 | },
153 | {
154 | id: "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55",
155 | idNumber: 1382,
156 | firstName: "Dniren",
157 | lastName: "Pidwell",
158 | username: "dniren.pidwell.1382",
159 | email: "dpidwell9@hexun.com",
160 | contactNumber: "311-548-0007",
161 | address: "39 Meadow Ridge Alley",
162 | medicalRecord: "da16d247-a252-4abc-9692-82167f84c9e2",
163 | isDeleted: false,
164 | createdAt: "2019-09-16 09:43:44",
165 | updatedAt: "2019-06-21 00:51:37",
166 | },
167 | ],
168 | users_auth: [
169 | {
170 | id: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
171 | idNumber: 1767,
172 | username: "leodora.crouch.1767",
173 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
174 | role_id: "rolesEnum.ADMINISTRATOR.id",
175 | isDeleted: false,
176 | createdAt: "2020-01-22 14:00:18",
177 | updatedAt: "2020-02-27 20:11:39",
178 | },
179 | {
180 | id: "832b0c00-7b1e-4a4d-8c0d-39bdfb40f05c",
181 | idNumber: 3469,
182 | username: "hayden.haseman.3469",
183 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
184 | role_id: "rolesEnum.MEDIC.id",
185 | isDeleted: false,
186 | createdAt: "2020-02-03 08:45:34",
187 | updatedAt: "2019-12-31 18:44:32",
188 | },
189 | {
190 | id: "795b5699-7ca9-4344-8fe5-4a4f501678a9",
191 | idNumber: 5582,
192 | username: "basile.matteini.5582",
193 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
194 | role_id: "rolesEnum.BACTERIOLOGIST.id",
195 | isDeleted: true,
196 | createdAt: "2019-10-10 02:05:12",
197 | updatedAt: "2020-02-03 00:16:37",
198 | },
199 | {
200 | id: "1cffa615-e1d8-4113-b585-60603f0854fb",
201 | idNumber: 3123,
202 | username: "hersh.oloman.3123",
203 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
204 | role_id: "rolesEnum.PACIENT.id",
205 | isDeleted: false,
206 | createdAt: "2019-10-18 22:22:15",
207 | updatedAt: "2019-10-27 06:28:39",
208 | },
209 | {
210 | id: "0a96b420-ba2b-4741-b290-c916d92abdc0",
211 | idNumber: 1011,
212 | username: "ursula.cowen.1011",
213 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
214 | role_id: "rolesEnum.ADMINISTRATOR.id",
215 | isDeleted: true,
216 | createdAt: "2019-05-23 01:10:13",
217 | updatedAt: "2019-11-11 16:03:54",
218 | },
219 | {
220 | id: "b9df98a3-f23d-4711-b710-59efe5387921",
221 | idNumber: 7897,
222 | username: "norry.vockins.7897",
223 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
224 | role_id: "rolesEnum.MEDIC.id",
225 | isDeleted: false,
226 | createdAt: "2020-02-02 06:11:16",
227 | updatedAt: "2019-08-20 00:41:38",
228 | },
229 | {
230 | id: "ec8b28a6-d23b-42c3-a7bd-c9921c55d6cc",
231 | idNumber: 5839,
232 | username: "hort.mantram.5839",
233 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
234 | role_id: "rolesEnum.BACTERIOLOGIST.id",
235 | isDeleted: true,
236 | createdAt: "2019-10-12 12:44:17",
237 | updatedAt: "2020-04-14 12:24:05",
238 | },
239 | {
240 | id: "9d7f71ff-49fe-4819-859c-727ce7f44be3",
241 | idNumber: 6214,
242 | username: "christie.baelde.6214",
243 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
244 | role_id: "rolesEnum.PACIENT.id",
245 | isDeleted: false,
246 | createdAt: "2019-12-08 08:22:59",
247 | updatedAt: "2020-05-12 06:13:23",
248 | },
249 | {
250 | id: "c0f557a2-da71-4974-930f-17e52935bc7a",
251 | idNumber: 1325,
252 | username: "byram.doblin.1325",
253 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
254 | role_id: "rolesEnum.PACIENT.id",
255 | isDeleted: true,
256 | createdAt: "2020-01-02 01:26:33",
257 | updatedAt: "2020-02-27 09:26:02",
258 | },
259 | {
260 | id: "5a12fd8d-24f5-4327-a40a-3b1ff02c8d55",
261 | idNumber: 1382,
262 | username: "dniren.pidwell.1382",
263 | password: "$2b$10$ZpVyOQe2bRJFYC3NujyBg.CyRcytVp.d6UmNYJ/A7CN9jacsRJ05G",
264 | role_id: "rolesEnum.PACIENT.id",
265 | isDeleted: false,
266 | createdAt: "2019-09-16 09:43:44",
267 | updatedAt: "2019-06-21 00:51:37",
268 | },
269 | ],
270 | roles: [
271 | {
272 | ADMINISTRATOR: {
273 | id: "d436e99b-afea-44cf-a31c-ff35b7740c67",
274 | name: "administrator",
275 | description: "An administrator Role",
276 | isDeleted: false,
277 | createdAt: "2020-05-15 00:00:00",
278 | updatedAt: "2020-05-15 00:00:00",
279 | },
280 | MEDIC: {
281 | id: "a81bd60a-b09e-4936-b74a-22b823e39464",
282 | name: "medic",
283 | description: "A medic Role",
284 | isDeleted: false,
285 | createdAt: "2020-05-15 00:00:00",
286 | updatedAt: "2020-05-15 00:00:00",
287 | },
288 | BACTERIOLOGIST: {
289 | id: "bf90b55d-0e69-4f31-8211-514049a42625",
290 | name: "bacteriologist",
291 | description: "A bacteriologist Role",
292 | isDeleted: false,
293 | createdAt: "2020-05-15 00:00:00",
294 | updatedAt: "2020-05-15 00:00:00",
295 | },
296 | PACIENT: {
297 | id: "9511dfc8-e17e-4c8e-927e-cb933f9b77f6",
298 | name: "pacient",
299 | description: "A pacient Role",
300 | isDeleted: false,
301 | createdAt: "2020-05-15 00:00:00",
302 | updatedAt: "2020-05-15 00:00:00",
303 | },
304 | },
305 | ],
306 | exams: [
307 | {
308 | id: 1,
309 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
310 | examDate: "2020-02-14",
311 | examName: "Exámen de Orina",
312 | examDescription: "Lorem Ipsum",
313 | examStatus: "Referenciado",
314 | },
315 | {
316 | id: 2,
317 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
318 | examDate: "2020-02-14",
319 | examName: "Exámen de Sangre",
320 | examDescription: "Lorem Ipsum",
321 | examStatus: "Pendiente",
322 | },
323 | {
324 | id: 3,
325 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
326 | examDate: "2020-02-10",
327 | examName: "Exámen de Glucosa",
328 | examDescription: "Lorem Ipsum",
329 | examStatus: "Con Resultados",
330 | },
331 | {
332 | id: 4,
333 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
334 | examDate: "2020-01-01",
335 | examName: "Exámen de Colesterol",
336 | examDescription: "Lorem Ipsum",
337 | examStatus: "Referenciado",
338 | },
339 | {
340 | id: 5,
341 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
342 | examDate: "2020-02-21",
343 | examName: "Exámen Glóbulos blancos",
344 | examDescription: "Lorem Ipsum",
345 | examStatus: "Pendiente",
346 | },
347 | {
348 | id: 6,
349 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
350 | examDate: "2020-02-14",
351 | examName: "Exámen de Sangre",
352 | examDescription: "Lorem Ipsum",
353 | examStatus: "Referenciado",
354 | },
355 | {
356 | id: 7,
357 | userId: "018a5472-bf8e-4889-a3a2-ebdd4722d6b2",
358 | examDate: "2020-01-18",
359 | examName: "Exámen de Plaquetas",
360 | examDescription: "Lorem Ipsum",
361 | examStatus: "Con Resultados",
362 | },
363 | ],
364 | };
365 |
366 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
367 | const store = createStore(reducer, initialState, composeEnhancers());
368 |
369 | ReactDOM.render(
370 |
371 |
372 | ,
373 | document.getElementById("app")
374 | );
375 |
376 | ReactDOM.render(, container);
377 |
--------------------------------------------------------------------------------