create-sales 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 (44) hide show
  1. package/.gitignore +19 -0
  2. package/README.md +40 -0
  3. package/backend-project/.env.example +11 -0
  4. package/backend-project/database/schema.sql +101 -0
  5. package/backend-project/db.js +13 -0
  6. package/backend-project/middleware/auth.js +24 -0
  7. package/backend-project/package.json +21 -0
  8. package/backend-project/routes/auth.js +55 -0
  9. package/backend-project/routes/bookings.js +151 -0
  10. package/backend-project/routes/buses.js +81 -0
  11. package/backend-project/routes/schedules.js +95 -0
  12. package/backend-project/routes/users.js +86 -0
  13. package/backend-project/server.js +54 -0
  14. package/bin/create-sales.js +102 -0
  15. package/frontend-project/index.html +12 -0
  16. package/frontend-project/package.json +31 -0
  17. package/frontend-project/src/App.tsx +77 -0
  18. package/frontend-project/src/api/auth.ts +20 -0
  19. package/frontend-project/src/api/axios.ts +11 -0
  20. package/frontend-project/src/api/bookings.ts +31 -0
  21. package/frontend-project/src/api/buses.ts +26 -0
  22. package/frontend-project/src/api/schedules.ts +26 -0
  23. package/frontend-project/src/api/users.ts +21 -0
  24. package/frontend-project/src/components/common/Layout.tsx +15 -0
  25. package/frontend-project/src/components/common/Modal.tsx +42 -0
  26. package/frontend-project/src/components/common/Navbar.tsx +124 -0
  27. package/frontend-project/src/components/common/ProtectedRoute.tsx +30 -0
  28. package/frontend-project/src/components/common/StatCard.tsx +24 -0
  29. package/frontend-project/src/components/common/Table.tsx +67 -0
  30. package/frontend-project/src/context/AuthContext.tsx +41 -0
  31. package/frontend-project/src/index.css +43 -0
  32. package/frontend-project/src/main.tsx +10 -0
  33. package/frontend-project/src/pages/Bookings.tsx +295 -0
  34. package/frontend-project/src/pages/Buses.tsx +255 -0
  35. package/frontend-project/src/pages/Dashboard.tsx +197 -0
  36. package/frontend-project/src/pages/Login.tsx +112 -0
  37. package/frontend-project/src/pages/Report.tsx +252 -0
  38. package/frontend-project/src/pages/Schedules.tsx +256 -0
  39. package/frontend-project/src/pages/Users.tsx +199 -0
  40. package/frontend-project/src/types/index.ts +63 -0
  41. package/frontend-project/src/utils/cn.ts +6 -0
  42. package/frontend-project/tsconfig.json +31 -0
  43. package/frontend-project/vite.config.ts +19 -0
  44. package/package.json +50 -0
package/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ .env
5
+ .env.local
6
+ .env.*.local
7
+ coverage/
8
+ .DS_Store
9
+ Thumbs.db
10
+ npm-debug.log*
11
+ yarn-debug.log*
12
+ yarn-error.log*
13
+ pnpm-debug.log*
14
+
15
+ frontend-project/node_modules/
16
+ frontend-project/dist/
17
+ frontend-project/.env
18
+ backend-project/node_modules/
19
+ backend-project/.env
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # create-sales
2
+
3
+ This repository is prepared as an npm create initializer for a full-stack bus reservation system.
4
+
5
+ Use it with:
6
+
7
+ ```bash
8
+ npm create sales my-app
9
+ ```
10
+
11
+ ## What is included
12
+
13
+ - `frontend-project/` - React + Vite + TypeScript + Tailwind CSS client
14
+ - `backend-project/` - Express + MySQL + session-auth API
15
+ - Shared template documentation and workspace scripts at the repository root
16
+
17
+ ## Publishing notes
18
+
19
+ The repository root is the npm package entry point. It is configured with workspaces so the frontend and backend can be installed and run from one place.
20
+
21
+ The published package name must be `create-sales` so npm can resolve `npm create sales` correctly.
22
+
23
+ ## Getting started after unpacking the template
24
+
25
+ ```bash
26
+ npm install
27
+ npm run dev:backend
28
+ npm run dev:frontend
29
+ ```
30
+
31
+ ## Backend environment
32
+
33
+ Copy `backend-project/.env.example` to `backend-project/.env` and adjust the values for your local MySQL server.
34
+
35
+ ## Available root scripts
36
+
37
+ - `npm run dev:frontend`
38
+ - `npm run build:frontend`
39
+ - `npm run dev:backend`
40
+ - `npm run start:backend`
@@ -0,0 +1,11 @@
1
+ DB_HOST=localhost
2
+ DB_USER=root
3
+ DB_PASS=
4
+ DB_NAME=y_bus_booking
5
+ SESSION_SECRET=ybus_secret_key_2026
6
+ PORT=5000DB_HOST=localhost
7
+ DB_USER=root
8
+ DB_PASS=your_mysql_password
9
+ DB_NAME=y_bus_booking
10
+ SESSION_SECRET=ybus_super_secret_key_2026
11
+ PORT=5000
@@ -0,0 +1,101 @@
1
+ -- ============================================================
2
+ -- Y Bus Reservation System - Database Schema
3
+ -- Database: y_bus_booking
4
+ -- ============================================================
5
+
6
+ CREATE DATABASE IF NOT EXISTS y_bus_booking
7
+ CHARACTER SET utf8mb4
8
+ COLLATE utf8mb4_unicode_ci;
9
+
10
+ USE y_bus_booking;
11
+
12
+ -- ─── Table: yk_buses ────────────────────────────────────────
13
+ CREATE TABLE IF NOT EXISTS yk_buses (
14
+ BusID INT AUTO_INCREMENT PRIMARY KEY,
15
+ PlateNumber VARCHAR(20) NOT NULL UNIQUE,
16
+ TotalSeats INT NOT NULL DEFAULT 30,
17
+ BusType ENUM('Standard', 'Express', 'Luxury', 'Mini') NOT NULL DEFAULT 'Standard',
18
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
19
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
20
+
21
+ -- ─── Table: yk_users ────────────────────────────────────────
22
+ CREATE TABLE IF NOT EXISTS yk_users (
23
+ UserID INT AUTO_INCREMENT PRIMARY KEY,
24
+ UserName VARCHAR(100) NOT NULL UNIQUE,
25
+ Password VARCHAR(255) NOT NULL,
26
+ UserRole ENUM('admin', 'agent') NOT NULL DEFAULT 'agent',
27
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
28
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
29
+
30
+ -- ─── Table: yk_schedules ─────────────────────────────────────
31
+ CREATE TABLE IF NOT EXISTS yk_schedules (
32
+ ScheduleID INT AUTO_INCREMENT PRIMARY KEY,
33
+ BusID INT NOT NULL,
34
+ RouteName VARCHAR(150) NOT NULL,
35
+ DeparturePoint VARCHAR(150) NOT NULL,
36
+ Destination VARCHAR(150) NOT NULL,
37
+ DepartureTime DATETIME NOT NULL,
38
+ EstimatedArrivalTime DATETIME NOT NULL,
39
+ TicketPrice DECIMAL(10,2) NOT NULL DEFAULT 0.00,
40
+ ScheduleStatus ENUM('Active', 'Inactive', 'Cancelled') NOT NULL DEFAULT 'Active',
41
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
42
+ CONSTRAINT fk_schedule_bus FOREIGN KEY (BusID) REFERENCES yk_buses(BusID)
43
+ ON UPDATE CASCADE ON DELETE RESTRICT
44
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
45
+
46
+ -- ─── Table: yk_bookings ──────────────────────────────────────
47
+ CREATE TABLE IF NOT EXISTS yk_bookings (
48
+ BookingID INT AUTO_INCREMENT PRIMARY KEY,
49
+ ScheduleID INT NOT NULL,
50
+ UserID INT NOT NULL,
51
+ PassengerName VARCHAR(150) NOT NULL,
52
+ PassengerGender ENUM('Male', 'Female', 'Other') NOT NULL,
53
+ PassengerPhone VARCHAR(20) NOT NULL,
54
+ SeatNumber VARCHAR(10) NOT NULL,
55
+ PaymentStatus ENUM('Pending', 'Paid', 'Cancelled') NOT NULL DEFAULT 'Pending',
56
+ BookingDate DATE NOT NULL,
57
+ CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
58
+ CONSTRAINT fk_booking_schedule FOREIGN KEY (ScheduleID) REFERENCES yk_schedules(ScheduleID)
59
+ ON UPDATE CASCADE ON DELETE RESTRICT,
60
+ CONSTRAINT fk_booking_user FOREIGN KEY (UserID) REFERENCES yk_users(UserID)
61
+ ON UPDATE CASCADE ON DELETE RESTRICT
62
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
63
+
64
+ -- ─── Indexes for performance ─────────────────────────────────
65
+ CREATE INDEX idx_bookings_schedule ON yk_bookings(ScheduleID);
66
+ CREATE INDEX idx_bookings_user ON yk_bookings(UserID);
67
+ CREATE INDEX idx_schedules_bus ON yk_schedules(BusID);
68
+ CREATE INDEX idx_schedules_status ON yk_schedules(ScheduleStatus);
69
+
70
+ -- ─── Seed Data ───────────────────────────────────────────────
71
+
72
+ -- Default admin user (password: admin123)
73
+ INSERT INTO yk_users (UserName, Password, UserRole) VALUES
74
+ ('admin', '$2a$10$rBnT2rqbZTMcFKRnVpeTG.qvdV.LKQEm/5DYuCNiUEFbCGVgD/Yme', 'admin'),
75
+ ('agent1', '$2a$10$rBnT2rqbZTMcFKRnVpeTG.qvdV.LKQEm/5DYuCNiUEFbCGVgD/Yme', 'agent');
76
+ -- Both passwords above are hashed 'admin123'
77
+
78
+ -- Sample Buses
79
+ INSERT INTO yk_buses (PlateNumber, TotalSeats, BusType) VALUES
80
+ ('RAD 123 A', 45, 'Standard'),
81
+ ('RAB 456 B', 30, 'Express'),
82
+ ('RAC 789 C', 55, 'Luxury'),
83
+ ('RAE 012 D', 20, 'Mini');
84
+
85
+ -- Sample Schedules
86
+ INSERT INTO yk_schedules (BusID, RouteName, DeparturePoint, Destination, DepartureTime, EstimatedArrivalTime, TicketPrice, ScheduleStatus) VALUES
87
+ (1, 'KGL-BTR Route 1', 'Nyabugogo', 'Butare', '2026-02-01 06:00:00', '2026-02-01 09:00:00', 5000.00, 'Active'),
88
+ (2, 'KGL-MUS Route 1', 'Nyabugogo', 'Musanze', '2026-02-01 07:30:00', '2026-02-01 10:30:00', 4500.00, 'Active'),
89
+ (3, 'KGL-GHY Route 1', 'Downtown Kigali', 'Gisenyi', '2026-02-01 08:00:00', '2026-02-01 12:00:00', 7000.00, 'Active'),
90
+ (4, 'KGL-RWM Route 1', 'Nyabugogo', 'Rwamagana', '2026-02-01 09:00:00', '2026-02-01 10:30:00', 2500.00, 'Active');
91
+
92
+ -- Sample Bookings
93
+ INSERT INTO yk_bookings (ScheduleID, UserID, PassengerName, PassengerGender, PassengerPhone, SeatNumber, PaymentStatus, BookingDate) VALUES
94
+ (1, 1, 'Jean Pierre Habimana', 'Male', '+250788123456', 'A1', 'Paid', '2026-01-28'),
95
+ (1, 1, 'Marie Claire Uwimana', 'Female', '+250788654321', 'A2', 'Paid', '2026-01-28'),
96
+ (1, 2, 'Alexis Nkurunziza', 'Male', '+250789123456', 'B1', 'Pending', '2026-01-29'),
97
+ (2, 1, 'Diane Mukamana', 'Female', '+250788999111', 'A1', 'Paid', '2026-01-29'),
98
+ (2, 2, 'Patrick Mugisha', 'Male', '+250788444555', 'A2', 'Cancelled', '2026-01-30'),
99
+ (3, 1, 'Sandrine Uwase', 'Female', '+250789777888', 'C1', 'Paid', '2026-01-30'),
100
+ (3, 2, 'Eric Nshimiyimana', 'Male', '+250788333222', 'C2', 'Pending', '2026-01-31'),
101
+ (4, 1, 'Claudine Ingabire', 'Female', '+250789555666', 'A1', 'Paid', '2026-01-31');
@@ -0,0 +1,13 @@
1
+ const mysql = require('mysql2/promise');
2
+
3
+ const pool = mysql.createPool({
4
+ host: process.env.DB_HOST || 'localhost',
5
+ user: process.env.DB_USER || 'root',
6
+ password: process.env.DB_PASS || '',
7
+ database: process.env.DB_NAME || 'y_bus_booking',
8
+ waitForConnections: true,
9
+ connectionLimit: 10,
10
+ queueLimit: 0,
11
+ });
12
+
13
+ module.exports = pool;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Middleware: Require authenticated session
3
+ */
4
+ const requireAuth = (req, res, next) => {
5
+ if (!req.session || !req.session.user) {
6
+ return res.status(401).json({ message: 'Unauthorized. Please log in.' });
7
+ }
8
+ next();
9
+ };
10
+
11
+ /**
12
+ * Middleware: Require admin role
13
+ */
14
+ const requireAdmin = (req, res, next) => {
15
+ if (!req.session || !req.session.user) {
16
+ return res.status(401).json({ message: 'Unauthorized. Please log in.' });
17
+ }
18
+ if (req.session.user.UserRole !== 'admin') {
19
+ return res.status(403).json({ message: 'Forbidden. Admin access required.' });
20
+ }
21
+ next();
22
+ };
23
+
24
+ module.exports = { requireAuth, requireAdmin };
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "backend-project",
3
+ "version": "1.0.0",
4
+ "description": "Y Bus Reservation System - Backend API",
5
+ "main": "server.js",
6
+ "type": "commonjs",
7
+ "scripts": {
8
+ "start": "node server.js",
9
+ "dev": "nodemon server.js"
10
+ },
11
+ "dependencies": {
12
+ "bcryptjs": "^2.4.3",
13
+ "cors": "^2.8.5",
14
+ "express": "^4.18.2",
15
+ "express-session": "^1.17.3",
16
+ "mysql2": "^3.6.5"
17
+ },
18
+ "devDependencies": {
19
+ "nodemon": "^3.0.2"
20
+ }
21
+ }
@@ -0,0 +1,55 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const bcrypt = require('bcryptjs');
4
+ const pool = require('../db');
5
+
6
+ // POST /api/auth/login
7
+ router.post('/login', async (req, res) => {
8
+ const { UserName, Password } = req.body;
9
+ if (!UserName || !Password) {
10
+ return res.status(400).json({ message: 'Username and password are required.' });
11
+ }
12
+ try {
13
+ const [rows] = await pool.execute(
14
+ 'SELECT * FROM yk_users WHERE UserName = ?',
15
+ [UserName]
16
+ );
17
+ if (rows.length === 0) {
18
+ return res.status(401).json({ message: 'Invalid username or password.' });
19
+ }
20
+ const user = rows[0];
21
+ const isMatch = await bcrypt.compare(Password, user.Password);
22
+ if (!isMatch) {
23
+ return res.status(401).json({ message: 'Invalid username or password.' });
24
+ }
25
+ // Create session
26
+ req.session.user = {
27
+ UserID: user.UserID,
28
+ UserName: user.UserName,
29
+ UserRole: user.UserRole,
30
+ };
31
+ res.json({ message: 'Login successful', user: req.session.user });
32
+ } catch (err) {
33
+ console.error(err);
34
+ res.status(500).json({ message: 'Server error', error: err.message });
35
+ }
36
+ });
37
+
38
+ // POST /api/auth/logout
39
+ router.post('/logout', (req, res) => {
40
+ req.session.destroy((err) => {
41
+ if (err) return res.status(500).json({ message: 'Logout failed' });
42
+ res.clearCookie('connect.sid');
43
+ res.json({ message: 'Logged out successfully' });
44
+ });
45
+ });
46
+
47
+ // GET /api/auth/session
48
+ router.get('/session', (req, res) => {
49
+ if (req.session && req.session.user) {
50
+ return res.json({ user: req.session.user });
51
+ }
52
+ res.status(401).json({ message: 'No active session' });
53
+ });
54
+
55
+ module.exports = router;
@@ -0,0 +1,151 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const pool = require('../db');
4
+ const { requireAuth } = require('../middleware/auth');
5
+
6
+ // GET /api/bookings/report/passengers — Report grouped by schedule
7
+ router.get('/report/passengers', requireAuth, async (req, res) => {
8
+ try {
9
+ const [rows] = await pool.execute(`
10
+ SELECT
11
+ b.*,
12
+ s.RouteName, s.DeparturePoint, s.Destination, s.DepartureTime,
13
+ s.TicketPrice, s.ScheduleStatus,
14
+ bs.PlateNumber, bs.BusType,
15
+ u.UserName
16
+ FROM yk_bookings b
17
+ LEFT JOIN yk_schedules s ON b.ScheduleID = s.ScheduleID
18
+ LEFT JOIN yk_buses bs ON s.BusID = bs.BusID
19
+ LEFT JOIN yk_users u ON b.UserID = u.UserID
20
+ ORDER BY s.ScheduleID, b.BookingDate
21
+ `);
22
+ res.json(rows);
23
+ } catch (err) {
24
+ res.status(500).json({ message: 'Server error', error: err.message });
25
+ }
26
+ });
27
+
28
+ // GET /api/bookings — Get all bookings
29
+ router.get('/', requireAuth, async (req, res) => {
30
+ try {
31
+ const [rows] = await pool.execute(`
32
+ SELECT
33
+ b.*,
34
+ s.RouteName, s.DeparturePoint, s.Destination, s.DepartureTime,
35
+ s.TicketPrice,
36
+ bs.PlateNumber,
37
+ u.UserName
38
+ FROM yk_bookings b
39
+ LEFT JOIN yk_schedules s ON b.ScheduleID = s.ScheduleID
40
+ LEFT JOIN yk_buses bs ON s.BusID = bs.BusID
41
+ LEFT JOIN yk_users u ON b.UserID = u.UserID
42
+ ORDER BY b.BookingID DESC
43
+ `);
44
+ res.json(rows);
45
+ } catch (err) {
46
+ res.status(500).json({ message: 'Server error', error: err.message });
47
+ }
48
+ });
49
+
50
+ // GET /api/bookings/:id — Get single booking
51
+ router.get('/:id', requireAuth, async (req, res) => {
52
+ try {
53
+ const [rows] = await pool.execute(`
54
+ SELECT
55
+ b.*,
56
+ s.RouteName, s.DeparturePoint, s.Destination, s.DepartureTime,
57
+ s.TicketPrice,
58
+ bs.PlateNumber,
59
+ u.UserName
60
+ FROM yk_bookings b
61
+ LEFT JOIN yk_schedules s ON b.ScheduleID = s.ScheduleID
62
+ LEFT JOIN yk_buses bs ON s.BusID = bs.BusID
63
+ LEFT JOIN yk_users u ON b.UserID = u.UserID
64
+ WHERE b.BookingID = ?
65
+ `, [req.params.id]);
66
+ if (rows.length === 0) return res.status(404).json({ message: 'Booking not found' });
67
+ res.json(rows[0]);
68
+ } catch (err) {
69
+ res.status(500).json({ message: 'Server error', error: err.message });
70
+ }
71
+ });
72
+
73
+ // POST /api/bookings — Create booking
74
+ router.post('/', requireAuth, async (req, res) => {
75
+ const { ScheduleID, PassengerName, PassengerGender, PassengerPhone, SeatNumber, PaymentStatus, BookingDate } = req.body;
76
+ const UserID = req.session.user.UserID;
77
+
78
+ if (!ScheduleID || !PassengerName || !PassengerGender || !PassengerPhone || !SeatNumber) {
79
+ return res.status(400).json({ message: 'All required fields must be provided.' });
80
+ }
81
+
82
+ try {
83
+ // Check if seat is already taken for this schedule
84
+ const [existing] = await pool.execute(
85
+ 'SELECT BookingID FROM yk_bookings WHERE ScheduleID = ? AND SeatNumber = ? AND PaymentStatus != ?',
86
+ [ScheduleID, SeatNumber, 'Cancelled']
87
+ );
88
+ if (existing.length > 0) {
89
+ return res.status(409).json({ message: `Seat ${SeatNumber} is already booked for this schedule.` });
90
+ }
91
+
92
+ const [result] = await pool.execute(
93
+ `INSERT INTO yk_bookings (ScheduleID, UserID, PassengerName, PassengerGender, PassengerPhone, SeatNumber, PaymentStatus, BookingDate)
94
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
95
+ [ScheduleID, UserID, PassengerName, PassengerGender, PassengerPhone, SeatNumber, PaymentStatus || 'Pending', BookingDate || new Date()]
96
+ );
97
+
98
+ const [newBooking] = await pool.execute(`
99
+ SELECT b.*, s.RouteName, s.DeparturePoint, s.Destination, s.DepartureTime, s.TicketPrice,
100
+ bs.PlateNumber, u.UserName
101
+ FROM yk_bookings b
102
+ LEFT JOIN yk_schedules s ON b.ScheduleID = s.ScheduleID
103
+ LEFT JOIN yk_buses bs ON s.BusID = bs.BusID
104
+ LEFT JOIN yk_users u ON b.UserID = u.UserID
105
+ WHERE b.BookingID = ?
106
+ `, [result.insertId]);
107
+
108
+ res.status(201).json(newBooking[0]);
109
+ } catch (err) {
110
+ res.status(500).json({ message: 'Server error', error: err.message });
111
+ }
112
+ });
113
+
114
+ // PUT /api/bookings/:id — Update booking
115
+ router.put('/:id', requireAuth, async (req, res) => {
116
+ const { ScheduleID, PassengerName, PassengerGender, PassengerPhone, SeatNumber, PaymentStatus, BookingDate } = req.body;
117
+ try {
118
+ const [result] = await pool.execute(
119
+ `UPDATE yk_bookings SET ScheduleID=?, PassengerName=?, PassengerGender=?, PassengerPhone=?,
120
+ SeatNumber=?, PaymentStatus=?, BookingDate=? WHERE BookingID=?`,
121
+ [ScheduleID, PassengerName, PassengerGender, PassengerPhone, SeatNumber, PaymentStatus, BookingDate, req.params.id]
122
+ );
123
+ if (result.affectedRows === 0) return res.status(404).json({ message: 'Booking not found' });
124
+
125
+ const [updated] = await pool.execute(`
126
+ SELECT b.*, s.RouteName, s.DeparturePoint, s.Destination, s.DepartureTime, s.TicketPrice,
127
+ bs.PlateNumber, u.UserName
128
+ FROM yk_bookings b
129
+ LEFT JOIN yk_schedules s ON b.ScheduleID = s.ScheduleID
130
+ LEFT JOIN yk_buses bs ON s.BusID = bs.BusID
131
+ LEFT JOIN yk_users u ON b.UserID = u.UserID
132
+ WHERE b.BookingID = ?
133
+ `, [req.params.id]);
134
+ res.json(updated[0]);
135
+ } catch (err) {
136
+ res.status(500).json({ message: 'Server error', error: err.message });
137
+ }
138
+ });
139
+
140
+ // DELETE /api/bookings/:id — Delete booking
141
+ router.delete('/:id', requireAuth, async (req, res) => {
142
+ try {
143
+ const [result] = await pool.execute('DELETE FROM yk_bookings WHERE BookingID = ?', [req.params.id]);
144
+ if (result.affectedRows === 0) return res.status(404).json({ message: 'Booking not found' });
145
+ res.json({ message: 'Booking deleted successfully' });
146
+ } catch (err) {
147
+ res.status(500).json({ message: 'Server error', error: err.message });
148
+ }
149
+ });
150
+
151
+ module.exports = router;
@@ -0,0 +1,81 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const pool = require('../db');
4
+ const { requireAuth } = require('../middleware/auth');
5
+
6
+ // GET /api/buses — Get all buses
7
+ router.get('/', requireAuth, async (req, res) => {
8
+ try {
9
+ const [rows] = await pool.execute('SELECT * FROM yk_buses ORDER BY BusID DESC');
10
+ res.json(rows);
11
+ } catch (err) {
12
+ res.status(500).json({ message: 'Server error', error: err.message });
13
+ }
14
+ });
15
+
16
+ // GET /api/buses/:id — Get single bus
17
+ router.get('/:id', requireAuth, async (req, res) => {
18
+ try {
19
+ const [rows] = await pool.execute('SELECT * FROM yk_buses WHERE BusID = ?', [req.params.id]);
20
+ if (rows.length === 0) return res.status(404).json({ message: 'Bus not found' });
21
+ res.json(rows[0]);
22
+ } catch (err) {
23
+ res.status(500).json({ message: 'Server error', error: err.message });
24
+ }
25
+ });
26
+
27
+ // POST /api/buses — Create bus
28
+ router.post('/', requireAuth, async (req, res) => {
29
+ const { PlateNumber, TotalSeats, BusType } = req.body;
30
+ if (!PlateNumber || !TotalSeats || !BusType) {
31
+ return res.status(400).json({ message: 'PlateNumber, TotalSeats, and BusType are required.' });
32
+ }
33
+ try {
34
+ const [result] = await pool.execute(
35
+ 'INSERT INTO yk_buses (PlateNumber, TotalSeats, BusType) VALUES (?, ?, ?)',
36
+ [PlateNumber, TotalSeats, BusType]
37
+ );
38
+ const [newBus] = await pool.execute('SELECT * FROM yk_buses WHERE BusID = ?', [result.insertId]);
39
+ res.status(201).json(newBus[0]);
40
+ } catch (err) {
41
+ if (err.code === 'ER_DUP_ENTRY') {
42
+ return res.status(409).json({ message: 'A bus with this plate number already exists.' });
43
+ }
44
+ res.status(500).json({ message: 'Server error', error: err.message });
45
+ }
46
+ });
47
+
48
+ // PUT /api/buses/:id — Update bus
49
+ router.put('/:id', requireAuth, async (req, res) => {
50
+ const { PlateNumber, TotalSeats, BusType } = req.body;
51
+ try {
52
+ const [result] = await pool.execute(
53
+ 'UPDATE yk_buses SET PlateNumber = ?, TotalSeats = ?, BusType = ? WHERE BusID = ?',
54
+ [PlateNumber, TotalSeats, BusType, req.params.id]
55
+ );
56
+ if (result.affectedRows === 0) return res.status(404).json({ message: 'Bus not found' });
57
+ const [updated] = await pool.execute('SELECT * FROM yk_buses WHERE BusID = ?', [req.params.id]);
58
+ res.json(updated[0]);
59
+ } catch (err) {
60
+ if (err.code === 'ER_DUP_ENTRY') {
61
+ return res.status(409).json({ message: 'A bus with this plate number already exists.' });
62
+ }
63
+ res.status(500).json({ message: 'Server error', error: err.message });
64
+ }
65
+ });
66
+
67
+ // DELETE /api/buses/:id — Delete bus
68
+ router.delete('/:id', requireAuth, async (req, res) => {
69
+ try {
70
+ const [result] = await pool.execute('DELETE FROM yk_buses WHERE BusID = ?', [req.params.id]);
71
+ if (result.affectedRows === 0) return res.status(404).json({ message: 'Bus not found' });
72
+ res.json({ message: 'Bus deleted successfully' });
73
+ } catch (err) {
74
+ if (err.code === 'ER_ROW_IS_REFERENCED_2') {
75
+ return res.status(409).json({ message: 'Cannot delete bus. It is used in one or more schedules.' });
76
+ }
77
+ res.status(500).json({ message: 'Server error', error: err.message });
78
+ }
79
+ });
80
+
81
+ module.exports = router;
@@ -0,0 +1,95 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const pool = require('../db');
4
+ const { requireAuth } = require('../middleware/auth');
5
+
6
+ // GET /api/schedules — Get all schedules with bus info
7
+ router.get('/', requireAuth, async (req, res) => {
8
+ try {
9
+ const [rows] = await pool.execute(`
10
+ SELECT s.*, b.PlateNumber, b.BusType, b.TotalSeats
11
+ FROM yk_schedules s
12
+ LEFT JOIN yk_buses b ON s.BusID = b.BusID
13
+ ORDER BY s.ScheduleID DESC
14
+ `);
15
+ res.json(rows);
16
+ } catch (err) {
17
+ res.status(500).json({ message: 'Server error', error: err.message });
18
+ }
19
+ });
20
+
21
+ // GET /api/schedules/:id — Get single schedule
22
+ router.get('/:id', requireAuth, async (req, res) => {
23
+ try {
24
+ const [rows] = await pool.execute(`
25
+ SELECT s.*, b.PlateNumber, b.BusType, b.TotalSeats
26
+ FROM yk_schedules s
27
+ LEFT JOIN yk_buses b ON s.BusID = b.BusID
28
+ WHERE s.ScheduleID = ?
29
+ `, [req.params.id]);
30
+ if (rows.length === 0) return res.status(404).json({ message: 'Schedule not found' });
31
+ res.json(rows[0]);
32
+ } catch (err) {
33
+ res.status(500).json({ message: 'Server error', error: err.message });
34
+ }
35
+ });
36
+
37
+ // POST /api/schedules — Create schedule
38
+ router.post('/', requireAuth, async (req, res) => {
39
+ const { BusID, RouteName, DeparturePoint, Destination, DepartureTime, EstimatedArrivalTime, TicketPrice, ScheduleStatus } = req.body;
40
+ if (!BusID || !RouteName || !DeparturePoint || !Destination || !DepartureTime || !EstimatedArrivalTime || TicketPrice == null) {
41
+ return res.status(400).json({ message: 'All fields are required.' });
42
+ }
43
+ try {
44
+ const [result] = await pool.execute(
45
+ `INSERT INTO yk_schedules (BusID, RouteName, DeparturePoint, Destination, DepartureTime, EstimatedArrivalTime, TicketPrice, ScheduleStatus)
46
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
47
+ [BusID, RouteName, DeparturePoint, Destination, DepartureTime, EstimatedArrivalTime, TicketPrice, ScheduleStatus || 'Active']
48
+ );
49
+ const [newRow] = await pool.execute(`
50
+ SELECT s.*, b.PlateNumber, b.BusType, b.TotalSeats
51
+ FROM yk_schedules s LEFT JOIN yk_buses b ON s.BusID = b.BusID
52
+ WHERE s.ScheduleID = ?
53
+ `, [result.insertId]);
54
+ res.status(201).json(newRow[0]);
55
+ } catch (err) {
56
+ res.status(500).json({ message: 'Server error', error: err.message });
57
+ }
58
+ });
59
+
60
+ // PUT /api/schedules/:id — Update schedule
61
+ router.put('/:id', requireAuth, async (req, res) => {
62
+ const { BusID, RouteName, DeparturePoint, Destination, DepartureTime, EstimatedArrivalTime, TicketPrice, ScheduleStatus } = req.body;
63
+ try {
64
+ const [result] = await pool.execute(
65
+ `UPDATE yk_schedules SET BusID=?, RouteName=?, DeparturePoint=?, Destination=?,
66
+ DepartureTime=?, EstimatedArrivalTime=?, TicketPrice=?, ScheduleStatus=? WHERE ScheduleID=?`,
67
+ [BusID, RouteName, DeparturePoint, Destination, DepartureTime, EstimatedArrivalTime, TicketPrice, ScheduleStatus, req.params.id]
68
+ );
69
+ if (result.affectedRows === 0) return res.status(404).json({ message: 'Schedule not found' });
70
+ const [updated] = await pool.execute(`
71
+ SELECT s.*, b.PlateNumber, b.BusType, b.TotalSeats
72
+ FROM yk_schedules s LEFT JOIN yk_buses b ON s.BusID = b.BusID
73
+ WHERE s.ScheduleID = ?
74
+ `, [req.params.id]);
75
+ res.json(updated[0]);
76
+ } catch (err) {
77
+ res.status(500).json({ message: 'Server error', error: err.message });
78
+ }
79
+ });
80
+
81
+ // DELETE /api/schedules/:id — Delete schedule
82
+ router.delete('/:id', requireAuth, async (req, res) => {
83
+ try {
84
+ const [result] = await pool.execute('DELETE FROM yk_schedules WHERE ScheduleID = ?', [req.params.id]);
85
+ if (result.affectedRows === 0) return res.status(404).json({ message: 'Schedule not found' });
86
+ res.json({ message: 'Schedule deleted successfully' });
87
+ } catch (err) {
88
+ if (err.code === 'ER_ROW_IS_REFERENCED_2') {
89
+ return res.status(409).json({ message: 'Cannot delete schedule. It has existing bookings.' });
90
+ }
91
+ res.status(500).json({ message: 'Server error', error: err.message });
92
+ }
93
+ });
94
+
95
+ module.exports = router;