Open In App

Ticket Raising Platform using MERN Stack

Last Updated : 13 Mar, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

The Ticket Raising Platform project is a web application that enables users to create, manage, and track tickets for various issues or tasks. Built using the MERN (MongoDB, ExpressJS, ReactJS, NodeJS) stack, this project provides a comprehensive solution for efficiently handling ticket requests and resolutions.

Preview Image of Final Output:

Screenshot-2024-03-11-110307

Prerequisites

Approach to Creating a Ticket Raising Platform using MERN:

  • Creating new tickets with titles, descriptions, priorities, and status.
  • Viewing a list of tickets with filtering options based on status and priority.
  • Updating ticket details such as status and priority.
  • Deleting tickets.
  • Searching for tickets based on keywords in titles, descriptions, or creators.

Steps to Create the NodeJS App and Installing Module:

Step 1: Initialize a new NodeJS project using the following command:

npm init -y

Step 2: Install required dependencies using the following command:

npm install express mongoose cors

Step 3: Create models and routes for handling ticket data (CRUD operations) in the routes/ and models/ directories.

Project Structure(Backend):Screenshot-2024-03-11-110011

The updated dependencies in package.json file of backend will look like:

  "dependencies": {
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"express": "^4.18.2",
"mongoose": "^8.1.0"
}

Example: Write the following code in backend files.

Javascript
//index.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const ticketRoutes = require('./routes/ticketRoutes.js');

const app = express();
const PORT = process.env.PORT || 5000;

app.use(cors());
app.use(express.json());

mongoose.connect('mongodb://localhost:27017/ticketRaisingPlatform', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
});

app.use('/api', ticketRoutes);

app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});
Javascript
// models/ticket.js
const mongoose = require('mongoose');

const ticketSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    description: {
        type: String,
        required: true
    },
    status: {
        type: String,
        enum: ['Open', 'In Progress', 'Resolved'],
        default: 'Open'
    },
    priority: {
        type: String,
        enum: ['Low', 'Medium', 'High'],
        default: 'Medium'
    },
    createdBy: {
        type: String,
        required: true
    },
    createdAt: {
        type: Date,
        default: Date.now
    },
});

const Ticket = mongoose.model('Ticket', ticketSchema);

module.exports = Ticket;
Javascript
// ticketRoutes.js
const express = require('express');
const router = express.Router();
const Ticket = require('../models/Ticket.js');

router.get('/tickets',
    async (req, res) => {
        try {
            const tickets = await Ticket.find();
            res.json(tickets);
        } catch (error) {
            res.status(500).json({
                message: error.message
            });
        }
    });

router.get('/tickets/:id',
    getTicket, (req, res) => {
        res.json(res.ticket);
    });

router.post('/tickets', async (req, res) => {
    const ticket = new Ticket({
        title: req.body.title,
        description: req.body.description,
        createdBy: req.body.createdBy,
        status: 'Open',
        priority: req.body.priority || 'Low',
    });

    try {
        const newTicket = await ticket.save();
        res.status(201).json(newTicket);
    } catch (error) {
        res.status(400).json({
            message: error.message
        });
    }
});

router.patch('/tickets/:id', getTicket,
    async (req, res) => {
        if (req.body.title != null) {
            res.ticket.title = req.body.title;
        }
        if (req.body.description != null) {
            res.ticket.description = req.body.description;
        }
        if (req.body.status != null) {
            res.ticket.status = req.body.status;
        }
        if (req.body.priority != null) {
            res.ticket.priority = req.body.priority;
        }

        try {
            const updatedTicket = await res.ticket.save();
            res.json(updatedTicket);
        } catch (error) {
            res.status(400).json({
                message: error.message
            });
        }
    });

router.delete('/tickets/:id',
    getTicket, async (req, res) => {
        try {
            // Use deleteOne method here
            await res.ticket.deleteOne();
            res.json({ message: 'Ticket deleted' });
        } catch (error) {
            res.status(500).json({
                message: error.message
            });
        }
    });

async function getTicket(req, res, next) {
    let ticket;
    try {
        ticket = await Ticket.findById(req.params.id);
        if (ticket == null) {
            return res.status(404).json({
                message: 'Ticket not found'
            });
        }
    } catch (error) {
        return res.status(500).json({
            message: error.message
        });
    }

    res.ticket = ticket;
    next();
}

module.exports = router;

Start your server using the following command.

node server.js

Steps to Create the Frontend App and Installing Module:

Step 1: Create a new React.js project and install the required dependencies:-

npx create-react-app ticket-raising-platform.

Step 2: Navigate to the root directory of your project using the following command.

cd ticket-raising-platform

Step 3: Install the required packages in your project using the following command.

npm install axios

Step 5: Add the following code to theApp.js to render the featrues of ticket creation, listing, and management in the src/ directory.

Project Structure(Frontend):

Screenshot-2024-03-11-105938

The updated dependencies in package.json file of frontend will look like:

"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.2",
"react": "^18.2.0",
"react-bootstrap": "^2.10.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.21.2",
"react-scripts": "^5.0.1",
"web-vitals": "^2.1.4"
}

Example: Below is an example of creating a ticket raising platform using MERN.

CSS
/* Add the following styles to frontend/src/App.css */

body {
    font-family: 'Arial', sans-serif;
    margin: 0;
    padding: 0;
    background-color: #f5f5f5;
}

.App {
    margin: 0 auto;
    padding: 20px;
    background-color: #fff;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

h1,
h2 {
    color: #333;
}

form {
    margin-bottom: 20px;
}

label {
    display: block;
    margin-bottom: 10px;
    color: #555;
}

input,
textarea,
select {
    width: 100%;
    padding: 10px;
    margin-bottom: 15px;
    border: 1px solid #ddd;
    border-radius: 4px;
}

button {
    background-color: #4caf50;
    color: white;
    padding: 10px 15px;
    border: none;
    margin: 2%;
    border-radius: 4px;
    cursor: pointer;
}

button:hover {
    background-color: #45a049;
}

ul {
    list-style-type: none;
    padding: 0;
}

li {
    background-color: #fff;
    margin-bottom: 10px;
    padding: 15px;
    border-radius: 4px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);
}

li strong {
    color: #4caf50;
}

li small {
    color: #888;
}

/* Add a hover effect on ticket items */
li:hover {
    transform: scale(1.02);
    transition: transform 0.3s ease-in-out;
}

/* Styles for filter and search */
form label {
    font-weight: bold;
}

form select,
form input[type="text"] {
    width: calc(100% - 22px);
}

form input[type="text"] {
    padding: 8px;
}

/* Optional: Style the search bar with a magnifying glass icon */
label[for="search"]::before {
    content: "\1F50D";
    /* Unicode for magnifying glass icon */
    font-size: 18px;
    margin-right: 8px;
}

/* Update the existing NavBar.css file or create a new one */

.navbar {
    background-color: #2c3e50;
    padding: 15px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

.navbar-brand {
    font-size: 24px;
    font-weight: bold;
    color: #ecf0f1;
    text-decoration: none;
}

/* ... (existing styles) */

.card-container {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;

    border: #333 2px 2px 2px 2px solid;

}


.card {
    margin: 3%;
    padding: 2%;
    width: 200px;
    border-radius: 15px;
    text-align: center;


}

.card-content {
    text-align: center;
    font-weight: bolder;
}


.navbar-links {
    list-style: none;
    display: flex;
    gap: 20px;
    margin: 0;
}

.navbar-link {
    font-size: 18px;
    color: #ecf0f1;
    text-decoration: none;
    transition: color 0.3s ease-in-out;
}

.navbar-link:hover {
    color: #3498db;
}
Javascript
// App.js
import React, { useState, useEffect } from 'react';
import './App.css';

function App() {
    const [tickets, setTickets] = useState([]);
    const [formData, setFormData] = useState({
        title: '',
        description: '',
        createdBy: '',
        search: '',
        priority: 'Low',
    });
    const [filter, setFilter] = useState({
        status: 'All',
        priority: 'All',
    });

    const fetchTickets = async () => {
        try {
            const response = await
                fetch('http://localhost:5000/api/tickets');
            const data = await response.json();
            setTickets(data);
        } catch (error) {
            console.error(
                'Error fetching tickets:', error);
        }
    };

    useEffect(() => {
        fetchTickets();
    }, []);

    const handleInputChange = (e) => {
        setFormData({
            ...formData,
            [e.target.name]: e.target.value
        });
    };

    const handleFilterChange = (e) => {
        setFilter({
            ...filter,
            [e.target.name]: e.target.value
        });
    };

    const handleSubmit = async (e) => {
        e.preventDefault();

        try {
            const response = await
                fetch('http://localhost:5000/api/tickets', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(formData),
                });

            const newTicket = await response.json();
            setTickets([...tickets, newTicket]);
            setFormData({
                title: '',
                description: '',
                createdBy: '',
                search: '',
                priority: 'Low'
            });
        } catch (error) {
            console.error('Error creating ticket:', error);
        }
    };

    const handleSearch = async (query) => {
        // Trim white spaces and convert to lowercase
        const searchQuery = query.toLowerCase().trim();

        if (searchQuery !== '') {
            const searchedTickets = tickets.filter(
                (ticket) =>
                    ticket.title.toLowerCase().includes(searchQuery) ||
                    ticket.description.toLowerCase().includes(searchQuery) ||
                    ticket.createdBy.toLowerCase().includes(searchQuery)
            );

            setTickets(searchedTickets);
        } else {
            // If search query is empty, revert to original list of tickets
            fetchTickets(); // Refetch tickets from the server
        }
    };



    const handleDelete = async (ticketId) => {
        try {
            await
                fetch(`http://localhost:5000/api/tickets/${ticketId}`, {
                    method: 'DELETE',
                });
            setTickets(tickets.filter((ticket) => ticket._id !== ticketId));
        } catch (error) {
            console.error('Error deleting ticket:', error);
        }
    };

    const handlePriorityChange = async (ticketId, newPriority) => {
        try {
            const response = await
                fetch(`http://localhost:5000/api/tickets/${ticketId}`, {
                    method: 'PATCH',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ priority: newPriority }),
                });

            const updatedTicket = await response.json();
            setTickets((prevTickets) =>
                prevTickets.map((ticket) =>
                    ticket._id === ticketId ? {
                        ...ticket,
                        priority: updatedTicket.priority
                    } : ticket
                )
            );
        } catch (error) {
            console.error('Error updating priority:', error);
        }
    };

    const filteredTickets = tickets.filter((ticket) => {
        const statusFilter =
            filter.status === 'All' ?
                true : ticket.status === filter.status;
        const priorityFilter =
            filter.priority === 'All' ?
                true : ticket.priority === filter.priority;

        return statusFilter && priorityFilter;
    });
    function getPriorityColor(priority) {
        switch (priority) {
            case 'Low':
                return '#aafaae'; // Green
            case 'Medium':
                return '#fcee68'; // Yellow
            case 'High':
                return '#fc8181'; // Red
            default:
                return '#fff'; // White for no priority
        }
    }
    return (
        <div className="App">
            <h1>Ticket Raising Platform</h1>
            <form onSubmit={handleSubmit}>
                <label>
                    Title:
                    <input
                        type="text"
                        name="title"
                        value={formData.title}
                        onChange={handleInputChange}
                    />
                </label>
                <label>
                    Description:
                    <textarea
                        name="description"
                        value={formData.description}
                        onChange={handleInputChange}
                    />
                </label>
                <label>
                    Created By:
                    <input
                        type="text"
                        name="createdBy"
                        value={formData.createdBy}
                        onChange={handleInputChange}
                    />
                </label>
                <label>
                    Priority:
                    <select
                        name="priority"
                        value={formData.priority}
                        onChange={handleInputChange}
                    >
                        <option value="Low">Low</option>
                        <option value="Medium">Medium</option>
                        <option value="High">High</option>
                    </select>
                </label>
                <button type="submit">Submit</button>
            </form>

            <h2>FILTERS AND SEARCH</h2>
            <label>
                Status:
                <select name="status"
                    value={filter.status}
                    onChange={handleFilterChange}>
                    <option value="All">All</option>
                    <option value="Open">Open</option>
                    <option value="In Progress">In Progress</option>
                    <option value="Resolved">Resolved</option>
                </select>
            </label>

            <label>
                Priority:
                <select name="priority"
                    value={filter.priority}
                    onChange={handleFilterChange}>
                    <option value="All">All</option>
                    <option value="Low">Low</option>
                    <option value="Medium">Medium</option>
                    <option value="High">High</option>
                </select>
            </label>

            <label>
                Search:
                <input
                    type="text"
                    name="search"
                    value={formData.search}
                    onChange={(e) => {
                        setFormData({
                            ...formData,
                            search: e.target.value
                        }); // Update formData.search
                        // Invoke handleSearch with the input value
                        handleSearch(e.target.value);
                    }}
                />


            </label>


            <h2>Tickets</h2>
            <div className="card-container">
                {filteredTickets.map((ticket) => (
                    <div
                        key={ticket._id}
                        className="card"
                        style={{
                            backgroundColor: getPriorityColor(ticket.priority)
                        }}
                    >

                        <div className="card-content">

                            <strong>{ticket.title}</strong> <br />
                            {ticket.description}<br />
                            (Created by: {ticket.createdBy})
                        </div>
                        <br />
                        <div className="card-actions">
                            <span>Priority: {ticket.priority}</span>
                            <br />
                            <br />
                            <label>
                                Update Priority:
                                <br />
                                <br />

                                <select
                                    value={ticket.priority}
                                    onChange={(e) =>
                                        handlePriorityChange(ticket._id,
                                            e.target.value)
                                    }
                                >
                                    <option value="Low">Low</option>
                                    <option value="Medium">Medium</option>
                                    <option value="High">High</option>
                                </select>
                            </label>
                            <button
                                onClick={() => handleDelete(ticket._id)}>
                                Delete
                            </button>
                        </div>
                    </div>
                ))}
            </div>
        </div>
    );
}

export default App;

Start your application using the following command.

npm start

Output: Open a web browser and visit http://localhost:3000 to access the Ticket Raising Platform.

432423-ezgifcom-optimize




Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads