├── .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 | //
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 |
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 |
53 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
61 |
62 |
63 |
64 | {/* Card for Resolved Tickets */}
65 |
66 |
67 |
68 | Resolved Tickets: {ticketData.resolvedTickets}
69 |
70 |
80 |
81 |
82 |
83 | {/* Card for Approved Tickets */}
84 |
85 |
86 |
87 | Approved Tickets: {ticketData.approvedTickets}
88 |
89 |
99 |
100 |
101 |
102 | {/* Card for Pending Tickets */}
103 |
104 |
105 |
106 | Pending Tickets: {ticketData.pendingTickets}
107 |
108 |
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 |
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 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Resolved Tickets:{ticketData.resolvedTickets}
67 |
68 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | Approved Tickets:{ticketData.approvedTickets}
84 |
85 |
93 |
94 |
95 |
96 | {/* Add similar updates for other cards */}
97 |
98 |
99 |
100 | {" "}
101 | Pending Tickets:{ticketData.pendingTickets}
102 |
103 |
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 |
173 | >
174 | }
175 | />
176 |
180 |
181 |
182 |
183 |
184 | >
185 | }
186 | />
187 |
191 |
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 | | Id |
154 | Title |
155 | Description |
156 | Created Date |
157 | Status |
158 | Action |
159 |
160 |
161 |
162 | {currentTasks.map((task, id) => (
163 |
164 | | {id + 1} |
165 |
166 | {task.title} |
167 | {task.description} |
168 | {new Date(task.createdAt).toLocaleDateString()} |
169 | {task.status} |
170 |
171 | {task.status === "Submitted" ? (
172 |
178 | ) : (
179 |
180 |
181 |
182 | )}
183 | |
184 |
185 | ))}
186 |
187 |
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 | | Id |
141 | Name |
142 | Email |
143 | Role |
144 | Status |
145 | Number |
146 | Actions |
147 |
148 |
149 |
150 | {currentItems.map((user, id) => (
151 |
152 | | {id + 1} |
153 | {user.name} |
154 | {user.email} |
155 | {user.role} |
156 |
163 | {user.status}
164 | |
165 | {user.mobile} |
166 |
167 |
168 | {" "}
169 |
172 |
173 |
174 | {" "}
175 |
178 |
179 |
185 | |
186 |
187 | ))}
188 |
189 |
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 | | Id |
131 | Title |
132 | Image |
133 | Solution |
134 | Created Date |
135 | Status |
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 | | {i + 1 + indexOfFirstItem} |
162 | {e.title} |
163 |
164 |
169 | |
170 | {solutionContent} |
171 | {createdDate} |
172 | {e.status} |
173 |
174 | );
175 | })}
176 |
177 |
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 |
57 |
58 |
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 | //
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 |
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 |
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 | //
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 |
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 |
--------------------------------------------------------------------------------