Skip to content

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

Open
wesleylui wants to merge 2 commits intomainfrom
SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend
Open

SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend#62
wesleylui wants to merge 2 commits intomainfrom
SANC-60-vehicle-logs-create-api-endpoints-and-connect-to-frontend

Conversation

@wesleylui
Copy link
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
    • Added ability to create and edit vehicle logs with field validation
    • Added filtering options for vehicle logs by vehicle, driver, and date range
    • Enhanced user feedback with loading indicators and error messages during operations

@vercel
Copy link

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 Mar 1, 2026 1:47am

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Adds CRUD mutations to the vehicle logs API router with filtering and validation, introduces edit-mode support in the form by capturing log ids, and enhances the table view with loading and error state UI. The page integrates these mutations with form submission logic and session-based defaults.

Changes

Cohort / File(s) Summary
Backend API Router
src/server/api/routers/vehicle-logs.ts
Implemented getAll with optional filtering (vehicle, driverName, dateFrom, dateTo), and added create, update, and delete mutations with input validation schemas, error handling (NOT_FOUND, INTERNAL_SERVER_ERROR), and session-derived defaults.
Form Data Structure
src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx
Added id: number | null field to VehicleLogFormData interface to support edit mode.
Table Styling
src/app/_components/vehiclelogcomponents/vehicle-log-table-view.module.scss
Added loadingContainer and errorContainer SCSS classes for centered and padded state displays.
Table UI with State Handling
src/app/_components/vehiclelogcomponents/vehicle-log-table-view.tsx
Integrated Mantine UI components (Loader, Alert) with conditional rendering for loading, error, and success states; destructures isLoading and isError from query alongside data.
Page Integration
src/app/admin/vehicle-logs/page.tsx
Integrated create and update mutations with session retrieval, form id population on row click, input validation (required fields, numeric odometer), and mutation handlers with cache invalidation and notifications.

Sequence Diagram

sequenceDiagram
    actor User
    participant Form as Form Component
    participant Page as Vehicle Logs Page
    participant API as TRPC Router
    participant DB as Database

    rect rgba(100, 150, 255, 0.5)
    Note over User,DB: Create Flow
    User->>Form: Fill new log form
    User->>Page: Click Submit
    Page->>Page: Validate inputs
    Page->>API: call createLog mutation
    API->>API: Validate schema (odometer, times)
    API->>API: Inject driverId from session
    API->>DB: Insert vehicle log
    DB-->>API: Return created log
    API-->>Page: Success response
    Page->>Form: Reset form, clear id
    Page->>User: Show success notification
    end

    rect rgba(100, 200, 100, 0.5)
    Note over User,DB: Edit Flow
    User->>Form: Click table row
    Form->>Page: Populate form with log data
    Page->>Form: Set id and fields
    User->>Form: Modify fields
    User->>Page: Click Submit
    Page->>API: call updateLog mutation
    API->>DB: Update vehicle log by id
    DB-->>API: Return updated log
    API-->>Page: Success response
    Page->>Page: Invalidate cache
    Page->>Form: Reset form
    Page->>User: Show success notification
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

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

Poem

🐰 Hops gleefully through the code

Oh, what a patch we have here today!
New mutations to create, update, and sway—
The logs now dance with filters and grace,
Edit mode shines with id in its place,
Loading states spin, errors display,
The vehicle logs found their way! 🚗✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately describes the main work in this PR: creating API endpoints (create, update, delete mutations in vehicle-logs.ts) and connecting them to the frontend (form submission and loading/error state handling in vehicle-logs page and table-view).

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

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

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

@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

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

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

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.

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.

1 participant