Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
.env
dist/
build/
.vscode/
*.log
.DS_Store
Binary file added AbrarKivande/1_pgadmin_tables.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AbrarKivande/1_ui_main.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AbrarKivande/2_ui_routes_add.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AbrarKivande/4_ui_schedule_add.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AbrarKivande/6_ui_delete_route_after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added AbrarKivande/7_server_terminal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 26 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
# React + Vite
# Cab Booking — Local Setup (by Abrar)

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
## Overview
This repo contains:
- server (Express + pg) -> `server/index.js`
- DB schema + seed -> `server/sql/db_schema_fixed.sql`
- Postman collection -> `server/sql/CabBooking.postman_collection.json`
- Demo images in `AbrarKivande/`

Currently, two official plugins are available:
## Run locally (Windows 11)
1. Create DB in pgAdmin4: `cab_booking`.
2. In pgAdmin Query Tool run `server/sql/db_schema_fixed.sql`.
3. In `server` folder create `.env` from `.env.example` and set `PGPASSWORD`.
4. Start server:
cd server
npm install
node index.js
5. Start client (if client in repo root or `client` folder):
cd ..\client # or repo root if client present
npm install
npm start
6. Visit `http://localhost:3000`.

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Test steps (quick)
- Add/delete Cab, Route, Schedule in UI.
- Optional: import Postman collection from `server/sql` and test API calls.

## Expanding the ESLint configuration

If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
## Notes
- Do not commit real credentials (.env is ignored).
- If issues occur, check server console for stack traces.
6 changes: 6 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
PGUSER=postgres
PGHOST=localhost
PGDATABASE=cab_booking
PGPASSWORD=your_postgres_password_here
PGPORT=5432
PORT=5000
282 changes: 214 additions & 68 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// server/index.js (FINAL version) - replace your current file with this exact content
const express = require('express');
const cors = require('cors');
const { Pool } = require('pg');
Expand All @@ -6,102 +7,247 @@ const app = express();
app.use(cors());
app.use(express.json());

// DB config - set env vars if you prefer
const pool = new Pool({
user: 'postgres',
host: 'localhost',
database: 'cab_booking',
password: 'kamlesh@2004',
port: 5432,
user: process.env.PGUSER || 'postgres',
host: process.env.PGHOST || 'localhost',
database: process.env.PGDATABASE || 'cab_booking',
password: process.env.PGPASSWORD || '', // <-- update if different
port: process.env.PGPORT ? Number(process.env.PGPORT) : 5432,
});

// Get all cab operators
app.get('/api/cab-operators', async (req, res) => {
const result = await pool.query('SELECT * FROM cab_operators WHERE active = TRUE');
res.json(result.rows);
function logAndSendError(res, err) {
console.error('SERVER ERROR:', err && err.stack ? err.stack : err);
// Always return JSON (so front-end res.json() will not fail)
res.status(500).json({ error: String(err && err.message ? err.message : err) });
}

/* ----------------- Utility helpers ----------------- */

// Parse a "time" input which may be "HH:mm" or an ISO string.
// Returns "HH:mm" string suitable for Postgres TIME.
function normalizeTimeForDb(input) {
if (!input) return null;
// If already HH:mm
if (/^\d{1,2}:\d{2}$/.test(input)) {
const [h, m] = input.split(':').map(Number);
const hh = String(h).padStart(2, '0');
const mm = String(m).padStart(2, '0');
return `${hh}:${mm}`;
}
// Try parsing as ISO / Date
const d = new Date(input);
if (!isNaN(d.getTime())) {
// Use local time hours/minutes
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
return `${hh}:${mm}`;
}
// fallback null
return null;
}

// If the DB schema requires eta_min / base_fare not-null, provide sensible defaults
function defaultEtaAndFare(distanceKm) {
// crude ETA: 2 min per km (round up), min 10
const eta = Math.max(10, Math.ceil((Number(distanceKm) || 0) * 2));
// crude base fare: 1.5 per km, min 10
const fare = Math.max(10, (Number(distanceKm) || 0) * 1.5);
// round fare to 2 decimals
return { eta_min: eta, base_fare: Math.round(fare * 100) / 100 };
}

/* ----------------- Cab operators ----------------- */

app.get('/api/cab-operators', async (_req, res) => {
try {
const r = await pool.query('SELECT * FROM cab_operators WHERE active = TRUE ORDER BY id');
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

// Updated Get all routes
app.get('/api/routes', async (req, res) => {
const result = await pool.query("SELECT id, origin AS from, destination AS to, CONCAT(distance_km, ' km') AS distance FROM routes WHERE active = TRUE");
res.json(result.rows);
/* ----------------- Routes ----------------- */
// Frontend expects GET /api/routes -> array of { id, from, to, distance }
// Frontend POST sends { from, to, distance } (per the UI fields)
app.get('/api/routes', async (_req, res) => {
try {
// Return distance as "xx.xx km" string to match UI formatting
const q = `SELECT id, origin AS "from", destination AS "to", CONCAT(TRIM(TRAILING '.0' FROM TRIM(TO_CHAR(distance_km,'FM99999.99'))),' km') AS distance
FROM routes WHERE active = TRUE ORDER BY id`;
const r = await pool.query(q);
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

// Get reports
app.get('/api/reports', async (req, res) => {
const result = await pool.query('SELECT * FROM reports');
res.json(result.rows);
app.post('/api/routes', async (req, res) => {
try {
const { from, to, distance } = req.body || {};

if (!from || !to || (distance == null || distance === '')) {
return res.status(400).json({ error: 'from, to and distance are required' });
}

// Accept distance either "12.5" or "12.5 km" and coerce to number
const distanceStr = String(distance).replace(/[^0-9.]/g, '');
const distanceKm = Number(distanceStr);
if (Number.isNaN(distanceKm)) return res.status(400).json({ error: 'invalid distance' });

// Provide defaults for eta_min and base_fare if schema requires not-null
const { eta_min, base_fare } = defaultEtaAndFare(distanceKm);

const q = `INSERT INTO routes (origin, destination, distance_km, eta_min, base_fare, active)
VALUES ($1,$2,$3,$4,$5, TRUE)
RETURNING id, origin AS "from", destination AS "to", CONCAT(TO_CHAR(distance_km,'FM99999.99'), ' km') AS distance`;
const vals = [from, to, distanceKm, eta_min, base_fare];
const r = await pool.query(q, vals);
// return the created object (frontend expects object)
res.json(r.rows[0]);
} catch (err) { logAndSendError(res, err); }
});

// Get top revenue
app.get('/api/revenue', async (req, res) => {
const result = await pool.query('SELECT * FROM revenue ORDER BY total_revenue DESC LIMIT 5');
res.json(result.rows);
app.delete('/api/routes/:id', async (req, res) => {
try {
const id = parseInt(req.params.id, 10);
if (!id) return res.status(400).json({ error: 'invalid id' });
await pool.query('DELETE FROM routes WHERE id=$1', [id]);
res.json({ success: true });
} catch (err) { logAndSendError(res, err); }
});

// Get all vendors
app.get('/api/vendors', async (req, res) => {
const result = await pool.query('SELECT * FROM vendors');
res.json(result.rows);
/* ----------------- Cabs ----------------- */

app.get('/api/cabs', async (_req, res) => {
try {
const r = await pool.query('SELECT * FROM cabs ORDER BY id');
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

// Get all customers
app.get('/api/customers', async (req, res) => {
const result = await pool.query('SELECT * FROM customers');
res.json(result.rows);
app.post('/api/cabs', async (req, res) => {
try {
const { name, type, seats } = req.body || {};
if (!name || !type || !seats) return res.status(400).json({ error: 'name,type,seats required' });
const r = await pool.query('INSERT INTO cabs (name, type, seats) VALUES ($1,$2,$3) RETURNING *', [name, type, Number(seats)]);
res.json(r.rows[0]);
} catch (err) { logAndSendError(res, err); }
});

// Get all cabs
app.get('/api/cabs', async (req, res) => {
const result = await pool.query('SELECT * FROM cabs');
res.json(result.rows);
app.delete('/api/cabs/:id', async (req, res) => {
try {
const id = parseInt(req.params.id, 10);
if (!id) return res.status(400).json({ error: 'invalid id' });
await pool.query('DELETE FROM cabs WHERE id=$1', [id]);
res.json({ success: true });
} catch (err) { logAndSendError(res, err); }
});

// Updated Get all schedules
app.get('/api/schedules', async (req, res) => {
const result = await pool.query("SELECT s.id, c.name AS cab, CONCAT(r.origin, ' - ', r.destination) AS route, s.frequency, s.time, s.price FROM schedules s JOIN cabs c ON s.cab_id = c.id JOIN routes r ON s.route_id = r.id");
res.json(result.rows);
/* ----------------- Schedules ----------------- */
// GET returns rows shaped: { id, cab, route, frequency, time, price }
app.get('/api/schedules', async (_req, res) => {
try {
const q = `SELECT s.id, c.name AS cab, CONCAT(r.origin,' - ', r.destination) AS route,
s.frequency, TO_CHAR(s.time,'HH24:MI') AS time, s.price
FROM schedules s
JOIN cabs c ON s.cab_id = c.id
JOIN routes r ON s.route_id = r.id
ORDER BY s.id`;
const r = await pool.query(q);
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

// Add a cab
app.post('/api/cabs', async (req, res) => {
const { name, type, seats } = req.body;
const result = await pool.query('INSERT INTO cabs (name, type, seats) VALUES ($1, $2, $3) RETURNING *', [name, type, seats]);
res.json(result.rows[0]);
// POST expects frontend sends: { cab: <name>, route: "<origin> - <destination>", frequency, time, price }
// Accepts time as "HH:mm" or an ISO timestamp and converts to "HH:mm" for DB.
app.post('/api/schedules', async (req, res) => {
try {
const { cab, route, frequency, time, price } = req.body || {};
if (!cab || !route || !frequency || !time || price == null) {
return res.status(400).json({ error: 'cab, route, frequency, time and price required' });
}

// Look up cab_id by name (frontend uses cab name)
const cabRes = await pool.query('SELECT id FROM cabs WHERE name=$1 LIMIT 1', [cab]);
if (!cabRes.rows[0]) return res.status(400).json({ error: `Cab not found: ${cab}` });
const cabId = cabRes.rows[0].id;

// Look up route_id by splitting route string or by matching origin/destination
// route value format is usually "Origin - Destination"
let routeId = null;
// Try parsing "Origin - Destination"
const parts = String(route).split(' - ').map(p => p.trim());
if (parts.length === 2) {
const [originPart, destPart] = parts;
const rRes = await pool.query('SELECT id FROM routes WHERE origin = $1 AND destination = $2 LIMIT 1', [originPart, destPart]);
if (rRes.rows[0]) routeId = rRes.rows[0].id;
}
// If still not found, try searching by concatenation fallback
if (!routeId) {
const rRes2 = await pool.query("SELECT id FROM routes WHERE (origin || ' - ' || destination) = $1 LIMIT 1", [route]);
if (rRes2.rows[0]) routeId = rRes2.rows[0].id;
}
if (!routeId) return res.status(400).json({ error: `Route not found: ${route}` });

// Normalize time input
const normalizedTime = normalizeTimeForDb(String(time));
if (!normalizedTime) return res.status(400).json({ error: 'invalid time format' });

// Insert schedule
const insertQ = `INSERT INTO schedules (cab_id, route_id, frequency, time, price)
VALUES ($1,$2,$3,$4,$5) RETURNING id`;
const insertVals = [cabId, routeId, frequency, normalizedTime, Number(price)];
const ins = await pool.query(insertQ, insertVals);

// Respond with object shaped like frontend expects
res.json({
id: ins.rows[0].id,
cab,
route,
frequency,
time: normalizedTime,
price: Number(price)
});
} catch (err) { logAndSendError(res, err); }
});

// Delete a cab
app.delete('/api/cabs/:id', async (req, res) => {
const { id } = req.params;
await pool.query('DELETE FROM cabs WHERE id = $1', [id]);
res.json({ success: true });
app.delete('/api/schedules/:id', async (req, res) => {
try {
const id = parseInt(req.params.id, 10);
if (!id) return res.status(400).json({ error: 'invalid id' });
await pool.query('DELETE FROM schedules WHERE id=$1', [id]);
res.json({ success: true });
} catch (err) { logAndSendError(res, err); }
});

// Add a route
app.post('/api/routes', async (req, res) => {
const { origin, destination, distance_km, eta_min, base_fare, active, cab_operator_id } = req.body;
const result = await pool.query('INSERT INTO routes (origin, destination, distance_km, eta_min, base_fare, active, cab_operator_id) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *', [origin, destination, distance_km, eta_min, base_fare, active, cab_operator_id]);
res.json(result.rows[0]);
/* ----------------- Reports / Revenue / Vendors / Customers ----------------- */

app.get('/api/reports', async (_req, res) => {
try {
const r = await pool.query('SELECT * FROM reports ORDER BY date DESC LIMIT 100');
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

// Delete a route
app.delete('/api/routes/:id', async (req, res) => {
const { id } = req.params;
await pool.query('DELETE FROM routes WHERE id = $1', [id]);
res.json({ success: true });
app.get('/api/revenue', async (_req, res) => {
try {
const r = await pool.query('SELECT * FROM revenue ORDER BY total_revenue DESC LIMIT 10');
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

// Add a schedule
app.post('/api/schedules', async (req, res) => {
const { cab_id, route_id, frequency, time, price } = req.body;
const result = await pool.query('INSERT INTO schedules (cab_id, route_id, frequency, time, price) VALUES ($1, $2, $3, $4, $5) RETURNING *', [cab_id, route_id, frequency, time, price]);
res.json(result.rows[0]);
app.get('/api/vendors', async (_req, res) => {
try {
const r = await pool.query('SELECT id, name, contact, address, kyc_status AS "kycStatus" FROM vendors ORDER BY id');
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

// Delete a schedule
app.delete('/api/schedules/:id', async (req, res) => {
const { id } = req.params;
await pool.query('DELETE FROM schedules WHERE id = $1', [id]);
res.json({ success: true });
app.get('/api/customers', async (_req, res) => {
try {
const r = await pool.query('SELECT * FROM customers ORDER BY id');
res.json(r.rows);
} catch (err) { logAndSendError(res, err); }
});

app.listen(5000, () => console.log('Server running on port 5000'));
/* ----------------- Start server ----------------- */
const PORT = process.env.PORT ? Number(process.env.PORT) : 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Loading