├── .env ├── src ├── public │ ├── man.png │ └── vite.svg ├── components │ ├── utils │ │ ├── Status.jsx │ │ ├── Sipnners.jsx │ │ └── ApiService.jsx │ ├── hooks │ │ └── useLogout.jsx │ ├── Spiner │ │ └── Spiner.jsx │ ├── context │ │ └── ContextProvider.jsx │ ├── Dashboard │ │ ├── AdminDashboard │ │ │ ├── TaskDashboard.module.css │ │ │ ├── CircleProgressBar.jsx │ │ │ ├── TicketAreaChart.jsx │ │ │ ├── TaskDashboard.jsx │ │ │ ├── Dashboard.jsx │ │ │ └── TicketsDashboard.jsx │ │ ├── UserDashbooard │ │ │ ├── userDashboard.module.css │ │ │ ├── UserDataChart.jsx │ │ │ ├── UserTicketsDashoard.jsx │ │ │ ├── UserAreachart.jsx │ │ │ ├── UserTaskDashboard.jsx │ │ │ └── UserDashboard.jsx │ │ └── Dashboard Cards │ │ │ ├── ApprovedTickets.jsx │ │ │ ├── TicketsCard.jsx │ │ │ ├── PendingTickets.jsx │ │ │ └── RessolvedTickets.jsx │ ├── common │ │ └── TicketTile.jsx │ └── slider │ │ ├── Slider.module.css │ │ └── Slider.jsx ├── pages │ ├── User Task │ │ ├── task.module.css │ │ ├── TaskSubmissionForm.jsx │ │ ├── TaskList.jsx │ │ └── TaskPage.jsx │ ├── TASK │ │ ├── task.module.css │ │ ├── submittedtask.module.css │ │ ├── Task.jsx │ │ ├── EditTaskForm.jsx │ │ ├── TaskForm.jsx │ │ └── SubmittedTaskPage.jsx │ ├── Password │ │ ├── ForgotPassword.jsx │ │ ├── frogot.module.css │ │ ├── resetPassword.module.css │ │ └── Resetpassword.jsx │ ├── Signup │ │ ├── signup.module.css │ │ └── Signup.jsx │ ├── Tickets │ │ ├── Create.jsx │ │ └── Tickets.jsx │ ├── signin │ │ ├── signin.module.css │ │ └── SignIn.jsx │ └── Users │ │ ├── Details.jsx │ │ ├── Register.jsx │ │ ├── Home.jsx │ │ └── Edit.jsx ├── main.jsx ├── App.jsx ├── index.css └── routes │ └── AppRouters.jsx ├── netlify.toml ├── vite.config.js ├── .gitignore ├── .eslintrc.cjs ├── index.html ├── package.json └── README.md /.env: -------------------------------------------------------------------------------- 1 | VITE_API_URL='https://crm-1r8t.onrender.com' 2 | # VITE_API_URL='http://localhost:8000' 3 | -------------------------------------------------------------------------------- /src/public/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HarishVinayagamoorthy/CRM-MERN/HEAD/src/public/man.png -------------------------------------------------------------------------------- /src/components/utils/Status.jsx: -------------------------------------------------------------------------------- 1 | export const Status = { 2 | APPROVED: "approved", 3 | PENDING: "pending", 4 | RESOLVED: "resolved", 5 | }; 6 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "npm run build" 3 | publish = "dist" 4 | 5 | [[redirects]] 6 | from = "/*" 7 | to = "/index.html" 8 | status = 200 9 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | }); 9 | -------------------------------------------------------------------------------- /src/components/utils/Sipnners.jsx: -------------------------------------------------------------------------------- 1 | import Spinner from "react-bootstrap/Spinner"; 2 | 3 | function VariantsExample() { 4 | return ( 5 | <> 6 | 7 | 8 | ); 9 | } 10 | 11 | export default VariantsExample; 12 | -------------------------------------------------------------------------------- /src/pages/User Task/task.module.css: -------------------------------------------------------------------------------- 1 | /* task.module.css */ 2 | 3 | .taskPage { 4 | max-width: 800px; 5 | margin: auto; 6 | padding: 20px; 7 | } 8 | 9 | .error { 10 | color: red; 11 | margin-top: 5px; 12 | } 13 | 14 | /* You can add more styles based on your design requirements */ 15 | -------------------------------------------------------------------------------- /src/pages/TASK/task.module.css: -------------------------------------------------------------------------------- 1 | /* task.module.css */ 2 | 3 | .input { 4 | /* Add your input field styles here */ 5 | margin-bottom: 10px; 6 | } 7 | 8 | .error { 9 | color: red; 10 | /* Add your error message styles here */ 11 | } 12 | 13 | /* Add any additional styles for your form or other elements as needed */ 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | /.env -------------------------------------------------------------------------------- /src/components/hooks/useLogout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useNavigate } from 'react-router-dom' 3 | import {toast} from 'react-toastify' 4 | function useLogout() { 5 | let navigate = useNavigate() 6 | return ()=>{ 7 | sessionStorage.clear() 8 | toast.success("User Logout Successfull") 9 | navigate('/') 10 | } 11 | } 12 | 13 | export default useLogout -------------------------------------------------------------------------------- /src/pages/TASK/submittedtask.module.css: -------------------------------------------------------------------------------- 1 | /* submittedtask.module.css */ 2 | 3 | .submittedTaskPage { 4 | padding: 20px; 5 | } 6 | 7 | /* Search Bar Styles */ 8 | .form-group { 9 | margin-bottom: 1rem; 10 | } 11 | 12 | /* Card Styles */ 13 | .card { 14 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 15 | transition: box-shadow 0.3s; 16 | } 17 | 18 | .card:hover { 19 | box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/utils/ApiService.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const AxiosService = axios.create({ 4 | baseURL: `${import.meta.env.VITE_API_URL}`, 5 | headers: { 6 | "Content-Type": "application/json", 7 | }, 8 | }); 9 | 10 | AxiosService.interceptors.request.use((config) => { 11 | const token = sessionStorage.getItem("token"); 12 | if (token) config.headers.Authorization = `Bearer ${token}`; 13 | return config; 14 | }); 15 | 16 | export default AxiosService; 17 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.css"; 5 | import "bootstrap/dist/css/bootstrap.min.css"; 6 | import { ToastContainer } from "react-toastify"; 7 | import "react-toastify/dist/ReactToastify.css"; 8 | 9 | ReactDOM.createRoot(document.getElementById("root")).render( 10 | 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/components/Spiner/Spiner.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Spinner from "react-bootstrap/Spinner"; 4 | 5 | const Spiner = () => { 6 | return ( 7 | <> 8 |
12 | 13 |   Loading... 14 |
15 | 16 | ); 17 | }; 18 | 19 | export default Spiner; 20 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter } from "react-router-dom"; 3 | import AppRouters from "./routes/AppRouters"; 4 | import ContextProvider from "./components/context/ContextProvider"; 5 | import "bootstrap/dist/css/bootstrap.min.css"; 6 | import "bootstrap/dist/js/bootstrap.bundle.min.js"; 7 | import "./index.css"; 8 | 9 | function App() { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/components/context/ContextProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useState } from "react"; 2 | 3 | export const adddata = createContext(""); 4 | export const updatedata = createContext(""); 5 | export const deldata = createContext(""); 6 | 7 | const ContextProvider = ({ children }) => { 8 | const [udata, setUdata] = useState(""); 9 | const [updata, setUPdata] = useState(""); 10 | const [dltdata, setDLTdata] = useState(""); 11 | 12 | return ( 13 | 14 | 15 | 16 | {children} 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default ContextProvider; 24 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Vite + React 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/Dashboard/AdminDashboard/TaskDashboard.module.css: -------------------------------------------------------------------------------- 1 | /* Add more styles as needed */ 2 | /* TaskDashboard.module.css */ 3 | 4 | .title { 5 | font-size: 24px !important; 6 | font-weight: bold !important; 7 | margin-bottom: 20px !important; 8 | } 9 | 10 | .card { 11 | width: auto !important; 12 | margin-bottom: 20px !important; 13 | border-radius: 10px !important; 14 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important; 15 | height: auto; 16 | } 17 | 18 | .propertyValue { 19 | font-size: 18px !important; 20 | font-weight: bold !important; 21 | margin-bottom: 10px !important; 22 | } 23 | 24 | .warningCard { 25 | background-color: #ffcf3e; /* Bootstrap warning color */ 26 | } 27 | 28 | .successCard { 29 | background-color: #28a745; /* Bootstrap success color */ 30 | } 31 | 32 | /* Add more styles as needed */ 33 | -------------------------------------------------------------------------------- /src/components/Dashboard/UserDashbooard/userDashboard.module.css: -------------------------------------------------------------------------------- 1 | /* userDashboard.module.css */ 2 | 3 | .container { 4 | margin: 20px; 5 | } 6 | 7 | .summary { 8 | border: 1px solid #ccc; 9 | padding: 20px; 10 | margin-bottom: 20px; 11 | display: flex; 12 | justify-content: space-around; 13 | } 14 | 15 | .chartContainer { 16 | margin-top: 20px; 17 | } 18 | 19 | .tickets { 20 | border: 1px solid #ccc; 21 | padding: 20px; 22 | } 23 | 24 | .ticket { 25 | border-bottom: 1px solid #ccc; 26 | padding: 10px; 27 | } 28 | 29 | /* New styles for visualizations */ 30 | .chartContainer { 31 | margin-top: 20px; 32 | } 33 | 34 | /* Update existing styles */ 35 | .summary p { 36 | margin: 10px 0; 37 | } 38 | 39 | /* Colors for ticket statuses */ 40 | .status-resolved { 41 | color: green; 42 | } 43 | 44 | .status-pending { 45 | color: orange; 46 | } 47 | 48 | .status-approved { 49 | color: blue; 50 | } 51 | -------------------------------------------------------------------------------- /src/pages/TASK/Task.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import AxiosService from "../../components/utils/ApiService"; 3 | import TaskForm from "./TaskForm"; 4 | import TaskList from "./TaskList"; 5 | 6 | const Task = () => { 7 | const [tasks, setTasks] = useState([]); 8 | 9 | const fetchTasks = async () => { 10 | try { 11 | const response = await AxiosService.get("/task/tasks"); 12 | setTasks(response.data.tasks); 13 | } catch (error) { 14 | console.error("Error fetching tasks:", error.message); 15 | } 16 | }; 17 | 18 | useEffect(() => { 19 | fetchTasks(); 20 | }, []); 21 | 22 | const refreshTasks = () => { 23 | fetchTasks(); 24 | }; 25 | 26 | return ( 27 |
28 |
29 |
30 | {/*
31 | 32 | / 33 | 34 |
*/} 35 | 36 | 37 |
38 |
39 |
40 | ); 41 | }; 42 | 43 | export default Task; 44 | -------------------------------------------------------------------------------- /src/components/common/TicketTile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Button from "react-bootstrap/Button"; 3 | import Card from "react-bootstrap/Card"; 4 | 5 | function TicketTile({ ticket }) { 6 | const [showFullText, setShowFullText] = useState(false); 7 | 8 | const toggleTextVisibility = () => { 9 | setShowFullText(!showFullText); 10 | }; 11 | 12 | return ( 13 | 14 | 19 | 20 | {ticket.title} 21 | 22 | {ticket && ticket.description 23 | ? showFullText 24 | ? ticket.description 25 | : `${ticket.description.slice(0, 100)}...` 26 | : ""} 27 | 28 | 31 | 32 | 33 | ); 34 | } 35 | 36 | export default TicketTile; 37 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | font-family: "Poppins", sans-serif; 6 | } 7 | 8 | body::before { 9 | content: ""; 10 | position: absolute; 11 | width: 100%; 12 | height: 100%; 13 | z-index: -100; 14 | /* background: linear-gradient(45deg, rgb(15,23,42),rgb(25,49,80) 100%); */ 15 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 16 | 17 | background-position: center; 18 | background-size: cover; 19 | } 20 | 21 | .add_btn { 22 | text-align: right; 23 | } 24 | 25 | .container { 26 | overflow: auto; 27 | } 28 | 29 | .left_view h3 { 30 | font-size: 21px; 31 | } 32 | 33 | .left_view p { 34 | font-weight: 600; 35 | } 36 | 37 | .left_view span { 38 | font-weight: 400; 39 | } 40 | 41 | .right_view p { 42 | font-weight: 600; 43 | } 44 | 45 | .right_view span { 46 | font-weight: 400; 47 | } 48 | 49 | .header-nav-items { 50 | display: flex; 51 | flex-wrap: nowrap; 52 | justify-content: flex-start; 53 | gap: 20px; 54 | } 55 | 56 | .tickets-wrapper { 57 | margin: 0% 30% 0% 30%; 58 | padding: 10px; 59 | } 60 | 61 | .card { 62 | margin: 10px !important; 63 | } 64 | 65 | .table-image { 66 | width: 100px; 67 | } 68 | 69 | .cursor-pointer { 70 | cursor: pointer; 71 | } 72 | -------------------------------------------------------------------------------- /src/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/Dashboard/UserDashbooard/UserDataChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Doughnut } from "react-chartjs-2"; 3 | import AxiosService from "../../utils/ApiService"; 4 | 5 | const UserDataChart = () => { 6 | const [userData, setUserData] = useState(null); 7 | 8 | useEffect(() => { 9 | const fetchData = async () => { 10 | try { 11 | const response = await AxiosService.get(`/user/getdata`); 12 | setUserData(response.data); 13 | } catch (error) { 14 | console.error("Error fetching user data:", error); 15 | } 16 | }; 17 | 18 | fetchData(); 19 | }, []); 20 | 21 | return ( 22 | // 23 |
24 | {userData && ( 25 | 44 | )} 45 |
46 | //
47 | ); 48 | }; 49 | 50 | export default UserDataChart; 51 | -------------------------------------------------------------------------------- /src/components/Dashboard/AdminDashboard/CircleProgressBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Line, Pie } from "react-chartjs-2"; 3 | import AxiosService from "../../utils/ApiService"; 4 | 5 | const Dashboard = () => { 6 | const [userData, setUserData] = useState(null); 7 | const [ticketData, setTicketData] = useState(null); 8 | 9 | useEffect(() => { 10 | const fetchUserData = async () => { 11 | try { 12 | const response = await AxiosService.get("/user/getdata"); // Replace with your API endpoint 13 | setUserData(response.data); 14 | } catch (error) { 15 | console.error("Error fetching user data:", error); 16 | } 17 | }; 18 | 19 | fetchUserData(); 20 | }, []); 21 | 22 | return ( 23 | <> 24 |
25 | 26 | {userData && ( 27 | 45 | )} 46 |
47 | 48 | ); 49 | }; 50 | 51 | export default Dashboard; 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "dev": "vite", 9 | "build": "vite build", 10 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "@emotion/react": "^11.11.1", 15 | "@emotion/styled": "^11.11.0", 16 | "@fortawesome/fontawesome-svg-core": "^6.5.1", 17 | "@fortawesome/free-brands-svg-icons": "^6.5.1", 18 | "@fortawesome/react-fontawesome": "^0.2.0", 19 | "@mui/icons-material": "^5.14.18", 20 | "@mui/material": "^5.14.18", 21 | "axios": "^1.6.2", 22 | "bootstrap": "^5.3.2", 23 | "bootstrap-vue": "^2.23.1", 24 | "cdbreact": "^1.5.18", 25 | "chart.js": "^4.4.1", 26 | "font-awesome-icons": "^1.6.0", 27 | "formik": "^2.4.5", 28 | "material-icons": "^1.13.12", 29 | "moment": "^2.29.4", 30 | "nodemailer": "^6.9.7", 31 | "react": "^18.2.0", 32 | "react-bootstrap": "^2.9.1", 33 | "react-chartjs-2": "^5.2.0", 34 | "react-circular-progressbar": "^2.1.0", 35 | "react-dom": "^18.2.0", 36 | "react-graph-vis": "^1.0.7", 37 | "react-icons": "^4.12.0", 38 | "react-parallax-tilt": "^1.7.174", 39 | "react-router-dom": "^6.19.0", 40 | "react-scripts": "^3.0.1", 41 | "react-select": "^5.8.0", 42 | "react-spring": "^9.7.3", 43 | "react-toastify": "^9.1.3", 44 | "recharts": "^2.10.3", 45 | "styled-components": "^6.1.1", 46 | "yup": "^1.3.2" 47 | }, 48 | "devDependencies": { 49 | "@types/react": "^18.2.15", 50 | "@types/react-dom": "^18.2.7", 51 | "@vitejs/plugin-react": "^4.0.3", 52 | "eslint": "^8.45.0", 53 | "eslint-plugin-react": "^7.32.2", 54 | "eslint-plugin-react-hooks": "^4.6.0", 55 | "eslint-plugin-react-refresh": "^0.4.3", 56 | "tailwindcss": "^3.3.5", 57 | "vite": "^4.4.5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/pages/TASK/EditTaskForm.jsx: -------------------------------------------------------------------------------- 1 | // EditTaskForm.js 2 | import React, { useState } from "react"; 3 | 4 | const EditTaskForm = ({ task, onUpdate, onHide }) => { 5 | const [formData, setFormData] = useState({ 6 | title: task.title, 7 | description: task.description, 8 | assignedTo: task.assignedTo, 9 | }); 10 | 11 | const handleChange = (e) => { 12 | setFormData({ 13 | ...formData, 14 | [e.target.name]: e.target.value, 15 | }); 16 | }; 17 | 18 | const handleSubmit = (e) => { 19 | e.preventDefault(); 20 | // Perform the update using onUpdate callback 21 | onUpdate(formData); 22 | onHide(); // Close the modal 23 | }; 24 | 25 | return ( 26 |
27 |
28 | 31 | 39 |
40 |
41 | 44 | 51 |
52 |
53 | 56 | 64 |
65 | 68 |
69 | ); 70 | }; 71 | 72 | export default EditTaskForm; 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CRM Project with MERN Stack 2 | 3 | Welcome to our CRM (Customer Relationship Management) Project built with the MERN (MongoDB, Express.js, React.js, Node.js) stack. 4 | 5 | ## Overview 6 | 7 | This project implements a comprehensive CRM solution with role-based authentication, offering both Admin and User Dashboards for managing customer relationships, tickets, and tasks. 8 | 9 | ## Features 10 | 11 | ### Admin Dashboard 12 | 13 | - **User Management**: Create, edit, update, and delete user profiles. 14 | - **Ticket Management**: Manage ticket status (Pending, Approved, Resolved) for user-specific tickets. 15 | - **Task Assignment**: Create and manage tasks, assign tasks to users, edit, and delete tasks. 16 | - **Visualization**: Visual representation of user statistics, tickets, and tasks. 17 | 18 | - **User Status Management**: Admins can change the status of users between Active and Inactive. 19 | - Active users can log in and access their User Dashboard. 20 | - Inactive users are restricted from logging in and accessing their User Dashboard. 21 | 22 | ### User Dashboard 23 | 24 | - **Ticket Creation**: Create coding-related problem tickets. 25 | - **Ticket Submission**: Submit tickets for review and resolution by the admin. 26 | - **Task Submission**: Submit tasks assigned by the admin. 27 | - **Visualization**: Overview of submitted and pending tickets, tasks, and statistics. 28 | 29 | ### Authentication and Authorization 30 | 31 | - **Role-based Access**: Admins have exclusive access to the Admin Dashboard, while users can access their User Dashboard. 32 | - **Token-based Authentication**: Implementation of JWT for secure authentication and authorization. 33 | - **Password Management**: Forgot password, reset password functionality included. 34 | 35 | ## Installation 36 | 37 | To get started with the CRM Project: 38 | 39 | 1. Clone the repository: 40 | 41 | ```bash 42 | git clone https://github.com/HarishVinayagamoorthy/CRM-MERN 43 | 44 | 45 | 46 | - **Admin Email:** admin@gmail.com 47 | - **Admin Password:**@Password123 48 | 49 | - **User Email:** user@gmail.com 50 | - **User Password:**@Password123 51 | 52 | -------------------------------------------------------------------------------- /src/components/Dashboard/AdminDashboard/TicketAreaChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import { Bar } from "react-chartjs-2"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import { 5 | Chart, 6 | LinearScale, 7 | CategoryScale, 8 | BarController, 9 | BarElement, 10 | } from "chart.js/auto"; 11 | 12 | Chart.register(LinearScale, CategoryScale, BarController, BarElement); 13 | 14 | const TicketChart = () => { 15 | const chartRef = useRef(null); 16 | const [chartData, setChartData] = useState(null); 17 | 18 | useEffect(() => { 19 | const fetchData = async () => { 20 | try { 21 | const response = await AxiosService.get("/tickets/"); 22 | const data = response.data; 23 | 24 | if (chartRef.current) { 25 | // Update the existing chart data 26 | chartRef.current.data.labels = [ 27 | "Total Tickets", 28 | "Resolved Tickets", 29 | "Approved Tickets", 30 | "Pending Tickets", 31 | ]; 32 | chartRef.current.data.datasets[0].data = [ 33 | data.totalTickets, 34 | data.resolvedTickets, 35 | data.approvedTickets, 36 | 37 | data.pendingTickets, 38 | ]; 39 | chartRef.current.update(); 40 | } else { 41 | // Create a new chart 42 | const newChartData = new Chart("myChart", { 43 | type: "bar", 44 | data: { 45 | labels: [ 46 | "Total Tickets", 47 | "Approved Tickets", 48 | "Resolved Tickets", 49 | "Pending Tickets", 50 | ], 51 | datasets: [ 52 | { 53 | label: "Number of Tickets", 54 | backgroundColor: ["blue", "green", "orange", "red"], 55 | data: [ 56 | data.totalTickets, 57 | data.approvedTickets, 58 | data.resolvedTickets, 59 | data.pendingTickets, 60 | ], 61 | }, 62 | ], 63 | }, 64 | }); 65 | 66 | setChartData(newChartData); 67 | chartRef.current = newChartData; 68 | } 69 | } catch (error) { 70 | console.error("Error fetching data:", error); 71 | } 72 | }; 73 | 74 | fetchData(); 75 | }, []); // Empty dependency array to run once on mount 76 | 77 | return ( 78 |
79 | 80 |
81 | ); 82 | }; 83 | 84 | export default TicketChart; 85 | -------------------------------------------------------------------------------- /src/components/Dashboard/UserDashbooard/UserTicketsDashoard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import { Bar } from "react-chartjs-2"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import { 5 | Chart, 6 | LinearScale, 7 | CategoryScale, 8 | BarController, 9 | BarElement, 10 | } from "chart.js/auto"; 11 | 12 | Chart.register(LinearScale, CategoryScale, BarController, BarElement); 13 | 14 | const TicketChart = () => { 15 | const chartRef = useRef(null); 16 | const [chartData, setChartData] = useState(null); 17 | 18 | useEffect(() => { 19 | const fetchData = async () => { 20 | try { 21 | const response = await AxiosService.get("/tickets/user"); 22 | const data = response.data; 23 | 24 | if (chartRef.current) { 25 | // Update the existing chart data 26 | chartRef.current.data.labels = [ 27 | "Total Tickets", 28 | "Resolved Tickets", 29 | "Approved Tickets", 30 | "Pending Tickets", 31 | ]; 32 | chartRef.current.data.datasets[0].data = [ 33 | data.totalTickets, 34 | data.resolvedTickets, 35 | data.approvedTickets, 36 | 37 | data.pendingTickets, 38 | ]; 39 | chartRef.current.update(); 40 | } else { 41 | // Create a new chart 42 | const newChartData = new Chart("myChart", { 43 | type: "bar", 44 | data: { 45 | labels: [ 46 | "Total Tickets", 47 | "Approved Tickets", 48 | "Resolved Tickets", 49 | "Pending Tickets", 50 | ], 51 | datasets: [ 52 | { 53 | label: "Number of Tickets", 54 | backgroundColor: ["blue", "green", "orange", "red"], 55 | data: [ 56 | data.totalTickets, 57 | data.approvedTickets, 58 | data.resolvedTickets, 59 | data.pendingTickets, 60 | ], 61 | }, 62 | ], 63 | }, 64 | }); 65 | 66 | setChartData(newChartData); 67 | chartRef.current = newChartData; 68 | } 69 | } catch (error) { 70 | console.error("Error fetching data:", error); 71 | } 72 | }; 73 | 74 | fetchData(); 75 | }, []); // Empty dependency array to run once on mount 76 | 77 | return ( 78 |
79 | 80 |
81 | ); 82 | }; 83 | 84 | export default TicketChart; 85 | -------------------------------------------------------------------------------- /src/pages/Password/ForgotPassword.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useFormik } from "formik"; 3 | import * as Yup from "yup"; 4 | 5 | import AxiosService from "../../components/utils/ApiService"; 6 | 7 | import { toast } from "react-toastify"; 8 | import { useNavigate } from "react-router-dom"; 9 | import Spinner from "../../components/utils/Sipnners"; // Import your Spinner component 10 | 11 | import Forgotpassword from "../Password/frogot.module.css"; 12 | 13 | const ResetPassword = () => { 14 | const validationSchema = Yup.object().shape({ 15 | email: Yup.string().email("Invalid email").required("Email is required"), 16 | }); 17 | 18 | const navigate = useNavigate(); 19 | 20 | const handleCancel = () => { 21 | navigate("/"); 22 | }; 23 | 24 | const handleSubmit = async (values, { setSubmitting }) => { 25 | try { 26 | const response = await AxiosService.post("/user/resetpassword", values); 27 | toast.success(`OTP and Link Sent Successfully to ${values.email}`); 28 | navigate("/resetpassword"); 29 | console.log(response.data.message); 30 | } catch (error) { 31 | console.error(error.response.data.message); 32 | toast.error(error.response.data.message); 33 | } finally { 34 | setSubmitting(false); 35 | } 36 | }; 37 | 38 | const formik = useFormik({ 39 | initialValues: { 40 | email: "", 41 | }, 42 | validationSchema: validationSchema, 43 | onSubmit: handleSubmit, 44 | }); 45 | 46 | return ( 47 | <> 48 |
49 |
50 |
51 |
52 |
53 |
57 |

Welcome back!

58 |

Forgot Password

59 | 71 | {formik.touched.email && formik.errors.email && ( 72 |

{formik.errors.email}

73 | )} 74 | 75 | 78 | 81 |
82 |
83 | 84 | ); 85 | }; 86 | 87 | export default ResetPassword; 88 | -------------------------------------------------------------------------------- /src/pages/Signup/signup.module.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap"); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | ::placeholder { 8 | /* Chrome, Firefox, Opera, Safari 10.1+ */ 9 | color: #fff; 10 | opacity: 1; /* Firefox */ 11 | } 12 | 13 | .totalbody { 14 | padding: 0; 15 | margin: 0; 16 | background-color: #03080e; 17 | /*background: url('./bg3.jpg') no-repeat 49% 76%;*/ 18 | /*background-size: cover;*/ 19 | height: 100vh; 20 | 21 | display: flex; 22 | justify-content: center; 23 | align-items: center; 24 | font-family: poppins; 25 | position: relative; 26 | } 27 | 28 | .circles { 29 | width: 400px; 30 | height: 400px; 31 | margin: auto; 32 | position: absolute; 33 | top: 0; 34 | left: 0; 35 | right: 0; 36 | bottom: 0; 37 | } 38 | 39 | .circle1 { 40 | width: 300px; 41 | height: 300px; 42 | background: linear-gradient(45deg, #ff0099, #7a0ed6); 43 | border-radius: 50%; 44 | position: absolute; 45 | top: -100px; 46 | right: -155px; 47 | } 48 | 49 | .circle2 { 50 | width: 200px; 51 | height: 200px; 52 | background: linear-gradient(45deg, #ff237b, #f64838); 53 | border-radius: 50%; 54 | position: absolute; 55 | bottom: -90px; 56 | left: -70px; 57 | } 58 | 59 | .login_form { 60 | display: flex; 61 | flex-direction: column; 62 | color: #fff; 63 | padding: 40px 26px; 64 | width: 400px; 65 | /* height: auto; */ 66 | background-color: rgba(255, 255, 255, 0.2); 67 | backdrop-filter: blur(8px); 68 | border: 1px solid rgba(255, 255, 255, 0.15); 69 | border-radius: 10px; 70 | box-shadow: 0 20px 40px rgba(0, 0, 0, 0.18); 71 | } 72 | 73 | .login_form h1 { 74 | font-size: 25px; 75 | margin-top: 0; 76 | margin-bottom: 8px; 77 | } 78 | 79 | .login_form p { 80 | margin-top: 0; 81 | margin-bottom: 26px; 82 | } 83 | 84 | .login_form input { 85 | background: transparent; 86 | color: #fff; 87 | border: 1px solid rgba(255, 255, 255, 0.2); 88 | border-radius: 6px; 89 | padding: 14px 16px; 90 | margin-bottom: 30px; 91 | } 92 | 93 | .login_form input:focus { 94 | outline: none; 95 | border-color: #fff; 96 | } 97 | 98 | .login_form button { 99 | background: linear-gradient(45deg, #ff0d45, #ff01eb); 100 | color: #fff; 101 | border: none; 102 | border-radius: 6px; 103 | padding: 14px 16px; 104 | margin-top: 10px; 105 | font-size: 16px; 106 | font-weight: bold; 107 | } 108 | 109 | /*.login_form button:focus { 110 | outline: none; 111 | box-shadow: 0 0 15px #ff0d45; 112 | }*/ 113 | 114 | .signuplink { 115 | color: #fff !important; 116 | text-decoration: none !important; 117 | margin-top: 10px; 118 | font-size: smaller; 119 | } 120 | .signuptext { 121 | color: aliceblue; 122 | font-size: smaller; 123 | } 124 | -------------------------------------------------------------------------------- /src/components/Dashboard/UserDashbooard/UserAreachart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import AxiosService from "../../utils/ApiService"; 3 | import { Card } from "react-bootstrap"; 4 | import { useSpring, animated } from "react-spring"; 5 | import { Line } from "react-chartjs-2"; 6 | import styles from "../AdminDashboard/TaskDashboard.module.css"; 7 | 8 | const TaskDashboard = () => { 9 | const [dashboardData, setDashboardData] = useState({ 10 | totalTasks: 0, 11 | pendingTasks: 0, 12 | submittedTasks: 0, 13 | }); 14 | 15 | const animatedTotalTasks = useSpring({ 16 | value: dashboardData.totalTasks, 17 | from: { value: 0 }, 18 | }); 19 | 20 | useEffect(() => { 21 | const fetchData = async () => { 22 | try { 23 | const response = await AxiosService.get("/task/user"); 24 | const taskData = response.data; 25 | 26 | // Calculate total, pending, and submitted tasks based on the fetched data 27 | const totalTasks = taskData.length; 28 | const pendingTasks = taskData.filter( 29 | (task) => task.status === "Pending" 30 | ).length; 31 | const submittedTasks = taskData.filter( 32 | (task) => task.status === "Submitted" 33 | ).length; 34 | 35 | setDashboardData({ 36 | totalTasks, 37 | pendingTasks, 38 | submittedTasks, 39 | }); 40 | } catch (error) { 41 | console.error("Error fetching dashboard data:", error); 42 | } 43 | }; 44 | 45 | fetchData(); 46 | }, []); 47 | 48 | const chartData = { 49 | labels: ["Total Tasks", "Pending Tasks", "Submitted Tasks"], 50 | datasets: [ 51 | { 52 | label: "Tasks", 53 | data: [ 54 | dashboardData.totalTasks, 55 | dashboardData.pendingTasks, 56 | dashboardData.submittedTasks, 57 | ], 58 | backgroundColor: [ 59 | "rgba(75, 192, 192, 0.2)", 60 | "rgba(255, 99, 132, 0.2)", 61 | "rgba(54, 162, 235, 0.2)", 62 | ], 63 | borderColor: [ 64 | "rgba(75, 192, 192, 1)", 65 | "rgba(255, 99, 132, 1)", 66 | "rgba(54, 162, 235, 1)", 67 | ], 68 | borderWidth: 1, 69 | }, 70 | ], 71 | }; 72 | 73 | const chartOptions = { 74 | scales: { 75 | y: { 76 | beginAtZero: true, 77 | }, 78 | }, 79 | }; 80 | 81 | return ( 82 |
83 | 84 | 85 | {/* Chart for Tasks */} 86 | 87 | 88 | Tasks Chart 89 | 90 | 91 | 92 |
93 | ); 94 | }; 95 | 96 | export default TaskDashboard; 97 | -------------------------------------------------------------------------------- /src/pages/Password/frogot.module.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap"); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | ::placeholder { 8 | /* Chrome, Firefox, Opera, Safari 10.1+ */ 9 | color: #fff; 10 | opacity: 1; /* Firefox */ 11 | } 12 | 13 | .totalbody { 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | min-height: 100vh; 18 | width: 100%; 19 | padding: 0 10px; 20 | z-index: -10; 21 | /* background: linear-gradient(45deg, rgba(213, 15, 61, 0.6), rgba(13, 17, 198, 0.69) 100%); */ 22 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 23 | } 24 | 25 | .circles { 26 | width: 400px; 27 | height: 400px; 28 | margin: auto; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | z-index: 0; 35 | } 36 | 37 | .circle1 { 38 | width: 300px; 39 | height: 300px; 40 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 41 | z-index: -100; 42 | 43 | border-radius: 50%; 44 | position: absolute; 45 | top: -100px; 46 | right: -155px; 47 | } 48 | 49 | .circle2 { 50 | width: 200px; 51 | height: 200px; 52 | border-radius: 50%; 53 | position: absolute; 54 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 55 | z-index: -100; 56 | 57 | bottom: -90px; 58 | left: -70px; 59 | } 60 | 61 | .login_form { 62 | display: flex; 63 | flex-direction: column; 64 | color: #fff; 65 | padding: 40px 26px; 66 | width: 300px; 67 | /*height: 300px;*/ 68 | background-color: rgba(255, 255, 255, 0.2); 69 | backdrop-filter: blur(8px); 70 | border: 1px solid rgba(255, 255, 255, 0.15); 71 | border-radius: 10px; 72 | box-shadow: 0 20px 40px rgba(0, 0, 0, 0.18); 73 | } 74 | 75 | .login_form h1 { 76 | font-size: 25px; 77 | margin-top: 0; 78 | margin-bottom: 8px; 79 | } 80 | 81 | .login_form p { 82 | margin-top: 0; 83 | margin-bottom: 26px; 84 | } 85 | 86 | .login_form input { 87 | background: transparent; 88 | color: #fff; 89 | border: 1px solid rgba(255, 255, 255, 0.2); 90 | border-radius: 6px; 91 | padding: 14px 16px; 92 | margin-bottom: 30px; 93 | } 94 | 95 | .login_form input:focus { 96 | outline: none; 97 | border-color: #fff; 98 | } 99 | 100 | .login_form button { 101 | background: #fff; 102 | color: black; 103 | border: none; 104 | border-radius: 6px; 105 | padding: 14px 16px; 106 | margin-top: 10px; 107 | font-size: 16px; 108 | font-weight: bold; 109 | } 110 | 111 | /*.login_form button:focus { 112 | outline: none; 113 | box-shadow: 0 0 15px #ff0d45; 114 | }*/ 115 | 116 | .signuplink { 117 | color: #fff !important; 118 | text-decoration: none !important; 119 | margin-top: 10px; 120 | font-size: smaller; 121 | } 122 | .signuptext { 123 | color: aliceblue; 124 | font-size: smaller; 125 | } 126 | 127 | .login_form button:hover { 128 | color: #fff; 129 | border-color: #fff; 130 | background: rgba(255, 255, 255, 0.15); 131 | } 132 | -------------------------------------------------------------------------------- /src/components/Dashboard/Dashboard Cards/ApprovedTickets.jsx: -------------------------------------------------------------------------------- 1 | // AllTickets.js 2 | import React, { useState, useEffect } from "react"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import "bootstrap/dist/css/bootstrap.min.css"; // Import Bootstrap CSS 5 | import { IoArrowBack } from "react-icons/io5"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | const AllTickets = () => { 9 | const [approvedTickets, setApprovedTickets] = useState([]); 10 | const userData = JSON.parse(sessionStorage.getItem("userData")); 11 | let navigate = useNavigate() 12 | 13 | useEffect(() => { 14 | const fetchData = async () => { 15 | try { 16 | const url = userData.role === "admin" ? "/tickets/" : "/tickets/user"; 17 | 18 | const response = await AxiosService.get(url); 19 | const { tickets } = response.data; 20 | 21 | // Filter only approved tickets 22 | const approvedTickets = tickets.filter( 23 | (ticket) => ticket.status === "approved" 24 | ); 25 | if (response.status === 200) { 26 | setApprovedTickets(approvedTickets); 27 | } 28 | } catch (error) { 29 | console.error("Error fetching tickets:", error); 30 | } 31 | }; 32 | 33 | fetchData(); 34 | }, []); 35 | 36 | 37 | const handelBackbutton =()=>{ 38 | navigate(-1) 39 | } 40 | const getStatusBadgeColor = (status) => { 41 | switch (status) { 42 | case "resolved": 43 | return "badge bg-success"; // Green color for resolved status 44 | case "approved": 45 | return "badge bg-warning text-dark"; // Yellow color for approved status 46 | case "pending": 47 | return "badge bg-danger"; // Red color for pending status 48 | default: 49 | return "badge bg-secondary"; // Use a default color for other statuses 50 | } 51 | }; 52 | 53 | return ( 54 |
55 | {/* The 'overflow-auto' class adds both horizontal and vertical scrolling if needed */} 56 | 57 | 58 |

Approved Tickets

59 |

60 | Total Approved Tickets: {approvedTickets.length} 61 |

62 | 63 |
67 |
    68 | {approvedTickets.map((ticket) => ( 69 |
  • 70 |

    71 | Title: {ticket.title} 72 |

    73 |

    74 | Status:{" "} 75 | 76 | {ticket.status} 77 | 78 |

    79 | {/* Add other ticket details you want to display */} 80 |
  • 81 | ))} 82 |
83 |
84 |
85 | ); 86 | }; 87 | 88 | export default AllTickets; 89 | -------------------------------------------------------------------------------- /src/pages/Password/resetPassword.module.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap"); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | ::placeholder { 8 | /* Chrome, Firefox, Opera, Safari 10.1+ */ 9 | color: #fff; 10 | opacity: 1; /* Firefox */ 11 | } 12 | 13 | .totalbody { 14 | display: flex; 15 | align-items: center; 16 | justify-content: center; 17 | min-height: 100vh; 18 | width: 100%; 19 | padding: 0 10px; 20 | z-index: -10; 21 | /* background: linear-gradient(45deg, rgba(213, 15, 61, 0.6), rgba(13, 17, 198, 0.69) 100%); */ 22 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 23 | } 24 | 25 | .circles { 26 | width: 400px; 27 | height: 400px; 28 | margin: auto; 29 | position: absolute; 30 | top: 0; 31 | left: 0; 32 | right: 0; 33 | bottom: 0; 34 | } 35 | 36 | .circle1 { 37 | width: 300px; 38 | height: 300px; 39 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 40 | 41 | border-radius: 50%; 42 | position: absolute; 43 | top: -100px; 44 | right: -155px; 45 | } 46 | 47 | .circle2 { 48 | width: 200px; 49 | height: 200px; 50 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 51 | 52 | border-radius: 50%; 53 | position: absolute; 54 | bottom: -90px; 55 | left: -70px; 56 | } 57 | 58 | .login_form { 59 | display: flex; 60 | flex-direction: column; 61 | color: #fff; 62 | padding: 40px 26px; 63 | width: 400px; 64 | /* height: auto; */ 65 | background-color: rgba(255, 255, 255, 0.2); 66 | backdrop-filter: blur(8px); 67 | border: 1px solid rgba(255, 255, 255, 0.15); 68 | border-radius: 10px; 69 | box-shadow: 0 20px 40px rgba(0, 0, 0, 0.18); 70 | } 71 | 72 | .login_form h1 { 73 | font-size: 25px; 74 | margin-top: 0; 75 | margin-bottom: 8px; 76 | } 77 | 78 | .login_form p { 79 | margin-top: 0; 80 | margin-bottom: 26px; 81 | } 82 | 83 | .login_form input { 84 | background: transparent; 85 | color: #fff; 86 | border: 1px solid rgba(255, 255, 255, 0.2); 87 | border-radius: 6px; 88 | padding: 14px 16px; 89 | margin-bottom: 30px; 90 | } 91 | 92 | .login_form input:focus { 93 | outline: none; 94 | border-color: #fff; 95 | } 96 | 97 | .login_form button { 98 | background: #fff; 99 | color: black; 100 | border: none; 101 | border-radius: 6px; 102 | padding: 14px 16px; 103 | margin-top: 10px; 104 | font-size: 16px; 105 | font-weight: bold; 106 | } 107 | 108 | /*.login_form button:focus { 109 | outline: none; 110 | box-shadow: 0 0 15px #ff0d45; 111 | }*/ 112 | 113 | .signuplink { 114 | color: #fff !important; 115 | text-decoration: none !important; 116 | margin-top: 10px; 117 | font-size: smaller; 118 | } 119 | .signuptext { 120 | color: aliceblue; 121 | font-size: smaller; 122 | } 123 | 124 | .login_form button:hover { 125 | color: #fff; 126 | border-color: #fff; 127 | background: rgba(255, 255, 255, 0.15); 128 | } 129 | 130 | 131 | /* Add these styles to your resetPassword.module.css file or your preferred stylesheet */ 132 | 133 | 134 | .passwordIcon{ 135 | background-color: transparent; 136 | } -------------------------------------------------------------------------------- /src/components/Dashboard/AdminDashboard/TaskDashboard.jsx: -------------------------------------------------------------------------------- 1 | // TaskDashboard.js 2 | import React, { useState, useEffect } from "react"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import { ProgressBar, Card } from "react-bootstrap"; 5 | import { useSpring, animated } from "react-spring"; 6 | import styles from "./TaskDashboard.module.css"; // Import your custom styles 7 | const TaskDashboard = () => { 8 | const [dashboardData, setDashboardData] = useState({ 9 | totalTasks: 0, 10 | pendingTasks: 0, 11 | submittedTasks: 0, 12 | tasks: [], 13 | }); 14 | 15 | const animatedTotalTasks = useSpring({ 16 | value: dashboardData.totalTasks, 17 | from: { value: 0 }, 18 | }); 19 | const animatedPendingTasks = useSpring({ 20 | value: dashboardData.pendingTasks, 21 | from: { value: 0 }, 22 | }); 23 | const animatedSubmittedTasks = useSpring({ 24 | value: dashboardData.submittedTasks, 25 | from: { value: 0 }, 26 | }); 27 | 28 | useEffect(() => { 29 | const fetchData = async () => { 30 | try { 31 | const response = await AxiosService.get("/task/tasks"); 32 | setDashboardData(response.data); 33 | } catch (error) { 34 | console.error("Error fetching dashboard data:", error); 35 | } 36 | }; 37 | 38 | fetchData(); 39 | }, []); 40 | 41 | // Calculate the percentage of pending and submitted tasks 42 | const pendingPercentage = 43 | Math.round((dashboardData.pendingTasks / dashboardData.totalTasks) * 100) || 44 | 0; 45 | const submittedPercentage = 46 | Math.round( 47 | (dashboardData.submittedTasks / dashboardData.totalTasks) * 100 48 | ) || 0; 49 | 50 | return ( 51 |
52 | 53 | 54 | 55 | Total Tasks 56 | 57 | {animatedTotalTasks.value.interpolate((val) => val.toFixed(0))} 58 | 59 | 60 | 61 | 62 | {/* Progress bar for Pending Tasks */} 63 | 64 | 65 | Pending Tasks 66 | 67 | {animatedPendingTasks.value.interpolate((val) => val.toFixed(0))} 68 | 69 | 74 | 75 | 76 | 77 | {/* Progress bar for Submitted Tasks */} 78 | 79 | 80 | Submitted Tasks 81 | 82 | {animatedSubmittedTasks.value.interpolate((val) => val.toFixed(0))} 83 | 84 | 89 | 90 | 91 |
92 | ); 93 | }; 94 | 95 | export default TaskDashboard; 96 | -------------------------------------------------------------------------------- /src/components/slider/Slider.module.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"); 2 | /* * { 3 | margin: 0; 4 | padding: 0; 5 | box-sizing: border-box; 6 | font-family: "Poppins", sans-serif; 7 | } */ 8 | .Slider { 9 | height: 100vh; 10 | width: 100%; 11 | background-image: url("images/hero-bg.jpg"); 12 | background-position: center; 13 | background-size: cover; 14 | z-index: 100; 15 | } 16 | .container, 17 | .container-fluid, 18 | .container-lg, 19 | .container-md, 20 | .container-sm, 21 | .container-xl, 22 | .container-xxl { 23 | --bs-gutter-x: 0; 24 | --bs-gutter-y: 0; 25 | width: 87% !important; 26 | padding-right: calc(var(--bs-gutter-x) * 1.5rem); 27 | padding-left: calc(var(--bs-gutter-x) * 0.5); 28 | margin-right: auto; 29 | margin-left: auto; 30 | } 31 | .slider { 32 | position: fixed; 33 | top: 0; 34 | left: 0; 35 | width: 110px; 36 | height: 100%; 37 | display: flex; 38 | align-items: center; 39 | flex-direction: column; 40 | background: rgba(255, 255, 255, 0.2); 41 | backdrop-filter: blur(17px); 42 | --webkit-backdrop-filter: blur(17px); 43 | border-right: 1px solid rgba(255, 255, 255, 0.7); 44 | transition: width 0.3s ease; 45 | z-index: 100; 46 | } 47 | .sidebar:hover { 48 | width: 260px; 49 | } 50 | .sidebar .logo { 51 | color: #000; 52 | display: flex; 53 | align-items: center; 54 | padding: 25px 10px 15px; 55 | } 56 | .logo img { 57 | width: 43px; 58 | border-radius: 50%; 59 | } 60 | .logo h2 { 61 | font-size: 1.15rem; 62 | font-weight: 600; 63 | margin-left: 15px; 64 | display: none; 65 | } 66 | .sidebar:hover .logo h2 { 67 | display: block; 68 | } 69 | .sidebar .links { 70 | list-style: none; 71 | margin-top: 20px; 72 | overflow-y: auto; 73 | scrollbar-width: none; 74 | height: calc(100% - 140px); 75 | } 76 | .sidebar .links::-webkit-scrollbar { 77 | display: none; 78 | } 79 | .links li { 80 | display: flex; 81 | border-radius: 4px; 82 | align-items: center; 83 | } 84 | .links li:hover { 85 | cursor: pointer; 86 | background: #fff; 87 | } 88 | .links h4 { 89 | color: #222; 90 | font-weight: 500; 91 | display: none; 92 | margin-bottom: 10px; 93 | } 94 | .sidebar:hover .links h4 { 95 | display: block; 96 | } 97 | .links hr { 98 | margin: 10px 8px; 99 | border: 1px solid #4c4c4c; 100 | } 101 | .sidebar:hover .links hr { 102 | border-color: transparent; 103 | } 104 | .links li span { 105 | padding: 12px 10px; 106 | } 107 | .links li a { 108 | padding: 10px; 109 | color: #000; 110 | display: none; 111 | font-weight: 500; 112 | white-space: nowrap; 113 | text-decoration: none; 114 | } 115 | .sidebar:hover .links li a { 116 | display: block; 117 | } 118 | .links .logoutlink { 119 | margin-top: 20px; 120 | } 121 | 122 | /* @media screen and (max-width: 1200px) { 123 | /* Adjust styles for even smaller screens */ 124 | /* .sidebar { 125 | width: 100%; 126 | } 127 | 128 | .links { 129 | padding-left: 15px; 130 | padding-right: 15px; 131 | } 132 | 133 | .menu-title { 134 | display: none; 135 | } 136 | 137 | .separator { 138 | margin: 10px 0; 139 | border: 1px solid #ddd; 140 | } 141 | } 142 | */ 143 | -------------------------------------------------------------------------------- /src/pages/Tickets/Create.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Button from "react-bootstrap/Button"; 3 | import Form from "react-bootstrap/Form"; 4 | import TicketTile from "../../components/common/TicketTile"; 5 | import AxiosService from "../../components/utils/ApiService"; 6 | import { useNavigate } from "react-router-dom"; 7 | import useLogout from "../../components/hooks/useLogout"; 8 | import { toast } from "react-toastify"; 9 | 10 | function Create() { 11 | const [title, setTitle] = useState(""); 12 | const [imageUrl, setImage] = useState(""); 13 | const [description, setDescription] = useState(""); 14 | const [loading, setLoading] = useState(false); // New state for loading 15 | const navigate = useNavigate(); 16 | const logout = useLogout(); 17 | 18 | const createTicket = async () => { 19 | try { 20 | setLoading(true); // Set loading to true before the API call 21 | 22 | let res = await AxiosService.post("/tickets/create", { 23 | title, 24 | imageUrl, 25 | description, 26 | }); 27 | 28 | if (res.status === 201) { 29 | toast.success(res.data.message); 30 | navigate("/tickets"); 31 | } 32 | } catch (error) { 33 | toast.error(error.response.data.message); 34 | 35 | if (error.response.status === 401) { 36 | logout(); 37 | } 38 | } finally { 39 | setLoading(false); // Set loading to false after the API call 40 | } 41 | }; 42 | 43 | return ( 44 |
45 |
46 |

Share Your Tickets!

47 |
48 | 49 | Title 50 | setTitle(e.target.value)} 55 | /> 56 | 57 | 58 | 59 | Image Url 60 | setImage(e.target.value)} 65 | /> 66 | 67 | 68 | 69 | Description 70 | setDescription(e.target.value)} 76 | /> 77 | 78 | 79 |
80 | 95 |
96 |
97 |
98 |
99 | ); 100 | } 101 | 102 | export default Create; 103 | -------------------------------------------------------------------------------- /src/components/Dashboard/Dashboard Cards/TicketsCard.jsx: -------------------------------------------------------------------------------- 1 | // AllTickets.js 2 | import React, { useState, useEffect } from "react"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import "bootstrap/dist/css/bootstrap.min.css"; // Import Bootstrap CSS 5 | import { IoArrowBack } from "react-icons/io5"; 6 | import { useNavigate } from "react-router-dom"; 7 | const AllTickets = () => { 8 | const userData = JSON.parse(sessionStorage.getItem("userData")); 9 | 10 | const [allTickets, setAllTickets] = useState([]); 11 | const [loading, setLoading] = useState(true); 12 | let navigate = useNavigate() 13 | useEffect(() => { 14 | const fetchData = async () => { 15 | try { 16 | const url = userData.role === "admin" ? "/tickets/" : "/tickets/user"; 17 | const response = await AxiosService.get(url); 18 | if (response.status === 200) { 19 | const { tickets } = response.data; 20 | // Set all tickets without filtering 21 | setAllTickets(tickets); 22 | setLoading(false); 23 | } 24 | 25 | // Set loading to false when data is fetched 26 | } catch (error) { 27 | console.error("Error fetching tickets:", error); 28 | setLoading(false); // Set loading to false on error 29 | } 30 | }; 31 | 32 | fetchData(); 33 | }, []); 34 | 35 | const handelBackbutton =()=>{ 36 | navigate(-1) 37 | } 38 | const getStatusBadgeColor = (status) => { 39 | switch (status) { 40 | case "resolved": 41 | return "badge bg-success"; // Green color for resolved status 42 | case "approved": 43 | return "badge bg-warning text-dark"; // Yellow color for approved status 44 | case "pending": 45 | return "badge bg-danger"; // Red color for pending status 46 | default: 47 | return "badge bg-secondary"; // Use a default color for other statuses 48 | } 49 | }; 50 | 51 | return ( 52 |
53 | {/* The 'overflow-auto' class adds both horizontal and vertical scrolling if needed */} 54 | 55 |

All Tickets

56 | {loading ? ( 57 |
61 |
62 | Loading... 63 |
64 |
65 | ) : ( 66 | <> 67 |

Total Tickets: {allTickets.length}

68 |
72 |
    73 | {allTickets.map((ticket) => ( 74 |
  • 75 |

    76 | Title: {ticket.title} 77 |

    78 |

    79 | Status:{" "} 80 | 81 | {ticket.status} 82 | 83 |

    84 | {/* Add other ticket details you want to display */} 85 |
  • 86 | ))} 87 |
88 |
89 | 90 | )} 91 |
92 | ); 93 | }; 94 | 95 | export default AllTickets; 96 | -------------------------------------------------------------------------------- /src/components/Dashboard/Dashboard Cards/PendingTickets.jsx: -------------------------------------------------------------------------------- 1 | // AllTickets.js 2 | import React, { useState, useEffect } from "react"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import "bootstrap/dist/css/bootstrap.min.css"; // Import Bootstrap CSS 5 | import { IoArrowBack } from "react-icons/io5"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | const AllTickets = () => { 9 | const [pendingTickets, setPendingTickets] = useState([]); 10 | const [loading, setLoading] = useState(true); 11 | const userData = JSON.parse(sessionStorage.getItem("userData")); 12 | let navigate = useNavigate() 13 | 14 | 15 | 16 | 17 | useEffect(() => { 18 | const fetchData = async () => { 19 | try { 20 | const url = userData.role === "admin" ? "/tickets/" : "/tickets/user"; 21 | 22 | const response = await AxiosService.get(url); 23 | const { tickets } = response.data; 24 | 25 | // Filter only pending tickets 26 | const pendingTickets = tickets.filter( 27 | (ticket) => ticket.status === "pending" 28 | ); 29 | if (response.status === 200) { 30 | setPendingTickets(pendingTickets); 31 | setLoading(false); // Set loading to false when data is fetched 32 | } 33 | } catch (error) { 34 | console.error("Error fetching tickets:", error); 35 | setLoading(false); // Set loading to false on error 36 | } 37 | }; 38 | 39 | fetchData(); 40 | }, []); 41 | 42 | const handelBackbutton =()=>{ 43 | navigate(-1) 44 | } 45 | 46 | const getStatusBadgeColor = (status) => { 47 | switch (status) { 48 | case "resolved": 49 | return "badge bg-success"; // Green color for resolved status 50 | case "approved": 51 | return "badge bg-warning text-dark"; // Yellow color for approved status 52 | case "pending": 53 | return "badge bg-danger"; // Red color for pending status 54 | default: 55 | return "badge bg-secondary"; // Use a default color for other statuses 56 | } 57 | }; 58 | 59 | return ( 60 |
61 | {/* The 'overflow-auto' class adds both horizontal and vertical scrolling if needed */} 62 | 63 | 64 |

Pending Tickets

65 | {loading ? ( 66 |
70 |
71 | Loading... 72 |
73 |
74 | ) : ( 75 | <> 76 |

77 | Total Pending Tickets: {pendingTickets.length} 78 |

79 |
83 |
    84 | {pendingTickets.map((ticket) => ( 85 |
  • 86 |

    87 | Title: {ticket.title} 88 |

    89 |

    90 | Status:{" "} 91 | 92 | {ticket.status} 93 | 94 |

    95 | {/* Add other ticket details you want to display */} 96 |
  • 97 | ))} 98 |
99 |
100 | 101 | )} 102 |
103 | ); 104 | }; 105 | 106 | export default AllTickets; 107 | -------------------------------------------------------------------------------- /src/components/Dashboard/Dashboard Cards/RessolvedTickets.jsx: -------------------------------------------------------------------------------- 1 | // AllTickets.js 2 | import React, { useState, useEffect } from "react"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import "bootstrap/dist/css/bootstrap.min.css"; // Import Bootstrap CSS 5 | import { IoArrowBack } from "react-icons/io5"; 6 | import { useNavigate } from "react-router-dom"; 7 | 8 | 9 | const AllTickets = () => { 10 | const [resolvedTickets, setResolvedTickets] = useState([]); 11 | const [loading, setLoading] = useState(true); 12 | const userData = JSON.parse(sessionStorage.getItem("userData")); 13 | let navigate = useNavigate() 14 | 15 | useEffect(() => { 16 | const fetchData = async () => { 17 | try { 18 | const url = userData.role === "admin" ? "/tickets/" : "/tickets/user"; 19 | 20 | const response = await AxiosService.get(url); 21 | const { tickets } = response.data; 22 | 23 | // Filter only resolved tickets 24 | const resolvedTickets = tickets.filter( 25 | (ticket) => ticket.status === "resolved" 26 | ); 27 | if (response.status === 200) { 28 | setResolvedTickets(resolvedTickets); 29 | setLoading(false); 30 | } 31 | // Set loading to false when data is fetched 32 | } catch (error) { 33 | console.error("Error fetching tickets:", error); 34 | setLoading(false); // Set loading to false on error 35 | } 36 | }; 37 | 38 | fetchData(); 39 | }, []); 40 | 41 | const getStatusBadgeColor = (status) => { 42 | switch (status) { 43 | case "resolved": 44 | return "badge bg-success"; // Green color for resolved status 45 | case "approved": 46 | return "badge bg-warning text-dark"; // Yellow color for approved status 47 | case "pending": 48 | return "badge bg-danger"; // Red color for pending status 49 | default: 50 | return "badge bg-secondary"; // Use a default color for other statuses 51 | } 52 | }; 53 | const handelBackbutton =()=>{ 54 | navigate(-1) 55 | } 56 | 57 | return ( 58 |
59 | {/* The 'overflow-auto' class adds both horizontal and vertical scrolling if needed */} 60 | 61 |

Resolved Tickets

62 | {loading ? ( 63 |
67 |
68 | Loading... 69 |
70 |
71 | ) : ( 72 | <> 73 |

74 | Total Resolved Tickets: {resolvedTickets.length} 75 |

76 |
80 |
    81 | {resolvedTickets.map((ticket) => ( 82 |
  • 83 |

    84 | Title: {ticket.title} 85 |

    86 |

    87 | Status:{" "} 88 | 89 | {ticket.status} 90 | 91 |

    92 | {/* Add other ticket details you want to display */} 93 |
  • 94 | ))} 95 |
96 |
97 | 98 | )} 99 |
100 | ); 101 | }; 102 | 103 | export default AllTickets; 104 | -------------------------------------------------------------------------------- /src/components/Dashboard/UserDashbooard/UserTaskDashboard.jsx: -------------------------------------------------------------------------------- 1 | // TaskDashboard.js 2 | import React, { useState, useEffect } from "react"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import { ProgressBar, Card } from "react-bootstrap"; 5 | import { useSpring, animated } from "react-spring"; 6 | import styles from "../AdminDashboard/TaskDashboard.module.css"; 7 | 8 | const TaskDashboard = () => { 9 | const [dashboardData, setDashboardData] = useState({ 10 | totalTasks: 0, 11 | pendingTasks: 0, 12 | submittedTasks: 0, 13 | }); 14 | 15 | const animatedTotalTasks = useSpring({ 16 | value: dashboardData.totalTasks, 17 | from: { value: 0 }, 18 | }); 19 | const animatedPendingTasks = useSpring({ 20 | value: dashboardData.pendingTasks, 21 | from: { value: 0 }, 22 | }); 23 | const animatedSubmittedTasks = useSpring({ 24 | value: dashboardData.submittedTasks, 25 | from: { value: 0 }, 26 | }); 27 | 28 | useEffect(() => { 29 | const fetchData = async () => { 30 | try { 31 | const response = await AxiosService.get("/task/user"); 32 | const taskData = response.data; 33 | console.log(response.data); 34 | 35 | // Calculate total, pending, and submitted tasks based on the fetched data 36 | const totalTasks = taskData.length; 37 | const pendingTasks = taskData.filter( 38 | (task) => task.status === "Pending" 39 | ).length; 40 | const submittedTasks = taskData.filter( 41 | (task) => task.status === "Submitted" 42 | ).length; 43 | 44 | setDashboardData({ 45 | totalTasks, 46 | pendingTasks, 47 | submittedTasks, 48 | }); 49 | } catch (error) { 50 | console.error("Error fetching dashboard data:", error); 51 | } 52 | }; 53 | 54 | fetchData(); 55 | }, []); 56 | 57 | // Calculate the percentage of pending and submitted tasks 58 | const pendingPercentage = 59 | Math.round((dashboardData.pendingTasks / dashboardData.totalTasks) * 100) || 60 | 0; 61 | const submittedPercentage = 62 | Math.round( 63 | (dashboardData.submittedTasks / dashboardData.totalTasks) * 100 64 | ) || 0; 65 | 66 | return ( 67 |
68 | 69 | 70 | 71 | 72 | Total Tasks 73 | 74 | {animatedTotalTasks.value.interpolate((val) => val.toFixed(0))} 75 | 76 | 77 | 78 | 79 | {/* Progress bar for Pending Tasks */} 80 | 81 | 82 | Pending Tasks 83 | 84 | {animatedPendingTasks.value.interpolate((val) => val.toFixed(0))} 85 | 86 | 91 | 92 | 93 | 94 | {/* Progress bar for Submitted Tasks */} 95 | 96 | 97 | Submitted Tasks 98 | 99 | {animatedSubmittedTasks.value.interpolate((val) => val.toFixed(0))} 100 | 101 | 106 | 107 | 108 |
109 | ); 110 | }; 111 | 112 | export default TaskDashboard; 113 | -------------------------------------------------------------------------------- /src/components/slider/Slider.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import useLogout from "../hooks/useLogout"; 3 | import { useEffect, useState } from "react"; 4 | import { 5 | CDBSidebar, 6 | CDBSidebarContent, 7 | CDBSidebarFooter, 8 | CDBSidebarHeader, 9 | CDBSidebarMenu, 10 | CDBSidebarMenuItem, 11 | } from "cdbreact"; 12 | import { FaTicketAlt, FaTasks, FaUser, FaCog } from "react-icons/fa"; 13 | 14 | import { TbLogout2 } from "react-icons/tb"; 15 | import { FaUserCircle } from "react-icons/fa"; 16 | 17 | import { NavLink } from "react-router-dom"; 18 | 19 | const Slider = () => { 20 | let userData = JSON.parse(sessionStorage.getItem("userData")); 21 | let [role, setRole] = useState(""); 22 | let logout = useLogout(); 23 | 24 | useEffect(() => { 25 | if (!userData) { 26 | logout(); 27 | } else { 28 | setRole(userData.role); 29 | } 30 | }, []); 31 | 32 | const iconMap = { 33 | ticket: FaTicketAlt, 34 | task: FaTasks, 35 | }; 36 | 37 | return ( 38 |
41 | 42 | }> 43 | 48 | 49 | {` ${userData.name} `} 50 | 51 | 52 | 53 | 54 | 55 | {role === "admin" ? : } 56 | 57 | 58 | 59 | 60 |
66 | Logout 67 |
68 |
69 |
70 |
71 | ); 72 | }; 73 | 74 | function AdminNavLinks() { 75 | // let navigate = useNavigate() 76 | let logout = useLogout(); 77 | return ( 78 | <> 79 | 80 | Dashboard 81 | 82 | 83 | User Data 84 | 85 | 86 | Create User 87 | 88 | 89 | Tickets 90 | 91 | 92 | Create Task 93 | 94 | 95 | Tasks List 96 | 97 | 98 | ); 99 | } 100 | 101 | function UserNavLinks() { 102 | // let navigate = useNavigate() 103 | let logout = useLogout(); 104 | return ( 105 | <> 106 | 107 | Dashboard 108 | 109 | 110 | Task 111 | 112 | 113 | Create Tickets 114 | 115 | 116 | Tickets 117 | 118 | 119 | Submited Task 120 | 121 | 122 | ); 123 | } 124 | export default Slider; 125 | -------------------------------------------------------------------------------- /src/pages/signin/signin.module.css: -------------------------------------------------------------------------------- 1 | /* @import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@200;300;400;500;600;700&display=swap"); */ 2 | 3 | .Signin { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | min-height: 100vh; 8 | width: 100%; 9 | padding: 0 10px; 10 | /* z-index: -10; */ 11 | /* background: linear-gradient(45deg, rgba(213, 15, 61, 0.6), rgba(13, 17, 198, 0.69) 100%); */ 12 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 13 | } 14 | 15 | .Signin::before { 16 | content: ""; 17 | position: absolute; 18 | width: 100%; 19 | height: 100%; 20 | /* z-index: -10; */ 21 | /* background: linear-gradient(45deg, rgba(213, 15, 61, 0.6), rgba(13, 17, 198, 0.69) 100%); */ 22 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 23 | 24 | /* background: url("https://www.codingnepalweb.com/demos/create-glassmorphism-login-form-html-css/hero-bg.jpg"), #000; */ 25 | background-position: center; 26 | background-size: cover; 27 | } 28 | 29 | .wrapper { 30 | width: 400px; 31 | border-radius: 8px; 32 | padding: 30px; 33 | text-align: center; 34 | background-color: rgba(255, 255, 255, 0.2); 35 | backdrop-filter: blur(7px); 36 | -webkit-backdrop-filter: blur(9px); 37 | } 38 | 39 | .loginform { 40 | display: flex; 41 | flex-direction: column; 42 | } 43 | 44 | .heading { 45 | font-size: 2rem; 46 | margin-bottom: 20px; 47 | color: #fff; 48 | } 49 | 50 | .inputfield { 51 | position: relative; 52 | border-bottom: 2px solid #ccc; 53 | margin: 15px 0; 54 | } 55 | 56 | .inputfield label { 57 | position: absolute; 58 | top: 50%; 59 | left: 0; 60 | transform: translateY(-50%); 61 | color: #fff; 62 | font-size: 16px; 63 | pointer-events: none; 64 | transition: 0.15s ease; 65 | } 66 | 67 | .inputfield input { 68 | width: 100%; 69 | height: 40px; 70 | background: transparent; 71 | border: none; 72 | outline: none; 73 | font-size: 16px; 74 | color: #fff; 75 | } 76 | 77 | .inputfield input:focus ~ label, 78 | .inputfield input:valid ~ label { 79 | font-size: 0.8rem; 80 | top: 10px; 81 | transform: translateY(-120%); 82 | } 83 | 84 | .forget { 85 | display: flex; 86 | align-items: center; 87 | justify-content: space-between; 88 | margin: 25px 0 35px 0; 89 | color: #fff; 90 | } 91 | 92 | .remember { 93 | accent-color: #fff; 94 | margin-top: -14px; 95 | } 96 | 97 | .forget label { 98 | display: flex; 99 | align-items: center; 100 | } 101 | 102 | .forget label p { 103 | margin-left: 8px; 104 | } 105 | 106 | .wrapper a { 107 | color: #efefef; 108 | text-decoration: none; 109 | } 110 | 111 | .wrapper a:hover { 112 | text-decoration: underline; 113 | } 114 | 115 | .login { 116 | background: #fff; 117 | color: #000; 118 | font-weight: 600; 119 | border: none; 120 | padding: 12px 20px; 121 | cursor: pointer; 122 | border-radius: 3px; 123 | font-size: 16px; 124 | border: 2px solid transparent; 125 | transition: 0.3s ease; 126 | } 127 | 128 | .login:hover { 129 | color: #fff; 130 | border-color: #fff; 131 | background: rgba(255, 255, 255, 0.15); 132 | } 133 | 134 | .register { 135 | text-align: center; 136 | margin-top: 30px; 137 | color: #fff; 138 | } 139 | 140 | .circles { 141 | width: 400px; 142 | height: 400px; 143 | margin: auto; 144 | position: absolute; 145 | top: 0; 146 | left: 0; 147 | right: 0; 148 | bottom: 0; 149 | z-index: 0; 150 | } 151 | 152 | .circle1 { 153 | width: 300px; 154 | height: 300px; 155 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 156 | z-index: -100 !important; 157 | 158 | border-radius: 50%; 159 | position: absolute; 160 | top: -100px; 161 | right: -155px; 162 | } 163 | 164 | .circle2 { 165 | width: 200px; 166 | height: 200px; 167 | border-radius: 50%; 168 | position: absolute; 169 | background: linear-gradient(45deg, rgb(15, 23, 42), rgb(35, 66, 105) 100%); 170 | z-index: -100 !important; 171 | 172 | bottom: -90px; 173 | left: -70px; 174 | } 175 | 176 | .passwordIcon { 177 | position: absolute; 178 | top: 50%; 179 | right: 10px; 180 | transform: translateY(-50%); 181 | cursor: pointer; 182 | font-size: 1.2em; 183 | color: #ffffff; /* Adjust the color as needed */ 184 | } 185 | 186 | /* Style for the eye icon when password is visible */ 187 | .passwordIcon.visible { 188 | color: #2ecc71; /* Adjust the color as needed */ 189 | } -------------------------------------------------------------------------------- /src/pages/TASK/TaskForm.jsx: -------------------------------------------------------------------------------- 1 | // TaskForm.js 2 | 3 | import React, { useState } from "react"; 4 | import AxiosService from "../../components/utils/ApiService"; 5 | import { Modal, Button } from "react-bootstrap"; 6 | import "bootstrap/dist/css/bootstrap.min.css"; 7 | import { toast } from "react-toastify"; 8 | import "react-toastify/dist/ReactToastify.css"; 9 | import styles from "./task.module.css"; 10 | import Spinner from "../../components/utils/Sipnners" 11 | 12 | const TaskForm = ({ refreshTasks }) => { 13 | const [showModal, setShowModal] = useState(false); 14 | const [title, setTitle] = useState(""); 15 | const [description, setDescription] = useState(""); 16 | const [assignedTo, setAssignedTo] = useState(""); 17 | const [error, setError] = useState(""); 18 | const [loading, setLoading] = useState(false); 19 | const handleShow = () => setShowModal(true); 20 | const handleClose = () => { 21 | setShowModal(false); 22 | setError(""); // Clear error when closing the modal 23 | }; 24 | 25 | const handleSubmit = async (e) => { 26 | e.preventDefault(); 27 | 28 | try { 29 | if (!title || !description || !assignedTo) { 30 | setError("All fields are required"); 31 | return; 32 | } 33 | setLoading(true); 34 | const response = await AxiosService.post("/task/create", { 35 | title, 36 | description, 37 | assignedTo, 38 | }); 39 | toast.success(response.data.message); 40 | 41 | refreshTasks(); 42 | 43 | setTitle(""); 44 | setDescription(""); 45 | setAssignedTo(""); 46 | setError(""); 47 | 48 | handleClose(); 49 | } catch (error) { 50 | console.error("Error creating task:", error.message); 51 | 52 | // Display the backend error message using toast.error 53 | if (error.response && error.response.data && error.response.data.message) { 54 | toast.error(`Error creating task: ${error.response.data.message}`); 55 | } else { 56 | toast.error("An error occurred while creating the task."); 57 | } 58 | }finally { 59 | // Set loading back to false when the form submission is complete (success or error) 60 | setLoading(false); 61 | } 62 | }; 63 | 64 | 65 | 66 | 67 | return ( 68 |
69 | 72 | 73 | 74 | 75 | Create Task 76 | 77 | 78 |
79 |
80 | 83 | setTitle(e.target.value)} 89 | /> 90 |
91 |
92 | 95 | 102 |
103 |
104 | 107 | setAssignedTo(e.target.value)} 113 | /> 114 |
115 | {error &&
{error}
} 116 | 119 |
120 |
121 |
122 |
123 | ); 124 | }; 125 | 126 | export default TaskForm; 127 | -------------------------------------------------------------------------------- /src/pages/Signup/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useFormik } from "formik"; 3 | import * as Yup from "yup"; 4 | import AxiosService from "../../components/utils/ApiService"; 5 | import { Link } from "react-router-dom"; 6 | import { toast } from "react-toastify"; 7 | import { useNavigate } from "react-router-dom"; 8 | import Signupcss from "./signup.module.css"; 9 | import Spinner from "../../components/utils/Sipnners"; // Replace with the correct path to your Spinner component 10 | 11 | function Signup() { 12 | const validationSchema = Yup.object().shape({ 13 | firstName: Yup.string().required("First Name is required"), 14 | lastName: Yup.string().required("Last Name is required"), 15 | email: Yup.string().email("Invalid email").required("Email is required"), 16 | password: Yup.string().required("Password is required"), 17 | }); 18 | 19 | const navigate = useNavigate(); 20 | 21 | const handleSignup = async (values, { setSubmitting }) => { 22 | try { 23 | setSubmitting(true); 24 | 25 | const response = await AxiosService.post("/user/signup", values); 26 | console.log(response.data.message); 27 | toast.success(response.data.message); 28 | navigate("/"); 29 | } catch (error) { 30 | console.error(error.response.data.message); 31 | toast.error(error.response.data.message); 32 | } finally { 33 | setSubmitting(false); 34 | } 35 | }; 36 | 37 | const formik = useFormik({ 38 | initialValues: { 39 | firstName: "", 40 | lastName: "", 41 | email: "", 42 | password: "", 43 | }, 44 | validationSchema: validationSchema, 45 | onSubmit: handleSignup, 46 | }); 47 | 48 | return ( 49 | <> 50 |
51 |
52 |
53 |
54 |
55 |
56 |

Welcome back!

57 |

Signup to your account.

58 | 67 | {formik.touched.firstName && formik.errors.firstName && ( 68 |

{formik.errors.firstName}

69 | )} 70 | 71 | 80 | {formik.touched.lastName && formik.errors.lastName && ( 81 |

{formik.errors.lastName}

82 | )} 83 | 84 | 93 | {formik.touched.email && formik.errors.email && ( 94 |

{formik.errors.email}

95 | )} 96 | 97 | 106 | {formik.touched.password && formik.errors.password && ( 107 |

{formik.errors.password}

108 | )} 109 | 110 | 113 | 114 | {/* Login Link */} 115 |

116 | Already have an account?{" "} 117 | 118 | Login 119 | 120 |

121 |
122 |
123 | 124 | ); 125 | } 126 | 127 | export default Signup; 128 | -------------------------------------------------------------------------------- /src/pages/Users/Details.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import CreateIcon from "@mui/icons-material/Create"; 3 | import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; 4 | import Card from "@mui/material/Card"; 5 | import CardContent from "@mui/material/CardContent"; 6 | import MailOutlineIcon from "@mui/icons-material/MailOutline"; 7 | import WorkIcon from "@mui/icons-material/Work"; 8 | import PhoneAndroidIcon from "@mui/icons-material/PhoneAndroid"; 9 | import LocationOnIcon from "@mui/icons-material/LocationOn"; 10 | import { NavLink, useParams, useNavigate } from "react-router-dom"; 11 | import AxiosService from "../../components/utils/ApiService"; 12 | import { toast } from "react-toastify"; 13 | 14 | 15 | // Import Bootstrap CSS 16 | import "bootstrap/dist/css/bootstrap.min.css"; 17 | 18 | const Details = () => { 19 | const [getuserdata, setUserdata] = useState([]); 20 | const { id } = useParams(""); 21 | const navigate = useNavigate(); 22 | 23 | const getdata = async () => { 24 | try { 25 | const response = await AxiosService.get(`/user/getuser/${id}`); 26 | const data = response.data; 27 | 28 | if (response.status === 200) { 29 | setUserdata(data); 30 | console.log("Data fetched successfully:", data); 31 | } else { 32 | console.log("Error fetching data:", response.data.message); 33 | } 34 | } catch (error) { 35 | console.error("Error fetching data:", error); 36 | } 37 | }; 38 | 39 | const deleteuser = async (id) => { 40 | try { 41 | const response = await AxiosService.delete(`/user/deleteuser/${id}`); 42 | const deletedata = response.data; 43 | 44 | if (response.status === 422 || !deletedata) { 45 | console.log("error"); 46 | } else { 47 | console.log("user deleted"); 48 | toast.success("User deleted successfully"); 49 | navigate("/home"); 50 | } 51 | } catch (error) { 52 | console.error("Error deleting user:", error); 53 | } 54 | }; 55 | 56 | useEffect(() => { 57 | getdata(); 58 | }, []); 59 | 60 | return ( 61 |
62 |

User Profile

63 | 64 | 68 | 69 |
70 | 71 | 74 | 75 | 81 |
82 | 83 |
84 |
85 |

86 | Name: {getuserdata.name} 87 |

88 |
89 |
90 |

91 | Status: 92 | {getuserdata.status} 93 |

94 |
95 |
96 | 97 |
98 |
99 |

100 | Email: {getuserdata.email} 101 |

102 |
103 |
104 |

105 | Occupation: Software Engineer 106 |

107 |
108 |
109 | 110 |
111 |
112 |

113 | Mobile:{" "} 114 | +91 {getuserdata.mobile} 115 |

116 |
117 |
118 |

119 | Location: {getuserdata.add} 120 |

121 |
122 |
123 | 124 |
125 |
126 |

127 | UserId: {getuserdata._id} 128 |

129 |
130 |
131 |

132 | Description: {getuserdata.desc} 133 |

134 |
135 |
136 |
137 |
138 |
139 | ); 140 | }; 141 | 142 | export default Details; 143 | -------------------------------------------------------------------------------- /src/pages/User Task/TaskSubmissionForm.jsx: -------------------------------------------------------------------------------- 1 | // TaskSubmittingPage.js 2 | 3 | import React, { useState, useEffect } from "react"; 4 | import { useParams } from "react-router-dom"; 5 | import AxiosService from "../../components/utils/ApiService"; 6 | import { toast } from "react-toastify"; 7 | import { useNavigate } from "react-router-dom"; 8 | import Spinner from "../../components/utils/Sipnners"; 9 | 10 | const TaskSubmittingPage = () => { 11 | const navigate = useNavigate(); 12 | let { taskId } = useParams(); 13 | const [task, setTask] = useState(null); 14 | const [frontendUrl, setFrontendUrl] = useState(""); 15 | const [backendUrl, setBackendUrl] = useState(""); 16 | const [frontendUrlError, setFrontendUrlError] = useState(""); 17 | const [backendUrlError, setBackendUrlError] = useState(""); 18 | const [loading, setLoading] = useState(false); 19 | 20 | const fetchTask = async () => { 21 | try { 22 | const response = await AxiosService.get(`/task/taskID/${taskId}`); 23 | setTask(response.data); 24 | } catch (error) { 25 | console.error("Error fetching task:", error); 26 | } 27 | }; 28 | 29 | const validateForm = () => { 30 | let isValid = true; 31 | 32 | if (!frontendUrl) { 33 | setFrontendUrlError("Frontend URL is required"); 34 | isValid = false; 35 | } else { 36 | setFrontendUrlError(""); 37 | } 38 | 39 | if (!backendUrl) { 40 | setBackendUrlError("Backend URL is required"); 41 | isValid = false; 42 | } else { 43 | setBackendUrlError(""); 44 | } 45 | 46 | return isValid; 47 | }; 48 | 49 | const handleSubmit = async () => { 50 | try { 51 | if (!validateForm()) { 52 | return; 53 | } 54 | 55 | setLoading(true); // Set loading to true when submitting 56 | 57 | const response = await AxiosService.put(`/task/submit/${taskId}`, { 58 | frontendUrl, 59 | backendUrl, 60 | }); 61 | 62 | if (response && response.data) { 63 | toast.success(response.data.message); 64 | navigate("/userTask"); 65 | fetchTask(); 66 | console.log("Task submitted successfully"); 67 | } else { 68 | console.error("Invalid response:", response); 69 | toast.error("Unexpected response format"); 70 | } 71 | } catch (error) { 72 | console.error("Error submitting task:", error); 73 | 74 | if ( 75 | error.response && 76 | error.response.data && 77 | error.response.data.message 78 | ) { 79 | toast.error(error.response.data.message); 80 | } else { 81 | toast.error("An unexpected error occurred"); 82 | } 83 | } finally { 84 | setLoading(false); // Set loading to false after submission 85 | } 86 | }; 87 | 88 | useEffect(() => { 89 | fetchTask(); 90 | }, [taskId]); 91 | 92 | if (!task) { 93 | return
Loading...
; 94 | } 95 | 96 | return ( 97 |
98 |
99 |
100 |
101 |

Submit Task

102 |
103 | Task Title: {task.title} 104 |
105 |
106 | Description: {task.description} 107 |
108 | {/* Task Submission Form */} 109 |
e.preventDefault()} 111 | className="needs-validation" 112 | > 113 |
114 | 117 | setFrontendUrl(e.target.value)} 125 | required 126 | /> 127 | {frontendUrlError && ( 128 |
{frontendUrlError}
129 | )} 130 |
131 | 132 |
133 | 136 | setBackendUrl(e.target.value)} 144 | required 145 | /> 146 | {backendUrlError && ( 147 |
{backendUrlError}
148 | )} 149 |
150 | 151 |
152 | {" "} 153 | {/* Center the button */} 154 | 162 |
163 |
164 |
165 |
166 |
167 |
168 | ); 169 | }; 170 | 171 | export default TaskSubmittingPage; 172 | -------------------------------------------------------------------------------- /src/pages/Users/Register.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { NavLink, useNavigate } from "react-router-dom"; 3 | import { adddata } from "../../components/context/ContextProvider"; 4 | import useLogout from "../../components/hooks/useLogout"; 5 | import { toast } from "react-toastify"; 6 | import AxiosService from "../../components/utils/ApiService"; 7 | import Spinner from "../../components/utils/Sipnners"; 8 | 9 | const Register = () => { 10 | let logout = useLogout(); 11 | const { udata, setUdata } = useContext(adddata); 12 | const [loading, setLoading] = useState(false); // Set initial loading state to false 13 | const navigate = useNavigate(); 14 | 15 | const [inpval, setINP] = useState({ 16 | name: "", 17 | email: "", 18 | status: "Active", 19 | mobile: "", 20 | password: "", 21 | add: "", 22 | desc: "", 23 | }); 24 | 25 | 26 | 27 | const setdata = (e) => { 28 | const { name, value } = e.target; 29 | setINP((preval) => ({ 30 | ...preval, 31 | [name]: name === "status" ? e.target.value : value, 32 | })); 33 | }; 34 | 35 | const addinpdata = async (e) => { 36 | e.preventDefault(); 37 | 38 | setLoading(true); // Set loading to true when starting the registration process 39 | 40 | const { name, email, password, add, mobile, desc, status } = inpval; 41 | 42 | const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/; 43 | 44 | // Validate the password 45 | if (!passwordRegex.test(password)) { 46 | toast.error("Password must be at least 8 characters, with at least one uppercase letter, one lowercase letter, and one digit."); 47 | setLoading(false); // Set loading to false after validation 48 | return; 49 | } 50 | 51 | 52 | try { 53 | let res = await AxiosService.post("/user/register", inpval); 54 | const data = res.data; 55 | 56 | if (res.status === 201) { 57 | toast.success(res.data.message); 58 | navigate("/home"); 59 | setUdata(data); 60 | } 61 | } catch (error) { 62 | toast.error(error.response.data.message); 63 | if (error.response.status === 401) { 64 | logout(); 65 | } 66 | } finally { 67 | setLoading(false); // Set loading to false after the registration attempt (success or failure) 68 | } 69 | }; 70 | 71 | return ( 72 |
73 |
74 |
75 |
76 | 79 | 88 |
89 |
90 | 93 | 102 |
103 |
104 | 107 | 117 |
118 |
119 | 122 | 131 |
132 |
133 | 136 | 145 |
146 |
147 | 150 | 159 |
160 |
161 | 164 | 173 |
174 |
175 | 183 |
184 |
185 |
186 |
187 | ); 188 | }; 189 | 190 | export default Register; 191 | -------------------------------------------------------------------------------- /src/components/Dashboard/UserDashbooard/UserDashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import AxiosService from "../../utils/ApiService"; 3 | import UserTaskDashoard from "./UserTaskDashboard"; 4 | import UserTicketsDashboard from "./UserTicketsDashoard"; 5 | import UserAreachart from "./UserAreachart"; 6 | import Spinner from "../../Spiner/Spiner"; // Adjust the path to the Spinner component 7 | import { Link } from "react-router-dom"; 8 | import styles from "../AdminDashboard/Dashboard.module.css"; 9 | 10 | const Dashboard = () => { 11 | const [loading, setLoading] = useState(true); 12 | const [ticketData, setTicketData] = useState({ 13 | totalTickets: 0, 14 | resolvedTickets: 0, 15 | approvedTickets: 0, 16 | pendingTickets: 0, 17 | }); 18 | 19 | useEffect(() => { 20 | const fetchData = async () => { 21 | try { 22 | const response = await AxiosService.get("/tickets/user"); 23 | const data = response.data; 24 | setTicketData(data); 25 | setLoading(false); // Set loading to false when data is fetched 26 | } catch (error) { 27 | console.error("Error fetching data:", error); 28 | setLoading(false); // Set loading to false in case of an error 29 | } 30 | }; 31 | 32 | fetchData(); 33 | }, []); // Empty dependency array ensures the effect runs once when the component mounts 34 | 35 | if (loading) { 36 | // Display a loading spinner while data is being fetched 37 | return ; 38 | } 39 | return ( 40 |
44 |
45 | {/* Card for Total Tickets */} 46 |
47 |
48 |
49 | Total Tickets: {ticketData.totalTickets} 50 |
51 |
52 | 53 | 54 | View Details 55 | 56 | 57 |
58 | 59 |
60 |
61 |
62 |
63 | 64 | {/* Card for Resolved Tickets */} 65 |
66 |
67 |
68 | Resolved Tickets: {ticketData.resolvedTickets} 69 |
70 |
71 | 72 | 73 | View Details 74 | 75 | 76 |
77 | 78 |
79 |
80 |
81 |
82 | 83 | {/* Card for Approved Tickets */} 84 |
85 |
86 |
87 | Approved Tickets: {ticketData.approvedTickets} 88 |
89 |
90 | 91 | 92 | View Details 93 | 94 | 95 |
96 | 97 |
98 |
99 |
100 |
101 | 102 | {/* Card for Pending Tickets */} 103 |
104 |
105 |
106 | Pending Tickets: {ticketData.pendingTickets} 107 |
108 |
109 | 110 | 111 | View Details 112 | 113 | 114 |
115 | 116 |
117 |
118 |
119 |
120 |
121 | 122 |
123 |
124 |
127 |
128 | 129 | Task Progress Bar 130 |
131 |
132 | {/* Add your chart component here with the corresponding data */} 133 | 134 |
135 |
136 |
137 | 138 |
139 |
140 |
141 | 142 | Tickets Bar Chart 143 |
144 |
145 | 146 |
147 |
148 |
149 |
150 |
151 |
152 | 153 |
154 |
155 |
156 |
157 |
158 | ); 159 | }; 160 | 161 | export default Dashboard; 162 | -------------------------------------------------------------------------------- /src/components/Dashboard/AdminDashboard/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import axios from "axios"; 3 | import styles from "./Dashboard.module.css"; // Import the CSS module 4 | import AxiosService from "../../utils/ApiService"; 5 | import TicketAreaChart from "./TicketAreaChart"; 6 | import UserDataChart from "../UserDashbooard/UserDataChart"; 7 | import CircleProgressBar from "./CircleProgressBar"; 8 | import TaskDashboard from "./TaskDashboard"; 9 | import Spinner from "../../Spiner/Spiner"; 10 | import { Link } from "react-router-dom"; 11 | const Dashboard = () => { 12 | const [ticketData, setTicketData] = useState({}); 13 | const [userData, setUserData] = useState({}); 14 | const [loading, setLoading] = useState(true); 15 | 16 | useEffect(() => { 17 | const fetchData = async () => { 18 | try { 19 | // Fetch ticket data 20 | const ticketResponse = await AxiosService.get("/tickets/"); 21 | // 22 | setTicketData(ticketResponse.data); 23 | // Fetch user data 24 | const userResponse = await AxiosService.get("/user/getdata"); 25 | setUserData(userResponse.data); 26 | setLoading(false); 27 | } catch (error) { 28 | console.error("Error fetching data:", error); 29 | setLoading(false); 30 | } 31 | }; 32 | 33 | fetchData(); 34 | }, []); 35 | 36 | if (loading) { 37 | return ; 38 | } 39 | 40 | return ( 41 |
45 | {/* Cards */} 46 |
47 |
48 |
49 |
50 | Total Tickets: {ticketData.totalTickets} 51 |
52 |
53 | 54 | View Details 55 | 56 |
57 | 58 |
59 |
60 |
61 |
62 | 63 |
64 |
65 |
66 | Resolved Tickets:{ticketData.resolvedTickets} 67 |
68 |
69 | 70 | View Details 71 | 72 |

73 |
74 | 75 |
76 |
77 |
78 |
79 | 80 |
81 |
82 |
83 | Approved Tickets:{ticketData.approvedTickets} 84 |
85 |
86 | 87 | View Details 88 | 89 |
90 | 91 |
92 |
93 |
94 |
95 | 96 | {/* Add similar updates for other cards */} 97 |
98 |
99 |
100 | {" "} 101 | Pending Tickets:{ticketData.pendingTickets} 102 |
103 |
104 | 105 | View Details 106 | 107 |
108 | 109 |
110 |
111 |
112 |
113 |
114 | 115 | {/* Charts */} 116 |
117 |
118 |
119 |
120 | 121 | Task Pogress Bar 122 |
123 |
124 | {/* Add your chart component here with the corresponding data */} 125 | 126 | 127 |
128 |
129 |
130 | 131 |
132 |
133 |
134 | 135 |
136 |
137 | 138 |
139 |
140 |
141 | 142 |
143 |
144 |
145 | 146 | Ticket Bar 147 |
148 |
149 | {/* Add your chart component here with the corresponding data */} 150 | 151 | 152 |
153 |
154 |
155 |
156 |
157 |
158 | 159 | User Line chart 160 |
161 |
162 | {/* Add your chart component here with the corresponding data */} 163 | 164 |
165 |
166 |
167 |
168 | 169 | {/* DataTable */} 170 |
171 | ); 172 | }; 173 | 174 | export default Dashboard; 175 | -------------------------------------------------------------------------------- /src/routes/AppRouters.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Routes, Route, Navigate } from "react-router-dom"; 3 | import SignIn from "../pages/signin/SignIn.jsx"; 4 | import Slider from "../components/slider/Slider"; 5 | import Dashboard from "../components/Dashboard/AdminDashboard/Dashboard.jsx"; 6 | import Create from "../pages/Tickets/Create.jsx"; 7 | import TicketsDashboard from "../components/Dashboard/AdminDashboard/TicketsDashboard.jsx"; 8 | import UserDashboard from "../components/Dashboard/UserDashbooard/UserDashboard.jsx"; 9 | import Home from "../pages/Users/Home.jsx"; 10 | import Register from "../pages/Users/Register.jsx"; 11 | import Edit from "../pages/Users/Edit.jsx"; 12 | import Details from "../pages/Users/Details.jsx"; 13 | import Tickets from "../pages/Tickets/Tickets.jsx"; 14 | import Task from "../pages/TASK/Task.jsx"; 15 | import UserTask from "../pages/User Task/TaskPage.jsx"; 16 | import TaskSubmittingPage from "../pages/User Task/TaskSubmissionForm.jsx"; 17 | import SubmittedTaskPage from "../pages/TASK/SubmittedTaskPage.jsx"; 18 | import UserTasklistpage from "../pages/User Task/TaskList.jsx"; 19 | import Frogotpassword from "../pages/Password/ForgotPassword.jsx"; 20 | import ResetPassword from "../pages/Password/Resetpassword.jsx"; 21 | import TicketsCards from "../components/Dashboard/Dashboard Cards/TicketsCard.jsx"; 22 | import ResolvedTickets from "../components/Dashboard/Dashboard Cards/RessolvedTickets.jsx"; 23 | import ApprovedTickets from "../components/Dashboard/Dashboard Cards/ApprovedTickets.jsx"; 24 | import PendingTickets from "../components/Dashboard/Dashboard Cards/PendingTickets.jsx"; 25 | 26 | export const AppRouters = () => { 27 | return ( 28 | 29 | 33 | 34 | 35 | } 36 | /> 37 | 41 | 42 | 43 | } 44 | /> 45 | 49 | 50 | 51 | } 52 | /> 53 | 54 | 58 |
59 | 60 | 61 |
62 | 63 | } 64 | /> 65 | 69 |
70 | 71 | 72 |
73 | 74 | } 75 | /> 76 | 80 |
81 | 82 | 83 |
84 | 85 | } 86 | /> 87 | 91 |
92 | 93 |
94 |
95 | 96 | } 97 | /> 98 | 99 | 103 |
104 | 105 | 106 |
107 | 108 | } 109 | /> 110 | 114 |
115 | 116 | 117 |
118 | 119 | } 120 | /> 121 | 125 |
126 | 127 | 128 |
129 | 130 | } 131 | /> 132 | 136 |
137 | 138 | 139 |
140 | 141 | } 142 | /> 143 | 147 |
148 | 149 | 150 |
151 | 152 | } 153 | /> 154 | 158 |
159 | 160 | 161 |
162 | 163 | } 164 | /> 165 | 169 |
170 | 171 | 172 |
173 | 174 | } 175 | /> 176 | 180 |
181 | 182 | 183 |
184 | 185 | } 186 | /> 187 | 191 |
192 | 193 | 194 |
195 | 196 | } 197 | /> 198 | 199 | 203 |
204 | 205 | 206 |
207 | 208 | } 209 | /> 210 | 214 |
215 | 216 | 217 |
218 | 219 | } 220 | /> 221 | 222 | 226 |
227 | 228 | 229 |
230 | 231 | } 232 | /> 233 | 237 |
238 | 239 | 240 |
241 | 242 | } 243 | /> 244 | 248 |
249 | 250 | 251 |
252 | 253 | } 254 | /> 255 | 256 | {/* }/> */} 257 |
258 | ); 259 | }; 260 | 261 | export default AppRouters; 262 | -------------------------------------------------------------------------------- /src/pages/User Task/TaskList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { 3 | Pagination, 4 | InputGroup, 5 | FormControl, 6 | Button, 7 | Spinner, 8 | } from "react-bootstrap"; 9 | import AxiosService from "../../components/utils/ApiService"; 10 | import SearchIcon from "@mui/icons-material/Search"; 11 | import Table from "react-bootstrap/Table"; 12 | 13 | import styles from "../../components/Dashboard/AdminDashboard/Dashboard.module.css"; 14 | 15 | const TaskListPage = () => { 16 | const [tasks, setTasks] = useState([]); 17 | const [filteredTasks, setFilteredTasks] = useState([]); 18 | const [currentPage, setCurrentPage] = useState(1); 19 | const [tasksPerPage] = useState(6); // Adjust the number of tasks per page as needed 20 | const [searchTerm, setSearchTerm] = useState(""); 21 | const [sortOrder, setSortOrder] = useState("asc"); // 'asc' or 'desc' 22 | const [sortBy, setSortBy] = useState("createdAt"); 23 | const [loading, setLoading] = useState(false); // New state for loading 24 | 25 | useEffect(() => { 26 | // Fetch submitted tasks from the server when the component mounts 27 | fetchSubmittedTasks(); 28 | }, [sortOrder, sortBy]); 29 | 30 | const fetchSubmittedTasks = async () => { 31 | try { 32 | setLoading(true); // Set loading to true before the API call 33 | 34 | // Replace 'YOUR_API_ENDPOINT' with the actual endpoint for fetching tasks 35 | const response = await AxiosService.get("/task/user", { 36 | params: { 37 | sort: sortBy, 38 | order: sortOrder, 39 | }, 40 | }); 41 | // Filter tasks by status (assuming "Submitted" is the status you want) 42 | const submittedTasks = response.data.filter( 43 | (task) => task.status === "Submitted" 44 | ); 45 | setTasks(submittedTasks); 46 | setFilteredTasks(submittedTasks); 47 | } catch (error) { 48 | console.error("Error fetching tasks:", error); 49 | } finally { 50 | setLoading(false); // Set loading to false after the API call 51 | } 52 | }; 53 | 54 | // Pagination 55 | const paginate = (pageNumber) => setCurrentPage(pageNumber); 56 | 57 | // Search functionality 58 | const handleSearch = (event) => { 59 | const searchTerm = event.target.value.toLowerCase(); 60 | setSearchTerm(searchTerm); 61 | 62 | const filteredTasks = tasks.filter( 63 | (task) => 64 | task.title.toLowerCase().includes(searchTerm) || 65 | task.description.toLowerCase().includes(searchTerm) 66 | ); 67 | 68 | setFilteredTasks(filteredTasks); 69 | setCurrentPage(1); // Reset to the first page when searching 70 | }; 71 | 72 | // Sorting functionality 73 | const sortedTasks = [...filteredTasks].sort((a, b) => { 74 | const dateA = new Date(a.createdAt); 75 | const dateB = new Date(b.createdAt); 76 | return sortOrder === "asc" ? dateA - dateB : dateB - dateA; 77 | }); 78 | 79 | // Pagination based on sorted tasks 80 | const indexOfLastTask = currentPage * tasksPerPage; 81 | const indexOfFirstTask = indexOfLastTask - tasksPerPage; 82 | const currentTasks = sortedTasks.slice(indexOfFirstTask, indexOfLastTask); 83 | 84 | return ( 85 |
86 |
87 |
88 |
89 |
90 |
91 | 98 | 99 | 100 | 101 |
102 |
103 |     104 |
105 | 106 | 117 | 127 |
128 |
129 |
133 | {loading ? ( 134 |
135 | 136 | Loading... 137 | 138 |
139 | ) : ( 140 |
141 | {currentTasks.map((task) => ( 142 |
143 |
144 |
145 |
{task.title}
146 |

{task.description}

147 |

148 | Frontend URL: {task.frontendUrl} 149 |

150 |

151 | Backend URL: {task.backendUrl} 152 |

153 |
154 |
155 | 156 | Status: {task.status} 157 | 158 |
159 |
160 |
161 | ))} 162 |
163 | )} 164 |
165 | 180 |
181 |
182 |
183 | ); 184 | }; 185 | 186 | export default TaskListPage; 187 | -------------------------------------------------------------------------------- /src/pages/User Task/TaskPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { 3 | Card, 4 | Button, 5 | Table, 6 | Pagination, 7 | Form, 8 | Container, 9 | Row, 10 | Col, 11 | Spinner as BootstrapSpinner, 12 | } from "react-bootstrap"; 13 | import AxiosService from "../../components/utils/ApiService"; 14 | import { Link } from "react-router-dom"; 15 | import { toast } from "react-toastify"; 16 | 17 | import styles from "../../components/Dashboard/AdminDashboard/Dashboard.module.css"; 18 | 19 | import SearchIcon from "@mui/icons-material/Search"; 20 | // import Spinner from '../Spiner/Spiner'; 21 | 22 | const Spinner = () => ( 23 |
24 | 25 | Loading... 26 | 27 |
28 | ); 29 | 30 | const TaskListPage = () => { 31 | const [tasks, setTasks] = useState([]); 32 | const [currentPage, setCurrentPage] = useState(1); 33 | const [searchTerm, setSearchTerm] = useState(""); 34 | const [statusFilter, setStatusFilter] = useState(""); 35 | const [sortOrder, setSortOrder] = useState("desc"); 36 | const [loading, setLoading] = useState(true); 37 | const tasksPerPage = 10; 38 | 39 | const fetchTasks = async () => { 40 | try { 41 | const response = await AxiosService.get("/task/user"); 42 | setTasks(response.data); 43 | setLoading(false); // Set loading to false when data is fetched 44 | } catch (error) { 45 | console.error("Error fetching tasks:", error); 46 | setLoading(false); // Set loading to false even on error 47 | } 48 | }; 49 | 50 | useEffect(() => { 51 | fetchTasks(); 52 | }, []); 53 | 54 | const handleSubmittedTask = async () => { 55 | toast.error("Task already submitted"); 56 | }; 57 | 58 | const handleStatusFilterChange = (e) => { 59 | setCurrentPage(1); 60 | setStatusFilter(e.target.value); 61 | }; 62 | 63 | const handleSortOrderChange = (e) => { 64 | setCurrentPage(1); 65 | setSortOrder(e.target.value); 66 | }; 67 | 68 | const filteredTasks = tasks 69 | .filter( 70 | (task) => 71 | task.title.toLowerCase().includes(searchTerm.toLowerCase()) || 72 | task.description.toLowerCase().includes(searchTerm.toLowerCase()) 73 | ) 74 | .filter((task) => (statusFilter ? task.status === statusFilter : true)); 75 | 76 | const sortedTasks = filteredTasks.sort((a, b) => { 77 | const dateA = new Date(a.createdAt); 78 | const dateB = new Date(b.createdAt); 79 | return sortOrder === "asc" ? dateA - dateB : dateB - dateA; 80 | }); 81 | 82 | const indexOfLastTask = currentPage * tasksPerPage; 83 | const indexOfFirstTask = indexOfLastTask - tasksPerPage; 84 | const currentTasks = sortedTasks.slice(indexOfFirstTask, indexOfLastTask); 85 | 86 | const paginate = (pageNumber) => setCurrentPage(pageNumber); 87 | 88 | return ( 89 |
90 | {loading ? ( 91 | // Display the spinner while loading 92 | ) : ( 93 |
94 |
95 | 96 | Task List 97 |
98 | 99 |
100 | 101 | 102 |
103 | { 108 | setCurrentPage(1); // Reset page when searching 109 | setSearchTerm(e.target.value); 110 | }} 111 | className="form-control" 112 | /> 113 | 114 | 115 | 116 |
117 | 118 | 119 | 120 | 128 | 129 | 130 | 131 | 140 | 141 |
142 | 143 |
147 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | {currentTasks.map((task, id) => ( 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 184 | 185 | ))} 186 | 187 |
IdTitleDescriptionCreated DateStatusAction
{id + 1}{task.title}{task.description}{new Date(task.createdAt).toLocaleDateString()}{task.status} 171 | {task.status === "Submitted" ? ( 172 | 178 | ) : ( 179 | 180 | 181 | 182 | )} 183 |
188 |
189 | {/* Pagination */} 190 |
191 | 192 | {[ 193 | ...Array(Math.ceil(sortedTasks.length / tasksPerPage)).keys(), 194 | ].map((number) => ( 195 | paginate(number + 1)} 199 | > 200 | {number + 1} 201 | 202 | ))} 203 | 204 |
205 |
206 |
207 | )} 208 |
209 | ); 210 | }; 211 | 212 | export default TaskListPage; 213 | -------------------------------------------------------------------------------- /src/pages/Users/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from "react"; 2 | import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye"; 3 | import CreateIcon from "@mui/icons-material/Create"; 4 | import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; 5 | import SearchIcon from "@mui/icons-material/Search"; 6 | import { NavLink } from "react-router-dom"; 7 | import { adddata, deldata } from "../../components/context/ContextProvider"; 8 | import { updatedata } from "../../components/context/ContextProvider"; 9 | import AxiosService from "../../components/utils/ApiService"; 10 | import { toast } from "react-toastify"; 11 | import useLogout from "../../components/hooks/useLogout"; 12 | import styles from "../../components/Dashboard/AdminDashboard/Dashboard.module.css"; 13 | 14 | import Spinner from "../../components/Spiner/Spiner"; 15 | 16 | const Home = () => { 17 | let logout = useLogout(); 18 | const [loading, setLoading] = useState(true); 19 | const [getuserdata, setUserdata] = useState([]); 20 | const [searchInput, setSearchInput] = useState(""); 21 | const [filteredData, setFilteredData] = useState([]); 22 | const [currentPage, setCurrentPage] = useState(1); 23 | const [itemsPerPage, setItemsPerPage] = useState(5); 24 | 25 | const { udata, setUdata } = useContext(adddata); 26 | const { updata, setUPdata } = useContext(updatedata); 27 | const { dltdata, setDLTdata } = useContext(deldata); 28 | 29 | const getdata = async () => { 30 | try { 31 | let res = await AxiosService.get(`/user/getdata`); 32 | const data = res.data; 33 | 34 | if (res.status === 200) { 35 | toast.success(res.data.message); 36 | setUserdata(data.userData); 37 | setFilteredData(data.userData); 38 | setLoading(false); 39 | } 40 | } catch (error) { 41 | toast.error(error.response.data.message); 42 | if (error.response.status === 401) { 43 | logout(); 44 | setLoading(false); 45 | } 46 | } 47 | }; 48 | 49 | useEffect(() => { 50 | getdata(); 51 | }, []); 52 | 53 | const deleteuser = async (id) => { 54 | try { 55 | const response = await AxiosService.delete(`/user/deleteuser/${id}`); 56 | const deletedata = response.data; 57 | 58 | if (response.status === 422 || !deletedata) { 59 | console.log("error"); 60 | } else { 61 | console.log("user deleted"); 62 | setDLTdata(deletedata); 63 | toast.success("Delete Successfully"); 64 | getdata(); 65 | } 66 | } catch (error) { 67 | console.error("Error deleting user:", error); 68 | } 69 | }; 70 | 71 | const handleSearchInputChange = (e) => { 72 | const inputValue = e.target.value; 73 | setSearchInput(inputValue); 74 | 75 | const filtered = getuserdata.filter((user) => { 76 | const mobile = String(user.mobile); 77 | 78 | return ( 79 | user.name.toLowerCase().includes(inputValue.toLowerCase()) || 80 | user.email.toLowerCase().includes(inputValue.toLowerCase()) || 81 | user._id.toLowerCase().includes(inputValue.toLowerCase()) || 82 | mobile.toLowerCase().includes(inputValue.toLowerCase()) 83 | ); 84 | }); 85 | 86 | setFilteredData(filtered); 87 | setCurrentPage(1); 88 | }; 89 | 90 | useEffect(() => { 91 | setFilteredData(getuserdata); 92 | }, [getuserdata]); 93 | 94 | const indexOfLastItem = currentPage * itemsPerPage; 95 | const indexOfFirstItem = indexOfLastItem - itemsPerPage; 96 | const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); 97 | 98 | const paginate = (pageNumber) => setCurrentPage(pageNumber); 99 | if (loading) { 100 | return ; 101 | } 102 | return ( 103 |
104 |
105 |
106 | 107 | UserData 108 |
109 | 110 |
111 |
112 |
113 | 120 | 121 | 122 | 123 |
124 |
125 |
126 | 127 | Add data 128 | 129 |
130 |
134 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | {currentItems.map((user, id) => ( 151 | 152 | 153 | 154 | 155 | 156 | 165 | 166 | 186 | 187 | ))} 188 | 189 |
IdNameEmailRoleStatusNumberActions
{id + 1}{user.name}{user.email}{user.role} 163 | {user.status} 164 | {user.mobile} 167 | 168 | {" "} 169 | 172 | 173 | 174 | {" "} 175 | 178 | 179 | 185 |
190 |
191 | {/* Pagination */} 192 | 210 |
211 |
212 |
213 | ); 214 | }; 215 | 216 | export default Home; 217 | -------------------------------------------------------------------------------- /src/components/Dashboard/AdminDashboard/TicketsDashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import Table from "react-bootstrap/Table"; 3 | import AxiosService from "../../utils/ApiService"; 4 | import { useNavigate } from "react-router-dom"; 5 | import useLogout from "../../hooks/useLogout"; 6 | import { toast } from "react-toastify"; 7 | import styles from "./Dashboard.module.css"; 8 | import SearchIcon from "@mui/icons-material/Search"; 9 | import Spinner from "../../Spiner/Spiner"; 10 | import { Status } from "../../utils/Status"; // Replace with the correct path 11 | 12 | function Dashboard() { 13 | const userData = JSON.parse(sessionStorage.getItem("userData")); 14 | const [tickets, setTickets] = useState([]); 15 | const [searchInput, setSearchInput] = useState(""); 16 | const [searchResults, setSearchResults] = useState([]); 17 | const [currentPage, setCurrentPage] = useState(1); 18 | const [itemsPerPage] = useState(5); 19 | const [filterStatus, setFilterStatus] = useState(""); 20 | const [loading, setLoading] = useState(true); // Add loading state 21 | const navigate = useNavigate(); 22 | const logout = useLogout(); 23 | 24 | const getTickets = async () => { 25 | try { 26 | const url = userData.role === "admin" ? "/tickets/" : "/tickets/user"; 27 | const res = await AxiosService.get(url); 28 | if (res.status === 200) { 29 | setTickets(res.data.tickets); 30 | } 31 | } catch (error) { 32 | toast.error(error.response.data.message); 33 | if (error.response.status === 401) { 34 | logout(); 35 | } 36 | } finally { 37 | setLoading(false); // Set loading to false after fetching data 38 | } 39 | }; 40 | 41 | useEffect(() => { 42 | getTickets(); 43 | }, []); 44 | 45 | const handleSearchChange = (e) => { 46 | setSearchInput(e.target.value); 47 | setCurrentPage(1); 48 | searchTickets(e.target.value); 49 | }; 50 | 51 | const handleFilterStatusChange = (e) => { 52 | setFilterStatus(e.target.value); 53 | setCurrentPage(1); 54 | }; 55 | 56 | const searchTickets = (input) => { 57 | const filteredTickets = tickets.filter( 58 | (ticket) => 59 | ticket.title.toLowerCase().includes(input.toLowerCase()) || 60 | ticket._id.includes(input) 61 | ); 62 | setSearchResults(filteredTickets); 63 | }; 64 | 65 | const indexOfLastItem = currentPage * itemsPerPage; 66 | const indexOfFirstItem = indexOfLastItem - itemsPerPage; 67 | 68 | const filteredItems = 69 | searchResults.length > 0 70 | ? searchResults.filter((item) => 71 | filterStatus ? item.status === filterStatus : true 72 | ) 73 | : tickets.filter((item) => 74 | filterStatus ? item.status === filterStatus : true 75 | ); 76 | 77 | const currentItems = filteredItems.slice(indexOfFirstItem, indexOfLastItem); 78 | 79 | const paginate = (pageNumber) => setCurrentPage(pageNumber); 80 | 81 | return ( 82 |
83 | {loading ? ( 84 | // Show the spinner when loading 85 | ) : ( 86 |
87 |
88 | 89 | Tickets Data 90 |
91 | 92 |
93 |
94 |
95 |
96 | 103 | 104 | 105 | 106 |
107 |
108 |     109 |
110 | 111 | 121 |
122 |
123 |
127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | {currentItems.map((e, i) => { 140 | const createdAt = new Date(e.createdAt); 141 | const createdDate = createdAt.toLocaleDateString(); 142 | 143 | let solutionContent = e.solution; // Default to the solution from the ticket 144 | 145 | // Customize the solution content based on the ticket status 146 | if (e.status === Status.PENDING) { 147 | solutionContent = 148 | "Your ticket is in progress. We'll update you soon. Thanks for your patience!"; 149 | } else if (e.status === Status.APPROVED) { 150 | solutionContent = ` You can join a Google Meet to discuss further:https://meet.google.com/nxj-bwwb-mwp`; 151 | } 152 | if (e.status === Status.RESOLVED) { 153 | solutionContent = `${e.reason}`; 154 | } 155 | return ( 156 | navigate(`/ticket/${e._id}`)} 159 | className="cursor-pointer" 160 | > 161 | 162 | 163 | 170 | 171 | 172 | 173 | 174 | ); 175 | })} 176 | 177 |
IdTitleImageSolutionCreated DateStatus
{i + 1 + indexOfFirstItem}{e.title} 164 | {e.title} 169 | {solutionContent}{createdDate}{e.status}
178 |
179 | 202 |
203 |
204 | )} 205 |
206 | ); 207 | } 208 | 209 | export default Dashboard; 210 | -------------------------------------------------------------------------------- /src/pages/signin/SignIn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Button from "react-bootstrap/Button"; 3 | import Form from "react-bootstrap/Form"; 4 | import { toast } from "react-toastify"; 5 | import AxiosService from "../../components/utils/ApiService"; 6 | import { useNavigate, Link } from "react-router-dom"; 7 | import signincss from "./signin.module.css"; 8 | import Spinner from "../../components/utils/Sipnners"; 9 | import { FiEye, FiEyeOff } from "react-icons/fi"; // Import eye icons from react-icons/fi 10 | 11 | function SignIn() { 12 | const [email, setEmail] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | const [showPassword, setShowPassword] = useState(false); // State to manage password visibility 15 | const [loading, setLoading] = useState(false); 16 | const navigate = useNavigate(); 17 | 18 | const handleLogin = async (e) => { 19 | e.preventDefault(); 20 | 21 | try { 22 | setLoading(true); 23 | 24 | const res = await AxiosService.post(`/user/login`, { 25 | email, 26 | password, 27 | }); 28 | 29 | if (res.status === 200) { 30 | sessionStorage.setItem("token", res.data.token); 31 | sessionStorage.setItem("userData", JSON.stringify(res.data.userData)); 32 | if (res.data.userData.status === "InActive") { 33 | navigate("/"); 34 | toast.error("You are not Allow to login"); 35 | } else if (res.data.userData.role === "admin") { 36 | toast.success(res.data.message); 37 | navigate("/dashboard"); 38 | } else { 39 | toast.success(res.data.message); 40 | navigate("/userdash"); 41 | } 42 | } 43 | } catch (error) { 44 | toast.error(error.response.data.message); 45 | } finally { 46 | setLoading(false); 47 | } 48 | }; 49 | 50 | return ( 51 | <> 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |

Login

60 |
61 | setEmail(e.target.value)} 65 | /> 66 | 67 |
68 |
69 | 70 |
setPassword(e.target.value)} 74 | /> 75 | 76 | {/* Toggle password visibility */} 77 | {showPassword ? ( 78 | setShowPassword(!showPassword)} 81 | /> 82 | ) : ( 83 | setShowPassword(!showPassword)} 86 | /> 87 | )} 88 |
89 |
90 |
91 | 95 | Forgot password? 96 |
97 | 104 |
105 |

106 |
107 |
108 |
109 |
110 | 111 | ); 112 | } 113 | 114 | export default SignIn; 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | // import React, { useState } from "react"; 158 | // import Button from "react-bootstrap/Button"; 159 | // import Form from "react-bootstrap/Form"; 160 | // import { toast } from "react-toastify"; 161 | // import AxiosService from "../../components/utils/ApiService"; 162 | // import { useNavigate, Link } from "react-router-dom"; 163 | // import signincss from "./signin.module.css"; 164 | // import Spinner from "../../components/utils/Sipnners"; 165 | 166 | // function SignIn() { 167 | // const [email, setEmail] = useState(""); 168 | // const [password, setPassword] = useState(""); 169 | // const [loading, setLoading] = useState(false); 170 | // const navigate = useNavigate(); 171 | 172 | // const handleLogin = async (e) => { 173 | // e.preventDefault(); 174 | 175 | // try { 176 | // setLoading(true); // Set loading to true when starting the login process 177 | 178 | // const res = await AxiosService.post(`/user/login`, { 179 | // email, 180 | // password, 181 | // }); 182 | 183 | // if (res.status === 200) { 184 | // sessionStorage.setItem("token", res.data.token); 185 | // sessionStorage.setItem("userData", JSON.stringify(res.data.userData)); 186 | // if (res.data.userData.status === "InActive") { 187 | // navigate("/"); 188 | // toast.error("You are not Allow to login"); 189 | // } else if (res.data.userData.role === "admin") { 190 | // toast.success(res.data.message); 191 | // navigate("/dashboard"); 192 | // } else { 193 | // toast.success(res.data.message); 194 | // navigate("/userdash"); 195 | // } 196 | // } 197 | // } catch (error) { 198 | // toast.error(error.response.data.message); 199 | // } finally { 200 | // setLoading(false); // Set loading to false after the login attempt (success or failure) 201 | // } 202 | // }; 203 | 204 | // return ( 205 | // <> 206 | //
207 | //
208 | //
209 | //
210 | //
211 | //
212 | //
213 | //

Login

214 | //
215 | // setEmail(e.target.value)} 219 | // /> 220 | // 221 | //
222 | //
223 | // setPassword(e.target.value)} 227 | // /> 228 | // 229 | //
230 | //
231 | // 235 | // Forgot password? 236 | //
237 | // 244 | //
245 | //

246 | //
247 | //
248 | //
249 | //
250 | // 251 | // ); 252 | // } 253 | 254 | // export default SignIn; 255 | 256 | 257 | 258 | 259 | 260 | -------------------------------------------------------------------------------- /src/pages/TASK/SubmittedTaskPage.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState, useEffect } from "react"; 3 | import AxiosService from "../../components/utils/ApiService"; 4 | import { Card, Form, Pagination, Container, Row, Col } from "react-bootstrap"; 5 | import SearchIcon from "@mui/icons-material/Search"; 6 | import Spinner from "../../components/Spiner/Spiner"; // Replace with the correct path to your Spinner component 7 | 8 | const SubmittedTaskPage = () => { 9 | const [submittedTasks, setSubmittedTasks] = useState([]); 10 | const [filteredTasks, setFilteredTasks] = useState([]); 11 | const [searchTerm, setSearchTerm] = useState(""); 12 | const [statusFilter, setStatusFilter] = useState("Submitted"); 13 | const [loading, setLoading] = useState(true); 14 | const [totalTasks, setTotalTasks] = useState(0); 15 | const [pendingTasks, setPendingTasks] = useState(0); 16 | const [submittedTaskCount, setSubmittedTaskCount] = useState(0); 17 | const [currentPage, setCurrentPage] = useState(1); 18 | const [tasksPerPage] = useState(6); 19 | 20 | const fetchSubmittedTasks = async () => { 21 | try { 22 | setLoading(true); 23 | 24 | const params = statusFilter ? { status: statusFilter } : {}; 25 | const response = await AxiosService.get("/task/tasks/status", { params }); 26 | 27 | setTotalTasks(response.data.length); 28 | setPendingTasks(response.data.filter((task) => task.status === "Pending").length); 29 | setSubmittedTaskCount(response.data.filter((task) => task.status === "Submitted").length); 30 | setSubmittedTasks(response.data); 31 | setFilteredTasks(response.data); 32 | } catch (error) { 33 | console.error("Error fetching submitted tasks:", error); 34 | } finally { 35 | setLoading(false); 36 | } 37 | }; 38 | 39 | useEffect(() => { 40 | fetchSubmittedTasks(); 41 | }, [statusFilter]); 42 | 43 | useEffect(() => { 44 | filterTasks(); 45 | }, [searchTerm, statusFilter, submittedTasks]); 46 | 47 | const fetchIndividualUserEmail = async (userId) => { 48 | try { 49 | const response = await AxiosService.get(`/user/getuser/${userId}`); 50 | return response.data.email; 51 | } catch (error) { 52 | console.error("Error fetching individual user:", error); 53 | return ""; // Return an empty string or handle the error as needed 54 | } 55 | }; 56 | const filterTasks = async () => { 57 | const lowerCaseSearchTerm = searchTerm.toLowerCase(); 58 | 59 | const filteredTasks = await Promise.all( 60 | submittedTasks.map(async (task) => { 61 | const email = await fetchIndividualUserEmail(task.assignedTo); 62 | return { ...task, email }; 63 | }) 64 | ); 65 | 66 | const filteredAndSearchedTasks = filteredTasks.filter((task) => { 67 | const lowerCaseTaskId = task._id.toLowerCase(); 68 | const taskIdIncludes = lowerCaseTaskId.includes(lowerCaseSearchTerm); 69 | const emailIncludes = task.email && task.email.toLowerCase().includes(lowerCaseSearchTerm); 70 | const titleIncludes = task.title.toLowerCase().includes(lowerCaseSearchTerm); 71 | const descriptionIncludes = task.description.toLowerCase().includes(lowerCaseSearchTerm); 72 | 73 | return taskIdIncludes || emailIncludes || titleIncludes || descriptionIncludes; 74 | }); 75 | 76 | setFilteredTasks(filteredAndSearchedTasks); 77 | }; 78 | 79 | 80 | const handleSearch = (e) => { 81 | setSearchTerm(e.target.value); 82 | }; 83 | 84 | const handleStatusFilterChange = (e) => { 85 | const selectedStatus = e.target.value; 86 | setStatusFilter(selectedStatus); 87 | }; 88 | 89 | const indexOfLastTask = currentPage * tasksPerPage; 90 | const indexOfFirstTask = indexOfLastTask - tasksPerPage; 91 | const currentTasks = filteredTasks.slice(indexOfFirstTask, indexOfLastTask); 92 | 93 | const paginate = (pageNumber) => setCurrentPage(pageNumber); 94 | 95 | return ( 96 | <> 97 | {/* Show the spinner when loading */} 98 | 99 | 100 | 101 | 102 | Submitted Tasks 103 | 104 | 105 | 106 | 107 | 108 |
109 | {statusFilter === "Pending" && ( 110 |
111 |

Pending Tasks: {pendingTasks}

112 |
113 | )} 114 | 115 | {statusFilter === "Submitted" && ( 116 |
117 |

Submitted Tasks: {submittedTaskCount}

118 |
119 | )} 120 |
121 | 122 | {loading && } 123 | 124 |
125 | 132 | 133 | 134 | 135 |
136 | 137 | 138 | 139 | 147 | 148 |
149 | 150 |
154 | {currentTasks.length === 0 ? ( 155 |

No submitted tasks found

156 | ) : ( 157 | 158 | {currentTasks.map((task) => ( 159 | 160 | 161 | 162 | 163 | Title: {task.title} 164 | 165 | 166 | Description: {task.description} 167 | 168 | 169 | FrontendUrl: {task.frontendUrl} 170 | 171 | 172 | BackendUrl: {task.backendUrl} 173 | 174 | 175 | Submitted By: {task.assignedTo} 176 | 177 | Email: {task.email} 178 | Status: {task.status} 179 | 180 | 181 | 182 | ))} 183 | 184 | )} 185 |
186 | 187 |
188 | 189 | {[ 190 | ...Array( 191 | Math.ceil(filteredTasks.length / tasksPerPage) 192 | ).keys(), 193 | ].map((number) => ( 194 | paginate(number + 1)} 198 | > 199 | {number + 1} 200 | 201 | ))} 202 | 203 |
204 |
205 |
206 |
207 | 208 | ); 209 | }; 210 | 211 | export default SubmittedTaskPage; 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /src/pages/Users/Edit.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react'; 2 | import { NavLink, useParams, useNavigate } from 'react-router-dom'; 3 | import { updatedata } from '../../components/context/ContextProvider'; 4 | import { toast } from 'react-toastify' 5 | import AxiosService from '../../components/utils/ApiService'; 6 | import Spinner from '../../components/utils/Sipnners'; 7 | const Edit = () => { 8 | const { updata, setUPdata } = useContext(updatedata); 9 | const navigate = useNavigate(); // Use useNavigate instead of useHistory 10 | const [loading, setLoading] = useState(true); 11 | const [inpval, setINP] = useState({ 12 | name: "", 13 | email: "", 14 | status: "", 15 | mobile: "", 16 | password: "", 17 | add: "", 18 | desc: "" 19 | }); 20 | 21 | const setdata = (e) => { 22 | console.log(e.target.value); 23 | const { name, value } = e.target; 24 | setINP((preval) => { 25 | return { 26 | ...preval, 27 | [name]: value 28 | } 29 | }) 30 | } 31 | 32 | const { id } = useParams(""); 33 | console.log(id); 34 | 35 | // const getdata = async () => { 36 | // const res = await fetch(`http://localhost:8000/user/getuser/${id}`, { 37 | // method: "GET", 38 | // headers: { 39 | // "Content-Type": "application/json" 40 | // } 41 | // }); 42 | 43 | // const data = await res.json(); 44 | // console.log(data); 45 | 46 | // if (res.status === 422 || !data) { 47 | // console.log("error "); 48 | // } else { 49 | // setINP(data); 50 | // console.log("get data"); 51 | // } 52 | // } 53 | 54 | const getdata = async () => { 55 | try { 56 | const response = await AxiosService.get(`/user/getuser/${id}`); 57 | const data = response.data; 58 | 59 | console.log(data); 60 | 61 | if (response.status === 422 || !data) { 62 | console.log('error'); 63 | 64 | } else { 65 | setINP(data); 66 | console.log('get data'); 67 | setLoading(false); 68 | 69 | } 70 | } catch (error) { 71 | console.error('Error fetching data:', error); 72 | setLoading(false); 73 | } 74 | }; 75 | 76 | 77 | useEffect(() => { 78 | getdata(); 79 | }, []); 80 | 81 | // const updateuser = async (e) => { 82 | // e.preventDefault(); 83 | 84 | // const { name, email, password, add, mobile, desc, status } = inpval; 85 | 86 | // // Client-side validation 87 | // if (!name || !email || !password || !mobile || !add || !desc || !status) { 88 | // toast.error("Please fill in all the required fields."); 89 | // return; // Exit the function early if validation fails 90 | // } 91 | 92 | // try { 93 | // const res2 = await fetch(`/user/updateuser/${id}`, { 94 | // method: "PUT", 95 | // headers: { 96 | // "Content-Type": "application/json" 97 | // }, 98 | // body: JSON.stringify({ 99 | // name, email, password, add, mobile, desc, status 100 | // }) 101 | // }); 102 | 103 | // if (!res2.ok) { 104 | // throw new Error("Failed to update user"); 105 | // } 106 | 107 | // const data2 = await res2.json(); 108 | // console.log(data2); 109 | 110 | // navigate("/home"); 111 | // setUPdata(data2); 112 | // toast.success("User Updated Successfully"); 113 | // } catch (error) { 114 | // console.error("Error updating user:", error); 115 | // toast.error("Email or Mobile is Already exited"); 116 | // } 117 | // }; 118 | const updateuser = async (e) => { 119 | e.preventDefault(); 120 | 121 | const { name, email, password, add, mobile, desc, status } = inpval; 122 | 123 | // Client-side validation 124 | if (!name || !email || !password || !mobile || !add || !desc || !status) { 125 | toast.error("Please fill in all the required fields."); 126 | return; // Exit the function early if validation fails 127 | } 128 | 129 | // Password regex pattern: At least 8 characters, at least one uppercase letter, one lowercase letter, and one digit 130 | const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/; 131 | 132 | // Validate the password 133 | if (!passwordRegex.test(password)) { 134 | toast.error("Password must be at least 8 characters, with at least one uppercase letter, one lowercase letter, and one digit."); 135 | return; 136 | } 137 | 138 | 139 | 140 | try { 141 | const response = await AxiosService.put(`/user/updateuser/${id}`, { 142 | name, 143 | email, 144 | password, 145 | add, 146 | mobile, 147 | desc, 148 | status 149 | }, { 150 | headers: { 151 | "Content-Type": "application/json" 152 | } 153 | }); 154 | 155 | // Axios automatically throws an error for non-2xx responses 156 | const data2 = response.data; 157 | console.log(data2); 158 | 159 | navigate("/home"); 160 | setUPdata(data2); 161 | toast.success("User Updated Successfully"); 162 | } catch (error) { 163 | console.error("Error updating user:", error); 164 | 165 | // Check if the error is due to duplicate email or mobile 166 | if (error.response && (error.response.status === 400 || error.response.status === 409)) { 167 | toast.error("Email or Mobile is Already existed"); 168 | } else { 169 | toast.error("Failed to update user"); 170 | } 171 | } 172 | }; 173 | 174 | return ( 175 |
176 |
177 |
178 |
179 | 182 | 183 |
184 |
185 | 188 | 189 |
190 |
191 | 194 | 198 |
199 |
200 | 203 | 204 |
205 |
206 | 209 | 210 |
211 |
212 | 215 | 216 |
217 |
218 | 221 | 222 |
223 |
224 | 227 |
228 |
229 |
230 |
231 | ) 232 | } 233 | 234 | export default Edit; -------------------------------------------------------------------------------- /src/pages/Password/Resetpassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useFormik } from "formik"; 3 | import * as Yup from "yup"; 4 | import { FaEye, FaEyeSlash } from "react-icons/fa"; 5 | import AxiosService from "../../components/utils/ApiService"; 6 | import { toast } from "react-toastify"; 7 | import { useNavigate } from "react-router-dom"; 8 | import Spinner from "../../components/utils/Sipnners"; // Replace with the correct path to your Spinner component 9 | import resetcss from "./resetPassword.module.css"; 10 | 11 | function ResetPassword() { 12 | const navigate = useNavigate(); 13 | const [showPassword, setShowPassword] = useState(false); 14 | 15 | const validationSchema = Yup.object().shape({ 16 | token: Yup.string().required("OTP is required"), 17 | password: Yup.string() 18 | .required("Password is required") 19 | .matches( 20 | /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/, 21 | "Password must be at least 8 characters, with at least one uppercase letter, one lowercase letter, and one digit." 22 | ), 23 | confirmPassword: Yup.string() 24 | .oneOf([Yup.ref("password"), null], "Passwords must match") 25 | .required("Confirm Password is required"), 26 | }); 27 | 28 | const handleSubmit = async (values, { setSubmitting }) => { 29 | try { 30 | const response = await AxiosService.post("/user/reset-password", { 31 | token: values.token, 32 | password: values.password, 33 | }); 34 | 35 | // Assuming your backend returns a message on success 36 | toast.success(response.data.message); 37 | navigate("/"); 38 | } catch (error) { 39 | // Handle errors from the backend 40 | if (error.response.status === 404) { 41 | toast.error(error.response.data.message); 42 | } 43 | if (error.response.status === 401) { 44 | toast.error(error.response.data.message); 45 | navigate("/"); 46 | } 47 | } finally { 48 | setSubmitting(false); 49 | } 50 | }; 51 | 52 | const togglePasswordVisibility = () => { 53 | setShowPassword(!showPassword); 54 | }; 55 | 56 | const formik = useFormik({ 57 | initialValues: { 58 | token: "", 59 | password: "", 60 | confirmPassword: "", 61 | }, 62 | validationSchema: validationSchema, 63 | onSubmit: handleSubmit, 64 | }); 65 | 66 | return ( 67 | <> 68 |
69 |
70 |
71 |
72 |
73 |
74 |

Welcome back!

75 |

Reset Password

76 | 77 | 86 | {formik.touched.token && formik.errors.token && ( 87 |

{formik.errors.token}

88 | )} 89 | 90 |
91 | 104 | 112 | 113 |
114 | 115 | {formik.touched.password && formik.errors.password && ( 116 |

{formik.errors.password}

117 | )} 118 | 119 | 128 | {formik.touched.confirmPassword && formik.errors.confirmPassword && ( 129 |

{formik.errors.confirmPassword}

130 | )} 131 | 132 | 135 |
136 |
137 | 138 | ); 139 | } 140 | 141 | export default ResetPassword; 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | // import React from "react"; 174 | // import { useFormik } from "formik"; 175 | // import * as Yup from "yup"; 176 | // import AxiosService from "../../components/utils/ApiService"; 177 | // import { toast } from "react-toastify"; 178 | // import { useNavigate } from "react-router-dom"; 179 | // import Spinner from "../../components/utils/Sipnners"; // Replace with the correct path to your Spinner component 180 | // import resetcss from "./resetPassword.module.css"; 181 | 182 | // function ResetPassword() { 183 | // const navigate = useNavigate(); 184 | // const validationSchema = Yup.object().shape({ 185 | // token: Yup.string().required("OTP is required"), 186 | // password: Yup.string().required("Password is required"), 187 | // confirmPassword: Yup.string() 188 | // .oneOf([Yup.ref("password"), null], "Passwords must match") 189 | // .required("Confirm Password is required"), 190 | // }); 191 | 192 | // const handleSubmit = async (values, { setSubmitting }) => { 193 | // try { 194 | // const response = await AxiosService.post("/user/reset-password", { 195 | // token: values.token, 196 | // password: values.password, 197 | // }); 198 | 199 | // // Assuming your backend returns a message on success 200 | // toast.success(response.data.message); 201 | // navigate("/"); 202 | // } catch (error) { 203 | // // Handle errors from the backend 204 | // if (error.response.status === 404) { 205 | // toast.error(error.response.data.message); 206 | // } 207 | // if (error.response.status === 401) { 208 | // toast.error(error.response.data.message); 209 | // navigate("/"); 210 | // } 211 | // } finally { 212 | // setSubmitting(false); 213 | // } 214 | // }; 215 | 216 | // const formik = useFormik({ 217 | // initialValues: { 218 | // token: "", 219 | // password: "", 220 | // confirmPassword: "", 221 | // }, 222 | // validationSchema: validationSchema, 223 | // onSubmit: handleSubmit, 224 | // }); 225 | 226 | // return ( 227 | // <> 228 | //
229 | //
230 | //
231 | //
232 | //
233 | //
234 | //

Welcome back!

235 | //

Reset Password

236 | 237 | // 246 | // {formik.touched.token && formik.errors.token && ( 247 | //

{formik.errors.token}

248 | // )} 249 | 250 | // 259 | // {formik.touched.password && formik.errors.password && ( 260 | //

{formik.errors.password}

261 | // )} 262 | 263 | // 272 | // {formik.touched.confirmPassword && formik.errors.confirmPassword && ( 273 | //

{formik.errors.confirmPassword}

274 | // )} 275 | 276 | // 279 | //
280 | //
281 | // 282 | // ); 283 | // } 284 | 285 | // export default ResetPassword; 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | -------------------------------------------------------------------------------- /src/pages/Tickets/Tickets.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import TicketTile from "../../components/common/TicketTile"; 3 | import { useParams } from "react-router-dom"; 4 | import useLogout from "../../components/hooks/useLogout"; 5 | import { toast } from "react-toastify"; 6 | import AxiosService from "../../components/utils/ApiService"; 7 | import Button from "react-bootstrap/Button"; 8 | import Form from "react-bootstrap/Form"; 9 | import { useNavigate } from "react-router-dom"; 10 | import Container from "react-bootstrap/Container"; 11 | import Row from "react-bootstrap/Row"; 12 | import Col from "react-bootstrap/Col"; 13 | import Spinner from "../../components/Spiner/Spiner" 14 | 15 | function Ticket() { 16 | let logout = useLogout(); 17 | let userData = JSON.parse(sessionStorage.getItem("userData")); 18 | 19 | return ( 20 | 21 | 22 | 23 | {userData.role === "admin" ? : } 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | function EditTicket() { 31 | let params = useParams(); 32 | let [title, setTitle] = useState(""); 33 | let [imageUrl, setImage] = useState(""); 34 | let [description, setDescription] = useState(""); 35 | let [ticket, setTicket] = useState({}); 36 | let navigate = useNavigate(); 37 | let logout = useLogout(); 38 | 39 | let getTicket = async () => { 40 | try { 41 | let res = await AxiosService.get(`/tickets/${params.id}`); 42 | if (res.status === 200) { 43 | setTitle(res.data.ticket.title); 44 | setImage(res.data.ticket.imageUrl); 45 | setDescription(res.data.ticket.description); 46 | setTicket(res.data.ticket); 47 | } 48 | } catch (error) { 49 | toast.error(error.response.data.message); 50 | if (error.response.status === 401) { 51 | logout(); 52 | } 53 | } 54 | }; 55 | 56 | useEffect(() => { 57 | if (params.id) { 58 | getTicket(); 59 | } else { 60 | logout(); 61 | } 62 | }, [params.id]); 63 | 64 | let editticket = async () => { 65 | try { 66 | let res = await AxiosService.put(`/tickets/edit/${ticket._id}`, { 67 | title, 68 | imageUrl, 69 | description, 70 | }); 71 | if (res.status === 200) { 72 | toast.success(res.data.message); 73 | navigate("/tickets"); 74 | } 75 | } catch (error) { 76 | console.log(error); 77 | toast.error(error.response.data.message); 78 | if (error.response.status === 401) { 79 | logout(); 80 | } 81 | } 82 | }; 83 | 84 | return ( 85 |
86 |
87 |

Share Your Tickets!

88 |
89 | 90 | Title 91 | setTitle(e.target.value)} 96 | /> 97 | 98 | 99 | 100 | Image Url 101 | setImage(e.target.value)} 106 | /> 107 | 108 | 109 | 110 | Description 111 | setDescription(e.target.value)} 117 | /> 118 | 119 |
120 | 123 |   124 | 127 |
128 |
129 |
130 |
131 | ); 132 | } 133 | 134 | // ... (your imports) 135 | 136 | function AdminTicket() { 137 | const params = useParams(); 138 | const [ticket, setTicket] = useState({}); 139 | const [reason, setReason] = useState(""); 140 | const [loading, setLoading] = useState(true); // Add loading state 141 | const logout = useLogout(); 142 | const navigate = useNavigate(); 143 | 144 | const getTicket = async () => { 145 | try { 146 | const res = await AxiosService.get(`/tickets/${params.id}`); 147 | if (res.status === 200) { 148 | setTicket(res.data.ticket); 149 | } 150 | } catch (error) { 151 | toast.error(error.response.data.message); 152 | if (error.response.status === 401) { 153 | logout(); 154 | } 155 | } finally { 156 | // Set loading to false when the data is received (whether successful or not) 157 | setLoading(false); 158 | } 159 | }; 160 | 161 | useEffect(() => { 162 | if (params.id) { 163 | getTicket(); 164 | } else { 165 | logout(); 166 | } 167 | }, [params.id]); 168 | 169 | const changeStatus = async (status) => { 170 | try { 171 | if (!reason && status === "resolved") { 172 | toast.error("Please provide a Solution for the Resolve."); 173 | return; 174 | } 175 | 176 | // Set loading to true when making a request to change status 177 | setLoading(true); 178 | 179 | const res = await AxiosService.put( 180 | `/tickets/status/${ticket._id}/${status}`, 181 | { 182 | reason: reason, 183 | } 184 | ); 185 | 186 | if (res.status === 200) { 187 | getTicket(); 188 | setReason(""); // Clear the reason after a successful status change 189 | 190 | // Provide different responses for each status change 191 | switch (status) { 192 | case "pending": 193 | toast.success("Ticket status changed to Pending."); 194 | navigate("/tickets"); 195 | break; 196 | case "approved": 197 | toast.success("Ticket status changed to Approved."); 198 | navigate("/tickets"); 199 | break; 200 | case "resolved": 201 | toast.success("Ticket status changed to Resolved."); 202 | navigate("/tickets"); 203 | break; 204 | default: 205 | break; 206 | } 207 | } 208 | } catch (error) { 209 | toast.error(error.response.data.message); 210 | if (error.response.status === 401) { 211 | logout(); 212 | } 213 | } finally { 214 | // Set loading back to false when the request is complete (success or error) 215 | setLoading(false); 216 | } 217 | }; 218 | 219 | return ( 220 | 229 | {loading ? ( // Display spinner while loading 230 | 231 | ) : ( 232 | 233 | 244 |
248 | 249 |
250 |
251 | {ticket.status !== "pending" ? ( 252 | 258 | ) : ( 259 | <> 260 | )} 261 |   262 | {ticket.status !== "approved" ? ( 263 | 269 | ) : ( 270 | <> 271 | )} 272 |   273 | {ticket.status !== "resolved" ? ( 274 | <> 275 | 281 | 285 | setReason(e.target.value)} 290 | style={{ height: "100px" }} 291 | /> 292 | 293 | 294 | ) : ( 295 | <> 296 | )} 297 |
298 | 299 |
300 | )} 301 |
302 | ); 303 | } 304 | 305 | export default Ticket; 306 | --------------------------------------------------------------------------------