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
package/bin/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const projectName = process.argv[2];
|
|
7
|
+
|
|
8
|
+
if (!projectName) {
|
|
9
|
+
console.log("Please provide project name");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const templateDir = path.join(
|
|
14
|
+
__dirname,
|
|
15
|
+
"../templates/VehicleRentalReservationSystem"
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const targetDir = path.join(process.cwd(), projectName);
|
|
19
|
+
|
|
20
|
+
fs.copySync(templateDir, targetDir);
|
|
21
|
+
|
|
22
|
+
console.log("");
|
|
23
|
+
console.log(" Project created successfully!");
|
|
24
|
+
console.log(`📁 ${projectName}`);
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log("Next steps:");
|
|
27
|
+
console.log(`cd ${projectName}`);
|
|
28
|
+
console.log("cd backend-project && npm install");
|
|
29
|
+
console.log("cd frontend-project && npm install");
|
|
30
|
+
console.log("");
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-vrrsystem-app",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-vrrs-app": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"react",
|
|
18
|
+
"nodejs",
|
|
19
|
+
"mongodb",
|
|
20
|
+
"vrr",
|
|
21
|
+
"vehicle-rental"
|
|
22
|
+
],
|
|
23
|
+
"author": "Teta",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"type": "commonjs",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"fs-extra": "^11.3.5"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# API Documentation
|
|
2
|
+
|
|
3
|
+
Base URL: `http://localhost:5000/api`
|
|
4
|
+
|
|
5
|
+
All endpoints (except `/auth/login`, `/auth/register`, `/auth/refresh`) require:
|
|
6
|
+
```
|
|
7
|
+
Authorization: Bearer <accessToken>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Auth
|
|
11
|
+
### POST /auth/login
|
|
12
|
+
Body:
|
|
13
|
+
```json
|
|
14
|
+
{ "username": "admin", "password": "admin123" }
|
|
15
|
+
```
|
|
16
|
+
Response:
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"user": { "_id": "...", "username": "admin", "role": "ADMIN" },
|
|
20
|
+
"accessToken": "...",
|
|
21
|
+
"refreshToken": "..."
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### POST /auth/register
|
|
26
|
+
Body: `{ username, password, fullName?, email?, role? }`
|
|
27
|
+
|
|
28
|
+
### POST /auth/refresh
|
|
29
|
+
Body: `{ refreshToken }`
|
|
30
|
+
|
|
31
|
+
## Customers
|
|
32
|
+
- `GET /customers?search=&page=1&limit=10`
|
|
33
|
+
- `POST /customers` — `{ fullName, nationalId, phone, email, address }`
|
|
34
|
+
- `PUT /customers/:id`
|
|
35
|
+
- `DELETE /customers/:id` (ADMIN)
|
|
36
|
+
|
|
37
|
+
## Vehicles
|
|
38
|
+
- `GET /vehicles?search=&status=AVAILABLE&page=1&limit=10`
|
|
39
|
+
- `POST /vehicles` (ADMIN) — `{ plateNumber, brand, model, year, vehicleType, purchasePrice, dailyRate }`
|
|
40
|
+
|
|
41
|
+
## Reservations
|
|
42
|
+
- `GET /reservations?status=PENDING`
|
|
43
|
+
- `POST /reservations` — `{ customerId, vehicleId, startDate, endDate }`
|
|
44
|
+
- `PATCH /reservations/:id/approve` (ADMIN/MANAGER)
|
|
45
|
+
- `PATCH /reservations/:id/reject` (ADMIN/MANAGER)
|
|
46
|
+
- `PATCH /reservations/:id/pickup` (ADMIN/STAFF)
|
|
47
|
+
- `PATCH /reservations/:id/return` (ADMIN/STAFF)
|
|
48
|
+
|
|
49
|
+
## Reports
|
|
50
|
+
- `GET /reports?period=monthly` — period ∈ `daily|weekly|monthly|yearly|all`
|
|
51
|
+
- Returns: `{ period, from, to, count, totalRevenue, rows[] }`
|
|
52
|
+
|
|
53
|
+
## Dashboard
|
|
54
|
+
- `GET /dashboard` — full stats payload
|
|
55
|
+
|
|
56
|
+
## Users (ADMIN)
|
|
57
|
+
- `GET/POST/PUT/DELETE /users[/:id]`
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Data Flow Diagram (DFD)
|
|
2
|
+
|
|
3
|
+
## External Entities
|
|
4
|
+
- **Customer** — provides personal info, requests reservations
|
|
5
|
+
- **Staff** — creates reservations, performs pickup/return
|
|
6
|
+
- **Manager** — approves/rejects, reviews reports
|
|
7
|
+
- **Admin** — manages users and master data
|
|
8
|
+
|
|
9
|
+
## Data Stores
|
|
10
|
+
- D1: Customers
|
|
11
|
+
- D2: Vehicles
|
|
12
|
+
- D3: Reservations / Rentals
|
|
13
|
+
- D4: Users
|
|
14
|
+
|
|
15
|
+
## Context Diagram (Level 0 — single process)
|
|
16
|
+
|
|
17
|
+
```mermaid
|
|
18
|
+
flowchart LR
|
|
19
|
+
C[Customer] -->|Reservation Request| VRS((Vehicle Rental & Reservation System))
|
|
20
|
+
S[Staff] -->|Customer & Reservation Data| VRS
|
|
21
|
+
M[Manager] -->|Approvals / Report Requests| VRS
|
|
22
|
+
A[Admin] -->|User & Master Data Mgmt| VRS
|
|
23
|
+
VRS -->|Receipts / Confirmations| C
|
|
24
|
+
VRS -->|Operational Lists| S
|
|
25
|
+
VRS -->|Reports / Analytics| M
|
|
26
|
+
VRS -->|System Configurations| A
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Level 0 DFD (decomposition)
|
|
30
|
+
|
|
31
|
+
```mermaid
|
|
32
|
+
flowchart TB
|
|
33
|
+
C[Customer] --> P1[1.0 Manage Customers]
|
|
34
|
+
S[Staff] --> P1
|
|
35
|
+
P1 <--> D1[(D1 Customers)]
|
|
36
|
+
|
|
37
|
+
A[Admin] --> P2[2.0 Manage Vehicles]
|
|
38
|
+
P2 <--> D2[(D2 Vehicles)]
|
|
39
|
+
|
|
40
|
+
S --> P3[3.0 Manage Reservations]
|
|
41
|
+
P3 <--> D1
|
|
42
|
+
P3 <--> D2
|
|
43
|
+
P3 <--> D3[(D3 Reservations/Rentals)]
|
|
44
|
+
|
|
45
|
+
S --> P4[4.0 Manage Rentals]
|
|
46
|
+
P4 <--> D2
|
|
47
|
+
P4 <--> D3
|
|
48
|
+
|
|
49
|
+
M[Manager] --> P5[5.0 Approvals]
|
|
50
|
+
P5 <--> D3
|
|
51
|
+
|
|
52
|
+
M --> P6[6.0 Reports & Analytics]
|
|
53
|
+
P6 <--> D1
|
|
54
|
+
P6 <--> D2
|
|
55
|
+
P6 <--> D3
|
|
56
|
+
|
|
57
|
+
A --> P7[7.0 Manage Users]
|
|
58
|
+
P7 <--> D4[(D4 Users)]
|
|
59
|
+
|
|
60
|
+
P6 -->|PDF / Excel / CSV| M
|
|
61
|
+
P3 -->|Confirmation| C
|
|
62
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Entity Relationship Diagram (ERD)
|
|
2
|
+
|
|
3
|
+
## Entities
|
|
4
|
+
|
|
5
|
+
### CUSTOMER
|
|
6
|
+
| Field | Type | Key |
|
|
7
|
+
|--------------|---------|-----|
|
|
8
|
+
| customerId | ObjectId| PK |
|
|
9
|
+
| fullName | String | |
|
|
10
|
+
| nationalId | String | UQ |
|
|
11
|
+
| phone | String | |
|
|
12
|
+
| email | String | |
|
|
13
|
+
| address | String | |
|
|
14
|
+
|
|
15
|
+
### VEHICLE
|
|
16
|
+
| Field | Type | Key |
|
|
17
|
+
|---------------|----------|-----|
|
|
18
|
+
| vehicleId | ObjectId | PK |
|
|
19
|
+
| plateNumber | String | UQ |
|
|
20
|
+
| brand | String | |
|
|
21
|
+
| model | String | |
|
|
22
|
+
| year | Number | |
|
|
23
|
+
| vehicleType | String | |
|
|
24
|
+
| purchasePrice | Number | |
|
|
25
|
+
| dailyRate | Number | |
|
|
26
|
+
| status | Enum | |
|
|
27
|
+
|
|
28
|
+
### RESERVATION_RENTAL
|
|
29
|
+
| Field | Type | Key |
|
|
30
|
+
|---------------------|----------|-----------|
|
|
31
|
+
| reservationRentalId | ObjectId | PK |
|
|
32
|
+
| customerId | ObjectId | FK→CUSTOMER|
|
|
33
|
+
| vehicleId | ObjectId | FK→VEHICLE |
|
|
34
|
+
| userId | ObjectId | FK→USERS |
|
|
35
|
+
| reservationDate | Date | |
|
|
36
|
+
| startDate | Date | |
|
|
37
|
+
| endDate | Date | |
|
|
38
|
+
| reservationStatus | Enum | |
|
|
39
|
+
| rentalDate | Date | |
|
|
40
|
+
| returnDate | Date | |
|
|
41
|
+
| rentalFee | Number | |
|
|
42
|
+
| rentalStatus | Enum | |
|
|
43
|
+
|
|
44
|
+
### USERS
|
|
45
|
+
| Field | Type | Key |
|
|
46
|
+
|----------|----------|-----|
|
|
47
|
+
| userId | ObjectId | PK |
|
|
48
|
+
| username | String | UQ |
|
|
49
|
+
| password | String | |
|
|
50
|
+
| role | Enum | |
|
|
51
|
+
|
|
52
|
+
## Cardinalities
|
|
53
|
+
|
|
54
|
+
- CUSTOMER (1) ──< (N) RESERVATION_RENTAL
|
|
55
|
+
- VEHICLE (1) ──< (N) RESERVATION_RENTAL
|
|
56
|
+
- USERS (1) ──< (N) RESERVATION_RENTAL
|
|
57
|
+
|
|
58
|
+
## Mermaid ERD
|
|
59
|
+
|
|
60
|
+
```mermaid
|
|
61
|
+
erDiagram
|
|
62
|
+
CUSTOMER ||--o{ RESERVATION_RENTAL : "places"
|
|
63
|
+
VEHICLE ||--o{ RESERVATION_RENTAL : "is reserved in"
|
|
64
|
+
USERS ||--o{ RESERVATION_RENTAL : "creates"
|
|
65
|
+
|
|
66
|
+
CUSTOMER {
|
|
67
|
+
ObjectId customerId PK
|
|
68
|
+
string fullName
|
|
69
|
+
string nationalId UK
|
|
70
|
+
string phone
|
|
71
|
+
string email
|
|
72
|
+
string address
|
|
73
|
+
}
|
|
74
|
+
VEHICLE {
|
|
75
|
+
ObjectId vehicleId PK
|
|
76
|
+
string plateNumber UK
|
|
77
|
+
string brand
|
|
78
|
+
string model
|
|
79
|
+
int year
|
|
80
|
+
string vehicleType
|
|
81
|
+
float purchasePrice
|
|
82
|
+
float dailyRate
|
|
83
|
+
string status
|
|
84
|
+
}
|
|
85
|
+
RESERVATION_RENTAL {
|
|
86
|
+
ObjectId reservationRentalId PK
|
|
87
|
+
ObjectId customerId FK
|
|
88
|
+
ObjectId vehicleId FK
|
|
89
|
+
ObjectId userId FK
|
|
90
|
+
date reservationDate
|
|
91
|
+
date startDate
|
|
92
|
+
date endDate
|
|
93
|
+
string reservationStatus
|
|
94
|
+
date rentalDate
|
|
95
|
+
date returnDate
|
|
96
|
+
float rentalFee
|
|
97
|
+
string rentalStatus
|
|
98
|
+
}
|
|
99
|
+
USERS {
|
|
100
|
+
ObjectId userId PK
|
|
101
|
+
string username UK
|
|
102
|
+
string password
|
|
103
|
+
string role
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Draw.io compatible (text)
|
|
108
|
+
|
|
109
|
+
Use Draw.io → Extras → Edit Diagram with the Mermaid snippet above (Draw.io supports Mermaid import directly).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Installation Guide
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
- Node.js ≥ 18
|
|
5
|
+
- npm ≥ 9
|
|
6
|
+
- MongoDB ≥ 6 (running on `mongodb://localhost:27017`)
|
|
7
|
+
|
|
8
|
+
## Backend
|
|
9
|
+
```bash
|
|
10
|
+
cd backend-project
|
|
11
|
+
npm install
|
|
12
|
+
# .env is already created from .env.example
|
|
13
|
+
npm run seed # seed DB (users + customers + vehicles + reservations)
|
|
14
|
+
npm run dev # http://localhost:5000
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Frontend
|
|
18
|
+
```bash
|
|
19
|
+
cd frontend-project
|
|
20
|
+
npm install
|
|
21
|
+
npm run dev # http://localhost:5173
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Login
|
|
25
|
+
- admin / admin123
|
|
26
|
+
- manager / manager123
|
|
27
|
+
- staff / staff123
|
|
28
|
+
|
|
29
|
+
## MongoDB Setup
|
|
30
|
+
- Install MongoDB Community Server (https://www.mongodb.com/try/download/community)
|
|
31
|
+
- Start the service:
|
|
32
|
+
- macOS: `brew services start mongodb-community`
|
|
33
|
+
- Linux: `sudo systemctl start mongod`
|
|
34
|
+
- Windows: MongoDB runs as a service after install
|
|
35
|
+
- The database `VRS` is created automatically on first connection.
|
|
36
|
+
|
|
37
|
+
## Deployment
|
|
38
|
+
- **Backend** → deploy to Render / Railway / Heroku / VPS. Set env vars (`MONGO_URI`, `JWT_SECRET`, `JWT_REFRESH_SECRET`, `CLIENT_URL`).
|
|
39
|
+
- **Frontend** → `npm run build` then deploy `dist/` to Netlify / Vercel / static host. Set `VITE_API_URL` and adjust `vite.config.js` proxy or call full URL.
|
|
40
|
+
- **Database** → MongoDB Atlas free tier recommended for production.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# 🚗 Vehicle Rental & Reservation System (VRS)
|
|
2
|
+
|
|
3
|
+
**SwiftWheels Enterprises — Huye City, Southern Province, Rwanda**
|
|
4
|
+
|
|
5
|
+
A complete production-ready MERN stack web application that digitizes SwiftWheels' previously paper-based operations: vehicle reservations, rentals, customers, vehicles, staff activities, and management reporting.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🧱 Tech Stack
|
|
10
|
+
|
|
11
|
+
**Frontend:** React 18, Vite, Tailwind CSS, Axios, React Router DOM, Framer Motion, React Hook Form, Recharts, React Icons, React Toastify, jsPDF, xlsx
|
|
12
|
+
**Backend:** Node.js, Express, MongoDB, Mongoose, JWT, bcryptjs, Helmet, CORS, Express Validator, express-rate-limit
|
|
13
|
+
**Database:** MongoDB (database name: `VRS`)
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 📁 Project Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
VehicleRentalReservationSystem/
|
|
21
|
+
├── backend-project/ Express + MongoDB API (MVC)
|
|
22
|
+
├── frontend-project/ React + Vite + Tailwind UI
|
|
23
|
+
├── Documentation/ ERD, DFD, API docs, deployment guide
|
|
24
|
+
└── README.md
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## ⚙️ Installation
|
|
30
|
+
|
|
31
|
+
### 1. Prerequisites
|
|
32
|
+
- Node.js ≥ 18
|
|
33
|
+
- MongoDB running locally on `mongodb://localhost:27017`
|
|
34
|
+
- Git
|
|
35
|
+
|
|
36
|
+
### 2. Backend
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd backend-project
|
|
40
|
+
npm install
|
|
41
|
+
cp .env.example .env # already provided
|
|
42
|
+
npm run seed # creates default users + 20 customers + 20 vehicles + 30 reservations
|
|
43
|
+
npm run dev # starts API at http://localhost:5000
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Frontend
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd frontend-project
|
|
50
|
+
npm install
|
|
51
|
+
npm run dev # starts UI at http://localhost:5173
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The frontend proxies `/api/*` to the backend, so no extra config is needed.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🔐 Default Accounts
|
|
59
|
+
|
|
60
|
+
| Role | Username | Password |
|
|
61
|
+
|----------|----------|-------------|
|
|
62
|
+
| Admin | admin | admin123 |
|
|
63
|
+
| Manager | manager | manager123 |
|
|
64
|
+
| Staff | staff | staff123 |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 👥 Roles & Permissions
|
|
69
|
+
|
|
70
|
+
| Module | ADMIN | MANAGER | STAFF |
|
|
71
|
+
|---------------|:-----:|:-------:|:-----:|
|
|
72
|
+
| Dashboard | ✅ | ✅ | ✅ |
|
|
73
|
+
| Customers | ✅ | 👁 | ✅ |
|
|
74
|
+
| Vehicles | ✅ | 👁 | 👁 |
|
|
75
|
+
| Reservations | ✅ | approve| ✅ |
|
|
76
|
+
| Rentals | ✅ | 👁 | ✅ |
|
|
77
|
+
| Reports | ✅ | ✅ | ✅ |
|
|
78
|
+
| Users | ✅ | — | — |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 🌟 Features
|
|
83
|
+
|
|
84
|
+
- 🔐 **JWT Authentication** with refresh tokens, password hashing (bcrypt), protected routes
|
|
85
|
+
- 👤 **Role-Based Access Control** (ADMIN / MANAGER / STAFF)
|
|
86
|
+
- 📊 **Analytics Dashboard** — total/available/reserved/rented vehicles, customers, revenue, recent activity, charts (Recharts)
|
|
87
|
+
- 🚗 **Vehicles Module** — CRUD, status management (AVAILABLE / RESERVED / RENTED / MAINTENANCE), search, filter, pagination
|
|
88
|
+
- 👥 **Customers Module** — CRUD, validation (NID/phone/email), search, pagination
|
|
89
|
+
- 📅 **Reservations Module** — create, approve, reject, pickup, return; automatic fee calculation; late-return surcharge (1.5×)
|
|
90
|
+
- 🔑 **Rentals Module** — pickup/return workflow tracking
|
|
91
|
+
- 📑 **Reports Module** — Daily / Weekly / Monthly / Yearly / All-time
|
|
92
|
+
- 📥 **Export** — PDF (jsPDF), Excel (xlsx), CSV, Print
|
|
93
|
+
- 🌗 **Dark / Light Mode** with persistence
|
|
94
|
+
- 💎 **Premium Glassmorphism UI** with Tailwind + Framer Motion animations
|
|
95
|
+
- 🛡 **Security** — Helmet, CORS, rate limiting, input validation, sanitization
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 🌐 API Endpoints
|
|
100
|
+
|
|
101
|
+
### Auth
|
|
102
|
+
- `POST /api/auth/register` — register user
|
|
103
|
+
- `POST /api/auth/login` — login (returns `accessToken`, `refreshToken`)
|
|
104
|
+
- `POST /api/auth/refresh` — refresh access token
|
|
105
|
+
- `POST /api/auth/logout`
|
|
106
|
+
- `GET /api/auth/me`
|
|
107
|
+
|
|
108
|
+
### Customers
|
|
109
|
+
- `GET /api/customers` `?search=&page=&limit=`
|
|
110
|
+
- `GET /api/customers/:id`
|
|
111
|
+
- `POST /api/customers`
|
|
112
|
+
- `PUT /api/customers/:id`
|
|
113
|
+
- `DELETE /api/customers/:id`
|
|
114
|
+
|
|
115
|
+
### Vehicles
|
|
116
|
+
- `GET /api/vehicles` `?search=&status=&page=&limit=`
|
|
117
|
+
- `GET /api/vehicles/:id`
|
|
118
|
+
- `POST /api/vehicles`
|
|
119
|
+
- `PUT /api/vehicles/:id`
|
|
120
|
+
- `DELETE /api/vehicles/:id`
|
|
121
|
+
|
|
122
|
+
### Reservations / Rentals
|
|
123
|
+
- `GET /api/reservations`
|
|
124
|
+
- `POST /api/reservations`
|
|
125
|
+
- `PUT /api/reservations/:id`
|
|
126
|
+
- `DELETE /api/reservations/:id`
|
|
127
|
+
- `PATCH /api/reservations/:id/approve`
|
|
128
|
+
- `PATCH /api/reservations/:id/reject`
|
|
129
|
+
- `PATCH /api/reservations/:id/pickup`
|
|
130
|
+
- `PATCH /api/reservations/:id/return`
|
|
131
|
+
|
|
132
|
+
### Reports
|
|
133
|
+
- `GET /api/reports?period=daily|weekly|monthly|yearly|all`
|
|
134
|
+
|
|
135
|
+
### Dashboard
|
|
136
|
+
- `GET /api/dashboard`
|
|
137
|
+
|
|
138
|
+
### Users (ADMIN only)
|
|
139
|
+
- `GET / POST / PUT / DELETE /api/users[/:id]`
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 🗄 Database (MongoDB — `VRS`)
|
|
144
|
+
|
|
145
|
+
Collections: `users`, `customers`, `vehicles`, `reservationrentals`
|
|
146
|
+
|
|
147
|
+
See **Documentation/ERD.md** and **Documentation/DFD.md** for full diagrams.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 🎨 Design Tokens
|
|
152
|
+
|
|
153
|
+
| Token | Value |
|
|
154
|
+
|-------------|-----------|
|
|
155
|
+
| Primary | `#1E293B` |
|
|
156
|
+
| Secondary | `#334155` |
|
|
157
|
+
| Accent | `#2563EB` |
|
|
158
|
+
| Background | `#F8FAFC` |
|
|
159
|
+
| Dark BG | `#0F172A` |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 📜 License
|
|
164
|
+
|
|
165
|
+
Proprietary — © SwiftWheels Enterprises, Huye, Rwanda.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
PORT=5000
|
|
2
|
+
MONGO_URI=mongodb://localhost:27017/VRS
|
|
3
|
+
JWT_SECRET=swiftwheels_super_secret_key_change_me
|
|
4
|
+
JWT_REFRESH_SECRET=swiftwheels_refresh_secret_key_change_me
|
|
5
|
+
JWT_EXPIRES_IN=1d
|
|
6
|
+
JWT_REFRESH_EXPIRES_IN=7d
|
|
7
|
+
NODE_ENV=development
|
|
8
|
+
CLIENT_URL=http://localhost:5173
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
module.exports = async function connectDB() {
|
|
3
|
+
try {
|
|
4
|
+
await mongoose.connect(process.env.MONGO_URI);
|
|
5
|
+
console.log('✅ MongoDB connected:', mongoose.connection.name);
|
|
6
|
+
} catch (err) {
|
|
7
|
+
console.error('❌ MongoDB error:', err.message);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
};
|
package/templates/VehicleRentalReservationSystem/backend-project/controllers/auth.controller.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const User = require('../models/User');
|
|
2
|
+
const jwt = require('jsonwebtoken');
|
|
3
|
+
const { signAccess, signRefresh } = require('../utils/token');
|
|
4
|
+
|
|
5
|
+
exports.register = async (req, res, next) => {
|
|
6
|
+
try {
|
|
7
|
+
const { username, password, fullName, email, role } = req.body;
|
|
8
|
+
const exists = await User.findOne({ username });
|
|
9
|
+
if (exists) return res.status(409).json({ message: 'Username already exists' });
|
|
10
|
+
const user = await User.create({ username, password, fullName, email, role: role || 'STAFF' });
|
|
11
|
+
res.status(201).json({ user, accessToken: signAccess(user), refreshToken: signRefresh(user) });
|
|
12
|
+
} catch (e) { next(e); }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
exports.login = async (req, res, next) => {
|
|
16
|
+
try {
|
|
17
|
+
const { username, password } = req.body;
|
|
18
|
+
const user = await User.findOne({ username });
|
|
19
|
+
if (!user) return res.status(401).json({ message: 'Invalid credentials' });
|
|
20
|
+
const ok = await user.compare(password);
|
|
21
|
+
if (!ok) return res.status(401).json({ message: 'Invalid credentials' });
|
|
22
|
+
if (!user.active) return res.status(403).json({ message: 'Account disabled' });
|
|
23
|
+
res.json({ user, accessToken: signAccess(user), refreshToken: signRefresh(user) });
|
|
24
|
+
} catch (e) { next(e); }
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
exports.refresh = async (req, res, next) => {
|
|
28
|
+
try {
|
|
29
|
+
const { refreshToken } = req.body;
|
|
30
|
+
if (!refreshToken) return res.status(400).json({ message: 'No refresh token' });
|
|
31
|
+
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
|
|
32
|
+
const user = await User.findById(decoded.id);
|
|
33
|
+
if (!user) return res.status(401).json({ message: 'Invalid token' });
|
|
34
|
+
res.json({ accessToken: signAccess(user), refreshToken: signRefresh(user) });
|
|
35
|
+
} catch (e) { res.status(401).json({ message: 'Invalid refresh token' }); }
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
exports.logout = (req, res) => res.json({ message: 'Logged out' });
|
|
39
|
+
exports.me = (req, res) => res.json({ user: req.user });
|
package/templates/VehicleRentalReservationSystem/backend-project/controllers/customer.controller.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const Customer = require('../models/Customer');
|
|
2
|
+
exports.list = async (req, res, next) => {
|
|
3
|
+
try {
|
|
4
|
+
const { search = '', page = 1, limit = 10 } = req.query;
|
|
5
|
+
const q = search ? { $or: [
|
|
6
|
+
{ fullName: new RegExp(search, 'i') },
|
|
7
|
+
{ nationalId: new RegExp(search, 'i') },
|
|
8
|
+
{ phone: new RegExp(search, 'i') },
|
|
9
|
+
{ email: new RegExp(search, 'i') }
|
|
10
|
+
]} : {};
|
|
11
|
+
const total = await Customer.countDocuments(q);
|
|
12
|
+
const items = await Customer.find(q).sort('-createdAt').skip((page-1)*limit).limit(+limit);
|
|
13
|
+
res.json({ items, total, page: +page, pages: Math.ceil(total/limit) });
|
|
14
|
+
} catch (e) { next(e); }
|
|
15
|
+
};
|
|
16
|
+
exports.get = async (req, res, next) => { try { const c = await Customer.findById(req.params.id); if(!c) return res.status(404).json({message:'Not found'}); res.json(c);} catch(e){next(e);} };
|
|
17
|
+
exports.create = async (req, res, next) => { try { res.status(201).json(await Customer.create(req.body)); } catch(e){next(e);} };
|
|
18
|
+
exports.update = async (req, res, next) => { try { const c = await Customer.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);} };
|
|
19
|
+
exports.remove = async (req, res, next) => { try { await Customer.findByIdAndDelete(req.params.id); res.json({ message: 'Deleted' }); } catch(e){next(e);} };
|
package/templates/VehicleRentalReservationSystem/backend-project/controllers/dashboard.controller.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const Vehicle = require('../models/Vehicle');
|
|
2
|
+
const Customer = require('../models/Customer');
|
|
3
|
+
const RR = require('../models/ReservationRental');
|
|
4
|
+
|
|
5
|
+
exports.stats = async (req, res, next) => {
|
|
6
|
+
try {
|
|
7
|
+
const [totalVehicles, available, reserved, rented, customers, reservations] = await Promise.all([
|
|
8
|
+
Vehicle.countDocuments(),
|
|
9
|
+
Vehicle.countDocuments({ status: 'AVAILABLE' }),
|
|
10
|
+
Vehicle.countDocuments({ status: 'RESERVED' }),
|
|
11
|
+
Vehicle.countDocuments({ status: 'RENTED' }),
|
|
12
|
+
Customer.countDocuments(),
|
|
13
|
+
RR.countDocuments()
|
|
14
|
+
]);
|
|
15
|
+
const revenueAgg = await RR.aggregate([{ $group: { _id: null, total: { $sum: '$rentalFee' } } }]);
|
|
16
|
+
const totalRevenue = revenueAgg[0]?.total || 0;
|
|
17
|
+
const startOfMonth = new Date(); startOfMonth.setDate(1); startOfMonth.setHours(0,0,0,0);
|
|
18
|
+
const monthlyAgg = await RR.aggregate([
|
|
19
|
+
{ $match: { createdAt: { $gte: startOfMonth } } },
|
|
20
|
+
{ $group: { _id: null, total: { $sum: '$rentalFee' } } }
|
|
21
|
+
]);
|
|
22
|
+
const monthlyRevenue = monthlyAgg[0]?.total || 0;
|
|
23
|
+
|
|
24
|
+
const recentReservations = await RR.find().sort('-createdAt').limit(5).populate('customerId').populate('vehicleId');
|
|
25
|
+
const recentRentals = await RR.find({ rentalDate: { $ne: null } }).sort('-rentalDate').limit(5).populate('customerId').populate('vehicleId');
|
|
26
|
+
|
|
27
|
+
// monthly revenue chart - last 6 months
|
|
28
|
+
const months = [];
|
|
29
|
+
for (let i = 5; i >= 0; i--) {
|
|
30
|
+
const d = new Date(); d.setMonth(d.getMonth() - i); d.setDate(1); d.setHours(0,0,0,0);
|
|
31
|
+
const end = new Date(d); end.setMonth(end.getMonth() + 1);
|
|
32
|
+
const agg = await RR.aggregate([
|
|
33
|
+
{ $match: { createdAt: { $gte: d, $lt: end } } },
|
|
34
|
+
{ $group: { _id: null, total: { $sum: '$rentalFee' }, count: { $sum: 1 } } }
|
|
35
|
+
]);
|
|
36
|
+
months.push({ month: d.toLocaleString('en', { month: 'short' }), revenue: agg[0]?.total || 0, count: agg[0]?.count || 0 });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const vehicleTypeAgg = await Vehicle.aggregate([{ $group: { _id: '$vehicleType', count: { $sum: 1 } } }]);
|
|
40
|
+
|
|
41
|
+
res.json({
|
|
42
|
+
totalVehicles, available, reserved, rented, customers, reservations,
|
|
43
|
+
totalRevenue, monthlyRevenue,
|
|
44
|
+
recentReservations, recentRentals, monthlyChart: months,
|
|
45
|
+
vehicleTypes: vehicleTypeAgg.map(x => ({ type: x._id, count: x.count }))
|
|
46
|
+
});
|
|
47
|
+
} catch (e) { next(e); }
|
|
48
|
+
};
|
package/templates/VehicleRentalReservationSystem/backend-project/controllers/report.controller.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const RR = require('../models/ReservationRental');
|
|
2
|
+
|
|
3
|
+
function rangeFor(period) {
|
|
4
|
+
const end = new Date();
|
|
5
|
+
const start = new Date();
|
|
6
|
+
if (period === 'daily') start.setHours(0,0,0,0);
|
|
7
|
+
else if (period === 'weekly') start.setDate(start.getDate() - 7);
|
|
8
|
+
else if (period === 'monthly') start.setMonth(start.getMonth() - 1);
|
|
9
|
+
else if (period === 'yearly') start.setFullYear(start.getFullYear() - 1);
|
|
10
|
+
else start.setFullYear(2000);
|
|
11
|
+
return { start, end };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.generate = async (req, res, next) => {
|
|
15
|
+
try {
|
|
16
|
+
const { period = 'monthly', from, to } = req.query;
|
|
17
|
+
let start, end;
|
|
18
|
+
if (from && to) { start = new Date(from); end = new Date(to); }
|
|
19
|
+
else ({ start, end } = rangeFor(period));
|
|
20
|
+
|
|
21
|
+
const items = await RR.find({ createdAt: { $gte: start, $lte: end } })
|
|
22
|
+
.populate('customerId').populate('vehicleId').populate('userId', 'username fullName')
|
|
23
|
+
.sort('-createdAt');
|
|
24
|
+
|
|
25
|
+
const rows = items.map(r => ({
|
|
26
|
+
customerName: r.customerId?.fullName || '-',
|
|
27
|
+
nationalId: r.customerId?.nationalId || '-',
|
|
28
|
+
phone: r.customerId?.phone || '-',
|
|
29
|
+
plateNumber: r.vehicleId?.plateNumber || '-',
|
|
30
|
+
brand: r.vehicleId?.brand || '-',
|
|
31
|
+
model: r.vehicleId?.model || '-',
|
|
32
|
+
year: r.vehicleId?.year || '-',
|
|
33
|
+
vehicleType: r.vehicleId?.vehicleType || '-',
|
|
34
|
+
reservationDate: r.reservationDate,
|
|
35
|
+
rentalDate: r.rentalDate,
|
|
36
|
+
returnDate: r.returnDate,
|
|
37
|
+
reservationStatus: r.reservationStatus,
|
|
38
|
+
rentalStatus: r.rentalStatus,
|
|
39
|
+
rentalFee: r.rentalFee
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
const totalRevenue = rows.reduce((s, r) => s + (r.rentalFee || 0), 0);
|
|
43
|
+
res.json({ period, from: start, to: end, count: rows.length, totalRevenue, rows });
|
|
44
|
+
} catch (e) { next(e); }
|
|
45
|
+
};
|