├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.png ├── index.html ├── manifest.json └── robots.txt ├── src ├── App.js ├── ThemeProvider.js ├── assets │ └── images │ │ ├── becil.png │ │ ├── essi-logo.png │ │ ├── fav.png │ │ ├── footer-wave.png │ │ ├── mod-logo.png │ │ ├── no-data.png │ │ ├── no-data1.png │ │ ├── noimage.jpg │ │ ├── passlogo.png │ │ ├── test.jpg │ │ ├── vms-logo.png │ │ └── web-logo.png ├── components │ ├── SignatureCapture │ │ ├── CanvasModal.css │ │ ├── CanvasModal.jsx │ │ ├── STPadServerLib-3.3.0.js │ │ └── SignatureCapture.jsx │ ├── alert │ │ └── index.jsx │ ├── camera │ │ └── index.jsx │ ├── footer │ │ └── index.jsx │ ├── header │ │ └── index.jsx │ ├── loading │ │ └── index.jsx │ ├── notification │ │ └── index.jsx │ ├── pagination │ │ └── index.jsx │ ├── sidebar │ │ └── index.jsx │ └── topbar │ │ └── index.jsx ├── context │ └── UserContext.jsx ├── index.css ├── index.js ├── reportWebVitals.js ├── setupTests.js ├── utils │ ├── Constants.jsx │ ├── data │ │ ├── userData.js │ │ └── visitorData.js │ └── http │ │ └── index.ts └── views │ ├── auth │ ├── Faq.jsx │ ├── Login.jsx │ ├── Profile.jsx │ └── ResetPassword.jsx │ ├── configure │ ├── adam │ │ ├── Adams.jsx │ │ ├── AddNewAdam.jsx │ │ └── UpdateAdam.jsx │ ├── index.jsx │ ├── key │ │ ├── AddNewKey.jsx │ │ ├── Keys.jsx │ │ └── UpdateKey.jsx │ ├── map-guard │ │ ├── AddNewGuardReaderMapping.jsx │ │ ├── GuardReaderMappings.jsx │ │ └── UpdateGuardReaderMapping.jsx │ ├── reader │ │ ├── AddNewReader.jsx │ │ ├── Readers.jsx │ │ └── UpdateReader.jsx │ └── zone │ │ ├── AddNewZone.jsx │ │ ├── UpdateZone.jsx │ │ └── Zones.jsx │ ├── dashboard │ └── Dashboard.jsx │ ├── guard │ └── index.jsx │ ├── pass │ ├── CreateNewPass.jsx │ ├── MultipleSelectDropdown.jsx │ ├── Passes.jsx │ └── ViewPass.jsx │ ├── report │ └── index.jsx │ ├── user │ ├── AddNewUser.jsx │ ├── ResetPasswordUser.jsx │ ├── UpdateUser.jsx │ ├── UserProfile.jsx │ ├── Users.jsx │ └── index.jsx │ └── visitor │ ├── AddNewVisitor.jsx │ ├── UpdateVisitor.jsx │ ├── VisitorProfile.jsx │ ├── Visitors.jsx │ └── index.jsx └── tailwind.config.js /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Welcome to the Visitor Management System (VMS)! Our system is designed to simplify and enhance the visitor registration and management process for various environments, including offices, buildings, or events. With a user-friendly interface and role-based access control, VMS offers administrators, receptionists, and guards the tools they need to efficiently manage visitor traffic while ensuring security and compliance. 4 | 5 | ## Features 6 | 7 | - **Comprehensive Dashboard**: Gain insights into visitor traffic with detailed charts and statistics, including monthly visits, day-wise trends, zone-wise distribution, and recent visitor details. 8 | 9 | - **Role-Based Access Control**: Administrators, receptionists, and guards each have specific roles with tailored access to features, ensuring smooth operation and security compliance. 10 | 11 | ### Admin Role: 12 | 13 | - **Full Access**: Administrators have full access to all functionalities 14 | - **Dashboard**: Dashboard with charts showing details about monthly visits, day-wise statistics, zone-wise distribution, and recent visitor details. 15 | - **User Management**: Create, update, delete, and retrieve all users with filters and pagination. 16 | - **Visitor Management**: Create, update, delete, and retrieve all visitors with filters and pagination. 17 | - **Pass Management**: Generate passes for visitors with printing functionality, assign RFID keys for access control. 18 | - **FAQ Section**: Provide answers to frequently asked questions about how to use VMS. 19 | - **Modify Gadget Configuration**: Configure settings for five hardware gadgets with all operations. 20 | 21 | 22 | ### Receptionist Role: 23 | 24 | - **Limited Access**: Receptionists have access to specific functionalities. 25 | - **Dashboard**: View charts showing details about monthly visits, day-wise statistics, zone-wise distribution, and recent visitor details. 26 | - **Visitor Management**: Create, update, delete, and retrieve all visitors with filters and pagination, with the ability to blacklist visitors. 27 | - **Pass Management**: Generate passes for visitors, assign RFID keys, and provide printing functionality. 28 | - **Reports**: View and generate reports for users, such as login-logout, visitor visits, and gadget configuration modifications, with validations. 29 | - **FAQ Section**: Provide answers to frequently asked questions about how to use VMS. 30 | 31 | #### Guard Role: 32 | 33 | - **Limited Access**: Guards have access to specific functionalities. 34 | - **Visitor Verification**: Verify if the current visitor has a valid pass,show alert if invalid or blacklisted visitor. 35 | - **Recent Visitors**: View the top 5 recent visitors for monitoring purposes. 36 | 37 | ### Technology Used 38 | 39 | - **React**: A powerful JavaScript library for building user interfaces. 40 | - **Material UI**: A popular React component library for creating beautiful and responsive UI designs. 41 | - **Tailwind CSS**: A utility-first CSS framework for building custom designs with ease. 42 | 43 | ### Additional Features 44 | 45 | - **Step-Form**: Implement a step-by-step form for smoother visitor registration, with progress bars and model view. 46 | - **Webcam Integration**: Capture visitor images during registration using the react-webcam library. 47 | - **Signature Integration**: Integrate SignoTech for capturing visitor signatures during registration. 48 | - **Routing**: Implement routing for a seamless user experience and easy navigation within the system. 49 | - **Models**: Use models for showing details or creating forms. 50 | - **Pagination and Searching**: Implement frontend as well as backend pagination and searching techniques for fast response. 51 | 52 | ![login](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/be65aec7-e6aa-4245-9e01-2ef899271a51) 53 | 54 | ![dashboard](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/647077ca-0c67-40fe-becf-bc923601bf88) 55 | 56 | ![add-new-visitor-step-form](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/d662ddfe-aa3f-47f7-a5ff-97cb72a13f8b) 57 | 58 | ![delete-alert-model](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/fad07bf6-d080-4542-aebd-4fff41eabe1e) 59 | 60 | ![profile-model](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/97b42902-54ac-4c22-8cdf-ae191339dedc) 61 | 62 | ![view-download-report](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/be9e89f5-8e0b-497b-a49f-6589f31d7669) 63 | 64 | ![configuration-module](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/d30be889-8dfe-4779-95f0-f7283e8d84c0) 65 | 66 | ![faq](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/b9279e6e-53f9-483a-850c-4c11336c4376) 67 | 68 | ![visitor-profile-sidebar](https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/48b7de52-bb2a-4d27-8a51-8d06f75da93d) 69 | 70 | https://github.com/sanjeev662/visitor-management-system-react/assets/94432263/4b305ee4-c8b4-4f55-b489-ada68f1b97e2 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mod-vms", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.11.4", 7 | "@emotion/styled": "^11.11.0", 8 | "@headlessui/react": "^1.7.19", 9 | "@mui/icons-material": "^5.15.12", 10 | "@mui/joy": "^5.0.0-beta.32", 11 | "@mui/material": "^5.15.12", 12 | "@reduxjs/toolkit": "^2.2.2", 13 | "@testing-library/jest-dom": "^5.17.0", 14 | "@testing-library/react": "^13.4.0", 15 | "@testing-library/user-event": "^13.5.0", 16 | "axios": "^1.6.8", 17 | "chart.js": "^4.4.2", 18 | "cors": "^2.8.5", 19 | "dotenv": "^16.4.5", 20 | "formik": "^2.4.5", 21 | "js-cookie": "^3.0.5", 22 | "next-redux-wrapper": "^8.1.0", 23 | "react": "^18.2.0", 24 | "react-chartjs-2": "^5.2.0", 25 | "react-dom": "^18.2.0", 26 | "react-icons": "^5.0.1", 27 | "react-notifications-component": "^4.0.1", 28 | "react-pro-sidebar": "^1.1.0", 29 | "react-redux": "^9.1.0", 30 | "react-router-dom": "^6.22.3", 31 | "react-scripts": "5.0.1", 32 | "react-use-websocket": "^4.8.1", 33 | "react-select": "^5.8.0", 34 | "react-use-websocket": "^4.8.1", 35 | "react-webcam": "^7.2.0", 36 | "sweetalert": "^2.1.2", 37 | "tailwindcss": "^3.4.1", 38 | "web-vitals": "^2.1.4", 39 | "yup": "^1.4.0" 40 | }, 41 | "scripts": { 42 | "start": "react-scripts start", 43 | "build": "react-scripts build", 44 | "test": "react-scripts test", 45 | "eject": "react-scripts eject" 46 | }, 47 | "eslintConfig": { 48 | "extends": [ 49 | "react-app", 50 | "react-app/jest" 51 | ] 52 | }, 53 | "browserslist": { 54 | "production": [ 55 | ">0.2%", 56 | "not dead", 57 | "not op_mini all" 58 | ], 59 | "development": [ 60 | "last 1 chrome version", 61 | "last 1 firefox version", 62 | "last 1 safari version" 63 | ] 64 | }, 65 | "devDependencies": { 66 | "@types/js-cookie": "^3.0.6" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | VMS 16 | 17 | 18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; 3 | import { UserContext, UserContextProvider } from "./context/UserContext"; 4 | import { useNavigate } from "react-router-dom"; 5 | import Topbar from "./components/topbar"; 6 | import Sidebar from "./components/sidebar"; 7 | import Footer from "./components/footer"; 8 | import Login from "./views/auth/Login"; 9 | import Dashboard from "./views/dashboard/Dashboard"; 10 | import User from "./views/user"; 11 | import Visitor from "./views/visitor"; 12 | import Passes from "./views/pass/Passes"; 13 | import Configure from "./views/configure"; 14 | import Guard from "./views/guard"; 15 | import Faq from "./views/auth/Faq"; 16 | import Report from "./views/report"; 17 | function App() { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | 27 | function Content() { 28 | const navigate = useNavigate(); 29 | const [isAuthenticated, setIsAuthenticated] = useState(true); 30 | const [userType, setUserType] = useState(''); 31 | 32 | useEffect(() => { 33 | const checkAuth = () => { 34 | const token = localStorage.getItem("token"); 35 | const type = localStorage.getItem("user_type"); 36 | setIsAuthenticated(!!token); 37 | setUserType(type); 38 | 39 | if (!token) { 40 | navigate("/login"); 41 | } else if (type === "Guard") { 42 | navigate("/"); 43 | } 44 | }; 45 | 46 | checkAuth(); 47 | window.addEventListener("storage", checkAuth); 48 | return () => window.removeEventListener("storage", checkAuth); 49 | }, [navigate]); 50 | 51 | const renderRoutes = () => { 52 | switch (userType) { 53 | case 'Guard': 54 | return } />; 55 | case 'Receptionist': 56 | return ( 57 | <> 58 | } /> 59 | } /> 60 | } /> 61 | } /> 62 | } /> 63 | 64 | ); 65 | default: 66 | return ( 67 | <> 68 | } /> 69 | } /> 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | } /> 75 | } /> 76 | 77 | ); 78 | } 79 | }; 80 | 81 | return isAuthenticated ? ( 82 |
83 |
84 | {userType !== 'Guard' && } 85 |
86 | 87 |
88 | 89 | {renderRoutes()} 90 | 91 |
92 |
93 |
94 |
95 |
96 | ) : ( 97 | 98 | } /> 99 | } /> 100 | 101 | ); 102 | } 103 | 104 | export default App; 105 | 106 | -------------------------------------------------------------------------------- /src/ThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useContext, useEffect } from "react"; 2 | import { createTheme } from "@mui/material/styles"; 3 | 4 | const ThemeContext = createContext(); 5 | 6 | // Light mode theme 7 | const lightTheme = createTheme({ 8 | palette: { 9 | mode: "light", 10 | primary: { 11 | main: "#17a2b8", 12 | }, 13 | secondary: { 14 | main: "#757575", 15 | }, 16 | error: { 17 | main: "#f44336", 18 | }, 19 | warning: { 20 | main: "#ff9800", 21 | }, 22 | info: { 23 | main: "#2196f3", 24 | }, 25 | success: { 26 | main: "#4caf50", 27 | }, 28 | background: { 29 | default: "#ffffff", 30 | main: "#17a2b8", 31 | header: "#f4f4f5", 32 | }, 33 | text: { 34 | main: "#ffffff", 35 | primary: "#000000", 36 | secondary: "#757575", 37 | }, 38 | border: { 39 | main: "#e0e0e0", 40 | }, 41 | }, 42 | }); 43 | 44 | // Dark mode theme 45 | const darkTheme = createTheme({ 46 | palette: { 47 | mode: "dark", 48 | primary: { 49 | main: "#011638", 50 | }, 51 | secondary: { 52 | main: "#3b82f6", 53 | }, 54 | error: { 55 | main: "#f44336", 56 | }, 57 | warning: { 58 | main: "#ff9800", 59 | }, 60 | info: { 61 | main: "#2196f3", 62 | }, 63 | success: { 64 | main: "#4caf50", 65 | }, 66 | background: { 67 | default: "#ffffff", 68 | main: "#011638", 69 | header: "#f4f4f5", 70 | }, 71 | text: { 72 | main: "#ffffff", 73 | primary: "#000000", 74 | secondary: "#3b82f6", 75 | }, 76 | border: { 77 | main: "#616161", 78 | }, 79 | }, 80 | }); 81 | 82 | // Default mode theme 83 | const defaultTheme = createTheme({ 84 | palette: { 85 | mode: "default", 86 | primary: { 87 | main: "#364b24", 88 | }, 89 | secondary: { 90 | main: "#eb8a2b", 91 | }, 92 | error: { 93 | main: "#f44336", 94 | }, 95 | warning: { 96 | main: "#ff9800", 97 | }, 98 | info: { 99 | main: "#2196f3", 100 | }, 101 | success: { 102 | main: "#4caf50", 103 | }, 104 | background: { 105 | default: "#ffffff", 106 | main: "#364b24", 107 | header: "#f4f4f5", 108 | }, 109 | text: { 110 | main: "#ffffff", 111 | primary: "#333333", 112 | secondary: "#697e57", 113 | }, 114 | border: { 115 | main: "#616161", 116 | }, 117 | }, 118 | }); 119 | 120 | // Custom mode theme 121 | const customTheme = createTheme({ 122 | palette: { 123 | mode: "custom", 124 | primary: { 125 | main: "#eb8a2b", 126 | }, 127 | secondary: { 128 | main: "#17a2b8", 129 | }, 130 | error: { 131 | main: "#f44336", 132 | }, 133 | warning: { 134 | main: "#ff9800", 135 | }, 136 | info: { 137 | main: "#2196f3", 138 | }, 139 | success: { 140 | main: "#4caf50", 141 | }, 142 | background: { 143 | default: "#ffffff", 144 | main: "#eb8a2b", 145 | header: "#f4f4f5", 146 | }, 147 | text: { 148 | main: "#ffffff", 149 | primary: "#333333", 150 | secondary: "#757575", 151 | }, 152 | border: { 153 | main: "#e0e0e0", 154 | }, 155 | }, 156 | }); 157 | 158 | export const ThemeProvider = ({ children }) => { 159 | const [theme, setTheme] = useState(darkTheme); 160 | 161 | const toggleTheme = (newTheme) => { 162 | switch (newTheme) { 163 | case "light": 164 | setTheme(lightTheme); 165 | break; 166 | case "dark": 167 | setTheme(darkTheme); 168 | break; 169 | case "default": 170 | setTheme(defaultTheme); 171 | break; 172 | case "custom": 173 | setTheme(customTheme); 174 | break; 175 | default: 176 | setTheme(lightTheme); 177 | break; 178 | } 179 | }; 180 | 181 | useEffect(() => { 182 | console.log(theme); 183 | }, [theme]); 184 | 185 | return ( 186 | 187 | {children} 188 | 189 | ); 190 | }; 191 | 192 | export const useTheme = () => useContext(ThemeContext); 193 | -------------------------------------------------------------------------------- /src/assets/images/becil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/becil.png -------------------------------------------------------------------------------- /src/assets/images/essi-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/essi-logo.png -------------------------------------------------------------------------------- /src/assets/images/fav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/fav.png -------------------------------------------------------------------------------- /src/assets/images/footer-wave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/footer-wave.png -------------------------------------------------------------------------------- /src/assets/images/mod-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/mod-logo.png -------------------------------------------------------------------------------- /src/assets/images/no-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/no-data.png -------------------------------------------------------------------------------- /src/assets/images/no-data1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/no-data1.png -------------------------------------------------------------------------------- /src/assets/images/noimage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/noimage.jpg -------------------------------------------------------------------------------- /src/assets/images/passlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/passlogo.png -------------------------------------------------------------------------------- /src/assets/images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/test.jpg -------------------------------------------------------------------------------- /src/assets/images/vms-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/vms-logo.png -------------------------------------------------------------------------------- /src/assets/images/web-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sanjeev662/visitor-management-system-react/d2344a2003699b1f38f7ba3ba709bda7223b78a4/src/assets/images/web-logo.png -------------------------------------------------------------------------------- /src/components/SignatureCapture/CanvasModal.css: -------------------------------------------------------------------------------- 1 | .modalBackground { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100vw; 6 | height: 100vh; 7 | background-color: rgba(0, 0, 0, 0.5); 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | z-index: 1000; 12 | } 13 | 14 | .modalContainer { 15 | width: 400px; 16 | background-color: #fff; 17 | border-radius: 8px; 18 | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); 19 | overflow: hidden; 20 | } 21 | 22 | .titleCloseBtn { 23 | display: flex; 24 | justify-content: flex-end; 25 | padding: 10px 15px 2px 15px; 26 | } 27 | 28 | .titleCloseBtn button { 29 | background: transparent; 30 | border: none; 31 | font-size: 18px; 32 | cursor: pointer; 33 | } 34 | 35 | .title { 36 | text-align: center; 37 | padding: 5px 10px 15px 10px; 38 | border-bottom: 1px solid #ccc; 39 | } 40 | 41 | .body { 42 | padding: 20px; 43 | display: flex; 44 | justify-content: center; 45 | } 46 | 47 | .footer { 48 | display: flex; 49 | justify-content: space-around; 50 | padding: 10px; 51 | border-top: 1px solid #ccc; 52 | } 53 | 54 | .footer button { 55 | padding: 5px 10px; 56 | border: none; 57 | border-radius: 5px; 58 | cursor: pointer; 59 | } 60 | 61 | #cancelBtn { 62 | background-color: #f44336; 63 | color: white; 64 | } 65 | 66 | #confirmBtn { 67 | background-color: #4caf50; 68 | color: white; 69 | } 70 | 71 | #retryBtn { 72 | background-color: #ff9800; 73 | color: white; 74 | } 75 | -------------------------------------------------------------------------------- /src/components/SignatureCapture/CanvasModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { 3 | STPadServerLibDefault, 4 | STPadServerLibCommons, 5 | } from "./STPadServerLib-3.3.0"; 6 | import "./CanvasModal.css"; 7 | import Notification from "../notification"; 8 | 9 | function CanvasModal({ 10 | open, 11 | liveImageData, 12 | setOpenModal, 13 | sendDatatoMain, 14 | clearCanvas, 15 | scaleFactorhorizontal, 16 | scaleFactorvertical, 17 | }) { 18 | const canvasRef = React.useRef(null); 19 | 20 | function drawStrokeStartPoint(canvasContext, softCoordX, softCoordY) { 21 | canvasContext.beginPath(); 22 | canvasContext.arc(softCoordX, softCoordY, 0.1, 0, 2 * Math.PI, true); 23 | canvasContext.fill(); 24 | canvasContext.stroke(); 25 | canvasContext.moveTo(softCoordX, softCoordY); 26 | } 27 | 28 | function isCanvasEmpty() { 29 | const canvas = canvasRef.current; 30 | const ctx = canvas.getContext("2d"); 31 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; 32 | 33 | for (let i = 3; i < imageData.length; i += 4) { 34 | if (imageData[i] !== 0) { 35 | return false; 36 | } 37 | } 38 | return true; 39 | } 40 | 41 | function drawStrokePoint(canvasContext, softCoordX, softCoordY) { 42 | canvasContext.lineTo(softCoordX, softCoordY); 43 | canvasContext.stroke(); 44 | } 45 | 46 | useEffect(() => { 47 | const canvas = canvasRef.current; 48 | const ctx = canvas.getContext("2d", { willReadFrequently: true }); 49 | if (liveImageData !== null) { 50 | const x = liveImageData.x; 51 | const y = liveImageData.y; 52 | const p = liveImageData.p; 53 | const scaleFactorX = scaleFactorhorizontal; 54 | const scaleFactorY = scaleFactorvertical; 55 | ctx.fillStyle = "#fff"; 56 | ctx.lineWidth = 1.5; 57 | 58 | ctx.strokeStyle = "#FF0000"; 59 | if (p === 0) { 60 | drawStrokeStartPoint(ctx, x * 0.25, y * 0.25); 61 | } else { 62 | drawStrokePoint(ctx, x * 0.25, y * 0.25); 63 | } 64 | } 65 | }, [liveImageData]); 66 | 67 | useEffect(() => { 68 | if (clearCanvas) { 69 | const canvas = canvasRef.current; 70 | const ctx = canvas.getContext("2d"); 71 | ctx.clearRect(0, 0, canvas.width, canvas.height); 72 | } 73 | }, [clearCanvas]); 74 | 75 | const handleRetry = async () => { 76 | await STPadServerLibDefault.retrySignature(); 77 | const canvas = canvasRef.current; 78 | const ctx = canvas.getContext("2d"); 79 | ctx.clearRect(0, 0, canvas.width, canvas.height); 80 | }; 81 | 82 | const handleConfirm = async () => { 83 | if (isCanvasEmpty()) { 84 | Notification.showErrorMessage("Info", "Please draw a signature"); 85 | } else { 86 | try { 87 | var awaitConfirmSignature = await STPadServerLibDefault.confirmSignature(); 88 | 89 | var params = new STPadServerLibDefault.Params.getSignatureImage(); 90 | var params2 = new STPadServerLibDefault.Params.closePad(0); 91 | params.setFileType(STPadServerLibDefault.FileType.PNG); 92 | params.setPenWidth(5); 93 | var awaitgetSignatureImage = await STPadServerLibDefault.getSignatureImage(params); 94 | 95 | const base64 = awaitgetSignatureImage.file; 96 | sendDatatoMain(base64); 97 | 98 | await STPadServerLibDefault.closePad(params2); 99 | await STPadServerLibCommons.destroyConnection(); 100 | } catch (error) { 101 | console.error(error); 102 | } 103 | setOpenModal(false); 104 | } 105 | }; 106 | 107 | const handleCancel = async () => { 108 | setOpenModal(false); 109 | try { 110 | await STPadServerLibDefault.cancelSignature(); 111 | var params2 = new STPadServerLibDefault.Params.closePad(0); 112 | await STPadServerLibDefault.closePad(params2); 113 | await STPadServerLibCommons.destroyConnection(); 114 | } catch (error) { 115 | console.error(error); 116 | } 117 | }; 118 | 119 | if (!open) return null; 120 | 121 | return ( 122 |
123 |
124 |
125 | 128 |
129 |
130 |

Please sign on the pad

131 |
132 |
133 | 134 |
135 |
136 | 139 | 142 | 145 |
146 |
147 |
148 | ); 149 | } 150 | 151 | export default CanvasModal; 152 | -------------------------------------------------------------------------------- /src/components/SignatureCapture/SignatureCapture.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Grid } from "@mui/material"; 3 | import { 4 | STPadServerLibDefault, 5 | STPadServerLibCommons, 6 | } from "./STPadServerLib-3.3.0"; 7 | import Notification from "../notification"; 8 | import CanvasModal from "./CanvasModal"; 9 | 10 | const SignatureCapture = ({ onCapture }) => { 11 | const [scaleFactorX, setScaleFactorX] = useState(0); 12 | const [scaleFactorY, setScaleFactorY] = useState(0); 13 | const [message, setMessage] = useState(""); 14 | const [clearCanvas, setClearCanvas] = useState(false); 15 | const [modalOpen, setModalOpen] = useState(false); 16 | const [data, setData] = useState(null); 17 | 18 | const handleDataFromCanvas = (data) => { 19 | setMessage(data); 20 | }; 21 | 22 | const fetchData = async () => { 23 | function onOpen(connection) { 24 | let sampleRate = 0; 25 | STPadServerLibDefault.handleConfirmSignature = async function () { 26 | try { 27 | var awaitConfirmSignature = 28 | await STPadServerLibDefault.confirmSignature(); 29 | const countedPoints = awaitConfirmSignature.countedPoints; 30 | 31 | const valueforSignature = countedPoints / sampleRate; 32 | var params = new STPadServerLibDefault.Params.getSignatureImage(); 33 | params.setFileType(STPadServerLibDefault.FileType.PNG); 34 | params.setPenWidth(5); 35 | var awaitgetSignatureImage = 36 | await STPadServerLibDefault.getSignatureImage(params); 37 | const base64 = awaitgetSignatureImage.file; 38 | setMessage(base64); 39 | setModalOpen(false); 40 | 41 | await STPadServerLibDefault.closePad(params4); 42 | await STPadServerLibCommons.destroyConnection(); 43 | } catch (error) { 44 | if ( 45 | (error.errorMessage = 46 | "The function could not be executed because no signature capture process was started.") 47 | ) { 48 | Notification.showErrorMessage("Info", "Please draw a signature"); 49 | return await STPadServerLibDefault.retrySignature(); 50 | } 51 | } 52 | }; 53 | 54 | STPadServerLibDefault.handleRetrySignature = async function () { 55 | await STPadServerLibDefault.retrySignature(); 56 | setClearCanvas(true); 57 | }; 58 | 59 | STPadServerLibDefault.handleCancelSignature = async function () { 60 | await STPadServerLibDefault.cancelSignature(); 61 | var params = new STPadServerLibDefault.Params.closePad(0); 62 | await STPadServerLibDefault.closePad(params); 63 | await STPadServerLibCommons.destroyConnection(); 64 | setModalOpen(false); 65 | }; 66 | 67 | const signatureQueue = []; 68 | 69 | STPadServerLibCommons.handleNextSignaturePoint = async function ( 70 | x, 71 | y, 72 | p 73 | ) { 74 | const signaturePoint = { x: x, y: y, p: p }; 75 | setData(signaturePoint); 76 | signatureQueue.push({ x, y, p }); 77 | }; 78 | 79 | async function performOperations( 80 | params1, 81 | params2, 82 | params3, 83 | getSignatureDataParams 84 | ) { 85 | try { 86 | const result1 = await STPadServerLibDefault.searchForPads(params1); 87 | const result2 = await STPadServerLibDefault.openPad(params2); 88 | const result3 = await STPadServerLibDefault.startSignature( 89 | params3, 90 | STPadServerLibDefault.handleCancelSignature, 91 | STPadServerLibDefault.handleRetrySignature, 92 | STPadServerLibDefault.handleConfirmSignature, 93 | STPadServerLibCommons.handleNextSignaturePoint 94 | ); 95 | 96 | const padHeight = result2.padInfo.displayHeight; 97 | const padWidth = result2.padInfo.displayWidth; 98 | const xResolution = result2.padInfo.xResolution; 99 | const yResolution = result2.padInfo.yResolution; 100 | sampleRate = result2.padInfo.samplingRate; 101 | setScaleFactorX(padWidth / xResolution); 102 | setScaleFactorY(padHeight / yResolution); 103 | } catch (error) { 104 | if ( 105 | error.errorMessage === 106 | "No compatible devices connected or the connection to a device has been cut." 107 | ) { 108 | Notification.showErrorMessage("Info", "Please connect a signing pad"); 109 | } 110 | STPadServerLibCommons.destroyConnection(); 111 | return "No Pad Found"; 112 | } 113 | } 114 | 115 | var params1 = new STPadServerLibDefault.Params.searchForPads(); 116 | var params2 = new STPadServerLibDefault.Params.openPad(0); 117 | var params4 = new STPadServerLibDefault.Params.closePad(0); 118 | var params3 = new STPadServerLibDefault.Params.startSignature(); 119 | params3.setFieldName("Customer Sign"); 120 | params3.setCustomText("Please sign"); 121 | params1.setPadSubset("HID"); 122 | var getSignatureDataParams = 123 | new STPadServerLibDefault.Params.getSignatureData(); 124 | getSignatureDataParams.setRsaScheme("PSS"); 125 | 126 | performOperations(params1, params2, params3, getSignatureDataParams); 127 | } 128 | 129 | function onClose(connection) { 130 | Notification.showErrorMessage("Info", "Signotec Sigma Pad disconnected"); 131 | } 132 | 133 | function onError(connection, error) { 134 | // console.log("Signotec Sigma Pad error:", error); 135 | } 136 | 137 | try { 138 | await STPadServerLibCommons.createConnection( 139 | "wss://local.signotecwebsocket.de:49494/", 140 | onOpen, 141 | onClose, 142 | onError 143 | ); 144 | } catch (e) { 145 | // console.log(e); 146 | } 147 | }; 148 | 149 | useEffect(() => { 150 | STPadServerLibCommons.destroyConnection(); 151 | }, []); 152 | 153 | useEffect(() => { 154 | if (message !== "") { 155 | onCapture(message); 156 | } 157 | }, [message]); 158 | 159 | const handleButtonClick = async () => { 160 | const result = await fetchData(); 161 | if (result === "No Pad Found") { 162 | setModalOpen(false); 163 | } else { 164 | setModalOpen(true); 165 | } 166 | }; 167 | 168 | const handleButtonClickRetake = () => { 169 | setMessage(""); 170 | const result = fetchData(); 171 | if (result === "No Pad Found") { 172 | setModalOpen(false); 173 | } else { 174 | setModalOpen(true); 175 | } 176 | }; 177 | 178 | return ( 179 |
183 | 184 | 185 | {message !== "" ? ( 186 | 189 | ) : ( 190 | 193 | )} 194 | 195 | 196 | {modalOpen && ( 197 | 206 | )} 207 |
208 | ); 209 | }; 210 | 211 | export default SignatureCapture; 212 | -------------------------------------------------------------------------------- /src/components/alert/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Dialog, 4 | DialogTitle, 5 | DialogContent, 6 | DialogActions, 7 | Button, 8 | IconButton, 9 | } from "@mui/material"; 10 | import CloseIcon from "@mui/icons-material/Close"; 11 | 12 | const Alert = ({ 13 | open, 14 | onClose, 15 | title, 16 | message, 17 | buttonText, 18 | buttonColor, 19 | onButtonClick, 20 | }) => { 21 | return ( 22 | 23 | 32 | 33 | 34 | 35 | {title} 36 | 37 | {message} 38 | 39 | 51 | 52 | 53 | ); 54 | }; 55 | 56 | export default Alert; 57 | -------------------------------------------------------------------------------- /src/components/camera/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import Notification from '../notification'; 4 | import Webcam from 'react-webcam'; 5 | 6 | const CameraModal = ({ open, onClose, onCaptured }) => { 7 | const webcamRef = useRef(null); 8 | const [image, setImage] = useState(null); 9 | const [hasCameraError, setHasCameraError] = useState(false); 10 | 11 | const capture = () => { 12 | const imageSrc = webcamRef.current.getScreenshot(); 13 | setImage(imageSrc); 14 | }; 15 | 16 | const confirm = () => { 17 | if (image) { 18 | const base64Data = image.replace(/^data:image\/(?:png|jpg|jpeg);base64,/, ''); 19 | onCaptured(base64Data); 20 | resetState(); 21 | } 22 | }; 23 | 24 | const resetState = () => { 25 | setImage(null); 26 | onClose(); 27 | }; 28 | 29 | const handleCameraError = (error) => { 30 | console.error('Webcam error:', error); 31 | setHasCameraError(true); 32 | 33 | Notification.showErrorMessage("Info", 'Unable to access the webcam.'); 34 | resetState(); 35 | }; 36 | 37 | const videoConstraints = { 38 | width: 200, 39 | height: 200, 40 | facingMode: "user" 41 | }; 42 | 43 | if (!open) return null; 44 | 45 | return ( 46 | 47 | 48 | Capture Image 49 | 50 |
51 |
52 | {image ? ( 53 | Captured 54 | ) : ( 55 | 63 | )} 64 |
65 |
66 | {!image && ( 67 | 70 | )} 71 | {image && ( 72 | 75 | )} 76 | {image && ( 77 | 80 | )} 81 | 84 |
85 |
86 |
87 | ); 88 | }; 89 | 90 | export default CameraModal; 91 | -------------------------------------------------------------------------------- /src/components/footer/index.jsx: -------------------------------------------------------------------------------- 1 | import essilogo from "../../assets/images/essi-logo.png"; 2 | import becillogo from "../../assets/images/becil.png"; 3 | import vmslogo from "../../assets/images/vms-logo.png"; 4 | 5 | const Footer = () => { 6 | return ( 7 |
8 |
9 | Designed and Maintained by Sanjeev 10 |
11 |
12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default Footer; 19 | -------------------------------------------------------------------------------- /src/components/header/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Box from "@mui/material/Box"; 3 | import Typography from "@mui/material/Typography"; 4 | import { Link as RouterLink } from "react-router-dom"; 5 | import { useTheme } from "../../ThemeProvider"; 6 | 7 | const Header = ({ title, breadcrumbs }) => { 8 | const theme = useTheme(); 9 | const { palette } = theme.theme; 10 | 11 | return ( 12 | 13 | {title && ( 14 | 20 | {title} 21 | 22 | )} 23 | {breadcrumbs && breadcrumbs.length > 0 && ( 24 | 25 | {breadcrumbs.map((breadcrumb, index) => ( 26 | 27 | 31 | {breadcrumb.text} 32 | 33 | {index < breadcrumbs.length - 1 && " / "} 34 | 35 | ))} 36 | 37 | )} 38 | 39 | ); 40 | }; 41 | 42 | export default Header; 43 | -------------------------------------------------------------------------------- /src/components/loading/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { CircularProgress, Grid } from "@mui/material"; 3 | import footerwave from "../../assets/images/footer-wave.png"; 4 | 5 | const Loading = () => { 6 | return ( 7 |
8 | 9 |
10 | Wave 11 |
12 |
13 | ); 14 | }; 15 | 16 | export default Loading; 17 | -------------------------------------------------------------------------------- /src/components/notification/index.jsx: -------------------------------------------------------------------------------- 1 | import { Store } from "react-notifications-component"; 2 | 3 | const showErrorMessage = (title, message, duration=2000) => { 4 | Store.addNotification({ 5 | title: title, 6 | message: message, 7 | type: "danger", 8 | insert: "bottom", 9 | container: "bottom-right", 10 | animationIn: ["animated", "fadeIn"], 11 | animationOut: ["animated", "fadeOut"], 12 | dismiss: { 13 | duration: duration, 14 | onScreen: true, 15 | showIcon: true, 16 | }, 17 | }); 18 | }; 19 | 20 | const showSuccessMessage = (title, message) => { 21 | Store.addNotification({ 22 | title: title, 23 | message: message, 24 | type: "success", 25 | insert: "bottom", 26 | container: "bottom-right", 27 | animationIn: ["animated", "fadeIn"], 28 | animationOut: ["animated", "fadeOut"], 29 | dismiss: { 30 | duration: 2000, 31 | onScreen: true, 32 | showIcon: true, 33 | }, 34 | }); 35 | }; 36 | 37 | 38 | const Notification = { 39 | showErrorMessage, 40 | showSuccessMessage, 41 | }; 42 | 43 | export default Notification; 44 | -------------------------------------------------------------------------------- /src/components/pagination/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Pagination = ({ currentPage, totalPages, paginate }) => { 4 | let startPage = currentPage - 1; 5 | if (startPage <= 1) startPage = 1; 6 | let endPage = startPage + 2; 7 | if (endPage > totalPages) { 8 | endPage = totalPages; 9 | startPage = endPage - 2; 10 | if (startPage < 1) startPage = 1; 11 | } 12 | 13 | const pageNumbers = []; 14 | for (let i = startPage; i <= endPage; i++) { 15 | pageNumbers.push(i); 16 | } 17 | 18 | return ( 19 |
20 | 28 | {pageNumbers.map(number => ( 29 | 37 | ))} 38 | 46 |
47 | ); 48 | }; 49 | 50 | export default Pagination; 51 | -------------------------------------------------------------------------------- /src/components/sidebar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react'; 2 | import { Link, useLocation } from 'react-router-dom'; 3 | import { UserContext } from '../../context/UserContext'; 4 | import modlogo from '../../assets/images/mod-logo.png'; 5 | import becillogo from '../../assets/images/becil.png'; 6 | import vmslogo from "../../assets/images/vms-logo.png"; 7 | import ForumIcon from '@mui/icons-material/Forum'; 8 | import CreditCardIcon from '@mui/icons-material/CreditCard'; 9 | import ReceiptIcon from '@mui/icons-material/Receipt'; 10 | import HomeOutlinedIcon from "@mui/icons-material/HomeOutlined"; 11 | import PeopleOutlinedIcon from "@mui/icons-material/PeopleOutlined"; 12 | import ListIcon from '@mui/icons-material/List'; 13 | import MenuIcon from '@mui/icons-material/Menu'; 14 | import SettingsIcon from '@mui/icons-material/Settings'; 15 | 16 | const navItemsAdmin = [ 17 | { name: 'Dashboard', icon: , path: '/' }, 18 | { name: 'Visitors', icon: , path: '/visitor' }, 19 | { name: 'Users', icon: , path: '/user' }, 20 | { name: 'Passes', icon: , path: '/pass' }, 21 | { name: 'Reports', icon: , path: '/report' }, 22 | { name: 'FAQ', icon: , path: '/faq' }, 23 | { name: 'Configure', icon: , path: '/configure' }, 24 | ]; 25 | 26 | const navItemsReceptionist = [ 27 | { name: 'Dashboard', icon: , path: '/' }, 28 | { name: 'Visitors', icon: , path: '/visitor' }, 29 | { name: 'Passes', icon: , path: '/pass' }, 30 | { name: 'Reports', icon: , path: '/report' }, 31 | { name: 'FAQ', icon: , path: '/faq' }, 32 | ]; 33 | 34 | const SideBar = () => { 35 | const [isCollapsed, setIsCollapsed] = useState(false); 36 | const [role, setRole] = useState(''); 37 | const { setUser } = useContext(UserContext); 38 | const location = useLocation(); 39 | 40 | useEffect(() => { 41 | const storedRole = localStorage.getItem("user_type"); 42 | setRole(storedRole); 43 | }, [setUser]); 44 | 45 | const toggleSidebar = () => { 46 | setIsCollapsed(!isCollapsed); 47 | }; 48 | 49 | if (role === "Guard") { 50 | return null; 51 | } 52 | 53 | const navItems = role === 'Admin' ? navItemsAdmin : navItemsReceptionist; 54 | 55 | return ( 56 | 83 | ) 84 | }; 85 | 86 | export default SideBar; 87 | -------------------------------------------------------------------------------- /src/components/topbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from "react"; 2 | import { Link, useNavigate, useLocation } from "react-router-dom"; 3 | import { UserContext } from "../../context/UserContext.jsx"; 4 | import Notification from "../../components/notification"; 5 | import essilogo from "../../assets/images/essi-logo.png"; 6 | import Profile from "../../views/auth/Profile.jsx"; 7 | import { url } from "../../utils/Constants"; 8 | 9 | const Topbar = () => { 10 | let history = useNavigate(); 11 | const [profileModalOpen, setProfileModalOpen] = useState(false); 12 | const { setIslogin, setUser, user, setDatas } = useContext(UserContext); 13 | 14 | const handleLogout = async () => { 15 | try { 16 | const response = await fetch(`${url}/accounts/logout-user/`, { 17 | method: "POST", 18 | headers: { 19 | "Content-Type": "application/json", 20 | Authorization: `Bearer ${localStorage.getItem("token")}`, 21 | }, 22 | body: JSON.stringify({ 23 | refresh: localStorage.getItem("refresh_token"), 24 | }), 25 | }); 26 | if (response.ok) { 27 | localStorage.clear(); 28 | setUser(); 29 | history("/login"); 30 | Notification.showSuccessMessage( 31 | "Logout Successfully!", 32 | "You have been logged out successfully." 33 | ); 34 | } 35 | } catch (err) { 36 | Notification.showErrorMessage("Error", "Server error!"); 37 | } 38 | }; 39 | 40 | let username = localStorage.getItem("user_name"); 41 | let userimage = localStorage.getItem("image"); 42 | 43 | useEffect(() => { 44 | username = localStorage.getItem("user_name"); 45 | userimage = localStorage.getItem("image"); 46 | }); 47 | 48 | return ( 49 | <> 50 |
51 | {/* MOD Logo */} 52 |
53 | VISITOR MANAGEMENT SYSTEM 54 |
55 | 56 | {localStorage.getItem("token") && ( 57 |
58 |
setProfileModalOpen(true)} 61 | > 62 |
63 | {userimage != "null" ? ( 64 | User 71 | ) : ( 72 | 73 | {username ? username.charAt(0).toUpperCase() : "N"} 74 | 75 | )} 76 |
77 | {username} 78 |
79 | 80 | 86 |
87 | )} 88 |
89 | 90 | setProfileModalOpen(false)} 93 | /> 94 | 95 | ); 96 | }; 97 | export default Topbar; 98 | -------------------------------------------------------------------------------- /src/context/UserContext.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import Notification from "../components/notification"; 4 | import { url } from "../utils/Constants"; 5 | 6 | export const UserContext = createContext(); 7 | 8 | export function UserContextProvider({ children }) { 9 | const [isAuthenticated, setIsAuthenticated] = useState(false); 10 | const [user, setUser] = useState(); 11 | const [username, setUsername] = useState(""); 12 | const [islogin, setIslogin] = useState(false); 13 | const [loggedUser, setLoggedUser] = useState(); 14 | const navigate = useNavigate(); 15 | 16 | useEffect(() => { 17 | const verifyToken = async () => { 18 | try { 19 | const response = await fetch(`${url}/accounts/validate-token/`, { 20 | method: "POST", 21 | headers: { 22 | "Content-Type": "application/json", 23 | Authorization: `Bearer ${localStorage.getItem("token")}`, 24 | }, 25 | body: JSON.stringify({ 26 | token: localStorage.getItem("token"), 27 | }), 28 | }); 29 | const json = await response.json(); 30 | 31 | if (!response.ok) { 32 | Notification.showErrorMessage("Sorry", "Session has Expired!"); 33 | localStorage.clear(); 34 | setUser(); 35 | navigate("/login"); 36 | } 37 | } catch (err) { 38 | Notification.showErrorMessage("Error", "Server error!"); 39 | } 40 | }; 41 | verifyToken(); 42 | }, []); 43 | 44 | return ( 45 | 56 | {children} 57 | 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap'); 7 | 8 | 9 | * { 10 | font-family: "Inter", sans-serif; 11 | font-optical-sizing: auto; 12 | font-weight: 300; 13 | font-style: normal; 14 | font-variation-settings: "slnt" 0; 15 | scrollbar-width: none; 16 | } 17 | 18 | 19 | @media print { 20 | body * { 21 | visibility: hidden; 22 | } 23 | 24 | .printableArea, 25 | .printableArea * { 26 | visibility: visible; 27 | } 28 | 29 | .printableArea { 30 | position: fixed; 31 | top: 0; 32 | left: 0; 33 | min-width: 100%; 34 | height: auto; 35 | overflow: visible; 36 | z-index: 1000; 37 | } 38 | 39 | .MuiDialog-paper { 40 | position: absolute; 41 | margin: 0 !important; 42 | padding: 0 !important; 43 | max-width: none !important; 44 | transform: none !important; 45 | box-shadow: none; 46 | } 47 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import "./index.css"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | import { ThemeProvider } from "./ThemeProvider"; 7 | import CssBaseline from "@mui/material/CssBaseline"; 8 | import { ReactNotifications } from "react-notifications-component"; 9 | import "react-notifications-component/dist/theme.css"; 10 | 11 | ReactDOM.render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | , 19 | document.getElementById("root") 20 | ); 21 | 22 | reportWebVitals(); 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | -------------------------------------------------------------------------------- /src/utils/Constants.jsx: -------------------------------------------------------------------------------- 1 | 2 | module.exports = Object.freeze({ 3 | url: "http://127.0.0.1:8000", 4 | // url: "http://192.168.1.50:8000", 5 | }); 6 | -------------------------------------------------------------------------------- /src/utils/data/userData.js: -------------------------------------------------------------------------------- 1 | const userData = [ 2 | { 3 | id: 1, 4 | user_name: "shah", 5 | firstName: "Mr", 6 | lastName: "Shah", 7 | user_type: "admin", 8 | phoneNumber: "9817730229", 9 | address: "New Delhi", 10 | bloodGroup: "A+", 11 | employeeCode: "9023", 12 | workLocation: "Block D", 13 | department: "Software", 14 | createdBy: null, 15 | updatedBy: null, 16 | userActive: true, 17 | image: "", 18 | signature: "", 19 | createdOn: "2024-03-23T06:42:51.121007Z", 20 | updatedOn: "2024-03-23T06:42:51.120004Z", 21 | }, 22 | { 23 | id: 2, 24 | user_name: "sanjeev", 25 | firstName: "sanjeev", 26 | lastName: "singh", 27 | user_type: "admin", 28 | phoneNumber: "9506009121", 29 | address: "New Delhi", 30 | bloodGroup: "B+", 31 | employeeCode: "1111", 32 | workLocation: "Block D", 33 | department: "Software", 34 | createdBy: null, 35 | updatedBy: null, 36 | userActive: true, 37 | image: "", 38 | signature: "", 39 | createdOn: "2024-03-25T13:13:47.147150Z", 40 | updatedOn: "2024-03-25T13:13:47.146140Z", 41 | }, 42 | ]; 43 | 44 | export default userData; 45 | -------------------------------------------------------------------------------- /src/utils/data/visitorData.js: -------------------------------------------------------------------------------- 1 | const visitorData = [ 2 | { 3 | id: 2, 4 | firstName: "shsssah", 5 | lastName: "Doess", 6 | type: "Guest", 7 | address: "123 Maissn Street, City", 8 | phoneNumber: "123451167890", 9 | image: "https://exampssle.com/image.jpg", 10 | gov_id_type: "Passssport", 11 | gov_id_no: "98 ss 9", 12 | email: "john@examplsse.com", 13 | bloodGroup: "O+", 14 | blacklisted: false, 15 | isPassCreated: false, 16 | signature: "https://exxxample.com/signature.jpg", 17 | createdOn: "2024-03-26T10:38:49.146307Z", 18 | createdBy: 2, 19 | updatedOn: "2024-03-26T10:38:49.146307Z", 20 | updatedBy: null, 21 | }, 22 | { 23 | id: 1, 24 | firstName: "shah", 25 | lastName: "Doe", 26 | type: "Guest", 27 | address: "123 Main Street, City", 28 | phoneNumber: "1234567890", 29 | image: "https://example.com/image.jpg", 30 | gov_id_type: "Passport", 31 | gov_id_no: "98 9", 32 | email: "john@example.com", 33 | bloodGroup: "O+", 34 | blacklisted: false, 35 | isPassCreated: false, 36 | signature: "https://example.com/signature.jpg", 37 | createdOn: "2024-03-26T10:36:46.385733Z", 38 | createdBy: 2, 39 | updatedOn: "2024-03-26T10:36:46.385733Z", 40 | updatedBy: null, 41 | }, 42 | ]; 43 | 44 | export default visitorData; 45 | -------------------------------------------------------------------------------- /src/utils/http/index.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export const post = async (url: string, data: any, headers: any = {}) => { 4 | return await axios.post(url, data, headers); 5 | }; 6 | 7 | export const put = async (url: string, data: any, headers: any = {}) => { 8 | return await axios.put(url, data, headers); 9 | }; 10 | 11 | export const get = async (url: string, headers: any = {}) => { 12 | return await axios.get(url, headers); 13 | }; 14 | 15 | export const deletes = async (url: string, headers: any = {}) => { 16 | return await axios.delete(url, headers); 17 | }; 18 | -------------------------------------------------------------------------------- /src/views/auth/Faq.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Header from "../../components/header"; 3 | 4 | const Faq = () => { 5 | const [expandedQuestionIndex, setExpandedQuestionIndex] = useState(null); // Stores the index of the expanded question 6 | 7 | const toggleQuestion = (index) => { 8 | setExpandedQuestionIndex(expandedQuestionIndex === index ? null : index); 9 | }; 10 | 11 | return ( 12 |
13 |
14 | 15 |
16 | {questions.map((question, index) => ( 17 |
18 | 29 | {expandedQuestionIndex === index && ( 30 |
31 |

{question.answer}

32 |
33 | )} 34 |
35 | ))} 36 |
37 |
38 | ); 39 | }; 40 | 41 | const questions = [ 42 | { 43 | title: "How to Create an Appointment", 44 | answer: "Click on the 'create appointment' button from the sidebar or the dashboard, then fill in the details and click on the create button.", 45 | }, 46 | { 47 | title: "How to Add a new Visitor", 48 | answer: "Click on the 'add new visitor' button, then take or select a picture. If there are multiple pictures, fill in the details and finally submit after signature.", 49 | }, 50 | { 51 | title: "How to Generate a Pass", 52 | answer: "You can generate a pass directly from the visitor table by filling the details, or by choosing from the face recognition table.", 53 | }, 54 | { 55 | title: "How to Add an Employee", 56 | answer: "By clicking on the 'add employee' button from the sidebar and filling in the details in the form.", 57 | }, 58 | { 59 | title: "How Can We See Visitors/Passes/Appointments/Employees", 60 | answer: "We can easily see all details listing from the sidebar.", 61 | }, 62 | ]; 63 | 64 | export default Faq; 65 | 66 | -------------------------------------------------------------------------------- /src/views/auth/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { url } from "../../utils/Constants"; 4 | import { UserContext } from "../../context/UserContext.jsx"; 5 | import Notification from "../../components/notification"; 6 | import modlogo from "../../assets/images/web-logo.png"; 7 | import footerwave from "../../assets/images/footer-wave.png"; 8 | import { Visibility, VisibilityOff } from "@mui/icons-material"; 9 | import Loading from "../../components/loading"; 10 | 11 | const Login = () => { 12 | let navigate = useNavigate(); 13 | 14 | const { setUser } = useContext(UserContext); 15 | const [username, setUsername] = useState(""); 16 | const [password, setPassword] = useState(""); 17 | const [showPassword, setShowPassword] = useState(false); 18 | const [isLoading, setIsLoading] = useState(false); 19 | 20 | const handleTogglePasswordVisibility = () => { 21 | setShowPassword(!showPassword); 22 | }; 23 | 24 | const handleSubmit = async (event) => { 25 | event.preventDefault(); 26 | setIsLoading(true); 27 | try { 28 | const response = await fetch(`${url}/accounts/login-user/`, { 29 | method: "POST", 30 | headers: { 31 | "Content-Type": "application/json", 32 | }, 33 | body: JSON.stringify({ 34 | username: username, 35 | password: password, 36 | }), 37 | }); 38 | const json = await response.json(); 39 | 40 | if (response.ok) { 41 | Notification.showSuccessMessage("Welcome", "Logged in Successfully"); 42 | 43 | localStorage.setItem("user_id", json.id); 44 | localStorage.setItem("user_name", json.username); 45 | localStorage.setItem("user_type", json.user_type); 46 | localStorage.setItem("image", json.image); 47 | localStorage.setItem("token", json.token.access); 48 | localStorage.setItem("refresh_token", json.token.refresh); 49 | localStorage.setItem("userInfo", JSON.stringify(json)); 50 | 51 | setUser(json); 52 | setUsername(""); 53 | setPassword(""); 54 | navigate("/"); 55 | } else { 56 | setIsLoading(false); 57 | Notification.showErrorMessage("Login Failed", json.error || "Invalid credentials"); 58 | } 59 | } catch (err) { 60 | setIsLoading(false); 61 | Notification.showErrorMessage("Error", "Server error!"); 62 | } 63 | }; 64 | 65 | useEffect(() => { 66 | if (localStorage.getItem("token")) { 67 | navigate("/"); 68 | } 69 | }); 70 | 71 | if (isLoading) { 72 | return
; 73 | } 74 | 75 | return ( 76 |
77 | {/*
*/} 78 | 79 |
80 | Ministry of Defence 81 |
82 |
83 | {/* Ministry Of Defence, India */} 84 | Visitor Management System 85 |
86 |
87 | {/* रक्षा मंत्रालय, भारत */} 88 | विज़िटर प्रबंधन प्रणाली 89 |
90 |
91 | 92 | setUsername(e.target.value)} 99 | /> 100 |
101 |
102 | 103 | setPassword(e.target.value)} 110 | /> 111 | 118 |
119 | 120 | {/* 124 |
125 |
126 | Wave 127 |
128 |
129 | ); 130 | }; 131 | 132 | export default Login; 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/views/auth/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, Paper, Menu, MenuItem, IconButton, ListItemIcon, ListItemText } from '@mui/material'; 3 | import PersonIcon from '@mui/icons-material/Person'; 4 | import HomeIcon from '@mui/icons-material/Home'; 5 | import PhoneIcon from '@mui/icons-material/Phone'; 6 | import EmailIcon from '@mui/icons-material/Email'; 7 | import BadgeIcon from '@mui/icons-material/Badge'; 8 | import BloodtypeIcon from '@mui/icons-material/Bloodtype'; 9 | import BlockIcon from '@mui/icons-material/Block'; 10 | import LockResetIcon from '@mui/icons-material/LockReset'; 11 | import VpnKeyIcon from '@mui/icons-material/VpnKey'; 12 | import MoreVertIcon from '@mui/icons-material/MoreVert'; 13 | import ResetPassword from './ResetPassword'; 14 | 15 | const Profile = ({ open, onClose }) => { 16 | const [userData, setUserData] = useState(null); 17 | const [anchorEl, setAnchorEl] = useState(null); 18 | const [resetPasswordModalOpen, setResetPasswordModalOpen] = useState(false); 19 | 20 | const handleClick = (event) => { 21 | setAnchorEl(event.currentTarget); 22 | }; 23 | 24 | const handleClose = () => { 25 | setAnchorEl(null); 26 | }; 27 | 28 | const onActionClick = (action) => { 29 | if (action === "resetPassword") { 30 | setResetPasswordModalOpen(true); 31 | handleClose(); 32 | } 33 | }; 34 | 35 | useEffect(() => { 36 | const userDataJSON = localStorage.getItem('userInfo'); 37 | setUserData(JSON.parse(userDataJSON)); 38 | }, []); 39 | 40 | if (!userData) return
Loading...
; 41 | 42 | return ( 43 | 50 | 51 |
52 |
53 |
54 | 60 | 61 | 62 | 69 | onActionClick("resetPassword")}> 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 |
78 |
79 | {userData.image ? ( 80 | User 81 | ) : ( 82 |
83 | {userData.first_name ? userData.first_name.charAt(0).toUpperCase() : "N"} 84 |
85 | )} 86 |
87 |
88 |
89 | {`${userData.first_name} ${userData.last_name}`} 90 |
91 |
92 | } label="User Type" value={userData.user_type} /> 93 | } label="Address" value={userData.address} /> 94 | } label="Phone" value={userData.phone} /> 95 | } label="Email" value={userData.email} /> 96 | } label="Employee Code" value={userData.employee_code} /> 97 | } label="Blood Group" value={userData.blood_group} /> 98 | } label="Is Active" value={userData.is_active ? "Yes" : "No"} /> 99 | } label="Is Staff" value={userData.is_staff ? "Yes" : "No"} /> 100 |
101 |
102 |
103 | setResetPasswordModalOpen(false)} 106 | /> 107 |
108 |
109 | ); 110 | }; 111 | 112 | const InfoItem = ({ icon, label, value }) => ( 113 |
114 | {icon} 115 | {label} 116 | {value} 117 |
118 | ); 119 | 120 | export default Profile; 121 | 122 | -------------------------------------------------------------------------------- /src/views/auth/ResetPassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { Dialog, DialogTitle } from '@mui/material'; 4 | import Notification from "../../components/notification"; 5 | import { url } from "../../utils/Constants"; 6 | 7 | const ResetPassword = ({ open, onClose }) => { 8 | const navigate = useNavigate(); 9 | 10 | const initialValues = { 11 | newPassword: '', 12 | confirmPassword: '' 13 | }; 14 | 15 | const [passwords, setPasswords] = useState(initialValues); 16 | const [errors, setErrors] = useState({}); 17 | const [progress, setProgress] = useState(0); 18 | 19 | useEffect(() => { 20 | const filledFields = Object.values(passwords).filter(value => value.trim() !== '').length; 21 | setProgress((filledFields / 2) * 100); 22 | }, [passwords]); 23 | 24 | const handleInputChange = (e) => { 25 | const { name, value } = e.target; 26 | setPasswords({ ...passwords, [name]: value }); 27 | setErrors({ ...errors, [name]: null }); 28 | }; 29 | 30 | const validate = () => { 31 | const newErrors = {}; 32 | if (!passwords.newPassword.trim()) { 33 | newErrors.newPassword = 'New password is required'; 34 | } 35 | if (passwords.newPassword !== passwords.confirmPassword) { 36 | newErrors.confirmPassword = 'Passwords must match'; 37 | } 38 | setErrors(newErrors); 39 | return Object.keys(newErrors).length === 0; 40 | }; 41 | 42 | const handleSubmit = async () => { 43 | if (!validate()) return; 44 | 45 | try { 46 | const response = await fetch(`${url}/accounts/reset-password-by-user/`, { 47 | method: 'PATCH', 48 | headers: { 49 | 'Content-Type': 'application/json', 50 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 51 | }, 52 | body: JSON.stringify({ password: passwords.confirmPassword }) 53 | }); 54 | 55 | if (response.ok) { 56 | Notification.showSuccessMessage('Success', 'Password Updated Successfully'); 57 | } else { 58 | const json = await response.json(); 59 | Notification.showErrorMessage('Try Again!', json.error); 60 | } 61 | } catch (error) { 62 | Notification.showErrorMessage('Error', 'Server error!'); 63 | } finally { 64 | onClose(); 65 | } 66 | }; 67 | 68 | return ( 69 | 76 |
77 | 78 | Reset Password 79 | 80 |
81 |
82 |
86 |
87 |
88 |
89 |
90 | 91 | 100 | {errors.newPassword &&
{errors.newPassword}
} 101 | 102 | 103 | 112 | {errors.confirmPassword &&
{errors.confirmPassword}
} 113 |
114 |
115 | 121 |
122 |
123 |
124 |
125 | ); 126 | }; 127 | 128 | export default ResetPassword; 129 | -------------------------------------------------------------------------------- /src/views/configure/adam/Adams.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { IconButton, Menu, MenuItem, ListItemIcon, ListItemText, CircularProgress } from "@mui/material"; 4 | import MoreVertIcon from "@mui/icons-material/MoreVert"; 5 | import EditIcon from "@mui/icons-material/Edit"; 6 | import DeleteIcon from "@mui/icons-material/Delete"; 7 | import AddIcon from "@mui/icons-material/Add"; 8 | import { url } from "../../../utils/Constants.jsx"; 9 | import Notification from "../../../components/notification/index.jsx"; 10 | import AddNewAdam from "./AddNewAdam"; 11 | import UpdateAdam from "./UpdateAdam"; 12 | import Pagination from "../../../components/pagination/index.jsx"; 13 | 14 | const Adams = () => { 15 | const [adamsData, setAdamsData] = useState([]); 16 | const [filteredData, setFilteredData] = useState([]); 17 | const [currentPage, setCurrentPage] = useState(1); 18 | const [itemsPerPage, setItemsPerPage] = useState(10); 19 | const [searchTerm, setSearchTerm] = useState(""); 20 | const [isLoading, setIsLoading] = useState(false); 21 | const [anchorEl, setAnchorEl] = useState(null); 22 | const [currentSelectedAdam, setCurrentSelectedAdam] = useState(null); 23 | const [showAddNewAdam, setShowAddNewAdam] = useState(false); 24 | const [showUpdateAdam, setShowUpdateAdam] = useState(false); 25 | 26 | let navigate = useNavigate(); 27 | 28 | useEffect(() => { 29 | if (!localStorage.getItem("token")) { 30 | navigate("/login"); 31 | } 32 | fetchData(); 33 | }, []); 34 | 35 | const fetchData = async () => { 36 | setIsLoading(true); 37 | try { 38 | const response = await fetch(`${url}/gadgets/get-adam/`, { 39 | method: "GET", 40 | headers: { 41 | "Content-Type": "application/json", 42 | Authorization: `Bearer ${localStorage.getItem("token")}`, 43 | }, 44 | }); 45 | const data = await response.json(); 46 | if (response.ok) { 47 | setAdamsData(data); 48 | setFilteredData(data); 49 | } else { 50 | Notification.showErrorMessage("Try Again!", data.error); 51 | } 52 | } catch (err) { 53 | Notification.showErrorMessage("Error", "Server error!"); 54 | } 55 | setIsLoading(false); 56 | }; 57 | 58 | const handleSearch = (event) => { 59 | const value = event.target.value; 60 | setSearchTerm(value); 61 | const filtered = adamsData.filter(adam => 62 | adam.name.toLowerCase().includes(value.toLowerCase()) || 63 | adam.ip.toLowerCase().includes(value.toLowerCase()) 64 | ); 65 | setFilteredData(filtered); 66 | setCurrentPage(1); 67 | }; 68 | 69 | const handlePageSizeChange = (event) => { 70 | setItemsPerPage(parseInt(event.target.value, 10)); 71 | setCurrentPage(1); 72 | }; 73 | 74 | const handleClick = (event, adam) => { 75 | setAnchorEl(event.currentTarget); 76 | setCurrentSelectedAdam(adam); 77 | }; 78 | 79 | const handleClose = () => { 80 | setAnchorEl(null); 81 | }; 82 | 83 | const onActionClick = (action, adam) => { 84 | setCurrentSelectedAdam(adam); 85 | if (action === 'addNewAdam') { 86 | setShowAddNewAdam(true); 87 | } else if (action === 'update') { 88 | setShowUpdateAdam(true); 89 | } else if (action === 'delete') { 90 | // console.log('Delete:', adam); 91 | deleteAdam(adam); 92 | } 93 | }; 94 | 95 | const deleteAdam = async (adam) => { 96 | try { 97 | const response = await fetch(`${url}/gadgets/update-adam/${adam.id}`, { 98 | method: "DELETE", 99 | headers: { 100 | "Content-Type": "application/json", 101 | Authorization: `Bearer ${localStorage.getItem("token")}`, 102 | }, 103 | // body: JSON.stringify("formData"), 104 | }); 105 | if (response.ok) { 106 | Notification.showSuccessMessage("Success", "Adam Deleted"); 107 | fetchData(); 108 | // onClose(); 109 | } else { 110 | const json = await response.json(); 111 | Notification.showErrorMessage("Error", json.error); 112 | } 113 | } catch (error) { 114 | Notification.showErrorMessage("Error", "Server error"); 115 | } 116 | }; 117 | const indexOfLastItem = currentPage * itemsPerPage; 118 | const indexOfFirstItem = indexOfLastItem - itemsPerPage; 119 | const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); 120 | 121 | const totalPages = Math.ceil(filteredData.length / itemsPerPage); 122 | 123 | return ( 124 |
125 |
126 |
127 | 134 | 143 |
144 | 148 |
149 | {isLoading ? ( 150 |
151 | 152 |
153 | ) : currentItems.length > 0 ? ( 154 |
155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | {currentItems.map((adam, index) => ( 168 | 169 | 170 | 171 | 172 | 173 | 174 | 189 | 190 | ))} 191 | 192 |
IDNameIPPortAddressAction
{adam.id}{adam.name}{adam.ip}{adam.port}{adam.address} 175 | handleClick(event, adam)}> 176 | 177 | 178 | 179 | { onActionClick('update', currentSelectedAdam); handleClose(); }}> 180 | 181 | 182 | 183 | { onActionClick('delete', currentSelectedAdam); handleClose(); }}> 184 | 185 | 186 | 187 | 188 |
193 | 194 | 195 |
196 | ) : ( 197 |
198 |

No data found.

199 |
200 | )} 201 | {showAddNewAdam && setShowAddNewAdam(false)} fetchData={fetchData} />} 202 | {showUpdateAdam && setShowUpdateAdam(false)} fetchData={fetchData} />} 203 |
204 | ); 205 | }; 206 | 207 | export default Adams; 208 | 209 | -------------------------------------------------------------------------------- /src/views/configure/adam/AddNewAdam.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import { url } from "../../../utils/Constants.jsx"; 4 | import Notification from "../../../components/notification"; 5 | 6 | const AddNewAdam = ({ open, onClose, fetchData }) => { 7 | const initialValues = { 8 | ip: '', 9 | port: '', 10 | address: '', 11 | name: '' 12 | }; 13 | 14 | const [formData, setFormData] = useState(initialValues); 15 | const [errors, setErrors] = useState({}); 16 | 17 | const [progress, setProgress] = useState(0); 18 | 19 | useEffect(() => { 20 | const filledFields = Object.values(formData).filter(value => value.trim() !== '').length; 21 | setProgress((filledFields / 4) * 100); 22 | }, [formData]); 23 | 24 | const validate = () => { 25 | const newErrors = {}; 26 | 27 | if (!formData.ip.trim()) { newErrors.ip = 'IP address is required'; } 28 | else if (!/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(formData.ip.trim())) { newErrors.ip = 'Invalid IP address'; } 29 | 30 | if (!formData.port.trim()) { newErrors.port = 'Port number is required'; } 31 | else if (!/^\d{1,}$/.test(formData.port.trim())) { newErrors.port = 'Port must be exactly three digits'; } 32 | 33 | if (!formData.address.trim()) { newErrors.address = 'Address is required'; } 34 | if (!formData.name.trim()) { newErrors.name = 'Name is required'; } 35 | 36 | setErrors(newErrors); 37 | return Object.keys(newErrors).length === 0; 38 | }; 39 | 40 | 41 | const handleInputChange = (e) => { 42 | const { name, value } = e.target; 43 | setFormData({ ...formData, [name]: value }); 44 | setErrors({ ...errors, [name]: null }); 45 | }; 46 | 47 | const handleSubmit = async () => { 48 | if (!validate()) return; 49 | try { 50 | const response = await fetch(`${url}/gadgets/register-adam/`, { 51 | method: "POST", 52 | headers: { 53 | "Content-Type": "application/json", 54 | Authorization: `Bearer ${localStorage.getItem("token")}`, 55 | }, 56 | body: JSON.stringify(formData), 57 | }); 58 | if (response.ok) { 59 | Notification.showSuccessMessage("Success", "Device added successfully"); 60 | setFormData(initialValues); 61 | fetchData(); 62 | onClose(); 63 | } else { 64 | const json = await response.json(); 65 | Notification.showErrorMessage("Error", json.error); 66 | } 67 | } catch (error) { 68 | Notification.showErrorMessage("Error", "Server error"); 69 | } 70 | }; 71 | 72 | return ( 73 | 74 |
75 | 76 | Add New Adam 77 | 78 |
79 |
80 |
84 |
85 |
86 |
87 |
88 | 89 | 98 | {errors.ip &&
{errors.ip}
} 99 | 100 | 101 | 110 | {errors.port &&
{errors.port}
} 111 | 112 | 113 | 125 | {errors.address &&
{errors.address}
} 126 | 127 | 128 | 137 | {errors.name &&
{errors.name}
} 138 |
139 |
140 | 146 |
147 |
148 |
149 |
150 | ); 151 | }; 152 | 153 | export default AddNewAdam; 154 | -------------------------------------------------------------------------------- /src/views/configure/adam/UpdateAdam.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import { url } from "../../../utils/Constants.jsx"; 4 | import Notification from "../../../components/notification"; 5 | 6 | const UpdateAdam = ({ open, onClose, fetchData, adamData }) => { 7 | const [formData, setFormData] = useState({}); 8 | const [errors, setErrors] = useState({}); 9 | const [progress, setProgress] = useState(0); 10 | 11 | useEffect(() => { 12 | setFormData(adamData); 13 | }, [adamData]); 14 | 15 | useEffect(() => { 16 | const filledFields = Object.values(formData).filter(value => String(value).trim() !== '').length; 17 | setProgress((filledFields / 5) * 100); 18 | }, [formData]); 19 | 20 | const validate = () => { 21 | const newErrors = {}; 22 | 23 | if (!String(formData.ip).trim()) { newErrors.ip = 'IP address is required'; } 24 | else if (!/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(formData.ip.trim())) { newErrors.ip = 'Invalid IP address'; } 25 | 26 | if (!String(formData.port).trim()) { newErrors.port = 'Port number is required'; } 27 | else if (!/^\d{3}$/.test(String(formData.port).trim())) { newErrors.port = 'Port must be exactly three digits'; } 28 | 29 | if (!String(formData.address).trim()) { newErrors.address = 'Address is required'; } 30 | if (!String(formData.name).trim()) { newErrors.name = 'Name is required'; } 31 | 32 | setErrors(newErrors); 33 | return Object.keys(newErrors).length === 0; 34 | }; 35 | 36 | const handleInputChange = (e) => { 37 | const { name, value } = e.target; 38 | setFormData({ ...formData, [name]: value }); 39 | setErrors({ ...errors, [name]: null }); 40 | }; 41 | 42 | const handleSubmit = async () => { 43 | if (!validate()) return; 44 | try { 45 | const response = await fetch(`${url}/gadgets/update-adam/${adamData.id}`, { 46 | method: "PUT", 47 | headers: { 48 | "Content-Type": "application/json", 49 | Authorization: `Bearer ${localStorage.getItem("token")}`, 50 | }, 51 | body: JSON.stringify(formData), 52 | }); 53 | if (response.ok) { 54 | Notification.showSuccessMessage("Success", "Adam updated successfully"); 55 | fetchData(); 56 | onClose(); 57 | } else { 58 | const json = await response.json(); 59 | Notification.showErrorMessage("Error", json.error); 60 | } 61 | } catch (error) { 62 | Notification.showErrorMessage("Error", "Server error"); 63 | } 64 | }; 65 | 66 | 67 | const handleClose = () => { 68 | onClose(); 69 | setErrors({}); 70 | setFormData({}); 71 | }; 72 | 73 | return ( 74 | 75 |
76 | 77 | Update Adam Detail 78 | 79 |
80 |
81 |
85 |
86 |
87 |
88 |
89 | 90 | 99 | {errors.ip &&
{errors.ip}
} 100 | 101 | 102 | 111 | {errors.port &&
{errors.port}
} 112 | 113 | 114 | 126 | {errors.address &&
{errors.address}
} 127 | 128 | 129 | 138 | {errors.name &&
{errors.name}
} 139 |
140 |
141 | 147 |
148 |
149 |
150 |
151 | ); 152 | }; 153 | 154 | export default UpdateAdam; 155 | -------------------------------------------------------------------------------- /src/views/configure/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Adams from './adam/Adams'; 3 | import Zones from './zone/Zones'; 4 | import Keys from './key/Keys'; 5 | import Readers from './reader/Readers'; 6 | import GuardReaderMappings from './map-guard/GuardReaderMappings'; 7 | 8 | const Configure = () => { 9 | const [activeTab, setActiveTab] = useState('adam'); 10 | 11 | const handleTabChange = (tab) => { 12 | setActiveTab(tab); 13 | }; 14 | 15 | return ( 16 |
17 |
18 |
    19 |
  • 20 | 27 |
  • 28 |
  • 29 | 36 |
  • 37 |
  • 38 | 45 |
  • 46 |
  • 47 | 54 |
  • 55 | {/*
  • 56 | 63 |
  • */} 64 |
65 |
66 |
67 | {activeTab === 'adam' && } 68 | {activeTab === 'zone' && } 69 | {activeTab === 'key' && } 70 | {activeTab === 'reader' && } 71 | {activeTab === 'guardreadermaps' && } 72 |
73 |
74 | ); 75 | }; 76 | 77 | export default Configure; 78 | -------------------------------------------------------------------------------- /src/views/configure/key/AddNewKey.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import { url } from "../../../utils/Constants.jsx"; 4 | import Notification from "../../../components/notification"; 5 | 6 | const AddNewKey = ({ open, onClose, fetchData }) => { 7 | const initialValues = { 8 | RFID_key: '' 9 | }; 10 | 11 | const [formData, setFormData] = useState(initialValues); 12 | const [errors, setErrors] = useState({}); 13 | 14 | const [progress, setProgress] = useState(0); 15 | 16 | useEffect(() => { 17 | const filledFields = Object.values(formData).filter(value => value.trim() !== '').length; 18 | setProgress((filledFields / 1) * 100); 19 | }, [formData]); 20 | 21 | const validate = () => { 22 | const newErrors = {}; 23 | if (!formData.RFID_key.trim()) { newErrors.RFID_key = 'RFID key is required'; } 24 | setErrors(newErrors); 25 | return Object.keys(newErrors).length === 0; 26 | }; 27 | 28 | const handleInputChange = (e) => { 29 | const { name, value } = e.target; 30 | setFormData({ ...formData, [name]: value }); 31 | setErrors({ ...errors, [name]: null }); 32 | }; 33 | 34 | const handleSubmit = async () => { 35 | if (!validate()) return; 36 | try { 37 | const response = await fetch(`${url}/key/key-info`, { 38 | method: "POST", 39 | headers: { 40 | "Content-Type": "application/json", 41 | Authorization: `Bearer ${localStorage.getItem("token")}`, 42 | }, 43 | body: JSON.stringify(formData), 44 | }); 45 | if (response.ok) { 46 | Notification.showSuccessMessage("Success", "Key added successfully"); 47 | setFormData(initialValues); 48 | fetchData(); 49 | onClose(); 50 | } else { 51 | const json = await response.json(); 52 | Notification.showErrorMessage("Error", json.error); 53 | } 54 | } catch (error) { 55 | Notification.showErrorMessage("Error", "Server error"); 56 | } 57 | }; 58 | 59 | return ( 60 | 61 |
62 | 63 | Add New Key 64 | 65 |
66 |
67 |
71 |
72 |
73 |
74 |
75 | 76 | 85 | {errors.RFID_key &&
{errors.RFID_key}
} 86 |
87 |
88 | 94 |
95 |
96 |
97 |
98 | ); 99 | }; 100 | 101 | export default AddNewKey; 102 | -------------------------------------------------------------------------------- /src/views/configure/key/UpdateKey.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import { url } from "../../../utils/Constants.jsx"; 4 | import Notification from "../../../components/notification"; 5 | 6 | const UpdateKey = ({ open, onClose, fetchData, keyData }) => { 7 | const [formData, setFormData] = useState({}); 8 | const [errors, setErrors] = useState({}); 9 | const [progress, setProgress] = useState(0); 10 | 11 | useEffect(() => { 12 | setFormData(keyData); 13 | }, [keyData]); 14 | 15 | useEffect(() => { 16 | const filledFields = Object.values(formData).filter(value => value !== null && value.toString().trim() !== '').length; 17 | setProgress((filledFields / 1) * 100); 18 | }, [formData]); 19 | 20 | const validate = () => { 21 | const newErrors = {}; 22 | if (!formData.RFID_key || !formData.RFID_key.trim()) { 23 | newErrors.RFID_key = 'RFID key is required'; 24 | } 25 | if (!formData.blacklisted.trim()) { newErrors.blacklisted = 'Blacklisted status is required'; } 26 | setErrors(newErrors); 27 | return Object.keys(newErrors).length === 0; 28 | }; 29 | 30 | const handleInputChange = (e) => { 31 | const { name, value } = e.target; 32 | setFormData({ ...formData, [name]: value }); 33 | setErrors({ ...errors, [name]: null }); 34 | }; 35 | 36 | const handleSubmit = async () => { 37 | if (!validate()) return; 38 | try { 39 | const response = await fetch(`${url}/key/key-info/${keyData.id}`, { 40 | method: "PUT", 41 | headers: { 42 | "Content-Type": "application/json", 43 | Authorization: `Bearer ${localStorage.getItem("token")}`, 44 | }, 45 | body: JSON.stringify(formData), 46 | }); 47 | if (response.ok) { 48 | Notification.showSuccessMessage("Success", "Key updated successfully"); 49 | fetchData(); 50 | onClose(); 51 | } else { 52 | const json = await response.json(); 53 | Notification.showErrorMessage("Error", json.error); 54 | } 55 | } catch (error) { 56 | Notification.showErrorMessage("Error", "Server error"); 57 | } 58 | }; 59 | 60 | const handleClose = () => { 61 | onClose(); 62 | setErrors({}); 63 | setFormData({}); 64 | }; 65 | 66 | return ( 67 | 68 |
69 | 70 | Update Key Details 71 | 72 |
73 |
74 |
78 |
79 |
80 |
81 |
82 | 83 | 92 | {errors.RFID_key &&
{errors.RFID_key}
} 93 | 94 | 95 | 105 | {errors.blacklisted &&
{errors.blacklisted}
} 106 |
107 |
108 | 114 |
115 |
116 |
117 |
118 | ); 119 | }; 120 | 121 | export default UpdateKey; 122 | -------------------------------------------------------------------------------- /src/views/configure/map-guard/AddNewGuardReaderMapping.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import { url } from "../../../utils/Constants.jsx"; 4 | import Notification from "../../../components/notification"; 5 | 6 | const AddNewGuardReaderMapping = ({ open, onClose, fetchData }) => { 7 | const initialValues = { 8 | user_id: '', 9 | reader_id: '' 10 | }; 11 | 12 | const [formData, setFormData] = useState(initialValues); 13 | const [errors, setErrors] = useState({}); 14 | const [progress, setProgress] = useState(0); 15 | const [userList, setUserList] = useState([]); 16 | const [readerList, setReaderList] = useState([]); 17 | 18 | const getUserList = async () => { 19 | try { 20 | const response = await fetch(`${url}/accounts/get-all-user/`, { 21 | method: "GET", 22 | headers: { 23 | "Content-Type": "application/json", 24 | Authorization: `Bearer ${localStorage.getItem("token")}`, 25 | }, 26 | }); 27 | const json = await response.json(); 28 | if (response.ok) { 29 | setUserList(json); 30 | } else { 31 | Notification.showErrorMessage("Try Again!", json.error); 32 | } 33 | } catch (err) { 34 | Notification.showErrorMessage("Error", "Server error!"); 35 | } 36 | }; 37 | 38 | const getReaderList = async () => { 39 | try { 40 | const response = await fetch(`${url}/gadgets/register-reader/`, { 41 | method: "GET", 42 | headers: { 43 | "Content-Type": "application/json", 44 | Authorization: `Bearer ${localStorage.getItem("token")}`, 45 | }, 46 | }); 47 | const json = await response.json(); 48 | if (response.ok) { 49 | setReaderList(json); 50 | } else { 51 | Notification.showErrorMessage("Try Again!", json.error); 52 | } 53 | } catch (err) { 54 | Notification.showErrorMessage("Error", "Server error!"); 55 | } 56 | }; 57 | 58 | useEffect(() => { 59 | getUserList(); 60 | getReaderList(); 61 | }, []); 62 | 63 | useEffect(() => { 64 | const filledFields = Object.values(formData).filter(value => value.trim() !== '').length; 65 | setProgress((filledFields / 2) * 100); 66 | }, [formData]); 67 | 68 | const validate = () => { 69 | const newErrors = {}; 70 | if (!formData.user_id) { 71 | newErrors.user_id = 'User is required'; 72 | } 73 | if (!formData.reader_id) { 74 | newErrors.reader_id = 'Reader is required'; 75 | } 76 | setErrors(newErrors); 77 | return Object.keys(newErrors).length === 0; 78 | }; 79 | 80 | const handleInputChange = (e) => { 81 | const { name, value } = e.target; 82 | setFormData({ ...formData, [name]: value }); 83 | setErrors({ ...errors, [name]: null }); 84 | }; 85 | 86 | const handleSubmit = async () => { 87 | if (!validate()) return; 88 | try { 89 | const response = await fetch(`${url}/guard-reader-mappings/`, { 90 | method: "POST", 91 | headers: { 92 | "Content-Type": "application/json", 93 | Authorization: `Bearer ${localStorage.getItem("token")}`, 94 | }, 95 | body: JSON.stringify(formData), 96 | }); 97 | if (response.ok) { 98 | Notification.showSuccessMessage("Success", "Mapping added successfully"); 99 | setFormData(initialValues); 100 | fetchData(); 101 | onClose(); 102 | } else { 103 | const json = await response.json(); 104 | Notification.showErrorMessage("Error", json.error); 105 | } 106 | } catch (error) { 107 | Notification.showErrorMessage("Error", "Server error"); 108 | } 109 | }; 110 | 111 | return ( 112 | 113 |
114 | 115 | Add New Guard Reader Mapping 116 | 117 |
118 |
119 |
123 |
124 |
125 |
126 |
127 | 128 | 140 | {errors.user_id &&
{errors.user_id}
} 141 | 142 | 143 | 155 | {errors.reader_id &&
{errors.reader_id}
} 156 |
157 |
158 | 164 |
165 |
166 |
167 |
168 | ); 169 | }; 170 | 171 | export default AddNewGuardReaderMapping; 172 | -------------------------------------------------------------------------------- /src/views/configure/map-guard/GuardReaderMappings.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { CircularProgress } from "@mui/material"; 4 | import { url } from "../../../utils/Constants.jsx"; 5 | import AddIcon from "@mui/icons-material/Add"; 6 | import Notification from "../../../components/notification/index.jsx"; 7 | import Pagination from "../../../components/pagination/index.jsx"; 8 | import AddNewGuardReaderMapping from "./AddNewGuardReaderMapping"; 9 | 10 | const GuardReaderMappings = () => { 11 | const [mappingsData, setMappingsData] = useState([]); 12 | const [filteredData, setFilteredData] = useState([]); 13 | const [currentPage, setCurrentPage] = useState(1); 14 | const [itemsPerPage, setItemsPerPage] = useState(10); 15 | const [searchTerm, setSearchTerm] = useState(""); 16 | const [isLoading, setIsLoading] = useState(false); 17 | const [showAddNewMapping, setShowAddNewMapping] = useState(false); 18 | 19 | let navigate = useNavigate(); 20 | 21 | useEffect(() => { 22 | if (!localStorage.getItem("token")) { 23 | navigate("/login"); 24 | } 25 | fetchData(); 26 | }, []); 27 | 28 | const toggleAddNewMappingForm = () => { 29 | setShowAddNewMapping(!showAddNewMapping); 30 | }; 31 | 32 | const fetchData = async () => { 33 | setIsLoading(true); 34 | try { 35 | const response = await fetch(`${url}/guard-reader-mappings/`, { 36 | method: "GET", 37 | headers: { 38 | "Content-Type": "application/json", 39 | Authorization: `Bearer ${localStorage.getItem("token")}`, 40 | }, 41 | }); 42 | const data = await response.json(); 43 | if (response.ok) { 44 | setMappingsData(data); 45 | setFilteredData(data); 46 | } else { 47 | Notification.showErrorMessage("Try Again!", data.error); 48 | } 49 | } catch (err) { 50 | Notification.showErrorMessage("Error", "Server error!"); 51 | } 52 | setIsLoading(false); 53 | }; 54 | 55 | const handleSearch = (event) => { 56 | const value = event.target.value; 57 | setSearchTerm(value); 58 | const filtered = mappingsData.filter(mapping => 59 | mapping.name.toLowerCase().includes(value.toLowerCase()) 60 | ); 61 | setFilteredData(filtered); 62 | setCurrentPage(1); 63 | }; 64 | 65 | const handlePageSizeChange = (event) => { 66 | setItemsPerPage(parseInt(event.target.value, 10)); 67 | setCurrentPage(1); 68 | }; 69 | 70 | const indexOfLastItem = currentPage * itemsPerPage; 71 | const indexOfFirstItem = indexOfLastItem - itemsPerPage; 72 | const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); 73 | 74 | const totalPages = Math.ceil(filteredData.length / itemsPerPage); 75 | 76 | return ( 77 |
78 |
79 |
80 | 87 | 96 |
97 | 101 |
102 | {isLoading ? ( 103 |
104 | 105 |
106 | ) : currentItems.length > 0 ? ( 107 |
108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | {currentItems.map((mapping, index) => ( 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ))} 134 | 135 |
IDNameAdam NameZone NameAssociated UsersMoxa IPReader TypeCOM Port
{mapping.id}{mapping.name}{mapping.adam_name}{mapping.zone_name}{mapping.associated_users.map(user => user.username).join(', ')}{mapping.moxa_ip}{mapping.reader_type}{mapping.com_port}
136 | 137 | 138 | 139 |
140 | ) : ( 141 |
142 |

No data found.

143 |
144 | )} 145 | {showAddNewMapping && setShowAddNewMapping(false)} fetchData={fetchData} />} 146 |
147 | ); 148 | }; 149 | 150 | export default GuardReaderMappings; 151 | -------------------------------------------------------------------------------- /src/views/configure/map-guard/UpdateGuardReaderMapping.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const UpdateGuardReaderMapping = () => { 4 | 5 | return ( 6 |
7 | UpdateGuardReaderMapping 8 |
9 | ); 10 | }; 11 | 12 | export default UpdateGuardReaderMapping; -------------------------------------------------------------------------------- /src/views/configure/reader/Readers.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { IconButton, Menu, MenuItem, ListItemIcon, ListItemText, CircularProgress } from "@mui/material"; 4 | import MoreVertIcon from "@mui/icons-material/MoreVert"; 5 | import EditIcon from "@mui/icons-material/Edit"; 6 | import DeleteIcon from "@mui/icons-material/Delete"; 7 | import AddIcon from "@mui/icons-material/Add"; 8 | import { url } from "../../../utils/Constants.jsx"; 9 | import Notification from "../../../components/notification/index.jsx"; 10 | import AddNewReader from "./AddNewReader"; 11 | import UpdateReader from "./UpdateReader"; 12 | import Pagination from "../../../components/pagination/index.jsx"; 13 | 14 | const Readers = () => { 15 | const [readersData, setReadersData] = useState([]); 16 | const [filteredData, setFilteredData] = useState([]); 17 | const [currentPage, setCurrentPage] = useState(1); 18 | const [itemsPerPage, setItemsPerPage] = useState(10); 19 | const [searchTerm, setSearchTerm] = useState(""); 20 | const [isLoading, setIsLoading] = useState(false); 21 | const [anchorEl, setAnchorEl] = useState(null); 22 | const [currentSelectedReader, setCurrentSelectedReader] = useState(null); 23 | const [showAddNewReader, setShowAddNewReader] = useState(false); 24 | const [showUpdateReader, setShowUpdateReader] = useState(false); 25 | 26 | let navigate = useNavigate(); 27 | 28 | useEffect(() => { 29 | if (!localStorage.getItem("token")) { 30 | navigate("/login"); 31 | } 32 | fetchData(); 33 | }, []); 34 | 35 | const fetchData = async () => { 36 | setIsLoading(true); 37 | try { 38 | const response = await fetch(`${url}/gadgets/register-reader/`, { 39 | method: "GET", 40 | headers: { 41 | "Content-Type": "application/json", 42 | Authorization: `Bearer ${localStorage.getItem("token")}`, 43 | }, 44 | }); 45 | const data = await response.json(); 46 | if (response.ok) { 47 | setReadersData(data); 48 | setFilteredData(data); 49 | } else { 50 | Notification.showErrorMessage("Try Again!", data.error); 51 | } 52 | } catch (err) { 53 | Notification.showErrorMessage("Error", "Server error!"); 54 | } 55 | setIsLoading(false); 56 | }; 57 | 58 | const handleSearch = (event) => { 59 | const value = event.target.value; 60 | setSearchTerm(value); 61 | const filtered = readersData.filter(reader => 62 | reader.name.toLowerCase().includes(value.toLowerCase()) 63 | ); 64 | setFilteredData(filtered); 65 | setCurrentPage(1); 66 | }; 67 | 68 | const handlePageSizeChange = (event) => { 69 | setItemsPerPage(parseInt(event.target.value, 10)); 70 | setCurrentPage(1); 71 | }; 72 | 73 | const handleClick = (event, reader) => { 74 | setAnchorEl(event.currentTarget); 75 | setCurrentSelectedReader(reader); 76 | }; 77 | 78 | const handleClose = () => { 79 | setAnchorEl(null); 80 | }; 81 | 82 | const onActionClick = (action, reader) => { 83 | setCurrentSelectedReader(reader); 84 | if (action === 'addNewReader') { 85 | setShowAddNewReader(true); 86 | } else if (action === 'update') { 87 | setShowUpdateReader(true); 88 | } else if (action === 'delete') { 89 | console.log('Delete:', reader); 90 | } 91 | }; 92 | 93 | const indexOfLastItem = currentPage * itemsPerPage; 94 | const indexOfFirstItem = indexOfLastItem - itemsPerPage; 95 | const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); 96 | 97 | const totalPages = Math.ceil(filteredData.length / itemsPerPage); 98 | 99 | return ( 100 |
101 |
102 |
103 | 110 | 119 |
120 | 124 |
125 | {isLoading ? ( 126 |
127 | 128 |
129 | ) : currentItems.length > 0 ? ( 130 |
131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | {currentItems.map((reader, index) => ( 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 169 | 170 | ))} 171 | 172 |
IDNameMoxa IPCOM PortReader TypeADAM ModuleZoneAction
{reader.id}{reader.name}{reader.moxa_ip}{reader.com_port}{reader.reader_type}{reader.adam_name}{reader.zone_name} 155 | handleClick(event, reader)}> 156 | 157 | 158 | 159 | { onActionClick('update', currentSelectedReader); handleClose(); }}> 160 | 161 | 162 | 163 | { onActionClick('delete', currentSelectedReader); handleClose(); }}> 164 | 165 | 166 | 167 | 168 |
173 | 174 | 175 | 176 |
177 | ) : ( 178 |
179 |

No data found.

180 |
181 | )} 182 | {showAddNewReader && setShowAddNewReader(false)} fetchData={fetchData} />} 183 | {showUpdateReader && setShowUpdateReader(false)} fetchData={fetchData} />} 184 |
185 | ); 186 | }; 187 | 188 | export default Readers; 189 | 190 | -------------------------------------------------------------------------------- /src/views/configure/zone/AddNewZone.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import { url } from "../../../utils/Constants.jsx"; 4 | import Notification from "../../../components/notification"; 5 | 6 | const AddNewZone = ({ open, onClose, fetchData }) => { 7 | const initialValues = { 8 | zone_name: '', 9 | allow_re_entry: 'true' 10 | }; 11 | 12 | const [formData, setFormData] = useState(initialValues); 13 | const [errors, setErrors] = useState({}); 14 | 15 | const [progress, setProgress] = useState(0); 16 | 17 | useEffect(() => { 18 | const filledFields = Object.values(formData).filter(value => value.trim() !== '').length; 19 | setProgress((filledFields / 2) * 100); 20 | }, [formData]); 21 | 22 | const validate = () => { 23 | const newErrors = {}; 24 | 25 | if (!formData.zone_name.trim()) { newErrors.zone_name = 'Zone name is required'; } 26 | if (!formData.allow_re_entry.trim()) { newErrors.allow_re_entry = 'Allow re-entry status is required'; } 27 | 28 | setErrors(newErrors); 29 | return Object.keys(newErrors).length === 0; 30 | }; 31 | 32 | 33 | const handleInputChange = (e) => { 34 | const { name, value } = e.target; 35 | setFormData({ ...formData, [name]: value }); 36 | setErrors({ ...errors, [name]: null }); 37 | }; 38 | 39 | const handleSubmit = async () => { 40 | if (!validate()) return; 41 | try { 42 | const response = await fetch(`${url}/zone/zone-info`, { 43 | method: "POST", 44 | headers: { 45 | "Content-Type": "application/json", 46 | Authorization: `Bearer ${localStorage.getItem("token")}`, 47 | }, 48 | body: JSON.stringify(formData), 49 | }); 50 | if (response.ok) { 51 | Notification.showSuccessMessage("Success", "Zone added successfully"); 52 | setFormData(initialValues); 53 | fetchData(); 54 | onClose(); 55 | } else { 56 | const json = await response.json(); 57 | Notification.showErrorMessage("Error", json.error); 58 | } 59 | } catch (error) { 60 | Notification.showErrorMessage("Error", "Server error"); 61 | } 62 | }; 63 | 64 | return ( 65 | 66 |
67 | 68 | Add New Zone 69 | 70 |
71 |
72 |
76 |
77 |
78 |
79 |
80 | 81 | 90 | {errors.zone_name &&
{errors.zone_name}
} 91 | 92 | 93 | 103 | {errors.allow_re_entry &&
{errors.allow_re_entry}
} 104 |
105 |
106 | 112 |
113 |
114 |
115 |
116 | ); 117 | }; 118 | 119 | export default AddNewZone; 120 | 121 | -------------------------------------------------------------------------------- /src/views/configure/zone/UpdateZone.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Dialog, DialogTitle } from '@mui/material'; 3 | import { url } from "../../../utils/Constants.jsx"; 4 | import Notification from "../../../components/notification"; 5 | 6 | const UpdateZone = ({ open, onClose, fetchData, zoneData }) => { 7 | const [formData, setFormData] = useState({}); 8 | const [errors, setErrors] = useState({}); 9 | const [progress, setProgress] = useState(0); 10 | 11 | useEffect(() => { 12 | setFormData(zoneData); 13 | }, [zoneData]); 14 | 15 | useEffect(() => { 16 | const filledFields = Object.values(formData).filter(value => value !== null && value.toString().trim() !== '').length; 17 | setProgress((filledFields / 2) * 100); 18 | }, [formData]); 19 | 20 | const validate = () => { 21 | const newErrors = {}; 22 | if (!formData.zone_name.trim()) { 23 | newErrors.zone_name = 'Zone name is required'; 24 | } 25 | setErrors(newErrors); 26 | return Object.keys(newErrors).length === 0; 27 | }; 28 | 29 | const handleInputChange = (e) => { 30 | const { name, value } = e.target; 31 | setFormData({ ...formData, [name]: value }); 32 | setErrors({ ...errors, [name]: null }); 33 | }; 34 | 35 | const handleSubmit = async () => { 36 | if (!validate()) return; 37 | try { 38 | const response = await fetch(`${url}/zone/zone-info/${zoneData.id}`, { 39 | method: "PUT", 40 | headers: { 41 | "Content-Type": "application/json", 42 | Authorization: `Bearer ${localStorage.getItem("token")}`, 43 | }, 44 | body: JSON.stringify(formData), 45 | }); 46 | if (response.ok) { 47 | Notification.showSuccessMessage("Success", "Zone updated successfully"); 48 | fetchData(); 49 | onClose(); 50 | } else { 51 | const json = await response.json(); 52 | Notification.showErrorMessage("Error", json.error); 53 | } 54 | } catch (error) { 55 | Notification.showErrorMessage("Error", "Server error"); 56 | } 57 | }; 58 | 59 | const handleClose = () => { 60 | onClose(); 61 | setErrors({}); 62 | setFormData({}); 63 | }; 64 | 65 | return ( 66 | 67 |
68 | 69 | Update Zone Details 70 | 71 |
72 |
73 |
77 |
78 |
79 |
80 |
81 | 82 | 91 | {errors.zone_name &&
{errors.zone_name}
} 92 | 93 | 94 | 104 | 105 |
106 |
107 | 113 |
114 |
115 |
116 |
117 | ); 118 | }; 119 | 120 | export default UpdateZone 121 | -------------------------------------------------------------------------------- /src/views/configure/zone/Zones.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { IconButton, Menu, MenuItem, ListItemIcon, ListItemText, CircularProgress } from "@mui/material"; 4 | import MoreVertIcon from "@mui/icons-material/MoreVert"; 5 | import EditIcon from "@mui/icons-material/Edit"; 6 | import DeleteIcon from "@mui/icons-material/Delete"; 7 | import AddIcon from "@mui/icons-material/Add"; 8 | import { url } from "../../../utils/Constants.jsx"; 9 | import Notification from "../../../components/notification/index.jsx"; 10 | import AddNewZone from "./AddNewZone"; 11 | import UpdateZone from "./UpdateZone"; 12 | import Pagination from "../../../components/pagination/index.jsx"; 13 | 14 | const Zones = () => { 15 | const [zonesData, setZonesData] = useState([]); 16 | const [filteredData, setFilteredData] = useState([]); 17 | const [currentPage, setCurrentPage] = useState(1); 18 | const [itemsPerPage, setItemsPerPage] = useState(10); 19 | const [searchTerm, setSearchTerm] = useState(""); 20 | const [isLoading, setIsLoading] = useState(false); 21 | const [anchorEl, setAnchorEl] = useState(null); 22 | const [currentSelectedZone, setCurrentSelectedZone] = useState(null); 23 | const [showAddNewZone, setShowAddNewZone] = useState(false); 24 | const [showUpdateZone, setShowUpdateZone] = useState(false); 25 | 26 | 27 | let navigate = useNavigate(); 28 | 29 | useEffect(() => { 30 | if (!localStorage.getItem("token")) { 31 | navigate("/login"); 32 | } 33 | fetchData(); 34 | }, []); 35 | 36 | const fetchData = async () => { 37 | setIsLoading(true); 38 | try { 39 | const response = await fetch(`${url}/zone/zone-info`, { 40 | method: "GET", 41 | headers: { 42 | "Content-Type": "application/json", 43 | Authorization: `Bearer ${localStorage.getItem("token")}`, 44 | }, 45 | }); 46 | const data = await response.json(); 47 | if (response.ok) { 48 | setZonesData(data); 49 | setFilteredData(data); 50 | } else { 51 | Notification.showErrorMessage("Try Again!", data.error); 52 | } 53 | } catch (err) { 54 | Notification.showErrorMessage("Error", "Server error!"); 55 | } 56 | setIsLoading(false); 57 | }; 58 | 59 | const handleSearch = (event) => { 60 | const value = event.target.value; 61 | setSearchTerm(value); 62 | const filtered = zonesData.filter(zone => 63 | zone.zone_name.toLowerCase().includes(value.toLowerCase()) 64 | ); 65 | setFilteredData(filtered); 66 | setCurrentPage(1); 67 | }; 68 | 69 | const handlePageSizeChange = (event) => { 70 | setItemsPerPage(parseInt(event.target.value, 10)); 71 | setCurrentPage(1); 72 | }; 73 | 74 | const handleClick = (event, zone) => { 75 | setAnchorEl(event.currentTarget); 76 | setCurrentSelectedZone(zone); 77 | }; 78 | 79 | const handleClose = () => { 80 | setAnchorEl(null); 81 | }; 82 | 83 | const onActionClick = (action, zone) => { 84 | setCurrentSelectedZone(zone); 85 | if (action === 'addNewZone') { 86 | setShowAddNewZone(true); 87 | } else if (action === 'update') { 88 | setShowUpdateZone(true); 89 | } else if (action === 'delete') { 90 | // console.log('Delete:', zone); 91 | deletezone(zone); 92 | } 93 | }; 94 | const deletezone = async (zone) => { 95 | try { 96 | const response = await fetch(`${url}/zone/zone-info/${zone.id}`, { 97 | method: "DELETE", 98 | headers: { 99 | "Content-Type": "application/json", 100 | Authorization: `Bearer ${localStorage.getItem("token")}`, 101 | }, 102 | // body: JSON.stringify("formData"), 103 | }); 104 | if (response.ok) { 105 | Notification.showSuccessMessage("Success", "Zone Deleted"); 106 | fetchData(); 107 | // onClose(); 108 | } else { 109 | const json = await response.json(); 110 | Notification.showErrorMessage("Error", json.error); 111 | } 112 | } catch (error) { 113 | Notification.showErrorMessage("Error", "Server error"); 114 | } 115 | }; 116 | 117 | const indexOfLastItem = currentPage * itemsPerPage; 118 | const indexOfFirstItem = indexOfLastItem - itemsPerPage; 119 | const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); 120 | 121 | const totalPages = Math.ceil(filteredData.length / itemsPerPage); 122 | 123 | return ( 124 |
125 |
126 |
127 | 134 | 143 |
144 | 148 |
149 | {isLoading ? ( 150 |
151 | 152 |
153 | ) : currentItems.length > 0 ? ( 154 |
155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | {currentItems.map((zone, index) => ( 168 | 169 | 170 | 171 | 172 | 173 | 174 | 189 | 190 | ))} 191 | 192 |
IDZone NameAllow Re-EntryCreated OnUpdated OnAction
{zone.id}{zone.zone_name}{zone.allow_re_entry ? 'Yes' : 'No'}{new Date(zone.created_on).toLocaleString()}{new Date(zone.updated_on).toLocaleString()} 175 | handleClick(event, zone)}> 176 | 177 | 178 | 179 | { onActionClick('update', currentSelectedZone); handleClose(); }}> 180 | 181 | 182 | 183 | { onActionClick('delete', currentSelectedZone); handleClose(); }}> 184 | 185 | 186 | 187 | 188 |
193 | 194 | 195 | 196 |
197 | ) : ( 198 |
199 |

No data found.

200 |
201 | )} 202 | {showAddNewZone && setShowAddNewZone(false)} fetchData={fetchData} />} 203 | {showUpdateZone && setShowUpdateZone(false)} fetchData={fetchData} />} 204 |
205 | ); 206 | }; 207 | 208 | export default Zones; 209 | 210 | -------------------------------------------------------------------------------- /src/views/guard/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import test from "../../assets/images/no-data.png"; 3 | import Notification from "../../components/notification"; 4 | import defaultImage from "../../assets/images/no-data.png"; 5 | import useWebSocket, { ReadyState } from "react-use-websocket" 6 | 7 | 8 | const ProfileCardRight = ({ visitorObj }) => ( 9 |
10 |
11 | {visitorObj.first_name} 17 |
18 |
19 | {visitorObj.first_name ? ( 20 | <> 21 |
22 |
23 | 24 | {visitorObj.first_name} {visitorObj.last_name} 25 |
26 |
27 | 28 | {visitorObj.gov_id_type} 29 |
30 |
31 | 32 | {visitorObj.gov_id_no} 33 |
34 |
35 | 36 | {visitorObj.email} 37 |
38 |
39 | 40 | {visitorObj.phone} 41 |
42 |
43 | 44 | ) : ( 45 | //
46 |

No Data Available!

47 | //
48 | 49 | )} 50 |
51 |
52 | ); 53 | 54 | const ProfileCardLeft = ({ visitorObj }) => ( 55 |
56 |
57 | {visitorObj.first_name} 63 |
64 |
65 | {visitorObj.first_name ? ( 66 | <> 67 |
68 |
69 | 70 | {visitorObj.first_name} {visitorObj.last_name} 71 |
72 |
73 | 74 | {visitorObj.gov_id_type} 75 |
76 |
77 | 78 | {visitorObj.gov_id_no} 79 |
80 |
81 | 82 | {visitorObj.email} 83 |
84 |
85 | 86 | {visitorObj.phone} 87 |
88 |
89 | 90 | {visitorObj.visitor_type} 91 |
92 |
93 | 94 | {visitorObj.reader} 95 |
96 |
97 | 98 | ) : ( 99 | //

No Data Available!

100 |

No Data Available!

101 | )} 102 |
103 |
104 | ); 105 | 106 | const Guard = () => { 107 | const placeholderData = new Array(6).fill({ 108 | imageUrl: test, 109 | name: 'Jane Cooper', 110 | title: 'Paradigm Representative', 111 | admin: true, 112 | }); 113 | 114 | 115 | 116 | // const WS_URL = "ws://192.168.1.53:8000/ws/data/" // For running in remote server 117 | const WS_URL = "ws://127.0.0.1:8000/ws/data/" // For running in local server 118 | const [profiles, setProfiles] = useState(placeholderData); 119 | 120 | const token = localStorage.getItem("token"); 121 | const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket( 122 | WS_URL, 123 | { 124 | protocols: [token], 125 | share: false, 126 | shouldReconnect: () => false, 127 | onOpen: () => console.log('opened'), 128 | }, 129 | ) 130 | // Run when the connection state (readyState) changes 131 | useEffect(() => { 132 | console.log("Connection state changed", readyState); 133 | 134 | }, [readyState]) 135 | 136 | // Run when a new WebSocket message is received (lastJsonMessage) 137 | useEffect(() => { 138 | console.log(`Got a new message: ${lastJsonMessage}`) 139 | console.log(lastJsonMessage); 140 | if (lastJsonMessage && lastJsonMessage.response.success) { 141 | setProfiles([lastJsonMessage.response.success, ...profiles.slice(0, -1)]); 142 | } else if (lastJsonMessage && lastJsonMessage.response.error) { 143 | Notification.showErrorMessage("Error", lastJsonMessage.response.error, 5000); 144 | } 145 | 146 | console.log(":::progilesss", profiles); 147 | console.log("Shah Print--> ", profiles.slice(0,2)[1]); 148 | }, [lastJsonMessage]) 149 | 150 | const connectionStatus = { 151 | [ReadyState.CONNECTING]: 'Connecting', 152 | [ReadyState.OPEN]: 'Open', 153 | [ReadyState.CLOSING]: 'Closing', 154 | [ReadyState.CLOSED]: 'Closed', 155 | [ReadyState.UNINSTANTIATED]: 'Uninstantiated', 156 | }[readyState]; 157 | 158 | 159 | return ( 160 |
161 |
162 |
167 |
168 |
169 |
170 | {profiles.slice(0, 2).map((profile, index) => ( 171 | 172 | ))} 173 |
174 |
175 | {profiles.slice(2).map((profile, index) => ( 176 | 177 | ))} 178 |
179 |
180 | 181 |
182 | ); 183 | }; 184 | 185 | export default Guard; 186 | 187 | -------------------------------------------------------------------------------- /src/views/pass/MultipleSelectDropdown.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | 3 | const MultipleSelectDropdown = ({ options, selectedOptions, onChange }) => { 4 | const [displayedOptions, setDisplayedOptions] = useState(options); 5 | const [searchTerm, setSearchTerm] = useState(''); 6 | const [isDropdownVisible, setIsDropdownVisible] = useState(false); 7 | const dropdownRef = useRef(null); 8 | 9 | useEffect(() => { 10 | const filterOptions = searchTerm === '' 11 | ? options 12 | : options.filter(option => 13 | option.name.toLowerCase().includes(searchTerm.toLowerCase()) 14 | ); 15 | setDisplayedOptions(filterOptions); 16 | }, [searchTerm, options]); 17 | 18 | useEffect(() => { 19 | const handleClickOutside = (event) => { 20 | if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { 21 | setIsDropdownVisible(false); 22 | } 23 | }; 24 | document.addEventListener('mousedown', handleClickOutside); 25 | return () => { 26 | document.removeEventListener('mousedown', handleClickOutside); 27 | }; 28 | }, []); 29 | 30 | const handleInputChange = (e) => { 31 | setSearchTerm(e.target.value); 32 | setIsDropdownVisible(true); 33 | }; 34 | 35 | const handleSelectionChange = (id) => { 36 | const newSelection = selectedOptions.includes(id) 37 | ? selectedOptions.filter(selectedId => selectedId !== id) 38 | : [...selectedOptions, id]; 39 | onChange(newSelection); 40 | }; 41 | 42 | const removeChip = (id) => { 43 | onChange(selectedOptions.filter(selectedId => selectedId !== id)); 44 | }; 45 | 46 | return ( 47 |
48 | setIsDropdownVisible(true)} 55 | /> 56 | {isDropdownVisible && ( 57 |
62 | {displayedOptions.map(option => ( 63 |
handleSelectionChange(option.id)} 67 | > 68 | {}} 72 | className="mr-2" 73 | readOnly 74 | /> 75 | 76 |
77 | ))} 78 |
79 | )} 80 |
81 | {selectedOptions.map(id => { 82 | const zone = options.find(option => option.id === id); 83 | return ( 84 |
85 | {zone.name} 86 | 92 |
93 | ); 94 | })} 95 |
96 |
97 | ); 98 | }; 99 | 100 | export default MultipleSelectDropdown; 101 | 102 | -------------------------------------------------------------------------------- /src/views/pass/Passes.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { url } from "../../utils/Constants.jsx"; 4 | import Notification from "../../components/notification/index.jsx"; 5 | import CircularProgress from "@mui/material/CircularProgress"; 6 | import { Box } from "@mui/material"; 7 | import ViewPass from "./ViewPass"; 8 | 9 | const Passes = () => { 10 | const [passesData, setPassesData] = useState([]); 11 | const [isLoading, setIsLoading] = useState(false); 12 | const [currentSelectedPass, setCurrentSelectedPass] = useState(null); 13 | const [showViewPass, setShowViewPass] = useState(false); 14 | 15 | let navigate = useNavigate(); 16 | 17 | const fetchData = async () => { 18 | setIsLoading(true); 19 | try { 20 | const response = await fetch(`${url}/passes/visitor-pass-info`, { 21 | method: "GET", 22 | headers: { 23 | "Content-Type": "application/json", 24 | Authorization: `Bearer ${localStorage.getItem("token")}`, 25 | }, 26 | }); 27 | const json = await response.json(); 28 | if (response.ok) { 29 | setPassesData(json); 30 | } else { 31 | Notification.showErrorMessage("Try Again!", json.error); 32 | } 33 | } catch (err) { 34 | Notification.showErrorMessage("Error", "Server error!"); 35 | } 36 | setIsLoading(false); 37 | }; 38 | 39 | useEffect(() => { 40 | if (!localStorage.getItem("token")) { 41 | navigate("/login"); 42 | } 43 | fetchData(); 44 | }, []); 45 | 46 | const handleRowClick = (pass) => { 47 | setCurrentSelectedPass(pass); 48 | setShowViewPass(true); 49 | }; 50 | 51 | return ( 52 |
53 |
54 |
55 | 60 |
61 |
62 | {isLoading ? ( 63 | 72 | 73 | 74 | ) : ( 75 |
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | {passesData.map((pass, index) => ( 90 | handleRowClick(pass)}> 91 | 104 | 105 | 106 | 107 | 108 | 111 | 114 | 115 | ))} 116 | 117 |
Visitor ImageVisitor NamePurposeWhom To VisitVisiting DepartmentCreated OnValid Until
92 |
93 |
94 | {pass.visitor.image ? ( 95 | User 96 | ) : ( 97 |
98 | {pass.visitor.first_name ? pass.visitor.first_name.charAt(0).toUpperCase() : 'N'} 99 |
100 | )} 101 |
102 |
103 |
{pass.visitor.first_name} {pass.visitor.last_name}{pass.visiting_purpose}{pass.whom_to_visit}{pass.visiting_department} 109 | {new Date(pass.created_on).toLocaleString('en-IN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false })} 110 | 112 | {new Date(pass.valid_until).toLocaleString('en-IN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false })} 113 |
118 |
)} 119 | {currentSelectedPass && setShowViewPass(false)} fetchData={fetchData} />} 120 |
121 | ); 122 | }; 123 | 124 | export default Passes; 125 | -------------------------------------------------------------------------------- /src/views/pass/ViewPass.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dialog from '@mui/material/Dialog'; 3 | import Paper from '@mui/material/Paper'; 4 | import IconButton from '@mui/material/IconButton'; 5 | import PrintIcon from '@mui/icons-material/Print'; 6 | import passlogo from '../../assets/images/passlogo.png'; 7 | 8 | 9 | const ViewPass = ({ open, onClose, passData }) => { 10 | 11 | const handlePrint = () => { 12 | window.print(); 13 | }; 14 | 15 | return ( 16 | 23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 |
31 | Pass Logo 32 | {passData?.visitor.image ? ( 33 | User 34 | ) : ( 35 |
36 | {passData?.visitor.first_name ? passData?.visitor.first_name.charAt(0) : 'N/A'} 37 |
38 | )} 39 |
40 |
41 |
42 |
43 |
44 |

Temporary Entry Pass

45 |
46 |
47 |
48 | 49 | 50 | 51 |
52 |
53 | 54 | 55 | 56 |
57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 |
66 | ); 67 | }; 68 | 69 | const InfoItem = ({ label, value }) => ( 70 |
71 | {label}: 72 | {value} 73 |
74 | ); 75 | 76 | export default ViewPass; 77 | -------------------------------------------------------------------------------- /src/views/user/ResetPasswordUser.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { Dialog, DialogTitle } from '@mui/material'; 4 | import Notification from "../../components/notification"; 5 | import { url } from "../../utils/Constants"; 6 | 7 | const ResetPasswordUser = ({ open, onClose, user }) => { 8 | const navigate = useNavigate(); 9 | 10 | const initialValues = { 11 | newPassword: '', 12 | confirmPassword: '' 13 | }; 14 | 15 | const [passwords, setPasswords] = useState(initialValues); 16 | const [errors, setErrors] = useState({}); 17 | const [progress, setProgress] = useState(0); 18 | 19 | useEffect(() => { 20 | const filledFields = Object.values(passwords).filter(value => value.trim() !== '').length; 21 | setProgress((filledFields / 2) * 100); 22 | }, [passwords]); 23 | 24 | const handleInputChange = (e) => { 25 | const { name, value } = e.target; 26 | setPasswords({ ...passwords, [name]: value }); 27 | setErrors({ ...errors, [name]: null }); 28 | }; 29 | 30 | const validate = () => { 31 | const newErrors = {}; 32 | if (!passwords.newPassword.trim()) { 33 | newErrors.newPassword = 'New password is required'; 34 | } 35 | if (passwords.newPassword !== passwords.confirmPassword) { 36 | newErrors.confirmPassword = 'Passwords must match'; 37 | } 38 | setErrors(newErrors); 39 | return Object.keys(newErrors).length === 0; 40 | }; 41 | 42 | const handleSubmit = async () => { 43 | if (!validate()) return; 44 | 45 | try { 46 | const response = await fetch(`${url}/accounts/reset-password-by-admin/${user.id}/`, { 47 | method: 'PATCH', 48 | headers: { 49 | 'Content-Type': 'application/json', 50 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 51 | }, 52 | body: JSON.stringify({ password: passwords.confirmPassword }) 53 | }); 54 | 55 | if (response.ok) { 56 | Notification.showSuccessMessage('Success', 'User Password Updated Successfully'); 57 | navigate('/user'); 58 | } else { 59 | const json = await response.json(); 60 | Notification.showErrorMessage('Try Again!', json.error); 61 | } 62 | } catch (error) { 63 | Notification.showErrorMessage('Error', 'Server error!'); 64 | } finally { 65 | onClose(); 66 | } 67 | }; 68 | 69 | return ( 70 | 77 |
78 | 79 | Reset Password 80 | 81 |
82 |
83 |
87 |
88 |
89 |
90 |
91 | 92 | 101 | {errors.newPassword &&
{errors.newPassword}
} 102 | 103 | 104 | 113 | {errors.confirmPassword &&
{errors.confirmPassword}
} 114 |
115 |
116 | 122 |
123 |
124 |
125 |
126 | ); 127 | }; 128 | 129 | export default ResetPasswordUser; 130 | -------------------------------------------------------------------------------- /src/views/user/UserProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import Dialog from '@mui/material/Dialog'; 3 | import Paper from '@mui/material/Paper'; 4 | import { 5 | Menu, 6 | MenuItem, 7 | IconButton, 8 | ListItemIcon, 9 | ListItemText, 10 | } from "@mui/material"; 11 | import MoreVertIcon from "@mui/icons-material/MoreVert"; 12 | import EditIcon from "@mui/icons-material/Edit"; 13 | import LockResetIcon from '@mui/icons-material/LockReset'; 14 | 15 | import PersonIcon from '@mui/icons-material/Person'; 16 | import HomeIcon from '@mui/icons-material/Home'; 17 | import PhoneIcon from '@mui/icons-material/Phone'; 18 | import EmailIcon from '@mui/icons-material/Email'; 19 | import BadgeIcon from '@mui/icons-material/Badge'; 20 | import BloodtypeIcon from '@mui/icons-material/Bloodtype'; 21 | import BlockIcon from '@mui/icons-material/Block'; 22 | import VpnKeyIcon from '@mui/icons-material/VpnKey'; 23 | 24 | const UserProfile = ({ open, onClose, user, onActionClick }) => { 25 | const userData = user; 26 | const [anchorEl, setAnchorEl] = useState(null); 27 | const [currentSelectedUser, setCurrentSelectedUser] = useState(user); 28 | 29 | const handleClick = (event, user) => { 30 | setAnchorEl(event.currentTarget); 31 | setCurrentSelectedUser(user); 32 | }; 33 | 34 | const handleClose = () => { 35 | setAnchorEl(null); 36 | }; 37 | 38 | return ( 39 | 40 | 41 |
42 |
43 |
44 | handleClick(event, user)} 49 | > 50 | 51 | 52 | 59 | { onActionClick('update', currentSelectedUser); handleClose(); }}> 60 | 61 | 62 | 63 | 64 | 65 | { onActionClick('resetPassword', currentSelectedUser); handleClose(); }}> 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 |
74 |
75 | {userData.image ? ( 76 | User 77 | ) : ( 78 |
79 | {userData.first_name ? userData.first_name.charAt(0) : 'N'} 80 |
81 | )} 82 |
83 |
84 | 85 |
86 |
87 |
88 | {`${userData.first_name} ${userData.last_name}`} 89 |
90 |
91 | } label="User Type" value={userData.user_type} /> 92 | } label="Address" value={userData.address} /> 93 | } label="Phone" value={userData.phone} /> 94 | } label="Email" value={userData.email} /> 95 | } label="Gov ID" value={userData.employee_code} /> 96 | } label="Blood Group" value={userData.blood_group} /> 97 | } label="Is Active" value={userData.is_active ? "Yes" : "No"} /> 98 | } label="Is Staff" value={userData.is_staff ? "Yes" : "No"} /> 99 |
100 |
101 |
102 |
103 |
104 | ); 105 | }; 106 | 107 | const InfoItem = ({ icon, label, value, isReversed = false }) => ( 108 |
109 |
110 | {icon} 111 | {label} 112 |
113 | : 114 | {isReversed && {value}} 115 | {!isReversed && {value}} 116 |
117 | ); 118 | 119 | export default UserProfile; 120 | -------------------------------------------------------------------------------- /src/views/user/Users.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Box, CircularProgress, IconButton, Menu, MenuItem, ListItemIcon, ListItemText, Select, FormControl, InputLabel } from "@mui/material"; 3 | import MoreVertIcon from "@mui/icons-material/MoreVert"; 4 | import VisibilityIcon from "@mui/icons-material/Visibility"; 5 | import EditIcon from "@mui/icons-material/Edit"; 6 | import DeleteIcon from "@mui/icons-material/Delete"; 7 | import LockResetIcon from "@mui/icons-material/LockReset"; 8 | import AddIcon from "@mui/icons-material/Add"; 9 | import Alert from "../../components/alert/index.jsx"; 10 | import Pagination from "../../components/pagination/index.jsx"; 11 | 12 | const Users = ({ users, isLoading, onActionClick }) => { 13 | const [anchorEl, setAnchorEl] = useState(null); 14 | const [currentSelectedUser, setCurrentSelectedUser] = useState(null); 15 | const [showDeleteAlert, setShowDeleteAlert] = useState(false); 16 | const [currentPage, setCurrentPage] = useState(1); 17 | const [itemsPerPage, setItemsPerPage] = useState(10); 18 | const [filteredUsers, setFilteredUsers] = useState([]); 19 | const [searchTerm, setSearchTerm] = useState(""); 20 | 21 | useEffect(() => { 22 | const filtered = users?.filter(user => 23 | user.username.toLowerCase().includes(searchTerm.toLowerCase()) || 24 | `${user.first_name} ${user.last_name}`.toLowerCase().includes(searchTerm.toLowerCase()) 25 | ); 26 | setFilteredUsers(filtered); 27 | }, [searchTerm, users]); 28 | 29 | const handleSearchChange = (event) => { 30 | const value = event.target.value; 31 | setSearchTerm(value); 32 | }; 33 | 34 | const handlePageSizeChange = (event) => { 35 | setItemsPerPage(event.target.value); 36 | setCurrentPage(1); 37 | }; 38 | 39 | const handleClick = (event, user) => { 40 | setAnchorEl(event.currentTarget); 41 | setCurrentSelectedUser(user); 42 | }; 43 | 44 | const handleClose = () => { 45 | setAnchorEl(null); 46 | }; 47 | 48 | const handleDelete = (user) => { 49 | setCurrentSelectedUser(user); 50 | setShowDeleteAlert(true); 51 | handleClose(); 52 | }; 53 | 54 | const confirmDelete = () => { 55 | console.log("Deleting user:", currentSelectedUser); 56 | setShowDeleteAlert(false); 57 | // Perform delete action here 58 | }; 59 | 60 | const indexOfLastUser = currentPage * itemsPerPage; 61 | const indexOfFirstUser = indexOfLastUser - itemsPerPage; 62 | const currentUsers = filteredUsers?.slice(indexOfFirstUser, indexOfLastUser); 63 | const totalPages = Math.ceil(filteredUsers?.length / itemsPerPage); 64 | 65 | return ( 66 |
67 |
68 |
69 | 76 | 85 |
86 | 93 |
94 | {isLoading ? ( 95 | 96 | 97 | 98 | ) : currentUsers?.length > 0 ? ( 99 |
100 | 101 | 102 | 103 | 106 | 109 | 112 | 115 | 118 | 121 | 124 | 127 | 128 | 129 | 130 | {currentUsers?.map((user, index) => ( 131 | 132 | 145 | 148 | 151 | 154 | 157 | 160 | 163 | 205 | 206 | ))} 207 | 208 |
104 | User Image 105 | 107 | User Name 108 | 110 | Name 111 | 113 | User Type 114 | 116 | Employee Code 117 | 119 | Work Location 120 | 122 | Department 123 | 125 | Action 126 |
133 |
134 |
135 | {user.image ? ( 136 | User 137 | ) : ( 138 |
139 | {user.username ? user.username.charAt(0).toUpperCase() : 'N'} 140 |
141 | )} 142 |
143 |
144 |
146 | {user.username} 147 | 149 | {user.first_name} {user.last_name} 150 | 152 | {user.user_type} 153 | 155 | {user.employee_code} 156 | 158 | {user.work_location} 159 | 161 | {user.department} 162 | 164 | handleClick(event, user)} 169 | > 170 | 171 | 172 | 179 | { onActionClick('view', currentSelectedUser); handleClose(); }}> 180 | 181 | 182 | 183 | 184 | 185 | { onActionClick('update', currentSelectedUser); handleClose(); }}> 186 | 187 | 188 | 189 | 190 | 191 | { handleDelete(currentSelectedUser) }}> 192 | 193 | 194 | 195 | 196 | 197 | { onActionClick('resetPassword', currentSelectedUser); handleClose(); }}> 198 | 199 | 200 | 201 | 202 | 203 | 204 |
209 | 210 |
211 | ) : ( 212 | 213 |

No users found.

214 |
215 | )} 216 | setShowDeleteAlert(false)} 219 | title="Confirm Delete" 220 | message="Are you sure you want to delete this user?" 221 | buttonText="Delete" 222 | buttonColor="red" 223 | onButtonClick={confirmDelete} 224 | /> 225 |
226 | ); 227 | }; 228 | 229 | export default Users; 230 | 231 | -------------------------------------------------------------------------------- /src/views/user/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import { Link, useNavigate, useLocation } from "react-router-dom"; 3 | import { url } from "../../utils/Constants.jsx"; 4 | import Notification from "../../components/notification/index.jsx"; 5 | import { UserContext } from "../../context/UserContext.jsx"; 6 | 7 | import Users from './Users'; 8 | import UserProfile from './UserProfile'; 9 | import UpdateUser from './UpdateUser'; 10 | import AddNewUser from './AddNewUser'; 11 | import ResetPasswordUser from './ResetPasswordUser'; 12 | 13 | const User = () => { 14 | const [selectedUser, setSelectedUser] = useState(null); 15 | const [viewModalOpen, setViewModalOpen] = useState(false); 16 | const [updateModalOpen, setUpdateModalOpen] = useState(false); 17 | const [addNewUserModalOpen, setAddNewUserModalOpen] = useState(false); 18 | const [resetPasswordModalOpen, setResetPasswordModalOpen] = useState(false); 19 | 20 | const handleActionClick = (action, user = null) => { 21 | if (user) setSelectedUser(user); 22 | if (action === 'view') setViewModalOpen(true); 23 | if (action === 'update') setUpdateModalOpen(true); 24 | if (action === 'addNewUser') setAddNewUserModalOpen(true); 25 | if (action === 'resetPassword') setResetPasswordModalOpen(true); 26 | }; 27 | 28 | let history = useNavigate(); 29 | 30 | const [userData, setUserData] = useState(null); 31 | const [isLoading, setIsLoading] = useState(false); 32 | 33 | const fetchData = async () => { 34 | setIsLoading(true); 35 | try { 36 | const response = await fetch(`${url}/accounts/get-all-user/`, { 37 | method: "GET", 38 | headers: { 39 | "Content-Type": "application/json", 40 | Authorization: `Bearer ${localStorage.getItem("token")}`, 41 | }, 42 | }); 43 | const json = await response.json(); 44 | if (response.ok) { 45 | setUserData(json.results); 46 | } else { 47 | Notification.showErrorMessage("Try Again!", json.error); 48 | } 49 | } catch (err) { 50 | Notification.showErrorMessage("Error", "Server error!"); 51 | } 52 | setIsLoading(false); 53 | }; 54 | 55 | useEffect(() => { 56 | if (!localStorage.getItem("token")) { 57 | history("/login"); 58 | } 59 | fetchData(); 60 | }, []); 61 | 62 | return ( 63 |
64 | {userData && ()} 65 | {selectedUser && (<> 66 | setViewModalOpen(false)} user={selectedUser} onActionClick={handleActionClick} /> 67 | setUpdateModalOpen(false)} user={selectedUser} fetchData={fetchData} /> 68 | setResetPasswordModalOpen(false)} user={selectedUser} /> 69 | 70 | )} 71 | setAddNewUserModalOpen(false)} fetchData={fetchData} /> 72 |
73 | ); 74 | }; 75 | 76 | export default User; 77 | -------------------------------------------------------------------------------- /src/views/visitor/VisitorProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Dialog from '@mui/material/Dialog'; 3 | import Paper from '@mui/material/Paper'; 4 | import { 5 | Menu, 6 | MenuItem, 7 | IconButton, 8 | ListItemIcon, 9 | ListItemText, 10 | } from "@mui/material"; 11 | import MoreVertIcon from "@mui/icons-material/MoreVert"; 12 | import EditIcon from "@mui/icons-material/Edit"; 13 | import DeleteIcon from "@mui/icons-material/Delete"; 14 | import PersonIcon from '@mui/icons-material/Person'; 15 | import HomeIcon from '@mui/icons-material/Home'; 16 | import PhoneIcon from '@mui/icons-material/Phone'; 17 | import EmailIcon from '@mui/icons-material/Email'; 18 | import VpnKeyIcon from '@mui/icons-material/VpnKey'; 19 | import BadgeIcon from '@mui/icons-material/Badge'; 20 | import BloodtypeIcon from '@mui/icons-material/Bloodtype'; 21 | import BlockIcon from '@mui/icons-material/Block'; 22 | import CreditCardIcon from '@mui/icons-material/CreditCard'; 23 | 24 | const VisitorProfile = ({ open, onClose, visitor, onActionClick }) => { 25 | const [anchorEl, setAnchorEl] = useState(null); 26 | const [currentSelectedVisitor, setCurrentSelectedVisitor] = useState(visitor); 27 | 28 | const handleClick = (event, visitor) => { 29 | setAnchorEl(event.currentTarget); 30 | setCurrentSelectedVisitor(visitor); 31 | }; 32 | 33 | const handleClose = () => { 34 | setAnchorEl(null); 35 | }; 36 | 37 | return ( 38 | 39 | 40 |
41 |
42 |
43 | handleClick(event, visitor)} 48 | > 49 | 50 | 51 | 58 | { onActionClick('update', currentSelectedVisitor); handleClose(); }}> 59 | 60 | 61 | 62 | 63 | 64 | { onActionClick('pass', currentSelectedVisitor); handleClose(); }}> 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 |
73 |
74 | {visitor.image ? ( 75 | User 76 | ) : ( 77 |
78 | {visitor.first_name ? visitor.first_name.charAt(0) : 'N'} 79 |
80 | )} 81 |
82 |
83 |
84 |
85 |
86 | {`${visitor.first_name} ${visitor.last_name}`} 87 |
88 |
89 | } label="Visitor Type" value={visitor.visitor_type} /> 90 | } label="Address" value={visitor.address} /> 91 | } label="Phone" value={visitor.phone} /> 92 | } label="Email" value={visitor.email} /> 93 | } label="Gov ID Type" value={visitor.gov_id_type.replace('_', ' ')} /> 94 | } label="Gov ID No" value={visitor.gov_id_no} /> 95 | } label="Blood Group" value={visitor.blood_group} /> 96 | } label="Blacklisted" value={visitor.is_blacklisted ? "Yes" : "No"} /> 97 |
98 |
99 |
100 |
101 |
102 | ); 103 | }; 104 | 105 | const InfoItem = ({ icon, label, value }) => ( 106 |
107 | {icon} 108 | {label} 109 | : 110 | {value} 111 |
112 | ); 113 | 114 | export default VisitorProfile; 115 | 116 | -------------------------------------------------------------------------------- /src/views/visitor/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { url } from "../../utils/Constants.jsx"; 4 | import Notification from "../../components/notification/index.jsx"; 5 | 6 | import Visitors from './Visitors'; 7 | import VisitorProfile from './VisitorProfile'; 8 | import UpdateVisitor from './UpdateVisitor'; 9 | import AddNewVisitor from './AddNewVisitor'; 10 | import CreateNewPass from '../pass/CreateNewPass'; 11 | 12 | const Visitor = () => { 13 | const [selectedVisitor, setSelectedVisitor] = useState(null); 14 | const [viewModalOpen, setViewModalOpen] = useState(false); 15 | const [updateModalOpen, setUpdateModalOpen] = useState(false); 16 | const [addNewVisitorModalOpen, setAddNewVisitorModalOpen] = useState(false); 17 | const [createNewPassModalOpen, setCreateNewPassModalOpen] = useState(false); 18 | 19 | const handleActionClick = (action, visitor = null) => { 20 | if (visitor) setSelectedVisitor(visitor); 21 | switch (action) { 22 | case 'view': 23 | setViewModalOpen(true); 24 | break; 25 | case 'update': 26 | setUpdateModalOpen(true); 27 | break; 28 | case 'addNewVisitor': 29 | setAddNewVisitorModalOpen(true); 30 | break; 31 | case 'pass': 32 | setCreateNewPassModalOpen(true); 33 | setViewModalOpen(false); 34 | break; 35 | default: 36 | console.log("Unhandled action:", action); 37 | } 38 | }; 39 | 40 | let navigate = useNavigate(); 41 | 42 | const [visitorData, setVisitorData] = useState(null); 43 | const [totalVisitors, setTotalVisitors] = useState(null); 44 | const [isLoading, setIsLoading] = useState(false); 45 | const [searchParams, setSearchParams] = useState({ 46 | first_name__icontains: '', 47 | last_name__icontains: '', 48 | phone__icontains: '', 49 | gov_id_no__icontains: '', 50 | offset: 0, 51 | limit: 10 52 | }); 53 | 54 | 55 | const fetchData = async () => { 56 | setIsLoading(true); 57 | const queryString = new URLSearchParams(searchParams).toString(); 58 | try { 59 | const response = await fetch(`${url}/visitor/visitor-info?${queryString}`, { 60 | method: "GET", 61 | headers: { 62 | "Content-Type": "application/json", 63 | Authorization: `Bearer ${localStorage.getItem("token")}`, 64 | }, 65 | }); 66 | const json = await response.json(); 67 | if (response.ok) { 68 | setVisitorData(json?.results); 69 | setTotalVisitors(json?.count); 70 | } else { 71 | Notification.showErrorMessage("Try Again!", json.error); 72 | } 73 | } catch (err) { 74 | Notification.showErrorMessage("Error", "Server error!"); 75 | } 76 | setIsLoading(false); 77 | }; 78 | 79 | useEffect(() => { 80 | if (!localStorage.getItem("token")) { 81 | navigate("/login"); 82 | } 83 | fetchData(); 84 | }, [searchParams]); 85 | 86 | return ( 87 |
88 | 89 | setAddNewVisitorModalOpen(false)} fetchData={fetchData} onActionClick={handleActionClick} /> 90 | {selectedVisitor && ( 91 | <> 92 | setViewModalOpen(false)} visitor={selectedVisitor} onActionClick={handleActionClick} /> 93 | setUpdateModalOpen(false)} visitor={selectedVisitor} fetchData={fetchData} /> 94 | setCreateNewPassModalOpen(false)} visitor={selectedVisitor} fetchData={fetchData} /> 95 | 96 | )} 97 |
98 | ); 99 | }; 100 | 101 | export default Visitor; 102 | 103 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: { 6 | colors: { 7 | customGreen: '#40664F', 8 | customFieldGreen: '#58866A' 9 | } 10 | }, 11 | }, 12 | plugins: [], 13 | }; 14 | --------------------------------------------------------------------------------