├── .gitignore ├── MUI-table.gif ├── README.md ├── app.yaml ├── bin └── www ├── client ├── .eslintrc ├── analyze.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── Components │ │ ├── ComponentHeader.js │ │ ├── Department │ │ │ ├── AddNewDepartment.js │ │ │ ├── DepartmentList.js │ │ │ ├── EditDepartment.js │ │ │ ├── SearchFilterDepartment.js │ │ │ ├── Snackbars │ │ │ │ ├── EditItemConfirmSnackbar.js │ │ │ │ ├── EmptyFieldSnackBar.js │ │ │ │ └── NewItemAddedConfirmSnackbar.js │ │ │ ├── TableHeadDepartment.js │ │ │ └── TableToolbarDepartment.js │ │ ├── Employee │ │ │ ├── AddNewEmployee.js │ │ │ ├── CurrentMonthEmployees.js │ │ │ ├── DateRangeFilterEmployees.js │ │ │ ├── EditEmployees.js │ │ │ ├── EmployeeList.js │ │ │ ├── SearchFilterEmployees.js │ │ │ ├── ShowEmployees.js │ │ │ ├── Snackbars │ │ │ │ ├── EditItemConfirmSnackbar.js │ │ │ │ ├── EmptyFieldSnackBar.js │ │ │ │ ├── NewItemAddedConfirmSnackbar.js │ │ │ │ ├── NoRecordForDateRangeQuerySnackbar.js │ │ │ │ └── WrongDateRangeSnackBar.js │ │ │ ├── TableHeadEmployees.js │ │ │ ├── TableToolbarEmployees.js │ │ │ └── __test__ │ │ │ │ ├── EmployeeList.test.js │ │ │ │ └── __snapshots__ │ │ │ │ └── BerthThroughputList.test.js.snap │ │ ├── LandingPage.js │ │ ├── NotFound.css │ │ ├── NotFound.js │ │ ├── UtilFunctions │ │ │ ├── MySnackbarContent.js │ │ │ ├── TablePaginationActionsWrapped-bkp.js │ │ │ ├── TablePaginationActionsWrapped.js │ │ │ ├── confirmDelete.css │ │ │ ├── showEmptyFieldAndDeleteSnackbar.js │ │ │ ├── snackbar.css │ │ │ └── tableSortByHeading.js │ │ └── commonStyles │ │ │ ├── AddNewItemStyles.js │ │ │ ├── AddNewItemThemes.js │ │ │ ├── BootstrapInput.js │ │ │ ├── ModuleItemListStyles.js │ │ │ ├── SearchFilter-InputField.css │ │ │ ├── SearchFilter-Styles.js │ │ │ ├── combineStyles.js │ │ │ └── toolbarStyles.js │ ├── Routes.js │ ├── assets │ │ ├── images │ │ │ ├── 404.svg │ │ │ ├── LandingPage │ │ │ │ ├── code-1.jpeg │ │ │ │ ├── code-2.jpeg │ │ │ │ ├── code-3.jpeg │ │ │ │ └── code-4.jpeg │ │ │ ├── PageNotFound │ │ │ │ └── PageNotFound.jpg │ │ │ ├── SVG-Icons │ │ │ │ └── baseline-people_outline-24px.svg │ │ │ ├── astronaut.svg │ │ │ ├── bg_purple.png │ │ │ ├── earth.svg │ │ │ ├── logo.png │ │ │ ├── moon.svg │ │ │ ├── overlay_stars.svg │ │ │ └── rocket.svg │ │ └── js │ │ │ ├── docs.min.js │ │ │ ├── ie10-viewport-bug-workaround.js │ │ │ └── vendor │ │ │ └── jquery.min.js │ ├── history.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── serviceWorker.js └── yarn.lock ├── config ├── config.js ├── dev.js ├── keys.js ├── prod.js └── settings.js ├── index.js ├── models ├── department.js └── employee.js ├── package-lock.json ├── package.json ├── routes ├── createAllocationLodash.js ├── departmentRoutes.js └── employeeRoutes.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | */node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /client/build 12 | */build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .env 21 | .env.override 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # Ignore docs files 28 | _gh_pages 29 | .ruby-version 30 | 31 | # Numerous always-ignore extensions 32 | *.diff 33 | *.err 34 | *.orig 35 | *.log 36 | *.rej 37 | *.swo 38 | *.swp 39 | *.zip 40 | *.vi 41 | *~ 42 | *.~lock* 43 | .~lock* 44 | 45 | # OS or Editor folders 46 | .DS_Store 47 | ._* 48 | Thumbs.db 49 | .cache 50 | .project 51 | .settings 52 | .tmproj 53 | *.esproj 54 | nbproject 55 | *.sublime-project 56 | *.sublime-workspace 57 | .idea 58 | 59 | # Komodo 60 | *.komodoproject 61 | .komodotools 62 | 63 | # grunt-html-validation 64 | validation-status.json 65 | validation-report.json 66 | 67 | # Folders to ignore 68 | node_modules 69 | Project-Note-PAUL 70 | .vscode 71 | 72 | # Ignore all logfiles and tempfiles. 73 | !/log/.keep 74 | /tmp 75 | /.gems 76 | 77 | -------------------------------------------------------------------------------- /MUI-table.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/MUI-table.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Live Demo running in [Google Cloud Platform](https://steel-aileron-266311.appspot.com) 2 | 3 | 4 | 5 | ### A quite exhaustive React, Node/Express, MongoDB App for rendering tabular data with pagination with full Create, Edit, Delete functionality. Also, have implemented filter functionality for text-based search and date-range based search. Have used Material-UI extensively across the app. Functinality to download the data in `.csv` format. 6 | 7 | ##### Quite a few standard and simple tests have also been implemented with `jest` 8 | 9 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 10 | 11 | ### To launch this project in the local machine. 12 | 13 | run `npm install` in both the server directory, which in this project is the root directory (`./`) and also `./client` directories separately, which will install all the npm packages for server and client respectively. 14 | 15 | Then, start mongodb service with `sudo service mongod start` and then finally run the following command from inside the `server` directory. 16 | 17 | `npm run dev` 18 | 19 | This will start both the client (port 3000) and server (port 8080) and launch the site in port 3000. 20 | 21 | #### To build the project for production and serve the React static files from `/client/build` (i.e. do these steps before deploying to Google Cloud Platform's Google App Engine) 22 | 23 | ``` 24 | cd client 25 | 26 | npm run build 27 | 28 | cd .. 29 | 30 | ``` 31 | 32 | And check that everything is running properly 33 | 34 | ``` 35 | npm start 36 | 37 | ``` 38 | 39 | And then finally deploy to Google Cloud 40 | 41 | ``` 42 | gcloud app deploy --stop-previous-version 43 | 44 | ``` 45 | 46 | ### Other Commands 47 | 48 | #### `npm test` 49 | 50 | Launches the test runner in the interactive watch mode.
51 | 52 | #### To check the bundle size of overall app and various packages of the Client 53 | 54 | ``cd client`` and then run 55 | 56 | ``npm run analyze`` -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | runtime: nodejs 2 | env: flex 3 | 4 | # https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml 5 | # This sample incurs costs to run on the App Engine flexible environment. 6 | # The settings below are to reduce costs during testing and are not appropriate 7 | # for production use. For more information, see: 8 | # https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml 9 | manual_scaling: 10 | instances: 1 11 | 12 | resources: 13 | cpu: .5 14 | memory_gb: 0.5 15 | disk_size_gb: 10 16 | 17 | handlers: 18 | - url: / 19 | static_files: client/build/index.html 20 | upload: client/build/index.html 21 | - url: / 22 | static_dir: client/build 23 | # manual_scaling - Lock instances to 1 . using App Engine Flexible will create VM instances to serve from. If you want to lower your usage, you might restrict your App to just one 1 instance (probably not what you want in production, but perfect for in prototyping and development cases ). 24 | # https://stackoverflow.com/questions/37381694/google-app-engine-how-to-correctly-deploy-an-app 25 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* The below four line To deal with env variable thats already set in the machine - sometime my local machine is taking AWS kyes and secret keys something else than what is set in the .env file 4 | 5 | https://github.com/motdotla/dotenv#what-happens-to-environment-variables-that-were-already-set 6 | "We will never modify any environment variables that have already been set. In particular, if there is a variable in your .env file which collides with one that already exists in your environment, then that variable will be skipped. This behavior allows you to override all .env configurations with a machine-specific environment, although it is not recommended." 7 | */ 8 | // require("dotenv").config(); 9 | const fs = require("fs"); 10 | // const dotenv = require("dotenv"); 11 | require('dotenv').config() 12 | // const envConfig = dotenv.parse(fs.readFileSync(".env.override")); 13 | // for (let k in envConfig) { 14 | // process.env[k] = envConfig[k]; 15 | // } 16 | 17 | // require('dotenv').config(); 18 | var app = require('../app'); 19 | var debug = require('debug')('mean-app:server'); 20 | var http = require('http'); 21 | // const path = require("path"); 22 | 23 | // Get port from environment and store in Express. 24 | var port = normalizePort(process.env.PORT || '8080'); 25 | app.set('port', port); 26 | 27 | 28 | // Create HTTP server. 29 | var server = http.createServer(app); 30 | 31 | // Listen on provided port, on all network interfaces. 32 | server.listen(port); 33 | server.on('error', onError); 34 | server.on('listening', onListening); 35 | 36 | // Normalize a port into a number, string, or false. 37 | function normalizePort(val) { 38 | var port = parseInt(val, 10); 39 | 40 | if (isNaN(port)) { 41 | // named pipe 42 | return val; 43 | } 44 | 45 | if (port >= 0) { 46 | // port number 47 | return port; 48 | } 49 | return false; 50 | } 51 | 52 | 53 | // Event listener for HTTP server "error" event. 54 | function onError(error) { 55 | if (error.syscall !== 'listen') { 56 | console.log(error); 57 | } 58 | 59 | var bind = typeof port === 'string' 60 | ? 'Pipe ' + port 61 | : 'Port ' + port; 62 | 63 | // handle specific listen errors with friendly messages 64 | switch (error.code) { 65 | case 'EACCES': 66 | console.error(bind + ' requires elevated privileges'); 67 | process.exit(1); 68 | break; 69 | case 'EADDRINUSE': 70 | console.error(bind + ' is already in use'); 71 | process.exit(1); 72 | break; 73 | default: 74 | console.log(error); 75 | } 76 | } 77 | 78 | 79 | // Event listener for HTTP server "listening" event. 80 | function onListening() { 81 | var addr = server.address(); 82 | var bind = typeof addr === 'string' 83 | ? 'pipe ' + addr 84 | : 'port ' + addr.port; 85 | debug('Listening on ' + bind); 86 | } 87 | -------------------------------------------------------------------------------- /client/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "react-app" 4 | } -------------------------------------------------------------------------------- /client/analyze.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'production'; 2 | 3 | const webpack = require('webpack'); 4 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 5 | const webpackConfigProd = require('react-scripts/config/webpack.config')('production'); 6 | 7 | // this one is optional, just for better feedback on build 8 | const chalk = require('chalk'); 9 | const ProgressBarPlugin = require('progress-bar-webpack-plugin'); 10 | const green = text => { 11 | return chalk.green.bold(text); 12 | }; 13 | 14 | // pushing BundleAnalyzerPlugin to plugins array 15 | webpackConfigProd.plugins.push(new BundleAnalyzerPlugin()); 16 | 17 | // optional - pushing progress-bar plugin for better feedback; 18 | // it can and will work without progress-bar, 19 | // but during build time you will not see any messages for 10-60 seconds (depends on the size of the project) 20 | // and decide that compilation is kind of hang up on you; progress bar shows nice progression of webpack compilation 21 | webpackConfigProd.plugins.push( 22 | new ProgressBarPlugin({ 23 | format: `${green('analyzing...')} ${green('[:bar]')}${green('[:percent]')}${green('[:elapsed seconds]')} - :msg`, 24 | }), 25 | ); 26 | 27 | // actually running compilation and waiting for plugin to start explorer 28 | webpack(webpackConfigProd, (err, stats) => { 29 | if (err || stats.hasErrors()) { 30 | console.error(err); 31 | } 32 | }); -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend-portal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://localhost:8080", 6 | "eslintConfig": { 7 | "extends": "react-app", 8 | "root": true 9 | }, 10 | "dependencies": { 11 | "@date-io/date-fns": "1.1.0", 12 | "@fortawesome/fontawesome-svg-core": "^1.2.14", 13 | "@fortawesome/free-solid-svg-icons": "^5.7.1", 14 | "@fortawesome/react-fontawesome": "^0.1.4", 15 | "@material-ui/core": "^3.9.2", 16 | "@material-ui/icons": "^3.0.2", 17 | "axios": "^0.18.0", 18 | "bootstrap": "^4.2.1", 19 | "chart.js": "^2.7.3", 20 | "classnames": "^2.2.6", 21 | "date-fns": "2.0.0-alpha.26", 22 | "eslint": "5.12.0", 23 | "eslint-config-airbnb": "^17.1.0", 24 | "eslint-plugin-import": "^2.16.0", 25 | "eslint-plugin-jsx-a11y": "^6.2.1", 26 | "eslint-plugin-react": "^7.12.4", 27 | "file-loader": "^3.0.1", 28 | "history": "^4.7.2", 29 | "i": "^0.3.6", 30 | "lodash": "^4.17.11", 31 | "material-ui": "^0.20.2", 32 | "material-ui-pickers": "^2.2.1", 33 | "nock": "^10.0.6", 34 | "npm": "^6.7.0", 35 | "prettier-eslint-cli": "^4.7.1", 36 | "prettier-stylelint": "^0.4.2", 37 | "prop-types": "^15.6.2", 38 | "react": "^16.8.1", 39 | "react-chartkick": "^0.3.0", 40 | "react-confirm-alert": "^2.1.0", 41 | "react-csv": "^1.1.1", 42 | "react-dom": "^16.8.1", 43 | "react-helmet": "^5.2.0", 44 | "react-icons": "^3.3.0", 45 | "react-material-ui-form-validator": "^2.0.3", 46 | "react-paginate": "^6.2.1", 47 | "react-required-if": "^1.0.3", 48 | "react-router-dom": "^4.3.1", 49 | "react-scripts": "^2.1.5", 50 | "react-select": "^2.3.0", 51 | "react-social-icons": "^4.1.0", 52 | "react-vis": "^1.11.6", 53 | "react-welcome-page": "^0.2.1", 54 | "reactstrap": "^7.1.0", 55 | "sinon": "^7.2.4", 56 | "styled-components": "^4.1.3" 57 | }, 58 | "scripts": { 59 | "start": "react-scripts start", 60 | "build": "react-scripts build", 61 | "test": "react-scripts test --env=jsdom", 62 | "eject": "react-scripts eject", 63 | "analyze": "node ./analyze.js", 64 | "count": "cloc src/" 65 | }, 66 | "browserslist": [ 67 | ">0.2%", 68 | "not dead", 69 | "not ie <= 11", 70 | "not op_mini all" 71 | ], 72 | "devDependencies": { 73 | "cloc": "^2.5.1", 74 | "enzyme": "^3.9.0", 75 | "enzyme-adapter-react-16": "^1.9.1", 76 | "progress-bar-webpack-plugin": "^2.1.0", 77 | "react-test-renderer": "^16.8.3", 78 | "webpack-bundle-analyzer": "^3.6.0" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | React App 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Route, BrowserRouter, Switch, Router, Link } from "react-router-dom"; 3 | // import Routes from "./Routes"; 4 | import { createMuiTheme, MuiThemeProvider } from "@material-ui/core"; 5 | import history from "./history"; 6 | import axios from "axios"; 7 | import { library } from "@fortawesome/fontawesome-svg-core"; 8 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 9 | import { 10 | faFilter, 11 | faDownload, 12 | faCalendarAlt 13 | } from "@fortawesome/free-solid-svg-icons"; 14 | import EmployeeList from "./Components/Employee/EmployeeList"; 15 | import DepartmentList from "./Components/Department/DepartmentList"; 16 | import NotFound from "./Components/NotFound"; 17 | import ComponentHeader from "./Components/ComponentHeader"; 18 | import LandingPage from "./Components/LandingPage"; 19 | 20 | library.add(faFilter, faDownload, faCalendarAlt); 21 | 22 | const theme = createMuiTheme({ 23 | typography: { 24 | useNextVariants: true 25 | } 26 | }); 27 | 28 | class App extends Component { 29 | state = { 30 | allDepartmentsForSiblingCommunication: [], 31 | isSideDrawerOpen: false 32 | }; 33 | 34 | onSideDrawerOpen = () => { 35 | this.setState({ 36 | isSideDrawerOpen: true 37 | }); 38 | }; 39 | 40 | onSideDrawerClose = () => { 41 | this.setState({ 42 | isSideDrawerOpen: false 43 | }); 44 | }; 45 | 46 | componentDidMount() { 47 | axios 48 | .get("/api/department") 49 | .then(res => { 50 | this.setState({ 51 | allDepartmentsForSiblingCommunication: res.data 52 | }); 53 | }) 54 | .catch(error => {}); 55 | } 56 | 57 | setDepartmentForSiblingCommunication = department => { 58 | this.setState({ 59 | allDepartmentsForSiblingCommunication: department 60 | }); 61 | }; 62 | 63 | render() { 64 | const { allDepartmentsForSiblingCommunication } = this.state; 65 | 66 | const pad = 16; 67 | const appBarHeight = 74; 68 | const drawerWidth = 240; 69 | 70 | const left = this.state.isSideDrawerOpen ? drawerWidth : 85; 71 | const top = appBarHeight; 72 | 73 | const width = this.state.isSideDrawerOpen 74 | ? "calc(100% - " + (drawerWidth + 2 * pad) + "px)" 75 | : "calc(100% - " + 2 * (pad + 50) + "px)"; 76 | 77 | const contentStyle = { 78 | width: width, 79 | marginTop: top + pad, 80 | marginLeft: left + pad, 81 | marginBottom: pad, 82 | marginRight: pad, 83 | 84 | padding: 0 85 | }; 86 | 87 | return ( 88 |
89 | 90 | 91 |
92 | 96 |
97 | 98 | } /> 99 | ( 103 | 109 | )} 110 | /> 111 | ( 115 | 121 | )} 122 | /> 123 | 124 | 125 |
126 |
127 |
128 |
129 |
130 | ); 131 | } 132 | } 133 | 134 | export default App; 135 | -------------------------------------------------------------------------------- /client/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /client/src/Components/ComponentHeader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import classNames from "classnames"; 4 | import { withStyles } from "@material-ui/core/styles"; 5 | import Drawer from "@material-ui/core/Drawer"; 6 | import AppBar from "@material-ui/core/AppBar"; 7 | import Toolbar from "@material-ui/core/Toolbar"; 8 | import List from "@material-ui/core/List"; 9 | import CssBaseline from "@material-ui/core/CssBaseline"; 10 | import Typography from "@material-ui/core/Typography"; 11 | import Divider from "@material-ui/core/Divider"; 12 | import IconButton from "@material-ui/core/IconButton"; 13 | import MenuIcon from "@material-ui/icons/Menu"; 14 | import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; 15 | import ChevronRightIcon from "@material-ui/icons/ChevronRight"; 16 | import ListItem from "@material-ui/core/ListItem"; 17 | import ListItemIcon from "@material-ui/core/ListItemIcon"; 18 | import ListItemText from "@material-ui/core/ListItemText"; 19 | import InboxIcon from "@material-ui/icons/MoveToInbox"; 20 | import MailIcon from "@material-ui/icons/Mail"; 21 | import SvgIcon from '@material-ui/core/SvgIcon'; 22 | import red from '@material-ui/core/colors/red'; 23 | 24 | const drawerWidth = 240; 25 | 26 | const styles = theme => ({ 27 | root: { 28 | display: "flex" 29 | }, 30 | appBar: { 31 | zIndex: theme.zIndex.drawer + 1, 32 | transition: theme.transitions.create(["width", "margin"], { 33 | easing: theme.transitions.easing.sharp, 34 | duration: theme.transitions.duration.leavingScreen 35 | }) 36 | }, 37 | appBarShift: { 38 | marginLeft: drawerWidth, 39 | width: `calc(100% - ${drawerWidth}px)`, 40 | transition: theme.transitions.create(["width", "margin"], { 41 | easing: theme.transitions.easing.sharp, 42 | duration: theme.transitions.duration.enteringScreen 43 | }) 44 | }, 45 | icon: { 46 | margin: theme.spacing.unit * 1 47 | 48 | }, 49 | menuButton: { 50 | marginLeft: 12, 51 | marginRight: 36 52 | }, 53 | hide: { 54 | display: "none" 55 | }, 56 | drawer: { 57 | width: drawerWidth, 58 | flexShrink: 0, 59 | whiteSpace: "nowrap" 60 | }, 61 | drawerOpen: { 62 | width: drawerWidth, 63 | transition: theme.transitions.create("width", { 64 | easing: theme.transitions.easing.sharp, 65 | duration: theme.transitions.duration.enteringScreen 66 | }) 67 | }, 68 | drawerClose: { 69 | transition: theme.transitions.create("width", { 70 | easing: theme.transitions.easing.sharp, 71 | duration: theme.transitions.duration.leavingScreen 72 | }), 73 | overflowX: "hidden", 74 | width: theme.spacing.unit * 7 + 1, 75 | [theme.breakpoints.up("sm")]: { 76 | width: theme.spacing.unit * 9 + 1 77 | } 78 | }, 79 | toolbar: { 80 | display: "flex", 81 | alignItems: "center", 82 | justifyContent: "flex-end", 83 | padding: "0 8px", 84 | ...theme.mixins.toolbar 85 | }, 86 | content: { 87 | flexGrow: 1, 88 | padding: theme.spacing.unit * 3 89 | } 90 | }); 91 | 92 | const ListItemLink = props => { 93 | return ; 94 | }; 95 | 96 | const EmployeeIcon = props => { 97 | return ( 98 | 99 | 100 | 101 | ); 102 | } 103 | 104 | const DepartmentIcon = props => { 105 | return ( 106 | 107 | 108 | 109 | ); 110 | } 111 | 112 | class ComponentHeader extends React.Component { 113 | state = { 114 | open: false 115 | }; 116 | 117 | handleDrawerOpen = () => { 118 | this.setState({ open: true }); 119 | this.props.onSideDrawerOpen(); 120 | }; 121 | 122 | handleDrawerClose = () => { 123 | this.setState({ open: false }); 124 | this.props.onSideDrawerClose(); 125 | }; 126 | 127 | render() { 128 | const { classes, theme } = this.props; 129 | 130 | return ( 131 |
132 | 133 | 139 | 140 | 148 | 149 | 150 | 151 | Corporate Records 152 | 153 | 154 | 155 | 169 |
170 | 171 | {theme.direction === "rtl" ? ( 172 | 173 | ) : ( 174 | 175 | )} 176 | 177 |
178 | 179 | 180 | 181 | 184 | 185 | 186 | 187 | 188 | 189 | 192 | 193 | 194 | 195 | 196 | {["All mail", "Trash", "Spam"].map((text, index) => ( 197 | 198 | 199 | {index % 2 === 0 ? : } 200 | 201 | 202 | 203 | ))} 204 | 205 |
206 |
207 | ); 208 | } 209 | } 210 | 211 | ComponentHeader.propTypes = { 212 | classes: PropTypes.object.isRequired, 213 | theme: PropTypes.object.isRequired 214 | }; 215 | 216 | export default withStyles(styles, { withTheme: true })(ComponentHeader); 217 | -------------------------------------------------------------------------------- /client/src/Components/Department/AddNewDepartment.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import axios from "axios"; 3 | import PropTypes from "prop-types"; 4 | import history from "../../history"; 5 | import Button from "@material-ui/core/Button"; 6 | import TextField from "@material-ui/core/TextField"; 7 | import Dialog from "@material-ui/core/Dialog"; 8 | import DialogActions from "@material-ui/core/DialogActions"; 9 | import DialogContent from "@material-ui/core/DialogContent"; 10 | import DialogTitle from "@material-ui/core/DialogTitle"; 11 | import { MuiThemeProvider } from "@material-ui/core/styles"; 12 | import Fab from "@material-ui/core/Fab"; 13 | import { withStyles } from "@material-ui/core"; 14 | import AddIcon from "@material-ui/icons/Add"; 15 | import { styles } from "../commonStyles/AddNewItemStyles"; 16 | import theme from "../commonStyles/AddNewItemThemes"; 17 | import EmptyFieldSnackBar from "./Snackbars/EmptyFieldSnackBar"; 18 | import NewItemAddedConfirmSnackbar from "./Snackbars/NewItemAddedConfirmSnackbar"; 19 | import IconButton from "@material-ui/core/IconButton"; 20 | import CancelIcon from "@material-ui/icons/Cancel"; 21 | 22 | class AddNewDepartment extends Component { 23 | state = { 24 | open: false, 25 | openNewItemAddedConfirmSnackbar: false, 26 | openEmptyTextFieldSnackbar: false, 27 | vertical: "top", 28 | horizontal: "center", 29 | name: "", 30 | type: "" 31 | }; 32 | 33 | handleToggle = () => { 34 | this.setState({ 35 | open: !this.state.open 36 | }); 37 | }; 38 | 39 | handleFabOpen = () => { 40 | this.setState({ open: true }); 41 | }; 42 | 43 | closeNewItemConfirmSnackbar = () => { 44 | this.setState({ openNewItemAddedConfirmSnackbar: false }); 45 | }; 46 | 47 | closeEmptyFieldSnackbar = () => { 48 | this.setState({ openEmptyTextFieldSnackbar: false }); 49 | }; 50 | 51 | onChange = e => { 52 | const state = this.state; 53 | state[e.target.name] = e.target.value; 54 | this.setState(state); 55 | }; 56 | 57 | handleFormSubmit = () => { 58 | const { addNewItemToParentState } = this.props; 59 | const { name, type } = this.state; 60 | if (name !== "" && type !== "") { 61 | axios 62 | .post("/api/department/", { 63 | name, 64 | type 65 | }) 66 | .then(() => { 67 | addNewItemToParentState({ 68 | name, 69 | type 70 | }); 71 | this.setState( 72 | { 73 | open: false, 74 | openNewItemAddedConfirmSnackbar: true, 75 | vertical: "top", 76 | horizontal: "center" 77 | }, 78 | () => { 79 | history.push("/department"); 80 | } 81 | ); 82 | }) 83 | .catch(error => { 84 | console.log("THE ERROR RESPONSE IS ", error.response); 85 | if ( 86 | error.response && 87 | error.response.data.name === "MongoError" && 88 | (error.response && error.response.data.code === 11000) 89 | ) { 90 | alert( 91 | "Duplicate Department Name! please select another name for the Department" 92 | ); 93 | } else { 94 | alert( 95 | "Ooops something wrong happened while adding new item, please try again" 96 | ); 97 | } 98 | }); 99 | } else { 100 | this.setState({ openEmptyTextFieldSnackbar: true }); 101 | } 102 | }; 103 | 104 | handleCancel = () => { 105 | this.setState({ open: false }); 106 | }; 107 | 108 | handleEnterEscapeKeyPress = e => { 109 | if (e.key === "Enter") { 110 | this.handleFormSubmit(); 111 | } else if (e.key === "Escape") { 112 | this.handleCancel(); 113 | } 114 | }; 115 | 116 | render() { 117 | const { classes } = this.props; 118 | const { name, type } = this.state; 119 | 120 | return ( 121 | 122 |
123 | 131 | 132 | 133 | 147 | 153 | New Department 154 | 158 | 159 | 160 | 161 | 162 | 170 | this.setState({ 171 | name: e.target.value 172 | }) 173 | } 174 | error={name === ""} 175 | helperText={name === "" ? "Please enter Name" : " "} 176 | label="Name" 177 | type="string" 178 | fullWidth 179 | InputProps={{ 180 | classes: { 181 | underline: classes.underline 182 | } 183 | }} 184 | /> 185 | 194 | this.setState({ 195 | type: e.target.value 196 | }) 197 | } 198 | error={type === ""} 199 | helperText={ 200 | type === "" ? "Please enter Type of Department" : " " 201 | } 202 | label="Type of Department" 203 | type="string" 204 | fullWidth 205 | InputProps={{ 206 | classes: { 207 | underline: classes.underline 208 | } 209 | }} 210 | /> 211 | 212 | 218 | 231 | 244 | 245 | 246 | 252 | 253 | 257 |
258 |
259 | ); 260 | } 261 | } 262 | 263 | AddNewDepartment.propTypes = { 264 | classes: PropTypes.object.isRequired 265 | }; 266 | 267 | export default withStyles(styles)(AddNewDepartment); 268 | 269 | // The Cancel button color - https://material-ui.com/customization/overrides/ 270 | -------------------------------------------------------------------------------- /client/src/Components/Department/EditDepartment.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import axios from "axios"; 3 | import PropTypes from "prop-types"; 4 | import history from "../../history"; 5 | import Button from "@material-ui/core/Button"; 6 | import TextField from "@material-ui/core/TextField"; 7 | import Dialog from "@material-ui/core/Dialog"; 8 | import DialogActions from "@material-ui/core/DialogActions"; 9 | import DialogContent from "@material-ui/core/DialogContent"; 10 | import DialogTitle from "@material-ui/core/DialogTitle"; 11 | import { MuiThemeProvider } from "@material-ui/core/styles"; 12 | import { withStyles } from "@material-ui/core"; 13 | import EditIcon from "@material-ui/icons/Edit"; 14 | import { styles } from "../commonStyles/AddNewItemStyles"; 15 | import theme from "../commonStyles/AddNewItemThemes"; 16 | import EmptyFieldSnackBar from "./Snackbars/EmptyFieldSnackBar"; 17 | import EditItemConfirmSnackbar from "./Snackbars/EditItemConfirmSnackbar"; 18 | import IconButton from "@material-ui/core/IconButton"; 19 | import CancelIcon from "@material-ui/icons/Cancel"; 20 | 21 | class EditDepartment extends Component { 22 | state = { 23 | open: false, 24 | openEditItemConfirmSnackbar: false, 25 | openEmptyTextFieldSnackbar: false, 26 | vertical: "top", 27 | horizontal: "center", 28 | name: this.props.departmentToEdit[0].name, 29 | type: this.props.departmentToEdit[0].type, 30 | arrowRef: null 31 | }; 32 | 33 | handleArrowRef = node => { 34 | this.setState({ 35 | arrowRef: node 36 | }); 37 | }; 38 | 39 | handleToggle = () => { 40 | this.setState({ 41 | open: !this.state.open 42 | }); 43 | }; 44 | 45 | handleFabOpen = () => { 46 | this.setState({ open: true }); 47 | }; 48 | 49 | closeNewItemConfirmSnackbar = () => { 50 | this.setState({ openEditItemConfirmSnackbar: false }); 51 | this.props.unSelectItems(); 52 | }; 53 | 54 | closeEmptyFieldSnackbar = () => { 55 | this.setState({ openEmptyTextFieldSnackbar: false }, () => {}); 56 | }; 57 | 58 | handleEditFormSubmit = () => { 59 | const { editItemToParentState } = this.props; 60 | const { name, type } = this.state; 61 | if (name !== "" && type !== "") { 62 | axios 63 | .put(`/api/department/${this.props.departmentToEdit[0]._id}`, { 64 | name, 65 | type 66 | }) 67 | .then(() => { 68 | editItemToParentState(); 69 | this.setState( 70 | { 71 | open: false, 72 | openEditItemConfirmSnackbar: true, 73 | vertical: "top", 74 | horizontal: "center" 75 | }, 76 | () => { 77 | history.push("/department"); 78 | } 79 | ); 80 | }) 81 | .catch(error => { 82 | alert( 83 | "Ooops something wrong happened while editing, please try again" 84 | ); 85 | }); 86 | } else { 87 | this.setState({ openEmptyTextFieldSnackbar: true }); 88 | } 89 | }; 90 | 91 | handleCancel = () => { 92 | this.setState({ open: false }); 93 | }; 94 | 95 | handleEnterEscapeKeyPress = e => { 96 | if (e.key === "Enter") { 97 | this.handleEditFormSubmit(); 98 | } else if (e.key === "Escape") { 99 | this.handleCancel(); 100 | } 101 | }; 102 | 103 | render() { 104 | const { classes } = this.props; 105 | const { name, type } = this.state; 106 | 107 | return ( 108 | 109 |
110 | 111 | 112 | 113 | 127 | 132 | Edit Department 133 | 137 | 138 | 139 | 140 | 141 | 149 | this.setState({ 150 | name: e.target.value 151 | }) 152 | } 153 | error={name === ""} 154 | helperText={name === "" ? "Please enter Name" : " "} 155 | label="Name" 156 | type="email" 157 | fullWidth 158 | /> 159 | 168 | this.setState({ 169 | type: e.target.value 170 | }) 171 | } 172 | error={type === ""} 173 | helperText={type === "" ? "Please enter Type" : " "} 174 | label="Type" 175 | type="email" 176 | fullWidth 177 | /> 178 | 179 | 185 | 196 | 208 | 209 | 210 | 214 | 215 | 219 |
220 |
221 | ); 222 | } 223 | } 224 | 225 | EditDepartment.propTypes = { 226 | classes: PropTypes.object.isRequired 227 | }; 228 | 229 | export default withStyles(styles)(EditDepartment); 230 | 231 | /* The Cancel button's color was done initially implementing MUI override - https://material-ui.com/customization/overrides/ 232 | But later changed to regular inline style, as I was not able to differentiate the coloring the Cancel button with Save 233 | */ 234 | -------------------------------------------------------------------------------- /client/src/Components/Department/SearchFilterDepartment.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import Select from "react-select"; 4 | import { withStyles } from "@material-ui/core/styles"; 5 | import Typography from "@material-ui/core/Typography"; 6 | import TextField from "@material-ui/core/TextField"; 7 | import Paper from "@material-ui/core/Paper"; 8 | import MenuItem from "material-ui/MenuItem"; 9 | import styles from "../commonStyles/SearchFilter-Styles.js"; 10 | import toolbarStyles from "../commonStyles/toolbarStyles"; 11 | import combineStyles from "../commonStyles/combineStyles"; 12 | import SelectField from "material-ui/SelectField"; 13 | import IconButton from "@material-ui/core/IconButton"; 14 | import CancelIcon from "@material-ui/icons/Cancel"; 15 | import "../commonStyles/SearchFilter-InputField.css"; 16 | import Grid from "@material-ui/core/Grid"; 17 | import Tooltip from "@material-ui/core/Tooltip"; 18 | 19 | const NoOptionsMessage = props => { 20 | return ( 21 | 26 | {props.children} 27 | 28 | ); 29 | }; 30 | 31 | const inputComponent = ({ inputRef, ...props }) => { 32 | return
; 33 | }; 34 | 35 | const Control = props => { 36 | return ( 37 | 56 | ); 57 | }; 58 | 59 | const Option = props => { 60 | return ( 61 | 70 | {props.children} 71 | 72 | ); 73 | }; 74 | 75 | const Placeholder = props => { 76 | return ( 77 | 82 | {props.children} 83 | 84 | ); 85 | }; 86 | 87 | const SingleValue = props => { 88 | return ( 89 | 93 | {props.children} 94 | 95 | ); 96 | }; 97 | 98 | const ValueContainer = props => { 99 | return ( 100 |
101 | {props.children} 102 |
103 | ); 104 | }; 105 | 106 | const Menu = props => { 107 | return ( 108 | 113 | {props.children} 114 | 115 | ); 116 | }; 117 | 118 | const components = { 119 | Control, 120 | Menu, 121 | NoOptionsMessage, 122 | Option, 123 | Placeholder, 124 | SingleValue, 125 | ValueContainer 126 | }; 127 | 128 | class SearchFilter extends React.Component { 129 | state = { 130 | query: "", 131 | columnToQuery: "name", 132 | arrowRef: null 133 | }; 134 | 135 | handleArrowRef = node => { 136 | this.setState({ 137 | arrowRef: node 138 | }); 139 | }; 140 | 141 | render() { 142 | const { columnToQuery } = this.state; 143 | 144 | // conditionally set the value of 'suggestions' with and IIFE 145 | const suggestions = (() => { 146 | switch (columnToQuery) { 147 | case "name": 148 | return this.props.allDepartments.map(item => ({ 149 | value: item.name, 150 | label: item.name 151 | })); 152 | case "type": 153 | return this.props.allDepartments.map(item => ({ 154 | value: item.type, 155 | label: item.type 156 | })); 157 | } 158 | })(); 159 | 160 | const { 161 | classes, 162 | theme, 163 | value, 164 | onChange, 165 | inputValue, 166 | onInputChange 167 | } = this.props; 168 | 169 | const selectStyles = { 170 | input: base => ({ 171 | ...base, 172 | width: "20em", 173 | marginTop: "20px", 174 | color: theme.palette.text.primary, 175 | "& input": { 176 | font: "inherit" 177 | } 178 | }) 179 | }; 180 | 181 | return ( 182 | 195 | 202 | 203 | 226 | 233 | this.setState({ columnToQuery: value }, () => { 234 | this.props.handleColumnToQuery(this.state.columnToQuery); 235 | }) 236 | } 237 | > 238 | 239 | 243 | 244 | 245 | 246 | 247 | 250 |

Clear the Search Filter

251 | 252 | 253 | } 254 | placement="top-end" 255 | classes={{ 256 | tooltip: classes.bootstrapTooltip, 257 | popper: classes.bootstrapPopper, 258 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 259 | tooltipPlacementRight: classes.bootstrapPlacementRight, 260 | tooltipPlacementTop: classes.bootstrapPlacementTop, 261 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 262 | }} 263 | PopperProps={{ 264 | popperOptions: { 265 | modifiers: { 266 | arrow: { 267 | enabled: Boolean(this.state.arrowRef), 268 | element: this.state.arrowRef 269 | } 270 | } 271 | } 272 | }} 273 | onOpen={() => 274 | this.props.setTextFilterTooltip("Clear the Search Filter") 275 | } 276 | > 277 | 285 | 286 | 287 |
288 |
289 |
290 |
291 | ); 292 | } 293 | } 294 | 295 | SearchFilter.propTypes = { 296 | classes: PropTypes.object.isRequired, 297 | theme: PropTypes.object.isRequired 298 | }; 299 | 300 | const combinedStyles = combineStyles(styles, toolbarStyles); 301 | 302 | export default withStyles(combinedStyles, { withTheme: true })(SearchFilter); 303 | -------------------------------------------------------------------------------- /client/src/Components/Employee/ShowEmployees.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import PropTypes from "prop-types"; 3 | import TextField from "@material-ui/core/TextField"; 4 | import Dialog from "@material-ui/core/Dialog"; 5 | import DialogContent from "@material-ui/core/DialogContent"; 6 | import DialogTitle from "@material-ui/core/DialogTitle"; 7 | import { MuiThemeProvider } from "@material-ui/core/styles"; 8 | import { withStyles } from "@material-ui/core"; 9 | import VisibilityIcon from "@material-ui/icons/Visibility"; 10 | import { MuiPickersUtilsProvider, DatePicker } from "material-ui-pickers"; 11 | import DateFnsUtils from "@date-io/date-fns"; 12 | import { styles } from "../commonStyles/AddNewItemStyles"; 13 | import theme from "../commonStyles/AddNewItemThemes"; 14 | import IconButton from "@material-ui/core/IconButton"; 15 | import CancelIcon from "@material-ui/icons/Cancel"; 16 | 17 | class ShowEmployee extends Component { 18 | state = { 19 | open: false 20 | }; 21 | 22 | handleToggle = () => { 23 | this.setState({ 24 | open: !this.state.open 25 | }); 26 | }; 27 | 28 | handleFabOpen = () => { 29 | this.setState({ open: true }); 30 | }; 31 | 32 | handleCancel = () => { 33 | this.setState({ open: false }); 34 | }; 35 | 36 | handleEscapeKeyPress = e => { 37 | if (e.key === "Escape") { 38 | this.handleCancel(); 39 | } 40 | }; 41 | 42 | render() { 43 | const { classes } = this.props; 44 | 45 | const employee_name = 46 | this.props.itemToEdit[0] && this.props.itemToEdit[0].employee_name; 47 | const work_description = 48 | this.props.itemToEdit[0] && this.props.itemToEdit[0].work_description; 49 | 50 | const avg_employee_productivity = 51 | this.props.itemToEdit[0] && 52 | this.props.itemToEdit[0].avg_employee_productivity; 53 | const benchmark_employee_productivity = 54 | this.props.itemToEdit[0] && 55 | this.props.itemToEdit[0].benchmark_employee_productivity; 56 | const department_name = 57 | this.props.itemToEdit[0] && this.props.itemToEdit[0].department_name; 58 | const date = this.props.itemToEdit[0] && this.props.itemToEdit[0].date; 59 | 60 | return ( 61 | 62 | 63 |
64 | 65 | 66 | 67 | 75 | 79 | Details of this Employees 80 | 84 | 85 | 86 | 87 | 88 | 109 | 124 | 140 | 156 | 173 |
178 |
179 | 186 | value 187 | ? [ 188 | /\d/, 189 | /\d/, 190 | "/", 191 | /\d/, 192 | /\d/, 193 | "/", 194 | /\d/, 195 | /\d/, 196 | /\d/, 197 | /\d/ 198 | ] 199 | : [] 200 | } 201 | label="Date of Employment" 202 | value={date} 203 | disableOpenOnEnter 204 | animateYearScrolling={false} 205 | style={{ paddingRight: "5px" }} 206 | /> 207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 | ); 215 | } 216 | } 217 | 218 | ShowEmployee.propTypes = { 219 | classes: PropTypes.object.isRequired 220 | }; 221 | 222 | export default withStyles(styles)(ShowEmployee); 223 | -------------------------------------------------------------------------------- /client/src/Components/Employee/Snackbars/EditItemConfirmSnackbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Snackbar from "@material-ui/core/Snackbar"; 3 | import MySnackbarContent from "../../UtilFunctions/MySnackbarContent"; 4 | import { withStyles } from "@material-ui/core"; 5 | import { styles } from "../../commonStyles/AddNewItemStyles"; 6 | import PropTypes from "prop-types"; 7 | 8 | class EditItemConfirmSnackbar extends Component { 9 | state = { 10 | vertical: "top", 11 | horizontal: "center" 12 | }; 13 | 14 | render() { 15 | const { 16 | classes, 17 | openNewItemAddedConfirmSnackbar, 18 | closeNewItemConfirmSnackbar 19 | } = this.props; 20 | return ( 21 | 33 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | EditItemConfirmSnackbar.propTypes = { 45 | classes: PropTypes.object.isRequired 46 | }; 47 | 48 | export default withStyles(styles)(EditItemConfirmSnackbar); 49 | -------------------------------------------------------------------------------- /client/src/Components/Employee/Snackbars/EmptyFieldSnackBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Snackbar from "@material-ui/core/Snackbar"; 3 | import MySnackbarContent from "../../UtilFunctions/MySnackbarContent"; 4 | import { withStyles } from "@material-ui/core"; 5 | import { styles } from "../../commonStyles/AddNewItemStyles"; 6 | import PropTypes from "prop-types"; 7 | 8 | class EmptyFieldSnackBar extends Component { 9 | state = { 10 | vertical: "top", 11 | horizontal: "center" 12 | }; 13 | 14 | render() { 15 | const { 16 | classes, 17 | closeEmptyFieldSnackbar, 18 | openEmptyTextFieldSnackbar 19 | } = this.props; 20 | return ( 21 | 33 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | EmptyFieldSnackBar.propTypes = { 45 | classes: PropTypes.object.isRequired 46 | }; 47 | 48 | export default withStyles(styles)(EmptyFieldSnackBar); 49 | -------------------------------------------------------------------------------- /client/src/Components/Employee/Snackbars/NewItemAddedConfirmSnackbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Snackbar from "@material-ui/core/Snackbar"; 3 | import MySnackbarContent from "../../UtilFunctions/MySnackbarContent"; 4 | import { withStyles } from "@material-ui/core"; 5 | import { styles } from "../../commonStyles/AddNewItemStyles"; 6 | import PropTypes from "prop-types"; 7 | 8 | class NewItemAddedConfirmSnackbar extends Component { 9 | state = { 10 | vertical: "top", 11 | horizontal: "center" 12 | }; 13 | 14 | render() { 15 | const { 16 | classes, 17 | openNewItemAddedConfirmSnackbar, 18 | closeNewItemConfirmSnackbar 19 | } = this.props; 20 | return ( 21 | 34 | 40 | 41 | ); 42 | } 43 | } 44 | 45 | NewItemAddedConfirmSnackbar.propTypes = { 46 | classes: PropTypes.object.isRequired 47 | }; 48 | 49 | export default withStyles(styles)(NewItemAddedConfirmSnackbar); 50 | -------------------------------------------------------------------------------- /client/src/Components/Employee/Snackbars/NoRecordForDateRangeQuerySnackbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Snackbar from "@material-ui/core/Snackbar"; 3 | import MySnackbarContent from "../../UtilFunctions/MySnackbarContent"; 4 | import { withStyles } from "@material-ui/core"; 5 | import { styles } from "../../commonStyles/AddNewItemStyles"; 6 | import PropTypes from "prop-types"; 7 | 8 | class NoRecordForDateRangeQuerySnackbar extends Component { 9 | state = { 10 | vertical: "top", 11 | horizontal: "center" 12 | }; 13 | 14 | render() { 15 | const { 16 | classes, 17 | openNoRecordForDateRangeQuery, 18 | closeNoRecordForDateRangeQuery 19 | } = this.props; 20 | return ( 21 | 33 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | NoRecordForDateRangeQuerySnackbar.propTypes = { 45 | classes: PropTypes.object.isRequired 46 | }; 47 | 48 | export default withStyles(styles)(NoRecordForDateRangeQuerySnackbar); 49 | -------------------------------------------------------------------------------- /client/src/Components/Employee/Snackbars/WrongDateRangeSnackBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Snackbar from "@material-ui/core/Snackbar"; 3 | import MySnackbarContent from "../../UtilFunctions/MySnackbarContent"; 4 | import { withStyles } from "@material-ui/core"; 5 | import { styles } from "../../commonStyles/AddNewItemStyles"; 6 | import PropTypes from "prop-types"; 7 | 8 | class WrongDateRangeSnackBar extends Component { 9 | state = { 10 | vertical: "top", 11 | horizontal: "center" 12 | }; 13 | 14 | render() { 15 | const { 16 | classes, 17 | closeWrongDateRangeSnackBar, 18 | openWrongDateRangeSnackBar 19 | } = this.props; 20 | return ( 21 | 33 | 39 | 40 | ); 41 | } 42 | } 43 | 44 | WrongDateRangeSnackBar.propTypes = { 45 | classes: PropTypes.object.isRequired 46 | }; 47 | 48 | export default withStyles(styles)(WrongDateRangeSnackBar); 49 | -------------------------------------------------------------------------------- /client/src/Components/Employee/TableHeadEmployees.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import TableCell from "@material-ui/core/TableCell"; 4 | import TableHead from "@material-ui/core/TableHead"; 5 | import TableRow from "@material-ui/core/TableRow"; 6 | import TableSortLabel from "@material-ui/core/TableSortLabel"; 7 | import Checkbox from "@material-ui/core/Checkbox"; 8 | import Tooltip from "@material-ui/core/Tooltip"; 9 | import { styles } from "../commonStyles/ModuleItemListStyles"; 10 | import toolbarStyles from "../commonStyles/toolbarStyles"; 11 | import combineStyles from "../commonStyles/combineStyles"; 12 | import { withStyles } from "@material-ui/core"; 13 | 14 | const rows = [ 15 | { 16 | tableHeaderProp: "department_name", 17 | disablePadding: true, 18 | label: "Department" 19 | }, 20 | { 21 | tableHeaderProp: "employee_name", 22 | disablePadding: true, 23 | label: "Employee Name" 24 | }, 25 | { 26 | tableHeaderProp: "work_description", 27 | disablePadding: true, 28 | label: "Work Description" 29 | }, 30 | { 31 | tableHeaderProp: "avg_employee_productivity", 32 | disablePadding: false, 33 | label: "Avg. Employee Productivity" 34 | }, 35 | { 36 | tableHeaderProp: "benchmark_employee_productivity", 37 | disablePadding: false, 38 | label: "Benchmark Employee Productivity" 39 | }, 40 | 41 | { 42 | tableHeaderProp: "date", 43 | disablePadding: false, 44 | label: "Date of Employment" 45 | } 46 | ]; 47 | 48 | class TableHeadEmployee extends React.Component { 49 | state = { 50 | arrowRef: null 51 | }; 52 | 53 | // function to handle the placement of the arrow on top of the Tooltip 54 | handleArrowRef = node => { 55 | this.setState({ 56 | arrowRef: node 57 | }); 58 | }; 59 | 60 | createSortHandler = property => event => { 61 | this.props.onRequestSort(event, property); 62 | }; 63 | 64 | render() { 65 | const { 66 | onSelectAllClick, 67 | order, 68 | orderBy, 69 | numSelected, 70 | page, 71 | rowsPerPage, 72 | count, 73 | noOfItemsInCurrentPage 74 | } = this.props; 75 | 76 | const { classes } = this.props; 77 | 78 | return ( 79 | 80 | 81 | 84 |

85 | {noOfItemsInCurrentPage > 1 86 | ? `Select all ${noOfItemsInCurrentPage}` 87 | : `Select the item`} 88 |

89 | 90 | 91 | } 92 | enterDelay={300} 93 | placement={"top-end"} 94 | classes={{ 95 | tooltip: classes.bootstrapTooltip, 96 | popper: classes.bootstrapPopper, 97 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 98 | tooltipPlacementRight: classes.bootstrapPlacementRight, 99 | tooltipPlacementTop: classes.bootstrapPlacementTop, 100 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 101 | }} 102 | PopperProps={{ 103 | popperOptions: { 104 | modifiers: { 105 | arrow: { 106 | enabled: Boolean(this.state.arrowRef), 107 | element: this.state.arrowRef 108 | } 109 | } 110 | } 111 | }} 112 | > 113 | 114 | 0 && numSelected < rowsPerPage 118 | : numSelected > 0 && numSelected < noOfItemsInCurrentPage 119 | } 120 | checked={ 121 | noOfItemsInCurrentPage === 0 122 | ? false 123 | : page !== Math.max(0, Math.ceil(count / rowsPerPage) - 1) 124 | ? numSelected === rowsPerPage 125 | : noOfItemsInCurrentPage < rowsPerPage 126 | ? numSelected === noOfItemsInCurrentPage 127 | : numSelected === rowsPerPage 128 | } 129 | onChange={onSelectAllClick} 130 | /> 131 | 132 |
133 | {rows.map( 134 | row => ( 135 | 142 | 145 |

Sort by {row.label}

146 | 150 | 151 | } 152 | enterDelay={300} 153 | placement={"top-end"} 154 | classes={{ 155 | tooltip: classes.bootstrapTooltip, 156 | popper: classes.bootstrapPopper, 157 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 158 | tooltipPlacementRight: classes.bootstrapPlacementRight, 159 | tooltipPlacementTop: classes.bootstrapPlacementTop, 160 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 161 | }} 162 | PopperProps={{ 163 | popperOptions: { 164 | modifiers: { 165 | arrow: { 166 | enabled: Boolean(this.state.arrowRef), 167 | element: this.state.arrowRef 168 | } 169 | } 170 | } 171 | }} 172 | > 173 | 178 | {row.label} 179 | 180 |
181 |
182 | ), 183 | this 184 | )} 185 |
186 |
187 | ); 188 | } 189 | } 190 | 191 | TableHeadEmployee.propTypes = { 192 | numSelected: PropTypes.number.isRequired, 193 | onRequestSort: PropTypes.func.isRequired, 194 | onSelectAllClick: PropTypes.func.isRequired, 195 | order: PropTypes.string.isRequired, 196 | orderBy: PropTypes.string.isRequired, 197 | rowsPerPage: PropTypes.number.isRequired, 198 | classes: PropTypes.object.isRequired 199 | }; 200 | 201 | const combinedStyles = combineStyles(styles, toolbarStyles); 202 | 203 | export default withStyles(combinedStyles)(TableHeadEmployee); 204 | 205 | /* The expression < Math.max(0, Math.ceil(count / rowsPerPage) - 1) > is the index value of the variable 'page' ONLY when I am on the last page. So the condition //#endregion 206 | < page === Math.max(0, Math.ceil(count / rowsPerPage) - 1) > WILL ONLY BE TRUE on the last page. 207 | 208 | Note, - The variable 'page' is a built-in prop of TablePagination API (https://material-ui.com/api/table-pagination/) - and the value of 'page' is a zero-based index of the current page. 209 | */ 210 | -------------------------------------------------------------------------------- /client/src/Components/Employee/__test__/EmployeeList.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import expect from "expect"; 4 | import { mount, render } from "enzyme"; 5 | import { createShallow } from "@material-ui/core/test-utils"; 6 | import sinon from "sinon"; 7 | import { Helmet } from "react-helmet"; 8 | import Paper from "@material-ui/core/Paper"; 9 | import axios from "axios"; 10 | import nock from "nock"; 11 | import TableRow from "@material-ui/core/TableRow"; 12 | import TableHeadEmployee from "../TableHeadEmployees"; 13 | import TableToolbarEmployee from "../TableToolbarEmployees"; 14 | import AddNewEmployee from "../AddNewEmployee"; 15 | import EmployeeList from "../EmployeeList"; 16 | // const moment = require("moment"); 17 | // import Table from "@material-ui/core/Table"; 18 | // import TableBody from "@material-ui/core/TableBody"; 19 | // import TableCell from "@material-ui/core/TableCell"; 20 | // import IconButton from "@material-ui/core/IconButton"; 21 | // import { Icon } from "@material-ui/core"; 22 | 23 | const host = "http://localhost:3000"; 24 | 25 | const mockEmployeeData = [ 26 | { 27 | _id: "5c793b825b9fac072bab18c8", 28 | department_objectId: { 29 | _id: "5c6c0f839c84ea3c7194a7dd", 30 | name: "Coal", 31 | type: "Minerals", 32 | createdAt: "2019-02-19T14:15:31.140Z", 33 | updatedAt: "2019-02-19T14:15:31.140Z", 34 | __v: 0 35 | }, 36 | avg_employee_productivity: 2, 37 | benchmark_employee_productivity: 1, 38 | date: "2019-03-03T00:00:00.000Z", 39 | createdAt: "2019-03-01T14:02:42.177Z", 40 | updatedAt: "2019-03-03T08:48:11.678Z", 41 | __v: 0 42 | }, 43 | { 44 | _id: "5c7937665b9fac072bab18c6", 45 | department_objectId: { 46 | _id: "5c651f71ddff9f780e4cdbcd", 47 | name: "Platinum", 48 | type: "Precious Metals", 49 | createdAt: "2019-02-14T07:57:37.736Z", 50 | updatedAt: "2019-02-28T12:53:50.876Z", 51 | __v: 0 52 | }, 53 | avg_employee_productivity: 11, 54 | benchmark_employee_productivity: 111, 55 | date: "2019-03-01T13:43:56.249Z", 56 | createdAt: "2019-03-01T13:45:10.027Z", 57 | updatedAt: "2019-03-01T13:45:10.027Z", 58 | __v: 0 59 | } 60 | ]; 61 | 62 | const departmentListProps = [ 63 | { 64 | _id: "5c6c0f969c84ea3c7194a7de", 65 | name: "Ash", 66 | type: "Industrial Material", 67 | createdAt: "2019-02-19T14:15:50.690Z", 68 | updatedAt: "2019-02-19T14:15:50.690Z", 69 | __v: 0 70 | }, 71 | { 72 | _id: "5c6c0f839c84ea3c7194a7dd", 73 | name: "Coal", 74 | type: "Minerals", 75 | createdAt: "2019-02-19T14:15:31.140Z", 76 | updatedAt: "2019-02-19T14:15:31.140Z", 77 | __v: 0 78 | }, 79 | 80 | { 81 | _id: "5c651f44ddff9f780e4cdbcc", 82 | name: "Steel123", 83 | type: "Department", 84 | createdAt: "2019-02-14T07:56:52.310Z", 85 | updatedAt: "2019-02-27T11:05:55.121Z", 86 | __v: 0 87 | } 88 | ]; 89 | 90 | const initialState = { 91 | totalItemsUnformatted: [], 92 | totalItemsFormatted: [], 93 | order: "desc", 94 | orderBy: "date", 95 | selected: [], 96 | page: 0, 97 | rowsPerPage: 5, 98 | queryStringFromChild: "", 99 | columnToQuery: "department_name", 100 | itemsDateRangePaginated: [], 101 | totalDateRangeSearchResultParent: [], 102 | start_date: new Date(), 103 | end_date: new Date(), 104 | ifUserSearchedDateRange: false, 105 | ifUserClickedForCurrentMonth: false, 106 | currentMonthPaginated: [], 107 | currentMonthTotal: [], 108 | currentDatePaginated: [], 109 | currentDateTotal: [], 110 | arrowRef: null 111 | }; 112 | 113 | describe("", () => { 114 | const shallow = createShallow(); 115 | 116 | afterEach(() => { 117 | // if (!nock.isDone()) { 118 | // this.test.error(new Error('Not all nock interceptors were used!')); 119 | nock.cleanAll(); 120 | // nock.restore(); 121 | // } 122 | }); 123 | 124 | beforeEach(done => { 125 | if (!nock.isActive()) nock.activate(); 126 | done(); 127 | }); 128 | 129 | test("EmployeeList component renders", () => { 130 | const wrapper = shallow(); 131 | expect(wrapper.exists()).toBe(true); 132 | }); 133 | 134 | it("should renders without crashing", () => { 135 | const div = document.createElement("div"); 136 | ReactDOM.render( 137 | , 140 | div 141 | ); 142 | ReactDOM.unmountComponentAtNode(div); 143 | }); 144 | 145 | test("renders", () => { 146 | const wrapper = shallow( 147 | 151 | ); 152 | 153 | expect(wrapper).toMatchSnapshot(); 154 | }); 155 | 156 | test("returns the default empty array when there is no data to map through or what would happen when I render the component with no data", () => { 157 | const wrapper = shallow(); 158 | expect(wrapper).toMatchSnapshot(); 159 | }); 160 | 161 | test("doesn't break without list of data", () => { 162 | const wrapper = shallow(); 163 | expect(wrapper.find("li")).toHaveLength(0); 164 | }); 165 | 166 | test("doesn't break with an empty array", () => { 167 | const wrapper = shallow(); 168 | expect(wrapper.find("li")).toHaveLength(0); 169 | }); 170 | 171 | it("should render a placeholder", () => { 172 | const placeholder_text = "type anything here"; 173 | const wrapper = shallow(); 174 | expect(wrapper.prop("placeholder")).toEqual(placeholder_text); 175 | }); 176 | 177 | it("should render a correct type", () => { 178 | const type = "password"; 179 | const wrapper = shallow(); 180 | expect(wrapper.prop("type")).toEqual(type); 181 | }); 182 | 183 | it("renders one components", () => { 184 | const wrapper = shallow(); 185 | expect(wrapper.dive().find(TableHeadEmployee)).toHaveLength(1); 186 | }); 187 | 188 | it("renders one components", () => { 189 | const wrapper = shallow(); 190 | expect(wrapper.dive().find(TableToolbarEmployee)).toHaveLength(1); 191 | }); 192 | 193 | it("renders one components", () => { 194 | const wrapper = shallow(); 195 | expect(wrapper.dive().find(AddNewEmployee)).toHaveLength(1); 196 | }); 197 | 198 | it("renders the Helmet wrapper component", () => { 199 | const component = shallow(); 200 | const title = component 201 | .dive() 202 | .find(Helmet) 203 | .text(); 204 | 205 | expect(title).toBe(""); 206 | }); 207 | 208 | it("should render react-helment title", () => { 209 | const wrapper = mount( 210 | 213 | ); 214 | // this will return all the markup assigned to helmet 215 | // which will get rendered inside head. 216 | const helmet = Helmet.peek(); 217 | expect(helmet.title).toBe("MyCompany | Employee!"); 218 | // expect(helmet.metaTags[1].name).toBe("description"); 219 | // expect(helmet.metaTags[2].content).toBe("MyCompany List of Vessel Types!"); 220 | // console.log("HELMET IS ", helmet.content); 221 | }); 222 | 223 | it("calls componentDidMount", () => { 224 | sinon.spy(EmployeeList.prototype, "componentDidMount"); 225 | const wrapper = mount( 226 | 229 | ); 230 | expect(EmployeeList.prototype.componentDidMount).toHaveProperty( 231 | "callCount", 232 | 1 233 | ); 234 | }); 235 | 236 | it("allows us to set props", () => { 237 | const wrapper = mount( 238 | 242 | ); 243 | expect(wrapper.props().bar).toEqual("baz"); 244 | wrapper.setProps({ bar: "foo" }); 245 | expect(wrapper.props().bar).toEqual("foo"); 246 | }); 247 | 248 | it("should have the initial state set properly", () => { 249 | const wrapper = shallow(); 250 | expect(wrapper.dive().state()).toEqual(initialState); 251 | }); 252 | 253 | it("should render correct number of Paper and TableRow MUI component", () => { 254 | const component = mount( 255 | 258 | ); 259 | expect(component.find(Paper)).toHaveLength(2); 260 | expect(component.find(TableRow)).toHaveLength(3); 261 | }); 262 | 263 | // Test proptypes for allDepartmentsForSiblingCommunication 264 | it("check the type of allDepartmentsForSiblingCommunication", () => { 265 | const props = { 266 | allDepartmentsForSiblingCommunication: [ 267 | { 268 | _id: "5c6c0f969c84ea3c7194a7de", 269 | name: "Ash", 270 | type: "Industrial Material", 271 | createdAt: "2019-02-19T14:15:50.690Z", 272 | updatedAt: "2019-02-19T14:15:50.690Z", 273 | __v: 0 274 | } 275 | ] 276 | }; 277 | const wrapper = mount(); 278 | expect(wrapper.prop("allDepartmentsForSiblingCommunication")).toHaveLength( 279 | 1 280 | ); 281 | }); 282 | 283 | it("API test-1 - GET (/api/employee)", done => { 284 | nock(host) 285 | .defaultReplyHeaders({ 286 | "access-control-allow-origin": "*", 287 | "Content-Type": "application/json" 288 | }) 289 | 290 | .get("/api/employee") 291 | .reply(200, "Get data"); 292 | // .log(console.log); 293 | 294 | axios.get("http://localhost:3000/api/employee").then(response => { 295 | expect(response.data).toBe("Get data"); 296 | expect(typeof response.data).toBe("string"); 297 | done(); 298 | }); 299 | }); 300 | }); 301 | 302 | /* Shallow tests with 'withStyles' - needed to switch from `shallow` to `mount` since `withStyles` is a higher order component and 'wraps' the other component - (https://github.com/mui-org/material-ui/issues/9266) */ 303 | -------------------------------------------------------------------------------- /client/src/Components/LandingPage.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Welcome from "react-welcome-page"; 3 | 4 | // A functional component only for the landing page 5 | // The height & width has no effect though (I included for testing) 6 | const LandingPage = () => ({ 7 | render() { 8 | return ( 9 |
10 | 50 |
51 | ); 52 | } 53 | }); 54 | 55 | export default LandingPage; 56 | -------------------------------------------------------------------------------- /client/src/Components/NotFound.css: -------------------------------------------------------------------------------- 1 | /* The below google font is overwriting my fonts in main comopnent, hence commented out */ 2 | /* @import url('https://fonts.googleapis.com/css?family=Dosis:300,400,500'); */ 3 | 4 | @-moz-keyframes rocket-movement { 5 | 100% { 6 | -moz-transform: translate(1200px, -600px); 7 | } 8 | } 9 | 10 | @-webkit-keyframes rocket-movement { 11 | 100% { 12 | -webkit-transform: translate(1200px, -600px); 13 | } 14 | } 15 | 16 | @keyframes rocket-movement { 17 | 100% { 18 | transform: translate(1200px, -600px); 19 | } 20 | } 21 | 22 | @-moz-keyframes spin-earth { 23 | 100% { 24 | -moz-transform: rotate(-360deg); 25 | transition: transform 20s; 26 | } 27 | } 28 | 29 | @-webkit-keyframes spin-earth { 30 | 100% { 31 | -webkit-transform: rotate(-360deg); 32 | transition: transform 20s; 33 | } 34 | } 35 | 36 | @keyframes spin-earth { 37 | 100% { 38 | -webkit-transform: rotate(-360deg); 39 | transform: rotate(-360deg); 40 | transition: transform 20s; 41 | } 42 | } 43 | 44 | @-moz-keyframes move-astronaut { 45 | 100% { 46 | -moz-transform: translate(-160px, -160px); 47 | } 48 | } 49 | 50 | @-webkit-keyframes move-astronaut { 51 | 100% { 52 | -webkit-transform: translate(-160px, -160px); 53 | } 54 | } 55 | 56 | @keyframes move-astronaut { 57 | 100% { 58 | -webkit-transform: translate(-160px, -160px); 59 | transform: translate(-160px, -160px); 60 | } 61 | } 62 | 63 | @-moz-keyframes rotate-astronaut { 64 | 100% { 65 | -moz-transform: rotate(-720deg); 66 | } 67 | } 68 | 69 | @-webkit-keyframes rotate-astronaut { 70 | 100% { 71 | -webkit-transform: rotate(-720deg); 72 | } 73 | } 74 | 75 | @keyframes rotate-astronaut { 76 | 100% { 77 | -webkit-transform: rotate(-720deg); 78 | transform: rotate(-720deg); 79 | } 80 | } 81 | 82 | @-moz-keyframes glow-star { 83 | 40% { 84 | -moz-opacity: 0.3; 85 | } 86 | 87 | 90%, 88 | 100% { 89 | -moz-opacity: 1; 90 | -moz-transform: scale(1.2); 91 | } 92 | } 93 | 94 | @-webkit-keyframes glow-star { 95 | 40% { 96 | -webkit-opacity: 0.3; 97 | } 98 | 99 | 90%, 100 | 100% { 101 | -webkit-opacity: 1; 102 | -webkit-transform: scale(1.2); 103 | } 104 | } 105 | 106 | @keyframes glow-star { 107 | 40% { 108 | -webkit-opacity: 0.3; 109 | opacity: 0.3; 110 | } 111 | 112 | 90%, 113 | 100% { 114 | -webkit-opacity: 1; 115 | opacity: 1; 116 | -webkit-transform: scale(1.2); 117 | transform: scale(1.2); 118 | border-radius: 999999px; 119 | } 120 | } 121 | 122 | .spin-earth-on-hover { 123 | 124 | transition: ease 200s !important; 125 | transform: rotate(-3600deg) !important; 126 | } 127 | 128 | html, 129 | body { 130 | margin: 0; 131 | width: 100%; 132 | height: 100%; 133 | font-family: 'Dosis', sans-serif; 134 | font-weight: 300; 135 | -webkit-user-select: none; 136 | /* Safari 3.1+ */ 137 | -moz-user-select: none; 138 | /* Firefox 2+ */ 139 | -ms-user-select: none; 140 | /* IE 10+ */ 141 | user-select: none; 142 | /* Standard syntax */ 143 | } 144 | 145 | .bg-purple { 146 | background: url("../assets/images/bg_purple.png"); 147 | background-repeat: repeat-x; 148 | background-size: cover; 149 | background-position: left top; 150 | height: 100vh; 151 | overflow: hidden; 152 | 153 | } 154 | 155 | /* --------------------------- */ 156 | 157 | 158 | .custom-navbar { 159 | padding-top: 15px; 160 | } 161 | 162 | .brand-logo { 163 | margin-left: 25px; 164 | margin-top: 5px; 165 | display: inline-block; 166 | } 167 | 168 | .navbar-links { 169 | display: inline-block; 170 | float: right; 171 | margin-right: 15px; 172 | text-transform: uppercase; 173 | 174 | 175 | } 176 | 177 | ul { 178 | list-style-type: none; 179 | margin: 0; 180 | padding: 0; 181 | align-items: center; 182 | } 183 | 184 | li { 185 | 186 | padding: 0px 15px; 187 | } 188 | 189 | li a { 190 | display: block; 191 | color: white; 192 | text-align: center; 193 | text-decoration: none; 194 | letter-spacing: 2px; 195 | font-size: 12px; 196 | 197 | -webkit-transition: all 0.3s ease-in; 198 | -moz-transition: all 0.3s ease-in; 199 | -ms-transition: all 0.3s ease-in; 200 | -o-transition: all 0.3s ease-in; 201 | transition: all 0.3s ease-in; 202 | } 203 | 204 | li a:hover { 205 | color: #ffcb39; 206 | } 207 | 208 | /* clear */ 209 | .btn-request { 210 | padding: 10px 25px; 211 | border: 1px solid #FFCB39; 212 | border-radius: 100px; 213 | font-weight: 400; 214 | } 215 | 216 | .btn-request:hover { 217 | background-color: #FFCB39; 218 | color: #fff; 219 | transform: scale(1.05); 220 | box-shadow: 0px 20px 20px rgba(0, 0, 0, 0.1); 221 | } 222 | 223 | .btn-go-home { 224 | position: relative; 225 | z-index: 200; 226 | margin: 15px auto; 227 | width: 100px; 228 | padding: 10px 15px; 229 | border: 1px solid #FFCB39; 230 | border-radius: 100px; 231 | font-weight: 400; 232 | display: block; 233 | color: white; 234 | text-align: center; 235 | text-decoration: none; 236 | letter-spacing: 2px; 237 | font-size: 11px; 238 | 239 | -webkit-transition: all 0.3s ease-in; 240 | -moz-transition: all 0.3s ease-in; 241 | -ms-transition: all 0.3s ease-in; 242 | -o-transition: all 0.3s ease-in; 243 | transition: all 0.3s ease-in; 244 | } 245 | 246 | .btn-go-home:hover { 247 | background-color: #FFCB39; 248 | color: #fff; 249 | transform: scale(1.05); 250 | box-shadow: 0px 20px 20px rgba(0, 0, 0, 0.1); 251 | } 252 | 253 | /* clear */ 254 | .central-body { 255 | /* width: 100%;*/ 256 | padding: 17% 5% 10% 5%; 257 | text-align: center; 258 | } 259 | 260 | .objects img { 261 | z-index: 90; 262 | pointer-events: none; 263 | } 264 | 265 | /* ============================= */ 266 | .object_rocket { 267 | z-index: 95; 268 | position: absolute; 269 | transform: translateX(-50px); 270 | top: 75%; 271 | pointer-events: none; 272 | animation: rocket-movement 200s linear infinite both running; 273 | } 274 | 275 | .object_earth { 276 | position: absolute; 277 | top: 20%; 278 | left: 15%; 279 | z-index: 90; 280 | /* animation: spin-earth 100s infinite linear both;*/ 281 | } 282 | 283 | .object_moon { 284 | position: absolute; 285 | top: 12%; 286 | left: 25%; 287 | /* 288 | transform: rotate(0deg); 289 | transition: transform ease-in 99999999999s; 290 | */ 291 | } 292 | 293 | .earth-moon {} 294 | 295 | .object_astronaut { 296 | animation: rotate-astronaut 200s infinite linear both alternate; 297 | } 298 | 299 | .box_astronaut { 300 | z-index: 110 !important; 301 | position: absolute; 302 | top: 60%; 303 | right: 20%; 304 | will-change: transform; 305 | animation: move-astronaut 50s infinite linear both alternate; 306 | } 307 | 308 | .image-404 { 309 | position: relative; 310 | z-index: 100; 311 | pointer-events: none; 312 | } 313 | 314 | .stars { 315 | background: url("../assets/images/overlay_stars.svg"); 316 | background-repeat: repeat; 317 | background-size: contain; 318 | background-position: left top; 319 | } 320 | 321 | .glowing_stars .star { 322 | position: absolute; 323 | border-radius: 100%; 324 | background-color: #fff; 325 | width: 3px; 326 | height: 3px; 327 | opacity: 0.3; 328 | will-change: opacity; 329 | } 330 | 331 | .glowing_stars .star:nth-child(1) { 332 | top: 80%; 333 | left: 25%; 334 | animation: glow-star 2s infinite ease-in-out alternate 1s; 335 | } 336 | 337 | .glowing_stars .star:nth-child(2) { 338 | top: 20%; 339 | left: 40%; 340 | animation: glow-star 2s infinite ease-in-out alternate 3s; 341 | } 342 | 343 | .glowing_stars .star:nth-child(3) { 344 | top: 25%; 345 | left: 25%; 346 | animation: glow-star 2s infinite ease-in-out alternate 5s; 347 | } 348 | 349 | .glowing_stars .star:nth-child(4) { 350 | top: 75%; 351 | left: 80%; 352 | animation: glow-star 2s infinite ease-in-out alternate 7s; 353 | } 354 | 355 | .glowing_stars .star:nth-child(5) { 356 | top: 90%; 357 | left: 50%; 358 | animation: glow-star 2s infinite ease-in-out alternate 9s; 359 | } 360 | 361 | @media only screen and (max-width: 600px) { 362 | .navbar-links { 363 | display: none; 364 | } 365 | 366 | .custom-navbar { 367 | text-align: center; 368 | } 369 | 370 | .brand-logo img { 371 | width: 120px; 372 | } 373 | 374 | .box_astronaut { 375 | top: 70%; 376 | } 377 | 378 | .central-body { 379 | padding-top: 25%; 380 | } 381 | } -------------------------------------------------------------------------------- /client/src/Components/NotFound.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import "./NotFound.css"; 4 | 5 | export default class NotFound extends Component { 6 | render() { 7 | return ( 8 |
9 |
10 |
11 |
12 | 16 |
17 |
18 |
    19 |
  • 20 | 21 | Home 22 | 23 |
  • 24 |
25 |
26 |
27 |
28 | 33 | 34 | Return to Home Page 35 | 36 |
37 |
38 | 43 |
44 | 49 | 54 |
55 |
56 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/src/Components/UtilFunctions/MySnackbarContent.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import classNames from "classnames"; 4 | import CheckCircleIcon from "@material-ui/icons/CheckCircle"; 5 | import ErrorIcon from "@material-ui/icons/Error"; 6 | import InfoIcon from "@material-ui/icons/Info"; 7 | import CloseIcon from "@material-ui/icons/Close"; 8 | import green from "@material-ui/core/colors/green"; 9 | import amber from "@material-ui/core/colors/amber"; 10 | import IconButton from "@material-ui/core/IconButton"; 11 | import SnackbarContent from "@material-ui/core/SnackbarContent"; 12 | import WarningIcon from "@material-ui/icons/Warning"; 13 | import { withStyles } from "@material-ui/core/styles"; 14 | 15 | const variantIcon = { 16 | success: CheckCircleIcon, 17 | warning: WarningIcon, 18 | error: ErrorIcon, 19 | info: InfoIcon 20 | }; 21 | 22 | const styles1 = theme => ({ 23 | close: { 24 | padding: theme.spacing.unit * 2 25 | }, 26 | success: { 27 | backgroundColor: green[600] 28 | }, 29 | error: { 30 | backgroundColor: theme.palette.error.dark 31 | }, 32 | info: { 33 | backgroundColor: theme.palette.primary.dark 34 | }, 35 | warning: { 36 | backgroundColor: amber[700] 37 | }, 38 | icon: { 39 | fontSize: 20 40 | }, 41 | iconVariant: { 42 | opacity: 0.9, 43 | marginRight: theme.spacing.unit 44 | }, 45 | message: { 46 | display: "flex", 47 | alignItems: "center" 48 | } 49 | }); 50 | 51 | function MySnackbarContent(props) { 52 | const { classes, className, message, onClose, variant, ...other } = props; 53 | const Icon = variantIcon[variant]; 54 | 55 | return ( 56 | 61 | 62 | {message} 63 | 64 | } 65 | action={[ 66 | 73 | 74 | 75 | ]} 76 | {...other} 77 | /> 78 | ); 79 | } 80 | 81 | MySnackbarContent.propTypes = { 82 | classes: PropTypes.object.isRequired, 83 | className: PropTypes.string, 84 | message: PropTypes.node, 85 | onClose: PropTypes.func, 86 | variant: PropTypes.oneOf(["success", "warning", "error", "info"]).isRequired 87 | }; 88 | 89 | export default withStyles(styles1)(MySnackbarContent); 90 | -------------------------------------------------------------------------------- /client/src/Components/UtilFunctions/TablePaginationActionsWrapped-bkp.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "@material-ui/core/styles"; 4 | import IconButton from "@material-ui/core/IconButton"; 5 | import FirstPageIcon from "@material-ui/icons/FirstPage"; 6 | import KeyboardArrowLeft from "@material-ui/icons/KeyboardArrowLeft"; 7 | import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"; 8 | import LastPageIcon from "@material-ui/icons/LastPage"; 9 | import toolbarStyles from "../commonStyles/toolbarStyles"; 10 | import Tooltip from "@material-ui/core/Tooltip"; 11 | 12 | const actionsStyles = theme => ({ 13 | root: { 14 | flexShrink: 0, 15 | color: theme.palette.text.secondary, 16 | marginLeft: theme.spacing.unit * 2.5 17 | } 18 | }); 19 | 20 | class TablePaginationActions extends React.Component { 21 | state = { 22 | arrowRef: null 23 | }; 24 | 25 | // function to handle the placement of the arrow on top of the Tooltip 26 | handleArrowRef = node => { 27 | this.setState({ 28 | arrowRef: node 29 | }); 30 | }; 31 | 32 | handleFirstPageButtonClick = event => { 33 | this.props.onChangePage(event, 0); 34 | }; 35 | 36 | handleBackButtonClick = event => { 37 | this.props.onChangePage(event, this.props.page - 1); 38 | }; 39 | 40 | handleNextButtonClick = event => { 41 | this.props.onChangePage(event, this.props.page + 1); 42 | }; 43 | 44 | handleLastPageButtonClick = event => { 45 | this.props.onChangePage( 46 | event, 47 | Math.max(0, Math.ceil(this.props.count / this.props.rowsPerPage) - 1) 48 | ); 49 | }; 50 | 51 | render() { 52 | const { classes, count, page, rowsPerPage, theme } = this.props; 53 | 54 | return ( 55 |
56 | 59 |

First page

60 | 61 | 62 | } 63 | placement="bottom-start" 64 | classes={{ 65 | tooltip: classes.bootstrapTooltip, 66 | popper: classes.bootstrapPopper, 67 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 68 | tooltipPlacementRight: classes.bootstrapPlacementRight, 69 | tooltipPlacementTop: classes.bootstrapPlacementTop, 70 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 71 | }} 72 | PopperProps={{ 73 | popperOptions: { 74 | modifiers: { 75 | arrow: { 76 | enabled: Boolean(this.state.arrowRef), 77 | element: this.state.arrowRef 78 | } 79 | } 80 | } 81 | }} 82 | > 83 | 88 | {theme.direction === "rtl" ? : } 89 | 90 |
91 | 94 |

Previous page

95 | 96 | 97 | } 98 | placement="bottom-start" 99 | classes={{ 100 | tooltip: classes.bootstrapTooltip, 101 | popper: classes.bootstrapPopper, 102 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 103 | tooltipPlacementRight: classes.bootstrapPlacementRight, 104 | tooltipPlacementTop: classes.bootstrapPlacementTop, 105 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 106 | }} 107 | PopperProps={{ 108 | popperOptions: { 109 | modifiers: { 110 | arrow: { 111 | enabled: Boolean(this.state.arrowRef), 112 | element: this.state.arrowRef 113 | } 114 | } 115 | } 116 | }} 117 | > 118 | 123 | {theme.direction === "rtl" ? ( 124 | 125 | ) : ( 126 | 127 | )} 128 | 129 |
130 | 133 |

Next page

134 | 135 | 136 | } 137 | placement="bottom-start" 138 | classes={{ 139 | tooltip: classes.bootstrapTooltip, 140 | popper: classes.bootstrapPopper, 141 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 142 | tooltipPlacementRight: classes.bootstrapPlacementRight, 143 | tooltipPlacementTop: classes.bootstrapPlacementTop, 144 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 145 | }} 146 | PopperProps={{ 147 | popperOptions: { 148 | modifiers: { 149 | arrow: { 150 | enabled: Boolean(this.state.arrowRef), 151 | element: this.state.arrowRef 152 | } 153 | } 154 | } 155 | }} 156 | > 157 | = Math.ceil(count / rowsPerPage) - 1} 160 | aria-label="Next Page" 161 | > 162 | {theme.direction === "rtl" ? ( 163 | 164 | ) : ( 165 | 166 | )} 167 | 168 |
169 | 172 |

Last page

173 | 174 | 175 | } 176 | placement="bottom-start" 177 | classes={{ 178 | tooltip: classes.bootstrapTooltip, 179 | popper: classes.bootstrapPopper, 180 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 181 | tooltipPlacementRight: classes.bootstrapPlacementRight, 182 | tooltipPlacementTop: classes.bootstrapPlacementTop, 183 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 184 | }} 185 | PopperProps={{ 186 | popperOptions: { 187 | modifiers: { 188 | arrow: { 189 | enabled: Boolean(this.state.arrowRef), 190 | element: this.state.arrowRef 191 | } 192 | } 193 | } 194 | }} 195 | > 196 | = Math.ceil(count / rowsPerPage) - 1} 199 | aria-label="Last Page" 200 | > 201 | {theme.direction === "rtl" ? : } 202 | 203 |
204 |
205 | ); 206 | } 207 | } 208 | 209 | TablePaginationActions.propTypes = { 210 | classes: PropTypes.object.isRequired, 211 | count: PropTypes.number.isRequired, 212 | onChangePage: PropTypes.func.isRequired, 213 | page: PropTypes.number.isRequired, 214 | rowsPerPage: PropTypes.number.isRequired, 215 | theme: PropTypes.object.isRequired 216 | }; 217 | 218 | // Make an HOC from the above TablePaginationActions component which will be exported and used in DevelopmentList.js 219 | const TablePaginationActionsWrapped = withStyles(actionsStyles, { 220 | withTheme: true 221 | })(TablePaginationActions); 222 | 223 | export default withStyles(toolbarStyles)(TablePaginationActionsWrapped); 224 | -------------------------------------------------------------------------------- /client/src/Components/UtilFunctions/TablePaginationActionsWrapped.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { withStyles } from "@material-ui/core/styles"; 4 | import IconButton from "@material-ui/core/IconButton"; 5 | import FirstPageIcon from "@material-ui/icons/FirstPage"; 6 | import KeyboardArrowLeft from "@material-ui/icons/KeyboardArrowLeft"; 7 | import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"; 8 | import LastPageIcon from "@material-ui/icons/LastPage"; 9 | import Tooltip from "@material-ui/core/Tooltip"; 10 | 11 | const actionsStyles = theme => ({ 12 | root: { 13 | flexShrink: 0, 14 | color: theme.palette.text.secondary, 15 | marginLeft: theme.spacing.unit * 2.5 16 | } 17 | }); 18 | 19 | class TablePaginationActions extends React.Component { 20 | state = { 21 | arrowRef: null 22 | }; 23 | 24 | // function to handle the placement of the arrow on top of the Tooltip 25 | handleArrowRef = node => { 26 | this.setState({ 27 | arrowRef: node 28 | }); 29 | }; 30 | 31 | handleFirstPageButtonClick = event => { 32 | this.props.onChangePage(event, 0); 33 | }; 34 | 35 | handleBackButtonClick = event => { 36 | this.props.onChangePage(event, this.props.page - 1); 37 | }; 38 | 39 | handleNextButtonClick = event => { 40 | this.props.onChangePage(event, this.props.page + 1); 41 | }; 42 | 43 | handleLastPageButtonClick = event => { 44 | this.props.onChangePage( 45 | event, 46 | Math.max(0, Math.ceil(this.props.count / this.props.rowsPerPage) - 1) 47 | ); 48 | }; 49 | 50 | render() { 51 | const { classes, count, page, rowsPerPage, theme } = this.props; 52 | 53 | return ( 54 |
55 | 58 |

First page

59 | 60 | 61 | } 62 | placement="bottom-start" 63 | classes={{ 64 | tooltip: classes.bootstrapTooltip, 65 | popper: classes.bootstrapPopper, 66 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 67 | tooltipPlacementRight: classes.bootstrapPlacementRight, 68 | tooltipPlacementTop: classes.bootstrapPlacementTop, 69 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 70 | }} 71 | PopperProps={{ 72 | popperOptions: { 73 | modifiers: { 74 | arrow: { 75 | enabled: Boolean(this.state.arrowRef), 76 | element: this.state.arrowRef 77 | } 78 | } 79 | } 80 | }} 81 | > 82 | 87 | {theme.direction === "rtl" ? : } 88 | 89 |
90 | 93 |

Previous page

94 | 95 | 96 | } 97 | placement="bottom-start" 98 | classes={{ 99 | tooltip: classes.bootstrapTooltip, 100 | popper: classes.bootstrapPopper, 101 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 102 | tooltipPlacementRight: classes.bootstrapPlacementRight, 103 | tooltipPlacementTop: classes.bootstrapPlacementTop, 104 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 105 | }} 106 | PopperProps={{ 107 | popperOptions: { 108 | modifiers: { 109 | arrow: { 110 | enabled: Boolean(this.state.arrowRef), 111 | element: this.state.arrowRef 112 | } 113 | } 114 | } 115 | }} 116 | > 117 | 122 | {theme.direction === "rtl" ? ( 123 | 124 | ) : ( 125 | 126 | )} 127 | 128 |
129 | 132 |

Next page

133 | 134 | 135 | } 136 | placement="bottom-start" 137 | classes={{ 138 | tooltip: classes.bootstrapTooltip, 139 | popper: classes.bootstrapPopper, 140 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 141 | tooltipPlacementRight: classes.bootstrapPlacementRight, 142 | tooltipPlacementTop: classes.bootstrapPlacementTop, 143 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 144 | }} 145 | PopperProps={{ 146 | popperOptions: { 147 | modifiers: { 148 | arrow: { 149 | enabled: Boolean(this.state.arrowRef), 150 | element: this.state.arrowRef 151 | } 152 | } 153 | } 154 | }} 155 | > 156 | = Math.ceil(count / rowsPerPage) - 1} 159 | aria-label="Next Page" 160 | > 161 | {theme.direction === "rtl" ? ( 162 | 163 | ) : ( 164 | 165 | )} 166 | 167 |
168 | 171 |

Last page

172 | 173 | 174 | } 175 | placement="bottom-start" 176 | classes={{ 177 | tooltip: classes.bootstrapTooltip, 178 | popper: classes.bootstrapPopper, 179 | tooltipPlacementLeft: classes.bootstrapPlacementLeft, 180 | tooltipPlacementRight: classes.bootstrapPlacementRight, 181 | tooltipPlacementTop: classes.bootstrapPlacementTop, 182 | tooltipPlacementBottom: classes.bootstrapPlacementBottom 183 | }} 184 | PopperProps={{ 185 | popperOptions: { 186 | modifiers: { 187 | arrow: { 188 | enabled: Boolean(this.state.arrowRef), 189 | element: this.state.arrowRef 190 | } 191 | } 192 | } 193 | }} 194 | > 195 | = Math.ceil(count / rowsPerPage) - 1} 198 | aria-label="Last Page" 199 | > 200 | {theme.direction === "rtl" ? : } 201 | 202 |
203 |
204 | ); 205 | } 206 | } 207 | 208 | TablePaginationActions.propTypes = { 209 | classes: PropTypes.object.isRequired, 210 | count: PropTypes.number.isRequired, 211 | onChangePage: PropTypes.func.isRequired, 212 | page: PropTypes.number.isRequired, 213 | rowsPerPage: PropTypes.number.isRequired, 214 | theme: PropTypes.object.isRequired 215 | }; 216 | 217 | // Make an HOC from the above TablePaginationActions component which will be exported and used in DevelopmentList.js 218 | const TablePaginationActionsWrapped = withStyles(actionsStyles, { 219 | withTheme: true 220 | })(TablePaginationActions); 221 | 222 | export default TablePaginationActionsWrapped; 223 | -------------------------------------------------------------------------------- /client/src/Components/UtilFunctions/confirmDelete.css: -------------------------------------------------------------------------------- 1 | .custom-ui { 2 | text-align: center; 3 | width: 500px; 4 | padding: 40px; 5 | background: #28bae6; 6 | box-shadow: 0 20px 75px rgba(0, 0, 0, 0.23); 7 | color: #fff; 8 | } 9 | 10 | .custom-ui>h1 { 11 | margin-top: 0; 12 | } 13 | 14 | .custom-ui>button { 15 | width: 160px; 16 | padding: 10px; 17 | border: 1px solid #fff; 18 | margin: 10px; 19 | cursor: pointer; 20 | background: none; 21 | color: #fff; 22 | font-size: 14px; 23 | } -------------------------------------------------------------------------------- /client/src/Components/UtilFunctions/showEmptyFieldAndDeleteSnackbar.js: -------------------------------------------------------------------------------- 1 | // This snackbar will be rendered when I submit an empty description field in each of the individual modules like "Development Works", "Income & Port Dues" etc 2 | // Also this same showEmptyFieldSnackbar will render the snackbar after successfully deleting a document, port-notice, GO, Tariff or Tidal data 3 | 4 | module.exports = { 5 | showDeleteSnackbar: () => { 6 | var x = document.getElementById("snackbar"); 7 | x.className = "show"; 8 | setTimeout(() => { 9 | x.className = x.className.replace("show", ""); 10 | }, 2000); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /client/src/Components/UtilFunctions/snackbar.css: -------------------------------------------------------------------------------- 1 | #snackbar { 2 | visibility: hidden; 3 | min-width: 250px; 4 | margin-left: -250px; 5 | background-color: #333; 6 | color: #fff; 7 | text-align: center; 8 | border-radius: 2px; 9 | padding: 16px; 10 | position: fixed; 11 | z-index: 1; 12 | left: 50%; 13 | top: 100px; 14 | font-size: 17px; 15 | } 16 | 17 | #snackbar.show { 18 | visibility: visible; 19 | -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s; 20 | animation: fadein 0.5s, fadeout 0.5s 2.5s; 21 | } 22 | 23 | @-webkit-keyframes fadein { 24 | from { 25 | top: 0; 26 | opacity: 0; 27 | } 28 | 29 | to { 30 | top: 80px; 31 | opacity: 1; 32 | } 33 | } 34 | 35 | @keyframes fadein { 36 | from { 37 | top: 0; 38 | opacity: 0; 39 | } 40 | 41 | to { 42 | top: 80px; 43 | opacity: 1; 44 | } 45 | } 46 | 47 | @-webkit-keyframes fadeout { 48 | from { 49 | top: 80px; 50 | opacity: 1; 51 | } 52 | 53 | to { 54 | top: 0; 55 | opacity: 0; 56 | } 57 | } 58 | 59 | @keyframes fadeout { 60 | from { 61 | top: 80px; 62 | opacity: 1; 63 | } 64 | 65 | to { 66 | top: 0; 67 | opacity: 0; 68 | } 69 | } -------------------------------------------------------------------------------- /client/src/Components/UtilFunctions/tableSortByHeading.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | desc: (a, b, orderBy) => { 3 | if (b[orderBy] < a[orderBy]) { 4 | return -1; 5 | } 6 | if (b[orderBy] > a[orderBy]) { 7 | return 1; 8 | } 9 | return 0; 10 | }, 11 | 12 | /* stableSort() higher order function to sort by a table heading 13 | 1> the argument 'comparisonCB' is the getSorting() defined below. 14 | 2> The sort() method takes a single compareFunction as a callback and compares each pair of successive elements. 15 | */ 16 | stableSort: (array, comparisonCB) => { 17 | const stabilizedThis = array.map((el, index) => [el, index]); 18 | stabilizedThis.sort((a, b) => { 19 | const order = comparisonCB(a[0], b[0]); 20 | if (order !== 0) return order; 21 | return a[1] - b[1]; 22 | }); 23 | return stabilizedThis.map(el => el[0]); 24 | }, 25 | 26 | /* getSorting() to be passed as a callback to the above stableSort(). The argument 'orderBy' will initially start (as a state) its value with 'imported_date' (which is a table header label) and then will take on whichever table header label the user clicks on. 27 | A> First the 'orderBy' value will be set by handleRequestSort() with its argument 'property' 28 | B> Then this function will be passed down as a prop 'onRequestSort' to EnhancedTableHead child component. 29 | C> In EnhancedTableHead, it will be called within createSortHandler() function and will be invoked on onClick and passed row.tableHeaderProp (which is the Table-header field value) 30 | */ 31 | getSorting: (order, orderBy) => { 32 | return order === "desc" 33 | ? (a, b) => module.exports.desc(a, b, orderBy) 34 | : (a, b) => -module.exports.desc(a, b, orderBy); 35 | } 36 | }; 37 | 38 | /* Special Note - Inside the getSorting() function I am calling a “local” function (desc()) within module.exports from another function (getSorting()) in module.exports with < module.exports > 39 | 40 | */ 41 | -------------------------------------------------------------------------------- /client/src/Components/commonStyles/AddNewItemStyles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | styles: theme => ({ 3 | palette: { 4 | primary: { main: '#9E9E9E' }, 5 | secondary: { main: '#ee0053' }, 6 | error: { main: '#ee0053' } 7 | }, 8 | root: { 9 | width: '100%', 10 | background: 'linear-gradient(45deg, #E91E63 30%, #FF8E53 90%)', 11 | marginTop: theme.spacing.unit * 10, 12 | backgroundColor: '#E0E0E0' 13 | }, 14 | paper: { 15 | backgroundColor: '#F5F5F5', 16 | boxShadow: 'none', 17 | overflow: 'hidden' 18 | }, 19 | table: { 20 | minWidth: 1020 21 | }, 22 | tableWrapper: { 23 | overflowX: 'auto' 24 | }, 25 | gutters: theme.mixins.gutters(), 26 | styledHeader: { 27 | background: '#66CCFF', 28 | '& h1': { 29 | color: '#f9f9f6' 30 | }, 31 | color: '#f9f9f6', 32 | fontSize: 25 33 | }, 34 | styledFooter: { 35 | background: '#FAFAFA', 36 | '& h2': { 37 | color: '#FAFAFA' 38 | } 39 | }, 40 | space: { 41 | marginTop: theme.spacing.unit * 4, 42 | marginBottom: theme.spacing.unit * 4 43 | }, 44 | spaceDialogAction: { 45 | marginTop: theme.spacing.unit * 2, 46 | marginBottom: theme.spacing.unit, 47 | marginRight: theme.spacing.unit 48 | }, 49 | fab: { 50 | margin: theme.spacing.unit * 4 51 | }, 52 | extendedIcon: { 53 | marginRight: theme.spacing.unit 54 | }, 55 | margin: { 56 | margin: theme.spacing.unit * 4 57 | }, 58 | 59 | lightTooltip: { 60 | background: theme.palette.common.white, 61 | color: theme.palette.text.primary, 62 | boxShadow: theme.shadows[1], 63 | fontSize: 16 64 | }, 65 | close: { 66 | padding: theme.spacing.unit * 2, 67 | fontSize: 25 68 | }, 69 | formControl: { 70 | margin: theme.spacing.unit, 71 | maxWidth: 300 72 | }, 73 | selectEmpty: { 74 | marginTop: theme.spacing.unit * 2 75 | }, 76 | container: { 77 | display: 'flex', 78 | flexWrap: 'wrap' 79 | }, 80 | notchedOutline: { 81 | border: '1px solid red' 82 | }, 83 | overrides: { 84 | MuiOutlinedInput: { 85 | focused: { 86 | border: '1px solid #4A90E2' 87 | }, 88 | '& $notchedOutline': { 89 | border: '1px solid #4A90E2' 90 | } 91 | } 92 | }, 93 | underline: { 94 | '&:after': { 95 | borderBottomColor: 'rgb(70, 197, 29)', 96 | borderWidth: '1px' 97 | } 98 | } 99 | }) 100 | }; 101 | 102 | /* 103 | 1> In above, the 'space' will control the vertical line spacing between TestFields and DatePicker inside the Dialog (i.e. inside AddNewDevelopmentWork component) 104 | 105 | And 'spaceDialogAction' will control the vertical space 106 | */ 107 | -------------------------------------------------------------------------------- /client/src/Components/commonStyles/AddNewItemThemes.js: -------------------------------------------------------------------------------- 1 | import { MuiThemeProvider, createMuiTheme } from "@material-ui/core/styles"; 2 | 3 | export default createMuiTheme({ 4 | typography: { 5 | useNextVariants: true 6 | }, 7 | palette: { 8 | primary: { main: "#2196f3" }, 9 | secondary: { main: "#fdff00" }, 10 | error: { main: "#f44336" } 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /client/src/Components/commonStyles/BootstrapInput.js: -------------------------------------------------------------------------------- 1 | import { withStyles } from "@material-ui/core"; 2 | import InputBase from "@material-ui/core/InputBase"; 3 | 4 | const BootstrapInput = withStyles(theme => ({ 5 | root: { 6 | "label + &": { 7 | marginTop: theme.spacing.unit * 3 8 | } 9 | }, 10 | input: { 11 | borderRadius: 4, 12 | position: "relative", 13 | backgroundColor: theme.palette.background.paper, 14 | border: "1px solid #ced4da", 15 | fontSize: 16, 16 | width: "auto", 17 | padding: "10px 26px 10px 12px", 18 | transition: theme.transitions.create(["border-color", "box-shadow"]), 19 | // Use the system font instead of the default Roboto font. 20 | fontFamily: [ 21 | "-apple-system", 22 | "BlinkMacSystemFont", 23 | '"Segoe UI"', 24 | "Roboto", 25 | '"Helvetica Neue"', 26 | "Arial", 27 | "sans-serif", 28 | '"Apple Color Emoji"', 29 | '"Segoe UI Emoji"', 30 | '"Segoe UI Symbol"' 31 | ].join(","), 32 | "&:focus": { 33 | borderRadius: 4, 34 | borderColor: "#80bdff", 35 | boxShadow: "0 0 0 0.2rem rgba(0,123,255,.25)" 36 | } 37 | } 38 | }))(InputBase); 39 | 40 | export default BootstrapInput; 41 | -------------------------------------------------------------------------------- /client/src/Components/commonStyles/ModuleItemListStyles.js: -------------------------------------------------------------------------------- 1 | export const styles = theme => ({ 2 | palette: { 3 | primary: { main: "#2196f3" }, 4 | secondary: { main: "#fdff00" }, 5 | error: { main: "#ee0053" } 6 | }, 7 | root: { 8 | width: "100%", 9 | marginTop: theme.spacing.unit * 3, 10 | overflow: "auto" 11 | }, 12 | space: { 13 | marginTop: theme.spacing.unit * 2 14 | }, 15 | fab: { 16 | margin: theme.spacing.unit 17 | }, 18 | fabButton: { 19 | position: "fixed", 20 | zIndex: 1, 21 | top: "auto", 22 | bottom: 0, 23 | marginBottom: 10, 24 | right: 10, 25 | margin: "0 auto" 26 | }, 27 | extendedIcon: { 28 | marginRight: theme.spacing.unit 29 | }, 30 | lightTooltip: { 31 | background: theme.palette.common.white, 32 | color: theme.palette.text.primary, 33 | boxShadow: theme.shadows[1], 34 | fontSize: 16 35 | }, 36 | table: { 37 | minWidth: 500, 38 | tableLayout: "auto", 39 | overflowX: "fixed" 40 | }, 41 | tableWrapper: { 42 | overflow: "auto" 43 | }, 44 | customTableCell: { 45 | whiteSpace: "nowrap", 46 | maxWidth: 0, 47 | overflow: "hidden", 48 | textOverflow: "ellipsis" 49 | }, 50 | row: { 51 | "&:nth-of-type(odd)": { 52 | backgroundColor: theme.palette.background.default 53 | } 54 | } 55 | }); 56 | 57 | /* 58 | To clip text with an ellipsis when it overflows a table cell, you will need to set the max-width CSS property on each td class for the overflow to work. 59 | 60 | For responsive layouts; use the max-width CSS property to specify the effective minimum width of the column, or just use max-width: 0; for unlimited flexibility. 61 | */ 62 | -------------------------------------------------------------------------------- /client/src/Components/commonStyles/SearchFilter-InputField.css: -------------------------------------------------------------------------------- 1 | #fb-gg-search { 2 | overflow: hidden; 3 | max-width: 200px; 4 | white-space: nowrap; 5 | text-overflow: ellipsis; 6 | opacity: 1 !important; 7 | } -------------------------------------------------------------------------------- /client/src/Components/commonStyles/SearchFilter-Styles.js: -------------------------------------------------------------------------------- 1 | import { emphasize } from "@material-ui/core/styles/colorManipulator"; 2 | 3 | var styles = theme => ({ 4 | root: { 5 | flexGrow: 1 6 | }, 7 | input: { 8 | display: "flex", 9 | borderRadius: 2, 10 | border: "1px solid #ced4da", 11 | backgroundColor: theme.palette.common.white, 12 | fontSize: 14, 13 | minHeight: 28, 14 | padding: "8px 0 8px 20px", 15 | opacity: "1 !important", 16 | justifyContent: "center" 17 | }, 18 | valueContainer: { 19 | display: "flex", 20 | flexWrap: "wrap", 21 | flex: 1, 22 | alignItems: "center", 23 | overflow: "hidden", 24 | justifyContent: "center" 25 | }, 26 | margin: { 27 | margin: theme.spacing.unit 28 | }, 29 | chip: { 30 | margin: `${theme.spacing.unit / 2}px ${theme.spacing.unit / 4}px`, 31 | borderRadius: 0, 32 | backgroundColor: "#ffffff", 33 | fontWeight: "bold" 34 | }, 35 | chipFocused: { 36 | backgroundColor: emphasize( 37 | theme.palette.type === "light" 38 | ? theme.palette.grey[300] 39 | : theme.palette.grey[700], 40 | 0.08 41 | ) 42 | }, 43 | noOptionsMessage: { 44 | padding: `${theme.spacing.unit}px ${theme.spacing.unit * 2}px` 45 | }, 46 | singleValue: { 47 | fontSize: 14, 48 | display: "none" 49 | }, 50 | placeholder: { 51 | position: "absolute", 52 | left: 22, 53 | fontSize: 14, 54 | fontWeight: "normal", 55 | color: "rgba(9, 10, 60,0.38)" 56 | }, 57 | paper: { 58 | position: "absolute", 59 | zIndex: 1, 60 | marginTop: theme.spacing.unit, 61 | left: 0, 62 | right: 0 63 | }, 64 | textSearchFilter: { 65 | padding: theme.spacing.unit * 2, 66 | textAlign: "center", 67 | color: theme.palette.text.secondary, 68 | display: "flex", 69 | width: "30em", 70 | flexDirection: "row" 71 | }, 72 | 73 | currentMonthData: { 74 | width: "740px", 75 | height: "55px", 76 | paddingLeft: "15px", 77 | verticalAlign: "center", 78 | textAlign: "center", 79 | color: "white", 80 | backgroundColor: "#007bff", 81 | borderRadius: "10px", 82 | fontSize: 20, 83 | cursor: "pointer" 84 | }, 85 | currentMonthDataEmployee: { 86 | width: "800px", 87 | height: "55px", 88 | paddingLeft: "15px", 89 | verticalAlign: "center", 90 | textAlign: "center", 91 | color: "white", 92 | backgroundColor: "#007bff", 93 | borderRadius: "10px", 94 | fontSize: 20, 95 | cursor: "pointer" 96 | }, 97 | divider: { 98 | height: theme.spacing.unit * 2 99 | }, 100 | labelRoot: { 101 | fontSize: 22, 102 | fontWeight: "bold", 103 | marginBottom: 0, 104 | color: theme.palette.primary.dark, 105 | position: "absolute", 106 | left: "-64px", 107 | top: "-12px" 108 | } 109 | }); 110 | 111 | export default styles; 112 | -------------------------------------------------------------------------------- /client/src/Components/commonStyles/combineStyles.js: -------------------------------------------------------------------------------- 1 | // Utility function to combine multiple Material-UI styles 2 | 3 | const combineStyles = (...styles) => { 4 | return function CombineStyles(theme) { 5 | const outStyles = styles.map(arg => { 6 | // Apply the "theme" object for style functions. 7 | if (typeof arg === "function") { 8 | return arg(theme); 9 | } 10 | // Objects need no change. 11 | return arg; 12 | }); 13 | return outStyles.reduce((acc, val) => Object.assign(acc, val)); 14 | }; 15 | }; 16 | 17 | export default combineStyles; 18 | -------------------------------------------------------------------------------- /client/src/Components/commonStyles/toolbarStyles.js: -------------------------------------------------------------------------------- 1 | function arrowGenerator(color) { 2 | return { 3 | '&[x-placement*="bottom"] $arrow': { 4 | top: 0, 5 | left: 0, 6 | marginTop: "-0.95em", 7 | width: "3em", 8 | height: "1em", 9 | "&::before": { 10 | borderWidth: "0 1em 1em 1em", 11 | borderColor: `transparent transparent ${color} transparent` 12 | } 13 | }, 14 | '&[x-placement*="top"] $arrow': { 15 | bottom: 0, 16 | left: 0, 17 | marginBottom: "-0.95em", 18 | width: "3em", 19 | height: "1em", 20 | "&::before": { 21 | borderWidth: "1em 1em 0 1em", 22 | borderColor: `${color} transparent transparent transparent` 23 | } 24 | }, 25 | '&[x-placement*="right"] $arrow': { 26 | left: 0, 27 | marginLeft: "-0.95em", 28 | height: "3em", 29 | width: "1em", 30 | "&::before": { 31 | borderWidth: "1em 1em 1em 0", 32 | borderColor: `transparent ${color} transparent transparent` 33 | } 34 | }, 35 | '&[x-placement*="left"] $arrow': { 36 | right: 0, 37 | marginRight: "-0.95em", 38 | height: "3em", 39 | width: "1em", 40 | "&::before": { 41 | borderWidth: "1em 0 1em 1em", 42 | borderColor: `transparent transparent transparent ${color}` 43 | } 44 | } 45 | }; 46 | } 47 | 48 | const toolbarStyles = theme => ({ 49 | root: { 50 | paddingRight: theme.spacing.unit 51 | }, 52 | monthlyData: { 53 | marginRight: "12px", 54 | width: "650px", 55 | minHeight: "40px", 56 | paddingTop: "10px", 57 | verticalAlign: "center", 58 | textAlign: "center", 59 | borderRadius: "10px", 60 | cursor: "pointer" 61 | }, 62 | todaysData: { 63 | marginRight: "12px", 64 | width: "200px", 65 | paddingTop: "6px", 66 | verticalAlign: "center", 67 | textAlign: "center", 68 | borderRadius: "10px", 69 | cursor: "pointer" 70 | }, 71 | arrowPopper: arrowGenerator(theme.palette.common.black), 72 | arrow: { 73 | position: "absolute", 74 | fontSize: 6, 75 | width: "3em", 76 | height: "3em", 77 | "&::before": { 78 | content: '""', 79 | margin: "auto", 80 | display: "block", 81 | width: 0, 82 | height: 0, 83 | borderStyle: "solid" 84 | } 85 | }, 86 | bootstrapPopper: arrowGenerator(theme.palette.common.black), 87 | bootstrapTooltip: { 88 | backgroundColor: theme.palette.common.black, 89 | paddingTop: "12px" 90 | }, 91 | bootstrapPlacementLeft: { 92 | margin: "0 8px" 93 | }, 94 | bootstrapPlacementRight: { 95 | margin: "0 8px" 96 | }, 97 | bootstrapPlacementTop: { 98 | margin: "8px 0" 99 | }, 100 | bootstrapPlacementBottom: { 101 | margin: "8px 0" 102 | }, 103 | highlight: 104 | theme.palette.type === "light" 105 | ? { 106 | color: theme.palette.secondary.main, 107 | backgroundColor: "#66CCFF" 108 | } 109 | : { 110 | color: theme.palette.text.primary, 111 | backgroundColor: theme.palette.secondary.dark 112 | }, 113 | 114 | spacer: { 115 | flex: "1 2 100%" 116 | }, 117 | spacerCurrentMonth: { 118 | flex: "1 2 100%" 119 | }, 120 | 121 | actions: { 122 | color: theme.palette.text.secondary 123 | }, 124 | title: { 125 | flex: "0 0 auto" 126 | } 127 | }); 128 | 129 | export default toolbarStyles; 130 | 131 | /* 132 | 1> Explanation of < flex: 1 1 100% > - With this I am making 'spacer' to stay full-width of the available space. And then adding two 'spacer' class to both side of the '{currentMonth} month's Exports' to make the currentMonth get centered within the available space 133 | 134 | A) The 100% in the above is the 'flex-basis', a sub-property of the Flexible Box Layout module. It specifies the initial size of the flexible item, before any available space is distributed according to the flex factors. 135 | 136 | B) flex - This is the shorthand for flex-grow, flex-shrink and flex-basis combined. Defaults is 0 1 auto. 137 | 138 | B) In other words, the general syntax - 139 | flex : flex-grow | flex-shrink | flex-basis 140 | (https://developer.mozilla.org/en-US/docs/Web/CSS/flex) 141 | 142 | C) The general way flex work - I need to add display:flex to the parent tag and then flex:1 to the child to enable the child to expand to 100% of parent. 143 | 144 | */ 145 | -------------------------------------------------------------------------------- /client/src/Routes.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Route, BrowserRouter, Switch, Router, Link } from "react-router-dom"; 3 | import EmployeeList from "./Components/Employee/EmployeeList"; 4 | import DepartmentList from "./Components/Department/DepartmentList"; 5 | import NotFound from "./Components/NotFound"; 6 | 7 | class Routes extends Component { 8 | render() { 9 | return ( 10 |
11 | 12 |
13 | 14 | 15 | ( 19 | 25 | )} 26 | /> 27 | 28 | 29 |
30 |
31 |
32 | ); 33 | } 34 | } 35 | 36 | export default Routes; 37 | -------------------------------------------------------------------------------- /client/src/assets/images/LandingPage/code-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/src/assets/images/LandingPage/code-1.jpeg -------------------------------------------------------------------------------- /client/src/assets/images/LandingPage/code-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/src/assets/images/LandingPage/code-2.jpeg -------------------------------------------------------------------------------- /client/src/assets/images/LandingPage/code-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/src/assets/images/LandingPage/code-3.jpeg -------------------------------------------------------------------------------- /client/src/assets/images/LandingPage/code-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/src/assets/images/LandingPage/code-4.jpeg -------------------------------------------------------------------------------- /client/src/assets/images/PageNotFound/PageNotFound.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/src/assets/images/PageNotFound/PageNotFound.jpg -------------------------------------------------------------------------------- /client/src/assets/images/SVG-Icons/baseline-people_outline-24px.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/bg_purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/src/assets/images/bg_purple.png -------------------------------------------------------------------------------- /client/src/assets/images/earth.svg: -------------------------------------------------------------------------------- 1 | earth -------------------------------------------------------------------------------- /client/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rohan-paul/material-ui-table-with-node-mongodb-running-in-GCP-also-the-source-code-for-GCP-Blog/911edd8dc9fa46c5eeec320379560b719acfb4df/client/src/assets/images/logo.png -------------------------------------------------------------------------------- /client/src/assets/images/moon.svg: -------------------------------------------------------------------------------- 1 | moon_1 -------------------------------------------------------------------------------- /client/src/assets/images/overlay_stars.svg: -------------------------------------------------------------------------------- 1 | overlay_stars_1 -------------------------------------------------------------------------------- /client/src/assets/images/rocket.svg: -------------------------------------------------------------------------------- 1 | rocket_1 -------------------------------------------------------------------------------- /client/src/assets/js/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | // See the Getting Started docs for more information: 8 | // http://getbootstrap.com/getting-started/#support-ie10-width 9 | 10 | (function () { 11 | 'use strict'; 12 | 13 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 14 | var msViewportStyle = document.createElement('style') 15 | msViewportStyle.appendChild( 16 | document.createTextNode( 17 | '@-ms-viewport{width:auto!important}' 18 | ) 19 | ) 20 | document.querySelector('head').appendChild(msViewportStyle) 21 | } 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /client/src/history.js: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from 'history'; 2 | 3 | export default createBrowserHistory(); -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /client/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const mongoose = require("mongoose"); 3 | 4 | module.exports = { 5 | database: process.env.MONGO_DB, 6 | 7 | options: { 8 | reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect 9 | reconnectInterval: 1000, // Reconnect every 500ms 10 | poolSize: 10 // Maintain up to 10 socket connections 11 | }, 12 | 13 | // Connect connection with MongoDB Database 14 | connectDB: function() { 15 | mongoose.connect(this.database, this.options); 16 | }, 17 | 18 | // Disconnect connection with MongoDB Database 19 | disconnectDB: function() { 20 | mongoose.disconnect(this.database); 21 | } 22 | }; 23 | // on mongo connection open event print a console statement 24 | mongoose.connection.on("open", function() { 25 | console.log("Connected to Database (MongoDB) "); 26 | }); 27 | 28 | // on mongo connection error event 29 | mongoose.connection.on("error", function() { 30 | console.log("error in Database (MongoDB) connections"); 31 | }); 32 | 33 | // on mongo connection disconnected event 34 | mongoose.connection.on("disconnected", () => { 35 | console.log("Mongoose default connection disconnected"); 36 | }); 37 | 38 | //listen for SIGINT event(generated with +C in the terminal) and close db connection on that event 39 | process.on("SIGINT", () => { 40 | mongoose.connection.close(() => { 41 | console.log("Mongoose disconnected through app termination"); 42 | process.exit(0); 43 | }); 44 | }); 45 | 46 | /* Our application exits ( generated with 'SIGINT' event ) will also close database connection. 47 | 48 | What is the cause? - EventLoop 49 | 50 | As we know NodeJS will exit when EventLoop queue is empty and nothing left to do. 51 | 52 | But sometime your application can have more functions and will not exit automatically, in this point comes our last work to do. 53 | We need to exit from process using process.exit function. 54 | 55 | Argument 0 means exit with a “success” code. 56 | To exit with a “failure” code use 1. 57 | */ 58 | -------------------------------------------------------------------------------- /config/dev.js: -------------------------------------------------------------------------------- 1 | // prod.js - production keys here!! 2 | -------------------------------------------------------------------------------- /config/keys.js: -------------------------------------------------------------------------------- 1 | // keys.js - figure out what set of credentials to return 2 | if (process.env.NODE_ENV === "production") { 3 | // we are in production - return the prod set of keys. 4 | module.exports = require("./prod"); 5 | } else { 6 | // we are in development - return the dev keys!!! 7 | module.exports = require("./dev"); 8 | } 9 | -------------------------------------------------------------------------------- /config/prod.js: -------------------------------------------------------------------------------- 1 | // prod.js - production keys here!! 2 | -------------------------------------------------------------------------------- /config/settings.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | secret: "auth" 3 | }; 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // const dotenv = require("dotenv"); 2 | require("dotenv").config(); 3 | const fs = require("fs"); 4 | 5 | // const envConfig = dotenv.parse(fs.readFileSync(".env.override")); 6 | // for (let k in envConfig) { 7 | // process.env[k] = envConfig[k]; 8 | // } 9 | const express = require("express"); 10 | const app = express(); 11 | const path = require("path"); 12 | const morgan = require("morgan"); 13 | const bodyParser = require("body-parser"); 14 | const methodOverride = require("method-override"); 15 | const url = require("url"); 16 | 17 | const departmentRoutes = require("./routes/departmentRoutes"); 18 | const employeeRoutes = require("./routes/employeeRoutes"); 19 | 20 | const config = require("./config/config"); 21 | 22 | const addRequestId = require("express-request-id")(); 23 | 24 | config.connectDB(); 25 | 26 | // Generate UUID for request and add it to X-Request-Id header. To work along with morgan logging. Adding a request id to the request object, to facilitate tying different log entries to each other. So a Request log and its associated Response log would have the same id. 27 | app.use(addRequestId); 28 | app.use(morgan()); // I am both writing to a log file while showing logs on the console. 29 | app.use(methodOverride("_method")); 30 | app.use(bodyParser.json()); 31 | app.use(bodyParser.urlencoded({ extended: true })); 32 | 33 | morgan.token("id", function getId(req) { 34 | return req.id; 35 | }); 36 | 37 | // Serving build's index.html file for Deploying to Google App Engine is MUST for production 38 | 39 | // From - https://facebook.github.io/create-react-app/docs/deployment 40 | app.use(express.static(path.join(__dirname, "/client/build"))); 41 | 42 | // The below two routes are also Perfectly Working with GAE, but I used the 'catchAll' routes below 43 | // app.get("/employee", function(req, res) { 44 | // res.status(200).sendFile(path.join(__dirname, "/client/build", "index.html")); 45 | // }); 46 | 47 | // app.get("/department", function(req, res) { 48 | // res.status(200).sendFile(path.join(__dirname, "/client/build", "index.html")); 49 | // }); 50 | 51 | // The below IS ALSO WORKING when run < npm run dev > locally - but ofcourse it will not refer to the /client/build folder 52 | // app.get("/", function(req, res) { 53 | // res.sendFile(path.join(__dirname, "/client/public", "index.html")); 54 | // }); 55 | 56 | // Morgan - For saving logs to a log file 57 | let accessLogStream = fs.createWriteStream(__dirname + "/access.log", { 58 | flags: "a" 59 | }); 60 | 61 | // my custom log format, just adding ":id" to the pre-defined "combined" format from morgan 62 | // let loggerFormat = 63 | // ':id :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :req[header] :response-time ms'; 64 | 65 | let loggerFormat = `:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]`; 66 | 67 | app.use(morgan(loggerFormat, { stream: accessLogStream })); 68 | 69 | // Below two functions are for showing logs on the console. Define streams for Morgan. This logs to stderr for status codes greater than 400 and stdout for status codes less than 400. 70 | app.use( 71 | morgan(loggerFormat, { 72 | skip: function(req, res) { 73 | return res.statusCode < 400; 74 | }, 75 | stream: process.stderr 76 | }) 77 | ); 78 | 79 | app.use( 80 | morgan(loggerFormat, { 81 | skip: function(req, res) { 82 | return res.statusCode >= 400; 83 | }, 84 | stream: process.stdout 85 | }) 86 | ); 87 | 88 | app.use("/api/department", departmentRoutes); 89 | app.use("/api/employee", employeeRoutes); 90 | 91 | // Only now, AFTER the above /api/ routes, the "catchall" handler routes: for any request that doesn't match any route after "/" below and send back React's index.html file. 92 | // Note, this 'catchall" route MUST be put after the above two /api/ routes. Otherwise those api routes will never be hit 93 | app.get("/*", (req, res) => { 94 | res.sendFile(path.join(__dirname, "/client/build/index.html")); 95 | }); 96 | 97 | app.use((err, req, res, next) => { 98 | res.status(422).send({ error: err._message }); 99 | }); 100 | 101 | app.listen(8080, () => { 102 | console.log("Express Server running on port 8080"); 103 | }); 104 | 105 | // Graceful shutdown, on sigint ( generated with +C in the terminal ) - kill/close database connection and exit 106 | process.on("SIGINT", () => { 107 | config.disconnectDB(); 108 | process.exit(0); 109 | }); 110 | -------------------------------------------------------------------------------- /models/department.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const mongoose = require("mongoose"), 3 | Schema = mongoose.Schema; 4 | 5 | let departmentSchema = new Schema( 6 | { 7 | name: { type: String, unique: true }, 8 | type: { type: String } 9 | }, 10 | { 11 | // createdAt,updatedAt fields are automatically added into records 12 | timestamps: true 13 | } 14 | ); 15 | 16 | // exports 17 | module.exports = mongoose.model("Department", departmentSchema); 18 | -------------------------------------------------------------------------------- /models/employee.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const mongoose = require("mongoose"), 4 | autopopulate = require("mongoose-autopopulate"), 5 | Department = require("./department"), 6 | Schema = mongoose.Schema, 7 | mongoosePaginate = require("mongoose-paginate"); 8 | 9 | let employeeSchema = new Schema( 10 | { 11 | department_objectId: { 12 | type: Schema.Types.ObjectId, 13 | ref: "Department", 14 | autopopulate: true 15 | }, 16 | employee_name: { type: String }, 17 | work_description: { type: String }, 18 | avg_employee_productivity: { type: Number }, // in Tonnes 19 | benchmark_employee_productivity: { type: Number }, // in Tonnes 20 | date: { type: Date } 21 | }, 22 | { 23 | // createdAt,updatedAt fields are automatically added into records 24 | timestamps: true 25 | } 26 | ); 27 | 28 | // plugins 29 | employeeSchema.plugin(autopopulate); 30 | employeeSchema.plugin(mongoosePaginate); 31 | 32 | module.exports = mongoose.model("Employee", employeeSchema); 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "engines": { 5 | "node": "10.15.3" 6 | }, 7 | "description": "", 8 | "homepage": "https://mui-table-3.appspot.com/employee", 9 | "main": "index.js", 10 | "scripts": { 11 | "start": "nodemon index.js ", 12 | "server": "nodemon index.js", 13 | "client": "npm start --prefix client", 14 | "dev": "concurrently \"npm run server\" \"npm run client\"", 15 | "heroku-postbuild": "cd client && npm install && npm run build", 16 | "fix-code": "prettier-eslint --write 'src/**/*.{js,jsx}' ", 17 | "fix-styles": "prettier-stylelint --write 'src/**/*.{css,scss}' " 18 | }, 19 | "author": "", 20 | "license": "ISC", 21 | "dependencies": { 22 | "adm-zip": "^0.4.13", 23 | "async": "^2.6.1", 24 | "body-parser": "^1.18.3", 25 | "buffer-to-stream": "^1.0.0", 26 | "client-sessions": "^0.8.0", 27 | "concurrently": "^4.1.0", 28 | "cookie-session": "^2.0.0-beta.3", 29 | "dotenv": "^6.2.0", 30 | "eslint": "5.12.0", 31 | "eslint-config-airbnb": "^17.1.0", 32 | "eslint-plugin-import": "^2.16.0", 33 | "eslint-plugin-jsx-a11y": "^6.2.1", 34 | "eslint-plugin-react": "^7.12.4", 35 | "express": "^4.16.4", 36 | "express-request-id": "^1.4.1", 37 | "jquery": "^3.3.1", 38 | "lodash": "^4.17.11", 39 | "method-override": "^3.0.0", 40 | "mocha": "^5.2.0", 41 | "mongoose": "^5.4.11", 42 | "mongoose-autopopulate": "^0.9.1", 43 | "mongoose-paginate": "^5.0.3", 44 | "mongoose-validator": "^2.1.0", 45 | "morgan": "^1.9.1", 46 | "nodemon": "^1.18.10", 47 | "pm2": "^3.2.9", 48 | "prettier-eslint": "^8.8.2", 49 | "prettier-eslint-cli": "^4.7.1", 50 | "prettier-stylelint": "^0.4.2" 51 | } 52 | } -------------------------------------------------------------------------------- /routes/createAllocationLodash.js: -------------------------------------------------------------------------------- 1 | /* Utility function (createAllocationExports) to create a top-level field (named 'department_objectId') from a field from the nested Department-object (i.e. from the referenced Department schema). 2 | When I send a data back to client (front-end), it should give me the item's Department name ('department_name' filed) as a top-level field to the schema instead of this being fetched from the nested object. (See I have "department_name": "Gold", as a top-level field that has been created in my backend routes file). The key requirement is, for the referenced Model (Department), I am passing only the ObjectId for the field 'department_objectId' with req.body but in the returned data, I will need the field 'department_name' as a separate top level field with data populated from the referenced Model 'Department' 3 | 4 | The reason I need this variable as a top-level field rather than as a nested object, is because of the sort functionality of Material-UI table. While I was able to render the table rows properly by fetching this value from the nested returned object. But the sort functionality's various util functions were becoming too uncontrollable without a top-level field value. 5 | */ 6 | 7 | module.exports = { 8 | createAllocationEmployee: item => ({ 9 | _id: item._id, 10 | department_objectId: 11 | item.department_objectId && item.department_objectId._id, 12 | employee_name: item.employee_name, 13 | work_description: item.work_description, 14 | avg_employee_productivity: item.avg_employee_productivity, 15 | benchmark_employee_productivity: item.benchmark_employee_productivity, 16 | date: item.date, 17 | department_name: item.department_objectId && item.department_objectId.name 18 | }) 19 | }; 20 | -------------------------------------------------------------------------------- /routes/departmentRoutes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let express = require("express"), 4 | router = express.Router(), 5 | Department = require("../models/department"); 6 | 7 | // To GET ALL Departments (without mongoose-paginate)-working 8 | router.get("/", (req, res, next) => { 9 | Department.find( 10 | {}, 11 | null, 12 | { 13 | sort: { createdAt: -1 } 14 | }, 15 | (err, docs) => { 16 | if (err) { 17 | return next(err); 18 | } else { 19 | res.status(200).json(docs); 20 | } 21 | } 22 | ); 23 | }); 24 | 25 | // To Add New Department 26 | router.post("/", (req, res, next) => { 27 | let department = new Department(req.body); 28 | department.save((error, newDocument) => { 29 | if (error) { 30 | if (error.name === "MongoError" && error.code === 11000) { 31 | // console.log("DUPLICATE IN BACK END IS ", error); 32 | res.status(400).send(error); 33 | } else { 34 | next(error); 35 | } 36 | } else { 37 | res.status(200).send(newDocument); 38 | } 39 | }); 40 | }); 41 | 42 | // Edit/update by department's _id - Working 43 | router.put("/:id", (req, res) => { 44 | Department.findByIdAndUpdate( 45 | req.params.id, 46 | { 47 | $set: { 48 | name: req.body.name, 49 | type: req.body.type 50 | } 51 | }, 52 | { new: true }, 53 | (err, updatedRecord) => { 54 | if (err) { 55 | console.error(err); 56 | res.send(err); 57 | } else { 58 | res.status(200).send(updatedRecord); 59 | } 60 | } 61 | ); 62 | }); 63 | 64 | // Delete by _id - Working 65 | router.route("/delete").delete((req, res, next) => { 66 | Department.remove( 67 | { _id: { $in: req.body.department_id_list_arr } }, 68 | (err, result) => { 69 | if (err) { 70 | console.log(err); 71 | return next(err); 72 | } else { 73 | res.status(200).send(result); 74 | } 75 | } 76 | ); 77 | }); 78 | 79 | module.exports = router; 80 | 81 | /* 82 | 1> < Department.find({}, "-updatedAt -createdAt -__v", (err, records) > Means exclude updatedAt and createdAt, include other fields 83 | 84 | https://mongoosejs.com/docs/api.html#model_Model.find 85 | 86 | https://mongoosejs.com/docs/api.html#query_Query-select 87 | 88 | 2> To Test Delete route in Postman - (http://localhost:3000/api/department/delete) 89 | 90 | { 91 | "department_id_list_arr":["5c5eefa0518f005ac93cb4da", "5c5eef9b518f005ac93cb4d9"] 92 | } 93 | 94 | */ 95 | -------------------------------------------------------------------------------- /routes/employeeRoutes.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const express = require("express"), 4 | Department = require("../models/department"), 5 | router = express.Router(), 6 | moment = require("moment"), 7 | Employee = require("../models/employee"); 8 | 9 | const _ = require("lodash"); 10 | const flatMap = require("lodash/flatMap"); 11 | const { createAllocationEmployee } = require("./createAllocationLodash"); 12 | 13 | // General route to get raw database from direct call to mongo (not needed in the app) 14 | router.get("/directdatafrommongo", (req, res, next) => { 15 | Employee.find( 16 | {}, 17 | null, 18 | { 19 | sort: { createdAt: -1 } 20 | }, 21 | (err, docs) => { 22 | if (err) { 23 | return next(err); 24 | } else { 25 | res.status(200).json(docs); 26 | } 27 | } 28 | ); 29 | }); 30 | 31 | // To GET ALL Imports without mongoose-paginate - Working in Postman* 32 | router.get("/", (req, res, next) => { 33 | Employee.find( 34 | {}, 35 | null, 36 | { 37 | sort: { createdAt: -1 } 38 | }, 39 | (err, records) => { 40 | if (err) { 41 | return next(err); 42 | } else { 43 | const flatDocs = _.flatMap(records, item => [ 44 | createAllocationEmployee(item) 45 | ]); 46 | res.status(200).json(flatDocs); 47 | } 48 | } 49 | ); 50 | }); 51 | 52 | // To GET ALL Imports with mongoose-paginate - max 5 or 10 or 15 at a time (determined by whatever users sets it to be at the front-end MAT-UI). By sating < lean: true > the returned data will be made lean i.e. the "department_objectId": "5c6121e4722f134f27c49ee4", will be a single line instead of a nested object with all details. Working in Postman* 53 | router.get("/paginate", (req, res, next) => { 54 | let pageOptions = { 55 | page: parseInt(req.query.page) || 0, 56 | limit: parseInt(req.query.rowsperpage) || 5 57 | }; 58 | 59 | if (req.query.page && req.query.rowsperpage) { 60 | Employee.paginate( 61 | {}, 62 | { 63 | offset: pageOptions.page * pageOptions.limit, 64 | limit: pageOptions.limit, 65 | sort: { createdAt: -1 } 66 | }, 67 | 68 | (err, records) => { 69 | if (err) { 70 | console.log(err); 71 | next(err); 72 | } else { 73 | const flatDocs = _.flatMap(records.docs, item => [ 74 | createAllocationEmployee(item) 75 | ]); 76 | res.status(200).json(flatDocs); 77 | } 78 | } 79 | ); 80 | } 81 | }); 82 | 83 | // Create a new item (note, I have to have an object_id reference of the relevant department) -Working in Postman* 84 | router.post("/", (req, res, next) => { 85 | let employee = new Employee(req.body); 86 | employee.save((err, newImport) => { 87 | if (err) { 88 | console.log("Failed to post new data because ", err); 89 | return next(err); 90 | } else { 91 | res.status(200).send(newImport); 92 | } 93 | }); 94 | }); 95 | 96 | // Edit / Update by _id - Working in Postman* 97 | router.put("/:id", (req, res, next) => { 98 | let editedItem = req.body; 99 | 100 | // I also had to reformat the date, else mongo was saving the date record as one day previous 101 | editedItem.date = moment(editedItem.date).format("YYYY-MM-DD"); 102 | 103 | Employee.findByIdAndUpdate(req.params.id, editedItem, { 104 | new: true 105 | }).exec((err, record) => { 106 | if (err) { 107 | console.log(err); 108 | return next(err); 109 | } else { 110 | res.status(200).json(record); 111 | } 112 | }); 113 | }); 114 | 115 | // Delete by _id array - Working in Postman 116 | router.delete("/delete", (req, res, next) => { 117 | Employee.remove( 118 | { _id: { $in: req.body.employee_id_list_arr } }, 119 | (err, records) => { 120 | if (err) { 121 | console.log(err); 122 | return next(err); 123 | } else { 124 | res.status(200).send(records); 125 | } 126 | } 127 | ); 128 | }); 129 | 130 | // get Imports by date range WITHOUT PAGINATION - Working in Postman 131 | router.post("/daterange", (req, res, next) => { 132 | let startDateByUser = moment(req.body.start_date).format("YYYY-MM-DD"); 133 | let endDateByUser = moment(req.body.end_date).format("YYYY-MM-DD"); 134 | // Now, Increment the user input date by one, because the query should return records for both the user-input date inclusive data 135 | let incremented_endDateByUser = moment(endDateByUser) 136 | .add(1, "days") 137 | .format("YYYY-MM-DD"); 138 | 139 | Employee.find( 140 | { 141 | date: { 142 | $gte: startDateByUser, 143 | $lt: incremented_endDateByUser 144 | } 145 | }, 146 | (err, records) => { 147 | if (err) { 148 | return next(err); 149 | } else { 150 | const flatDocs = _.flatMap(records, item => [ 151 | createAllocationEmployee(item) 152 | ]); 153 | res.status(200).json(flatDocs); 154 | } 155 | } 156 | ); 157 | }); 158 | 159 | // get Employee works by date range - WORKING WITH PAGINATION - Working in Postman 160 | // The signature is - Model.paginate([query], [options], [callback]) 161 | router.post("/paginate/daterange", (req, res, next) => { 162 | let startDateByUser = moment(req.body.start_date).format("YYYY-MM-DD"); 163 | let endDateByUser = moment(req.body.end_date).format("YYYY-MM-DD"); 164 | 165 | // Now, Increment the user input date by one, because the query should return records for both the user-input date inclusive data 166 | let incremented_endDateByUser = moment(endDateByUser) 167 | .add(1, "days") 168 | .format("YYYY-MM-DD"); 169 | 170 | let pageOptions = { 171 | page: parseInt(req.query.page) || 0, 172 | limit: parseInt(req.query.rowsperpage) || 5 173 | }; 174 | 175 | Employee.paginate( 176 | { 177 | date: { 178 | $gte: startDateByUser, 179 | $lt: incremented_endDateByUser 180 | } 181 | }, 182 | { 183 | offset: pageOptions.page * pageOptions.limit, 184 | limit: pageOptions.limit, 185 | sort: { createdAt: -1 } 186 | }, 187 | 188 | (err, records) => { 189 | if (err) { 190 | console.log(err); 191 | return next(err); 192 | } else { 193 | const flatDocs = _.flatMap(records.docs, item => [ 194 | createAllocationEmployee(item) 195 | ]); 196 | res.status(200).json(flatDocs); 197 | } 198 | } 199 | ); 200 | }); 201 | 202 | // get employee by Today's Date WITHOUT pagination- WORKING IN POSTMAN 203 | router.get("/current/today", (req, res, next) => { 204 | let today = moment().format("YYYY-MM-DD"); 205 | let tomorrow = moment(today) 206 | .add(1, "days") 207 | .format("YYYY-MM-DD"); 208 | Employee.find( 209 | { 210 | date: { 211 | $gte: today, 212 | $lt: tomorrow 213 | } 214 | }, 215 | (err, records) => { 216 | if (err) { 217 | return next(err); 218 | } 219 | const flatDocs = _.flatMap(records, item => [ 220 | createAllocationEmployee(item) 221 | ]); 222 | res.status(200).json(flatDocs); 223 | } 224 | ); 225 | }); 226 | 227 | // get employee by Today's Date - Rohan's code with pagination(Working in Postman) 228 | router.post("/paginate/current/today", (req, res, next) => { 229 | let pageOptions = { 230 | page: parseInt(req.query.page) || 0, 231 | limit: parseInt(req.query.rowsperpage) || 5 232 | }; 233 | let today = moment().format("YYYY-MM-DD"); 234 | let tomorrow = moment(today) 235 | .add(1, "days") 236 | .format("YYYY-MM-DD"); 237 | 238 | Employee.paginate( 239 | { 240 | date: { 241 | $gte: today, 242 | $lt: tomorrow 243 | } 244 | }, 245 | { 246 | offset: pageOptions.page * pageOptions.limit, 247 | limit: pageOptions.limit, 248 | sort: { createdAt: -1 } 249 | }, 250 | (err, records) => { 251 | if (err) { 252 | console.log(err); 253 | return next(err); 254 | } 255 | const flatDocs = _.flatMap(records.docs, item => [ 256 | createAllocationEmployee(item) 257 | ]); 258 | res.status(200).json(flatDocs); 259 | } 260 | ); 261 | }); 262 | 263 | // get employee by current month WITHOUT mongoose-pagination - Rohan's code (Working in Postman) 264 | router.post("/currentmonth", (req, res, next) => { 265 | var startDate = moment(new Date()) 266 | .startOf("month") 267 | .format("YYYY-MM-DD"); 268 | var endDate = moment(new Date()) 269 | .endOf("month") 270 | .add(1, "days") 271 | .format("YYYY-MM-DD"); 272 | 273 | Employee.find( 274 | { 275 | date: { 276 | $gte: startDate, 277 | $lt: endDate 278 | } 279 | }, 280 | (err, records) => { 281 | if (err) { 282 | return next(err); 283 | } 284 | const flatDocs = _.flatMap(records, item => [ 285 | createAllocationEmployee(item) 286 | ]); 287 | res.status(200).json(flatDocs); 288 | } 289 | ); 290 | }); 291 | 292 | // get employee by current month WITH mongoose-pagination - Rohan's code (Working in Postman) 293 | router.post("/paginate/currentmonth", (req, res, next) => { 294 | var startDate = moment(new Date()) 295 | .startOf("month") 296 | .format("YYYY-MM-DD"); 297 | var endDate = moment(new Date()) 298 | .endOf("month") 299 | .add(1, "days") 300 | .format("YYYY-MM-DD"); 301 | 302 | let pageOptions = { 303 | page: parseInt(req.query.page) || 0, 304 | limit: parseInt(req.query.rowsperpage) || 5 305 | }; 306 | 307 | Employee.paginate( 308 | { 309 | date: { 310 | $gte: startDate, 311 | $lt: endDate 312 | } 313 | }, 314 | { 315 | offset: pageOptions.page * pageOptions.limit, 316 | limit: pageOptions.limit, 317 | sort: { createdAt: -1 } 318 | }, 319 | 320 | (err, records) => { 321 | if (err) { 322 | return next(err); 323 | } 324 | const flatDocs = _.flatMap(records.docs, item => [ 325 | createAllocationEmployee(item) 326 | ]); 327 | res.status(200).json(flatDocs); 328 | } 329 | ); 330 | }); 331 | 332 | module.exports = router; 333 | 334 | /* 1> To test the POST (New Item) route http://localhost:3000/api/employee in Postman - ie. for adding a new Employee, I have to take an existing object_id from the department module, meaning a real Department data should be existing already, i.e. Department should be created before creating Employee 335 | 336 | And If I dont yet have a department module take an arbitrary _id for filling up the objectId field for the 'department_objectId' prop of the Employee scheme. 337 | 338 | The below one is with a real object_id 339 | 340 | { 341 | "department_objectId":"5c6c0f969c84ea3c7194a7de", 342 | "avg_employee_productivity":"6", 343 | "benchmark_employee_productivity":"20", 344 | "date":"2019-02-26" 345 | } 346 | 347 | ========== 348 | 349 | 2> To test Edit route - http://localhost:3000/api/employee/5c75512bc014924f462b753b 350 | 351 | { 352 | "department_objectId":"5c6c0f839c84ea3c7194a7dd", 353 | "avg_employee_productivity":"6", 354 | "benchmark_employee_productivity":"25", 355 | "date":"2019-02-26" 356 | } 357 | 358 | 3> Testing DELETE - router.delete("/delete" 359 | 360 | { 361 | "employee_id_list_arr":["5c6a6ecc1c01895fcfa3fe5d"] 362 | } 363 | 364 | 4> To test router.post("/paginate/daterange", - First post a a document with date set to as below 365 | 366 | "date":"2019-02-12", 367 | 368 | Then send a POST request with Params of page=0 and rowsperpage=2 369 | 370 | { 371 | "start_date": "2019-02-12", 372 | "end_date": "2019-02-12" 373 | } 374 | 375 | 376 | */ 377 | --------------------------------------------------------------------------------