mongoose-cta 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/install.js +29 -0
- package/package.json +10 -0
- package/template/backend/models/Ticket.js +10 -0
- package/template/backend/package.json +13 -0
- package/template/backend/routes/TicketRoutes.js +38 -0
- package/template/backend/server.js +19 -0
- package/template/frontend/package.json +27 -0
- package/template/frontend/public/index.html +12 -0
- package/template/frontend/src/App.js +14 -0
- package/template/frontend/src/components/TicketForm.css +50 -0
- package/template/frontend/src/components/TicketForm.js +58 -0
- package/template/frontend/src/components/TicketList.css +76 -0
- package/template/frontend/src/components/TicketList.js +72 -0
- package/template/frontend/src/index.js +10 -0
package/install.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const targetDir = process.env.INIT_CWD || process.cwd();
|
|
6
|
+
const templateDir = path.join(__dirname, 'template');
|
|
7
|
+
|
|
8
|
+
function copyRecursiveSync(src, dest) {
|
|
9
|
+
const exists = fs.existsSync(src);
|
|
10
|
+
const stats = exists && fs.statSync(src);
|
|
11
|
+
const isDirectory = exists && stats.isDirectory();
|
|
12
|
+
if (isDirectory) {
|
|
13
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest);
|
|
14
|
+
fs.readdirSync(src).forEach((childItemName) => {
|
|
15
|
+
copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
fs.copyFileSync(src, dest);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log('Dropping ticketing app files into ' + targetDir);
|
|
23
|
+
try {
|
|
24
|
+
copyRecursiveSync(templateDir, targetDir);
|
|
25
|
+
console.log('App scaffolded successfully!');
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error('Error copying files:', e);
|
|
28
|
+
}
|
|
29
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
const TicketSchema = new mongoose.Schema({
|
|
3
|
+
title: String,
|
|
4
|
+
description: String,
|
|
5
|
+
createdBy: String,
|
|
6
|
+
priority: { type: String, default: 'Low' },
|
|
7
|
+
status: { type: String, default: 'Open' },
|
|
8
|
+
createdAt: { type: Date, default: Date.now }
|
|
9
|
+
});
|
|
10
|
+
module.exports = mongoose.model('Ticket', TicketSchema);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const Ticket = require('../models/Ticket');
|
|
3
|
+
const router = express.Router();
|
|
4
|
+
|
|
5
|
+
router.post('/', async (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const ticket = new Ticket(req.body);
|
|
8
|
+
await ticket.save();
|
|
9
|
+
res.status(201).json(ticket);
|
|
10
|
+
} catch (err) {
|
|
11
|
+
res.status(400).json({ error: err.message });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
router.get('/', async (req, res) => {
|
|
16
|
+
const tickets = await Ticket.find();
|
|
17
|
+
res.json(tickets);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
router.patch('/:id', async (req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
const updated = await Ticket.findByIdAndUpdate(req.params.id, req.body, { new: true });
|
|
23
|
+
res.json(updated);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
res.status(400).json({ error: err.message });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
router.delete('/:id', async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
await Ticket.findByIdAndDelete(req.params.id);
|
|
32
|
+
res.json({ message: 'Deleted' });
|
|
33
|
+
} catch (err) {
|
|
34
|
+
res.status(400).json({ error: err.message });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
module.exports = router;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const mongoose = require('mongoose');
|
|
3
|
+
const cors = require('cors');
|
|
4
|
+
const app = express();
|
|
5
|
+
|
|
6
|
+
// Middleware
|
|
7
|
+
app.use(cors());
|
|
8
|
+
app.use(express.json());
|
|
9
|
+
|
|
10
|
+
// Connect to MongoDB
|
|
11
|
+
mongoose.connect('mongodb://127.0.0.1:27017/ticketDB')
|
|
12
|
+
.then(() => console.log('MongoDB Connected...'))
|
|
13
|
+
.catch(err => console.log(err));
|
|
14
|
+
|
|
15
|
+
// Use the route
|
|
16
|
+
const ticketRoute = require('./routes/TicketRoutes');
|
|
17
|
+
app.use('/api/tickets', ticketRoute);
|
|
18
|
+
|
|
19
|
+
app.listen(5000, () => console.log('Server is running on port 5000'));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "frontend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"axios": "^1.4.0",
|
|
7
|
+
"react": "^18.2.0",
|
|
8
|
+
"react-dom": "^18.2.0",
|
|
9
|
+
"react-scripts": "5.0.1"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"start": "react-scripts start",
|
|
13
|
+
"build": "react-scripts build"
|
|
14
|
+
},
|
|
15
|
+
"browserslist": {
|
|
16
|
+
"production": [
|
|
17
|
+
">0.2%",
|
|
18
|
+
"not dead",
|
|
19
|
+
"not op_mini all"
|
|
20
|
+
],
|
|
21
|
+
"development": [
|
|
22
|
+
"last 1 chrome version",
|
|
23
|
+
"last 1 firefox version",
|
|
24
|
+
"last 1 safari version"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Ticketing App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import TicketForm from "./components/TicketForm";
|
|
3
|
+
import TicketList from "./components/TicketList";
|
|
4
|
+
|
|
5
|
+
function App() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="App" style={{ padding: '20px' }}>
|
|
8
|
+
<TicketForm />
|
|
9
|
+
<TicketList />
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default App;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
.ticket-form {
|
|
2
|
+
max-width: 400px;
|
|
3
|
+
margin: auto;
|
|
4
|
+
padding: 25px;
|
|
5
|
+
background: white;
|
|
6
|
+
border-radius: 12px;
|
|
7
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
8
|
+
font-family: Arial, sans-serif;
|
|
9
|
+
}
|
|
10
|
+
.ticket-form h2 {
|
|
11
|
+
text-align: center;
|
|
12
|
+
margin-bottom: 20px;
|
|
13
|
+
}
|
|
14
|
+
.form-group {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
margin-bottom: 15px;
|
|
18
|
+
}
|
|
19
|
+
.form-group label {
|
|
20
|
+
margin-bottom: 6px;
|
|
21
|
+
font-weight: bold;
|
|
22
|
+
font-size: 14px;
|
|
23
|
+
}
|
|
24
|
+
.form-group input,
|
|
25
|
+
.form-group textarea,
|
|
26
|
+
.form-group select {
|
|
27
|
+
padding: 8px 10px;
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
border-radius: 6px;
|
|
30
|
+
border: 1px solid #ccc;
|
|
31
|
+
width: 100%;
|
|
32
|
+
box-sizing: border-box;
|
|
33
|
+
}
|
|
34
|
+
.form-group textarea {
|
|
35
|
+
resize: vertical;
|
|
36
|
+
min-height: 80px;
|
|
37
|
+
}
|
|
38
|
+
.submit-btn {
|
|
39
|
+
background-color: #007bff;
|
|
40
|
+
color: white;
|
|
41
|
+
font-size: 16px;
|
|
42
|
+
border: none;
|
|
43
|
+
padding: 10px;
|
|
44
|
+
border-radius: 6px;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
width: 100%;
|
|
47
|
+
}
|
|
48
|
+
.submit-btn:hover {
|
|
49
|
+
background-color: #0056b3;
|
|
50
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import './TicketForm.css';
|
|
4
|
+
|
|
5
|
+
function TicketForm() {
|
|
6
|
+
const [form, setForm] = useState({
|
|
7
|
+
title: '',
|
|
8
|
+
description: '',
|
|
9
|
+
priority: '',
|
|
10
|
+
createdBy: ''
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const handleChange = (e) => {
|
|
14
|
+
setForm({ ...form, [e.target.name]: e.target.value });
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const handleSubmit = async (e) => {
|
|
18
|
+
e.preventDefault();
|
|
19
|
+
try {
|
|
20
|
+
await axios.post('http://localhost:5000/api/tickets', form);
|
|
21
|
+
alert('Ticket created');
|
|
22
|
+
setForm({ title: '', description: '', priority: '', createdBy: '' });
|
|
23
|
+
window.dispatchEvent(new Event('ticketCreated'));
|
|
24
|
+
} catch (error) {
|
|
25
|
+
alert('Error creating ticket. Please try again');
|
|
26
|
+
console.error(error);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="ticket-form">
|
|
32
|
+
<h2>Ticket Tracker</h2>
|
|
33
|
+
<form onSubmit={handleSubmit}>
|
|
34
|
+
<div className='form-group'>
|
|
35
|
+
<label htmlFor="title">Title:</label>
|
|
36
|
+
<input type="text" id="title" name="title" value={form.title} onChange={handleChange} required />
|
|
37
|
+
|
|
38
|
+
<label htmlFor="description">Description:</label>
|
|
39
|
+
<textarea name="description" id="description" value={form.description} onChange={handleChange} required />
|
|
40
|
+
|
|
41
|
+
<label htmlFor="priority">Priority:</label>
|
|
42
|
+
<select name="priority" value={form.priority} onChange={handleChange} required>
|
|
43
|
+
<option value="">Select</option>
|
|
44
|
+
<option value="Low">Low</option>
|
|
45
|
+
<option value="Medium">Medium</option>
|
|
46
|
+
<option value="High">High</option>
|
|
47
|
+
</select>
|
|
48
|
+
|
|
49
|
+
<label htmlFor="createdBy">Your Name:</label>
|
|
50
|
+
<input type="text" name="createdBy" id="createdBy" value={form.createdBy} onChange={handleChange} required />
|
|
51
|
+
|
|
52
|
+
<button type="submit" className="submit-btn">Create Ticket</button>
|
|
53
|
+
</div>
|
|
54
|
+
</form>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
export default TicketForm;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
.ticket-list-container {
|
|
2
|
+
max-width: 800px;
|
|
3
|
+
margin: 40px auto;
|
|
4
|
+
padding: 20px;
|
|
5
|
+
font-family: Arial, sans-serif;
|
|
6
|
+
}
|
|
7
|
+
.ticket-list-container h2 {
|
|
8
|
+
text-align: center;
|
|
9
|
+
margin-bottom: 30px;
|
|
10
|
+
color: #333;
|
|
11
|
+
}
|
|
12
|
+
.ticket-card {
|
|
13
|
+
background-color: #fefefe;
|
|
14
|
+
border: 1px solid #ddd;
|
|
15
|
+
border-radius: 12px;
|
|
16
|
+
padding: 20px;
|
|
17
|
+
margin-bottom: 20px;
|
|
18
|
+
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
|
|
19
|
+
}
|
|
20
|
+
.ticket-card h3 {
|
|
21
|
+
margin-top: 0;
|
|
22
|
+
color: #007bff;
|
|
23
|
+
}
|
|
24
|
+
.ticket-card p {
|
|
25
|
+
margin: 8px 0;
|
|
26
|
+
color: #555;
|
|
27
|
+
}
|
|
28
|
+
.ticket-buttons {
|
|
29
|
+
margin-top: 15px;
|
|
30
|
+
display: flex;
|
|
31
|
+
gap: 10px;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
}
|
|
34
|
+
.ticket-buttons button {
|
|
35
|
+
padding: 10px 14px;
|
|
36
|
+
border: none;
|
|
37
|
+
border-radius: 8px;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
font-size: 14px;
|
|
40
|
+
transition: background-color 0.2s ease;
|
|
41
|
+
}
|
|
42
|
+
.ticket-buttons button:hover {
|
|
43
|
+
opacity: 0.9;
|
|
44
|
+
}
|
|
45
|
+
.ticket-buttons button:nth-child(1) {
|
|
46
|
+
background-color: #ffc107;
|
|
47
|
+
color: #000;
|
|
48
|
+
}
|
|
49
|
+
.ticket-buttons button:nth-child(2) {
|
|
50
|
+
background-color: #28a745;
|
|
51
|
+
color: white;
|
|
52
|
+
}
|
|
53
|
+
.ticket-buttons .delete-btn {
|
|
54
|
+
background-color: #dc3545;
|
|
55
|
+
color: white;
|
|
56
|
+
}
|
|
57
|
+
.status-badge {
|
|
58
|
+
padding: 5px 10px;
|
|
59
|
+
border-radius: 20px;
|
|
60
|
+
font-size: 12px;
|
|
61
|
+
font-weight: bold;
|
|
62
|
+
display: inline-block;
|
|
63
|
+
margin-left: 5px;
|
|
64
|
+
}
|
|
65
|
+
.status-open {
|
|
66
|
+
background-color: #007bff;
|
|
67
|
+
color: white;
|
|
68
|
+
}
|
|
69
|
+
.status-in-progress {
|
|
70
|
+
background-color: #ffc107;
|
|
71
|
+
color: #000;
|
|
72
|
+
}
|
|
73
|
+
.status-resolved {
|
|
74
|
+
background-color: #28a745;
|
|
75
|
+
color: white;
|
|
76
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import './TicketList.css';
|
|
4
|
+
|
|
5
|
+
function TicketList() {
|
|
6
|
+
const [tickets, setTickets] = useState([]);
|
|
7
|
+
|
|
8
|
+
const fetchTickets = async () => {
|
|
9
|
+
try {
|
|
10
|
+
const res = await axios.get('http://localhost:5000/api/tickets');
|
|
11
|
+
setTickets(res.data);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('Error fetching tickets:', error);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const updateStatus = async (id, newStatus) => {
|
|
18
|
+
try {
|
|
19
|
+
await axios.patch(`http://localhost:5000/api/tickets/${id}`, { status: newStatus });
|
|
20
|
+
setTickets(prevTickets =>
|
|
21
|
+
prevTickets.map(ticket =>
|
|
22
|
+
ticket._id === id ? { ...ticket, status: newStatus } : ticket
|
|
23
|
+
)
|
|
24
|
+
);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
alert('Error updating ticket.');
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const deleteTicket = async (id) => {
|
|
31
|
+
try {
|
|
32
|
+
await axios.delete(`http://localhost:5000/api/tickets/${id}`);
|
|
33
|
+
setTickets(prevTickets => prevTickets.filter(ticket => ticket._id !== id));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
alert('Error deleting ticket.');
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
fetchTickets();
|
|
41
|
+
window.addEventListener('ticketCreated', fetchTickets);
|
|
42
|
+
return () => window.removeEventListener('ticketCreated', fetchTickets);
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="ticket-list-container">
|
|
47
|
+
<h2>Tickets</h2>
|
|
48
|
+
{tickets.length === 0 && <p>No tickets found.</p>}
|
|
49
|
+
<div className="ticket-list">
|
|
50
|
+
{tickets.map((ticket) => (
|
|
51
|
+
<div key={ticket._id} className="ticket-card">
|
|
52
|
+
<h3>{ticket.title}</h3>
|
|
53
|
+
<p>Priority: {ticket.priority}</p>
|
|
54
|
+
<p>Status: {ticket.status}</p>
|
|
55
|
+
<div className="ticket-buttons">
|
|
56
|
+
<button onClick={() => updateStatus(ticket._id, 'In Progress')}>
|
|
57
|
+
In Progress
|
|
58
|
+
</button>
|
|
59
|
+
<button onClick={() => updateStatus(ticket._id, 'Resolved')}>
|
|
60
|
+
Resolved
|
|
61
|
+
</button>
|
|
62
|
+
<button className="delete-btn" onClick={() => deleteTicket(ticket._id)}>
|
|
63
|
+
Delete
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
export default TicketList;
|