Skip to content
Merged
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
10 changes: 10 additions & 0 deletions src/app/_components/common/calendar/calendar-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,15 @@ interface CalendarViewProps {
currentDate?: Date;
setIsDayView?: (isDayView: boolean) => void;
includeButtons?: boolean;
onDateChangeFunction?: (...args: any[]) => void;
}

export default function CalendarView({
bookings,
currentDate,
setIsDayView,
includeButtons,
onDateChangeFunction,
}: CalendarViewProps) {
const events = useMemo(() => transformBookingsToEvents(bookings ?? []), [bookings]);
const calendarRef = useRef<FullCalendar>(null);
Expand Down Expand Up @@ -237,6 +239,14 @@ export default function CalendarView({
firstDay={1}
height={700}
weekends={false}
datesSet={(date) => {
if (onDateChangeFunction) {
onDateChangeFunction({
startDate: date.startStr,
endDate: date.endStr,
});
}
}}
/>
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.hiddenCalendar {
visibility: hidden;
position: fixed;
}
49 changes: 49 additions & 0 deletions src/app/_components/drivercomponents/driver-dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client";

import { useState } from "react";
import { api } from "@/trpc/react";
import type { Booking } from "@/types/types";
import CalendarView from "../common/calendar/calendar-view";
import LoadingScreen from "../common/loadingscreen";
import styles from "./driver-dashboard.module.scss";

type CalendarDates = {
startDate: string;
endDate: string;
};

export const DriverDashboard = () => {
const [dateJSON, setDateJSON] = useState<CalendarDates>({
startDate: "",
endDate: "",
});

let driverTrips = [{}] as Booking[];

const tripQuery = api.bookings.getDriverTrip.useQuery(
{
startDate: dateJSON.startDate,
endDate: dateJSON.endDate,
},
{ enabled: dateJSON.startDate !== "" && dateJSON.endDate !== "" },
);

if (tripQuery.data) {
//Query returned data from endpoint call
driverTrips = tripQuery.data as Booking[];
}
Comment thread
jason-duong4509 marked this conversation as resolved.

return (
<>
{!tripQuery.data && <LoadingScreen message="Loading Trips..." />}

<div className={!tripQuery.data ? styles.hiddenCalendar : undefined}>
<CalendarView
bookings={driverTrips}
includeButtons={true}
onDateChangeFunction={setDateJSON}
/>
</div>
</>
);
};
6 changes: 2 additions & 4 deletions src/app/driver/home/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import CalendarView from "@/app/_components/common/calendar/calendar-view";
import { api } from "@/trpc/server";
import { DriverDashboard } from "@/app/_components/drivercomponents/driver-dashboard";

export default async function DriverHome() {
const initialBookings = await api.bookings.getAll();
return (
<main>
<CalendarView bookings={initialBookings} includeButtons={true}></CalendarView>
<DriverDashboard />
</main>
);
}
61 changes: 60 additions & 1 deletion src/server/api/routers/bookings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { and, desc, eq, gte, lt, or } from "drizzle-orm";
import { z } from "zod";
import { BOOKING_STATUSES, BookingStatus } from "@/types/types";
import { BOOKING_STATUSES, BookingStatus, Role } from "@/types/types";
import { isoTimeRegex, isoTimeRegexFourDigitYears } from "@/types/validation";
import { type BookingInsertType, bookings } from "../../db/booking-schema";
import { createTRPCRouter, protectedProcedure } from "../trpc";
Expand Down Expand Up @@ -275,4 +275,63 @@ export const bookingsRouter = createTRPCRouter({
.returning()
.then((r) => r[0]);
}),

getDriverTrip: protectedProcedure
.input(
z.object({
startDate: z.string(),
endDate: z.string(),
}),
)
.query(async ({ ctx, input }) => {
const user = await ctx.db.query.user.findFirst({
where: (user, { eq }) => eq(user.id, ctx.session.user.id),
});

if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found in database",
});
}
Comment thread
jason-duong4509 marked this conversation as resolved.

if (user.role !== Role.DRIVER) {
throw new TRPCError({
code: "FORBIDDEN",
message: "User is not a driver",
});
}
Comment on lines +298 to +303
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.

what if on the admin page we want to be able to see bookings for a specific driver? this endpoint wouldn't work then, would it?

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.

I guess we could just filter by driver id on the client side, but I think all endpoints should be accessible by admins regardless. Let me know what you think

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

As of right now, the endpoint is designed to return bookings that are assigned to the user who called it. If we extended this to admins, the endpoint would only return bookings assigned to the admin. I do not think this would be useful in this context

Achieving functionality where the admin could use the endpoint to filter bookings for a specific driver would require modification of the endpoint

Comment thread
coderabbitai[bot] marked this conversation as resolved.

let startAndEndDateErrorMessage = "Invalid: ";

if (
!(isoTimeRegex.test(input.startDate) || isoTimeRegexFourDigitYears.test(input.startDate))
) {
startAndEndDateErrorMessage = startAndEndDateErrorMessage + "Start Date ";
}

if (!(isoTimeRegex.test(input.endDate) || isoTimeRegexFourDigitYears.test(input.endDate))) {
startAndEndDateErrorMessage = startAndEndDateErrorMessage + "End Date ";
}

if (startAndEndDateErrorMessage !== "Invalid: ") {
//Either (or both) dates failed regex check
throw new TRPCError({
code: "BAD_REQUEST",
message: startAndEndDateErrorMessage,
});
}

return ctx.db
.select()
.from(bookings)
.where(
and(
eq(bookings.driverId, user.id),
gte(bookings.startTime, input.startDate),
lt(bookings.startTime, input.endDate),
),
Comment on lines +305 to +333
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

Validate start/end ordering to avoid inverted ranges.

Format checks pass even if endDate is before startDate, which yields confusing empty results. Add an explicit ordering guard.

✅ Suggested fix
       if (startAndEndDateErrorMessage !== "Invalid: ") {
         //Either (or both) dates failed regex check
         throw new TRPCError({
           code: "BAD_REQUEST",
           message: startAndEndDateErrorMessage,
         });
       }
+
+      const start = new Date(input.startDate);
+      const end = new Date(input.endDate);
+      if (!(end > start)) {
+        throw new TRPCError({
+          code: "BAD_REQUEST",
+          message: "End Date must be after Start Date",
+        });
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let startAndEndDateErrorMessage = "Invalid: ";
if (
!(isoTimeRegex.test(input.startDate) || isoTimeRegexFourDigitYears.test(input.startDate))
) {
startAndEndDateErrorMessage = startAndEndDateErrorMessage + "Start Date ";
}
if (!(isoTimeRegex.test(input.endDate) || isoTimeRegexFourDigitYears.test(input.endDate))) {
startAndEndDateErrorMessage = startAndEndDateErrorMessage + "End Date ";
}
if (startAndEndDateErrorMessage !== "Invalid: ") {
//Either (or both) dates failed regex check
throw new TRPCError({
code: "BAD_REQUEST",
message: startAndEndDateErrorMessage,
});
}
return ctx.db
.select()
.from(bookings)
.where(
and(
eq(bookings.driverId, user.id),
gte(bookings.startTime, input.startDate),
lt(bookings.startTime, input.endDate),
),
let startAndEndDateErrorMessage = "Invalid: ";
if (
!(isoTimeRegex.test(input.startDate) || isoTimeRegexFourDigitYears.test(input.startDate))
) {
startAndEndDateErrorMessage = startAndEndDateErrorMessage + "Start Date ";
}
if (!(isoTimeRegex.test(input.endDate) || isoTimeRegexFourDigitYears.test(input.endDate))) {
startAndEndDateErrorMessage = startAndEndDateErrorMessage + "End Date ";
}
if (startAndEndDateErrorMessage !== "Invalid: ") {
//Either (or both) dates failed regex check
throw new TRPCError({
code: "BAD_REQUEST",
message: startAndEndDateErrorMessage,
});
}
const start = new Date(input.startDate);
const end = new Date(input.endDate);
if (!(end > start)) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "End Date must be after Start Date",
});
}
return ctx.db
.select()
.from(bookings)
.where(
and(
eq(bookings.driverId, user.id),
gte(bookings.startTime, input.startDate),
lt(bookings.startTime, input.endDate),
),
🤖 Prompt for AI Agents
In `@src/server/api/routers/bookings.ts` around lines 281 - 309, The current
validation only checks date formats (using isoTimeRegex and
isoTimeRegexFourDigitYears) but does not ensure the start/end ordering, so add
an explicit check after the format checks and before querying the DB: parse or
compare input.startDate and input.endDate (e.g., create Date objects or compare
normalized ISO strings) and if end < start throw a TRPCError with code
"BAD_REQUEST" and a clear message like "Invalid: End Date is before Start Date";
keep this guard near the existing TRPCError block that validates formats so the
DB query (ctx.db.select().from(bookings) with gte(bookings.startTime, ...) and
lt(bookings.startTime, ...)) only runs for a valid chronological range.

)
.orderBy(desc(bookings.createdAt));
}),
});