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
1 change: 1 addition & 0 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default defineConfig({
"./src/server/db/post-trip-schema.ts", // post-trip schema
"./src/server/db/vehicle-log.ts", // logs schema
"./src/server/db/passenger-schema.ts", // passenger schema
"./src/server/db/vehicles-schema.ts", // vehicles schema
],
dialect: "postgresql",
dbCredentials: {
Expand Down
58 changes: 57 additions & 1 deletion drizzle/seed.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/** biome-ignore-all lint/style/noNonNullAssertion: its just a seeding script so i think this is fine */
import { generateId } from "better-auth";
import { eq } from "drizzle-orm";
import { eq, sql } from "drizzle-orm";
import { auth } from "@/lib/auth";
import { db } from "@/server/db";
import { member, organization, type User, user } from "@/server/db/auth-schema";
import { bookings } from "@/server/db/booking-schema";
import { logs } from "@/server/db/vehicle-log";
import { vehicles } from "@/server/db/vehicles-schema";
import { BookingStatus } from "@/types/types";

// Parse command line arguments
Expand All @@ -14,6 +15,7 @@ const flags = {
users: args.includes("--users"),
bookings: args.includes("--bookings"),
logs: args.includes("--logs"),
vehicles: args.includes("--vehicles"),
clear: args.includes("--clear"),
};

Expand All @@ -22,6 +24,7 @@ if (!flags.users && !flags.bookings && !flags.logs) {
flags.users = true;
flags.bookings = true;
flags.logs = true;
flags.vehicles = true;
}

const DEFAULT_PASSWORD = "Password123!";
Expand Down Expand Up @@ -557,6 +560,55 @@ async function seedLogs() {
}
}

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

try {
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "vehicles" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL UNIQUE,
"active" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
`);

const vehicleData = [
"Ford Expedition CTW 2776",
"Salvation Army Vehicle 2",
"Salvation Army Vehicle 3",
];

console.log(`Creating ${vehicleData.length} vehicles...`);

for (const vehicleName of vehicleData) {
const existing = await db
.select()
.from(vehicles)
.where(eq(vehicles.name, vehicleName))
.limit(1);

if (existing.length === 0) {
await db.insert(vehicles).values({
id: generateId(),
name: vehicleName,
active: true,
});
console.log(` ✓ Created: ${vehicleName}`);
} else {
console.log(` → Already exists: ${vehicleName}`);
}
}

console.log("\n✅ Vehicles seeded successfully!\n");
} catch (error) {
console.error("❌ Failed to seed vehicles:", error);
throw error;
}
}

async function main() {
console.log("\nStarting seed process...");
console.log(`Flags: ${JSON.stringify(flags, null, 2)}\n`);
Expand All @@ -574,6 +626,10 @@ async function main() {
await seedLogs();
}

if (flags.vehicles) {
await seedVehicles();
}

console.log("\n" + "=".repeat(50));
console.log("SEED COMPLETED SUCCESSFULLY");
console.log("=".repeat(50));
Expand Down
3 changes: 2 additions & 1 deletion src/app/_components/common/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ export default function Navbar({ view, agencyName }: NavbarProps) {
<span className={styles.navLinkDefault}>Invite</span>
</button>
<NavLink href="/admin/rider-logs">Rider Logs</NavLink>
<NavLink href="/admin/driver-logs">Vehicle Logs</NavLink>
<NavLink href="/admin/vehicle-logs">Vehicle Logs</NavLink>
<NavLink href="/admin/vehicles">View Vehicles</NavLink>
<NavLink href="/admin/schedule">View Schedule</NavLink>
<Profile />
</Group>
Expand Down
18 changes: 17 additions & 1 deletion src/app/_components/drivercomponents/TripSurveyModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use client";

import { Group, Radio, Rating, Stack, Text, Textarea, TextInput } from "@mantine/core";
import { Group, Radio, Rating, Select, Stack, Text, Textarea, TextInput } from "@mantine/core";
import { DateTimePicker } from "@mantine/dates";
import type { UseFormReturnType } from "@mantine/form";
import dayjs from "dayjs";
import { useVehicles } from "@/app/hooks/useVehicles";
import Rating1 from "@/assets/icons/rating1";
import Rating2 from "@/assets/icons/rating2";
import Rating3 from "@/assets/icons/rating3";
Expand All @@ -20,6 +21,7 @@ interface SurveyForm {
timeOfDeparture: string;
timeOfArrival: string;
destinationAddress: string;
vehicle: string;
originalLocationChanged: boolean;
passengerFitRating: number | "";
comments: string;
Expand All @@ -31,6 +33,7 @@ interface SurveyFormProps {

export const TripSurveyModal = ({ form }: SurveyFormProps) => {
const now = new Date();
const { vehicleOptions, isLoading: isLoadingVehicles } = useVehicles();

const getIconStyle = (color?: string) => ({
width: 32,
Expand Down Expand Up @@ -97,6 +100,19 @@ export const TripSurveyModal = ({ form }: SurveyFormProps) => {
{...form.getInputProps("destinationAddress")}
required
/>
<Select
withAsterisk
label="Vehicle"
placeholder="Select a vehicle"
data={vehicleOptions}
searchable
key={form.key("vehicle")}
{...form.getInputProps("vehicle")}
error={form.errors.vehicle}
disabled={
form.values.tripCompletionStatus === BookingStatus.CANCELLED || isLoadingVehicles
}
/>
<DateTimePicker
label="Time of Departure"
withAsterisk={true}
Expand Down
28 changes: 27 additions & 1 deletion src/app/_components/drivercomponents/surveyForm/surveyForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
"use client";

import { Box, Divider, NumberInput, Radio, Stack, Textarea, TextInput } from "@mantine/core";
import {
Box,
Divider,
NumberInput,
Radio,
Select,
Stack,
Textarea,
TextInput,
} from "@mantine/core";
import { DateTimePicker } from "@mantine/dates";
import type { UseFormReturnType } from "@mantine/form";
import dayjs from "dayjs";
import { useVehicles } from "@/app/hooks/useVehicles";
import { BookingStatus } from "@/types/types";
import styles from "./survey-form.module.scss";

Expand All @@ -14,6 +24,7 @@ interface SurveyForm {
timeOfDeparture: string;
timeOfArrival: string;
destinationAddress: string;
vehicle: string;
originalLocationChanged: boolean;
passengerFitRating: number | "";
comments: string;
Expand All @@ -25,6 +36,7 @@ interface SurveyFormProps {

export const SurveyForm = ({ form }: SurveyFormProps) => {
const now = new Date();
const { vehicleOptions, isLoading: isLoadingVehicles } = useVehicles();

return (
<Stack gap="lg">
Expand Down Expand Up @@ -171,6 +183,20 @@ export const SurveyForm = ({ form }: SurveyFormProps) => {
/>
</div>

<div className={styles.formRow}>
<Select
withAsterisk
label="Vehicle"
placeholder="Select a vehicle"
data={vehicleOptions}
searchable
disabled={isLoadingVehicles}
key={form.key("vehicle")}
{...form.getInputProps("vehicle")}
error={form.errors.vehicle}
/>
</div>
Comment on lines +186 to +198

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.


<div className={styles.formRow}>
<Radio.Group
withAsterisk={form.values.tripCompletionStatus !== BookingStatus.CANCELLED}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default function SurveyNotification({ survey, completed = false }: Survey
: "",
timeOfArrival: inputSurvey.timeOfArrival ? dayjs(inputSurvey.timeOfArrival).toISOString() : "",
destinationAddress: inputSurvey.destinationAddress || "",
vehicle: inputSurvey.vehicle || "",
originalLocationChanged: inputSurvey.originalLocationChanged ?? false,
passengerFitRating: inputSurvey.passengerFitRating || ("" as number | ""),
comments: inputSurvey.comments || "",
Expand Down Expand Up @@ -106,6 +107,10 @@ export default function SurveyNotification({ survey, completed = false }: Survey
return validateTimeRange(values.timeOfDeparture, value);
},
destinationAddress: (value) => validateStringLength(value, 1, 300, "Destination address"),
vehicle: (value, values) => {
if (values.tripCompletionStatus === BookingStatus.CANCELLED) return null;
return validateStringLength(value, 1, 100, "Vehicle");
},
originalLocationChanged: (value, values) => {
if (values.tripCompletionStatus === BookingStatus.CANCELLED) return null;
if (typeof value !== "boolean") return "Please select an option";
Expand Down Expand Up @@ -166,6 +171,7 @@ export default function SurveyNotification({ survey, completed = false }: Survey
submitSurveyMutation.mutate({
bookingId: survey.bookingId as number, // TODO: will remove eventually!
driverId: survey.driverId as string, // TODO: will remove castin eveentually
vehicle: form.values.vehicle,
...basePayload,
});
};
Expand Down
12 changes: 9 additions & 3 deletions src/app/_components/vehiclelogcomponents/vehicle-log-form.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"use client";

import { Box, Divider, Stack, TextInput } from "@mantine/core";
import { Box, Divider, Select, Stack, TextInput } from "@mantine/core";
import { DateTimePicker } from "@mantine/dates";
import type { UseFormReturnType } from "@mantine/form";
import { useVehicles } from "@/app/hooks/useVehicles";
import classes from "./vehicle-log-form.module.scss";

interface VehicleLogFormData {
id: number | null;
date: string | null;
destination: string;
departureTime: string | null;
Expand All @@ -24,6 +26,7 @@ export const VehicleLogForm = ({ form }: VehicleLogFormProps) => {
const now = new Date();
const ninetyDaysAgo = new Date();
ninetyDaysAgo.setDate(now.getDate() - 90);
const { vehicleOptions, isLoading: isLoadingVehicles } = useVehicles();

// Calculate KM driven
const calculateKmDriven = () => {
Expand Down Expand Up @@ -174,10 +177,13 @@ export const VehicleLogForm = ({ form }: VehicleLogFormProps) => {
</div>

<div className={classes.formRow}>
<TextInput
<Select
withAsterisk
label="Vehicle"
placeholder="Enter vehicle name"
placeholder="Select a vehicle"
data={vehicleOptions}
searchable
disabled={isLoadingVehicles}
key={form.key("vehicle")}
{...form.getInputProps("vehicle")}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,18 @@
font-size: 14px;
font-weight: $font-weight-semibold;
}

.loadingContainer {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}

.errorContainer {
display: flex;
align-items: flex-start;
width: 100%;
padding: 16px;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"use client";
import { Alert, Box, Loader } from "@mantine/core";
import type { ColDef, IHeaderParams } from "ag-grid-community";
import { AllCommunityModule, ModuleRegistry, themeQuartz } from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";
Expand Down Expand Up @@ -81,8 +82,8 @@ export default function VehicleLogTableView({ onRowClick }: VehicleLogTableViewP
// Custom theme for the table
const theme = themeQuartz.withParams(TABLE_THEME_PARAMS);

// Fetch vehicle logs from database (bookings + post-trip surveys joined)
const { data: vehicleLogs = [] } = api.vehicleLogs.getAll.useQuery();
// Fetch vehicle logs from database
const { data: vehicleLogs = [], isLoading, isError } = api.vehicleLogs.getAll.useQuery();

const columnDefs: ColDef[] = useMemo(
() => [
Expand Down Expand Up @@ -162,14 +163,26 @@ export default function VehicleLogTableView({ onRowClick }: VehicleLogTableViewP

return (
<div className={styles.tableContainer}>
<AgGridReact
theme={theme}
rowData={vehicleLogs}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
pagination={false}
onRowClicked={(event) => event.data && onRowClick?.(event.data)}
/>
{isLoading ? (
<Box className={styles.loadingContainer}>
<Loader color="#A03145" type="dots" />
</Box>
) : isError ? (
<Box className={styles.errorContainer}>
<Alert variant="light" color="red">
Failed to load vehicle logs. Please try again later.
</Alert>
</Box>
) : (
<AgGridReact
theme={theme}
rowData={vehicleLogs}
columnDefs={columnDefs}
defaultColDef={defaultColDef}
pagination={false}
onRowClicked={(event) => event.data && onRowClick?.(event.data)}
/>
)}
</div>
);
}
Loading
Loading