create-vrrsystem-app 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.
Files changed (64) hide show
  1. package/bin/index.js +30 -0
  2. package/package.json +29 -0
  3. package/templates/VehicleRentalReservationSystem/Documentation/API.md +57 -0
  4. package/templates/VehicleRentalReservationSystem/Documentation/DFD.md +62 -0
  5. package/templates/VehicleRentalReservationSystem/Documentation/ERD.md +109 -0
  6. package/templates/VehicleRentalReservationSystem/Documentation/Installation.md +40 -0
  7. package/templates/VehicleRentalReservationSystem/README.md +165 -0
  8. package/templates/VehicleRentalReservationSystem/backend-project/.env.example +8 -0
  9. package/templates/VehicleRentalReservationSystem/backend-project/config/db.js +10 -0
  10. package/templates/VehicleRentalReservationSystem/backend-project/controllers/auth.controller.js +39 -0
  11. package/templates/VehicleRentalReservationSystem/backend-project/controllers/customer.controller.js +19 -0
  12. package/templates/VehicleRentalReservationSystem/backend-project/controllers/dashboard.controller.js +48 -0
  13. package/templates/VehicleRentalReservationSystem/backend-project/controllers/report.controller.js +45 -0
  14. package/templates/VehicleRentalReservationSystem/backend-project/controllers/reservation.controller.js +94 -0
  15. package/templates/VehicleRentalReservationSystem/backend-project/controllers/user.controller.js +16 -0
  16. package/templates/VehicleRentalReservationSystem/backend-project/controllers/vehicle.controller.js +20 -0
  17. package/templates/VehicleRentalReservationSystem/backend-project/middleware/auth.js +22 -0
  18. package/templates/VehicleRentalReservationSystem/backend-project/middleware/errorHandler.js +10 -0
  19. package/templates/VehicleRentalReservationSystem/backend-project/middleware/validate.js +6 -0
  20. package/templates/VehicleRentalReservationSystem/backend-project/models/Customer.js +11 -0
  21. package/templates/VehicleRentalReservationSystem/backend-project/models/ReservationRental.js +17 -0
  22. package/templates/VehicleRentalReservationSystem/backend-project/models/User.js +20 -0
  23. package/templates/VehicleRentalReservationSystem/backend-project/models/Vehicle.js +14 -0
  24. package/templates/VehicleRentalReservationSystem/backend-project/package-lock.json +1695 -0
  25. package/templates/VehicleRentalReservationSystem/backend-project/package.json +26 -0
  26. package/templates/VehicleRentalReservationSystem/backend-project/routes/auth.routes.js +21 -0
  27. package/templates/VehicleRentalReservationSystem/backend-project/routes/customer.routes.js +20 -0
  28. package/templates/VehicleRentalReservationSystem/backend-project/routes/dashboard.routes.js +6 -0
  29. package/templates/VehicleRentalReservationSystem/backend-project/routes/report.routes.js +6 -0
  30. package/templates/VehicleRentalReservationSystem/backend-project/routes/reservation.routes.js +14 -0
  31. package/templates/VehicleRentalReservationSystem/backend-project/routes/user.routes.js +10 -0
  32. package/templates/VehicleRentalReservationSystem/backend-project/routes/vehicle.routes.js +21 -0
  33. package/templates/VehicleRentalReservationSystem/backend-project/seed/seed.js +80 -0
  34. package/templates/VehicleRentalReservationSystem/backend-project/server.js +40 -0
  35. package/templates/VehicleRentalReservationSystem/backend-project/utils/token.js +3 -0
  36. package/templates/VehicleRentalReservationSystem/frontend-project/index.html +14 -0
  37. package/templates/VehicleRentalReservationSystem/frontend-project/package-lock.json +3759 -0
  38. package/templates/VehicleRentalReservationSystem/frontend-project/package.json +32 -0
  39. package/templates/VehicleRentalReservationSystem/frontend-project/postcss.config.js +1 -0
  40. package/templates/VehicleRentalReservationSystem/frontend-project/src/App.jsx +33 -0
  41. package/templates/VehicleRentalReservationSystem/frontend-project/src/components/ConfirmDelete.jsx +12 -0
  42. package/templates/VehicleRentalReservationSystem/frontend-project/src/components/Modal.jsx +22 -0
  43. package/templates/VehicleRentalReservationSystem/frontend-project/src/components/ProtectedRoute.jsx +9 -0
  44. package/templates/VehicleRentalReservationSystem/frontend-project/src/components/Sidebar.jsx +42 -0
  45. package/templates/VehicleRentalReservationSystem/frontend-project/src/components/Topbar.jsx +24 -0
  46. package/templates/VehicleRentalReservationSystem/frontend-project/src/context/AuthContext.jsx +30 -0
  47. package/templates/VehicleRentalReservationSystem/frontend-project/src/context/ThemeContext.jsx +13 -0
  48. package/templates/VehicleRentalReservationSystem/frontend-project/src/index.css +37 -0
  49. package/templates/VehicleRentalReservationSystem/frontend-project/src/layouts/AppLayout.jsx +18 -0
  50. package/templates/VehicleRentalReservationSystem/frontend-project/src/main.jsx +20 -0
  51. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Customers.jsx +95 -0
  52. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Dashboard.jsx +84 -0
  53. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Login.jsx +54 -0
  54. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Register.jsx +36 -0
  55. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Rentals.jsx +58 -0
  56. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Reports.jsx +108 -0
  57. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Reservations.jsx +125 -0
  58. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Settings.jsx +32 -0
  59. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Users.jsx +78 -0
  60. package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Vehicles.jsx +110 -0
  61. package/templates/VehicleRentalReservationSystem/frontend-project/src/services/api.js +16 -0
  62. package/templates/VehicleRentalReservationSystem/frontend-project/src/utils/format.js +20 -0
  63. package/templates/VehicleRentalReservationSystem/frontend-project/tailwind.config.js +18 -0
  64. package/templates/VehicleRentalReservationSystem/frontend-project/vite.config.js +9 -0
@@ -0,0 +1,94 @@
1
+ const RR = require('../models/ReservationRental');
2
+ const Vehicle = require('../models/Vehicle');
3
+
4
+ const pop = (q) => q.populate('customerId').populate('vehicleId').populate('userId', 'username fullName role');
5
+
6
+ exports.list = async (req, res, next) => {
7
+ try {
8
+ const { search = '', status, page = 1, limit = 10 } = req.query;
9
+ const q = {};
10
+ if (status) q.reservationStatus = status;
11
+ const total = await RR.countDocuments(q);
12
+ let items = await pop(RR.find(q).sort('-createdAt').skip((page-1)*limit).limit(+limit));
13
+ if (search) {
14
+ const re = new RegExp(search, 'i');
15
+ items = items.filter(r => re.test(r.customerId?.fullName || '') || re.test(r.vehicleId?.plateNumber || ''));
16
+ }
17
+ res.json({ items, total, page: +page, pages: Math.ceil(total/limit) });
18
+ } catch (e) { next(e); }
19
+ };
20
+
21
+ exports.get = async (req, res, next) => { try { const r = await pop(RR.findById(req.params.id)); if(!r) return res.status(404).json({message:'Not found'}); res.json(r);} catch(e){next(e);} };
22
+
23
+ exports.create = async (req, res, next) => {
24
+ try {
25
+ const body = { ...req.body, userId: req.user._id };
26
+ const start = new Date(body.startDate), end = new Date(body.endDate);
27
+ const days = Math.max(1, Math.ceil((end - start) / 86400000));
28
+ const v = await Vehicle.findById(body.vehicleId);
29
+ if (!v) return res.status(404).json({ message: 'Vehicle not found' });
30
+ if (!body.rentalFee) body.rentalFee = days * (v.dailyRate || 50);
31
+ const r = await RR.create(body);
32
+ await Vehicle.findByIdAndUpdate(v._id, { status: 'RESERVED' });
33
+ res.status(201).json(await pop(RR.findById(r._id)));
34
+ } catch (e) { next(e); }
35
+ };
36
+
37
+ exports.update = async (req, res, next) => {
38
+ try {
39
+ const r = await RR.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true });
40
+ if (!r) return res.status(404).json({ message: 'Not found' });
41
+ res.json(await pop(RR.findById(r._id)));
42
+ } catch (e) { next(e); }
43
+ };
44
+
45
+ exports.remove = async (req, res, next) => {
46
+ try {
47
+ const r = await RR.findById(req.params.id);
48
+ if (r) await Vehicle.findByIdAndUpdate(r.vehicleId, { status: 'AVAILABLE' });
49
+ await RR.findByIdAndDelete(req.params.id);
50
+ res.json({ message: 'Deleted' });
51
+ } catch (e) { next(e); }
52
+ };
53
+
54
+ exports.approve = async (req, res, next) => {
55
+ try {
56
+ const r = await RR.findByIdAndUpdate(req.params.id, { reservationStatus: 'APPROVED' }, { new: true });
57
+ res.json(await pop(RR.findById(r._id)));
58
+ } catch (e) { next(e); }
59
+ };
60
+ exports.reject = async (req, res, next) => {
61
+ try {
62
+ const r = await RR.findByIdAndUpdate(req.params.id, { reservationStatus: 'REJECTED' }, { new: true });
63
+ if (r) await Vehicle.findByIdAndUpdate(r.vehicleId, { status: 'AVAILABLE' });
64
+ res.json(await pop(RR.findById(r._id)));
65
+ } catch (e) { next(e); }
66
+ };
67
+ exports.pickup = async (req, res, next) => {
68
+ try {
69
+ const r = await RR.findByIdAndUpdate(req.params.id, { rentalDate: new Date(), rentalStatus: 'ONGOING' }, { new: true });
70
+ if (r) await Vehicle.findByIdAndUpdate(r.vehicleId, { status: 'RENTED' });
71
+ res.json(await pop(RR.findById(r._id)));
72
+ } catch (e) { next(e); }
73
+ };
74
+ exports.returnVehicle = async (req, res, next) => {
75
+ try {
76
+ const r = await RR.findById(req.params.id);
77
+ if (!r) return res.status(404).json({ message: 'Not found' });
78
+ const now = new Date();
79
+ const late = r.endDate && now > r.endDate;
80
+ let extra = 0;
81
+ if (late) {
82
+ const v = await Vehicle.findById(r.vehicleId);
83
+ const lateDays = Math.ceil((now - r.endDate) / 86400000);
84
+ extra = lateDays * (v?.dailyRate || 50) * 1.5;
85
+ }
86
+ r.returnDate = now;
87
+ r.rentalStatus = late ? 'LATE' : 'RETURNED';
88
+ r.reservationStatus = 'COMPLETED';
89
+ r.rentalFee = (r.rentalFee || 0) + extra;
90
+ await r.save();
91
+ await Vehicle.findByIdAndUpdate(r.vehicleId, { status: 'AVAILABLE' });
92
+ res.json(await pop(RR.findById(r._id)));
93
+ } catch (e) { next(e); }
94
+ };
@@ -0,0 +1,16 @@
1
+ const User = require('../models/User');
2
+ exports.list = async (req, res, next) => { try { res.json(await User.find().sort('-createdAt')); } catch(e){next(e);} };
3
+ exports.get = async (req, res, next) => { try { const u = await User.findById(req.params.id); if(!u) return res.status(404).json({message:'Not found'}); res.json(u);} catch(e){next(e);} };
4
+ exports.create = async (req, res, next) => { try { res.status(201).json(await User.create(req.body)); } catch(e){next(e);} };
5
+ exports.update = async (req, res, next) => {
6
+ try {
7
+ const data = { ...req.body };
8
+ if (!data.password) delete data.password;
9
+ const u = await User.findById(req.params.id);
10
+ if (!u) return res.status(404).json({ message: 'Not found' });
11
+ Object.assign(u, data);
12
+ await u.save();
13
+ res.json(u);
14
+ } catch (e) { next(e); }
15
+ };
16
+ exports.remove = async (req, res, next) => { try { await User.findByIdAndDelete(req.params.id); res.json({ message: 'Deleted' }); } catch(e){next(e);} };
@@ -0,0 +1,20 @@
1
+ const Vehicle = require('../models/Vehicle');
2
+ exports.list = async (req, res, next) => {
3
+ try {
4
+ const { search = '', status, page = 1, limit = 10 } = req.query;
5
+ const q = {};
6
+ if (search) q.$or = [
7
+ { plateNumber: new RegExp(search, 'i') },
8
+ { brand: new RegExp(search, 'i') },
9
+ { model: new RegExp(search, 'i') }
10
+ ];
11
+ if (status) q.status = status;
12
+ const total = await Vehicle.countDocuments(q);
13
+ const items = await Vehicle.find(q).sort('-createdAt').skip((page-1)*limit).limit(+limit);
14
+ res.json({ items, total, page: +page, pages: Math.ceil(total/limit) });
15
+ } catch (e) { next(e); }
16
+ };
17
+ exports.get = async (req, res, next) => { try { const c = await Vehicle.findById(req.params.id); if(!c) return res.status(404).json({message:'Not found'}); res.json(c);} catch(e){next(e);} };
18
+ exports.create = async (req, res, next) => { try { res.status(201).json(await Vehicle.create(req.body)); } catch(e){next(e);} };
19
+ exports.update = async (req, res, next) => { try { const c = await Vehicle.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true }); if(!c) return res.status(404).json({message:'Not found'}); res.json(c);} catch(e){next(e);} };
20
+ exports.remove = async (req, res, next) => { try { await Vehicle.findByIdAndDelete(req.params.id); res.json({ message: 'Deleted' }); } catch(e){next(e);} };
@@ -0,0 +1,22 @@
1
+ const jwt = require('jsonwebtoken');
2
+ const User = require('../models/User');
3
+
4
+ exports.protect = async (req, res, next) => {
5
+ try {
6
+ const header = req.headers.authorization;
7
+ if (!header || !header.startsWith('Bearer ')) return res.status(401).json({ message: 'No token' });
8
+ const token = header.split(' ')[1];
9
+ const decoded = jwt.verify(token, process.env.JWT_SECRET);
10
+ const user = await User.findById(decoded.id);
11
+ if (!user || !user.active) return res.status(401).json({ message: 'Invalid token' });
12
+ req.user = user;
13
+ next();
14
+ } catch (e) {
15
+ return res.status(401).json({ message: 'Unauthorized', error: e.message });
16
+ }
17
+ };
18
+
19
+ exports.authorize = (...roles) => (req, res, next) => {
20
+ if (!roles.includes(req.user.role)) return res.status(403).json({ message: 'Forbidden: insufficient role' });
21
+ next();
22
+ };
@@ -0,0 +1,10 @@
1
+ module.exports = (err, req, res, next) => {
2
+ console.error(err);
3
+ if (err.code === 11000) {
4
+ return res.status(409).json({ message: 'Duplicate value', field: Object.keys(err.keyValue)[0] });
5
+ }
6
+ if (err.name === 'ValidationError') {
7
+ return res.status(400).json({ message: err.message });
8
+ }
9
+ res.status(err.status || 500).json({ message: err.message || 'Server error' });
10
+ };
@@ -0,0 +1,6 @@
1
+ const { validationResult } = require('express-validator');
2
+ module.exports = (req, res, next) => {
3
+ const errors = validationResult(req);
4
+ if (!errors.isEmpty()) return res.status(400).json({ message: 'Validation failed', errors: errors.array() });
5
+ next();
6
+ };
@@ -0,0 +1,11 @@
1
+ const mongoose = require('mongoose');
2
+ const customerSchema = new mongoose.Schema({
3
+ fullName: { type: String, required: true, trim: true },
4
+ nationalId: { type: String, required: true, unique: true, trim: true },
5
+ phone: { type: String, required: true, trim: true },
6
+ email: { type: String, required: true, lowercase: true, trim: true },
7
+ address: { type: String, required: true, trim: true }
8
+ }, { timestamps: true });
9
+ customerSchema.virtual('customerId').get(function() { return this._id.toHexString(); });
10
+ customerSchema.set('toJSON', { virtuals: true });
11
+ module.exports = mongoose.model('Customer', customerSchema);
@@ -0,0 +1,17 @@
1
+ const mongoose = require('mongoose');
2
+ const schema = new mongoose.Schema({
3
+ customerId: { type: mongoose.Schema.Types.ObjectId, ref: 'Customer', required: true },
4
+ vehicleId: { type: mongoose.Schema.Types.ObjectId, ref: 'Vehicle', required: true },
5
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
6
+ reservationDate: { type: Date, default: Date.now },
7
+ startDate: { type: Date, required: true },
8
+ endDate: { type: Date, required: true },
9
+ reservationStatus: { type: String, enum: ['PENDING','APPROVED','REJECTED','CANCELLED','COMPLETED'], default: 'PENDING' },
10
+ rentalDate: { type: Date, default: null },
11
+ returnDate: { type: Date, default: null },
12
+ rentalFee: { type: Number, default: 0 },
13
+ rentalStatus: { type: String, enum: ['NOT_STARTED','ONGOING','RETURNED','LATE'], default: 'NOT_STARTED' }
14
+ }, { timestamps: true });
15
+ schema.virtual('reservationRentalId').get(function() { return this._id.toHexString(); });
16
+ schema.set('toJSON', { virtuals: true });
17
+ module.exports = mongoose.model('ReservationRental', schema);
@@ -0,0 +1,20 @@
1
+ const mongoose = require('mongoose');
2
+ const bcrypt = require('bcryptjs');
3
+ const userSchema = new mongoose.Schema({
4
+ username: { type: String, required: true, unique: true, trim: true },
5
+ password: { type: String, required: true, minlength: 6 },
6
+ fullName: { type: String, default: '' },
7
+ email: { type: String, default: '' },
8
+ role: { type: String, enum: ['ADMIN', 'STAFF', 'MANAGER'], required: true },
9
+ active: { type: Boolean, default: true }
10
+ }, { timestamps: true });
11
+
12
+ userSchema.pre('save', async function(next) {
13
+ if (!this.isModified('password')) return next();
14
+ this.password = await bcrypt.hash(this.password, 10);
15
+ next();
16
+ });
17
+ userSchema.methods.compare = function(p) { return bcrypt.compare(p, this.password); };
18
+ userSchema.virtual('userId').get(function() { return this._id.toHexString(); });
19
+ userSchema.set('toJSON', { virtuals: true, transform: (_, ret) => { delete ret.password; return ret; } });
20
+ module.exports = mongoose.model('User', userSchema);
@@ -0,0 +1,14 @@
1
+ const mongoose = require('mongoose');
2
+ const vehicleSchema = new mongoose.Schema({
3
+ plateNumber: { type: String, required: true, unique: true, uppercase: true, trim: true },
4
+ brand: { type: String, required: true, trim: true },
5
+ model: { type: String, required: true, trim: true },
6
+ year: { type: Number, required: true, min: 1980, max: new Date().getFullYear() + 1 },
7
+ vehicleType: { type: String, required: true, enum: ['Sedan','SUV','Hatchback','Pickup','Van','Bus','Truck','Coupe'] },
8
+ purchasePrice: { type: Number, required: true, min: 0 },
9
+ dailyRate: { type: Number, required: true, min: 0, default: 50 },
10
+ status: { type: String, enum: ['AVAILABLE','RESERVED','RENTED','MAINTENANCE'], default: 'AVAILABLE' }
11
+ }, { timestamps: true });
12
+ vehicleSchema.virtual('vehicleId').get(function() { return this._id.toHexString(); });
13
+ vehicleSchema.set('toJSON', { virtuals: true });
14
+ module.exports = mongoose.model('Vehicle', vehicleSchema);