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.
- package/LICENSE +21 -0
- package/PUBLISHING.md +35 -0
- package/README.md +51 -0
- package/bin/create-bus-project.js +113 -0
- package/package.json +31 -0
- package/template/README.md +35 -0
- package/template/backend/.env +8 -0
- package/template/backend/.env.example +8 -0
- package/template/backend/README.md +29 -0
- package/template/backend/config/db.js +21 -0
- package/template/backend/controllers/authController.js +109 -0
- package/template/backend/controllers/bookingController.js +99 -0
- package/template/backend/controllers/busController.js +34 -0
- package/template/backend/controllers/scheduleController.js +52 -0
- package/template/backend/controllers/userController.js +39 -0
- package/template/backend/index.js +39 -0
- package/template/backend/package.json +22 -0
- package/template/backend/routes/authRoutes.js +17 -0
- package/template/backend/routes/bookingRoutes.js +20 -0
- package/template/backend/routes/busRoutes.js +10 -0
- package/template/backend/routes/scheduleRoutes.js +10 -0
- package/template/backend/routes/userRoutes.js +10 -0
- package/template/backend/schema.sql +59 -0
- package/template/backend/seeders/seedUsers.js +35 -0
- package/template/frontend/.env +1 -0
- package/template/frontend/.env.example +1 -0
- package/template/frontend/README.md +14 -0
- package/template/frontend/index.html +12 -0
- package/template/frontend/package.json +21 -0
- package/template/frontend/src/App.jsx +55 -0
- package/template/frontend/src/api.js +1 -0
- package/template/frontend/src/main.jsx +10 -0
- package/template/frontend/src/pages/Bookings.jsx +129 -0
- package/template/frontend/src/pages/Buses.jsx +100 -0
- package/template/frontend/src/pages/Dashboard.jsx +10 -0
- package/template/frontend/src/pages/Login.jsx +141 -0
- package/template/frontend/src/pages/ManagerDashboard.jsx +39 -0
- package/template/frontend/src/pages/PassengerDashboard.jsx +248 -0
- package/template/frontend/src/pages/Report.jsx +158 -0
- package/template/frontend/src/pages/Schedules.jsx +142 -0
- package/template/frontend/src/pages/Users.jsx +105 -0
- package/template/frontend/src/style.css +236 -0
- 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
|
+
}
|