Skip to content

feat(transport): add bus route management with capacity-checked student assignments (fixes #262) - #267

Open
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-262-transport-management
Open

feat(transport): add bus route management with capacity-checked student assignments (fixes #262)#267
MOHITKOURAV01 wants to merge 2 commits into
Sitaram8472:mainfrom
MOHITKOURAV01:feat/issue-262-transport-management

Conversation

@MOHITKOURAV01

Copy link
Copy Markdown
Contributor

Related Issue

Closes #262

Description

Adds the transport module the project was missing entirely — routes, stops, vehicle and driver details, and per-student assignments with capacity that is actually enforced.

A student opening /transport sees their bus as a card (route code, driver name and a tappable phone number, vehicle number, their own pickup and drop stop with times) and the full stop timeline with their stop highlighted. A student with no assignment gets the route catalogue instead, searchable by the stop nearest their home, so they can tell the office which route they need.

The transport office gets a Dashboard tab: create and edit routes, reorder the stop list inline, watch a seats-occupied meter per route, assign or remove students, and open a roster grouped by stop in the order the bus actually reaches them.

Things worth a reviewer's attention

Occupancy cannot drift. seatsOccupied is recomputed from the TransportAssignment collection on every write rather than incremented in place, and the capacity check re-derives it immediately before saving. A stale counter cannot let one extra child onto a full bus. There is also an admin-only POST /recompute-occupancy repair hatch that rewrites every route's counter from the source data.

Stop sequences must be contiguous. The model rejects duplicate sequence numbers and gaps. The gap check matters because the timeline renders "stop 3 of 7" — a gap would silently mislabel every stop after it. resequenceStops() renumbers from array order so the UI never hand-maintains sequences.

Editing stops cannot strand a rider. PUT /routes/:id/stops replaces the whole list, but first checks every live assignment: if the new list drops a stop somebody boards at, it returns 409 naming the affected students rather than leaving them assigned to a stop the bus no longer visits.

One live assignment per student, enforced by a partial unique index on the database rather than an application check, so two concurrent requests cannot both succeed. Retiring or deleting a route with active assignments is refused.

Times are "HH:MM" strings, not Dates. A stop's pickup time is a recurring wall-clock time, not an instant — a Date would force an arbitrary date part and break the moment the server and the school sit in different timezones.

A note on Mongoose 9

While testing this I found that Mongoose 9 has dropped callback-style middleware. A hook written as pre('validate', function (next) { ... }) is silently skipped — the guards inside it never run, and any call to next(err) throws next is not a function. All hooks here are written as async functions that throw, which is the supported form. Worth knowing before anyone adds a hook elsewhere in backend/models/, since the failure is silent.

Pages / Components Added or Modified

  • backend/models/BusRoute.js — new; embedded stop array, sequence validation, occupancy virtuals
  • backend/models/TransportAssignment.js — new; partial unique index on the active assignment
  • backend/controllers/transportController.js — new; 12 handlers
  • backend/routes/transportRoutes.js — new; mounted at /api/transport
  • backend/server.js — two lines to register the router
  • frontend/src/pages/Transport.jsx — new; student-facing page at /transport
  • frontend/src/components/teacher/TransportPanel.jsx — new; Dashboard tab
  • frontend/src/App.jsx — lazy import plus the /transport route
  • frontend/src/pages/TeacherDashboard.jsx — one tab entry

Verification

  • node --check passes on every new backend file
  • npx eslint is clean on all new frontend files (the one pre-existing role is assigned but never used warning in TeacherDashboard.jsx is on main already and is untouched here)
  • Model guards exercised directly against in-memory documents: duplicate sequence, gap in sequence, capacity below current occupancy, malformed HH:MM, out-of-range latitude, single-stop route, identical pickup/drop, and end-date-before-start all reject with the intended message; the happy path and resequenceStops() behave as expected

Checklist

  • Lint passes with no new errors
  • Backend model and route guards exercised directly
  • Tested against a live MongoDB instance — I do not have the project's database credentials, so the HTTP layer has not been run end to end
  • Responsive layout (grid collapses to one column on mobile; the roster and dialogs scroll within max-h-[90vh])
  • No new console errors or warnings
  • Screenshots — omitted, since the pages need seeded route and assignment data to show anything meaningful

Notes

Purely additive. No existing model, controller, route or component is modified beyond registering the new router and adding the page route. Capacity is enforced in one place, so a future waiting-list feature has one obvious hook.

…nments

Adds a transport module covering routes, stops, vehicle and driver details,
and per-student assignments.

BusRoute embeds an ordered stop list and validates that sequences are unique
and contiguous from 1, so the "stop 3 of 7" labelling in the timeline cannot
silently mislabel stops after a gap. TransportAssignment carries a partial
unique index on an active status, which stops two concurrent requests from
both giving a student a live assignment.

Seat occupancy is recomputed from the assignment collection on every write
rather than incremented in place, and the capacity check re-derives it
immediately before saving, so a stale counter cannot let an extra child onto
a full bus. Retiring or deleting a route with live assignments is refused,
and replacing the stop list is refused when it would strand an assigned
student at a stop the bus no longer visits.

Students read only their own assignment; the office holds every write path.

Closes Sitaram8472#262
@MOHITKOURAV01

Copy link
Copy Markdown
Contributor Author

Merge note for whoever lands these

This PR is one of five sibling feature PRs (#267#271). Each is independent in its own right — separate models, controllers, routers, pages and panels, with no shared logic — but all five register themselves in the same three files:

  • backend/server.js — one require and one app.use
  • frontend/src/App.jsx — one lazy import and one <Route>
  • frontend/src/pages/TeacherDashboard.jsx — one import, one tab entry, one render line

So the first of the five to merge will go in clean, and the remaining four will then conflict in exactly those three files. Nothing else conflicts.

I simulated the full sequential merge locally and verified the combined result. Two things worth passing on:

  1. The resolution is "keep both sides" in all cases — the additions are independent and order does not matter.

  2. App.jsx needs care. A naive keep-both resolution (or a union merge driver) silently produces invalid JSX: each route block ends with the same two lines

        </RoleProtectedRoute>
      } />

    which get treated as shared trailing context and kept only once, so the earlier routes lose their closing tags. The build then fails with Unexpected closing "Routes" tag does not match opening "RoleProtectedRoute" tag. Each <Route> needs its own closing pair. I hit this exactly while testing, so it is worth knowing before it looks like one of the PRs is broken.

Verified on the fully merged tree (all five together):

  • node --check backend/server.js passes; all five routers load and all nine new models register with no name collisions
  • npx vite build succeeds with all five pages code-split into their own chunks
  • npx eslint src/ reports 18 problems on the merged tree and 18 on main — these five modules add none

Happy to rebase and push the resolution on this branch as soon as the first sibling lands; just say which order you want them in.

The route search passed the raw query string to `new RegExp`, so a search term
was silently reinterpreted as a pattern.

Three consequences. A search of `.*` matched every route rather than routes
containing that text. A term like `(a+)+$` triggered catastrophic backtracking
— measured at roughly two minutes of pinned CPU for a 33-character query
string, which any signed-in student could send. An unbalanced `[` threw out of
the handler and surfaced as a 500 instead of a bad-request.

Metacharacters are now escaped so the term matches literally, which is what
someone typing into a search box expects, and the term is capped at 80
characters.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Transport & Bus Route Management with capacity-checked student assignments

1 participant