├── Backend
├── README.md
├── .gitignore
├── config.npmrc
├── routes
│ ├── historyRoutes.js
│ ├── productRoutes.js
│ ├── user_routes.js
│ ├── locationRoutes.js
│ ├── companyRoutes.js
│ └── analyticsRoutes.js
├── db
│ └── user_db.js
├── middlewares
│ └── user_auth.js
├── utils
│ └── user_utils.js
├── package.json
├── models
│ ├── company_model.js
│ ├── locations_models.js
│ ├── user_model.js
│ ├── history_model.js
│ └── product_model.js
├── app.js
└── controllers
│ ├── user_controllers.js
│ └── product_controller.js
├── Frontend
├── README.md
├── src
│ ├── index.css
│ ├── App.jsx
│ ├── screens
│ │ ├── users
│ │ │ ├── components
│ │ │ │ ├── ChangeRolePopup.jsx
│ │ │ │ └── ManageUserTableRow.jsx
│ │ │ └── UserManagementScreen.jsx
│ │ ├── login
│ │ │ ├── AuthLayout.jsx
│ │ │ ├── LoginScreen.jsx
│ │ │ └── SignupScreen.jsx
│ │ ├── dashboard
│ │ │ ├── components
│ │ │ │ ├── PieChart.jsx
│ │ │ │ └── AnalyticsComponent.jsx
│ │ │ ├── DashBoardScreen.jsx
│ │ │ └── DashBoardLayout.jsx
│ │ ├── product
│ │ │ ├── ProductInfoScreen.jsx
│ │ │ ├── ProductHistoryScreen.jsx
│ │ │ ├── ProductsScreen.jsx
│ │ │ ├── AddNewProductScreen.jsx
│ │ │ └── ProductEditScreen.jsx
│ │ ├── locations
│ │ │ ├── NewLocationScreen.jsx
│ │ │ ├── LocationsScreen.jsx
│ │ │ └── EditLocationScreen.jsx
│ │ ├── brands
│ │ │ ├── NewBrandsScreen.jsx
│ │ │ ├── BrandsScreen.jsx
│ │ │ └── EditBrandsScreen.jsx
│ │ └── InventoryFormScreen.jsx
│ ├── components
│ │ ├── PopUpComponenet.jsx
│ │ ├── LoadingIndicator.jsx
│ │ ├── ShowSuccessMesasge.jsx
│ │ ├── ShowErrorMessage.jsx
│ │ ├── LogoutButton.jsx
│ │ ├── HeaderBar.jsx
│ │ ├── SideNavbar.jsx
│ │ └── WarrantyExpiringProductsTableComponent.jsx
│ ├── assets
│ │ ├── menu.svg
│ │ ├── user-logo.svg
│ │ ├── react.svg
│ │ ├── admin-logo.svg
│ │ ├── authenticate.svg
│ │ └── undraw_empty_re.svg
│ ├── main.jsx
│ └── router.jsx
├── postcss.config.js
├── vite.config.js
├── tailwind.config.js
├── .gitignore
├── index.html
├── .eslintrc.cjs
├── package.json
└── public
│ └── vite.svg
└── Readme.md
/Backend/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Frontend/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Backend/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | .env
3 |
--------------------------------------------------------------------------------
/Backend/config.npmrc:
--------------------------------------------------------------------------------
1 | proxy=null
2 | https-proxy=null
--------------------------------------------------------------------------------
/Frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
--------------------------------------------------------------------------------
/Frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/Frontend/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function App() {
4 | return (
5 |
App
6 | )
7 | }
8 |
9 | export default App
--------------------------------------------------------------------------------
/Frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------
/Frontend/src/screens/users/components/ChangeRolePopup.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function ChangeRolePopup() {
4 | return (
5 | ChangeRolePopup
6 | )
7 | }
8 |
9 | export default ChangeRolePopup
--------------------------------------------------------------------------------
/Frontend/src/components/PopUpComponenet.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function PopUpComponenet() {
4 | return (
5 |
6 | )
7 | }
8 |
9 | export default PopUpComponenet
--------------------------------------------------------------------------------
/Frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/Frontend/src/screens/login/AuthLayout.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Outlet } from "react-router-dom";
3 |
4 | function AuthLayout() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default AuthLayout;
13 |
--------------------------------------------------------------------------------
/Frontend/src/assets/menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Frontend/.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
26 |
--------------------------------------------------------------------------------
/Backend/routes/historyRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import HistoryModel from "../models/history_model.js";
3 |
4 | const historyRouter = express.Router();
5 |
6 | // historyRouter.get("/", async (req, res) => {
7 | // const { _id } = req.body;
8 |
9 |
10 | // const historyData = await HistoryModel.findById(_id);
11 | // });
12 |
13 |
14 |
15 | export default historyRouter;
16 |
--------------------------------------------------------------------------------
/Backend/db/user_db.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import dotenv from "dotenv";
3 | dotenv.config();
4 |
5 | export const connectdb = () => {
6 | mongoose
7 | .connect(process.env.MONGODB_URI, {
8 | dbName: "ims",
9 | })
10 | .then((c) => {
11 | console.log(`database connected with ${c.connection.host}`);
12 | })
13 | .catch((e) => {
14 | console.log(e);
15 | });
16 | };
17 |
--------------------------------------------------------------------------------
/Frontend/src/components/LoadingIndicator.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function LoadingIndicator() {
4 | return (
5 |
8 | );
9 | }
10 |
11 | export default LoadingIndicator;
12 |
--------------------------------------------------------------------------------
/Frontend/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 { RouterProvider } from "react-router-dom";
6 | import router from "./router.jsx";
7 |
8 | ReactDOM.createRoot(document.getElementById("root")).render(
9 |
10 |
11 |
12 | );
13 |
14 |
--------------------------------------------------------------------------------
/Frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vite + React
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/Frontend/src/components/ShowSuccessMesasge.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function ShowSuccessMesasge({ children }) {
4 | return (
5 |
6 |
7 | {children}
8 |
9 |
10 | );
11 | }
12 |
13 | export default ShowSuccessMesasge;
14 |
--------------------------------------------------------------------------------
/Frontend/src/components/ShowErrorMessage.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function ShowErrorMessage({ children }) {
4 | return (
5 |
6 |
7 | Oops some thing went wrong! {children}
8 |
9 |
10 | );
11 | }
12 |
13 | export default ShowErrorMessage;
14 |
--------------------------------------------------------------------------------
/Backend/middlewares/user_auth.js:
--------------------------------------------------------------------------------
1 | import User from "../models/user_model.js";
2 | import jwt from "jsonwebtoken";
3 |
4 | export const isAuthenticated = async (req, res, next) => {
5 | const { token } = req.cookies;
6 | console.log("token", token);
7 | if (!token) {
8 | return res.status(400).json({
9 | success: false,
10 | message: "user is not found or Login first",
11 | });
12 | }
13 |
14 | const decode = jwt.verify(token, process.env.SECRET_KEY);
15 |
16 | req.user = await User.findById(decode._id);
17 | next();
18 | };
19 |
--------------------------------------------------------------------------------
/Backend/utils/user_utils.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 |
3 | export const sendcookie = (user, res, message, statuscode = 200) => {
4 | const token = jwt.sign({ _id: user._id }, process.env.SECRET_KEY);
5 | res
6 | .status(statuscode)
7 | .cookie("token", token, {
8 | httpOnly: true,
9 | maxAge: 30 * 60 * 10000,
10 | sameSite: process.env.NODE_ENV === "development" ? "lax" : "none",
11 | secure: process.env.NODE_ENV === "development" ? false : true,
12 | })
13 | .json({
14 | success: true,
15 | message,
16 | });
17 | };
18 |
--------------------------------------------------------------------------------
/Frontend/src/assets/user-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backendapi",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "type": "module",
7 | "engines": {
8 | "node": "18.8.0"
9 | },
10 | "scripts": {
11 | "start": "node app.js",
12 | "dev": "nodemon app.js"
13 | },
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "bcrypt": "^5.1.1",
18 | "cookie-parser": "^1.4.6",
19 | "cors": "^2.8.5",
20 | "dotenv": "^16.3.1",
21 | "express": "^4.18.2",
22 | "jsonwebtoken": "^9.0.2",
23 | "mongodb": "^6.2.0",
24 | "mongoose": "^8.0.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/Backend/models/company_model.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const companySchema = new mongoose.Schema(
4 | {
5 | name: { type: String, required: true },
6 | description: { type: String, required: true },
7 | createdBy: {
8 | ref: "User",
9 | type: mongoose.Schema.Types.ObjectId,
10 | required: true,
11 | },
12 |
13 | editedBy: {
14 | ref: "User",
15 | type: mongoose.Schema.Types.ObjectId,
16 | },
17 | },
18 |
19 | {
20 | timestamps: true,
21 | }
22 | );
23 |
24 | const LocationModel = mongoose.model("Company", companySchema);
25 |
26 | export default LocationModel;
27 |
--------------------------------------------------------------------------------
/Backend/models/locations_models.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const locationSchema = new mongoose.Schema(
4 | {
5 | createdBy: {
6 | ref: "User",
7 | type: mongoose.Schema.Types.ObjectId,
8 | required: true,
9 | },
10 |
11 | editedBy: {
12 | ref: "User",
13 | type: mongoose.Schema.Types.ObjectId,
14 | },
15 |
16 | name: { type: String, required: true },
17 | description: { type: String, required: true },
18 | },
19 | {
20 | timestamps: true,
21 | }
22 | );
23 |
24 | const LocationModel = mongoose.model("Location", locationSchema);
25 |
26 | export default LocationModel;
27 |
--------------------------------------------------------------------------------
/Frontend/src/screens/dashboard/components/PieChart.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useRef, useEffect } from "react";
2 | import { Line, Pie } from "react-chartjs-2";
3 | import "chart.js/auto";
4 |
5 | const PieChart = ({ data }) => {
6 | const ref = useRef(null);
7 | useEffect(() => {
8 | return ref?.current?.destroy();
9 | }, []);
10 | return (
11 |
23 | );
24 | };
25 |
26 | export default PieChart;
27 |
--------------------------------------------------------------------------------
/Frontend/.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/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/Backend/models/user_model.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | const Userschema = new mongoose.Schema(
3 | {
4 | name: {
5 | type: String,
6 | required: true,
7 | },
8 | email: {
9 | type: String,
10 | required: true,
11 | unique: true,
12 | },
13 | password: {
14 | type: String,
15 | required: true,
16 | select: false, //password won't included mainly used for sensitive info
17 | },
18 | role: {
19 | type: String,
20 | enum: ["user", "admin"],
21 | default: "user",
22 | },
23 | },
24 | {
25 | timeStamps: true,
26 | }
27 | );
28 |
29 | const User = mongoose.model("User", Userschema);
30 |
31 | export default User;
32 |
--------------------------------------------------------------------------------
/Frontend/src/screens/dashboard/DashBoardScreen.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link, useOutletContext } from "react-router-dom";
4 | import AnalyticsComponent from "./components/AnalyticsComponent";
5 | import WarrantyExpiringProductsTablesComponent from "../../components/WarrantyExpiringProductsTableComponent";
6 |
7 | function DashBoardScreen() {
8 | // const [data] = useOutletContext();
9 | // // console.log("data ", data)
10 |
11 | return (
12 |
17 | );
18 | }
19 |
20 | export default DashBoardScreen;
21 |
--------------------------------------------------------------------------------
/Backend/models/history_model.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const historySchema = new mongoose.Schema(
4 | {
5 | location: {
6 | type: mongoose.Schema.Types.ObjectId,
7 | ref: "Location",
8 | required: true,
9 | },
10 | status: [
11 | {
12 | name: {
13 | type: String,
14 | required: true,
15 | enum: ["repair", "in use", "not in use"],
16 | },
17 | date: {
18 | type: Date,
19 | default: Date.now(),
20 | },
21 | },
22 | ],
23 | },
24 | {
25 | timestamps: true,
26 | }
27 | );
28 |
29 | const HistoryModel = mongoose.model("History", historySchema);
30 |
31 | export default HistoryModel;
32 |
--------------------------------------------------------------------------------
/Backend/routes/productRoutes.js:
--------------------------------------------------------------------------------
1 | // routes/productRoutes.js
2 |
3 | import express from "express";
4 | import {
5 | createProduct,
6 | getAllProducts,
7 | getProductById,
8 | updateProductById,
9 | deleteProductById,
10 | getProductHistroy,
11 | } from "../controllers/product_controller.js";
12 | import { isAuthenticated } from "../middlewares/user_auth.js";
13 |
14 | const productRouter = express.Router();
15 |
16 | // Create a new product
17 | productRouter.post("/", isAuthenticated, createProduct);
18 |
19 | // Get all products
20 | productRouter.get("/", getAllProducts);
21 |
22 | // Get a single product by ID
23 | productRouter.get("/:id", getProductById);
24 | productRouter.get("/:id/history", getProductHistroy);
25 |
26 | // Update a product by ID
27 | productRouter.put("/:id", isAuthenticated, updateProductById);
28 |
29 | // Delete a product by ID
30 | productRouter.delete("/:id", isAuthenticated, deleteProductById);
31 |
32 | //search bar feature
33 | //expired products
34 | export default productRouter;
35 |
--------------------------------------------------------------------------------
/Frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front-end",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "axios": "^1.6.8",
14 | "chartjs": "^0.3.24",
15 | "dagre": "^0.8.5",
16 | "localforage": "^1.10.0",
17 | "match-sorter": "^6.3.4",
18 | "react": "^18.2.0",
19 | "react-chartjs-2": "^5.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-icons": "^5.2.1",
22 | "react-router-dom": "^6.23.1",
23 | "reactflow": "^11.11.3",
24 | "sort-by": "^1.2.0"
25 | },
26 | "devDependencies": {
27 | "@types/react": "^18.2.66",
28 | "@types/react-dom": "^18.2.22",
29 | "@vitejs/plugin-react": "^4.2.1",
30 | "autoprefixer": "^10.4.19",
31 | "eslint": "^8.57.0",
32 | "eslint-plugin-react": "^7.34.1",
33 | "eslint-plugin-react-hooks": "^4.6.0",
34 | "eslint-plugin-react-refresh": "^0.4.6",
35 | "postcss": "^8.4.38",
36 | "tailwindcss": "^3.4.3",
37 | "vite": "^5.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Backend/routes/user_routes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | getAllUsers,
4 | getMyprofile,
5 | login,
6 | logout,
7 | register,
8 | } from "../controllers/user_controllers.js";
9 | import { isAuthenticated } from "../middlewares/user_auth.js";
10 | import User from "../models/user_model.js";
11 |
12 | const router = express.Router();
13 |
14 | router.post("/new", register);
15 | router.post("/login", login);
16 | router.get("/logout", logout);
17 |
18 | router.get("/all", getAllUsers);
19 |
20 | router.patch("/chage-role", isAuthenticated, async (req, res) => {
21 | const { targetUserId, role } = req.body;
22 | const user = req.user;
23 | if (!targetUserId) {
24 | return res.status(404).json({ message: "please provide target user id" });
25 | }
26 | if (user.role !== "admin") {
27 | return res
28 | .status(400)
29 | .json({ message: "Not authorized to make this call" });
30 | }
31 |
32 | await User.findByIdAndUpdate(targetUserId, {
33 | $set: {
34 | role: role,
35 | },
36 | });
37 |
38 | return res.status(200).json({ message: "success" });
39 | });
40 |
41 | router.get("/me", isAuthenticated, getMyprofile);
42 |
43 | export default router;
44 |
--------------------------------------------------------------------------------
/Backend/models/product_model.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const Productschema = new mongoose.Schema({
4 | title: {
5 | type: String,
6 | required: true,
7 | },
8 | description: {
9 | type: String,
10 | },
11 | serialNo: {
12 | type: String,
13 | required: true,
14 | },
15 | createdBy: {
16 | type: mongoose.Schema.ObjectId,
17 | ref: "User",
18 | required: true,
19 | },
20 | createdAt: {
21 | type: Date,
22 | default: Date.now(),
23 | },
24 | rackMountable: {
25 | type: Boolean,
26 | default: false,
27 | },
28 | isPart: {
29 | type: Boolean,
30 | default: false,
31 | },
32 | manufacturer: {
33 | type: mongoose.Schema.Types.ObjectId,
34 | ref: "Company",
35 | },
36 |
37 | model: {
38 | type: String,
39 | required: true,
40 | },
41 |
42 | dateOfPurchase: {
43 | type: Date,
44 | required: true,
45 | },
46 | warrantyMonths: {
47 | type: Number,
48 | required: true,
49 | },
50 |
51 | user: {
52 | type: String,
53 | required: true,
54 | enum: ["normal user", "department", "admin"],
55 | default: "normal user",
56 | },
57 |
58 | history: [
59 | {
60 | type: mongoose.Schema.Types.ObjectId,
61 | ref: "History",
62 | },
63 | ],
64 | });
65 |
66 | const Product = mongoose.model("Product", Productschema);
67 |
68 | export default Product;
69 |
--------------------------------------------------------------------------------
/Frontend/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Frontend/src/components/LogoutButton.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { IoIosLogOut } from "react-icons/io";
3 | import LoadingIndicator from "./LoadingIndicator";
4 | import axios from "axios";
5 | import { useNavigate } from "react-router-dom";
6 | import { SERVER_URL } from "../router";
7 |
8 | function LogoutButton() {
9 | const [isLoading, setLoading] = useState(false);
10 | const navigator = useNavigate();
11 |
12 | const handleLogout = async () => {
13 | try {
14 | setLoading(true);
15 | const { data, status } = await axios.get(
16 | `${SERVER_URL}/api/v1/users/logout`,
17 | {
18 | withCredentials: true,
19 | headers: {
20 | "Content-Type": "application/json",
21 | },
22 | }
23 | );
24 |
25 | if (status === 200) {
26 | navigator("/", { replace: true });
27 | window.location.reload();
28 | }
29 | } catch (error) {
30 | console.error("Something went wrong:", error);
31 | } finally {
32 | setLoading(false);
33 | }
34 | };
35 | return (
36 |
52 | );
53 | }
54 |
55 | export default LogoutButton;
56 |
--------------------------------------------------------------------------------
/Backend/routes/locationRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import LocationModel from "../models/locations_models.js";
3 | import { isAuthenticated } from "../middlewares/user_auth.js";
4 |
5 | const locationRouter = express.Router();
6 |
7 | locationRouter.get("/", async (req, res) => {
8 | const locations = await LocationModel.find()
9 | .populate("createdBy")
10 | .populate("editedBy");
11 |
12 | return res.status(200).json(locations || []);
13 | });
14 |
15 | locationRouter.get("/:id", async (req, res) => {
16 | const locations = await LocationModel.findById(req.params.id)
17 | .populate("createdBy")
18 | .populate("editedBy");
19 |
20 | return res.status(200).json(locations || []);
21 | });
22 |
23 | locationRouter.patch("/:id", isAuthenticated, async (req, res, next) => {
24 | try {
25 | const { name, description } = req.body;
26 | const editedBy = req.user._id;
27 | await LocationModel.findByIdAndUpdate(req.params.id, {
28 | $set: {
29 | name,
30 | description,
31 | editedBy,
32 | },
33 | });
34 |
35 | return res.status(200).json();
36 | } catch (e) {
37 | throw next(e);
38 | }
39 | });
40 |
41 | locationRouter.post("/", isAuthenticated, async (req, res, next) => {
42 | try {
43 | const { name, description } = req.body;
44 | const user_id = req.user._id;
45 | const location = new LocationModel({
46 | createdBy: user_id,
47 | name,
48 | description,
49 | });
50 |
51 | await location.save();
52 | return res.status(200).json({ message: "Success" });
53 | } catch (e) {
54 | throw next(e);
55 | }
56 | });
57 |
58 | export default locationRouter;
59 |
--------------------------------------------------------------------------------
/Backend/app.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { connectdb } from "./db/user_db.js";
3 | import userRouter from "./routes/user_routes.js";
4 | import dotenv from "dotenv";
5 | import cookieParser from "cookie-parser";
6 | import cors from "cors";
7 | import productRouter from "./routes/productRoutes.js";
8 | import companyRouter from "./routes/companyRoutes.js";
9 | import locationRouter from "./routes/locationRoutes.js";
10 | import analyticsRoutes from "./routes/analyticsRoutes.js";
11 |
12 | dotenv.config();
13 |
14 | const app = express();
15 | app.use(
16 | cors({
17 | // methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
18 | // credentials: true,
19 | // preflightContinue: false,
20 | origin: true,
21 | methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
22 | credentials: true,
23 | })
24 | );
25 |
26 | app.use(cookieParser());
27 | app.use(express.json());
28 | app.use(express.urlencoded());
29 | connectdb();
30 |
31 | app.use("/api/v1/users", userRouter);
32 | app.use("/api/v1/products", productRouter);
33 | app.use("/api/v1/brands", companyRouter);
34 | app.use("/api/v1/location", locationRouter);
35 | app.use("/api/v1/analytics", analyticsRoutes);
36 |
37 | app.use(express.urlencoded({ extended: true }));
38 |
39 | // console.log(process.env.FRONTEND_URL);
40 |
41 | app.get("/", (req, res) => {
42 | res.send("working nicely
");
43 | });
44 |
45 | app.use((error, req, res, next) => {
46 | console.log(error, error.message);
47 | return res.status(400).json({ message: "internal server error" });
48 | });
49 |
50 | app.listen(process.env.PORT, () => {
51 | console.log(
52 | `server is working at port:${process.env.PORT} in ${process.env.NODE_ENV} mode`
53 | );
54 | });
55 |
--------------------------------------------------------------------------------
/Backend/routes/companyRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import CompanyModel from "../models/company_model.js";
3 | import { isAuthenticated } from "../middlewares/user_auth.js";
4 |
5 | const companyRouter = express.Router();
6 |
7 | companyRouter.get("/", async (req, res, next) => {
8 | try {
9 | const locations = await CompanyModel.find()
10 | .populate("createdBy")
11 | .populate("editedBy");
12 |
13 | return res.status(200).json(locations || []);
14 | } catch (e) {
15 | throw next(e);
16 | }
17 | });
18 | companyRouter.get("/:id", async (req, res, next) => {
19 | try {
20 | const locations = await CompanyModel.findById(req.params.id)
21 | .populate("createdBy")
22 | .populate("editedBy");
23 |
24 | return res.status(200).json(locations);
25 | } catch (e) {
26 | throw next(e);
27 | }
28 | });
29 |
30 | companyRouter.patch("/:id", isAuthenticated, async (req, res, next) => {
31 | try {
32 | const { name, description } = req.body;
33 | const editedBy = req.user._id;
34 |
35 | await CompanyModel.findByIdAndUpdate(req.params.id, {
36 | $set: {
37 | name,
38 | description,
39 | editedBy,
40 | },
41 | });
42 |
43 | return res.status(200).json({ message: "Success" });
44 | } catch (e) {
45 | throw next(e);
46 | }
47 | });
48 |
49 | companyRouter.post("/", isAuthenticated, async (req, res, next) => {
50 | try {
51 | const { name, description } = req.body;
52 | const user_id = req.user._id;
53 |
54 | const location = new CompanyModel({
55 | createdBy: user_id,
56 | name,
57 | description,
58 | });
59 |
60 | await location.save();
61 | return res.status(200).json({ message: "Success" });
62 | } catch (e) {
63 | throw next(e);
64 | }
65 | });
66 |
67 | export default companyRouter;
68 |
--------------------------------------------------------------------------------
/Frontend/src/screens/users/components/ManageUserTableRow.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useState } from "react";
3 | import { NavLink } from "react-router-dom";
4 | import { SERVER_URL } from "../../../router";
5 |
6 | function ManageUserTableRow({ user, role }) {
7 | const [userRole, setUserRole] = useState(user.role);
8 | const [isLoading, setLoading] = useState(false);
9 | async function changeRoleAPicall() {
10 | try {
11 | setLoading(true);
12 | await axios.patch(
13 | `${SERVER_URL}/api/v1/users/chage-role`,
14 | {
15 | targetUserId: user._id,
16 | role: userRole==="user" ? "admin":"user",
17 | },
18 | {
19 | withCredentials: "include",
20 | headers: {
21 | "Content-Type": "application/json",
22 | },
23 | }
24 | );
25 | console.log(userRole)
26 | setUserRole((s) => (s === "admin" ? "user" : "admin"));
27 | } catch (e) {
28 | console.log(e);
29 | setUserRole((s) => (s === "admin" ? "user" : "admin"));
30 | } finally {
31 | setLoading(false);
32 | }
33 | }
34 | async function changeRole() {
35 | // setUserRole((s) => (s === "admin" ? "user" : "admin"));
36 | await changeRoleAPicall();
37 | }
38 | return (
39 |
40 | | {user.name} |
41 | {user.email} |
42 |
43 |
50 | {userRole}
51 |
52 | |
53 |
54 | {user && role === "admin" && (
55 |
56 |
66 | |
67 | )}
68 |
69 | );
70 | }
71 |
72 | export default ManageUserTableRow;
73 |
--------------------------------------------------------------------------------
/Frontend/src/components/HeaderBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import adminLogo from "../assets/admin-logo.svg";
4 | import userLogo from "../assets/user-logo.svg";
5 | import axios from "axios";
6 |
7 | function HeaderBar({ user }) {
8 | const [showMenu, setShowMenu] = useState(false);
9 | const navigator = useNavigate();
10 |
11 | const handleMenuClick = () => {
12 | setShowMenu(!showMenu);
13 | };
14 |
15 | const [isLoading, setLoading] = useState(false);
16 |
17 | return (
18 | <>
19 | {isLoading && (
20 |
21 |
22 |
Loading please wait...
23 |
24 | )}
25 | {!isLoading && (
26 |
27 |
28 |
29 | Inventory Management
30 |
31 |
32 |
33 |
34 |

39 |
40 |
41 | {user.name}
42 |
43 | {user.email}
44 |
45 |
46 |
65 |
66 |
67 |
68 | )}
69 | >
70 | );
71 | }
72 |
73 | export default HeaderBar;
74 |
--------------------------------------------------------------------------------
/Backend/routes/analyticsRoutes.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import ProductModel from "../models/product_model.js";
3 | import HistoryModel from "../models/history_model.js";
4 |
5 | const analyticsRoutes = express.Router();
6 |
7 | analyticsRoutes.get("/expiring", async (req, res) => {
8 | try {
9 | const { months = 1 } = req.query;
10 | const expiringProducts = await getExpiringProducts(parseInt(months));
11 | res.status(200).json(expiringProducts);
12 | } catch (error) {
13 | res.status(500).json({ error: error.message });
14 | }
15 | });
16 |
17 | const getExpiringProducts = async (months) => {
18 | const currentDate = new Date();
19 | const expirationDate = new Date(currentDate);
20 | expirationDate.setMonth(expirationDate.getMonth() + months);
21 |
22 | const expiringProducts = await ProductModel.find({
23 | dateOfPurchase: { $lte: expirationDate },
24 | })
25 | .populate("createdBy")
26 | .populate({
27 | path: "history",
28 | populate: {
29 | path: "location",
30 | },
31 | })
32 | .populate("manufacturer");
33 |
34 | return expiringProducts;
35 | };
36 |
37 | analyticsRoutes.get("/", async (req, res) => {
38 | try {
39 | const useby = await getProductUsageByUser();
40 | const expiry = await getWarrantyStatus();
41 | const status = await getProductStatus();
42 |
43 | const analytics = { useby, expiry, status };
44 |
45 | res.status(200).json(analytics);
46 | } catch (error) {
47 | res.status(500).json({ error: error.message });
48 | }
49 | });
50 |
51 | // Function to get product usage by user
52 | const getProductUsageByUser = async () => {
53 | const result = await ProductModel.aggregate([
54 | {
55 | $group: {
56 | _id: "$user",
57 | count: { $sum: 1 },
58 | },
59 | },
60 | ]);
61 |
62 | const labels = result.map((item) => item._id);
63 | const data = result.map((item) => item.count);
64 |
65 | return { title: "Products used by", labels, data };
66 | };
67 |
68 | // Function to get warranty status
69 | const getWarrantyStatus = async () => {
70 | const result = await ProductModel.aggregate([
71 | {
72 | $project: {
73 | status: {
74 | $cond: [
75 | { $lte: ["$dateOfPurchase", new Date()] },
76 | "in warranty",
77 | "not in warranty",
78 | ],
79 | },
80 | },
81 | },
82 | {
83 | $group: {
84 | _id: "$status",
85 | count: { $sum: 1 },
86 | },
87 | },
88 | ]);
89 |
90 | const labels = result.map((item) => item._id);
91 | const data = result.map((item) => item.count);
92 |
93 | return { title: "Warranty", labels, data };
94 | };
95 |
96 | const getProductStatus = async () => {
97 | const result = await HistoryModel.aggregate([
98 | {
99 | $unwind: "$status",
100 | },
101 | {
102 | $group: {
103 | _id: "$status.name",
104 | count: { $sum: 1 },
105 | },
106 | },
107 | ]);
108 |
109 | const labels = result.map((item) => item._id);
110 | const data = result.map((item) => item.count);
111 |
112 | return { title: "Product Status", labels, data };
113 | };
114 |
115 | export default analyticsRoutes;
116 |
--------------------------------------------------------------------------------
/Frontend/src/screens/dashboard/components/AnalyticsComponent.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useRef } from "react";
2 | import { Pie, Bar } from "react-chartjs-2";
3 | import axios from "axios";
4 |
5 | import "chart.js/auto";
6 | import { SERVER_URL } from "../../../router";
7 | export const AnalyticsComponent = () => {
8 | const [loading, setLoading] = useState(true);
9 | const [analyticsData, setAnalyticsData] = useState(null);
10 | const ref1 = useRef(null);
11 | const ref2 = useRef(null);
12 | const ref3 = useRef(null);
13 | useEffect(() => {
14 | const fetchData = async () => {
15 | try {
16 | const response = await axios.get(
17 | `${SERVER_URL}/api/v1/analytics/`
18 | );
19 | setAnalyticsData(response.data);
20 | setLoading(false);
21 | } catch (error) {
22 | console.error("Error fetching data:", error);
23 | setLoading(false);
24 | }
25 | };
26 |
27 | fetchData();
28 |
29 | return () => {
30 | ref1.current = null;
31 | ref2.current = null;
32 | ref3.current = null;
33 | };
34 | }, []);
35 |
36 | return (
37 |
38 | {loading ? (
39 |
40 |
41 | Loading...
42 |
43 |
44 | ) : (
45 | <>
46 |
47 |
48 | {analyticsData.useby.title}
49 |
50 |
62 |
63 |
64 |
65 | {analyticsData.expiry.title}
66 |
67 |
79 |
80 |
81 |
82 | {analyticsData.status.title}
83 |
84 |
96 |
97 | >
98 | )}
99 |
100 | );
101 | };
102 |
103 | export default AnalyticsComponent;
104 |
--------------------------------------------------------------------------------
/Frontend/src/router.jsx:
--------------------------------------------------------------------------------
1 | import { createBrowserRouter } from "react-router-dom";
2 | import DashBoardLayout from "./screens/dashboard/DashBoardLayout";
3 | import LoginScreen from "./screens/login/LoginScreen";
4 | import SignupScreen from "./screens/login/SignupScreen";
5 | import DashBoardScreen from "./screens/dashboard/DashBoardScreen";
6 |
7 | import AuthLayout from "./screens/login/AuthLayout";
8 | import InventoryFormScreen from "./screens/InventoryFormScreen";
9 | import ProductInfoScreen from "./screens/product/ProductInfoScreen";
10 | import AddNewProductScreen from "./screens/product/AddNewProductScreen";
11 | import ProductEditScreen from "./screens/product/ProductEditScreen";
12 | import PrductsScreen from "./screens/product/ProductsScreen";
13 | import LocationsScreen from "./screens/locations/LocationsScreen";
14 | import NewLocationScreen from "./screens/locations/NewLocationScreen";
15 | import EditLocationScreen from "./screens/locations/EditLocationScreen";
16 |
17 | import BrandsScreen from "./screens/brands/BrandsScreen";
18 | import NewBrandsScreen from "./screens/brands/NewBrandsScreen";
19 | import EditBrandsScreen from "./screens/brands/EditBrandsScreen";
20 | import UserManagementScreen from "./screens/users/UserManagementScreen";
21 | import ProductHistoryScreen from "./screens/product/ProductHistoryScreen";
22 | const router = createBrowserRouter([
23 | {
24 | path: "/",
25 | element: ,
26 | children: [
27 | {
28 | path: "",
29 | element: ,
30 | },
31 | // {
32 | // path: "add-item",
33 | // element: ,
34 | // },
35 | {
36 | path: "add-product",
37 | element: ,
38 | },
39 | {
40 | path: "products/:id",
41 | element: ,
42 | },
43 | {
44 | path: "products/:id/edit",
45 | element: ,
46 | },
47 |
48 | // new routes
49 |
50 | {
51 | path: "/products",
52 | element: ,
53 | },
54 | { path: "/products/new", element: },
55 | { path: "/products/edit/:id", element: },
56 | { path: "/products/history/:id", element: },
57 |
58 | // brands
59 |
60 | {
61 | path: "/brands",
62 | element: ,
63 | },
64 | {
65 | path: "/brands/new",
66 | element: ,
67 | },
68 | {
69 | path: "/brands/edit/:id",
70 | element: ,
71 | },
72 |
73 | // locations
74 | {
75 | path: "/locations",
76 | element: ,
77 | },
78 | {
79 | path: "/locations/new",
80 | element: ,
81 | },
82 | {
83 | path: "/locations/edit/:id",
84 | element: ,
85 | },
86 | // users
87 | {
88 | path: "/users",
89 | element: ,
90 | },
91 | ],
92 | },
93 | {
94 | path: "/auth",
95 | element: ,
96 | children: [
97 | {
98 | index: true,
99 | path: "",
100 | element: ,
101 | },
102 | {
103 | path: "signup",
104 | element: ,
105 | },
106 | ],
107 | },
108 | ]);
109 |
110 | export default router;
111 | export const SERVER_URL =
112 | import.meta.env.VITE_MODE === "DEV"
113 | ? import.meta.env.VITE_LOCAL
114 | : import.meta.env.VITE_SERVER;
115 |
--------------------------------------------------------------------------------
/Frontend/src/screens/dashboard/DashBoardLayout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import HeaderBar from "../../components/HeaderBar";
3 | import { Link, Outlet, useNavigate } from "react-router-dom";
4 | import axios from "axios";
5 | import DashBoardScreen from "./DashBoardScreen";
6 | import loginLogo from "../../assets/authenticate.svg";
7 | import SideNavbar from "../../components/SideNavbar";
8 | import { SERVER_URL } from "../../router";
9 |
10 | function DashBoardLayout() {
11 | const navigator = useNavigate();
12 | const [data, setData] = useState(null);
13 | const [user, setUser] = useState(null);
14 | const [isLoading, setLoading] = useState(true);
15 |
16 | useEffect(() => {
17 | fetchUserInfo();
18 | }, []);
19 | const fetchUserInfo = async () => {
20 | try {
21 | const { data, status } = await axios.get(
22 | `${SERVER_URL}/api/v1/users/me`,
23 |
24 | {
25 | withCredentials: true,
26 | headers: {
27 | "Content-Type": "application/json",
28 | },
29 | }
30 | );
31 | console.log(data);
32 | if (status === 400) {
33 | // navigator("/auth");
34 | }
35 | if (status === 200) {
36 | setData(data);
37 | setUser(data.user);
38 | setLoading(false);
39 | }
40 | } catch (e) {
41 | // navigator("/auth");
42 | console.log(e);
43 | } finally {
44 | setLoading(false);
45 | }
46 | };
47 | return (
48 | <>
49 | {!data &&
}
50 | {!data && (
51 |
52 | Your are not authenticated please login to continue
53 |
54 | )}
55 |
56 | {/* loading spinner animation */}
57 | {isLoading && (
58 |
64 | )}
65 |
66 | {/* showing the data from data base */}
67 | {data && }
68 | {data && (
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | )}
81 |
82 | {/* home screen logout component */}
83 | {!data && (
84 |
85 |
86 |

87 |
88 |
92 | Login Now
93 |
94 |
95 |
96 |
97 | )}
98 | >
99 | );
100 | }
101 |
102 | export default DashBoardLayout;
103 |
--------------------------------------------------------------------------------
/Frontend/src/screens/product/ProductInfoScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useParams } from "react-router-dom";
3 | import axios from "axios"; // Import Axios
4 | import { SERVER_URL } from "../../router";
5 |
6 | function ProductInfoScreen() {
7 | const params = useParams();
8 |
9 | const [isLoading, setLoading] = useState(true); // Adjusted loading state
10 | const [isError, setError] = useState(null);
11 | const [isSuccess, setSuccess] = useState(false);
12 | const [product, setProduct] = useState(null); // Initialize product as null
13 |
14 | useEffect(() => {
15 | getProductInfo();
16 | }, [params.id]); // Include params.id in the dependency array
17 |
18 | const getProductInfo = async () => {
19 | try {
20 | setLoading(true); // Set loading state to true
21 | const { data, status } = await axios.get(
22 | `${SERVER_URL}/api/v1/products/${params.id}`,
23 | {
24 | withCredentials: true, // Change "include" to true
25 | headers: {
26 | "Content-Type": "application/json",
27 | },
28 | }
29 | );
30 | console.log(data);
31 | if (status === 200) {
32 | setSuccess(true);
33 | setProduct(data);
34 | } else {
35 | throw new Error(data.error);
36 | }
37 | } catch (error) {
38 | setError("Error while fetching product information");
39 | console.error(error);
40 | } finally {
41 | setLoading(false); // Set loading state to false
42 | }
43 | };
44 |
45 | // JSX for rendering product information
46 | return (
47 |
48 | {isError && (
49 |
50 |
51 |
52 | {isError}{" "}
53 |
54 | Click here to reload!
55 |
56 |
57 |
58 |
59 | )}
60 | {isLoading && (
61 |
67 | )}
68 | {isSuccess && product && (
69 |
70 |
{product.title}
71 |
{product.description}
72 |
Serial No: {product.serialNo}
73 |
74 | Manufacturer: {product.manufacturer}
75 |
76 |
Model: {product.model}
77 |
78 | Date of Purchase:{" "}
79 | {new Date(product.dateOfPurchase).toLocaleDateString()}
80 |
81 |
82 | Warranty Months: {product.warrantyMonths}
83 |
84 |
Status: {product.status}
85 |
User: {product.user}
86 |
87 | Created By: {product.createdBy.name}
88 |
89 | {/* Render other product information */}
90 |
91 | )}
92 |
93 | );
94 | }
95 |
96 | export default ProductInfoScreen;
97 |
--------------------------------------------------------------------------------
/Frontend/src/components/SideNavbar.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link, NavLink } from "react-router-dom";
3 |
4 | import { FaBlog, FaSleigh } from "react-icons/fa";
5 | import { IoMdHome } from "react-icons/io";
6 | import { AiFillProduct } from "react-icons/ai";
7 |
8 | import { LuShoppingBag } from "react-icons/lu";
9 | import { MdCategory } from "react-icons/md";
10 | import { SiBrandfolder } from "react-icons/si";
11 | import { BsCollectionFill } from "react-icons/bs";
12 |
13 | import { FcAdvertising } from "react-icons/fc";
14 | import { RiCoupon2Fill } from "react-icons/ri";
15 |
16 | import { IoIosSettings } from "react-icons/io";
17 | import { FcRatings } from "react-icons/fc";
18 | import { FaUserAlt } from "react-icons/fa";
19 | import { BiCart, BiLocationPlus, BiUser } from "react-icons/bi";
20 | import LogoutButton from "./LogoutButton";
21 |
22 | function SideNavbar() {
23 | const quickLinks = {
24 | title: "QUICK LINKS",
25 | links: [
26 | { link: "", name: "Dashboard", icon: , end: true },
27 | {
28 | link: "/products/new",
29 | name: "New Product",
30 | icon: ,
31 | end: true,
32 | },
33 | {
34 | link: "/brands/new",
35 | name: "New Brand",
36 | icon: ,
37 | end: true,
38 | },
39 | {
40 | link: "/locations/new",
41 | name: "New Location",
42 | icon: ,
43 | end: true,
44 | },
45 | ],
46 | };
47 | const catalogLinks = {
48 | title: "CATALOG",
49 | links: [
50 | {
51 | link: "/products",
52 | name: "Products",
53 | icon: ,
54 | end: false,
55 | },
56 | { link: "/brands", name: "Brands", icon: , end: false },
57 | {
58 | link: "/locations",
59 | name: "Locations",
60 | icon: ,
61 | end: false,
62 | },
63 | { link: "/users", name: "User Management", icon: , end: false },
64 | ],
65 | };
66 |
67 | const links = [quickLinks, catalogLinks];
68 | return (
69 |
70 | {links.map((link, index) => (
71 |
72 |
73 | {link.title}
74 |
75 |
76 | {link.links.map((_link, idx) => (
77 |
82 | `${
83 | prop.isActive
84 | ? "text-teal-600 border-l-4 border-l-teal-600 rounded-sm bg-slate-50"
85 | : "hover:bg-slate-50 hover:text-teal-800"
86 | } pl-6 py-2 font-semibold text-slate-700 flex items-center gap-3`
87 | }
88 | >
89 | {_link.icon}
90 | {_link.name}
91 |
92 | ))}
93 |
94 |
95 | ))}
96 |
97 |
98 |
99 | );
100 | }
101 |
102 | export default SideNavbar;
103 |
104 | {
105 | /*
106 |
112 |
113 | Settings
114 |
115 |
116 | */
117 | }
118 |
--------------------------------------------------------------------------------
/Backend/controllers/user_controllers.js:
--------------------------------------------------------------------------------
1 | import User from "../models/user_model.js";
2 | import bcrypt from "bcrypt";
3 | import { sendcookie } from "../utils/user_utils.js";
4 |
5 | export const getAllUsers = async (req, res) => {
6 | try {
7 | const {
8 | page = 1,
9 | itemsPerPage = 10,
10 | search = "",
11 | role, // Role parameter for filtering
12 | } = req.query;
13 |
14 | // Define the search query
15 | const searchQuery = {
16 | $or: [
17 | { name: { $regex: search, $options: "i" } },
18 | { email: { $regex: search, $options: "i" } },
19 | ],
20 | };
21 |
22 | // Include role-based filtering if role parameter is provided
23 | if (role) {
24 | searchQuery.role = role;
25 | }
26 |
27 | // Calculate the number of documents to skip
28 | const skipItems = (page - 1) * itemsPerPage;
29 |
30 | // Find users based on search query, role filter, and pagination
31 | const users = await User.find(searchQuery)
32 | .skip(skipItems)
33 | .limit(parseInt(itemsPerPage));
34 |
35 | // Count total number of users for pagination
36 | const totalUsers = await User.countDocuments(searchQuery);
37 | const totalPages = Math.ceil(totalUsers / itemsPerPage);
38 |
39 | res.status(200).json({
40 | success: true,
41 | users,
42 | totalPages,
43 | currentPage: page,
44 | });
45 | } catch (error) {
46 | res.status(500).json({ error: error.message });
47 | }
48 | };
49 |
50 | export const login = async (req, res, next) => {
51 | try {
52 | const { email, password } = req.body;
53 | const user = await User.findOne({ email }).select("+password");
54 | if (!user) {
55 | return res.status(404).json({
56 | success: false,
57 | message: "Register first",
58 | });
59 | }
60 |
61 | const encrypted = await bcrypt.compare(password, user.password);
62 | if (!encrypted) {
63 | return res.status(404).json({
64 | success: false,
65 | message: "your password is incorrect",
66 | });
67 | }
68 |
69 | sendcookie(user, res, `welcome back,${user.name}`, 201);
70 | } catch (e) {
71 | throw next(e);
72 | }
73 | };
74 |
75 | export const register = async (req, res, next) => {
76 | try {
77 | const { name, email, password } = req.body;
78 | let user = await User.findOne({ email });
79 | if (user) {
80 | return res.status(400).json({
81 | success: false,
82 | message: "user already exists",
83 | });
84 | }
85 |
86 | const hashedpassword = await bcrypt.hash(password, 10);
87 | user = await User.create({ name, email, password: hashedpassword });
88 |
89 | sendcookie(user, res, "successfully registered", 201);
90 | } catch (e) {
91 | throw next(e);
92 | }
93 | };
94 |
95 | export const getMyprofile = async (req, res, next) => {
96 | try {
97 | // 2nd method by {Abhisekhsuru}
98 | // const { id } = req.query;
99 | // const user = await User.findById(id);
100 | //without authentication
101 | const user = await User.findById(req.user._id);
102 | res.status(200).json({
103 | success: true,
104 | message: `your profile is found ${req.user.name}`,
105 | user: user,
106 | });
107 | } catch (e) {
108 | throw next(e);
109 | }
110 | };
111 |
112 | export const logout = async (req, res, next) => {
113 | try {
114 | res
115 | .cookie("token", null, {
116 | expires: new Date(0),
117 | sameSite: process.env.NODE_ENV === "development" ? "lax" : "none",
118 | secure: process.env.NODE_ENV === "development" ? false : true,
119 | })
120 | .status(200)
121 | .json({
122 | success: true,
123 | message: "Successfully logout",
124 | user: req.user,
125 | });
126 | } catch (e) {
127 | throw next(e);
128 | }
129 | };
130 |
--------------------------------------------------------------------------------
/Frontend/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Frontend/src/screens/locations/NewLocationScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link, useParams } from "react-router-dom";
3 | import ShowErrorMessage from "../../components/ShowErrorMessage";
4 | import ShowSuccessMesasge from "../../components/ShowSuccessMesasge";
5 | import LoadingIndicator from "../../components/LoadingIndicator";
6 | import axios from "axios";
7 | import { SERVER_URL } from "../../router";
8 |
9 | function NewLocationScreen() {
10 | const [success, setSuccess] = useState(false);
11 | const [uploading, setUploading] = useState(false);
12 | const [data, setData] = useState({});
13 | const [isError, setError] = useState("");
14 |
15 | function onchangeHandler(e) {
16 | e.preventDefault();
17 | const name = e.target.name;
18 | const value = e.target.value;
19 |
20 | setData({ ...data, [name]: value });
21 | }
22 | async function handleUpdate(e) {
23 | e.preventDefault();
24 | try {
25 | setError("");
26 | setUploading(true);
27 |
28 | const {} = await axios.post(
29 | `${SERVER_URL}/api/v1/location/`,
30 | data,
31 | {
32 | withCredentials: true,
33 | headers: {
34 | "Content-Type": "application/json",
35 | },
36 | }
37 | );
38 | setSuccess(true);
39 | } catch (e) {
40 | setError(e);
41 | console.log(e);
42 | } finally {
43 | setUploading(false);
44 | }
45 | }
46 | return (
47 |
48 |
Add New Location
49 |
50 | {isError && (
51 |
reload}
53 | />
54 | )}
55 | {success && (
56 | <>
57 |
58 |
59 |
60 | Updated Successfullly{" "}
61 |
62 | goto Home
63 |
64 |
65 |
66 |
67 | >
68 | )}
69 |
70 |
119 |
120 | );
121 | }
122 |
123 | export default NewLocationScreen;
124 |
--------------------------------------------------------------------------------
/Frontend/src/screens/brands/NewBrandsScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link, useParams } from "react-router-dom";
3 | import ShowErrorMessage from "../../components/ShowErrorMessage";
4 | import ShowSuccessMesasge from "../../components/ShowSuccessMesasge";
5 | import LoadingIndicator from "../../components/LoadingIndicator";
6 | import axios from "axios";
7 | import { SERVER_URL } from "../../router";
8 |
9 | function NewBrandsScreen() {
10 | const params = useParams();
11 | const [success, setSuccess] = useState(false);
12 | const [uploading, setUploading] = useState(false);
13 | const [data, setData] = useState({});
14 | const [isError, setError] = useState("");
15 |
16 | function onchangeHandler(e) {
17 | e.preventDefault();
18 | const name = e.target.name;
19 | const value = e.target.value;
20 |
21 | setData({ ...data, [name]: value });
22 | }
23 | async function handleUpdate(e) {
24 | e.preventDefault();
25 | try {
26 | setError("");
27 | setUploading(true);
28 |
29 | const {} = await axios.post(
30 | `${SERVER_URL}/api/v1/brands/`,
31 | data,
32 | {
33 | withCredentials: true,
34 | headers: {
35 | "Content-Type": "application/json",
36 | },
37 | }
38 | );
39 | setSuccess(true);
40 | } catch (e) {
41 | setError(e);
42 | console.log(e);
43 | } finally {
44 | setUploading(false);
45 | }
46 | }
47 | return (
48 |
49 |
Add New Brand
50 |
51 | {isError && (
52 |
reload}
54 | />
55 | )}
56 | {success && (
57 | <>
58 |
59 |
60 |
61 | Updated Successfullly{" "}
62 |
63 | goto Home
64 |
65 |
66 |
67 |
68 | >
69 | )}
70 |
71 |
120 |
121 | );
122 | }
123 |
124 | export default NewBrandsScreen;
125 |
--------------------------------------------------------------------------------
/Frontend/src/assets/admin-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/Frontend/src/components/WarrantyExpiringProductsTableComponent.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import EmptyData from "../assets/undraw_empty_re.svg";
3 | import axios from "axios";
4 | import { Link } from "react-router-dom";
5 | import { SERVER_URL } from "../router";
6 |
7 | function WarrantyExpiringProductsTablesComponent({ uid }) {
8 | const [isLoading, setLoading] = useState(true);
9 | const [isError, setError] = useState(null);
10 | const [inventoryData, setInventoryData] = useState([]);
11 |
12 | useEffect(() => {
13 | fetchInventory();
14 | }, []);
15 |
16 | const fetchInventory = async () => {
17 | try {
18 | const { data } = await axios.get(`${SERVER_URL}/api/v1/analytics/expiring`, {
19 | withCredentials: true,
20 | headers: {
21 | "Content-Type": "application/json",
22 | },
23 | });
24 | setInventoryData(data);
25 | setLoading(false);
26 | } catch (error) {
27 | console.error("Error fetching inventory data:", error);
28 | setError("Error fetching inventory data");
29 | setLoading(false);
30 | }
31 | };
32 |
33 | const calculateMonthsDifference = (date1, date2) => {
34 | const diffInMs = new Date(date2) - new Date(date1);
35 | return Math.round(diffInMs / (1000 * 60 * 60 * 24 * 30.44)); // Approximate number of days in a month
36 | };
37 |
38 | return (
39 |
40 | {isLoading ? (
41 |
Loading...
42 | ) : isError ? (
43 |
Error: {isError}
44 | ) : inventoryData.length === 0 ? (
45 |
46 |

47 |
No data available
48 |
49 | ) : (
50 |
51 |
52 |
53 |
54 | |
55 | Product
56 | |
57 |
58 | Serial Number
59 | |
60 |
61 | Warranty Months / Purchase Date
62 | |
63 |
64 | Status
65 | |
66 |
67 | Action
68 | |
69 |
70 |
71 |
72 | {inventoryData.map((item, index) => (
73 |
74 | |
75 | {item.title}
76 | |
77 |
78 | {item.serialNo}
79 | |
80 |
81 | {item.warrantyMonths} / {item.dateOfPurchase.split("T")[0]}
82 | |
83 |
84 | {item.history[0].status[0].name}
85 | |
86 |
87 |
88 | View
89 |
90 | |
91 |
92 | ))}
93 |
94 |
95 |
96 | )}
97 |
98 | );
99 | }
100 |
101 | export default WarrantyExpiringProductsTablesComponent;
102 |
--------------------------------------------------------------------------------
/Frontend/src/screens/brands/BrandsScreen.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import LoadingIndicator from "../../components/LoadingIndicator";
4 | import ShowErrorMessage from "../../components/ShowErrorMessage";
5 | import { IoMailOutline } from "react-icons/io5";
6 | import { FaUser } from "react-icons/fa";
7 | import { Link, NavLink } from "react-router-dom";
8 | import { SERVER_URL } from "../../router";
9 |
10 | function BrandsScreen() {
11 | const [isLoading, setLoading] = useState(true);
12 | const [data, setData] = useState(null);
13 | const [isError, setError] = useState("");
14 |
15 | useEffect(() => {
16 | getDataFromApi();
17 | }, []);
18 | async function getDataFromApi() {
19 | try {
20 | const { data } = await axios.get(`${SERVER_URL}/api/v1/brands`);
21 | setData(data);
22 | } catch (e) {
23 | setError(e);
24 | } finally {
25 | setLoading(false);
26 | }
27 | }
28 | return (
29 |
30 |
31 |
All Brands
32 |
36 | Add New Brand
37 |
38 |
39 |
40 |
41 | {isLoading &&
}
42 |
43 | {isError && (
44 |
reload}
46 | />
47 | )}
48 |
49 | {data && (
50 |
51 | {data.map((location) => (
52 |
53 |
54 |
55 | ))}
56 |
57 | )}
58 |
59 | );
60 | }
61 |
62 | function LoactionCard({ data }) {
63 | return (
64 |
65 |
{data.name}
66 |
{data.description}
67 |
68 | {data.editedBy ? (
69 | <>
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | {data.editedBy.name}
78 |
79 |
80 |
81 | Edited By
82 |
83 |
84 |
85 |
86 | {data.editedBy.email}
87 |
88 |
89 |
90 | >
91 | ) : (
92 | data.createdBy && (
93 | <>
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 | {data.createdBy.name}
102 |
103 |
104 |
105 | Created By
106 |
107 |
108 |
109 |
110 | {data.createdBy.email}
111 |
112 |
113 |
114 | >
115 | )
116 | )}
117 |
118 |
122 | Edit
123 |
124 |
125 | );
126 | }
127 |
128 | export default BrandsScreen;
129 |
--------------------------------------------------------------------------------
/Frontend/src/screens/locations/LocationsScreen.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import LoadingIndicator from "../../components/LoadingIndicator";
4 | import ShowErrorMessage from "../../components/ShowErrorMessage";
5 | import { IoMailOutline } from "react-icons/io5";
6 | import { FaUser } from "react-icons/fa";
7 | import { Link, NavLink } from "react-router-dom";
8 | import { SERVER_URL } from "../../router";
9 |
10 | function LocationsScreen() {
11 | const [isLoading, setLoading] = useState(true);
12 | const [data, setData] = useState(null);
13 | const [isError, setError] = useState("");
14 |
15 | useEffect(() => {
16 | getDataFromApi();
17 | }, []);
18 | async function getDataFromApi() {
19 | try {
20 | const { data } = await axios.get(`${SERVER_URL}/api/v1/location`);
21 | setData(data);
22 | } catch (e) {
23 | setError(e);
24 | } finally {
25 | setLoading(false);
26 | }
27 | }
28 | return (
29 |
30 |
31 |
All Locations
32 |
36 | Add New Location
37 |
38 |
39 |
40 |
41 | {isLoading &&
}
42 |
43 | {isError && (
44 |
reload}
46 | />
47 | )}
48 |
49 | {data && (
50 |
51 | {data.map((location) => (
52 |
53 |
54 |
55 | ))}
56 |
57 | )}
58 |
59 | );
60 | }
61 |
62 | function LoactionCard({ data }) {
63 | return (
64 |
65 |
{data.name}
66 |
{data.description}
67 |
68 | {data.editedBy ? (
69 | <>
70 |
71 |
72 |
73 |
74 |
75 |
76 |
{data.editedBy.name}
77 |
78 |
79 | Edited By
80 |
81 |
82 |
83 |
84 | {data.editedBy.email}
85 |
86 |
87 |
88 | >
89 | ) : (
90 | data.createdBy && (
91 | <>
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | {data.createdBy.name}
100 |
101 |
102 |
103 | Created By
104 |
105 |
106 |
107 |
108 | {data.createdBy.email}
109 |
110 |
111 |
112 | >
113 | )
114 | )}
115 |
116 |
120 | Edit
121 |
122 |
123 | );
124 | }
125 |
126 | export default LocationsScreen;
127 |
--------------------------------------------------------------------------------
/Frontend/src/screens/brands/EditBrandsScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link, useParams } from "react-router-dom";
3 | import ShowErrorMessage from "../../components/ShowErrorMessage";
4 | import ShowSuccessMesasge from "../../components/ShowSuccessMesasge";
5 | import LoadingIndicator from "../../components/LoadingIndicator";
6 | import axios from "axios";
7 | import { SERVER_URL } from "../../router";
8 |
9 | function EditBrandsScreen() {
10 | const params = useParams();
11 | const [isLoading, setLoading] = useState(true);
12 | const [success, setSuccess] = useState(false);
13 | const [uploading, setUploading] = useState(false);
14 | const [data, setData] = useState({});
15 | const [isError, setError] = useState("");
16 |
17 | useEffect(() => {
18 | getDataFromApi();
19 | }, []);
20 | async function getDataFromApi() {
21 | try {
22 | setError("");
23 |
24 | const { data } = await axios.get(
25 | `${SERVER_URL}/api/v1/brands/${params.id}`
26 | );
27 | setData(data);
28 | } catch (e) {
29 | setError(e);
30 | console.log(e);
31 | } finally {
32 | setLoading(false);
33 | }
34 | }
35 |
36 | function onchangeHandler(e) {
37 | e.preventDefault();
38 | const name = e.target.name;
39 | const value = e.target.value;
40 |
41 | setData({ ...data, [name]: value });
42 | }
43 | async function handleUpdate(e) {
44 | e.preventDefault();
45 | try {
46 | setError("");
47 | setUploading(true);
48 |
49 | const {} = await axios.patch(
50 | `${SERVER_URL}/api/v1/brands/${params.id}`,
51 | data,
52 | {
53 | withCredentials: true,
54 | headers: {
55 | "Content-Type": "application/json",
56 | },
57 | }
58 | );
59 | setSuccess(true);
60 | } catch (e) {
61 | setError(e);
62 | console.log(e);
63 | } finally {
64 | setUploading(false);
65 | }
66 | }
67 |
68 | return (
69 |
70 |
Brands Edit
71 | {isLoading &&
}
72 |
73 | {isError && (
74 |
77 | reload
78 |
79 | }
80 | />
81 | )}
82 | {success && (
83 |
86 | Updated Successfullly{" "}
87 |
88 | goto Home
89 |
90 |
91 | }
92 | />
93 | )}
94 |
95 | {data && !isError && (
96 |
149 | )}
150 |
151 | );
152 | }
153 |
154 | export default EditBrandsScreen;
155 |
--------------------------------------------------------------------------------
/Frontend/src/screens/locations/EditLocationScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link, useParams } from "react-router-dom";
3 | import ShowErrorMessage from "../../components/ShowErrorMessage";
4 | import ShowSuccessMesasge from "../../components/ShowSuccessMesasge";
5 | import LoadingIndicator from "../../components/LoadingIndicator";
6 | import axios from "axios";
7 | import { SERVER_URL } from "../../router";
8 |
9 | function EditLocationScreen() {
10 | const params = useParams();
11 | const [isLoading, setLoading] = useState(true);
12 | const [success, setSuccess] = useState(false);
13 | const [uploading, setUploading] = useState(false);
14 | const [data, setData] = useState({
15 | _id: "663c84f0479858cdb4a0ca9c",
16 | name: "sector 1",
17 | description: "about sector 1 some random content",
18 | createdAt: "2024-05-09T08:10:24.852Z",
19 | updatedAt: "2024-05-09T08:10:24.852Z",
20 | __v: 0,
21 | });
22 | const [isError, setError] = useState("");
23 |
24 | useEffect(() => {
25 | getDataFromApi();
26 | }, []);
27 | async function getDataFromApi() {
28 | try {
29 | setError("");
30 |
31 | const { data } = await axios.get(
32 | `${SERVER_URL}/api/v1/location/${params.id}`
33 | );
34 | setData(data);
35 | } catch (e) {
36 | setError(e);
37 | console.log(e);
38 | } finally {
39 | setLoading(false);
40 | }
41 | }
42 |
43 | function onchangeHandler(e) {
44 | e.preventDefault();
45 | const name = e.target.name;
46 | const value = e.target.value;
47 |
48 | setData({ ...data, [name]: value });
49 | }
50 | async function handleUpdate(e) {
51 | e.preventDefault();
52 | try {
53 | setError("");
54 | setUploading(true);
55 |
56 | const {} = await axios.patch(
57 | `${SERVER_URL}/api/v1/location/${params.id}`,
58 | data,
59 | {
60 | withCredentials: true,
61 | headers: {
62 | "Content-Type": "application/json",
63 | },
64 | }
65 | );
66 | setSuccess(true);
67 | } catch (e) {
68 | setError(e);
69 | console.log(e);
70 | } finally {
71 | setUploading(false);
72 | }
73 | }
74 |
75 | return (
76 |
77 | {isLoading &&
}
78 |
79 | {isError && (
80 |
83 | reload
84 |
85 | }
86 | />
87 | )}
88 | {success && (
89 |
92 | Updated Successfullly{" "}
93 |
94 | goto Home
95 |
96 |
97 | }
98 | />
99 | )}
100 |
101 | {data && !isError && (
102 |
155 | )}
156 |
157 | );
158 | }
159 |
160 | export default EditLocationScreen;
161 |
--------------------------------------------------------------------------------
/Frontend/src/screens/login/LoginScreen.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useState } from "react";
3 | import { Link, useNavigate } from "react-router-dom";
4 | import { SERVER_URL } from "../../router";
5 |
6 | function LoginScreen() {
7 |
8 | const [formData, setData] = useState({
9 | email: "",
10 | password: "",
11 | });
12 |
13 | const history = useNavigate(); // Use useNavigate instead of useHistory
14 |
15 | function handInputChange(e) {
16 | setData({ ...formData, [e.target.id]: e.target.value });
17 | }
18 |
19 | async function handleSignIn(e) {
20 | e.preventDefault();
21 |
22 | try {
23 | const { data, status } = await axios.post(
24 | `${SERVER_URL}/api/v1/users/login`,
25 | formData,
26 | {
27 | withCredentials: "include",
28 | headers: {
29 | "Content-Type": "application/json",
30 | },
31 | }
32 | );
33 |
34 | if (status === 201) {
35 | // console.log(response.headers.Set-Cookie)
36 | // alert("Login success");
37 | // Redirect to home upon successful login
38 | history("/"); // Use history function to redirect
39 | } else {
40 | alert("Wrong credentials. Check Email and password ");
41 | }
42 | } catch (error) {
43 | console.error("Something went wrong:", error); // Corrected console.log to console.error
44 | alert("Something went wrong");
45 | }
46 | }
47 |
48 | return (
49 |
50 |
51 |
52 |
55 |
56 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellat
57 | eligendi perspiciatis, sapiente si.
58 |
59 |
60 | Don't have an account?
61 |
62 | Get Started!
63 |
64 |
65 |
66 | Read our{" "}
67 |
68 | terms
69 | {" "}
70 | and{" "}
71 |
72 | conditions
73 |
74 |
75 |
76 |
77 |
78 | Account Login
79 |
80 |
140 |
141 |
142 |
143 | );
144 | }
145 |
146 | export default LoginScreen;
147 |
--------------------------------------------------------------------------------
/Frontend/src/screens/login/SignupScreen.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link, useNavigate } from "react-router-dom";
4 | import { SERVER_URL } from "../../router";
5 |
6 | function SignupScreen() {
7 | const navigator = useNavigate();
8 |
9 | const [formData, setData] = useState({
10 | name: "",
11 | email: "",
12 | password: "",
13 | });
14 |
15 | function handInputChange(e) {
16 | console.log(e.target.id, e.target.value);
17 |
18 | setData({ ...formData, [e.target.id]: e.target.value });
19 | }
20 |
21 | async function handleSignIN(e) {
22 | e.preventDefault();
23 | try {
24 | const { data, status } = await axios.post(
25 | `${SERVER_URL}/api/v1/users/new`,
26 | formData
27 | );
28 |
29 | console.log(data);
30 |
31 | if (status === 201) {
32 | // alert("Account Created");
33 | navigator("/auth", { replace: true });
34 | } else {
35 | alert("Something went wrong");
36 | }
37 | } catch (e) {
38 | console.log(e);
39 | alert("User Already exists with same email");
40 | }
41 | }
42 |
43 | return (
44 |
45 |
46 |
47 |
50 |
51 | Lorem ipsum dolor sit amet consectetur adipisicing elit. Repellat
52 | eligendi perspiciatis, sapiente si.
53 |
54 |
55 | Already have an account?
56 |
57 | Get Started!
58 |
59 |
60 |
61 | Read our{" "}
62 |
63 | terms
64 | {" "}
65 | and{" "}
66 |
67 | conditions
68 |
69 |
70 |
71 |
72 |
73 | Account Signup
74 |
75 |
148 |
149 |
150 |
151 | );
152 | }
153 |
154 | export default SignupScreen;
155 |
--------------------------------------------------------------------------------
/Frontend/src/assets/authenticate.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Backend/controllers/product_controller.js:
--------------------------------------------------------------------------------
1 | // controllers/productController.js
2 |
3 | import HistoryModel from "../models/history_model.js";
4 | import LocationModel from "../models/locations_models.js";
5 | import Product from "../models/product_model.js";
6 |
7 | export const createProduct = async (req, res) => {
8 | if (req.user.role !== "admin") {
9 | return res.status(403).json({ message: "not authorized" });
10 | }
11 | try {
12 | const {
13 | locationId,
14 | status,
15 | title,
16 | description,
17 | serialNo,
18 | rackMountable,
19 | isPart,
20 | manufacturer,
21 | model,
22 | warrantyMonths,
23 | dateOfPurchase,
24 | user,
25 | } = req.body;
26 |
27 | const history = new HistoryModel({
28 | location: locationId,
29 |
30 | status: [
31 | {
32 | name: status,
33 | },
34 | ],
35 | });
36 |
37 | await history.save();
38 |
39 | const product = new Product({
40 | title,
41 | description,
42 | serialNo,
43 | dateOfPurchase,
44 | createdBy: req.user._id,
45 | rackMountable,
46 | isPart,
47 | manufacturer,
48 | model,
49 | warrantyMonths,
50 | user,
51 | history: [history._id],
52 | });
53 |
54 | await product.save();
55 | res.status(201).json(product);
56 | } catch (error) {
57 | res.status(400).json({ error: error.message });
58 | }
59 | };
60 | export const getAllProducts = async (req, res) => {
61 | try {
62 | const {
63 | page = 1,
64 | itemsperpage = 10,
65 | search = "",
66 | manufacturer,
67 | } = req.query;
68 |
69 | let regex = new RegExp(search, "i");
70 |
71 | const query = {
72 | $or: [
73 | { title: regex },
74 | { description: regex },
75 | { serialNo: regex },
76 | { model: regex },
77 | ],
78 | };
79 |
80 | if (manufacturer) {
81 | query.manufacturer = mongoose.Types.ObjectId(manufacturer);
82 | }
83 |
84 | const skipItems = (page - 1) * itemsperpage;
85 |
86 | const totalCount = await Product.countDocuments(query);
87 | const pages_count = Math.ceil(totalCount / itemsperpage);
88 |
89 | const products = await Product.find(query)
90 | .skip(skipItems)
91 | .limit(parseInt(itemsperpage))
92 | .populate({
93 | path: "history",
94 | populate: {
95 | path: "location",
96 | },
97 | })
98 | .populate("manufacturer");
99 |
100 | res.status(200).json({
101 | data: products,
102 | pages_count,
103 | currentPage: page,
104 | });
105 | } catch (error) {
106 | res.status(500).json({ error: error.message });
107 | }
108 | };
109 |
110 | // Controller to get a single product by ID
111 | export const getProductById = async (req, res) => {
112 | try {
113 | const product = await Product.findById(req.params.id).populate("createdBy");
114 | console.log("product", product);
115 | if (!product) {
116 | return res.status(404).json({ error: "Product not found" });
117 | }
118 | res.status(200).json(product);
119 | } catch (error) {
120 | res.status(500).json({ error: error.message });
121 | }
122 | };
123 | export const getProductHistroy = async (req, res) => {
124 | try {
125 | const product = await Product.findById(req.params.id)
126 | .populate("createdBy")
127 | .populate({
128 | path: "history",
129 | populate: {
130 | path: "location",
131 | },
132 | })
133 | .populate("manufacturer");
134 |
135 | console.log("product", product);
136 | if (!product) {
137 | return res.status(404).json({ error: "Product not found" });
138 | }
139 | res.status(200).json(product);
140 | } catch (error) {
141 | res.status(500).json({ error: error.message });
142 | }
143 | };
144 |
145 | // Controller to update a product by ID
146 | export const updateProductById = async (req, res) => {
147 | const productId = req.params.id;
148 | try {
149 | const {
150 | locationId,
151 | status,
152 | title,
153 | description,
154 | serialNo,
155 | createdBy,
156 | rackMountable,
157 | isPart,
158 | manufacturer,
159 | model,
160 | warrantyMonths,
161 | user,
162 | } = req.body;
163 |
164 | if (locationId) {
165 | // Create a new history entry for the location change
166 | const history = new HistoryModel({
167 | location: locationId,
168 | status: [
169 | {
170 | name: status,
171 | },
172 | ],
173 | });
174 |
175 | await history.save();
176 |
177 | // Update product information and push the new history entry
178 | await Product.findByIdAndUpdate(productId, {
179 | $set: {
180 | title,
181 | description,
182 | serialNo,
183 | createdBy,
184 | rackMountable,
185 | isPart,
186 | manufacturer,
187 | model,
188 | warrantyMonths,
189 | user,
190 | },
191 | $push: {
192 | history: history._id,
193 | },
194 | });
195 | } else if (status) {
196 | // Add a new status entry to the existing history
197 | const productRes = await Product.findById(productId);
198 | if (!productRes) {
199 | return res.status(404).json({ error: "Product not found" });
200 | }
201 | const historyId = productRes.history[0];
202 | const his = await HistoryModel.findById(historyId);
203 |
204 | // Push the new status object into the history
205 | his.status.push({
206 | name: status,
207 | });
208 |
209 | await his.save();
210 | } else {
211 | console.log("Unhandled condition");
212 | }
213 |
214 | res.status(200).json({ message: "success" });
215 | } catch (error) {
216 | res.status(400).json({ error: error.message });
217 | }
218 | };
219 |
220 | // Controller to delete a product by ID
221 | export const deleteProductById = async (req, res) => {
222 | try {
223 | const product = await Product.findByIdAndDelete(req.params.id);
224 | if (!product) {
225 | return res.status(404).json({ error: "Product not found" });
226 | }
227 | res.json({ message: "Product deleted successfully" });
228 | } catch (error) {
229 | res.status(500).json({ error: error.message });
230 | }
231 | };
232 |
--------------------------------------------------------------------------------
/Frontend/src/screens/users/UserManagementScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
4 | import LoadingIndicator from "../../components/LoadingIndicator";
5 | import ShowErrorMessage from "../../components/ShowErrorMessage";
6 | import { NavLink, useOutletContext } from "react-router-dom";
7 | import ManageUserTableRow from "./components/ManageUserTableRow";
8 | import { SERVER_URL } from "../../router";
9 |
10 | function UserManagementScreen() {
11 | const [data, user] = useOutletContext();
12 | console.log(user);
13 | const [isLoading, setLoading] = useState(true);
14 | const [itemsPerPage, setItemsPerPage] = useState(10);
15 | const [currentPage, setCurrentPage] = useState(1);
16 | const [totalPages, setTotalPages] = useState(0);
17 | const [users, setUsers] = useState([]);
18 | const [searchTerm, setSearchTerm] = useState("");
19 | const [error, setError] = useState(null);
20 | const [roleFilter, setRoleFilter] = useState("");
21 |
22 | useEffect(() => {
23 | getDataFromApi();
24 | }, [currentPage, itemsPerPage, searchTerm, roleFilter]);
25 |
26 | async function getDataFromApi() {
27 | try {
28 | const { data } = await axios.get(
29 | `${SERVER_URL}/api/v1/users/all`,
30 | {
31 | params: {
32 | page: currentPage,
33 | itemsPerPage,
34 | search: searchTerm,
35 | role: roleFilter,
36 | },
37 | }
38 | );
39 | setUsers(data.users);
40 | setTotalPages(data.totalPages);
41 | setLoading(false);
42 | } catch (error) {
43 | setError(error.message);
44 | setLoading(false);
45 | }
46 | }
47 |
48 | function handlePrevPage() {
49 | if (currentPage > 1) {
50 | setCurrentPage(currentPage - 1);
51 | }
52 | }
53 |
54 | function handleNextPage() {
55 | if (currentPage < totalPages) {
56 | setCurrentPage(currentPage + 1);
57 | }
58 | }
59 |
60 | return (
61 |
62 |
63 |
64 | User Management
65 |
66 |
67 | Here you can modify the privilege of a user [admin/user]
68 |
69 |
70 |
71 |
72 | {error && (
73 |
reload}
75 | message={error}
76 | />
77 | )}
78 |
79 |
80 |
81 | setSearchTerm(e.target.value)}
87 | />
88 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | | Name |
106 | Email |
107 | Role |
108 | {user && user.role === "admin" && (
109 | Actions |
110 | )}
111 |
112 |
113 |
114 | {isLoading ? (
115 |
116 | |
117 |
118 | |
119 |
120 | ) : (
121 | users.map((_user) => (
122 |
123 | ))
124 | )}
125 |
126 |
127 |
165 |
166 |
167 |
168 | );
169 | }
170 |
171 | export default UserManagementScreen;
172 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 |
2 | # Inventory Management System (IMS)
3 |
4 | An Inventory Management System built with Vite, React.js for the frontend, and Node.js, Express, and MongoDB for the backend.
5 |
6 | ## Table of Contents
7 |
8 | - [Features](#features)
9 | - [Folder Structure](#folder-structure)
10 | - [Prerequisites](#prerequisites)
11 | - [Installation](#installation)
12 | - [Running the Application](#running-the-application)
13 | - [Environment Variables](#environment-variables)
14 | - [API Endpoints](#api-endpoints)
15 | - [Contributing](#contributing)
16 | - [License](#license)
17 |
18 | ## Features
19 |
20 | - User authentication and authorization
21 | - Manage products, companies, locations, and brands
22 | - Track product history
23 | - Dashboard with analytics
24 | - Responsive design with Tailwind CSS
25 |
26 | ## Folder Structure
27 |
28 | ```plaintext
29 | C:.
30 | ├───Backend
31 | │ │ .env
32 | │ │ .gitignore
33 | │ │ app.js
34 | │ │ config.npmrc
35 | │ │ package-lock.json
36 | │ │ package.json
37 | │ │ README.md
38 | │ │
39 | │ ├───controllers
40 | │ │ product_controller.js
41 | │ │ user_controllers.js
42 | │ │
43 | │ ├───db
44 | │ │ user_db.js
45 | │ │
46 | │ ├───middlewares
47 | │ │ user_auth.js
48 | │ │
49 | │ ├───models
50 | │ │ company_model.js
51 | │ │ history_model.js
52 | │ │ locations_models.js
53 | │ │ product_model.js
54 | │ │ user_model.js
55 | │ │
56 | │ ├───routes
57 | │ │ analyticsRoutes.js
58 | │ │ companyRoutes.js
59 | │ │ historyRoutes.js
60 | │ │ locationRoutes.js
61 | │ │ productRoutes.js
62 | │ │ user_routes.js
63 | │ │
64 | │ └───utils
65 | │ user_utils.js
66 | │
67 | └───Frontend
68 | │ .env
69 | │ .eslintrc.cjs
70 | │ .gitignore
71 | │ index.html
72 | │ package-lock.json
73 | │ package.json
74 | │ postcss.config.js
75 | │ README.md
76 | │ tailwind.config.js
77 | │ vite.config.js
78 | │
79 | ├───public
80 | │ vite.svg
81 | │
82 | └───src
83 | │ App.jsx
84 | │ index.css
85 | │ main.jsx
86 | │ router.jsx
87 | │
88 | ├───assets
89 | │ admin-logo.svg
90 | │ authenticate.svg
91 | │ menu.svg
92 | │ react.svg
93 | │ undraw_empty_re.svg
94 | │ user-logo.svg
95 | │
96 | ├───components
97 | │ HeaderBar.jsx
98 | │ LoadingIndicator.jsx
99 | │ LogoutButton.jsx
100 | │ PopUpComponenet.jsx
101 | │ ShowErrorMessage.jsx
102 | │ ShowSuccessMesasge.jsx
103 | │ SideNavbar.jsx
104 | │ WarrantyExpiringProductsTableComponent.jsx
105 | │
106 | └───screens
107 | │ InventoryFormScreen.jsx
108 | │
109 | ├───brands
110 | │ BrandsScreen.jsx
111 | │ EditBrandsScreen.jsx
112 | │ NewBrandsScreen.jsx
113 | │
114 | ├───dashboard
115 | │ │ DashBoardLayout.jsx
116 | │ │ DashBoardScreen.jsx
117 | │ │
118 | │ └───components
119 | │ AnalyticsComponent.jsx
120 | │ PieChart.jsx
121 | │
122 | ├───locations
123 | │ EditLocationScreen.jsx
124 | │ LocationsScreen.jsx
125 | │ NewLocationScreen.jsx
126 | │
127 | ├───login
128 | │ AuthLayout.jsx
129 | │ LoginScreen.jsx
130 | │ SignupScreen.jsx
131 | │
132 | ├───product
133 | │ AddNewProductScreen.jsx
134 | │ ProductEditScreen.jsx
135 | │ ProductHistoryScreen.jsx
136 | │ ProductInfoScreen.jsx
137 | │ ProductsScreen.jsx
138 | │
139 | └───users
140 | │ UserManagementScreen.jsx
141 | │
142 | └───components
143 | ChangeRolePopup.jsx
144 | ManageUserTableRow.jsx
145 | ```
146 |
147 | ## Prerequisites
148 |
149 | - Node.js
150 | - npm or yarn
151 | - MongoDB
152 |
153 | ## Installation
154 |
155 | 1. **Clone the repository:**
156 | ```bash
157 | git clone https://github.com/your-username/your-repo.git
158 | cd your-repo
159 | ```
160 |
161 | 2. **Backend Setup:**
162 | ```bash
163 | cd Backend
164 | npm install
165 | ```
166 |
167 | 3. **Frontend Setup:**
168 | ```bash
169 | cd ../Frontend
170 | npm install
171 | ```
172 |
173 | ## Running the Application
174 |
175 | 1. **Run Backend:**
176 | ```bash
177 | cd Backend
178 | npm start
179 | ```
180 |
181 | 2. **Run Frontend:**
182 | ```bash
183 | cd ../Frontend
184 | npm run dev
185 | ```
186 |
187 | ## Environment Variables
188 |
189 | Create a `.env` file in the Backend and Frontend directories and configure the following:
190 |
191 | ### Backend `.env`:
192 | ```env
193 | MONGODB_URI=your_mongodb_connection_string
194 | PORT=3000
195 | SECRET_KEY=your_secret_key
196 | NODE_ENV=development
197 | ORIGIN=http://localhost:3000
198 | ```
199 |
200 | ### Frontend `.env`:
201 | ```env
202 | VITE_SERVER=https://inventory-management-backend-hsaf.onrender.com
203 | VITE_MODE=PROD
204 | VITE_LOCAL=http://localhost:3000
205 | ```
206 |
207 | ## API Endpoints
208 |
209 | ### User Routes
210 |
211 | - **POST** `/api/v1/users/signup` - Sign up a new user
212 | - **POST** `/api/v1/users/login` - Log in a user
213 | - **GET** `/api/v1/users/logout` - Log out a user
214 |
215 | ### Product Routes
216 |
217 | - **GET** `/api/v1/products` - Get all products
218 | - **POST** `/api/v1/products` - Add a new product
219 | - **PUT** `/api/v1/products/:id` - Update a product
220 | - **DELETE** `/api/v1/products/:id` - Delete a product
221 |
222 | ### History Routes
223 |
224 | - **GET** `/api/v1/history/:productId` - Get product history
225 |
226 | ### Company Routes
227 |
228 | - **GET** `/api/v1/companies` - Get all companies
229 | - **POST** `/api/v1/companies` - Add a new company
230 |
231 | ### Location Routes
232 |
233 | - **GET** `/api/v1/locations` - Get all locations
234 | - **POST** `/api/v1/locations` - Add a new location
235 |
236 | ### Analytics Routes
237 |
238 | - **GET** `/api/v1/analytics` - Get analytics data
239 |
240 | ## Contributing
241 |
242 | Contributions are welcome! Please open an issue or submit a pull request for any changes.
243 |
244 | ## License
245 |
246 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
247 |
--------------------------------------------------------------------------------
/Frontend/src/screens/product/ProductHistoryScreen.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import LoadingIndicator from "../../components/LoadingIndicator";
4 | import ShowErrorMessage from "../../components/ShowErrorMessage";
5 | import { IoMailOutline } from "react-icons/io5";
6 | import { FaUser } from "react-icons/fa";
7 | import { Link, NavLink } from "react-router-dom";
8 | import { useParams } from "react-router-dom";
9 |
10 | import { useCallback } from "react";
11 | import ReactFlow, {
12 | addEdge,
13 | ConnectionLineType,
14 | Panel,
15 | useNodesState,
16 | useEdgesState,
17 | } from "reactflow";
18 | import dagre from "dagre";
19 | import { ProductRow } from "./ProductsScreen";
20 | import { SERVER_URL } from "../../router";
21 |
22 | function ProductHistoryScreen() {
23 | const params = useParams();
24 | const [isLoading, setLoading] = useState(true);
25 | const [productData, setData] = useState(null);
26 | const [isError, setError] = useState("");
27 |
28 | useEffect(() => {
29 | getDataFromApi();
30 | }, []);
31 | async function getDataFromApi() {
32 | try {
33 | const { data } = await axios.get(
34 | `${SERVER_URL}/api/v1/products/${params.id}/history`
35 | );
36 | // console.log(data);
37 | setData(data);
38 | } catch (e) {
39 | console.log(e);
40 | setError(e);
41 | } finally {
42 | setLoading(false);
43 | }
44 | }
45 | console.log(productData?.history);
46 | return (
47 |
48 |
49 |
50 |
Product Information
51 |
52 |
53 |
54 | {isLoading && (
55 |
56 |
57 |
58 | )}
59 |
60 | {isError && (
61 |
62 |
68 | reload
69 |
70 | }
71 | />
72 |
73 | )}
74 |
75 |
76 |
77 |
78 |
79 |
80 | | DETAILS |
81 | SERIAL NUMBER |
82 | USED BY |
83 | isPart |
84 | RACKMOUNTABLE |
85 | DATE OF PURCHASE |
86 | MODEL |
87 | WARRANTY |
88 | MANUFACTURER |
89 |
90 |
91 |
92 | {isLoading ? (
93 |
94 | |
95 |
96 | |
97 |
98 | ) : (
99 |
100 |
101 |
102 |
103 | {productData.title}
104 |
105 |
106 | {productData.description}
107 |
108 |
109 | |
110 |
111 | {productData.serialNo}
112 | |
113 |
114 | {productData.user}
115 | |
116 |
117 | {productData.isPart ? "TRUE" : "FALSE"}
118 | |
119 |
120 | {productData.rackMountable ? "TRUE" : "FALSE"}
121 | |
122 |
123 | {productData.dateOfPurchase.split("T")[0]}
124 | |
125 |
126 | {productData.model}
127 | |
128 |
129 | {productData.warrantyMonths}
130 | |
131 |
132 | {productData.manufacturer.name}
133 | |
134 |
135 | )}
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
Product History
144 |
145 |
146 |
147 | {productData && (
148 |
149 | )}
150 |
151 |
152 | );
153 | }
154 | const HistoryTable = ({ historyInformation }) => {
155 | return (
156 |
157 |
158 |
159 |
160 |
161 | |
162 | Location
163 | |
164 |
165 | Description
166 | |
167 |
168 | Status
169 | |
170 |
171 | Date
172 | |
173 |
174 |
175 |
176 | {historyInformation.map((history) => (
177 |
178 | |
179 | {history.location.name}
180 | |
181 |
182 | {history.location.description}
183 | |
184 |
185 | {history.status.map((status, index) => (
186 |
187 | {status.name}
188 |
189 | ))}
190 | |
191 |
192 | {history.status.map((status, index) => (
193 |
194 |
195 | {new Date(status.date).toLocaleDateString()}
196 |
197 |
198 | ))}
199 | |
200 |
201 | ))}
202 |
203 |
204 |
205 |
206 | );
207 | };
208 |
209 | export default ProductHistoryScreen;
210 |
--------------------------------------------------------------------------------
/Frontend/src/screens/InventoryFormScreen.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useState } from "react";
3 | import { useOutletContext } from "react-router-dom";
4 | import { SERVER_URL } from "../router";
5 |
6 | function InventoryForm() {
7 | const [isLoading, seELoading] = useState(false);
8 | const [isError, setError] = useState(null);
9 | const [isSuccess, setSuccess] = useState(false);
10 |
11 | const [data] = useOutletContext();
12 | const [formData, setFormData] = useState({
13 | createdBy: data.user._id,
14 | title: "",
15 | description: "",
16 | serialNo: "",
17 | manufacturer: "",
18 | model: "",
19 | dateOfPurchase: "",
20 | warrantyMonths: "",
21 | status: "none",
22 | user: "none",
23 | rackMountable: false,
24 | isPart: false,
25 | });
26 |
27 | const handleChange = (e) => {
28 | const { name, value, type, checked } = e.target;
29 | setFormData((prevData) => ({
30 | ...prevData,
31 | [name]: type === "checkbox" ? checked : value,
32 | }));
33 | };
34 |
35 | const handleSubmit = async (e) => {
36 | e.preventDefault();
37 | // Add form submission logic here
38 | console.log(formData);
39 |
40 | try {
41 | if (isError) setError(false);
42 | if (isSuccess) setSuccess(false);
43 |
44 | const { data, status } = await axios.post(
45 | `${SERVER_URL}/api/v1/products/`,
46 | formData,
47 | {
48 | withCredentials: "include",
49 | headers: {
50 | "Content-Type": "application/json",
51 | },
52 | }
53 | );
54 |
55 | if (status === 201) {
56 | setSuccess(true);
57 | setFormData({
58 | ...formData,
59 | title: "",
60 | description: "",
61 | serialNo: "",
62 | manufacturer: "",
63 | model: "",
64 | dateOfPurchase: "",
65 | warrantyMonths: "",
66 | status: "none",
67 | user: "none",
68 | rackMountable: false,
69 | isPart: false,
70 | });
71 | }
72 | if (status === 400) {
73 | throw new Error(data.error);
74 | }
75 | } catch (e) {
76 | setError("Error while addin new item");
77 | console.log(e);
78 | } finally {
79 | seELoading(false);
80 | }
81 | };
82 |
83 | return (
84 |
263 | );
264 | }
265 |
266 | export default InventoryForm;
267 |
--------------------------------------------------------------------------------
/Frontend/src/screens/product/ProductsScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import axios from "axios";
3 | import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
4 | import LoadingIndicator from "../../components/LoadingIndicator";
5 | import { ImEqualizer } from "react-icons/im";
6 | import { Link, NavLink, useOutletContext } from "react-router-dom";
7 | import { SERVER_URL } from "../../router";
8 |
9 | function ProductsScreen() {
10 | const [isLoading, setLoading] = useState(true);
11 | const [currentPage, setCurrentPage] = useState(1);
12 | const [itemsPerPage, setItemsPerPage] = useState(10);
13 | const [totalPages, setTotalPages] = useState(0);
14 | const [products, setProducts] = useState([]);
15 | const [searchTerm, setSearchTerm] = useState("");
16 | const [error, setError] = useState(null);
17 |
18 | useEffect(() => {
19 | fetchData();
20 | }, [currentPage, itemsPerPage, searchTerm]);
21 |
22 | async function fetchData() {
23 | try {
24 | const response = await axios.get(
25 | `${SERVER_URL}/api/v1/products`,
26 | {
27 | params: {
28 | page: currentPage,
29 | itemsperpage: itemsPerPage,
30 | search: searchTerm,
31 | },
32 | }
33 | );
34 | setProducts(response.data.data);
35 | setTotalPages(response.data.pages_count);
36 | setLoading(false);
37 | } catch (error) {
38 | setError(error.message);
39 | setLoading(false);
40 | }
41 | }
42 |
43 | function handlePrevPage() {
44 | if (currentPage > 1) {
45 | setCurrentPage(currentPage - 1);
46 | }
47 | }
48 |
49 | function handleNextPage() {
50 | if (currentPage < totalPages) {
51 | setCurrentPage(currentPage + 1);
52 | }
53 | }
54 |
55 | return (
56 |
57 |
58 |
Products
59 |
60 | Here are the products created by you!
61 |
62 |
63 |
64 |
65 |
66 |
setSearchTerm(e.target.value)}
72 | />
73 | {/*
74 |
75 | Filter
76 |
*/}
77 |
78 |
84 | Create Product
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | | DETAILS |
95 | SERIAL NUMBER |
96 | USED BY |
97 | isPart |
98 | RACKMOUNTABLE |
99 | DATE OF PURCHASE |
100 | MODEL |
101 | WARRANTY |
102 | HISTORY |
103 | ACTION |
104 |
105 |
106 |
107 | {isLoading ? (
108 |
109 | |
110 |
111 | |
112 |
113 | ) : (
114 | products.map((product, idx) => (
115 |
122 | ))
123 | )}
124 |
125 |
126 |
164 |
165 |
166 |
167 | );
168 | }
169 |
170 | export function ProductRow({ product, index, currentPage, itemsPerPage }) {
171 | // console.log(product)
172 | const [_, user] = useOutletContext();
173 | return (
174 |
175 |
176 |
177 | {index + 1 + (currentPage - 1) * itemsPerPage}
178 |
179 |
180 |
181 | {product.title}
182 |
183 |
184 | {/* {product.description} */}
185 | {product.manufacturer.name}
186 |
187 |
188 | |
189 |
190 | {product.serialNo}
191 | |
192 |
193 | {product.user}
194 | |
195 |
196 | {product.isPart ? "TRUE" : "FALSE"}
197 | |
198 |
199 | {product.rackMountable ? "TRUE" : "FALSE"}
200 | |
201 |
202 | {product.dateOfPurchase.split("T")[0]}
203 | |
204 |
205 | {product.model}
206 | |
207 |
208 | {product.warrantyMonths}
209 | |
210 |
211 |
215 | View
216 |
217 | |
218 |
219 |
224 | {user._id === product.createdBy ? "Edit" : "Action Not allowed"}
225 |
226 | |
227 |
228 | );
229 | }
230 |
231 | export default ProductsScreen;
232 |
--------------------------------------------------------------------------------
/Frontend/src/screens/product/AddNewProductScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import ShowSuccessMesasge from "../../components/ShowSuccessMesasge";
4 | import { SERVER_URL } from "../../router";
5 |
6 | function AddNewProductScreen() {
7 | const [isError, setisError] = useState("");
8 | const [allLocations, setAllLocations] = useState([]);
9 | const [manufacturer, setManufacturer] = useState([]);
10 | const [formData, setFormData] = useState({
11 | locationId: "",
12 | status: "not in use",
13 | title: "",
14 | description: "",
15 | serialNo: "",
16 | rackMountable: false,
17 | isPart: false,
18 | manufacturer: "",
19 | model: "",
20 | warrantyMonths: "",
21 | user: "department",
22 |
23 | dateOfPurchase: "",
24 | });
25 | const [isLoading, setIsLoading] = useState(false);
26 | const [error, setError] = useState("");
27 | const [successMessage, setSuccessMessage] = useState("");
28 |
29 | const handleChange = (e) => {
30 | const { name, value } = e.target;
31 |
32 | setFormData({ ...formData, [name]: value });
33 | };
34 |
35 | useEffect(() => {
36 | fetchNecessaryData();
37 | }, []);
38 | const fetchNecessaryData = async () => {
39 | try {
40 | const manufacturersRes = await axios.get(
41 | `${SERVER_URL}/api/v1/brands`
42 | );
43 | const locationsRes = await axios.get(
44 | `${SERVER_URL}/api/v1/location`
45 | );
46 | setAllLocations(locationsRes.data);
47 | setManufacturer(manufacturersRes.data);
48 | if (locationsRes.data.length > 0 && manufacturersRes.data.length > 0) {
49 | setFormData({
50 | ...formData,
51 | manufacturer: manufacturersRes.data[0]._id,
52 | locationId: locationsRes.data[0]._id,
53 | });
54 | }
55 | } catch (e) {
56 | setError(e);
57 | console.log(e);
58 | } finally {
59 | }
60 | };
61 | const handleSubmit = async (e) => {
62 | e.preventDefault();
63 | setIsLoading(true);
64 | setError("");
65 | try {
66 | const response = await axios.post(
67 | `${SERVER_URL}/api/v1/products`,
68 | formData,
69 | {
70 | withCredentials: true,
71 | headers: {
72 | "Content-Type": "application/json",
73 | },
74 | }
75 | );
76 | setSuccessMessage("Product added successfully");
77 | setFormData({
78 | locationId: "",
79 | status: "",
80 | title: "",
81 | description: "",
82 | serialNo: "",
83 | rackMountable: false,
84 | isPart: false,
85 | manufacturer: "",
86 | model: "",
87 | warrantyMonths: "",
88 | user: "",
89 | dateOfPurchase: "",
90 | });
91 | console.log(formData);
92 | } catch (error) {
93 | setError("Failed to add product");
94 | }
95 | setIsLoading(false);
96 | };
97 |
98 | return (
99 |
100 |
101 | Add New Product
102 |
103 | {error &&
{error}
}
104 | {successMessage &&
{successMessage}
}
105 |
338 |
339 | {successMessage &&
{successMessage} }
341 |
342 | />}
343 |
344 | );
345 | }
346 |
347 | export default AddNewProductScreen;
348 |
--------------------------------------------------------------------------------
/Frontend/src/screens/product/ProductEditScreen.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useOutletContext, useParams } from "react-router-dom";
3 | import axios from "axios"; // Import Axios
4 | import ShowSuccessMesasge from "../../components/ShowSuccessMesasge";
5 | import { SERVER_URL } from "../../router";
6 |
7 | function ProductEditScreen() {
8 | const params = useParams();
9 | const [isLoading, seELoading] = useState(false);
10 | const [isError, setError] = useState(null);
11 | const [isSuccess, setSuccess] = useState(false);
12 |
13 | const [allLocations, setAllLocations] = useState([]);
14 | const [manufacturer, setManufacturer] = useState([]);
15 |
16 | const [data] = useOutletContext();
17 | const [formData, setFormData] = useState({
18 | createdBy: data.user._id,
19 | locationId: "",
20 | status: "not in use",
21 | title: "",
22 | description: "",
23 | serialNo: "",
24 | rackMountable: false,
25 | isPart: false,
26 | manufacturer: "",
27 | model: "",
28 | warrantyMonths: "",
29 | user: "department",
30 | dateOfPurchase: "",
31 | });
32 |
33 | useEffect(() => {
34 | getProductInfo();
35 | }, [params.id]);
36 | const getProductInfo = async () => {
37 | try {
38 | seELoading(true);
39 | const manufacturersRes = await axios.get(
40 | `${SERVER_URL}/api/v1/brands`
41 | );
42 | const locationsRes = await axios.get(
43 | `${SERVER_URL}/api/v1/location`
44 | );
45 | setAllLocations(locationsRes.data);
46 | setManufacturer(manufacturersRes.data);
47 | if (locationsRes.data.length > 0 && manufacturersRes.data.length > 0) {
48 | setFormData({
49 | ...formData,
50 | manufacturer: manufacturersRes.data[0]._id,
51 | locationId: locationsRes.data[0]._id,
52 | });
53 | }
54 |
55 | const { data, status } = await axios.get(
56 | `${SERVER_URL}/api/v1/products/${params.id}`,
57 | {
58 | withCredentials: true, // Change "include" to true
59 | headers: {
60 | "Content-Type": "application/json",
61 | },
62 | }
63 | );
64 | console.log(data);
65 | if (status === 200) {
66 | const id = formData.createdBy;
67 |
68 | setFormData({
69 | ...data,
70 | createdBy: id,
71 | });
72 | } else {
73 | throw new Error(data.error);
74 | }
75 | } catch (error) {
76 | setError("Error while fetching product information");
77 | console.error(error);
78 | } finally {
79 | seELoading(false); // Set loading state to false
80 | }
81 | };
82 |
83 | const handleChange = (e) => {
84 | const { name, value } = e.target;
85 |
86 | setFormData({ ...formData, [name]: value });
87 | };
88 |
89 | const handleSubmit = async (e) => {
90 | e.preventDefault();
91 | // Add form submission logic here
92 | console.log(formData);
93 |
94 | try {
95 | if (isError) setError(false);
96 | if (isSuccess) setSuccess(false);
97 |
98 | const { data, status } = await axios.put(
99 | `${SERVER_URL}/api/v1/products/${formData._id}`,
100 | formData,
101 | {
102 | withCredentials: "include",
103 | headers: {
104 | "Content-Type": "application/json",
105 | },
106 | }
107 | );
108 |
109 | if (status === 200) {
110 | setSuccess(true);
111 | alert("Product updated");
112 | }
113 | if (status === 404 || status === 400) {
114 | throw new Error(data.error);
115 | }
116 | } catch (e) {
117 | setError("Error while addin new item");
118 | console.log(e);
119 | } finally {
120 | seELoading(false);
121 | }
122 | };
123 |
124 | return (
125 |
126 |
127 | Edit Product
128 |
129 | {isError &&
{isError}
}
130 | {isSuccess &&
{"Updated successfully"}
}
131 |
364 |
365 | {isSuccess && (
366 |
369 | {"Updated Successfully"}
370 |
371 | }
372 | />
373 | )}
374 |
375 | );
376 | }
377 |
378 | {
379 | /* */
388 | }
389 |
390 | export default ProductEditScreen;
391 |
--------------------------------------------------------------------------------
/Frontend/src/assets/undraw_empty_re.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------