Skip to content

SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend#62

Merged
promatty merged 11 commits intomainfrom
SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend
Apr 14, 2026
Merged

SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend#62
promatty merged 11 commits intomainfrom
SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend

Conversation

@wesleylui
Copy link
Copy Markdown
Contributor

@wesleylui wesleylui commented Mar 1, 2026

leftover from SANC58-vehicle-logs-frontend:

  • fixed log.id bug by adding id to the forms initialValues so the update mutation can target the correct row

SANC60:

  • tRPC router for vehicle logs already existed (was doing getAll)
  • added create, update, and delete mutation
  • added error handling and loading states on modals
  • all procedures are adminProcedure, so they’re already gated behind an admin role check. non-admins would get an unauthorized error automatically

Summary by CodeRabbit

  • New Features

    • Admin Vehicles page and APIs to create, edit, view, and soft-delete vehicles
    • Vehicle selection added to vehicle logs and trip survey forms
    • Vehicle-based filtering for vehicle logs
  • Refactor

    • Vehicle logs UI now shows dedicated loading and error states; improved modal/mutation flows and form edit mode
    • Surveys now use transactional creation that may auto-create related driver logs
  • Bug Fixes

    • Odometer check relaxed to allow equal start/end values
  • Chores

    • Database schema, seeding, and navbar updated to support vehicles

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
salvationarmy Ready Ready Preview, Comment Apr 14, 2026 1:58am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 97011111-492f-4ff2-aedf-87c876cd6118

📥 Commits

Reviewing files that changed from the base of the PR and between 5f4adee and 5ce6ed3.

📒 Files selected for processing (1)
  • src/server/api/routers/vehicle-logs.ts

📝 Walkthrough

Walkthrough

Adds a vehicles feature (DB schema, router, seeding, hook, admin UI), integrates vehicle selection into surveys and vehicle logs, implements transactional survey creation that can auto-insert a driver log, and expands vehicle-logs API with filtering plus create/update/delete mutations.

Changes

Cohort / File(s) Summary
Vehicles DB & API
src/server/db/vehicles-schema.ts, src/server/api/routers/vehicles.ts, src/server/api/root.ts
New vehicles Drizzle table and vehiclesRouter with getAll, getAllAdmin, create, update, delete; router registered on appRouter.
Client: admin Vehicles UI & hook
src/app/admin/vehicles/page.tsx, src/app/hooks/useVehicles.ts
New admin Vehicles page with add/edit modals and mutations; useVehicles() returns { vehicleOptions, isLoading, hasDbVehicles }.
Vehicle logs UI & admin page
src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx, src/app/_components/vehiclelogcomponents/vehicle-log-table-view.tsx, src/app/_components/vehiclelogcomponents/vehicle-log-table-view.module.scss, src/app/admin/vehicle-logs/page.tsx
Form model adds id; vehicle input switched to Select wired to useVehicles(); table view now handles loading/error with Loader/Alert and new SCSS classes; admin page uses create/update mutations and mutation-driven modal logic.
Vehicle logs server API
src/server/api/routers/vehicle-logs.ts
getAll now accepts optional filters (vehicle, driverName, dateFrom, dateTo); added create, update, delete mutations with Zod validation, refinements, session-driven createdBy/updatedBy, and explicit error handling.
Survey UI changes
src/app/_components/drivercomponents/TripSurveyModal.tsx, src/app/_components/drivercomponents/surveyForm/surveyForm.tsx, src/app/_components/drivercomponents/surveyNotification/surveyNotification.tsx
Survey form state gains vehicle; forms add Select wired to useVehicles() and disable when cancelled or loading; notification flow includes vehicle in payload for non-completed path and adds validation.
Survey server & DB flow
src/server/api/routers/surveys.ts, src/server/db/post-trip-schema.ts, src/server/db/vehicle-log.ts
postTripSurveys gains vehicle column. Survey create accepts optional vehicle, requires it when not CANCELLED, and runs a DB transaction: insert survey, conditionally insert driver logs row (with driver lookup/validation), and update booking surveyCompleted. logs odometer check relaxed to >=.
Drizzle config & seeding
drizzle.config.ts, src/server/db/index.ts, drizzle/seed.ts
Register vehicles schema in Drizzle config/index and add --vehicles seeding path plus seedVehicles() that creates table and inserts default vehicles when missing.
Navbar update
src/app/_components/common/navbar.tsx
Admin navbar: "Vehicle Logs" now points to /admin/vehicle-logs and a new "View Vehicles" link to /admin/vehicles was added.

Sequence Diagram

sequenceDiagram
    actor Driver
    participant Client as Client (Browser)
    participant SurveyAPI as Survey API
    participant DB as Database

    Driver->>Client: submit trip survey (includes vehicle)
    Client->>SurveyAPI: call create survey
    SurveyAPI->>DB: BEGIN transaction
    SurveyAPI->>DB: INSERT postTripSurveys (with vehicle)
    DB-->>SurveyAPI: survey row
    alt trip not CANCELLED
        SurveyAPI->>DB: lookup assigned driver (user)
        DB-->>SurveyAPI: driver record
        SurveyAPI->>DB: INSERT logs row (auto-created from survey + booking)
        DB-->>SurveyAPI: log row
    end
    SurveyAPI->>DB: UPDATE bookings SET surveyCompleted = true
    DB-->>SurveyAPI: booking updated
    SurveyAPI->>DB: COMMIT
    DB-->>SurveyAPI: transaction complete
    SurveyAPI-->>Client: return survey (and log)
    Client->>Client: invalidate caches / show notification
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • themaxboucher
  • JustinTan-1
  • burtonjong
  • promatty
  • Lujarios
  • Yemyam

Poem

🐰 I hopped through tables, APIs, and forms,

I added Selects where data swarms.
Surveys now log with a transactional cheer,
Vehicles listed, admin near —
Hop, click, submit — the logs appear!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main objective: creating vehicle logs API endpoints and connecting them to the frontend.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/admin/vehicle-logs/page.tsx (1)

74-83: ⚠️ Potential issue | 🟠 Major

Odometer validation/submission is inconsistent with backend constraints.

Frontend permits equal values and silently rounds decimals, while backend enforces strict integer odometerEnd > odometerStart. This can pass client validation but still fail mutation (or mutate values unexpectedly).

💡 Suggested alignment with backend rules
       odometerStart: (value) => {
         if (value.trim().length === 0) return "Odometer start is required";
         const num = Number.parseFloat(value);
         if (Number.isNaN(num)) return "Must be a valid number";
-        if (num < 0) return "Must be a positive number";
+        if (num < 0) return "Must be a non-negative number";
+        if (!Number.isInteger(num)) return "Must be a whole number";
         return null;
       },
       odometerEnd: (value, values) => {
         if (value.trim().length === 0) return "Odometer end is required";
         const num = Number.parseFloat(value);
         if (Number.isNaN(num)) return "Must be a valid number";
-        if (num < 0) return "Must be a positive number";
+        if (num < 0) return "Must be a non-negative number";
+        if (!Number.isInteger(num)) return "Must be a whole number";
         const start = Number.parseFloat(values.odometerStart);
-        if (!Number.isNaN(start) && num < start) {
-          return "End reading must be greater than or equal to start reading";
+        if (!Number.isNaN(start) && num <= start) {
+          return "End reading must be greater than start reading";
         }
         return null;
       },
@@
-    const odometerStart = Math.round(Number.parseFloat(values.odometerStart));
-    const odometerEnd = Math.round(Number.parseFloat(values.odometerEnd));
+    const odometerStart = Number(values.odometerStart);
+    const odometerEnd = Number(values.odometerEnd);

Also applies to: 122-123

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin/vehicle-logs/page.tsx` around lines 74 - 83, The odometerEnd
validation in page.tsx currently allows equal values and accepts decimals which
frontend may round, causing backend failures; update the odometerEnd validator
to require an integer (no decimals) and enforce strictly odometerEnd >
odometerStart (not >=), and mirror the same integer constraint in the
odometerStart validation so both client-side checks match the backend rule;
ensure submission/parsing uses integer parsing (or rejects non-integer input)
instead of implicit float rounding so the mutation receives the exact integer
values the backend expects.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/admin/vehicle-logs/page.tsx`:
- Line 18: The VehicleLogsPage component should be moved from the current route
to the canonical route: create a new page at /admin/driver-logs with the same
implementation (or move the file and adjust path), rename the component/export
to DriverLogsPage (or keep default export but update references) and update any
imports/usages and the navbar link to point to /admin/driver-logs; ensure there
are no remaining placeholders at /admin/driver-logs and remove or redirect the
old /admin/vehicle-logs route so the feature lives only under the canonical path
(update route references in route tables, navigation components, and tests
accordingly).

In `@src/server/api/routers/vehicle-logs.ts`:
- Around line 139-157: The update schema's .refine checks for odometer and time
pairs allow single-field updates (e.g., only odometerEnd or arrivalTime) to
bypass validation; modify the same refine logic in the update input schema so
that if one of a pair is provided the other must also be provided and then
enforce the ordering (for odometer: require odometerStart when odometerEnd is
present and check odometerEnd > odometerStart; for times: require departureTime
when arrivalTime is present and check new Date(arrivalTime) > new
Date(departureTime)); update the two .refine blocks that reference
odometerEnd/odometerStart and arrivalTime/departureTime to implement these
presence+ordering guards so partial single-field edits no longer bypass
validation.
- Around line 15-16: The dateFrom and dateTo fields in the input Zod schema
accept any string and must be validated to avoid DB errors; update the Zod
schema (the schema where dateFrom and dateTo are defined) to use a stricter
validator such as z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().refine(v =>
!v || !Number.isNaN(Date.parse(v)), { message: "Invalid ISO date (YYYY-MM-DD)"
}) so malformed values are rejected before SQL is built, then convert the
validated strings to Date (or normalized ISO) when you build SQL predicates;
also add a cross-field check to ensure dateFrom <= dateTo and apply the same
validation change to the other identical schema block referenced (lines 29-34).

---

Outside diff comments:
In `@src/app/admin/vehicle-logs/page.tsx`:
- Around line 74-83: The odometerEnd validation in page.tsx currently allows
equal values and accepts decimals which frontend may round, causing backend
failures; update the odometerEnd validator to require an integer (no decimals)
and enforce strictly odometerEnd > odometerStart (not >=), and mirror the same
integer constraint in the odometerStart validation so both client-side checks
match the backend rule; ensure submission/parsing uses integer parsing (or
rejects non-integer input) instead of implicit float rounding so the mutation
receives the exact integer values the backend expects.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00c8326 and 51e0e99.

📒 Files selected for processing (5)
  • src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx
  • src/app/_components/vehiclelogcomponents/vehicle-log-table-view.module.scss
  • src/app/_components/vehiclelogcomponents/vehicle-log-table-view.tsx
  • src/app/admin/vehicle-logs/page.tsx
  • src/server/api/routers/vehicle-logs.ts

import { notify } from "@/lib/notifications";
import { api } from "@/trpc/react";

export default function VehicleLogsPage() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify canonical admin route usage for driver/vehicle logs
fd -t f 'navbar.tsx|page.tsx' src/app | head -20
rg -n '/admin/(driver-logs|vehicle-logs)' src/app src/lib --type tsx --type ts

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 696


🏁 Script executed:

#!/bin/bash
# Search for route patterns without file type flags
rg -n '/admin/(driver-logs|vehicle-logs)' src/app src/lib
# Also check navbar for navigation links
rg -n 'driver-logs|vehicle-logs' src/app/_components/common/navbar.tsx -A 2 -B 2

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 379


🏁 Script executed:

#!/bin/bash
# Check the content of both page files to understand their purpose
echo "=== /admin/driver-logs/page.tsx ==="
cat src/app/admin/driver-logs/page.tsx

echo -e "\n=== /admin/vehicle-logs/page.tsx ==="
cat src/app/admin/vehicle-logs/page.tsx

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 6723


Move this page to /admin/driver-logs to match the canonical route for this project.

The fully functional vehicle logs page is currently at /admin/vehicle-logs, but the canonical route for this feature is /admin/driver-logs. The navbar should link to /admin/driver-logs, and the placeholder at that location should be replaced with the actual implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin/vehicle-logs/page.tsx` at line 18, The VehicleLogsPage
component should be moved from the current route to the canonical route: create
a new page at /admin/driver-logs with the same implementation (or move the file
and adjust path), rename the component/export to DriverLogsPage (or keep default
export but update references) and update any imports/usages and the navbar link
to point to /admin/driver-logs; ensure there are no remaining placeholders at
/admin/driver-logs and remove or redirect the old /admin/vehicle-logs route so
the feature lives only under the canonical path (update route references in
route tables, navigation components, and tests accordingly).

Comment on lines +15 to +16
dateFrom: z.string().optional(), // ISO date string "YYYY-MM-DD"
dateTo: z.string().optional(), // ISO date string "YYYY-MM-DD"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate dateFrom / dateTo format before building SQL predicates.

These fields currently accept any string. Malformed values can surface as DB errors instead of clean input errors.

💡 Suggested hardening
   getAll: adminProcedure
     .input(
       z
         .object({
           vehicle: z.string().optional(),
           driverName: z.string().optional(),
-          dateFrom: z.string().optional(), // ISO date string "YYYY-MM-DD"
-          dateTo: z.string().optional(), // ISO date string "YYYY-MM-DD"
+          dateFrom: z
+            .string()
+            .regex(/^\d{4}-\d{2}-\d{2}$/, "dateFrom must be YYYY-MM-DD")
+            .optional(),
+          dateTo: z
+            .string()
+            .regex(/^\d{4}-\d{2}-\d{2}$/, "dateTo must be YYYY-MM-DD")
+            .optional(),
         })
+        .refine((v) => !v.dateFrom || !v.dateTo || v.dateFrom <= v.dateTo, {
+          message: "dateFrom must be on or before dateTo",
+          path: ["dateTo"],
+        })
         .optional(),
     )

Also applies to: 29-34

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/vehicle-logs.ts` around lines 15 - 16, The dateFrom
and dateTo fields in the input Zod schema accept any string and must be
validated to avoid DB errors; update the Zod schema (the schema where dateFrom
and dateTo are defined) to use a stricter validator such as
z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().refine(v => !v ||
!Number.isNaN(Date.parse(v)), { message: "Invalid ISO date (YYYY-MM-DD)" }) so
malformed values are rejected before SQL is built, then convert the validated
strings to Date (or normalized ISO) when you build SQL predicates; also add a
cross-field check to ensure dateFrom <= dateTo and apply the same validation
change to the other identical schema block referenced (lines 29-34).

Comment on lines +139 to +157
.refine(
(data) => {
if (data.odometerEnd !== undefined && data.odometerStart !== undefined) {
return data.odometerEnd > data.odometerStart;
}
return true;
},
{
message: "Odometer end must be greater than odometer start",
path: ["odometerEnd"],
},
)
.refine(
(data) => {
if (data.arrivalTime !== undefined && data.departureTime !== undefined) {
return new Date(data.arrivalTime) > new Date(data.departureTime);
}
return true;
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

update allows single-field time/odometer edits that can bypass app validation.

If only one field in a pair is sent (e.g., only odometerEnd), current refinements skip pair checks and failures are deferred to DB constraints.

💡 Suggested guardrails
         .object({
           id: z.number().int().positive(),
           date: z.string().min(1).optional(),
           travelLocation: z.string().min(1).optional(),
           departureTime: z.string().min(1).optional(),
           arrivalTime: z.string().min(1).optional(),
           odometerStart: z.number().int().nonnegative().optional(),
           odometerEnd: z.number().int().nonnegative().optional(),
           driverId: z.string().min(1).optional(),
           driverName: z.string().min(1).optional(),
           vehicle: z.string().min(1).optional(),
         })
+        .refine(
+          (data) => (data.odometerStart === undefined) === (data.odometerEnd === undefined),
+          {
+            message: "Provide both odometerStart and odometerEnd together",
+            path: ["odometerEnd"],
+          },
+        )
+        .refine(
+          (data) => (data.departureTime === undefined) === (data.arrivalTime === undefined),
+          {
+            message: "Provide both departureTime and arrivalTime together",
+            path: ["arrivalTime"],
+          },
+        )
         .refine(
           (data) => {
             if (data.odometerEnd !== undefined && data.odometerStart !== undefined) {
               return data.odometerEnd > data.odometerStart;
             }
             return true;
           },

Also applies to: 168-172

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/vehicle-logs.ts` around lines 139 - 157, The update
schema's .refine checks for odometer and time pairs allow single-field updates
(e.g., only odometerEnd or arrivalTime) to bypass validation; modify the same
refine logic in the update input schema so that if one of a pair is provided the
other must also be provided and then enforce the ordering (for odometer: require
odometerStart when odometerEnd is present and check odometerEnd > odometerStart;
for times: require departureTime when arrivalTime is present and check new
Date(arrivalTime) > new Date(departureTime)); update the two .refine blocks that
reference odometerEnd/odometerStart and arrivalTime/departureTime to implement
these presence+ordering guards so partial single-field edits no longer bypass
validation.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/server/api/routers/surveys.ts (1)

386-407: Survey updates don't sync to the auto-created vehicle log.

When a survey is created, an associated vehicle log is auto-created. However, the update mutation doesn't modify the corresponding log entry. If a driver corrects odometer readings or times, the vehicle log will have stale data.

Consider whether this is the intended behavior. If logs should stay in sync:

  1. Add vehicle log updates within the survey update mutation, or
  2. Store a reference (e.g., surveyId in logs) to enable future syncing
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/surveys.ts` around lines 386 - 407, The survey update
mutation (update: protectedProcedure in src/server/api/routers/surveys.ts)
currently updates only the survey row, leaving the auto-created vehicle log out
of sync; modify this mutation to also locate and update the associated vehicle
log (by existing linkage or by adding/using a surveyId reference on the
vehicle_logs table) with any changed fields (startReading, endReading,
timeOfDeparture, timeOfArrival, destinationAddress, originalLocationChanged,
etc.), perform both updates in a single transaction/atomic operation, and if
vehicle logs lack a stable link to surveys add a surveyId to vehicle logs and
backfill/create the reference so future syncs are possible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/_components/drivercomponents/surveyForm/surveyForm.tsx`:
- Around line 185-196: The Vehicle Select in surveyForm.tsx should be disabled
when the trip is cancelled to match TripSurveyModal.tsx and validation logic;
update the Select (the instance with props key={form.key("vehicle")} and
{...form.getInputProps("vehicle")}, data={AVAILABLE_VEHICLES}) to include a
disabled prop set when tripCompletionStatus === BookingStatus.CANCELLED (ensure
BookingStatus and tripCompletionStatus are in scope or passed into the
component) so the UI behavior matches the validation in surveyNotification.tsx.

---

Nitpick comments:
In `@src/server/api/routers/surveys.ts`:
- Around line 386-407: The survey update mutation (update: protectedProcedure in
src/server/api/routers/surveys.ts) currently updates only the survey row,
leaving the auto-created vehicle log out of sync; modify this mutation to also
locate and update the associated vehicle log (by existing linkage or by
adding/using a surveyId reference on the vehicle_logs table) with any changed
fields (startReading, endReading, timeOfDeparture, timeOfArrival,
destinationAddress, originalLocationChanged, etc.), perform both updates in a
single transaction/atomic operation, and if vehicle logs lack a stable link to
surveys add a surveyId to vehicle logs and backfill/create the reference so
future syncs are possible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5de246d2-9d69-448a-82cd-c84305ce8660

📥 Commits

Reviewing files that changed from the base of the PR and between 51e0e99 and fb2b073.

📒 Files selected for processing (8)
  • src/app/_components/common/navbar.tsx
  • src/app/_components/drivercomponents/TripSurveyModal.tsx
  • src/app/_components/drivercomponents/surveyForm/surveyForm.tsx
  • src/app/_components/drivercomponents/surveyNotification/surveyNotification.tsx
  • src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx
  • src/constants/vehicles.ts
  • src/server/api/routers/surveys.ts
  • src/server/db/post-trip-schema.ts
✅ Files skipped from review due to trivial changes (3)
  • src/app/_components/common/navbar.tsx
  • src/server/db/post-trip-schema.ts
  • src/constants/vehicles.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx

Comment on lines +185 to +196
<div className={styles.formRow}>
<Select
withAsterisk
label="Vehicle"
placeholder="Select a vehicle"
data={AVAILABLE_VEHICLES}
searchable
key={form.key("vehicle")}
{...form.getInputProps("vehicle")}
error={form.errors.vehicle}
/>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent disabled state for Vehicle field when trip is cancelled.

In TripSurveyModal.tsx (line 111), the Vehicle <Select> is disabled when tripCompletionStatus === BookingStatus.CANCELLED, but this implementation lacks that behavior. The validation in surveyNotification.tsx skips vehicle validation for cancelled trips, so the UI should match by disabling the field.

Proposed fix
         <Select
           withAsterisk
           label="Vehicle"
           placeholder="Select a vehicle"
           data={AVAILABLE_VEHICLES}
           searchable
           key={form.key("vehicle")}
           {...form.getInputProps("vehicle")}
           error={form.errors.vehicle}
+          disabled={form.values.tripCompletionStatus === BookingStatus.CANCELLED}
         />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/_components/drivercomponents/surveyForm/surveyForm.tsx` around lines
185 - 196, The Vehicle Select in surveyForm.tsx should be disabled when the trip
is cancelled to match TripSurveyModal.tsx and validation logic; update the
Select (the instance with props key={form.key("vehicle")} and
{...form.getInputProps("vehicle")}, data={AVAILABLE_VEHICLES}) to include a
disabled prop set when tripCompletionStatus === BookingStatus.CANCELLED (ensure
BookingStatus and tripCompletionStatus are in scope or passed into the
component) so the UI behavior matches the validation in surveyNotification.tsx.

@wesleylui
Copy link
Copy Markdown
Contributor Author

Changes:

  • added vehicle as a dropdown select to vehicle log forms and driver survey forms
  • changed admin navbar routing for Vehicle Logs button to /vehicle-logs
  • vehicle log is auto created after a driver completes a survey with all relevant data (odomoter readings, times, driver info)
  • wrapped all database operations (survey insert, log insert, and booking update) to ensure the transactions are atomic

Note: To make the vehicle dropdown to select from the list of Salvation Army cars, i added it to vehicles.ts in /constants. I've hardcoded Ford Expedition CTW 2276 in for now. How were thinking of doing this? Do we want to allow admins to add/manage the list of vehicles through a dashboard? Or load the vehicles from the database? Pls lmk :D

@promatty
Copy link
Copy Markdown
Contributor

Changes:

  • added vehicle as a dropdown select to vehicle log forms and driver survey forms
  • changed admin navbar routing for Vehicle Logs button to /vehicle-logs
  • vehicle log is auto created after a driver completes a survey with all relevant data (odomoter readings, times, driver info)
  • wrapped all database operations (survey insert, log insert, and booking update) to ensure the transactions are atomic

Note: To make the vehicle dropdown to select from the list of Salvation Army cars, i added it to vehicles.ts in /constants. I've hardcoded Ford Expedition CTW 2276 in for now. How were thinking of doing this? Do we want to allow admins to add/manage the list of vehicles through a dashboard? Or load the vehicles from the database? Pls lmk :D

lets actually just store some hardcoded vehicles in the database for now, and pull from there. lets also add a new tab for admins to add vehicles to the db. sorry for the scope creep 😅

Comment thread src/server/api/routers/vehicle-logs.ts Outdated
driverName: z.string().min(1, "Driver name is required"),
vehicle: z.string().min(1, "Vehicle is required"),
})
.refine((data) => data.odometerEnd > data.odometerStart, {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in ./src/app/admin/vehicle-logs/page.tsx we allow odometerEnd >= odometerStart. lets make this consistent

@wesleylui
Copy link
Copy Markdown
Contributor Author

wesleylui commented Apr 1, 2026

Changes:

  • Add vehicle table to database
  • Add admin/vehicles (and tab in navbar) for admins to manage vehicles including:
  • Editing vehicle names, editing vehicle status, adding new vehicles
  • Switched vehicle select fields to use database data instead of the hardcoded vehicles.ts in constants (which I removed)
    useVehicles.ts is a reusable hook so vehicle-select forms can pull from db

Fix:

  • the vehicle odometer log validation (making it >=)

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
drizzle/seed.ts (1)

23-28: ⚠️ Potential issue | 🟡 Minor

Bug: --vehicles flag not included in the "no flags" check.

When no flags are provided, the condition at line 23 doesn't check flags.vehicles, so it defaults all seeders to true including vehicles. However, this is inconsistent—if a user passes only --users, flags.vehicles stays false as expected. The condition should include flags.vehicles for consistency.

Proposed fix
 // If no flags provided, run all
-if (!flags.users && !flags.bookings && !flags.logs) {
+if (!flags.users && !flags.bookings && !flags.logs && !flags.vehicles) {
   flags.users = true;
   flags.bookings = true;
   flags.logs = true;
   flags.vehicles = true;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@drizzle/seed.ts` around lines 23 - 28, The condition that detects "no flags
provided" currently checks flags.users, flags.bookings, and flags.logs but omits
flags.vehicles, causing inconsistent defaulting; update the conditional in the
seeding entry point that references flags (the if block) to include
flags.vehicles (e.g., change if (!flags.users && !flags.bookings && !flags.logs)
to also && !flags.vehicles) so that when no flags are supplied all four (users,
bookings, logs, vehicles) are set to true consistently.
🧹 Nitpick comments (8)
src/server/api/routers/vehicles.ts (1)

86-89: Soft-delete should update updatedAt for consistency.

The delete mutation sets active: false but doesn't update updatedAt, unlike the update mutation which explicitly sets it. This could make audit trails inconsistent.

Proposed fix
   delete: adminProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
-    await ctx.db.update(vehicles).set({ active: false }).where(eq(vehicles.id, input.id));
+    await ctx.db
+      .update(vehicles)
+      .set({ active: false, updatedAt: new Date() })
+      .where(eq(vehicles.id, input.id));
     return { success: true };
   }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/vehicles.ts` around lines 86 - 89, The soft-delete
mutation (delete in adminProcedure.input -> .mutation) currently sets only
active: false via ctx.db.update(vehicles).set(...) and should also set updatedAt
for audit consistency; modify that .set call to include updatedAt: new Date()
(matching the approach used in the update mutation) so the record's timestamp is
updated when soft-deleted.
drizzle/seed.ts (2)

567-576: Raw SQL table creation duplicates schema definition.

The CREATE TABLE IF NOT EXISTS statement duplicates the schema in vehicles-schema.ts. While this works for development seeding (per learnings about using drizzle-kit push), changes to the schema must be mirrored here manually. Consider adding a comment noting this dependency, or using Drizzle's schema introspection if available.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@drizzle/seed.ts` around lines 567 - 576, The raw SQL CREATE TABLE in
drizzzle/seed.ts duplicates the schema defined in vehicles-schema.ts and must be
kept in sync; update the seed by either replacing the hardcoded CREATE TABLE
block (the db.execute(...) call that creates "vehicles") with a note to use
Drizzle schema-driven creation/introspection, or at minimum add a clear comment
above the db.execute(...) CREATE TABLE IF NOT EXISTS block referencing
vehicles-schema.ts and warning that changes to vehicles-schema.ts must be
mirrored here; ensure you reference the "vehicles" table creation and the
vehicles-schema.ts symbol in the comment so future editors know the dependency.

563-610: Missing --clear support for vehicles.

Other seed functions (seedBookings, seedLogs) check flags.clear to delete existing data before seeding. The seedVehicles() function doesn't support this, which may cause confusion when users expect --clear to reset all seeded data.

Proposed fix
 async function seedVehicles() {
   console.log("\n=== SEEDING VEHICLES ===\n");

   try {
+    if (flags.clear) {
+      console.log("Clearing existing vehicles...");
+      await db.delete(vehicles);
+      console.log("Existing vehicles cleared.\n");
+    }
+
     await db.execute(sql`
       CREATE TABLE IF NOT EXISTS "vehicles" (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@drizzle/seed.ts` around lines 563 - 610, The seedVehicles() function is
missing support for the flags.clear option; update seedVehicles to mirror
seedBookings/seedLogs by checking flags.clear at the start and, when true,
delete existing records from the vehicles table before inserting (e.g., perform
db.delete(vehicles) or execute a "DELETE FROM vehicles" to clear rows), log the
clearing action, and then continue with the existing insertion logic using
generateId(), vehicles.name checks, and the existing db.insert(vehicles) flow.
src/app/admin/vehicles/page.tsx (2)

162-168: Delete functionality is missing from the UI.

The vehiclesRouter includes a delete mutation (soft-delete), but there's no delete button in the admin UI. Consider adding a delete/deactivate action to the table row actions, or remove the unused endpoint if it's not needed.

Would you like me to generate the UI code to add a delete button with confirmation?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin/vehicles/page.tsx` around lines 162 - 168, Add a delete action
to each vehicle row: add a "Delete" Button next to the existing Edit Button
(same Table.Td) and wire it to a new handler (e.g., handleDeleteVehicle) that
shows a confirmation modal/confirm() and, on confirm, calls the
vehiclesRouter.delete mutation (or the frontend delete mutation name) with the
vehicle id; after success update the list (invalidate/query refetch) and handle
errors via toast/logging. Ensure the new handler and button coexist with
handleEditVehicle and reuse existing UI components (Button, Table.Td) and
mutation utilities to keep behavior consistent.

195-195: Simplify loading prop to only check the relevant mutation.

The Add modal checks both createVehicle.isPending || updateVehicle.isPending, but only createVehicle is relevant there. Similarly for the Edit modal. This is functionally correct but slightly imprecise.

Proposed fix
       <Modal
         opened={showAddModal}
         ...
-        loading={createVehicle.isPending || updateVehicle.isPending}
+        loading={createVehicle.isPending}
       >
       <Modal
         opened={showEditModal}
         ...
-        loading={createVehicle.isPending || updateVehicle.isPending}
+        loading={updateVehicle.isPending}
       >

Also applies to: 222-222

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin/vehicles/page.tsx` at line 195, The loading prop currently
combines both mutations but should target only the mutation used by each modal:
update the Add modal's loading prop to use createVehicle.isPending only (replace
createVehicle.isPending || updateVehicle.isPending) and update the Edit modal's
loading prop to use updateVehicle.isPending only; locate the props on the modal
components in src/app/admin/vehicles/page.tsx where createVehicle and
updateVehicle are referenced and change each loading expression accordingly.
src/app/hooks/useVehicles.ts (1)

13-17: Consider using vehicle.id as the Select value instead of vehicle.name.

Using vehicle.name as both value and label means if an admin renames a vehicle, historical references (e.g., in vehicle logs) could become inconsistent. Using vehicle.id as the value provides referential integrity, though this may require updates to forms and the logs schema to store vehicle IDs instead of names.

     vehicleOptions:
       dbVehicles?.map((vehicle) => ({
-        value: vehicle.name,
+        value: vehicle.id,
         label: vehicle.name,
       })) ?? [],

This is a larger change that impacts how vehicle data is stored in logs. If vehicle names are considered immutable or the team prefers simplicity, the current approach is acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/hooks/useVehicles.ts` around lines 13 - 17, The Select options
currently use vehicle.name for both value and label in vehicleOptions
(dbVehicles?.map(...)), which can break referential integrity if names change;
change the mapped value to vehicle.id while keeping label as vehicle.name, then
update any form handlers, components and persistence logic that read/write the
Select value (e.g. the form field that consumes vehicleOptions and any functions
that serialize vehicle selection to logs) to store vehicle IDs instead of names,
and adjust any log/schema fields expecting names to instead store or reference
vehicle IDs (or add a migration if needed).
src/app/_components/drivercomponents/surveyForm/surveyForm.tsx (1)

27-27: Consider removing withAsterisk when trip is cancelled.

Other fields in this form (e.g., endReading at line 79, timeOfDeparture at line 101) conditionally set withAsterisk based on tripCompletionStatus !== BookingStatus.CANCELLED. The vehicle field should follow the same pattern for UI consistency.

Proposed fix
         <Select
-          withAsterisk
+          withAsterisk={form.values.tripCompletionStatus !== BookingStatus.CANCELLED}
           label="Vehicle"

Also applies to: 186-198

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/_components/drivercomponents/surveyForm/surveyForm.tsx` at line 27,
The vehicle field's required indicator is currently always shown; update the JSX
for the vehicle input(s) in surveyForm (the "vehicle" form control occurrences
around the top and also the block at lines 186-198) to set withAsterisk
conditionally using the existing tripCompletionStatus check—i.e., change the
static withAsterisk to withAsterisk={tripCompletionStatus !==
BookingStatus.CANCELLED} so it matches the pattern used by endReading and
timeOfDeparture and hides the asterisk when BookingStatus.CANCELLED.
src/server/db/vehicles-schema.ts (1)

7-8: Consider adding .$onUpdate() hook for updatedAt consistency.

Per context snippet 2, other schemas in the codebase (vehicle-log, booking, auth schemas) use .$onUpdate() on updatedAt. While the router manually sets updatedAt: new Date(), adding the hook ensures consistency if future code paths update vehicles without explicitly setting the timestamp.

Proposed change
-  updatedAt: timestamp("updated_at").defaultNow().notNull(),
+  updatedAt: timestamp("updated_at").defaultNow().notNull().$onUpdate(() => new Date()),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/db/vehicles-schema.ts` around lines 7 - 8, The updatedAt column in
the vehicles schema should include the same .$onUpdate() hook used elsewhere to
ensure it auto-updates on any DB-side updates; modify the updatedAt definition
(timestamp("updated_at").defaultNow().notNull()) to chain .$onUpdate(new Date())
(matching patterns used in vehicle-log/booking/auth schemas) so updatedAt is
maintained consistently even when code paths forget to set updatedAt (note:
createdAt stays as timestamp("created_at").defaultNow().notNull()).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/api/routers/vehicle-logs.ts`:
- Around line 84-91: The create mutation's Zod schema uses a non-strict check
(odometerEnd >= odometerStart) which contradicts the DB check constraint
(odometerEnd > odometerStart) and causes insert failures; change the refinement
in the create mutation schema to use strict greater-than (odometerEnd >
odometerStart) to match the DB, and also update the frontend validation/display
in page.tsx (the message that currently says "greater than or equal to start
reading") to say "greater than start reading" so validations are consistent;
reference the create mutation's schema refinement and compare with the existing
update mutation refinement which already uses strict > to ensure both
server-side schemas match the database constraint.

---

Outside diff comments:
In `@drizzle/seed.ts`:
- Around line 23-28: The condition that detects "no flags provided" currently
checks flags.users, flags.bookings, and flags.logs but omits flags.vehicles,
causing inconsistent defaulting; update the conditional in the seeding entry
point that references flags (the if block) to include flags.vehicles (e.g.,
change if (!flags.users && !flags.bookings && !flags.logs) to also &&
!flags.vehicles) so that when no flags are supplied all four (users, bookings,
logs, vehicles) are set to true consistently.

---

Nitpick comments:
In `@drizzle/seed.ts`:
- Around line 567-576: The raw SQL CREATE TABLE in drizzzle/seed.ts duplicates
the schema defined in vehicles-schema.ts and must be kept in sync; update the
seed by either replacing the hardcoded CREATE TABLE block (the db.execute(...)
call that creates "vehicles") with a note to use Drizzle schema-driven
creation/introspection, or at minimum add a clear comment above the
db.execute(...) CREATE TABLE IF NOT EXISTS block referencing vehicles-schema.ts
and warning that changes to vehicles-schema.ts must be mirrored here; ensure you
reference the "vehicles" table creation and the vehicles-schema.ts symbol in the
comment so future editors know the dependency.
- Around line 563-610: The seedVehicles() function is missing support for the
flags.clear option; update seedVehicles to mirror seedBookings/seedLogs by
checking flags.clear at the start and, when true, delete existing records from
the vehicles table before inserting (e.g., perform db.delete(vehicles) or
execute a "DELETE FROM vehicles" to clear rows), log the clearing action, and
then continue with the existing insertion logic using generateId(),
vehicles.name checks, and the existing db.insert(vehicles) flow.

In `@src/app/_components/drivercomponents/surveyForm/surveyForm.tsx`:
- Line 27: The vehicle field's required indicator is currently always shown;
update the JSX for the vehicle input(s) in surveyForm (the "vehicle" form
control occurrences around the top and also the block at lines 186-198) to set
withAsterisk conditionally using the existing tripCompletionStatus check—i.e.,
change the static withAsterisk to withAsterisk={tripCompletionStatus !==
BookingStatus.CANCELLED} so it matches the pattern used by endReading and
timeOfDeparture and hides the asterisk when BookingStatus.CANCELLED.

In `@src/app/admin/vehicles/page.tsx`:
- Around line 162-168: Add a delete action to each vehicle row: add a "Delete"
Button next to the existing Edit Button (same Table.Td) and wire it to a new
handler (e.g., handleDeleteVehicle) that shows a confirmation modal/confirm()
and, on confirm, calls the vehiclesRouter.delete mutation (or the frontend
delete mutation name) with the vehicle id; after success update the list
(invalidate/query refetch) and handle errors via toast/logging. Ensure the new
handler and button coexist with handleEditVehicle and reuse existing UI
components (Button, Table.Td) and mutation utilities to keep behavior
consistent.
- Line 195: The loading prop currently combines both mutations but should target
only the mutation used by each modal: update the Add modal's loading prop to use
createVehicle.isPending only (replace createVehicle.isPending ||
updateVehicle.isPending) and update the Edit modal's loading prop to use
updateVehicle.isPending only; locate the props on the modal components in
src/app/admin/vehicles/page.tsx where createVehicle and updateVehicle are
referenced and change each loading expression accordingly.

In `@src/app/hooks/useVehicles.ts`:
- Around line 13-17: The Select options currently use vehicle.name for both
value and label in vehicleOptions (dbVehicles?.map(...)), which can break
referential integrity if names change; change the mapped value to vehicle.id
while keeping label as vehicle.name, then update any form handlers, components
and persistence logic that read/write the Select value (e.g. the form field that
consumes vehicleOptions and any functions that serialize vehicle selection to
logs) to store vehicle IDs instead of names, and adjust any log/schema fields
expecting names to instead store or reference vehicle IDs (or add a migration if
needed).

In `@src/server/api/routers/vehicles.ts`:
- Around line 86-89: The soft-delete mutation (delete in adminProcedure.input ->
.mutation) currently sets only active: false via
ctx.db.update(vehicles).set(...) and should also set updatedAt for audit
consistency; modify that .set call to include updatedAt: new Date() (matching
the approach used in the update mutation) so the record's timestamp is updated
when soft-deleted.

In `@src/server/db/vehicles-schema.ts`:
- Around line 7-8: The updatedAt column in the vehicles schema should include
the same .$onUpdate() hook used elsewhere to ensure it auto-updates on any
DB-side updates; modify the updatedAt definition
(timestamp("updated_at").defaultNow().notNull()) to chain .$onUpdate(new Date())
(matching patterns used in vehicle-log/booking/auth schemas) so updatedAt is
maintained consistently even when code paths forget to set updatedAt (note:
createdAt stays as timestamp("created_at").defaultNow().notNull()).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0fe10cdb-df52-4e53-8983-fc905d6a52a2

📥 Commits

Reviewing files that changed from the base of the PR and between fb2b073 and 7726848.

📒 Files selected for processing (13)
  • drizzle.config.ts
  • drizzle/seed.ts
  • src/app/_components/common/navbar.tsx
  • src/app/_components/drivercomponents/TripSurveyModal.tsx
  • src/app/_components/drivercomponents/surveyForm/surveyForm.tsx
  • src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx
  • src/app/admin/vehicles/page.tsx
  • src/app/hooks/useVehicles.ts
  • src/server/api/root.ts
  • src/server/api/routers/vehicle-logs.ts
  • src/server/api/routers/vehicles.ts
  • src/server/db/index.ts
  • src/server/db/vehicles-schema.ts
✅ Files skipped from review due to trivial changes (2)
  • drizzle.config.ts
  • src/server/db/index.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/_components/common/navbar.tsx
  • src/app/_components/drivercomponents/TripSurveyModal.tsx
  • src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx

Comment on lines +84 to +91
.refine((data) => data.odometerEnd >= data.odometerStart, {
message: "Odometer end must be greater than odometer start",
path: ["odometerEnd"],
})
.refine((data) => new Date(data.arrivalTime) > new Date(data.departureTime), {
message: "Arrival time must be after departure time",
path: ["arrivalTime"],
}),
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the vehicle-logs.ts file
cd src/server/api/routers
wc -l vehicle-logs.ts
head -150 vehicle-logs.ts

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 5294


🏁 Script executed:

# Search for the database schema definition with odometer constraints
find . -type f \( -name "*.ts" -o -name "*.sql" -o -name "*.js" \) | xargs grep -l "odometer_check\|odometerEnd.*odometerStart" | head -20

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 184


🏁 Script executed:

# Look for page.tsx mentioned in the comment
find . -type f -name "page.tsx" | xargs grep -l "odometer" 2>/dev/null

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 114


🏁 Script executed:

cat src/server/db/vehicle-log.ts

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 1871


🏁 Script executed:

cat drizzle/0000_wise_bloodscream.sql

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 8388


🏁 Script executed:

cat src/app/admin/vehicle-logs/page.tsx | head -300

Repository: Code-the-Change-YYC/salvationarmy

Length of output: 6542


Odometer validation mismatch will cause database constraint violations.

The Zod refinement in the create mutation uses >= (line 84) but the database check constraint requires strict >:

check("odometer_check", sql`${table.odometerEnd} > ${table.odometerStart}`)

When odometerEnd === odometerStart, the Zod validation passes, but the insert fails with a PostgreSQL constraint error. The update mutation (line 142) correctly uses strict >, and the frontend validation message incorrectly allows >= ("greater than or equal to start reading").

🔧 Proposed fix
         })
-        .refine((data) => data.odometerEnd >= data.odometerStart, {
+        .refine((data) => data.odometerEnd > data.odometerStart, {
           message: "Odometer end must be greater than odometer start",
           path: ["odometerEnd"],
         })

Also update the frontend validation in page.tsx to match the strict constraint (change "greater than or equal to" to "greater than").

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/vehicle-logs.ts` around lines 84 - 91, The create
mutation's Zod schema uses a non-strict check (odometerEnd >= odometerStart)
which contradicts the DB check constraint (odometerEnd > odometerStart) and
causes insert failures; change the refinement in the create mutation schema to
use strict greater-than (odometerEnd > odometerStart) to match the DB, and also
update the frontend validation/display in page.tsx (the message that currently
says "greater than or equal to start reading") to say "greater than start
reading" so validations are consistent; reference the create mutation's schema
refinement and compare with the existing update mutation refinement which
already uses strict > to ensure both server-side schemas match the database
constraint.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make this >= @wesleylui

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/server/api/routers/vehicle-logs.ts (2)

15-16: ⚠️ Potential issue | 🟠 Major

Validate and order-check date filters before building SQL predicates.

Line 15 and Line 16 accept arbitrary strings, and dateFrom > dateTo is not rejected. This can leak malformed input into query construction and produce DB-level failures instead of clean input errors.

Proposed hardening
       z
         .object({
           vehicle: z.string().optional(),
           driverName: z.string().optional(),
-          dateFrom: z.string().optional(), // ISO date string "YYYY-MM-DD"
-          dateTo: z.string().optional(), // ISO date string "YYYY-MM-DD"
+          dateFrom: z
+            .string()
+            .regex(/^\d{4}-\d{2}-\d{2}$/, "dateFrom must be YYYY-MM-DD")
+            .optional(),
+          dateTo: z
+            .string()
+            .regex(/^\d{4}-\d{2}-\d{2}$/, "dateTo must be YYYY-MM-DD")
+            .optional(),
         })
+        .refine((v) => !v.dateFrom || !v.dateTo || v.dateFrom <= v.dateTo, {
+          message: "dateFrom must be on or before dateTo",
+          path: ["dateTo"],
+        })
         .optional(),

Also applies to: 29-34


139-157: ⚠️ Potential issue | 🟠 Major

Require odometer/time fields in pairs for update.

At Line 139 onward, sending only one field in a pair (odometerEnd without odometerStart, or arrivalTime without departureTime) bypasses application-level validation and defers failures to DB constraints.

Proposed guardrails
         .object({
           id: z.number().int().positive(),
           date: z.string().min(1).optional(),
           travelLocation: z.string().min(1).optional(),
           departureTime: z.string().min(1).optional(),
           arrivalTime: z.string().min(1).optional(),
           odometerStart: z.number().int().nonnegative().optional(),
           odometerEnd: z.number().int().nonnegative().optional(),
           driverId: z.string().min(1).optional(),
           driverName: z.string().min(1).optional(),
           vehicle: z.string().min(1).optional(),
         })
+        .refine(
+          (data) => (data.odometerStart === undefined) === (data.odometerEnd === undefined),
+          {
+            message: "Provide both odometerStart and odometerEnd together",
+            path: ["odometerEnd"],
+          },
+        )
+        .refine(
+          (data) => (data.departureTime === undefined) === (data.arrivalTime === undefined),
+          {
+            message: "Provide both departureTime and arrivalTime together",
+            path: ["arrivalTime"],
+          },
+        )
         .refine(
           (data) => {
             if (data.odometerEnd !== undefined && data.odometerStart !== undefined) {
               return data.odometerEnd >= data.odometerStart;
             }
             return true;
           },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/api/routers/vehicle-logs.ts` around lines 139 - 157, The update
input schema currently allows sending only one side of paired fields, so add
refinements to the schema in vehicle-logs.ts (the chain that includes the
existing .refine calls) to enforce that odometerStart and odometerEnd must be
provided together and arrivalTime and departureTime must be provided together:
for each pair, validate that either both are undefined or both are present (and
keep the existing comparison checks when both exist); update the refine
message/path to indicate the missing partner (e.g., "odometerStart and
odometerEnd must be provided together" with path ["odometerStart","odometerEnd"]
and similarly for arrivalTime/departureTime) so single-field updates are
rejected before hitting DB constraints.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/api/routers/vehicle-logs.ts`:
- Around line 85-87: Update the validation error text for the odometer end check
so it matches the actual comparison operator (>=); find occurrences where the
validation error object uses path ["odometerEnd"] and message "Odometer end must
be greater than" and change them to "Odometer end must be greater than or equal
to" (this applies to both places in the vehicle-logs router where the
odometerEnd validation message is constructed).

---

Duplicate comments:
In `@src/server/api/routers/vehicle-logs.ts`:
- Around line 139-157: The update input schema currently allows sending only one
side of paired fields, so add refinements to the schema in vehicle-logs.ts (the
chain that includes the existing .refine calls) to enforce that odometerStart
and odometerEnd must be provided together and arrivalTime and departureTime must
be provided together: for each pair, validate that either both are undefined or
both are present (and keep the existing comparison checks when both exist);
update the refine message/path to indicate the missing partner (e.g.,
"odometerStart and odometerEnd must be provided together" with path
["odometerStart","odometerEnd"] and similarly for arrivalTime/departureTime) so
single-field updates are rejected before hitting DB constraints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7568cad2-6fee-405d-beb0-fea59612dafb

📥 Commits

Reviewing files that changed from the base of the PR and between 7726848 and 5f4adee.

📒 Files selected for processing (2)
  • src/server/api/routers/vehicle-logs.ts
  • src/server/db/vehicle-log.ts

Comment thread src/server/api/routers/vehicle-logs.ts Outdated
Copy link
Copy Markdown
Contributor

@promatty promatty left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@promatty promatty merged commit 4097f0e into main Apr 14, 2026
4 checks passed
@promatty promatty deleted the SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend branch April 14, 2026 03:27
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.

2 participants