create-bus-project 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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/PUBLISHING.md +35 -0
  3. package/README.md +51 -0
  4. package/bin/create-bus-project.js +113 -0
  5. package/package.json +31 -0
  6. package/template/README.md +35 -0
  7. package/template/backend/.env +8 -0
  8. package/template/backend/.env.example +8 -0
  9. package/template/backend/README.md +29 -0
  10. package/template/backend/config/db.js +21 -0
  11. package/template/backend/controllers/authController.js +109 -0
  12. package/template/backend/controllers/bookingController.js +99 -0
  13. package/template/backend/controllers/busController.js +34 -0
  14. package/template/backend/controllers/scheduleController.js +52 -0
  15. package/template/backend/controllers/userController.js +39 -0
  16. package/template/backend/index.js +39 -0
  17. package/template/backend/package.json +22 -0
  18. package/template/backend/routes/authRoutes.js +17 -0
  19. package/template/backend/routes/bookingRoutes.js +20 -0
  20. package/template/backend/routes/busRoutes.js +10 -0
  21. package/template/backend/routes/scheduleRoutes.js +10 -0
  22. package/template/backend/routes/userRoutes.js +10 -0
  23. package/template/backend/schema.sql +59 -0
  24. package/template/backend/seeders/seedUsers.js +35 -0
  25. package/template/frontend/.env +1 -0
  26. package/template/frontend/.env.example +1 -0
  27. package/template/frontend/README.md +14 -0
  28. package/template/frontend/index.html +12 -0
  29. package/template/frontend/package.json +21 -0
  30. package/template/frontend/src/App.jsx +55 -0
  31. package/template/frontend/src/api.js +1 -0
  32. package/template/frontend/src/main.jsx +10 -0
  33. package/template/frontend/src/pages/Bookings.jsx +129 -0
  34. package/template/frontend/src/pages/Buses.jsx +100 -0
  35. package/template/frontend/src/pages/Dashboard.jsx +10 -0
  36. package/template/frontend/src/pages/Login.jsx +141 -0
  37. package/template/frontend/src/pages/ManagerDashboard.jsx +39 -0
  38. package/template/frontend/src/pages/PassengerDashboard.jsx +248 -0
  39. package/template/frontend/src/pages/Report.jsx +158 -0
  40. package/template/frontend/src/pages/Schedules.jsx +142 -0
  41. package/template/frontend/src/pages/Users.jsx +105 -0
  42. package/template/frontend/src/style.css +236 -0
  43. package/template/frontend/vite.config.js +10 -0
@@ -0,0 +1,141 @@
1
+ import React from "react";
2
+ import { useState } from "react";
3
+ import { API_BASE_URL } from "../api.js";
4
+
5
+ export default function Login({ setUser }) {
6
+ const [isRegister, setIsRegister] = useState(false);
7
+
8
+ const [form, setForm] = useState({
9
+ username: "",
10
+ password: "",
11
+ userRole: "passenger"
12
+ });
13
+
14
+ const handleChange = (e) => {
15
+ setForm({
16
+ ...form,
17
+ [e.target.name]: e.target.value
18
+ });
19
+ };
20
+
21
+ const handleRole = (role) => {
22
+ setForm({
23
+ ...form,
24
+ userRole: role
25
+ });
26
+ };
27
+
28
+ const resetForm = () => {
29
+ setForm({
30
+ username: "",
31
+ password: "",
32
+ userRole: "passenger"
33
+ });
34
+ };
35
+
36
+ const handleSubmit = async (e) => {
37
+ e.preventDefault();
38
+
39
+ const endpoint = isRegister ? "register" : "login";
40
+
41
+ const res = await fetch(`${API_BASE_URL}/api/auth/${endpoint}`, {
42
+ method: "POST",
43
+ credentials: "include",
44
+ headers: {
45
+ "Content-Type": "application/json"
46
+ },
47
+ body: JSON.stringify(form)
48
+ });
49
+
50
+ const data = await res.json();
51
+
52
+ if (!res.ok) {
53
+ alert(data.message || "Something went wrong`);
54
+ return;
55
+ }
56
+
57
+ if (isRegister) {
58
+ alert(data.message);
59
+ setIsRegister(false);
60
+ resetForm();
61
+ return;
62
+ }
63
+
64
+ setUser(data.user);
65
+ };
66
+
67
+ return (
68
+ <section className="login-page">
69
+ <div className="login-card">
70
+ <h1>Y Bus Booking</h1>
71
+
72
+ <p className="muted">
73
+ {isRegister ? "Create your account" : "Login to continue"}
74
+ </p>
75
+
76
+ <div className="role-switch">
77
+ <button
78
+ type="button"
79
+ className={form.userRole === "passenger" ? "active-role" : ""}
80
+ onClick={() => handleRole("passenger")}
81
+ >
82
+ Passenger
83
+ </button>
84
+
85
+ <button
86
+ type="button"
87
+ className={form.userRole === "manager" ? "active-role" : ""}
88
+ onClick={() => handleRole("manager")}
89
+ >
90
+ Manager
91
+ </button>
92
+ </div>
93
+
94
+ <form onSubmit={handleSubmit}>
95
+ <label>Username</label>
96
+ <input
97
+ name="username"
98
+ placeholder="Enter username"
99
+ value={form.username}
100
+ onChange={handleChange}
101
+ />
102
+
103
+ <label>Password</label>
104
+ <input
105
+ name="password"
106
+ type="password"
107
+ placeholder="Enter password"
108
+ value={form.password}
109
+ onChange={handleChange}
110
+ />
111
+
112
+ <button className="full-btn" type="submit">
113
+ {isRegister
114
+ ? `Register as ${form.userRole}`
115
+ : `Login as ${form.userRole}`}
116
+ </button>
117
+ </form>
118
+
119
+ <div className="switch-auth">
120
+ {isRegister ? (
121
+ <p>
122
+ Already have an account?
123
+ <button type="button" onClick={() => setIsRegister(false)}>
124
+ Login
125
+ </button>
126
+ </p>
127
+ ) : (
128
+ <p>
129
+ No account?
130
+ <button type="button" onClick={() => setIsRegister(true)}>
131
+ Register
132
+ </button>
133
+ </p>
134
+ )}
135
+ </div>
136
+
137
+
138
+ </div>
139
+ </section>
140
+ );
141
+ }
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import { useState } from "react";
3
+ import Users from "./Users.jsx";
4
+ import Buses from "./Buses.jsx";
5
+ import Schedules from "./Schedules.jsx";
6
+ import Bookings from "./Bookings.jsx";
7
+ import Report from "./Report.jsx";
8
+
9
+ export default function ManagerDashboard({ user, logout }) {
10
+ const [page, setPage] = useState("users");
11
+
12
+ return (
13
+ <div>
14
+ <header className="topbar">
15
+ <div>
16
+ <h2>Manager Dashboard</h2>
17
+ <p>Welcome, {user.username}</p>
18
+ </div>
19
+
20
+ <nav>
21
+ <button onClick={() => setPage("users")}>Users</button>
22
+ <button onClick={() => setPage("buses")}>Buses</button>
23
+ <button onClick={() => setPage("schedules")}>Schedules</button>
24
+ <button onClick={() => setPage("bookings")}>Bookings</button>
25
+ <button onClick={() => setPage("report")}>Report</button>
26
+ <button onClick={logout}>Logout</button>
27
+ </nav>
28
+ </header>
29
+
30
+ <main className="container">
31
+ {page === "users" && <Users />}
32
+ {page === "buses" && <Buses />}
33
+ {page === "schedules" && <Schedules />}
34
+ {page === "bookings" && <Bookings />}
35
+ {page === "report" && <Report />}
36
+ </main>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,248 @@
1
+ import React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import { API_BASE_URL } from "../api.js";
4
+
5
+ export default function PassengerDashboard({ user, logout }) {
6
+ const [schedules, setSchedules] = useState([]);
7
+ const [myTickets, setMyTickets] = useState([]);
8
+ const [selectedSchedule, setSelectedSchedule] = useState(null);
9
+ const [bookedSeats, setBookedSeats] = useState([]);
10
+
11
+ const [form, setForm] = useState({
12
+ passengername: user.username,
13
+ passengergender: "Female",
14
+ passengerphone: "",
15
+ seatnumber: "",
16
+ paymentstatus: "pending"
17
+ });
18
+
19
+ const fetchSchedules = async () => {
20
+ const res = await fetch(`${API_BASE_URL}/api/schedules`);
21
+ const data = await res.json();
22
+ setSchedules(data.filter((item) => item.scheduleStatus === "active"));
23
+ };
24
+
25
+ const fetchMyTickets = async () => {
26
+ const res = await fetch(`${API_BASE_URL}/api/bookings/my/${user.userid}`);
27
+ const data = await res.json();
28
+ setMyTickets(data);
29
+ };
30
+
31
+ const fetchBookedSeats = async (scheduleid) => {
32
+ const res = await fetch(`${API_BASE_URL}/api/bookings/seats/${scheduleid}`);
33
+ const data = await res.json();
34
+ setBookedSeats(data);
35
+ };
36
+
37
+ useEffect(() => {
38
+ fetchSchedules();
39
+ fetchMyTickets();
40
+ }, []);
41
+
42
+ const chooseSchedule = (schedule) => {
43
+ setSelectedSchedule(schedule);
44
+ setForm({
45
+ ...form,
46
+ seatnumber: ""
47
+ });
48
+ fetchBookedSeats(schedule.scheduleid);
49
+ };
50
+
51
+ const handleChange = (e) => {
52
+ setForm({
53
+ ...form,
54
+ [e.target.name]: e.target.value
55
+ });
56
+ };
57
+
58
+ const bookTicket = async (e) => {
59
+ e.preventDefault();
60
+
61
+ if (!selectedSchedule) {
62
+ alert("Please choose a schedule first`);
63
+ return;
64
+ }
65
+
66
+ const res = await fetch(`${API_BASE_URL}/api/bookings`, {
67
+ method: "POST",
68
+ headers: {
69
+ "Content-Type": "application/json"
70
+ },
71
+ body: JSON.stringify({
72
+ scheduleid: selectedSchedule.scheduleid,
73
+ userid: user.userid,
74
+ ...form
75
+ })
76
+ });
77
+
78
+ const data = await res.json();
79
+
80
+ if (!res.ok) {
81
+ alert(data.message || data.sqlMessage || "Booking failed`);
82
+ return;
83
+ }
84
+
85
+ alert("Ticket booked successfully`);
86
+
87
+ setForm({
88
+ passengername: user.username,
89
+ passengergender: "Female",
90
+ passengerphone: "",
91
+ seatnumber: "",
92
+ paymentstatus: "pending"
93
+ });
94
+
95
+ fetchMyTickets();
96
+ fetchBookedSeats(selectedSchedule.scheduleid);
97
+ };
98
+
99
+ const totalSeats = Number(selectedSchedule?.totalseats || 0);
100
+ const seats = Array.from({ length: totalSeats }, (_, i) => String(i + 1));
101
+
102
+ return (
103
+ <div>
104
+ <header className="topbar">
105
+ <div>
106
+ <h2>Passenger Dashboard</h2>
107
+ <p>Welcome, {user.username}</p>
108
+ </div>
109
+
110
+ <nav>
111
+ <button onClick={logout}>Logout</button>
112
+ </nav>
113
+ </header>
114
+
115
+ <main className="container">
116
+ <section className="card">
117
+ <h1>Available Schedules</h1>
118
+
119
+ <table>
120
+ <thead>
121
+ <tr>
122
+ <th>Route</th>
123
+ <th>From</th>
124
+ <th>To</th>
125
+ <th>Bus</th>
126
+ <th>Seats</th>
127
+ <th>Price</th>
128
+ <th>Action</th>
129
+ </tr>
130
+ </thead>
131
+
132
+ <tbody>
133
+ {schedules.map((schedule) => (
134
+ <tr key={schedule.scheduleid}>
135
+ <td>{schedule.routename}</td>
136
+ <td>{schedule.departurepoint}</td>
137
+ <td>{schedule.destination}</td>
138
+ <td>{schedule.platenumber}</td>
139
+ <td>{schedule.totalseats}</td>
140
+ <td>{schedule.ticketprice}</td>
141
+ <td>
142
+ <button onClick={() => chooseSchedule(schedule)}>
143
+ Book
144
+ </button>
145
+ </td>
146
+ </tr>
147
+ ))}
148
+ </tbody>
149
+ </table>
150
+ </section>
151
+
152
+ {selectedSchedule && (
153
+ <section className="card">
154
+ <h2>Book Ticket: {selectedSchedule.routename}</h2>
155
+ <p className="muted">
156
+ Choose one available seat. Red seats are already booked.
157
+ </p>
158
+
159
+ <div className="seat-grid">
160
+ {seats.map((seat) => {
161
+ const booked = bookedSeats.includes(seat);
162
+ const selected = form.seatnumber === seat;
163
+
164
+ return (
165
+ <button
166
+ type="button"
167
+ key={seat}
168
+ disabled={booked}
169
+ className={booked ? "seat booked" : selected ? "seat selected" : "seat"}
170
+ onClick={() => setForm({ ...form, seatnumber: seat })}
171
+ >
172
+ {seat}
173
+ </button>
174
+ );
175
+ })}
176
+ </div>
177
+
178
+ <form className="form-grid mt" onSubmit={bookTicket}>
179
+ <input
180
+ name="passengername"
181
+ placeholder="Passenger Name"
182
+ value={form.passengername}
183
+ onChange={handleChange}
184
+ />
185
+
186
+ <select
187
+ name="passengergender"
188
+ value={form.passengergender}
189
+ onChange={handleChange}
190
+ >
191
+ <option value="Female">Female</option>
192
+ <option value="Male">Male</option>
193
+ </select>
194
+
195
+ <input
196
+ name="passengerphone"
197
+ placeholder="Phone Number"
198
+ value={form.passengerphone}
199
+ onChange={handleChange}
200
+ />
201
+
202
+ <input
203
+ name="seatnumber"
204
+ placeholder="Selected Seat"
205
+ value={form.seatnumber}
206
+ readOnly
207
+ />
208
+
209
+ <button type="submit">Confirm Booking</button>
210
+ </form>
211
+ </section>
212
+ )}
213
+
214
+ <section className="card">
215
+ <h1>My Tickets</h1>
216
+
217
+ <table>
218
+ <thead>
219
+ <tr>
220
+ <th>Route</th>
221
+ <th>From</th>
222
+ <th>To</th>
223
+ <th>Passenger</th>
224
+ <th>Seat</th>
225
+ <th>Payment</th>
226
+ <th>Date</th>
227
+ </tr>
228
+ </thead>
229
+
230
+ <tbody>
231
+ {myTickets.map((ticket) => (
232
+ <tr key={ticket.bookingid}>
233
+ <td>{ticket.routename}</td>
234
+ <td>{ticket.departurepoint}</td>
235
+ <td>{ticket.destination}</td>
236
+ <td>{ticket.passengername}</td>
237
+ <td>{ticket.seatnumber}</td>
238
+ <td>{ticket.paymentstatus}</td>
239
+ <td>{ticket.bookingdate}</td>
240
+ </tr>
241
+ ))}
242
+ </tbody>
243
+ </table>
244
+ </section>
245
+ </main>
246
+ </div>
247
+ );
248
+ }
@@ -0,0 +1,158 @@
1
+ import React from "react";
2
+ import { useEffect, useState } from "react";
3
+
4
+ import jsPDF from "jspdf";
5
+ import autoTable from "jspdf-autotable";
6
+ import { API_BASE_URL } from "../api.js";
7
+
8
+ export default function Report() {
9
+
10
+ const [report, setReport] = useState([]);
11
+ const [filter, setFilter] = useState("");
12
+
13
+ const fetchReport = async () => {
14
+
15
+ const res = await fetch(
16
+ `${API_BASE_URL}/api/bookings/report`
17
+ );
18
+
19
+ const data = await res.json();
20
+
21
+ setReport(data);
22
+ };
23
+
24
+ useEffect(() => {
25
+ fetchReport();
26
+ }, []);
27
+
28
+ const filteredReport = report.filter((item) =>
29
+ item.routename
30
+ .toLowerCase()
31
+ .includes(filter.toLowerCase())
32
+ );
33
+
34
+ const exportPDF = () => {
35
+
36
+ const doc = new jsPDF();
37
+
38
+ doc.setFontSize(18);
39
+
40
+ doc.text(
41
+ "Booked Passengers Report",
42
+ 14,
43
+ 20
44
+ );
45
+
46
+ const tableColumn = [
47
+ "Schedule ID",
48
+ "Route",
49
+ "From",
50
+ "To",
51
+ "Passenger",
52
+ "Gender",
53
+ "Phone",
54
+ "Seat",
55
+ "Payment"
56
+ ];
57
+
58
+ const tableRows = [];
59
+
60
+ filteredReport.forEach((item) => {
61
+
62
+ const rowData = [
63
+
64
+ item.scheduleid,
65
+ item.routename,
66
+ item.departurepoint,
67
+ item.destination,
68
+ item.passengername,
69
+ item.passengergender,
70
+ item.passengerphone,
71
+ item.seatnumber,
72
+ item.paymentstatus
73
+
74
+ ];
75
+
76
+ tableRows.push(rowData);
77
+ });
78
+
79
+ autoTable(doc, {
80
+
81
+ head: [tableColumn],
82
+
83
+ body: tableRows,
84
+
85
+ startY: 30
86
+
87
+ });
88
+
89
+ doc.save("Booked_Passengers_Report.pdf");
90
+ };
91
+
92
+ return (
93
+ <div>
94
+
95
+ <div className="card">
96
+
97
+ <h1>
98
+ Booked Passengers Report
99
+ </h1>
100
+
101
+ <input
102
+ placeholder="Filter by route name"
103
+ value={filter}
104
+ onChange={(e) =>
105
+ setFilter(e.target.value)
106
+ }
107
+ />
108
+
109
+ <br /><br />
110
+
111
+ <button onClick={exportPDF}>
112
+ Export as PDF
113
+ </button>
114
+
115
+ </div>
116
+
117
+ <table>
118
+
119
+ <thead>
120
+
121
+ <tr>
122
+
123
+ <th>Route</th>
124
+ <th>From</th>
125
+ <th>To</th>
126
+ <th>Passenger</th>
127
+ <th>Gender</th>
128
+ <th>Phone</th>
129
+ <th>Seat</th>
130
+ <th>Payment</th>
131
+ </tr>
132
+
133
+ </thead>
134
+
135
+ <tbody>
136
+
137
+ {filteredReport.map((item, index) => (
138
+
139
+ <tr key={index}>
140
+ <td>{item.routename}</td>
141
+ <td>{item.departurepoint}</td>
142
+ <td>{item.destination}</td>
143
+ <td>{item.passengername}</td>
144
+ <td>{item.passengergender}</td>
145
+ <td>{item.passengerphone}</td>
146
+ <td>{item.seatnumber}</td>
147
+ <td>{item.paymentstatus}</td>
148
+ </tr>
149
+
150
+ ))}
151
+
152
+ </tbody>
153
+
154
+ </table>
155
+
156
+ </div>
157
+ );
158
+ }