Ticket Raising Platform using MERN Stack
Last Updated :
13 Mar, 2024
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:
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):
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):
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.
Share your thoughts in the comments
Please Login to comment...