├── CNAME ├── client ├── src │ ├── services │ │ ├── login │ │ │ ├── signout.js │ │ │ ├── forgotpassword.js │ │ │ ├── signin.js │ │ │ ├── resetpassword.js │ │ │ └── signup.js │ │ ├── notifications │ │ │ └── index.js │ │ ├── employees │ │ │ ├── allEmployees.js │ │ │ └── employee-details.js │ │ ├── departments │ │ │ └── departments.js │ │ ├── projects │ │ │ └── projects.js │ │ └── tasks │ │ │ ├── tasks.js │ │ │ └── taskBoards.js │ ├── font │ │ ├── font-Bold.ttf │ │ ├── Nunito-Bold.ttf │ │ ├── font-Light.ttf │ │ ├── font-Medium.ttf │ │ ├── Nunito-Medium.ttf │ │ ├── Nunito-Regular.ttf │ │ └── font-Regular.ttf │ ├── utils │ │ ├── check-all.js │ │ ├── email-validator.js │ │ ├── isHeaderHidden.js │ │ ├── localStorage.js │ │ ├── fetchUrl.js │ │ ├── date-handler.js │ │ └── checkLogin.js │ ├── containers │ │ ├── departments │ │ │ ├── create-depart.scss │ │ │ ├── index.js │ │ │ ├── create-depart-form.js │ │ │ ├── main-section.js │ │ │ └── styles.scss │ │ ├── reports │ │ │ └── index.js │ │ ├── settings │ │ │ └── index.js │ │ ├── sign-up │ │ │ ├── top-section.js │ │ │ ├── pre-signup │ │ │ │ ├── index.js │ │ │ │ └── form-section.js │ │ │ ├── OTP-verification │ │ │ │ ├── index.js │ │ │ │ └── form-section.js │ │ │ └── styles.scss │ │ ├── time-sheets │ │ │ └── index.js │ │ ├── sign-in │ │ │ ├── top-section.js │ │ │ ├── styles.scss │ │ │ ├── top-section.scss │ │ │ ├── index.js │ │ │ ├── form-section.scss │ │ │ └── form-section.js │ │ ├── resetPassword │ │ │ ├── styles.scss │ │ │ ├── top-sections.js │ │ │ ├── index.js │ │ │ ├── form-section.scss │ │ │ ├── top-section.scss │ │ │ └── form-section.js │ │ ├── employees │ │ │ ├── styles.scss │ │ │ ├── main-section.js │ │ │ ├── employeeDetails.js │ │ │ ├── index.js │ │ │ └── employee-details.scss │ │ ├── profile │ │ │ ├── index.js │ │ │ ├── main-section.js │ │ │ └── style.scss │ │ ├── tasks-board │ │ │ ├── top-section.js │ │ │ ├── empty-board.js │ │ │ ├── subtask.js │ │ │ ├── Task.js │ │ │ ├── column.js │ │ │ └── index.js │ │ ├── tasks │ │ │ ├── index.js │ │ │ ├── board-card.js │ │ │ ├── welcome-page.js │ │ │ └── styles.scss │ │ ├── projects │ │ │ ├── index.js │ │ │ ├── style.scss │ │ │ └── main-section.js │ │ ├── app │ │ │ ├── index.js │ │ │ └── all-routes.js │ │ └── dashboard │ │ │ ├── index.js │ │ │ ├── top-section.js │ │ │ └── main-section.js │ ├── components │ │ ├── loader │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── meta-tags │ │ │ └── index.js │ │ ├── dropdown │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── button │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── card │ │ │ ├── index.js │ │ │ └── styles.scss │ │ ├── modal │ │ │ ├── index.js │ │ │ └── style.scss │ │ ├── input │ │ │ ├── index.js │ │ │ └── styles.scss │ │ ├── ellipsis-menu │ │ │ ├── styles.scss │ │ │ └── ElipsisMenu.js │ │ ├── header │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── otp │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── name │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── email │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── password │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── delete-modal │ │ │ ├── DeleteModal.js │ │ │ └── styles.scss │ │ ├── create-board-modal │ │ │ └── styles.scss │ │ ├── navbar │ │ │ └── style.scss │ │ └── table │ │ │ └── styles.scss │ ├── index.js │ ├── redux │ │ ├── store.js │ │ └── reducers │ │ │ ├── employee.js │ │ │ ├── other.js │ │ │ └── board.js │ ├── data │ │ ├── projectsData.js │ │ ├── signup-data │ │ │ ├── otp-verification.json │ │ │ └── signup.json │ │ ├── employeesData.js │ │ ├── profileData.js │ │ ├── signin.json │ │ ├── tasksData.js │ │ ├── newTaskForm.js │ │ ├── departmentsData.js │ │ ├── navbarData.js │ │ ├── resetPasswordData.json │ │ ├── taskDetails.js │ │ ├── dashboardData.js │ │ └── employeeDetailsData.json │ └── index.css ├── public │ ├── logo.png │ ├── com_logo.png │ ├── assests │ │ ├── project.jpg │ │ └── kanban-background.jpg │ └── index.html ├── Documentation.md ├── .gitignore ├── package.json └── README.md ├── website ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── setupTests.js │ ├── App.test.js │ ├── index.css │ ├── reportWebVitals.js │ ├── index.js │ ├── App.js │ ├── App.css │ └── logo.svg ├── .gitignore ├── package.json └── README.md ├── server ├── server.js ├── Middilewares │ ├── checkOTP.js │ ├── checkLogin.js │ └── jwt_auth.js ├── db │ ├── connect.js │ ├── departmentModel.js │ ├── reportModel.js │ ├── projectModel.js │ ├── employeeModel.js │ ├── taskBoardModel.js │ └── taskModel.js ├── utils │ ├── sendOtp.js │ ├── apiFeatures.js │ └── replaceIdKey.js ├── Routers │ ├── reportRoutes.js │ ├── projectRoutes.js │ ├── departmentRoutes.js │ ├── taskBoardRoutes.js │ ├── taskRoutes.js │ └── employeeRoutes.js ├── html-template │ └── send-otp-template.js ├── package.json ├── send-in-blue │ └── index.js ├── app.js ├── Scripts │ └── index.js ├── .gitignore └── Controllers │ ├── reportController.js │ ├── department.js │ ├── project.js │ └── taskBoard.js ├── .github └── workflows │ └── client.yml └── README.md /CNAME: -------------------------------------------------------------------------------- 1 | tote-web.com -------------------------------------------------------------------------------- /client/src/services/login/signout.js: -------------------------------------------------------------------------------- 1 | export const signout = ()=>{ 2 | window.localStorage.clear(); 3 | } -------------------------------------------------------------------------------- /website/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /client/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/logo.png -------------------------------------------------------------------------------- /client/public/com_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/com_logo.png -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/website/public/favicon.ico -------------------------------------------------------------------------------- /website/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/website/public/logo192.png -------------------------------------------------------------------------------- /website/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/website/public/logo512.png -------------------------------------------------------------------------------- /client/src/font/font-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Bold.ttf -------------------------------------------------------------------------------- /client/src/font/Nunito-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/Nunito-Bold.ttf -------------------------------------------------------------------------------- /client/src/font/font-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Light.ttf -------------------------------------------------------------------------------- /client/src/font/font-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Medium.ttf -------------------------------------------------------------------------------- /client/public/assests/project.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/assests/project.jpg -------------------------------------------------------------------------------- /client/src/font/Nunito-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/Nunito-Medium.ttf -------------------------------------------------------------------------------- /client/src/font/Nunito-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/Nunito-Regular.ttf -------------------------------------------------------------------------------- /client/src/font/font-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Regular.ttf -------------------------------------------------------------------------------- /client/src/utils/check-all.js: -------------------------------------------------------------------------------- 1 | export const checkAllTrue = (list) => { 2 | return list.reduce((acc, item) => acc && item, true); 3 | }; -------------------------------------------------------------------------------- /client/public/assests/kanban-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/assests/kanban-background.jpg -------------------------------------------------------------------------------- /client/src/containers/departments/create-depart.scss: -------------------------------------------------------------------------------- 1 | .create-department-formSection form{ 2 | display: flex; 3 | flex-direction: column; 4 | gap: 20px; 5 | } -------------------------------------------------------------------------------- /client/src/components/loader/styles.scss: -------------------------------------------------------------------------------- 1 | .loader-component{ 2 | height: 80vh; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | } -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | require('./db/connect'); 3 | const port = process.env.PORT || 5000; 4 | 5 | app.listen(port, ()=>{ 6 | console.log("server is running on port : ", port); 7 | }) -------------------------------------------------------------------------------- /client/src/containers/reports/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Reports = () => { 4 | return ( 5 |
6 | comming soon 7 |
8 | ) 9 | } 10 | 11 | export default Reports 12 | -------------------------------------------------------------------------------- /client/src/components/meta-tags/index.js: -------------------------------------------------------------------------------- 1 | import { Helmet } from 'react-helmet'; 2 | 3 | const MetaTags = (props) => { 4 | return 5 | {props.title} 6 | 7 | }; 8 | 9 | export default MetaTags; -------------------------------------------------------------------------------- /client/src/containers/settings/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Settings = () => { 4 | return ( 5 |
6 | comming soon 7 |
8 | ) 9 | } 10 | 11 | export default Settings 12 | -------------------------------------------------------------------------------- /client/src/containers/sign-up/top-section.js: -------------------------------------------------------------------------------- 1 | const TopSection = (props) => { 2 | return
3 |
{props.title}
4 |
5 | }; 6 | 7 | export default TopSection; -------------------------------------------------------------------------------- /client/src/containers/time-sheets/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const TimeSheets = () => { 4 | return ( 5 |
6 | comming soon 7 |
8 | ) 9 | } 10 | 11 | export default TimeSheets 12 | -------------------------------------------------------------------------------- /client/src/containers/sign-in/top-section.js: -------------------------------------------------------------------------------- 1 | import './top-section.scss'; 2 | 3 | const TopSection = (props) => { 4 | return
5 |
{props.title}
6 |
7 | }; 8 | 9 | export default TopSection; -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './containers/app'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | root.render(); 8 | -------------------------------------------------------------------------------- /client/src/containers/sign-in/styles.scss: -------------------------------------------------------------------------------- 1 | .login-page { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | height: 100vh; 6 | } 7 | 8 | @media only screen and (max-width: 600px) { 9 | .login-page { 10 | background: #f7f7f7; 11 | } 12 | } -------------------------------------------------------------------------------- /website/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /website/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /client/src/containers/resetPassword/styles.scss: -------------------------------------------------------------------------------- 1 | .resetPassword-page { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | height: 100vh; 6 | } 7 | 8 | @media only screen and (max-width: 600px) { 9 | .resetPassword-page { 10 | background: #f7f7f7; 11 | } 12 | } -------------------------------------------------------------------------------- /client/src/utils/email-validator.js: -------------------------------------------------------------------------------- 1 | export const validateEmail = (email) => { 2 | return String(email) 3 | .toLowerCase() 4 | .match( 5 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 6 | ); 7 | }; -------------------------------------------------------------------------------- /client/src/components/loader/index.js: -------------------------------------------------------------------------------- 1 | import Loaders from 'react-js-loader'; 2 | import './styles.scss'; 3 | 4 | const Loader = ()=>{ 5 | return
6 | 7 |
8 | } 9 | 10 | export default Loader; -------------------------------------------------------------------------------- /client/src/containers/resetPassword/top-sections.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './top-section.scss'; 3 | 4 | const TopSection = (props) => { 5 | return
6 |
{props.title}
7 |
8 | }; 9 | 10 | export default TopSection 11 | -------------------------------------------------------------------------------- /client/src/utils/isHeaderHidden.js: -------------------------------------------------------------------------------- 1 | export const isHeaderHidden = (location) =>{ 2 | const authRoutes = ['login', 'signup', 'reset-password', 'otp-verification']; 3 | const isValid = authRoutes.find((route)=> (location.split('/')[1] === route)); 4 | if(isValid){ 5 | return true; 6 | } 7 | return false; 8 | } -------------------------------------------------------------------------------- /server/Middilewares/checkOTP.js: -------------------------------------------------------------------------------- 1 | const checkOtpData = (req, res, next)=>{ 2 | const {email} = req.body; 3 | if(!email){ 4 | res.status(400).json({ 5 | status: 'failed', 6 | message: '\email\ is required' 7 | }) 8 | }else{ 9 | next(); 10 | } 11 | } 12 | 13 | module.exports = checkOtpData; -------------------------------------------------------------------------------- /server/db/connect.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const db = process.env.DB_CLOUD_LINK.replace( 4 | '', 5 | process.env.DB_PASSWORD 6 | ); 7 | 8 | mongoose.connect(db).then(()=>{ 9 | console.log("DB succesfully connected!"); 10 | }).catch((error)=>{ 11 | console.log("Error in db", error.message); 12 | }) -------------------------------------------------------------------------------- /client/src/utils/localStorage.js: -------------------------------------------------------------------------------- 1 | export const setLocalStorageKey = (key, value)=>{ 2 | localStorage.setItem(key, value); 3 | return true; 4 | } 5 | 6 | export const getLocalStorageKey = (key) =>{ 7 | return localStorage.getItem(key); 8 | } 9 | 10 | export const removeLocalStorageKey = (key) =>{ 11 | localStorage.removeItem(key); 12 | return true; 13 | } -------------------------------------------------------------------------------- /client/src/utils/fetchUrl.js: -------------------------------------------------------------------------------- 1 | export const fetchUrl = async (url, options = {}) => { 2 | try { 3 | const response = await fetch(url, options); 4 | const data = await response.json(); 5 | return data; 6 | } catch (error) { 7 | console.log('API failed', url); 8 | console.log('API failed', error); 9 | return { error }; 10 | } 11 | }; -------------------------------------------------------------------------------- /server/db/departmentModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const department = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: [true, "A department should have a title."] 7 | }, 8 | url: { 9 | type: String, 10 | unique: true 11 | } 12 | }); 13 | 14 | module.exports = mongoose.model('departments', department); -------------------------------------------------------------------------------- /client/src/components/dropdown/styles.scss: -------------------------------------------------------------------------------- 1 | .dropdown-component .label { 2 | color: #000; 3 | font-weight: bold; 4 | margin-bottom: 4px; 5 | } 6 | 7 | .dropdown-component .dropdown-wrapper select{ 8 | width: 100%; 9 | height: auto; 10 | font-size: 16px; 11 | color: #4e4949; 12 | padding: 8px; 13 | border-radius: 5px; 14 | text-transform: capitalize; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/containers/employees/styles.scss: -------------------------------------------------------------------------------- 1 | .employees-page{ 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | .employees-container{ 6 | width: 100%; 7 | margin: 3rem 1rem; 8 | } 9 | } 10 | 11 | @media screen and (max-width: 700px) { 12 | .employees-page{ 13 | .employees-container{ 14 | margin: 3rem 0; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /website/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { navbarReducer } from './reducers/other'; 3 | import { employeeReducer } from './reducers/employee'; 4 | import { boardReducer } from './reducers/board'; 5 | 6 | const store = configureStore({ 7 | reducer : { 8 | navbar: navbarReducer, 9 | employee: employeeReducer, 10 | boards: boardReducer 11 | } 12 | }); 13 | 14 | export default store; -------------------------------------------------------------------------------- /website/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /client/src/redux/reducers/employee.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const employeeSlice = createSlice({ 4 | name: "employee", 5 | initialState: { 6 | loggedInEmployee: {}, 7 | }, 8 | reducers: { 9 | setLoggedInEmployee: (state, action) => { 10 | state.loggedInEmployee = action.payload; 11 | } 12 | } 13 | }) 14 | 15 | export const employeeReducer = employeeSlice.reducer; 16 | export const employeeActions = employeeSlice.actions; -------------------------------------------------------------------------------- /client/src/components/button/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './style.scss'; 3 | 4 | const Button = (props) => { 5 | return ( 6 |
7 | 14 |
15 | ) 16 | } 17 | 18 | export default Button 19 | -------------------------------------------------------------------------------- /client/src/components/card/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.scss'; 3 | 4 | const Card = (props) => { 5 | return ( 6 |
7 |
8 |
{props.title}
9 |
10 | {props.children} 11 |
12 |
13 |
14 | ) 15 | } 16 | 17 | export default Card 18 | -------------------------------------------------------------------------------- /server/utils/sendOtp.js: -------------------------------------------------------------------------------- 1 | const nodeMailer = require('nodemailer'); 2 | require('dotenv').config(); 3 | 4 | const transporter = nodeMailer.createTransport({ 5 | host: process.env.SMTP_HOST, 6 | port: process.env.SMTP_PORT, 7 | auth: { 8 | user: process.env.SMTP_USER, 9 | pass: process.env.SMTP_PASS 10 | } 11 | }); 12 | 13 | const generateOtp = ()=>{ 14 | return Math.floor(100000 + Math.random() * 900000); 15 | } 16 | 17 | module.exports = {transporter, generateOtp}; -------------------------------------------------------------------------------- /client/src/data/projectsData.js: -------------------------------------------------------------------------------- 1 | export const mapProjectsData = (projects = []) => { 2 | return { 3 | metaData: { 4 | title: "Projects | TOTE" 5 | }, 6 | mainSection: { 7 | activeProjectsList: projects?.filter((project) => project.status === 'active') || [], 8 | completedProjectsList: projects?.filter((project) => project.status === 'completed') || [], 9 | rejectedProjectsList: projects?.filter((project) => project.status === 'completed') || [], 10 | } 11 | } 12 | }; -------------------------------------------------------------------------------- /client/src/components/card/styles.scss: -------------------------------------------------------------------------------- 1 | .card-component{ 2 | width: fit-content; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | .card-wrapper{ 7 | width: 100%; 8 | padding: 8px 4px; 9 | background-color: #fff; 10 | -webkit-box-shadow: 1px 4px 7px 1px rgba(148,142,148,1); 11 | -moz-box-shadow: 1px 4px 7px 1px rgba(148,142,148,1); 12 | box-shadow: 1px 4px 7px 1px rgba(148,142,148,1); 13 | border-radius: 10%; 14 | } 15 | } -------------------------------------------------------------------------------- /client/src/services/login/forgotpassword.js: -------------------------------------------------------------------------------- 1 | import { fetchUrl } from "../../utils/fetchUrl"; 2 | 3 | export const forgotPassword = async (email)=>{ 4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/forgotPassword'; 5 | const headers = new Headers(); 6 | headers.append('Content-Type', 'application/json'); 7 | const body = { email }; 8 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect: 'follow' }; 9 | return await fetchUrl(url, requestOptions); 10 | } -------------------------------------------------------------------------------- /server/Middilewares/checkLogin.js: -------------------------------------------------------------------------------- 1 | const checkLoginData = (req, res, next)=>{ 2 | const {email, password} = req.body; 3 | if(!email){ 4 | res.status(400).json({ 5 | status: 'failed', 6 | message: "\email\ is required" 7 | }) 8 | }else if(!password){ 9 | res.status(400).json({ 10 | status: 'failed', 11 | message: '\password\ is required' 12 | }) 13 | }else{ 14 | next(); 15 | } 16 | } 17 | 18 | module.exports = checkLoginData; -------------------------------------------------------------------------------- /server/Routers/reportRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const verifyToken = require('../Middilewares/jwt_auth'); 4 | const reportControllers = require('../Controllers/reportController'); 5 | 6 | router.route('/create-report').post(verifyToken, reportControllers.createReport); 7 | router.route('/all-reports').get(verifyToken, reportControllers.getAllReports); 8 | router.route('/delete-report').delete(verifyToken, reportControllers.deleteReport); 9 | 10 | module.exports = router; -------------------------------------------------------------------------------- /client/src/services/login/signin.js: -------------------------------------------------------------------------------- 1 | import {fetchUrl} from '../../utils/fetchUrl'; 2 | 3 | export const signin = async (email, password) =>{ 4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/login'; 5 | const headers = new Headers(); 6 | headers.append('Content-Type', 'application/json'); 7 | const body = { 8 | email, 9 | password 10 | }; 11 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect: 'follow' }; 12 | return await fetchUrl(url, requestOptions); 13 | } -------------------------------------------------------------------------------- /client/src/services/login/resetpassword.js: -------------------------------------------------------------------------------- 1 | import { fetchUrl } from "../../utils/fetchUrl"; 2 | 3 | export const resetPassword = async (email, otp, password) =>{ 4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/resetPassword';; 5 | const headers = new Headers(); 6 | headers.append('Content-Type', 'application/json'); 7 | const body = { email, otp, newPassword: password }; 8 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect: 'follow' }; 9 | return await fetchUrl(url, requestOptions); 10 | } -------------------------------------------------------------------------------- /client/src/containers/employees/main-section.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useNavigate } from 'react-router-dom'; 3 | import TableComponent from '../../components/table'; 4 | 5 | const MainSection = (props) => { 6 | const navigate = useNavigate(); 7 | 8 | const onClickRow = (row)=>{ 9 | navigate(`/employees/${row.id}`); 10 | } 11 | 12 | return ( 13 |
14 | 15 |
16 | ) 17 | } 18 | export default MainSection; 19 | -------------------------------------------------------------------------------- /server/html-template/send-otp-template.js: -------------------------------------------------------------------------------- 1 | module.exports.sendOTPTemplate = (otp, title) => (` 2 | 3 |
4 |

5 | TOTEWEB 6 |

7 |
8 |
9 |

${title}

10 |

${otp}

11 |
12 | 13 | `); -------------------------------------------------------------------------------- /client/Documentation.md: -------------------------------------------------------------------------------- 1 | #employees 2 | -> list all the employees 3 | -> search an employee 4 | -> update the employee details 5 | -> delete the employee details 6 | 7 | #departments 8 | -> list all the departments 9 | -> search a department 10 | -> create new department 11 | 12 | #tasks 13 | -> a todo list 14 | -> list out upcomming , current tasks and completed tasks 15 | -> like a kanban board 16 | -> divide into three sections, upcomming, current , and completed 17 | 18 | #projects 19 | -> list all the projects which is made by me 20 | -> a project with its documentation and url 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /client/src/containers/resetPassword/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MetaTags from '../../components/meta-tags'; 3 | import TopSection from './top-sections'; 4 | import FormSection from './form-section'; 5 | 6 | import './styles.scss'; 7 | 8 | const ResetPassword = (props) => { 9 | return
10 |
11 | 12 | 13 | 14 |
15 |
16 | } 17 | 18 | export default ResetPassword 19 | -------------------------------------------------------------------------------- /server/Routers/projectRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const projectControllers = require('../Controllers/project'); 3 | const verifyToken = require('../Middilewares/jwt_auth'); 4 | const router = express.Router(); 5 | 6 | router.route('/create-project').post(verifyToken, projectControllers.createProject); 7 | router.route('/list-projects').get(verifyToken, projectControllers.listProjects); 8 | router.route('/project').get(verifyToken, projectControllers.getProjectDetails); 9 | router.route('/update-project').post(verifyToken, projectControllers.updateProject); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /website/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /client/src/components/modal/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './style.scss'; 3 | 4 | const Modal = (props) => { 5 | return ( 6 |
7 |
8 |
9 |
{props.title}
10 | × 11 |
12 |
13 | {props.children} 14 |
15 |
16 |
17 | ) 18 | } 19 | 20 | export default Modal 21 | -------------------------------------------------------------------------------- /website/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 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /server/Routers/departmentRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const verifyToken = require('../Middilewares/jwt_auth'); 4 | const departControllers = require('../Controllers/department'); 5 | 6 | router.route('/create-department').post(verifyToken, departControllers.createDepartment); 7 | router.route('/all-departments').get(verifyToken, departControllers.getAllDepartments); 8 | router.route('/delete-department').delete(verifyToken, departControllers.deleteDepartment); 9 | router.route('/department-count').get(verifyToken, departControllers.getDepartmentCount); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /website/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from './logo.svg'; 2 | import './App.css'; 3 | 4 | function App() { 5 | return ( 6 |
7 |
8 | logo 9 |

10 | Edit src/App.js and save to reload. 11 |

12 | 18 | Learn React 19 | 20 |
21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tote-backend", 3 | "version": "1.0.0", 4 | "description": "This is backend for tote application", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "nodemon server.js" 8 | }, 9 | "author": "Sumit baghel", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "nodemon": "^2.0.20" 13 | }, 14 | "dependencies": { 15 | "axios": "^1.4.0", 16 | "bcrypt": "^5.1.0", 17 | "cors": "^2.8.5", 18 | "dotenv": "^16.0.3", 19 | "express": "^4.18.2", 20 | "jsonwebtoken": "^9.0.0", 21 | "mongoose": "^6.8.3", 22 | "morgan": "^1.10.0", 23 | "nodemailer": "^6.9.0" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/redux/reducers/other.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { isHeaderHidden } from "../../utils/isHeaderHidden"; 3 | 4 | const navbarSlice = createSlice({ 5 | name: "navbar", 6 | initialState: { 7 | isActive: false, 8 | isHidden: isHeaderHidden(window.location.pathname), 9 | }, 10 | reducers: { 11 | setIsActive: (state, action) => { 12 | state.isActive = action.payload; 13 | }, 14 | setHeaderHidden: (state, action) => { 15 | state.isHidden = action.payload; 16 | } 17 | } 18 | }) 19 | 20 | export const navbarReducer = navbarSlice.reducer; 21 | export const navbarActions = navbarSlice.actions; -------------------------------------------------------------------------------- /server/Routers/taskBoardRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const verifyToken = require('../Middilewares/jwt_auth'); 4 | const boardControllers = require('../Controllers/taskBoard'); 5 | 6 | router.route('/boards').post(verifyToken, boardControllers.createBoard); 7 | router.route('/boards').get(verifyToken, boardControllers.getAllBoards); 8 | router.route('/boards/:id').get(verifyToken, boardControllers.getBoardById); 9 | router.route('/boards/:id').put(verifyToken, boardControllers.updateBoardById); 10 | router.route('/boards/:id').delete(verifyToken, boardControllers.deleteBoardById); 11 | 12 | module.exports = router; -------------------------------------------------------------------------------- /client/src/containers/sign-in/top-section.scss: -------------------------------------------------------------------------------- 1 | .login-page{ 2 | 3 | .login-container { 4 | width: 700px; 5 | padding: 40px 30px; 6 | background: rgb(247 247 247); 7 | border-radius: 5px; 8 | box-shadow: 0px 2px 10px #848181; 9 | } 10 | 11 | } 12 | 13 | .login-page{ 14 | .main-title{ 15 | font-size: 26px; 16 | font-weight: bold; 17 | } 18 | } 19 | 20 | .login-page{ 21 | .login-top { 22 | text-align: center; 23 | margin-bottom: 16px; 24 | } 25 | } 26 | 27 | @media only screen and (max-width: 700px) { 28 | .login-page { 29 | .login-container { 30 | padding: 16px; 31 | box-shadow: none; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /client/src/containers/sign-up/pre-signup/index.js: -------------------------------------------------------------------------------- 1 | import MetaTags from '../../../components/meta-tags'; 2 | import preSignupData from '../../../data/signup-data/signup.json'; 3 | import TopSection from '../top-section'; 4 | import FormSection from './form-section'; 5 | 6 | import '../styles.scss'; 7 | 8 | const Signup = () => { 9 | const { formSection, metaData, topSection } = preSignupData; 10 | 11 | return
12 |
13 | 14 | 15 | 16 |
17 |
18 | }; 19 | 20 | export default Signup; -------------------------------------------------------------------------------- /server/Routers/taskRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const verifyToken = require('../Middilewares/jwt_auth'); 4 | const taskControllers = require('../Controllers/taskController'); 5 | 6 | router.route('/create-task/:boardId/columns/:columnId/tasks').post(verifyToken, taskControllers.createTask); 7 | router.route('/update-task/:boardId/columns/:columnId/tasks/:taskId').post(verifyToken, taskControllers.updateTask); 8 | router.route('/update-task/:boardId/tasks/:taskId').post(verifyToken, taskControllers.dragTask); 9 | router.route('/delete-task/:boardId/tasks/:taskId').delete(verifyToken, taskControllers.deleteTask); 10 | 11 | module.exports = router; -------------------------------------------------------------------------------- /server/db/reportModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const schema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: [ true, "A report must have a title." ] 7 | }, 8 | description: { 9 | type: String, 10 | required: [ true, "A report must have a description." ] 11 | }, 12 | sent_by: { 13 | type: mongoose.Schema.Types.ObjectId, 14 | ref: 'employees' 15 | }, 16 | sent_to: { 17 | type: mongoose.Schema.Types.ObjectId, 18 | ref: 'employees' 19 | } 20 | }, { 21 | timestamps: true 22 | }); 23 | 24 | const Report = mongoose.model("reports", schema); 25 | 26 | module.exports = Report; -------------------------------------------------------------------------------- /client/src/containers/sign-up/OTP-verification/index.js: -------------------------------------------------------------------------------- 1 | 2 | import MetaTags from '../../../components/meta-tags'; 3 | import otpVerificationData from '../../../data/signup-data/otp-verification.json'; 4 | import TopSection from '../top-section'; 5 | import FormSection from './form-section'; 6 | 7 | import '../styles.scss'; 8 | 9 | const OTP_VERIFICATION = () => { 10 | const { topSection, formSection, metaData } = otpVerificationData; 11 | 12 | return
13 |
14 | 15 | 16 | 17 |
18 |
19 | }; 20 | 21 | export default OTP_VERIFICATION; -------------------------------------------------------------------------------- /client/src/containers/resetPassword/form-section.scss: -------------------------------------------------------------------------------- 1 | .resetPassword-page { 2 | .form-section { 3 | margin-top: 16px; 4 | } 5 | } 6 | 7 | .resetPassword-page { 8 | .form-section { 9 | .email-component { 10 | margin-bottom: 24px; 11 | } 12 | .otp-component { 13 | margin-bottom: 24px; 14 | } 15 | .btn{ 16 | margin: 24px 0px; 17 | } 18 | } 19 | } 20 | 21 | .resetPassword-page { 22 | .bottom-text { 23 | font-size: 16px; 24 | text-align: center; 25 | margin-top: 16px; 26 | font-weight: bold; 27 | } 28 | } 29 | 30 | .resetPassword-page { 31 | .bottom-text { 32 | a { 33 | text-decoration: none; 34 | margin-left: 8px; 35 | color: #086f25; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /client/src/utils/date-handler.js: -------------------------------------------------------------------------------- 1 | export const getInputDate = (date) => { 2 | const newDate = new Date(date); 3 | let day = newDate.getDate(); 4 | day = day < 10 ? '0' + day : day; 5 | let month = newDate.getMonth() + 1; 6 | month = month < 10 ? '0' + month : month; 7 | return `${newDate.getFullYear()}-${month}-${day}`; 8 | } 9 | 10 | export const getProgressPercentage = (startDate, endDate) => { 11 | const start = new Date(startDate); 12 | const end = new Date(endDate); 13 | const now = new Date(); 14 | const timeDiff = now.getTime() - start.getTime(); 15 | const totalTimeDiff = end.getTime() - start.getTime(); 16 | const percentage = (timeDiff / totalTimeDiff) * 100; 17 | return Math.floor(percentage); 18 | } -------------------------------------------------------------------------------- /client/src/containers/sign-in/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import TopSection from "./top-section"; 3 | import MetaTags from "../../components/meta-tags"; 4 | import FormSection from "./form-section"; 5 | import Loader from '../../components/loader'; 6 | import './styles.scss'; 7 | 8 | const Login = (props) => { 9 | const [isLoading, setIsLoading] = useState(false); 10 | 11 | return ( isLoading ? : 12 |
13 |
14 | 15 | 16 | 17 |
18 |
) 19 | }; 20 | 21 | export default Login; -------------------------------------------------------------------------------- /server/send-in-blue/index.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios'); 2 | 3 | const sendMail = async (to, subject, htmlContent) => { 4 | console.log({ to, subject, htmlContent }); 5 | const data = { 6 | sender: { 7 | name: 'no reply', 8 | email: process.env.EMAIL 9 | }, 10 | to, 11 | subject, 12 | htmlContent 13 | }; 14 | 15 | try { 16 | const response = await axios.post(process.env.SEND_IN_BLUE_URL, data, { 17 | headers: { 18 | 'Accept': 'application/json', 19 | 'API-Key': process.env.SEND_IN_BLUE_API_KEY, 20 | 'Content-Type': 'application/json' 21 | } 22 | }); 23 | } catch (error) { 24 | throw new Error(error); 25 | } 26 | } 27 | 28 | module.exports = { sendMail }; -------------------------------------------------------------------------------- /website/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /client/src/containers/resetPassword/top-section.scss: -------------------------------------------------------------------------------- 1 | .resetPassword-page{ 2 | 3 | .resetPassword-container { 4 | width: 700px; 5 | padding: 40px 30px; 6 | background: rgb(247 247 247); 7 | border-radius: 5px; 8 | box-shadow: 0px 2px 10px #848181; 9 | } 10 | 11 | } 12 | 13 | .resetPassword-page{ 14 | .main-title{ 15 | font-size: 26px; 16 | font-weight: bold; 17 | } 18 | } 19 | 20 | .resetPassword-page{ 21 | .resetPassword-top { 22 | text-align: center; 23 | margin-bottom: 16px; 24 | } 25 | } 26 | 27 | @media only screen and (max-width: 700px) { 28 | .resetPassword-page { 29 | .resetPassword-container { 30 | padding: 16px; 31 | box-shadow: none; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /client/src/data/signup-data/otp-verification.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaData": { 3 | "title": "OTP | TOTE" 4 | }, 5 | "topSection": { 6 | "title": "OTP Verification" 7 | }, 8 | "formSection": { 9 | "inputComponents": [ 10 | { 11 | "component": "otp", 12 | "details": { 13 | "label": "Please Enter The 6 Digit OTP", 14 | "placeholder": "Enter The 6 Digit OTP" 15 | } 16 | }, 17 | { 18 | "component": "button", 19 | "details": { 20 | "text": "Verify OTP", 21 | "type": "submit", 22 | "button": "primary" 23 | } 24 | } 25 | ], 26 | "bottomText": "Already a member?", 27 | "linkText": "Login", 28 | "signUpLink": "/login" 29 | } 30 | } -------------------------------------------------------------------------------- /.github/workflows/client.yml: -------------------------------------------------------------------------------- 1 | name: Client Pipeline 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - 'client/**' # Trigger only when code is pushed inside the 'client' folder 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: '14' 21 | 22 | - name: Install dependencies 23 | run: cd client && npm install 24 | 25 | - name: Build client 26 | run: | 27 | cd client 28 | npm run build 29 | env: 30 | REACT_APP_BASE_URI: ${{ secrets.REACT_APP_BASE_URI }} 31 | -------------------------------------------------------------------------------- /client/src/containers/employees/employeeDetails.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MetaTags from '../../components/meta-tags'; 3 | import FormSection from './formSection'; 4 | import './employee-details.scss'; 5 | 6 | const TopSection = (props) => { 7 | return
8 |
{props.title}
9 |
10 | } 11 | 12 | const EmployeeDetails = (props) => { 13 | return ( 14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 | ) 22 | } 23 | 24 | export default EmployeeDetails 25 | -------------------------------------------------------------------------------- /server/db/projectModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const projectSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: [ true, 'A project must have a name.' ] 7 | }, 8 | status: { 9 | type: String, 10 | enum: ['active', 'completed', 'rejected'], 11 | default: 'active' 12 | }, 13 | start_date: { 14 | type: Date, 15 | required: [ true, 'A project must have a start_date.' ] 16 | }, 17 | due_date: { 18 | type: Date, 19 | required: [ true, 'A project must have a due_date.' ] 20 | }, 21 | manager_name: { 22 | type: String, 23 | Default: 'manager' 24 | }, 25 | team_members: { 26 | type: [String] 27 | } 28 | }); 29 | 30 | module.exports = mongoose.model('projects', projectSchema); -------------------------------------------------------------------------------- /client/src/services/notifications/index.js: -------------------------------------------------------------------------------- 1 | import { toast } from 'react-toastify'; 2 | 3 | const toastProperty = { 4 | position: "top-right", 5 | autoClose: 3000, 6 | hideProgressBar: true, 7 | closeOnClick: true, 8 | pauseOnHover: true, 9 | draggable: true, 10 | progress: undefined, 11 | theme: "colored", 12 | } 13 | 14 | export const sendSuccessNotification = (message) =>{ 15 | toast.success(message,toastProperty); 16 | } 17 | 18 | export const sendErrorNotification = (message) => { 19 | toast.error(message, toastProperty); 20 | } 21 | 22 | export const sendWarningNotification = (message) => { 23 | toast.warn(message, toastProperty); 24 | } 25 | 26 | export const sendInfoNotification = (message) => { 27 | toast.info(message, toastProperty); 28 | } -------------------------------------------------------------------------------- /client/src/components/input/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import './styles.scss'; 3 | 4 | const SimpleInput = (props) => { 5 | 6 | const onChangeInput = (event) => { 7 | const {value} = event.target; 8 | props.onChange(value); 9 | } 10 | 11 | return
12 |
13 |
14 | {props.label} {props.required && *} 15 |
16 | 22 |
23 |
24 | } 25 | 26 | export default SimpleInput; -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | React App 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /client/src/containers/profile/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | import MetaTags from '../../components/meta-tags'; 4 | import { mapProfileData } from '../../data/profileData'; 5 | import MainSection from './main-section'; 6 | import './style.scss'; 7 | 8 | const Profile = () => { 9 | const employee = useSelector(state => state.employee.loggedInEmployee); 10 | const profileData = mapProfileData(employee); 11 | 12 | return ( 13 |
14 |
15 | 18 | 21 |
22 |
23 | ) 24 | } 25 | 26 | export default Profile 27 | -------------------------------------------------------------------------------- /server/db/employeeModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const employeeSchema = new mongoose.Schema({ 4 | name : String, 5 | email : { 6 | type : String, 7 | required : [true, "An user must have an email."], 8 | unique : true 9 | }, 10 | password : { 11 | type: String 12 | }, 13 | otp: { 14 | type: String 15 | }, 16 | phone:{ 17 | type: String 18 | }, 19 | address:{ 20 | type: String 21 | }, 22 | role:{ 23 | type: String, 24 | default: 'agent' 25 | }, 26 | isVerified: { 27 | type: Boolean, 28 | default: false 29 | }, 30 | verification_otp: { 31 | type: String, 32 | } 33 | }); 34 | 35 | module.exports = mongoose.model('employees', employeeSchema); -------------------------------------------------------------------------------- /client/src/components/ellipsis-menu/styles.scss: -------------------------------------------------------------------------------- 1 | .task-elipsis-menu { 2 | position: absolute; 3 | top: 1.5rem; 4 | right: 1rem; 5 | box-shadow: 1px 0px 3px; 6 | z-index: 50; 7 | .elipsis-content { 8 | display: flex; 9 | justify-content: flex-end; 10 | align-items: center; 11 | .details{ 12 | font-weight: 500; 13 | font-size: 0.875rem; 14 | line-height: 1.25rem; 15 | padding-top: 1.25rem; 16 | padding-bottom: 1.25rem; 17 | padding-right: 3rem; 18 | padding-left: 1rem; 19 | background-color: var(--white); 20 | width: 10rem; 21 | height: auto; 22 | z-index: 50; 23 | .edit-type{ 24 | cursor: pointer; 25 | } 26 | .delete-type { 27 | cursor: pointer; 28 | color: var(--red); 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /server/db/taskBoardModel.js: -------------------------------------------------------------------------------- 1 | // schema.js 2 | 3 | const mongoose = require('mongoose'); 4 | 5 | // Define the subtask schema 6 | const subtaskSchema = new mongoose.Schema({ 7 | title: String, 8 | isCompleted: Boolean, 9 | }); 10 | 11 | // Define the task schema 12 | const taskSchema = new mongoose.Schema({ 13 | title: String, 14 | description: String, 15 | status: String, 16 | subtasks: [subtaskSchema], 17 | }); 18 | 19 | // Define the column schema 20 | const columnSchema = new mongoose.Schema({ 21 | name: String, 22 | tasks: [taskSchema], 23 | }); 24 | 25 | // Define the main board schema 26 | const boardSchema = new mongoose.Schema({ 27 | name: String, 28 | columns: [columnSchema], 29 | }); 30 | 31 | // Create the Board model 32 | const Board = mongoose.model('Boards', boardSchema); 33 | 34 | module.exports = Board; -------------------------------------------------------------------------------- /client/src/data/employeesData.js: -------------------------------------------------------------------------------- 1 | 2 | export const mapEmployeesData = (data=[])=>{ 3 | return { 4 | metaData: { 5 | title: "Employees | TOTE" 6 | }, 7 | mainSection:{ 8 | 9 | employeesTable: { 10 | isFilters: true, 11 | isPagination: true, 12 | rowLabels: ['Full Name', 'Email id', 'Phone Number', 'Address', 'Role', 'Action'], 13 | rows: data?.map((item) => ({ 14 | action: 'Update', 15 | id: item?._id, 16 | cells: [ 17 | { value: item?.name ?? '-' }, 18 | { value: item?.email ?? '-' }, 19 | { value: item?.phone ?? '-' }, 20 | { value: item?.address ?? '-' }, 21 | { value: item?.role ?? '-' }, 22 | ], 23 | })), 24 | }, 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /server/Middilewares/jwt_auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | require('dotenv').config(); 3 | 4 | const verifyToken = (req, res, next) =>{ 5 | const token = req.headers.token; 6 | 7 | if(token === null){ 8 | 9 | return res.status(403).json({ 10 | status : "TokenNotAvailable", 11 | message : "Token is required please provide it!" 12 | }) 13 | } 14 | 15 | try{ 16 | 17 | const decode = jwt.verify(token, process.env.ACCESS_TOKEN); 18 | 19 | req.employee = decode; 20 | 21 | }catch(error){ 22 | return res.status(401).json({ 23 | status : "unauthorized", 24 | message : error.message, 25 | expiredAt: error.expiredAt 26 | }) 27 | } 28 | return next(); 29 | } 30 | 31 | module.exports = verifyToken; -------------------------------------------------------------------------------- /client/src/data/profileData.js: -------------------------------------------------------------------------------- 1 | export const mapProfileData = (data = {}) => { 2 | return { 3 | metaData: { 4 | title: "Profile | TOTE" 5 | }, 6 | mainSection: { 7 | userDetails: [ 8 | { 9 | field: "full name", value: data?.name || '' 10 | }, 11 | { 12 | field: "email id", value: data?.email || '' 13 | }, 14 | { 15 | field: "phone number", value: data?.phone || '' 16 | }, 17 | { 18 | field: "address", value: data?.address || '' 19 | }, 20 | { 21 | field: "organization role", value: data?.role || '' 22 | } 23 | ] 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /client/src/components/button/style.scss: -------------------------------------------------------------------------------- 1 | .btn{ 2 | button{ 3 | cursor: pointer; 4 | width: 100%; 5 | height: 100%; 6 | font-family: sans-serif; 7 | font-weight: 500; 8 | font-size: 16px; 9 | letter-spacing: 1px; 10 | padding: 0.8rem 0; 11 | border: none; 12 | border-radius: 5px; 13 | text-transform: capitalize; 14 | background-color: #efe7e7; 15 | } 16 | &.primary{ 17 | button{ 18 | background-color: var(--blue); 19 | color: #fff; 20 | } 21 | } 22 | &.success{ 23 | button{ 24 | background-color: #FF8B13; 25 | } 26 | } 27 | &.danger{ 28 | button{ 29 | color: #fff; 30 | background-color: #dc3545; 31 | border-color: #dc3545; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /client/src/containers/profile/main-section.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CustomInput = (props) => ( 4 |
5 |
{props.field}
6 |
{props.value}
7 |
8 | ) 9 | 10 | const Details = (props) => ( 11 |
12 | {props.details.map((item, index)=>( 13 | 17 | ))} 18 |
19 | ) 20 | 21 | const MainSection = (props) => { 22 | return ( 23 |
24 |
25 | 26 |
27 |
30 |
31 | ) 32 | } 33 | 34 | export default MainSection -------------------------------------------------------------------------------- /client/src/containers/sign-in/form-section.scss: -------------------------------------------------------------------------------- 1 | .login-page { 2 | .form-section { 3 | margin-top: 16px; 4 | } 5 | } 6 | 7 | .login-page { 8 | .form-section { 9 | .email-component { 10 | margin-bottom: 24px; 11 | } 12 | .btn{ 13 | margin: 24px 0px; 14 | } 15 | } 16 | } 17 | 18 | .login-page { 19 | .signup-link { 20 | display: block; 21 | text-align: center; 22 | text-decoration: none; 23 | font-size: 16px; 24 | font-weight: bold; 25 | color: #086f25; 26 | margin-top: 16px; 27 | } 28 | } 29 | 30 | .login-page { 31 | .bottom-text { 32 | font-size: 16px; 33 | text-align: center; 34 | margin-top: 16px; 35 | font-weight: bold; 36 | } 37 | } 38 | 39 | .login-page { 40 | .bottom-text { 41 | a { 42 | text-decoration: none; 43 | margin-left: 8px; 44 | color: #086f25; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /server/utils/apiFeatures.js: -------------------------------------------------------------------------------- 1 | class ApiFeatures{ 2 | constructor(query, queryStr){ 3 | this.query = query; 4 | this.queryStr = queryStr; 5 | } 6 | 7 | limitFields(){ 8 | if(this.queryStr.fields){ 9 | const fields = this.queryStr.fields.split(',').join(' '); 10 | this.query = this.query.select(fields); 11 | }else{ 12 | this.query = this.query.select('-password -__v -otp'); 13 | } 14 | return this; 15 | } 16 | 17 | pagination(){ 18 | const page = this.queryStr.page * 1 || 1; 19 | const limit = this.queryStr.limit * 1 || 10; 20 | const skip = (page - 1) * limit; // page = 1 limit = 10, [skip = (0 * 10) => 0] 21 | 22 | this.query = this.query.skip(skip).limit(limit); 23 | 24 | return this; 25 | } 26 | } 27 | 28 | module.exports = ApiFeatures; -------------------------------------------------------------------------------- /server/db/taskModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const schema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true 7 | }, 8 | start_date: { 9 | type: Date, 10 | default: Date.now 11 | }, 12 | due_date: { 13 | type: Date, 14 | required: true 15 | }, 16 | status: { 17 | type: String, 18 | enum: ['up next', 'in progress', 'done', 'backlog', 'on hold', 'questions'], 19 | default: 'up next' 20 | }, 21 | assigned_by: { 22 | type: mongoose.Schema.Types.ObjectId, 23 | ref: 'employees' 24 | }, 25 | assigned_to: { 26 | type: mongoose.Schema.Types.ObjectId, 27 | ref: 'employees' 28 | } 29 | }, { 30 | timestamps: true 31 | }); 32 | 33 | const Task = mongoose.model("tasks", schema); 34 | 35 | module.exports = Task; -------------------------------------------------------------------------------- /client/src/components/dropdown/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.scss'; 3 | 4 | const Dropdown = ({ label, value, items, onChange, defaultValue }) => { 5 | 6 | const handleChange = (e) =>{ 7 | const {value} = e.target; 8 | onChange(value, e.target.selectedIndex); 9 | } 10 | return ( 11 |
12 |
{label}
13 |
14 | 20 |
21 |
22 | ) 23 | } 24 | 25 | export default Dropdown; 26 | -------------------------------------------------------------------------------- /client/src/components/ellipsis-menu/ElipsisMenu.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import './styles.scss'; 3 | 4 | const ElipsisMenu = ({ type, setOpenEditModal, setOpenDeleteModal }) => { 5 | return ( 6 |
13 |
14 |
15 |

{ 17 | setOpenEditModal(); 18 | }} 19 | className="edit-type" 20 | > 21 | Edit {type} 22 |

23 | 24 |

setOpenDeleteModal()} 26 | className="delete-type" 27 | > 28 | Delete {type} 29 |

30 |
31 |
32 |
33 | ); 34 | } 35 | 36 | export default ElipsisMenu; 37 | -------------------------------------------------------------------------------- /client/src/components/input/styles.scss: -------------------------------------------------------------------------------- 1 | .simpleInput-component { 2 | .simpleInput-wrapper { 3 | display: flex; 4 | border: 2px solid #B7B78A; 5 | border-radius: 10px; 6 | height: 50px; 7 | position: relative; 8 | padding: 0 0 0 16px; 9 | 10 | .text { 11 | color: #000; 12 | font-weight: 600; 13 | position: absolute; 14 | top: -10px; 15 | background-color: #fff; 16 | padding: 0px 5px; 17 | text-transform: capitalize; 18 | } 19 | 20 | .simpleInput-input { 21 | display: inline-block; 22 | height: 100%; 23 | width: 100%; 24 | border: none; 25 | color: rgb(32, 33, 33); 26 | background-color: transparent; 27 | } 28 | 29 | .simpleInput-input:focus { 30 | outline: none; 31 | } 32 | 33 | } 34 | } -------------------------------------------------------------------------------- /client/src/utils/checkLogin.js: -------------------------------------------------------------------------------- 1 | import { sendErrorNotification } from '../services/notifications'; 2 | import { getLocalStorageKey } from './localStorage'; 3 | import { signout } from '../services/login/signout'; 4 | 5 | export const checkLoginStatus = (location, navigate) =>{ 6 | location = location.pathname; 7 | const now = Date.now(); 8 | const token = getLocalStorageKey('token'); 9 | const email = getLocalStorageKey('email'); 10 | const expiry = getLocalStorageKey('expiry') * 1000; 11 | 12 | const authRoutes = ['login', 'signup', 'reset-password', 'otp-verification']; 13 | const isValid = authRoutes.find((route)=> (location.split('/')[1] === route)); 14 | 15 | if(!isValid){ 16 | if(!(token || email) || (expiry < now)){ 17 | signout(); 18 | navigate('/login'); 19 | sendErrorNotification('Access has expired please login again.'); 20 | return; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const morgan = require('morgan'); 4 | require('dotenv').config(); 5 | const employeeRoutes = require('./Routers/employeeRoutes'); 6 | const departRoutes = require('./Routers/departmentRoutes'); 7 | const taskRoutes = require('./Routers/taskRoutes'); 8 | const reportRoutes = require('./Routers/reportRoutes'); 9 | const projectRoutes = require('./Routers/projectRoutes'); 10 | const taskBoardsRoutes = require('./Routers/taskBoardRoutes'); 11 | const app = express(); 12 | 13 | // middilewares 14 | app.use(express.json()); 15 | app.use(cors()); 16 | app.use(morgan("dev")); 17 | 18 | // routers 19 | app.use('/api/v1/employees', employeeRoutes); 20 | app.use('/api/v1/departments', departRoutes); 21 | app.use('/api/v1/tasks', taskRoutes); 22 | app.use('/api/v1/reports', reportRoutes); 23 | app.use('/api/v1/projects', projectRoutes); 24 | app.use('/api/v1/task-board', taskBoardsRoutes); 25 | 26 | 27 | module.exports = app; -------------------------------------------------------------------------------- /client/src/containers/tasks-board/top-section.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import AddEditTaskModal from './AddEditTaskModal'; 4 | 5 | const TopSection = ({ name, id }) => { 6 | const [isAddTaskModalOpen, setIsAddTaskModalOpen] = useState(false); 7 | const navigate = useNavigate(); 8 | 9 | return ( 10 |
11 | {isAddTaskModalOpen && ( 12 | 17 | )} 18 |
19 |
navigate('/tasks/')}> ←
20 |
{name || 'name'}
21 |
22 | 25 |
26 | ) 27 | } 28 | 29 | export default TopSection; 30 | -------------------------------------------------------------------------------- /server/utils/replaceIdKey.js: -------------------------------------------------------------------------------- 1 | function replaceIdWithKey(obj) { 2 | const stack = [{ data: obj }]; 3 | while (stack.length > 0) { 4 | const currentItem = stack.pop(); 5 | const { data } = currentItem; 6 | 7 | if (data instanceof Array) { 8 | currentItem.replaced = data.map((item) => { 9 | if (item instanceof Object) { 10 | const newObj = {}; 11 | Object.keys(item).forEach((key) => { 12 | const newKey = key === '_id' ? 'id' : key; 13 | newObj[newKey] = item[key]; 14 | }); 15 | return newObj; 16 | } 17 | return item; 18 | }); 19 | } else if (data instanceof Object) { 20 | Object.keys(data).forEach((key) => { 21 | if (data[key] instanceof Array || data[key] instanceof Object) { 22 | stack.push({ data: data[key] }); 23 | } 24 | }); 25 | 26 | data.id = data._id; 27 | delete data._id; 28 | } 29 | } 30 | return obj; 31 | } 32 | 33 | module.exports = { replaceIdWithKey }; 34 | -------------------------------------------------------------------------------- /client/src/data/signin.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaData": { 3 | "title": "Login | TOTE" 4 | }, 5 | "topSection": { 6 | "title": "Sign In" 7 | }, 8 | "formSection": { 9 | "inputComponents": [ 10 | { 11 | "component": "email", 12 | "details": { 13 | "label": "Email Address", 14 | "placeholder": "E.g. xyz@gmail.com" 15 | } 16 | }, 17 | { 18 | "component": "password", 19 | "details": { 20 | "label": "Password", 21 | "placeholder": "********" 22 | } 23 | }, 24 | { 25 | "component": "button", 26 | "details": { 27 | "text": "Login", 28 | "type": "submit", 29 | "button": "primary" 30 | } 31 | }, 32 | { 33 | "component": "link", 34 | "details": { 35 | "text": "Forgot Password?", 36 | "to": "/reset-password" 37 | } 38 | } 39 | ], 40 | "bottomText": "Not a member?", 41 | "linkText": "Create Account", 42 | "signUpLink": "/signup" 43 | } 44 | } -------------------------------------------------------------------------------- /client/src/components/header/styles.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | .ham-icon{ 6 | display: inline-block; 7 | cursor: pointer; 8 | width: 32px; 9 | height: 32px; 10 | margin: 10px; 11 | svg{ 12 | fill: var(--black1); 13 | } 14 | } 15 | .profile-section { 16 | background: var(--blue); 17 | padding: 20px; 18 | border-radius: 0 0 0 20px; 19 | width: max-content; 20 | height: auto; 21 | display: flex; 22 | justify-content: flex-end; 23 | gap: 5px; 24 | align-items: center; 25 | cursor: pointer; 26 | .employee-icon { 27 | display: flex; 28 | justify-content: center; 29 | align-items: center; 30 | padding: 5px; 31 | background: var(--yellow); 32 | border-radius: 50%; 33 | svg { 34 | width: 20px; 35 | fill: var(--gray); 36 | } 37 | } 38 | 39 | span{ 40 | font-size: 16px; 41 | color: #fff; 42 | text-transform: capitalize; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /client/src/containers/tasks/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import WelcomePage from "./welcome-page"; 3 | import './styles.scss'; 4 | import { useDispatch } from "react-redux"; 5 | import { useEffect, useState } from "react"; 6 | import { getAllBoards } from "../../services/tasks/taskBoards"; 7 | import { boardActions } from "../../redux/reducers/board"; 8 | import Loader from "../../components/loader"; 9 | 10 | const Task = () => { 11 | const [isLoading, setIsLoading] = useState(true); 12 | const dispatch = useDispatch(); 13 | 14 | const fetchBoards = async () => { 15 | const result = await getAllBoards(); 16 | if(result.status === 'success'){ 17 | const boards = result.response; 18 | dispatch(boardActions.setBoardsData(boards)); 19 | } 20 | setIsLoading(false); 21 | } 22 | 23 | useEffect(()=>{ 24 | fetchBoards(); 25 | },[]); 26 | 27 | return ( isLoading ? : 28 |
29 | 30 |
31 | ) 32 | } 33 | 34 | export default Task; -------------------------------------------------------------------------------- /client/src/data/signup-data/signup.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaData": { 3 | "title": "SignUp | TOTE" 4 | }, 5 | "topSection": { 6 | "title": "Sign Up" 7 | }, 8 | "formSection": { 9 | "inputComponents": [ 10 | { 11 | "component": "name", 12 | "details": { 13 | "label": "Full Name", 14 | "placeholder": "E.g. John Doe" 15 | } 16 | }, 17 | { 18 | "component": "email", 19 | "details": { 20 | "label": "Email Address", 21 | "placeholder": "E.g. xyz@gmail.com" 22 | } 23 | }, 24 | { 25 | "component": "password", 26 | "details": { 27 | "label": "Password", 28 | "placeholder": "********" 29 | } 30 | }, 31 | { 32 | "component": "button", 33 | "details": { 34 | "text": "Send OTP", 35 | "type": "submit", 36 | "button": "primary" 37 | } 38 | } 39 | ], 40 | "bottomText": "Already a member?", 41 | "linkText": "Login", 42 | "signUpLink": "/login" 43 | } 44 | } -------------------------------------------------------------------------------- /client/src/components/otp/styles.scss: -------------------------------------------------------------------------------- 1 | .otp-component { 2 | .otp-wrapper { 3 | display: flex; 4 | border: 2px solid #B7B78A; 5 | border-radius: 10px; 6 | height: 50px; 7 | position: relative; 8 | padding: 0 0 0 16px; 9 | .text{ 10 | color: #000; 11 | font-weight: 600; 12 | position: absolute; 13 | top: -10px; 14 | background-color: #fff; 15 | padding: 0px 5px; 16 | } 17 | .otp-input{ 18 | display: inline-block; 19 | height: 100%; 20 | width: 100%; 21 | border: none; 22 | color: rgb(32, 33, 33); 23 | background-color: transparent; 24 | } 25 | .otp-input:focus{ 26 | outline: none; 27 | } 28 | 29 | } 30 | .error-icon { 31 | padding-right: 8px; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | opacity: 0; 36 | svg{ 37 | fill: #e85347; 38 | } 39 | } 40 | &.error { 41 | .error-icon { 42 | opacity: 1; 43 | } 44 | .otp-wrapper{ 45 | border-color: #e85347; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/src/components/name/styles.scss: -------------------------------------------------------------------------------- 1 | .name-component { 2 | .name-wrapper { 3 | display: flex; 4 | border: 2px solid #B7B78A; 5 | border-radius: 10px; 6 | height: 50px; 7 | position: relative; 8 | padding: 0 0 0 16px; 9 | .text{ 10 | color: #000; 11 | font-weight: 600; 12 | position: absolute; 13 | top: -10px; 14 | background-color: #fff; 15 | padding: 0px 5px; 16 | } 17 | .name-input{ 18 | display: inline-block; 19 | height: 100%; 20 | width: 100%; 21 | border: none; 22 | color: rgb(32, 33, 33); 23 | background-color: transparent; 24 | } 25 | .name-input:focus{ 26 | outline: none; 27 | } 28 | 29 | } 30 | .error-icon { 31 | padding-right: 8px; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | opacity: 0; 36 | svg{ 37 | fill: #e85347; 38 | } 39 | } 40 | &.error { 41 | .error-icon { 42 | opacity: 1; 43 | } 44 | .name-wrapper{ 45 | border-color: #e85347; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/src/components/email/styles.scss: -------------------------------------------------------------------------------- 1 | .email-component { 2 | .email-wrapper { 3 | display: flex; 4 | border: 2px solid #B7B78A; 5 | border-radius: 10px; 6 | height: 50px; 7 | position: relative; 8 | padding: 0 0 0 16px; 9 | .text{ 10 | color: #000; 11 | font-weight: 600; 12 | position: absolute; 13 | top: -10px; 14 | background-color: #fff; 15 | padding: 0px 5px; 16 | } 17 | .email-input{ 18 | display: inline-block; 19 | height: 100%; 20 | width: 100%; 21 | border: none; 22 | color: rgb(32, 33, 33); 23 | background-color: transparent; 24 | } 25 | .email-input:focus{ 26 | outline: none; 27 | } 28 | 29 | } 30 | .error-icon { 31 | padding-right: 8px; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | opacity: 0; 36 | svg{ 37 | fill: #e85347; 38 | } 39 | } 40 | &.error { 41 | .error-icon { 42 | opacity: 1; 43 | } 44 | .email-wrapper{ 45 | border-color: #e85347; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/src/services/employees/allEmployees.js: -------------------------------------------------------------------------------- 1 | import { fetchUrl } from "../../utils/fetchUrl"; 2 | import { getLocalStorageKey } from "../../utils/localStorage"; 3 | 4 | export const getAllEmployees = async ()=>{ 5 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/allEmployees'; 6 | const token = getLocalStorageKey('token'); 7 | 8 | const headers = new Headers(); 9 | headers.append('Content-Type','application/json'); 10 | headers.append('token',token); 11 | 12 | const requestOptions = {method:'GET', headers, redirect: 'follow'}; 13 | 14 | return await fetchUrl(url, requestOptions); 15 | } 16 | 17 | export const getEmployeesCount = async ()=>{ 18 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/employees-count'; 19 | const token = getLocalStorageKey('token'); 20 | 21 | const headers = new Headers(); 22 | headers.append('Content-Type','application/json'); 23 | headers.append('token',token); 24 | 25 | const requestOptions = {method:'GET', headers, redirect: 'follow'}; 26 | 27 | return await fetchUrl(url, requestOptions); 28 | } -------------------------------------------------------------------------------- /client/src/containers/tasks-board/empty-board.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import CreateBoardModal from "../../components/create-board-modal/create-board-modal"; 3 | 4 | 5 | const EmptyBoard = ({ type, id, refetchData }) => { 6 | const [isBoardModalOpen, setIsBoardModalOpen] = useState(false); 7 | 8 | return ( 9 |
10 |

11 | {type === "edit" 12 | ? "This board is empty. Create a new column to get started." 13 | : "There are no boards available. Create a new board to get started"} 14 |

15 | 22 | {isBoardModalOpen && ( 23 | 29 | )} 30 |
31 | ); 32 | } 33 | 34 | export default EmptyBoard; 35 | -------------------------------------------------------------------------------- /client/src/containers/tasks-board/subtask.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { boardActions } from "../../redux/reducers/board"; 4 | 5 | const Subtask = ({ id, taskId, colId, setIsChanged }) => { 6 | const dispatch = useDispatch(); 7 | const board = useSelector((state) => state.boards.selectedBoard); 8 | const col = board.columns.find((col) => col._id === colId); 9 | const task = col.tasks.find((task) => task._id === taskId); 10 | const subtask = task.subtasks.find((subtask) => subtask._id === id); 11 | const checked = subtask.isCompleted; 12 | 13 | const onChange = (e) => { 14 | dispatch( 15 | boardActions.setSubtaskCompleted({ id, taskId, colId }) 16 | ); 17 | setIsChanged(true); 18 | }; 19 | 20 | return ( 21 |
22 | 27 |

28 | {subtask.title} 29 |

30 |
31 | ); 32 | } 33 | 34 | export default Subtask; 35 | -------------------------------------------------------------------------------- /server/Scripts/index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const departmentsData = require('./departments.json'); 3 | 4 | const departSchema = new mongoose.Schema({ 5 | title: { 6 | type: String, 7 | required: [true, "A department should have a title."] 8 | }, 9 | url: { 10 | type: String 11 | } 12 | }); 13 | 14 | const Department = mongoose.model('departments', departSchema); 15 | 16 | const createDeparments = async () => { 17 | const response = await Department.insertMany(departmentsData); 18 | console.log("result", response); 19 | } 20 | 21 | const getAllDepartments = async () => { 22 | const response = await Department.find(); 23 | console.log("length", response); 24 | } 25 | 26 | const db = ''; 27 | const password = ''; 28 | const dblink = db.replace( 29 | '', 30 | password 31 | ); 32 | 33 | mongoose.connect(dblink).then(()=>{ 34 | console.log("DB succesfully connected!"); 35 | createDeparments().then(()=>{ 36 | console.log("data inserted succesfully"); 37 | }) 38 | }).catch((error)=>{ 39 | console.log("Error in db", error.message); 40 | }) -------------------------------------------------------------------------------- /client/src/components/name/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { ExclamationMark } from '../icons'; 3 | 4 | import './styles.scss'; 5 | 6 | const Name = (props) => { 7 | const { onChange, isValid, ...rest } = props; 8 | const [isError, setIsError] = useState(false); 9 | 10 | const onChangeName = ({ target }) => { 11 | const isValid = target.value !== ''; 12 | setIsError(!isValid); 13 | props.onChange(target.value, !!isValid); 14 | }; 15 | 16 | const onBlurName = ({target}) => { 17 | const isValid = target.value !== ''; 18 | setIsError(!isValid); 19 | } 20 | 21 | const onFocusName = () => { 22 | setIsError(false); 23 | } 24 | 25 | return
26 |
27 |
28 | {props.label} 29 |
30 | 31 |
32 |
33 |
34 | }; 35 | 36 | export default Name; -------------------------------------------------------------------------------- /client/src/components/otp/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { ExclamationMark } from '../icons'; 3 | 4 | import './styles.scss'; 5 | 6 | const Otp = (props) => { 7 | const { onChange, isValid, ...rest } = props; 8 | const [isError, setIsError] = useState(false); 9 | 10 | const onChangeName = ({ target }) => { 11 | const isValid = target.value !== ''; 12 | setIsError(!isValid); 13 | props.onChange(target.value, !!isValid); 14 | }; 15 | 16 | const onBlurName = ({target}) => { 17 | const isValid = target.value !== ''; 18 | setIsError(!isValid); 19 | } 20 | 21 | const onFocusName = () => { 22 | setIsError(false); 23 | } 24 | 25 | return
26 |
27 |
28 | {props.label} 29 |
30 | 31 |
32 |
33 |
34 | }; 35 | 36 | export default Otp; -------------------------------------------------------------------------------- /client/src/containers/projects/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import MetaTags from '../../components/meta-tags'; 3 | import './style.scss'; 4 | import { mapProjectsData } from '../../data/projectsData'; 5 | import MainSection from './main-section'; 6 | import Loader from '../../components/loader'; 7 | import { getListProjects } from '../../services/projects/projects'; 8 | 9 | const Projects = () => { 10 | const [{ mainSection, metaData }, setProjectsData] = useState(mapProjectsData()); 11 | const [isLoading, setLoading] = useState(false); 12 | 13 | const fetchProjectsData = async () => { 14 | setLoading(true); 15 | const result = await getListProjects(); 16 | if (result.status === 'success') { 17 | setProjectsData(mapProjectsData(result.response)); 18 | } 19 | setLoading(false); 20 | } 21 | 22 | useEffect(()=> { 23 | fetchProjectsData(); 24 | }, []); 25 | 26 | return ( isLoading ? : 27 |
28 |
29 | 30 | 31 |
32 |
33 | ) 34 | }; 35 | 36 | export default Projects; 37 | -------------------------------------------------------------------------------- /client/src/containers/app/index.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Routes } from 'react-router-dom'; 2 | import { Provider, useSelector } from 'react-redux'; 3 | import { allRoutes } from './all-routes'; 4 | import Navbar from '../../components/navbar'; 5 | import { ToastContainer } from 'react-toastify'; 6 | import 'react-toastify/dist/ReactToastify.css'; 7 | import store from '../../redux/store'; 8 | import Header from '../../components/header'; 9 | 10 | const App = () => { 11 | const routes = allRoutes; 12 | const { isActive, isHidden } = useSelector(state => state.navbar); 13 | 14 | return ( 15 | 16 | 17 | 18 |
19 |
20 | 21 | {routes.map(route => { 22 | return 23 | })} 24 | 25 |
26 |
27 | ) 28 | }; 29 | 30 | const AppWithStore = () => ( 31 | 32 | 33 | 34 | ) 35 | 36 | export default AppWithStore; 37 | -------------------------------------------------------------------------------- /client/src/components/header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { navbarActions } from '../../redux/reducers/other'; 5 | import { HamburgerIcon, UserIcon } from '../icons'; 6 | import './styles.scss'; 7 | 8 | const Header = () => { 9 | const { isActive, isHidden } = useSelector(state => state.navbar); 10 | const employee = useSelector(state => state.employee.loggedInEmployee); 11 | const dispatch = useDispatch(); 12 | const navigate = useNavigate(); 13 | 14 | const handleNav = () => { 15 | dispatch(navbarActions.setIsActive(!isActive)); 16 | } 17 | 18 | const handleClick = () => { 19 | navigate('/profile'); 20 | }; 21 | 22 | return ( !isHidden ? 23 |
24 |
25 |
26 |
27 | 28 |
29 | Hi {employee?.name?.split(' ')[0] || '😎' } 30 |
31 |
: null 32 | ) 33 | } 34 | 35 | export default Header; 36 | -------------------------------------------------------------------------------- /client/src/components/email/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { validateEmail } from '../../utils/email-validator'; 3 | import { ExclamationMark } from '../icons'; 4 | import './styles.scss'; 5 | 6 | const Email = (props) => { 7 | const { onChange, isValid, ...rest } = props; 8 | const [isError, setIsError] = useState(false); 9 | 10 | const onChangeEmail = ({ target }) => { 11 | const isValid = validateEmail(target.value); 12 | setIsError(!isValid); 13 | props.onChange(target.value?.toLowerCase(), !!isValid); 14 | }; 15 | 16 | const onBlurEmail = ({target}) => { 17 | const isValid = validateEmail(target.value); 18 | setIsError(!isValid); 19 | } 20 | 21 | const onFocusEmail = () => { 22 | setIsError(false); 23 | } 24 | 25 | return
26 |
27 |
28 | {props.label} 29 |
30 | 31 |
32 |
33 |
34 | }; 35 | 36 | export default Email; -------------------------------------------------------------------------------- /client/src/components/modal/style.scss: -------------------------------------------------------------------------------- 1 | .modal{ 2 | display: block; 3 | position: fixed; 4 | z-index: 1; 5 | padding-top: 100px; 6 | left: 0; 7 | top: 0; 8 | width: 100%; 9 | height: 100%; 10 | overflow: auto; 11 | background-color: rgba(16, 16, 16, 0.4); 12 | .modal-container{ 13 | background-color: #fefefe; 14 | margin: auto; 15 | padding: 20px; 16 | border: 1px solid #888; 17 | width: 80%; 18 | .modal-header{ 19 | display: flex; 20 | justify-content: space-between; 21 | align-items: center; 22 | border-bottom: 2px solid #6c6b6b; 23 | margin-bottom: 20px; 24 | header{ 25 | font-size: larger; 26 | font-weight: bold; 27 | text-transform: capitalize; 28 | } 29 | .close{ 30 | color: #aaaaaa; 31 | font-size: 28px; 32 | font-weight: bold; 33 | &:hover, &:focus{ 34 | color: #000; 35 | text-decoration: none; 36 | cursor: pointer; 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /client/src/data/tasksData.js: -------------------------------------------------------------------------------- 1 | export const mapTasksData = (tasks = []) => { 2 | return { 3 | metaData: { 4 | title: "Tasks | TOTE" 5 | }, 6 | backgroundImage: { 7 | path: '/assests/kanban-background.jpg' 8 | }, 9 | mainSection: { 10 | allTasksDetails: [ 11 | { 12 | title: 'backlog', 13 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'backlog') || [], 14 | }, 15 | { 16 | title: 'up next', 17 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'up next') || [], 18 | }, 19 | { 20 | title: 'in progress', 21 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'in progress') || [], 22 | }, 23 | { 24 | title: 'on hold', 25 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'on hold') || [], 26 | }, 27 | { 28 | title: 'completed', 29 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'done') || [], 30 | }, 31 | { 32 | title: 'questions', 33 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'questions') || [], 34 | }, 35 | ] 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | # Package files 20 | *.jar 21 | 22 | # Maven 23 | target/ 24 | dist/ 25 | 26 | # JetBrains IDE 27 | .idea/ 28 | 29 | # Unit test reports 30 | TEST*.xml 31 | 32 | # Generated by MacOS 33 | .DS_Store 34 | 35 | # Generated by Windows 36 | Thumbs.db 37 | 38 | # Applications 39 | *.app 40 | *.exe 41 | *.war 42 | 43 | # Large media files 44 | *.mp4 45 | *.tiff 46 | *.avi 47 | *.flv 48 | *.mov 49 | *.wmv 50 | 51 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 52 | 53 | # dependencies 54 | /node_modules 55 | /.pnp 56 | .pnp.js 57 | 58 | # testing 59 | /coverage 60 | 61 | # production 62 | /build 63 | 64 | # misc 65 | .DS_Store 66 | .env 67 | .env.local 68 | .env.development.local 69 | .env.test.local 70 | .env.production.local 71 | 72 | npm-debug.log* 73 | yarn-debug.log* 74 | yarn-error.log* 75 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | 10 | # Compiled Java class files 11 | *.class 12 | 13 | # Compiled Python bytecode 14 | *.py[cod] 15 | 16 | # Log files 17 | *.log 18 | 19 | # Package files 20 | *.jar 21 | 22 | # Maven 23 | target/ 24 | dist/ 25 | 26 | # JetBrains IDE 27 | .idea/ 28 | 29 | # Unit test reports 30 | TEST*.xml 31 | 32 | # Generated by MacOS 33 | .DS_Store 34 | 35 | # Generated by Windows 36 | Thumbs.db 37 | 38 | # Applications 39 | *.app 40 | *.exe 41 | *.war 42 | 43 | # Large media files 44 | *.mp4 45 | *.tiff 46 | *.avi 47 | *.flv 48 | *.mov 49 | *.wmv 50 | 51 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 52 | 53 | # dependencies 54 | /node_modules 55 | /.pnp 56 | .pnp.js 57 | 58 | # testing 59 | /coverage 60 | 61 | # production 62 | /build 63 | 64 | # misc 65 | .DS_Store 66 | .env 67 | .env.local 68 | .env.development.local 69 | .env.test.local 70 | .env.production.local 71 | 72 | npm-debug.log* 73 | yarn-debug.log* 74 | yarn-error.log* 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Tote Web 3 | 4 | A MERN stack application to manage the employees of a particular organization. This is my side project and i want this project to look good in UI. Currently, my focus is to make it a MVP in tems of functionalities. If you are eager to work on this admin panel. 5 | 6 | 7 | 8 | 9 | ## Installation Guide 10 | 11 | Clone or fork the repository and run the provided commands in console of the tote_web folder. To run this project, you will need to do following steps : 12 | 13 | ##### To run client side 14 | 15 | ```http 16 | cd ../client 17 | npm Install 18 | create a .env file and add given variables 19 | npm run start / npm start 20 | ``` 21 | ### Client env variables 22 | 23 | `REACT_APP_BASE_URI=http://localhost:5000` 24 | 25 | #### To run server side 26 | 27 | ```http 28 | cd ../server 29 | npm install 30 | create a .env file and given variables 31 | npm run dev 32 | ``` 33 | ### Server env variables 34 | 35 | 36 | `ACCESS_TOKEN=[jwt_access_secret]` 37 | 38 | `PORT=5000` 39 | 40 | `DB_USERNAME=[mongodb_user_name]` 41 | 42 | `DB_PASSWORD=[mongodb_cluster_access_password]` 43 | 44 | `DB_CLOUD_LINK=[mongodb_cluster_url]` 45 | 46 | `SMTP_HOST=[smptp_host_uri]` 47 | 48 | `SMTP_PORT=[smtp_port]` 49 | 50 | `SMTP_USER=[smtp_user_key]` 51 | 52 | `SMTP_PASS=[smtp_user_password_key]` 53 | 54 | -------------------------------------------------------------------------------- /client/src/services/login/signup.js: -------------------------------------------------------------------------------- 1 | import { fetchUrl } from "../../utils/fetchUrl"; 2 | 3 | export const signup = async (id, otp) =>{ 4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/signup'; 5 | const headers = new Headers(); 6 | headers.append('Content-Type', 'application/json'); 7 | const body = { id, otp }; 8 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect:'follow' }; 9 | return await fetchUrl(url, requestOptions); 10 | } 11 | 12 | export const preSignup = async (name, email, password) =>{ 13 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/signup/send-otp'; 14 | const headers = new Headers(); 15 | headers.append('Content-Type', 'application/json'); 16 | const body = { name, email, password }; 17 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect:'follow' }; 18 | return await fetchUrl(url, requestOptions); 19 | } 20 | 21 | export const preSignupResendOTP = async (id) =>{ 22 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/signup/resend-otp/'; 23 | const headers = new Headers(); 24 | headers.append('Content-Type', 'application/json'); 25 | 26 | const requestOptions = { method: 'GET', headers, redirect:'follow' }; 27 | return await fetchUrl(url + id, requestOptions); 28 | } -------------------------------------------------------------------------------- /server/Routers/employeeRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const employeeControllers = require('../Controllers/employeeControllers'); 3 | const verifyToken = require('../Middilewares/jwt_auth'); 4 | const checkLoginData = require('../Middilewares/checkLogin'); 5 | const checkOtpData = require('../Middilewares/checkOTP'); 6 | const router = express.Router(); 7 | 8 | router.route('/login').post(checkLoginData, employeeControllers.login); 9 | 10 | router.route('/signup/send-otp').post(employeeControllers.preSignup); 11 | 12 | router.route('/signup/resend-otp/:id').get(employeeControllers.preSignupResendOTP); 13 | 14 | router.route('/signup').post(employeeControllers.signup); 15 | 16 | router.route('/forgotPassword').post(checkOtpData, employeeControllers.resetPasswordSendOTP); 17 | 18 | router.route('/resetPassword').post(employeeControllers.resetPassword); 19 | 20 | router.route('/employeeDetails').get(verifyToken, employeeControllers.employeeDetails); 21 | 22 | router.route('/updateEmployee').post(verifyToken, employeeControllers.updateEmployee); 23 | 24 | router.route('/deleteEmployee').delete(verifyToken, employeeControllers.deleteEmployee); 25 | 26 | router.route('/allEmployees').get(verifyToken, employeeControllers.getAllEmployees); 27 | 28 | router.route('/employees-count').get(verifyToken, employeeControllers.getEmployeesCount); 29 | 30 | module.exports = router; -------------------------------------------------------------------------------- /client/src/components/password/styles.scss: -------------------------------------------------------------------------------- 1 | .password-component { 2 | .password-wrapper { 3 | display: flex; 4 | border: 2px solid #B7B78A; 5 | border-radius: 10px; 6 | height: 50px; 7 | position: relative; 8 | padding: 0 0 0 16px; 9 | .text{ 10 | color: #000; 11 | font-weight: 600; 12 | position: absolute; 13 | top: -10px; 14 | background-color: #fff; 15 | padding: 0px 5px; 16 | } 17 | .password-input{ 18 | display: inline-block; 19 | height: 100%; 20 | width: 100%; 21 | border: none; 22 | color: rgb(32, 33, 33); 23 | background-color: transparent; 24 | } 25 | .password-input:focus{ 26 | outline: none; 27 | } 28 | .show-password{ 29 | cursor: pointer; 30 | background-color: transparent; 31 | color: #086b25; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | padding: 4px 10px; 36 | } 37 | } 38 | .error-icon { 39 | padding-right: 8px; 40 | display: none; 41 | opacity: 0; 42 | svg{ 43 | fill: #e85347; 44 | } 45 | } 46 | &.error { 47 | .error-icon { 48 | display: flex; 49 | justify-content: center; 50 | align-items: center; 51 | opacity: 1; 52 | } 53 | .password-wrapper{ 54 | border-color: #e85347; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "http://React-developer-want.github.io/tote_web", 6 | "dependencies": { 7 | "@reduxjs/toolkit": "^1.9.5", 8 | "@testing-library/jest-dom": "^5.16.5", 9 | "@testing-library/react": "^13.4.0", 10 | "@testing-library/user-event": "^13.5.0", 11 | "lodash": "^4.17.21", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-helmet": "^6.1.0", 15 | "react-js-loader": "^0.1.0", 16 | "react-redux": "^8.1.1", 17 | "react-router-dom": "^6.6.2", 18 | "react-scripts": "5.0.1", 19 | "react-toastify": "^9.1.1", 20 | "sass": "^1.57.1", 21 | "uuid": "^9.0.0", 22 | "web-vitals": "^2.1.4" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "predeploy": "npm run build", 27 | "deploy": "gh-pages -d build", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/src/components/password/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { OpenEyeIcon, CloseEyeIcon, ExclamationMark } from '../icons'; 3 | import './styles.scss'; 4 | 5 | const Password = (props) => { 6 | const [isError, setIsError] = useState(false); 7 | const [showPwd,setShowPwd] = useState(false); 8 | const { onChange, ...rest } = props; 9 | 10 | const onChangePassword = ({ target }) => { 11 | const isValid = target.value !== ''; 12 | props.onChange(target.value, isValid); 13 | } 14 | 15 | const onBlurPassword = ({target}) => { 16 | const isValid = target.value !== ''; 17 | setIsError(!isValid); 18 | } 19 | 20 | const onFocusPassword = () => { 21 | setIsError(false); 22 | } 23 | 24 | const handleShowPwd = () => { 25 | setShowPwd((prev)=> !prev); 26 | } 27 | 28 | return
29 |
30 |
{props.label}
31 | 32 |
33 |
34 | {showPwd ? : } 35 |
36 |
37 |
38 | }; 39 | 40 | export default Password; -------------------------------------------------------------------------------- /client/src/data/newTaskForm.js: -------------------------------------------------------------------------------- 1 | import { getInputDate } from "../utils/date-handler"; 2 | 3 | export const mapNewTaskData = (allEmployees, status) => ({ 4 | allFields: [ 5 | { 6 | label: 'Title', 7 | value: '', 8 | type: 'text', 9 | details: { 10 | placeholder: 'Title' 11 | } 12 | }, 13 | { 14 | label: 'Start Date', 15 | value: getInputDate((new Date()).toISOString()), 16 | type: 'date', 17 | details: { 18 | className: 'date' 19 | } 20 | }, 21 | { 22 | label: 'Due Date', 23 | value: '', 24 | type: 'date', 25 | details: { 26 | className: 'date' 27 | } 28 | }, 29 | { 30 | label: 'Status', 31 | type: 'dropdown', 32 | value: status || '', 33 | details: { 34 | className: "dropdown", 35 | list: [ 36 | { value: 'completed', label: 'Completed' }, { value: 'in progress', label: 'In progress' }, { value: 'backlog', label: 'Backlog' }, { value: 'up next', label: 'Up Next' }, { value: 'question', label: 'question' }, { value: 'on hold', label: 'On Hold' } 37 | ], 38 | } 39 | }, 40 | { 41 | label: 'Assigned to', 42 | value: '', 43 | type: 'dropdown', 44 | details: { 45 | className: 'dropdown', 46 | list: allEmployees.map((employee)=> ({ 47 | value: employee._id, label: employee.name 48 | })) 49 | } 50 | } 51 | ] 52 | }) -------------------------------------------------------------------------------- /client/src/containers/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import MetaTags from '../../components/meta-tags'; 4 | import { mapDashboardData } from '../../data/dashboardData.js'; 5 | import MainSection from './main-section.js'; 6 | import TopSection from './top-section.js'; 7 | import './styles.scss'; 8 | import Loader from '../../components/loader'; 9 | import { getAllEmployees } from '../../services/employees/allEmployees'; 10 | 11 | const Dashboard = () => { 12 | const [dashboardData, setDashboardData] = useState(mapDashboardData()); 13 | const [isLoading, setLoading] = useState(false); 14 | const navigate = useNavigate(); 15 | 16 | const syncDashboard = async () => { 17 | setLoading(true); 18 | const response = await getAllEmployees(); 19 | setDashboardData(mapDashboardData({}, response.body.data)); 20 | setLoading(false); 21 | }; 22 | 23 | useEffect(()=> { 24 | syncDashboard(); 25 | }, []); 26 | 27 | return ( isLoading ? : 28 |
29 |
30 | 33 | 36 | 39 |
40 |
41 | ) 42 | } 43 | 44 | export default Dashboard 45 | -------------------------------------------------------------------------------- /client/src/containers/tasks-board/Task.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import TaskModal from "./task-modal"; 4 | 5 | const Task = ({ colId, taskId }) => { 6 | const [isTaskModalOpen, setIsTaskModalOpen] = useState(false); 7 | const board = useSelector((state) => state.boards.selectedBoard); 8 | const columns = board.columns; 9 | const col = columns.find((col) => col._id === colId); 10 | const task = col.tasks.find((task) => task._id === taskId); 11 | 12 | let completed = 0; 13 | let subtasks = task.subtasks; 14 | subtasks.forEach((subtask) => { 15 | if (subtask.isCompleted) { 16 | completed++; 17 | } 18 | }); 19 | 20 | const handleOnDrag = (e) => { 21 | e.dataTransfer.setData( 22 | "text", 23 | JSON.stringify({ taskId, prevColId: colId }) 24 | ); 25 | }; 26 | 27 | return ( 28 |
29 |
setIsTaskModalOpen(true)} 31 | draggable 32 | onDragStart={handleOnDrag} 33 | className="task" 34 | > 35 |

{task.title}

36 |

37 | {completed} of {subtasks.length} completed tasks 38 |

39 |
40 | {isTaskModalOpen && ( 41 | 46 | )} 47 |
48 | ); 49 | } 50 | 51 | export default Task; 52 | -------------------------------------------------------------------------------- /client/src/containers/employees/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import { useEffect, useState } from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import Loader from '../../components/loader'; 5 | import MetaTags from '../../components/meta-tags'; 6 | import { mapEmployeesData } from '../../data/employeesData'; 7 | import { sendErrorNotification } from '../../services/notifications'; 8 | import { getAllEmployees } from '../../services/employees/allEmployees'; 9 | import MainSection from './main-section'; 10 | import './styles.scss'; 11 | 12 | const Employees = () => { 13 | const navigate = useNavigate(); 14 | const [{metaData, mainSection}, setEmployeesData] = useState(mapEmployeesData([])); 15 | const [isLoading, setIsLoading] = useState(false); 16 | 17 | useEffect( ()=>{ 18 | const fetchEmployees = async ()=>{ 19 | setIsLoading(true); 20 | const response = await getAllEmployees(); 21 | if(response.status === 'TokenExpiredError'){ 22 | navigate('/login'); 23 | sendErrorNotification('Session expired login again!') 24 | } 25 | setEmployeesData(mapEmployeesData(response.body.data)); 26 | setIsLoading(false); 27 | } 28 | fetchEmployees(); 29 | },[]) 30 | 31 | return ( isLoading ? : 32 |
33 |
34 | 35 | 36 |
37 |
38 | ) 39 | } 40 | 41 | export default Employees 42 | -------------------------------------------------------------------------------- /client/src/containers/dashboard/top-section.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TopSection = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
1,504
10 |
Daily Views
11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |
80
20 |
Sales
21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 |
284
30 |
Comments
31 |
32 |
33 | 34 |
35 |
36 | 37 |
38 |
39 |
$7,842
40 |
Earning
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | ) 49 | } 50 | 51 | export default TopSection; 52 | -------------------------------------------------------------------------------- /client/src/data/departmentsData.js: -------------------------------------------------------------------------------- 1 | export const mapDepartmentsData = (data = []) => { 2 | return { 3 | metaData: { 4 | title: "Departments | TOTE" 5 | }, 6 | mainSection: { 7 | filter: { 8 | search: { 9 | placeholder: "Search department" 10 | }, 11 | button: { 12 | text: "Create Department" 13 | } 14 | }, 15 | createDepartForm: [ 16 | { 17 | component: "name", 18 | details: { 19 | label: "Department name", 20 | type: "text", 21 | placeholder: "Enter department name", 22 | required: true 23 | } 24 | }, 25 | { 26 | component: "url", 27 | details: { 28 | label: "Website url", 29 | type: "url", 30 | placeholder: "Enter website url", 31 | required: true 32 | } 33 | }, 34 | { 35 | component: "button", 36 | details: { 37 | text: "create department", 38 | type: "submit", 39 | button: "primary" 40 | } 41 | } 42 | ], 43 | departmentsCards : data || [] 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /client/src/data/navbarData.js: -------------------------------------------------------------------------------- 1 | export const mapNavbarData = (data)=>{ 2 | return { 3 | header : 'Tote web', 4 | navbarItems : [ 5 | { 6 | to: '/', 7 | paths: [''], 8 | title: 'Dashboard', 9 | icon: 'home_icon', 10 | id: 'navitem-1' 11 | }, 12 | { 13 | to: '/employees', 14 | paths: ['employees'], 15 | title: 'Employees', 16 | icon: 'employees_icon', 17 | id: 'navitem-2' 18 | }, 19 | { 20 | to: '/departments', 21 | paths: ['departments'], 22 | title: 'Departments', 23 | icon: 'departments_icon', 24 | id: 'navitem-3' 25 | }, 26 | { 27 | to: '/projects', 28 | paths: ['projects'], 29 | title: 'Projects', 30 | icon: 'projects_icon', 31 | id: 'navitem-4' 32 | }, 33 | { 34 | to: '/tasks', 35 | paths: ['tasks'], 36 | title: 'Tasks Manager', 37 | icon: 'tasks_icon', 38 | id: 'navitem-5' 39 | }, 40 | { 41 | to: '/profile', 42 | paths: ['profile'], 43 | title: 'Profile', 44 | icon: 'profile_icon', 45 | id: 'navitem-8' 46 | }, 47 | ], 48 | employeeName: data?.name ?? '😎' 49 | } 50 | } -------------------------------------------------------------------------------- /client/src/containers/employees/employee-details.scss: -------------------------------------------------------------------------------- 1 | .employeeDetails-page{ 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | .employeeDetails-container{ 6 | width: 70%; 7 | margin: 3rem 1rem; 8 | .employeeDetails-topSection{ 9 | width: 100%; 10 | margin-bottom: 20px; 11 | .main-title{ 12 | font-size: 26px; 13 | font-weight: bold; 14 | } 15 | } 16 | .formSection{ 17 | display: flex; 18 | justify-content: center; 19 | align-items: center; 20 | form{ 21 | width: 100%; 22 | .simpleInput-component{ 23 | margin-bottom: 20px; 24 | } 25 | .dropdown-component{ 26 | margin-bottom: 20px; 27 | display: flex; 28 | gap: 20px; 29 | align-items: center; 30 | padding: 8px; 31 | 32 | } 33 | .buttons{ 34 | display: flex; 35 | justify-content: center; 36 | width: 100%; 37 | gap: 10px; 38 | .btn{ 39 | width: 40%; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | @media screen and (max-width: 700px) { 48 | .employeeDetails-page{ 49 | .employeeDetails-container{ 50 | width: 100%; 51 | margin: 3rem 0; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /client/src/services/departments/departments.js: -------------------------------------------------------------------------------- 1 | import { fetchUrl } from "../../utils/fetchUrl"; 2 | import { getLocalStorageKey } from "../../utils/localStorage" 3 | 4 | export const getAllDepartments = async () => { 5 | const token = getLocalStorageKey('token'); 6 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/departments/all-departments' ; 7 | 8 | const headers = new Headers(); 9 | headers.append('Content-Type','application/json'); 10 | headers.append('token',token); 11 | 12 | const requestOptions = {method: 'GET', headers, redirect: 'follow'}; 13 | return await fetchUrl(url, requestOptions); 14 | } 15 | 16 | export const getDepartmentsCount = async () => { 17 | const token = getLocalStorageKey('token'); 18 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/departments/department-count'; 19 | 20 | const headers = new Headers(); 21 | headers.append('Content-Type','application/json'); 22 | headers.append('token',token); 23 | 24 | const requestOptions = {method: 'GET', headers, redirect: 'follow'}; 25 | return await fetchUrl(url, requestOptions); 26 | } 27 | 28 | export const createDepartment = async (details) => { 29 | const token = getLocalStorageKey('token'); 30 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/departments/create-department'; 31 | 32 | const headers = new Headers(); 33 | headers.append('Content-Type', 'application/json'); 34 | headers.append('token', token); 35 | 36 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'}; 37 | return await fetchUrl(url, requestOptions); 38 | } -------------------------------------------------------------------------------- /client/src/containers/projects/style.scss: -------------------------------------------------------------------------------- 1 | .projects-page{ 2 | .projects-container{ 3 | padding: 3rem 1rem; 4 | .projects-main-section .project-card{ 5 | margin-bottom: 4rem; 6 | .title{ 7 | font-size: 2rem; 8 | font-weight: bold; 9 | margin-bottom: 1rem; 10 | border-bottom: 1px solid gray; 11 | } 12 | img{ 13 | width: 100%; 14 | height: 12rem; 15 | margin-bottom: .5rem; 16 | } 17 | .grid{ 18 | display: flex; 19 | justify-content: flex-start; 20 | gap: 1rem; 21 | flex-wrap: wrap; 22 | } 23 | .container{ 24 | cursor: pointer; 25 | width: 20.5rem; 26 | padding: 0.25rem; 27 | box-shadow: 0px 5px 5px rgb(219, 214, 214); 28 | &:hover{ 29 | box-shadow: 0px 5px 5px rgb(171, 161, 161); 30 | } 31 | .name{ 32 | text-align: left; 33 | font-size: 1.4rem; 34 | font-weight: bold; 35 | text-transform: capitalize; 36 | } 37 | .flag{ 38 | display: inline-block; 39 | color: #608906; 40 | font-weight: bolder; 41 | text-transform: uppercase; 42 | } 43 | .entity-pairs{ 44 | display: flex; 45 | justify-content: space-between; 46 | margin-bottom: .5rem; 47 | } 48 | } 49 | .no-data{ 50 | height: 4rem; 51 | display: flex; 52 | justify-content: center; 53 | align-items: center; 54 | font-size: 2rem; 55 | font-weight: lighter; 56 | color: gray; 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /client/src/data/resetPasswordData.json: -------------------------------------------------------------------------------- 1 | { 2 | "metaData": { 3 | "title": "Reset password | TOTE" 4 | }, 5 | "topSection": { 6 | "title": "Reset Password" 7 | }, 8 | "formSection": { 9 | "inputComponents": [ 10 | { 11 | "component": "email", 12 | "details": { 13 | "label": "Email", 14 | "placeholder": "E.g. xyz@gmail.com" 15 | } 16 | }, 17 | { 18 | "component": "button", 19 | "details": { 20 | "text": "Send OTP", 21 | "type": "submit", 22 | "button": "primary" 23 | } 24 | } 25 | ], 26 | "otpComponents": [ 27 | { 28 | "component": "otp", 29 | "details": { 30 | "label": "Please Enter The 6 Digit OTP", 31 | "placeholder": "Enter The 6 Digit OTP" 32 | } 33 | }, 34 | { 35 | "component": "password", 36 | "details": { 37 | "label": "New password", 38 | "placeholder": "********" 39 | } 40 | }, 41 | { 42 | "component": "button", 43 | "details": { 44 | "text": "Submit", 45 | "type": "submit", 46 | "button": "success" 47 | } 48 | } 49 | ], 50 | "linkText": "Return to Login", 51 | "signInLink": "/login" 52 | } 53 | } -------------------------------------------------------------------------------- /client/src/containers/projects/main-section.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const EntityPairs = (props) => ( 4 |
5 | {props.title} 6 | {props.value} 7 |
8 | ); 9 | 10 | const ProjectComponent = (props) => ( 11 |
12 |
{props?.title}
13 | {props.list.length > 0 ?
14 | {props?.list?.map((item, index)=> ( 15 |
16 | project 17 |
18 |
{item?.name}
19 | 20 | 21 |
22 |
{item?.status}
23 |
24 | ))} 25 |
: ( 26 |
27 | No Data to Show 28 |
29 | )} 30 |
31 | ); 32 | 33 | const MainSection = (props) => { 34 | const { activeProjectsList, completedProjectsList, rejectedProjectsList } = props; 35 | 36 | return ( 37 |
38 | 39 | 40 | 41 |
42 | ) 43 | } 44 | 45 | export default MainSection; 46 | -------------------------------------------------------------------------------- /client/src/containers/departments/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-hooks/exhaustive-deps */ 2 | import React, { useEffect, useState } from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import { getAllDepartments } from '../../services/departments/departments'; 5 | import MetaTags from '../../components/meta-tags'; 6 | import { sendErrorNotification } from '../../services/notifications'; 7 | import { mapDepartmentsData } from '../../data/departmentsData'; 8 | import MainSection from './main-section'; 9 | import './styles.scss'; 10 | import Loader from '../../components/loader'; 11 | 12 | const Departments = () => { 13 | const navigate = useNavigate(); 14 | const [isLoading, setIsLoading] = useState(false); 15 | const [departmentData, setDepartmentData] = useState(mapDepartmentsData([])); 16 | 17 | const fetchData = async () => { 18 | setIsLoading(true); 19 | const result = await getAllDepartments(); 20 | if(result.status === 'TokenExpiredError'){ 21 | navigate('/login'); 22 | sendErrorNotification('Session expired login again!') 23 | } 24 | setDepartmentData(mapDepartmentsData(result?.response)); 25 | setIsLoading(false); 26 | } 27 | 28 | useEffect(()=> { 29 | fetchData(); 30 | },[]); 31 | 32 | return ( isLoading ? : 33 |
34 |
35 | 38 | 42 |
43 |
44 | ) 45 | } 46 | 47 | export default Departments 48 | 49 | // https://up.gov.in/en/page/departments -------------------------------------------------------------------------------- /client/src/components/delete-modal/DeleteModal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import './styles.scss'; 3 | 4 | const DeleteModal = ({ type, title, onDeleteBtnClick, setIsDeleteModalOpen, isDeleteLoading }) => { 5 | 6 | const onClose = (e) => { 7 | if (e.target !== e.currentTarget) { 8 | return; 9 | } 10 | setIsDeleteModalOpen(false); 11 | } 12 | 13 | return ( 14 | // Modal Container 15 |
19 | {/* Delete Modal */} 20 | 21 |
22 |

23 | Delete this {type}? 24 |

25 | {type === "task" ? ( 26 |

27 | Are you sure you want to delete the "{title}" task and its subtasks? 28 | This action cannot be reversed. 29 |

30 | ) : ( 31 |

32 | Are you sure you want to delete the "{title}" board? This action 33 | will remove all columns and tasks and cannot be reversed. 34 |

35 | )} 36 | 37 |
38 | 45 | 53 |
54 |
55 |
56 | ); 57 | } 58 | 59 | export default DeleteModal; 60 | -------------------------------------------------------------------------------- /client/src/data/taskDetails.js: -------------------------------------------------------------------------------- 1 | import { getInputDate } from "../utils/date-handler"; 2 | 3 | export const mapTaskDetails = (allEmployees = [], details = {}) => { 4 | return { 5 | initialState: details ?? {}, 6 | allFields: [ 7 | { 8 | label: 'Title', 9 | value: details?.title || '', 10 | type: 'text', 11 | details: { 12 | placeholder: 'Title' 13 | } 14 | }, 15 | { 16 | label: 'Start Date', 17 | value: getInputDate(details?.start_date), 18 | type: 'date', 19 | details: { 20 | className: 'date' 21 | } 22 | }, 23 | { 24 | label: 'Due Date', 25 | value: getInputDate(details?.due_date), 26 | type: 'date', 27 | details: { 28 | className: 'date' 29 | } 30 | }, 31 | { 32 | label: 'Status', 33 | type: 'dropdown', 34 | value: details?.status.toLowerCase(), 35 | details: { 36 | className: "dropdown", 37 | list: [ 38 | { value: 'completed', label: 'Completed' }, { value: 'in progress', label: 'In progress' }, { value: 'backlog', label: 'Backlog' }, { value: 'up next', label: 'Up Next' }, { value: 'question', label: 'question' }, { value: 'on hold', label: 'On Hold' } 39 | ], 40 | } 41 | }, 42 | { 43 | label: 'Assigned to', 44 | value: allEmployees.find((employee)=> employee.name === details?.assigned_to?.name), 45 | type: 'dropdown', 46 | details: { 47 | className: 'dropdown', 48 | list: allEmployees.map((employee)=> ({ 49 | value: employee._id, label: employee.name 50 | })) 51 | } 52 | } 53 | ] 54 | } 55 | }; -------------------------------------------------------------------------------- /client/src/containers/profile/style.scss: -------------------------------------------------------------------------------- 1 | .profile-page{ 2 | .profile-container{ 3 | padding: 3rem 1rem; 4 | .profile-main-section { 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | flex-direction: column; 9 | .user-icon { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | padding: 50px; 14 | background: var(--yellow); 15 | border-radius: 50%; 16 | margin-bottom: 20px; 17 | ion-icon { 18 | width: 100px; 19 | height: 100px; 20 | } 21 | } 22 | .profile-details{ 23 | display: flex; 24 | flex-direction: column; 25 | gap: 20px; 26 | min-width: 40rem; 27 | padding: 20px; 28 | border: 2px solid var(--blue); 29 | border-radius: 20px; 30 | box-shadow: 0 5px 5px var(--blue); 31 | .custom-input{ 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | text-transform: capitalize; 36 | .field{ 37 | font-size: larger; 38 | font-weight: bolder; 39 | width: 50%; 40 | } 41 | .value{ 42 | width: 50%; 43 | border-bottom: 1px solid rgb(96, 95, 95); 44 | border-style: none none dotted none; 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /server/Controllers/reportController.js: -------------------------------------------------------------------------------- 1 | const Report = require("../db/reportModel"); 2 | // const Report = require("../db/reportModel"); 3 | 4 | exports.createReport = async (req, res) => { 5 | const { employee_id } = req.employee; 6 | // here sent to must be an Object 7 | const { title, description, sent_to } = req.body; 8 | try { 9 | const existingReport = await Report.find({ title: title }); 10 | if (existingReport.length !== 0) { 11 | return res.status(409).json({ 12 | status: "failed", 13 | message: "the report already exists.", 14 | }); 15 | } 16 | const report = await Report.create({ 17 | title, 18 | description, 19 | sent_to, 20 | sent_by: employee_id, 21 | }); 22 | 23 | res.status(201).json({ 24 | status: "success", 25 | message: "successfully created the report." 26 | }); 27 | } catch (error) { 28 | res.status(403).json({ 29 | status: "failed", 30 | message: error.message, 31 | }); 32 | } 33 | }; 34 | 35 | exports.getAllReports = async (req, res) => { 36 | try { 37 | const reports = await Report.find() 38 | .populate("sent_to", "name") 39 | .populate("sent_by", "name"); 40 | 41 | res.status(200).json({ 42 | status: "success", 43 | response: reports, 44 | }); 45 | } catch (error) { 46 | res.status(404).json({ 47 | status: "failed", 48 | message: error.message, 49 | }); 50 | } 51 | }; 52 | 53 | exports.deleteReport = async (req, res) => { 54 | const { id } = req.query; 55 | try { 56 | await Report.findByIdAndDelete(id); 57 | 58 | res.status(202).json({ 59 | status: "success", 60 | message: "successfully deleted report.", 61 | }); 62 | } catch (error) { 63 | res.status(404).json({ 64 | status: "failed", 65 | message: error.message, 66 | }); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /client/src/containers/sign-up/styles.scss: -------------------------------------------------------------------------------- 1 | .signup-page { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | height: 100vh; 6 | .signup-container { 7 | width: 700px; 8 | padding: 40px 30px; 9 | background: rgb(247 247 247); 10 | border-radius: 5px; 11 | box-shadow: 0px 2px 10px #848181; 12 | .signup-top { 13 | text-align: center; 14 | margin-bottom: 16px; 15 | .main-title{ 16 | font-size: 26px; 17 | font-weight: bold; 18 | } 19 | } 20 | .form-section { 21 | margin-top: 16px; 22 | .email-component { 23 | margin-bottom: 24px; 24 | } 25 | .name-component{ 26 | margin-bottom: 24px; 27 | } 28 | .btn{ 29 | margin: 24px 0px; 30 | } 31 | .signup-link { 32 | display: block; 33 | text-align: center; 34 | text-decoration: none; 35 | font-size: 16px; 36 | font-weight: bold; 37 | color: #086f25; 38 | margin-top: 16px; 39 | } 40 | .resent-otp { 41 | text-align: center; 42 | font-size: 1rem; 43 | font-weight: 600; 44 | .link { 45 | color: var(--blue); 46 | cursor: pointer; 47 | } 48 | .text { 49 | color: #999; 50 | font-weight: 500; 51 | } 52 | } 53 | .bottom-text { 54 | font-size: 16px; 55 | text-align: center; 56 | margin-top: 16px; 57 | font-weight: bold; 58 | a { 59 | text-decoration: none; 60 | margin-left: 8px; 61 | color: #086f25; 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | @media only screen and (max-width: 600px) { 69 | .signup-page { 70 | background: #f7f7f7; 71 | .signup-container { 72 | padding: 16px; 73 | box-shadow: none; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /website/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /client/src/components/delete-modal/styles.scss: -------------------------------------------------------------------------------- 1 | .delete-modal { 2 | background-color: #00000080; 3 | position: fixed; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | right: 0; 8 | top: 0; 9 | bottom: 0; 10 | left: 0; 11 | -ms-overflow-style: none; 12 | scrollbar-width: none; 13 | padding: 1rem 0.5rem; 14 | overflow: scroll; 15 | z-index: 50; 16 | .content { 17 | -ms-overflow-style: none; 18 | scrollbar-width: none; 19 | overflow-y: scroll; 20 | max-height: 95vh; 21 | background-color: var(--white); 22 | color: var(--black1); 23 | font-weight: bold; 24 | box-shadow: 0 4px 4px #364e7e1a; 25 | max-width: 600px; 26 | margin: auto; 27 | width: 100%; 28 | padding: 2rem; 29 | border-radius: 0.75rem; 30 | .title { 31 | font-weight: 700; 32 | color: var(--red); 33 | font-size: 1.25rem; 34 | line-height: 1.75rem; 35 | } 36 | .description { 37 | font-size: 0.75rem; 38 | line-height: 1rem; 39 | font-weight: 600; 40 | color: gray; 41 | padding-top: 1.5rem; 42 | letter-spacing: 0.025rem; 43 | } 44 | .btns { 45 | display: flex; 46 | justify-content: center; 47 | align-items: center; 48 | width: 100%; 49 | margin-top: 1rem; 50 | gap: 1rem; 51 | .delete-btn { 52 | color: var(--white); 53 | background-color: var(--red); 54 | } 55 | .cancel-btn { 56 | color: #635fc7; 57 | background-color: #635fc71a; 58 | } 59 | .delete-btn, .cancel-btn { 60 | font-size: 16px; 61 | border: none; 62 | width: 100%; 63 | cursor: pointer; 64 | border-radius: 9999px; 65 | padding-top: 0.5rem; 66 | padding-bottom: 0.5rem; 67 | &:hover { 68 | opacity: 0.75; 69 | } 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /client/src/containers/app/all-routes.js: -------------------------------------------------------------------------------- 1 | import Login from '../sign-in'; 2 | import Dashboard from '../dashboard'; 3 | import Signup from '../sign-up/pre-signup'; 4 | import loginData from '../../data/signin.json'; 5 | import resetPasswordData from '../../data/resetPasswordData.json'; 6 | import Ledger from '../tasks'; 7 | import Departments from '../departments'; 8 | import Employees from '../employees'; 9 | import Projects from '../projects'; 10 | import ResetPassword from '../resetPassword'; 11 | import { 12 | Navigate 13 | } from "react-router-dom"; 14 | import EmployeeDetails from '../employees/employeeDetails'; 15 | import employeeDetailsData from '../../data/employeeDetailsData.json'; 16 | import Profile from '../profile'; 17 | import TasksBoard from '../tasks-board'; 18 | import OTP_VERIFICATION from '../sign-up/OTP-verification'; 19 | 20 | export const allRoutes = [ 21 | { 22 | path: '/login', 23 | element: 24 | }, 25 | { 26 | path: '/signup', 27 | element: 28 | }, 29 | { 30 | path: '/otp-verification/:id', 31 | element: 32 | }, 33 | { 34 | path: '/reset-password', 35 | element: 36 | }, 37 | { 38 | path: '/', 39 | element: 40 | }, 41 | { 42 | path: '/tasks', 43 | element: 44 | }, 45 | { 46 | path: '/tasks/board/:id', 47 | element: 48 | }, 49 | { 50 | path: '/departments', 51 | element: 52 | }, 53 | { 54 | path: '/employees', 55 | element: 56 | }, 57 | { 58 | path: '/employees/:id', 59 | element: 60 | }, 61 | { 62 | path: '/projects', 63 | element: 64 | }, 65 | { 66 | path: '/profile', 67 | element: 68 | }, 69 | { 70 | path: '/*', 71 | element: 72 | } 73 | ]; -------------------------------------------------------------------------------- /client/src/data/dashboardData.js: -------------------------------------------------------------------------------- 1 | 2 | export const mapDashboardData = (allCounts = {}, employees = []) => { 3 | 4 | return { 5 | metaData: { 6 | title: "Dashboard | TOTE" 7 | }, 8 | topSection: { 9 | allEntities: [ 10 | { 11 | title: 'Total departments', count: allCounts?.departments || 0, link: '/departments' 12 | }, 13 | { 14 | title: 'Total employees', count: allCounts?.employees || 0, link: '/employees' 15 | }, 16 | { 17 | title: 'Total projects', count: allCounts?.projects || 0, link: '/projects' 18 | }, 19 | ], 20 | }, 21 | mainSection:{ 22 | recentOrders : [ 23 | { 24 | name: "Star Refrigerator", price: "$ 1200", payment: "Paid", status: "Delivered", statusStyle: "delivered", 25 | }, 26 | { 27 | name: "Dell Laptop", price: "$ 110", payment: "Due", status: "Pending", statusStyle: "pending", 28 | }, 29 | { 30 | name: "Apple Watch", price: "$ 1200", payment: "Paid", status: "Return", statusStyle: "return", 31 | }, 32 | { 33 | name: "Addidas Shoes", price: "$ 620", payment: "Due", status: "In Progress", statusStyle: "inProgress", 34 | }, 35 | { 36 | name: "Start Refrigerator", price: "$ 1200", payment: "Paid", status: "Delivered", statusStyle: "delivered", 37 | }, 38 | { 39 | name: "Dell Laptop", price: "$ 110", payment: "Due", status: "Pending", statusStyle: "pending", 40 | }, 41 | { 42 | name: "Apple Watch", price: "$ 1200", payment: "Paid", status: "Return", statusStyle: "return", 43 | }, 44 | { 45 | name: "Addidas Shoes", price: "$ 620", payment: "Due", status: "In Progress", statusStyle: "inProgress", 46 | }, 47 | ], 48 | recentCustomers: employees?.map((employee)=> ({ 49 | id: employee?._id, 50 | name: employee?.name, 51 | email: employee?.email 52 | })) || [], 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /client/src/containers/tasks/board-card.js: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { boardActions } from "../../redux/reducers/board"; 4 | import DeleteModal from "../../components/delete-modal/DeleteModal"; 5 | import { deleteBoardById } from "../../services/tasks/taskBoards"; 6 | import { sendErrorNotification, sendSuccessNotification } from "../../services/notifications"; 7 | 8 | const BoardCard = ({ id, name, onClickEdit, onSelectBoard }) => { 9 | const [isTooltipActive, setIsTooltipActive] = useState(false); 10 | const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); 11 | const [isDeleteLoading, setIsDeleteLoading] = useState(false); 12 | const dispatch = useDispatch(); 13 | 14 | const handleEdit = () => { 15 | setIsTooltipActive(false); 16 | onClickEdit(id); 17 | } 18 | 19 | const handleDelete = async () => { 20 | setIsDeleteLoading(true); 21 | const result = await deleteBoardById(id); 22 | if(result.status === 'success') { 23 | dispatch(boardActions.deleteBoard({id})); 24 | sendSuccessNotification(result.message); 25 | }else{ 26 | sendErrorNotification(result.message); 27 | } 28 | setIsDeleteLoading(false); 29 | setIsDeleteModalOpen(false); 30 | } 31 | 32 | return ( 33 |
34 | {isDeleteModalOpen ? : null} 35 |
onSelectBoard(id)}> 36 |
{name}
37 |
38 |
39 | {isTooltipActive ?
40 | Edit board 41 | setIsDeleteModalOpen(true)}>Delete board 42 |
: null} 43 | setIsTooltipActive(prev => !prev)}> 44 |
45 |
46 | ) 47 | }; 48 | 49 | export default BoardCard; -------------------------------------------------------------------------------- /client/src/services/projects/projects.js: -------------------------------------------------------------------------------- 1 | import { fetchUrl } from '../../utils/fetchUrl'; 2 | import { getLocalStorageKey } from '../../utils/localStorage'; 3 | 4 | export const createProject = async (details) => { 5 | const token = getLocalStorageKey('token'); 6 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/create-project'; 7 | 8 | const headers = new Headers(); 9 | headers.append('Content-Type','application/json'); 10 | headers.append('token',token); 11 | 12 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'}; 13 | return await fetchUrl(url, requestOptions); 14 | }; 15 | 16 | export const getProjectDetails = async (id) => { 17 | const token = getLocalStorageKey('token'); 18 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/project'; 19 | 20 | const headers = new Headers(); 21 | headers.append('Content-Type','application/json'); 22 | headers.append('token',token); 23 | 24 | const query = `?id=${id}`; 25 | 26 | const requestOptions = {method: 'GET', headers, redirect: 'follow'}; 27 | return await fetchUrl(url+query, requestOptions); 28 | }; 29 | 30 | export const getListProjects = async () => { 31 | const token = getLocalStorageKey('token'); 32 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/list-projects'; 33 | 34 | const headers = new Headers(); 35 | headers.append('Content-Type','application/json'); 36 | headers.append('token',token); 37 | 38 | const requestOptions = {method: 'GET', headers, redirect: 'follow'}; 39 | return await fetchUrl(url, requestOptions); 40 | }; 41 | 42 | export const updateProject = async (id, details) => { 43 | const token = getLocalStorageKey('token'); 44 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/update-project'; 45 | 46 | const headers = new Headers(); 47 | headers.append('Content-Type','application/json'); 48 | headers.append('token',token); 49 | 50 | const query = `?id=${id}`; 51 | 52 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'}; 53 | return await fetchUrl(url+query, requestOptions); 54 | }; -------------------------------------------------------------------------------- /server/Controllers/department.js: -------------------------------------------------------------------------------- 1 | const Department = require('../db/departmentModel'); 2 | 3 | exports.createDepartment = async (req, res) => { 4 | const departObj = req.body; 5 | try{ 6 | const existingDepartment = await Department.find({title: departObj.title}); 7 | if(existingDepartment.length !== 0){ 8 | return res.status(409).json({ 9 | status: 'failed', 10 | message: 'the department already exists.' 11 | }) 12 | } 13 | await Department.create(departObj); 14 | 15 | res.status(201).json({ 16 | status: 'success', 17 | message: "successfully created the department." 18 | }) 19 | }catch(error){ 20 | res.status(403).json({ 21 | status: "failed", 22 | message: error.message 23 | }) 24 | } 25 | } 26 | 27 | exports.getAllDepartments = async (req, res) => { 28 | try{ 29 | const departments = await Department.find(); 30 | 31 | res.status(200).json({ 32 | status: 'success', 33 | response: departments 34 | }) 35 | }catch(error){ 36 | res.status(404).json({ 37 | status: 'failed', 38 | message: error.message 39 | }) 40 | } 41 | } 42 | 43 | exports.deleteDepartment = async (req, res) => { 44 | const {id} = req.query; 45 | try{ 46 | await Department.findByIdAndDelete(id); 47 | 48 | res.status(202).json({ 49 | status: "success", 50 | message: "successfully deleted department." 51 | }) 52 | }catch(error){ 53 | res.status(404).json({ 54 | status: 'failed', 55 | message: error.message 56 | }) 57 | } 58 | } 59 | 60 | exports.getDepartmentCount = async (_, res) => { 61 | try{ 62 | const count = await Department.countDocuments(); 63 | 64 | res.status(202).json({ 65 | status: 'success', 66 | response: count 67 | }) 68 | }catch(error){ 69 | res.status(500).json({ 70 | status: 'failed', 71 | message: error.message 72 | }) 73 | } 74 | }; -------------------------------------------------------------------------------- /client/src/containers/tasks/welcome-page.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import BoardCard from './board-card'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { boardActions } from '../../redux/reducers/board'; 5 | import CreateBoardModal from '../../components/create-board-modal/create-board-modal'; 6 | import { useNavigate } from 'react-router-dom'; 7 | 8 | const WelcomePage = ({ fetchBoards }) => { 9 | const { boards } = useSelector(state => state.boards); 10 | const [isBoardModalOpen, setIsBoardModalOpen] = useState({ type: 'add', status: false }); 11 | const dispatch = useDispatch(); 12 | const navigate = useNavigate(); 13 | 14 | const onClickEdit = (id) => { 15 | dispatch(boardActions.setSelectedBoard(boards.find(board => board._id === id))); 16 | setIsBoardModalOpen({ type: 'edit', status: true, id }); 17 | } 18 | 19 | const onSelectBoard = (id) => { 20 | dispatch(boardActions.setSelectedBoard({id})); 21 | navigate(`/tasks/board/${id}`); 22 | } 23 | 24 | return ( 25 |
26 | {isBoardModalOpen.status ? setIsBoardModalOpen(prev => ({ ...prev, status}))} 28 | type={isBoardModalOpen.type} 29 | refetchData={fetchBoards} 30 | /> : null} 31 | {boards.length !== 0 ? ( 32 | <> 33 |
34 | {boards.map((board, index)=> { 35 | return 42 | })} 43 |
44 |
setIsBoardModalOpen({ type: 'add', status: true })}> 45 | + Create new board 46 |
47 | 48 | ) : 49 |
setIsBoardModalOpen({ type: 'add', status: true })}> 50 | Click to create new 51 |
} 52 |
53 | ) 54 | } 55 | 56 | export default WelcomePage 57 | -------------------------------------------------------------------------------- /client/src/containers/departments/create-depart-form.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import Button from '../../components/button' 3 | import SimpleInput from '../../components/input' 4 | import { createDepartment } from '../../services/departments/departments'; 5 | import { sendErrorNotification, sendSuccessNotification } from '../../services/notifications'; 6 | import { checkAllTrue } from '../../utils/check-all'; 7 | import './create-depart.scss'; 8 | 9 | const CreateDepartForm = (props) => { 10 | const [[name, isNameValid], setName] = useState(['', false]); 11 | const [[url, isUrlValid], setURL] = useState(['', false]); 12 | 13 | const handleSubmit = async (e)=> { 14 | e.preventDefault(); 15 | if(!checkAllTrue([isNameValid, isUrlValid])){ 16 | sendErrorNotification("Please fill the details"); 17 | return; 18 | } 19 | const details = {title: name, url}; 20 | const response = await createDepartment(details); 21 | if(response.status === 'success'){ 22 | sendSuccessNotification("Successfully created a new department"); 23 | props.closeModal(); 24 | props.fetchData(); 25 | }else{ 26 | sendErrorNotification(response.message); 27 | } 28 | } 29 | 30 | const formComponents = { 31 | name: (key, props) => setName([value, !!value])} 34 | />, 35 | url: (key, props) => setURL([value, !!value])} 38 | />, 39 | button: (key, props) =>