├── .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 | 
53 |
54 | 
55 |
56 | 
57 |
58 | 
59 |
60 | 
61 |
62 | 
63 |
64 | 
65 |
66 | 
67 |
68 | 
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 |
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 |
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 |

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 | {/*

*/}
52 |
53 | VISITOR MANAGEMENT SYSTEM
54 |
55 |
56 | {localStorage.getItem("token") && (
57 |
58 |
setProfileModalOpen(true)}
61 | >
62 |
63 | {userimage != "null" ? (
64 |

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 | {/*
125 |
126 |

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 |
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 |
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 | ID |
159 | Name |
160 | IP |
161 | Port |
162 | Address |
163 | Action |
164 |
165 |
166 |
167 | {currentItems.map((adam, index) => (
168 |
169 | {adam.id} |
170 | {adam.name} |
171 | {adam.ip} |
172 | {adam.port} |
173 | {adam.address} |
174 |
175 | handleClick(event, adam)}>
176 |
177 |
178 |
188 | |
189 |
190 | ))}
191 |
192 |
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 |
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 |
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 |
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 |
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 |
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 | ID |
112 | Name |
113 | Adam Name |
114 | Zone Name |
115 | Associated Users |
116 | Moxa IP |
117 | Reader Type |
118 | COM Port |
119 |
120 |
121 |
122 | {currentItems.map((mapping, index) => (
123 |
124 | {mapping.id} |
125 | {mapping.name} |
126 | {mapping.adam_name} |
127 | {mapping.zone_name} |
128 | {mapping.associated_users.map(user => user.username).join(', ')} |
129 | {mapping.moxa_ip} |
130 | {mapping.reader_type} |
131 | {mapping.com_port} |
132 |
133 | ))}
134 |
135 |
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 | ID |
135 | Name |
136 | Moxa IP |
137 | COM Port |
138 | Reader Type |
139 | ADAM Module |
140 | Zone |
141 | Action |
142 |
143 |
144 |
145 | {currentItems.map((reader, index) => (
146 |
147 | {reader.id} |
148 | {reader.name} |
149 | {reader.moxa_ip} |
150 | {reader.com_port} |
151 | {reader.reader_type} |
152 | {reader.adam_name} |
153 | {reader.zone_name} |
154 |
155 | handleClick(event, reader)}>
156 |
157 |
158 |
168 | |
169 |
170 | ))}
171 |
172 |
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 |
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 |
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 | ID |
159 | Zone Name |
160 | Allow Re-Entry |
161 | Created On |
162 | Updated On |
163 | Action |
164 |
165 |
166 |
167 | {currentItems.map((zone, index) => (
168 |
169 | {zone.id} |
170 | {zone.zone_name} |
171 | {zone.allow_re_entry ? 'Yes' : 'No'} |
172 | {new Date(zone.created_on).toLocaleString()} |
173 | {new Date(zone.updated_on).toLocaleString()} |
174 |
175 | handleClick(event, zone)}>
176 |
177 |
178 |
188 | |
189 |
190 | ))}
191 |
192 |
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 |
)
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 |
)
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 |
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 |
62 | {isLoading ? (
63 |
72 |
73 |
74 | ) : (
75 |
76 |
77 |
78 |
79 | Visitor Image |
80 | Visitor Name |
81 | Purpose |
82 | Whom To Visit |
83 | Visiting Department |
84 | Created On |
85 | Valid Until |
86 |
87 |
88 |
89 | {passesData.map((pass, index) => (
90 | handleRowClick(pass)}>
91 |
92 |
93 |
94 | {pass.visitor.image ? (
95 | 
96 | ) : (
97 |
98 | {pass.visitor.first_name ? pass.visitor.first_name.charAt(0).toUpperCase() : 'N'}
99 |
100 | )}
101 |
102 |
103 | |
104 | {pass.visitor.first_name} {pass.visitor.last_name} |
105 | {pass.visiting_purpose} |
106 | {pass.whom_to_visit} |
107 | {pass.visiting_department} |
108 |
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 | |
111 |
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 | |
114 |
115 | ))}
116 |
117 |
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 |
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 |
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 |
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 |
104 | User Image
105 | |
106 |
107 | User Name
108 | |
109 |
110 | Name
111 | |
112 |
113 | User Type
114 | |
115 |
116 | Employee Code
117 | |
118 |
119 | Work Location
120 | |
121 |
122 | Department
123 | |
124 |
125 | Action
126 | |
127 |
128 |
129 |
130 | {currentUsers?.map((user, index) => (
131 |
132 |
133 |
134 |
135 | {user.image ? (
136 | 
137 | ) : (
138 |
139 | {user.username ? user.username.charAt(0).toUpperCase() : 'N'}
140 |
141 | )}
142 |
143 |
144 | |
145 |
146 | {user.username}
147 | |
148 |
149 | {user.first_name} {user.last_name}
150 | |
151 |
152 | {user.user_type}
153 | |
154 |
155 | {user.employee_code}
156 | |
157 |
158 | {user.work_location}
159 | |
160 |
161 | {user.department}
162 | |
163 |
164 | handleClick(event, user)}
169 | >
170 |
171 |
172 |
204 | |
205 |
206 | ))}
207 |
208 |
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 |
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 |
--------------------------------------------------------------------------------