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.
- package/bin/index.js +30 -0
- package/package.json +29 -0
- package/templates/VehicleRentalReservationSystem/Documentation/API.md +57 -0
- package/templates/VehicleRentalReservationSystem/Documentation/DFD.md +62 -0
- package/templates/VehicleRentalReservationSystem/Documentation/ERD.md +109 -0
- package/templates/VehicleRentalReservationSystem/Documentation/Installation.md +40 -0
- package/templates/VehicleRentalReservationSystem/README.md +165 -0
- package/templates/VehicleRentalReservationSystem/backend-project/.env.example +8 -0
- package/templates/VehicleRentalReservationSystem/backend-project/config/db.js +10 -0
- package/templates/VehicleRentalReservationSystem/backend-project/controllers/auth.controller.js +39 -0
- package/templates/VehicleRentalReservationSystem/backend-project/controllers/customer.controller.js +19 -0
- package/templates/VehicleRentalReservationSystem/backend-project/controllers/dashboard.controller.js +48 -0
- package/templates/VehicleRentalReservationSystem/backend-project/controllers/report.controller.js +45 -0
- package/templates/VehicleRentalReservationSystem/backend-project/controllers/reservation.controller.js +94 -0
- package/templates/VehicleRentalReservationSystem/backend-project/controllers/user.controller.js +16 -0
- package/templates/VehicleRentalReservationSystem/backend-project/controllers/vehicle.controller.js +20 -0
- package/templates/VehicleRentalReservationSystem/backend-project/middleware/auth.js +22 -0
- package/templates/VehicleRentalReservationSystem/backend-project/middleware/errorHandler.js +10 -0
- package/templates/VehicleRentalReservationSystem/backend-project/middleware/validate.js +6 -0
- package/templates/VehicleRentalReservationSystem/backend-project/models/Customer.js +11 -0
- package/templates/VehicleRentalReservationSystem/backend-project/models/ReservationRental.js +17 -0
- package/templates/VehicleRentalReservationSystem/backend-project/models/User.js +20 -0
- package/templates/VehicleRentalReservationSystem/backend-project/models/Vehicle.js +14 -0
- package/templates/VehicleRentalReservationSystem/backend-project/package-lock.json +1695 -0
- package/templates/VehicleRentalReservationSystem/backend-project/package.json +26 -0
- package/templates/VehicleRentalReservationSystem/backend-project/routes/auth.routes.js +21 -0
- package/templates/VehicleRentalReservationSystem/backend-project/routes/customer.routes.js +20 -0
- package/templates/VehicleRentalReservationSystem/backend-project/routes/dashboard.routes.js +6 -0
- package/templates/VehicleRentalReservationSystem/backend-project/routes/report.routes.js +6 -0
- package/templates/VehicleRentalReservationSystem/backend-project/routes/reservation.routes.js +14 -0
- package/templates/VehicleRentalReservationSystem/backend-project/routes/user.routes.js +10 -0
- package/templates/VehicleRentalReservationSystem/backend-project/routes/vehicle.routes.js +21 -0
- package/templates/VehicleRentalReservationSystem/backend-project/seed/seed.js +80 -0
- package/templates/VehicleRentalReservationSystem/backend-project/server.js +40 -0
- package/templates/VehicleRentalReservationSystem/backend-project/utils/token.js +3 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/index.html +14 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/package-lock.json +3759 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/package.json +32 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/postcss.config.js +1 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/App.jsx +33 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/components/ConfirmDelete.jsx +12 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/components/Modal.jsx +22 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/components/ProtectedRoute.jsx +9 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/components/Sidebar.jsx +42 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/components/Topbar.jsx +24 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/context/AuthContext.jsx +30 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/context/ThemeContext.jsx +13 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/index.css +37 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/layouts/AppLayout.jsx +18 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/main.jsx +20 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Customers.jsx +95 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Dashboard.jsx +84 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Login.jsx +54 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Register.jsx +36 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Rentals.jsx +58 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Reports.jsx +108 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Reservations.jsx +125 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Settings.jsx +32 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Users.jsx +78 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/pages/Vehicles.jsx +110 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/services/api.js +16 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/src/utils/format.js +20 -0
- package/templates/VehicleRentalReservationSystem/frontend-project/tailwind.config.js +18 -0
- 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
|
+
};
|
package/templates/VehicleRentalReservationSystem/backend-project/controllers/user.controller.js
ADDED
|
@@ -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);} };
|
package/templates/VehicleRentalReservationSystem/backend-project/controllers/vehicle.controller.js
ADDED
|
@@ -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,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);
|