Skip to content

add combobox for locations #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
Mar 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
97a07cd
combobox for locations
tracyyh Feb 9, 2025
2b6eeb7
Merge branch 'main' into cities-api-combobox
tracyyh Feb 13, 2025
d98a9fe
COMBOX WITH FULL CITIES LIST MUAHAHAHHAHAHA
tracyyh Feb 13, 2025
2081c86
reduce file load
tracyyh Feb 13, 2025
c8e4054
create location endpoints
tracyyh Feb 23, 2025
452adb7
remove cities files
tracyyh Feb 23, 2025
476e216
add location schema and endpoint
tracyyh Feb 25, 2025
fe2837c
Merge branch 'main' into cities-api-combobox
tracyyh Feb 25, 2025
a0c6d15
Migration
banushi-a Feb 25, 2025
323c75c
Location Type and Frontend Call Example
banushi-a Feb 25, 2025
78160de
update frontend
tracyyh Feb 28, 2025
4bfdd41
fix frontend call
tracyyh Feb 28, 2025
f24bad8
change when location api is called
tracyyh Mar 12, 2025
9d4cd61
fix weird width
tracyyh Mar 12, 2025
7550df0
prettier bruh
tracyyh Mar 12, 2025
ebcc1db
edit retrieving locations in other instances
tracyyh Mar 12, 2025
8742212
try to get location to save to review
tracyyh Mar 12, 2025
071e296
fix relation
tracyyh Mar 13, 2025
6d14a01
add companiesToLocation schema
tracyyh Mar 16, 2025
db38850
fix companyToLocation creation
tracyyh Mar 16, 2025
661bb7e
React-form bug fix
banushi-a Mar 17, 2025
55db0c5
Merge branch 'main' into cities-api-combobox
banushi-a Mar 17, 2025
74782f7
Handle Multiple Locations Per Company
banushi-a Mar 17, 2025
e160a2b
Handle Multiple Locations per Company:
banushi-a Mar 17, 2025
f7c5b53
Wrong syntax
banushi-a Mar 17, 2025
c33ca2d
figure out location later
banushi-a Mar 17, 2025
91dbdde
Migrations
banushi-a Mar 17, 2025
5819edd
Schema linting
banushi-a Mar 17, 2025
81dc610
format
banushi-a Mar 17, 2025
7f991e2
Pls be the lint error for the love of god
banushi-a Mar 17, 2025
5850614
Fix this later
banushi-a Mar 17, 2025
72eca25
Linting + DB Migrations Ran
banushi-a Mar 17, 2025
c85d46d
merge conflicts?
banushi-a Mar 17, 2025
60a6dca
Merge branch 'main' into cities-api-combobox
banushi-a Mar 17, 2025
a31fd45
defer this to another ticket
banushi-a Mar 17, 2025
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 .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"version": "0.2.0",
"configurations": [

{
"name": "Next.js",
"type": "node-terminal",
Expand Down
25 changes: 22 additions & 3 deletions apps/web/src/app/_components/combo-box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ interface ComboBoxProps {
currLabel: string;
onSelect: (option: string) => void;
triggerClassName?: string;
onChange?: (value: string) => void;
variant?: "default" | "form";
}

/**
Expand All @@ -41,9 +43,16 @@ export default function ComboBox({
currLabel,
onSelect,
triggerClassName,
onChange,
variant,
}: ComboBoxProps) {
const [isOpen, setIsOpen] = useState(false);

const styleVariant =
variant === "form"
? "flex h-16 w-full rounded-md border-[3px] border-cooper-blue-600 bg-white px-3 py-2 text-xl font-normal ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
: "";

return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger
Expand All @@ -57,15 +66,25 @@ export default function ComboBox({
variant="outline"
role="combobox"
aria-expanded={isOpen}
className="justify-between"
className={cn(
styleVariant,
"w-[400px] justify-between overflow-hidden text-ellipsis text-nowrap",
)}
>
{defaultLabel}
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
{defaultLabel}
</span>
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[400px] p-0">
<Command>
<CommandInput placeholder={searchPlaceholder} />
<CommandInput
placeholder={searchPlaceholder}
onChangeCapture={(e) =>
onChange && onChange((e.target as HTMLInputElement).value)
}
/>
<CommandEmpty>{searchEmpty}</CommandEmpty>
<CommandGroup>
<CommandList>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function CompanyCardPreview({ companyObj }: CompanyCardPreviewProps) {
</div>
<div className="flex flex-col text-sm">
<h4 className="font-semibold">Location</h4>
<p>{companyObj.location}</p>
<p>TODO: companies</p>
</div>
</div>
<div className="m-4 flex items-center space-x-4">
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/_components/form/review-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const formSchema = z.object({
.min(8, {
message: "The review must be at least 8 characters.",
}),
location: z.string().optional(),
locationId: z.string().optional(),
hourlyPay: z.coerce
.number()
.positive()
Expand Down Expand Up @@ -166,7 +166,7 @@ const steps: {
},
{
label: "Review",
fields: ["reviewHeadline", "textReview", "location", "hourlyPay"],
fields: ["reviewHeadline", "textReview", "locationId", "hourlyPay"],
borderColor: "border-cooper-green-500",
textColor: "text-cooper-green-500",
bgColor: "bg-cooper-green-500",
Expand Down Expand Up @@ -214,7 +214,7 @@ export function ReviewForm(props: ReviewFormProps) {
interviewReview: "",
reviewHeadline: "",
textReview: "",
location: "",
locationId: "",
hourlyPay: "",
workEnvironment: undefined,
drugTest: undefined,
Expand Down
62 changes: 58 additions & 4 deletions apps/web/src/app/_components/form/sections/review-section.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"use client";

import { useEffect, useState } from "react";
import { useFormContext } from "react-hook-form";

import {
Expand All @@ -10,14 +13,46 @@ import {
import { Input } from "@cooper/ui/input";
import { Textarea } from "@cooper/ui/textarea";

import ComboBox from "~/app/_components/combo-box";
import { FormSection } from "~/app/_components/form/form-section";
import { api } from "~/trpc/react";

/**
* ReviewSection component renders form fields for writing a co-op review.
*/
export function ReviewSection({ textColor }: { textColor: string }) {
const form = useFormContext();

const [locationLabel, setLocationLabel] = useState<string>("");
const [searchTerm, setSearchTerm] = useState<string>("");
const [prefix, setPrefix] = useState<string>("");

useEffect(() => {
const newPrefix =
searchTerm.length === 3 ? searchTerm.slice(0, 3).toLowerCase() : null;
if (newPrefix && newPrefix !== prefix) {
setPrefix(newPrefix);
}
}, [prefix, searchTerm]);

const locationsToUpdate = api.location.getByPrefix.useQuery(
{ prefix },
{ enabled: searchTerm.length === 3 },
);

const locationValuesAndLabels = locationsToUpdate.data
? locationsToUpdate.data.map((location) => {
return {
value: location.id,
label:
location.city +
(location.state ? `, ${location.state}` : "") +
", " +
location.country,
};
})
: [];

return (
<FormSection title="Review" className={textColor}>
<FormField
Expand Down Expand Up @@ -49,17 +84,36 @@ export function ReviewSection({ textColor }: { textColor: string }) {
<div className="flex justify-between space-x-2">
<FormField
control={form.control}
name="location"
name="locationId"
render={({ field }) => (
<FormItem className="flex flex-col justify-end">
<FormLabel>Location</FormLabel>
<FormControl>
<Input {...field} />
<ComboBox
variant="form"
defaultLabel={locationLabel || "Type to begin..."}
searchPlaceholder="Type to begin..."
searchEmpty="No location found."
valuesAndLabels={locationValuesAndLabels}
currLabel={locationLabel}
onChange={(value) => {
setSearchTerm(value);
}}
onSelect={(currentValue) => {
setLocationLabel(
currentValue === locationLabel ? "" : currentValue,
);
field.onChange(
locationValuesAndLabels.find(
(location) => location.label === currentValue,
)?.value,
);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
></FormField>
<FormField
control={form.control}
name="hourlyPay"
Expand Down
20 changes: 18 additions & 2 deletions apps/web/src/app/_components/reviews/review-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import type { ReviewType } from "@cooper/db/schema";
import { Card, CardContent } from "@cooper/ui/card";

import { api } from "~/trpc/react";
import { ReviewCardStars } from "./review-card-stars";

// const InterviewDifficulty = [
Expand All @@ -19,6 +20,21 @@ interface ReviewCardProps {
}

export function ReviewCard({ reviewObj }: ReviewCardProps) {
// ===== LOCATION DATA ===== //
const locationName = (reviewObj: ReviewType) => {
if (reviewObj.locationId) {
const { data: location } = api.location.getById.useQuery({
id: reviewObj.locationId,
});
return location
? location.city +
(location.state ? `, ${location.state}` : "") +
", " +
location.country
: "N/A";
}
};

return (
<Card className="mx-auto w-[94%]">
<div className="flex pt-5">
Expand All @@ -29,10 +45,10 @@ export function ReviewCard({ reviewObj }: ReviewCardProps) {
</div>
<div className="align-center flex gap-2 pt-2">
<span
className={`${reviewObj.location ? "visibility: visible" : "visibility: hidden"}`}
className={`${locationName(reviewObj) ? "visibility: visible" : "visibility: hidden"}`}
>
{" "}
{reviewObj.location} •
{locationName(reviewObj)} •
</span>
<span>{reviewObj.workTerm}</span>
</div>
Expand Down
26 changes: 22 additions & 4 deletions apps/web/src/app/_components/reviews/role-card-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ export function RoleCardPreview({
roleId: role.data?.id ?? "",
});

// ===== LOCATION DATA ===== //
// TODO: Fix this
// const locationName = (reviewObj: ReviewType) => {
// if (reviewObj.locationId) {
// const { data: location } = api.location.getById.useQuery({
// id: reviewObj.locationId,
// });
// return location
// ? location.city +
// (location.state ? `, ${location.state}` : "") +
// ", " +
// location.country
// : "N/A";
// }
// };

return (
<Card
className={cn(
Expand All @@ -50,15 +66,17 @@ export function RoleCardPreview({
</CardTitle>
<div className="align-center flex gap-2 text-cooper-gray-400">
<span>{company.data?.name}</span>
{reviews.isSuccess && reviews.data.length > 0 && (
{/* {reviews.isSuccess && reviews.data.length > 0 && (
<span
className={`${reviews.data[0]?.location ? "visibility: visible" : "visibility: hidden"}`}
className={`${(reviews.data[0] ? locationName(reviews.data[0]) : "") ? "visibility: visible" : "visibility: hidden"}`}
>
</span>
)}
)} */}
{reviews.isSuccess && reviews.data.length > 0 && (
<span>{reviews.data[0]?.location}</span>
<span>
{/* {reviews.data[0] ? locationName(reviews.data[0]) : ""} */}
</span>
)}
</div>
</div>
Expand Down
26 changes: 22 additions & 4 deletions apps/web/src/app/_components/reviews/role-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ export function RoleInfo({ className, roleObj }: RoleCardProps) {
const companyData = companyQuery.data;
const averages = api.role.getAverageById.useQuery({ roleId: roleObj.id });

// ===== LOCATION DATA ===== //
// TODO: Fix this
// const locationName = (reviewObj: ReviewType) => {
// if (reviewObj.locationId) {
// const { data: location } = api.location.getById.useQuery({
// id: reviewObj.locationId,
// });
// return location
// ? location.city +
// (location.state ? `, ${location.state}` : "") +
// ", " +
// location.country
// : "N/A";
// }
// };

return (
<Card
className={cn(
Expand Down Expand Up @@ -70,15 +86,17 @@ export function RoleInfo({ className, roleObj }: RoleCardProps) {
</CardTitle>
<div className="align-center flex gap-2 text-cooper-gray-400">
<span>{companyData?.name}</span>
{reviews.isSuccess && reviews.data.length > 0 && (
{/* {reviews.isSuccess && reviews.data.length > 0 && (
<span
className={`${reviews.data[0]?.location ? "visibility: visible" : "visibility: hidden"}`}
className={`${(reviews.data[0] ? locationName(reviews.data[0]) : "") ? "visibility: visible" : "visibility: hidden"}`}
>
</span>
)}
)} */}
{reviews.isSuccess && reviews.data.length > 0 && (
<span>{reviews.data[0]?.location}</span>
<span>
{/* {reviews.data[0] ? locationName(reviews.data[0]) : ""} */}
</span>
)}
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/root.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
authRouter,
companyRouter,
locationRouter,
profileRouter,
reviewRouter,
roleRouter,
Expand All @@ -13,6 +14,7 @@ export const appRouter = createTRPCRouter({
role: roleRouter,
profile: profileRouter,
review: reviewRouter,
location: locationRouter,
});

// export type definition of API
Expand Down
18 changes: 17 additions & 1 deletion packages/api/src/router/company.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import { z } from "zod";

import type { ReviewType } from "@cooper/db/schema";
import { desc, eq } from "@cooper/db";
import { Company, CreateCompanySchema, Review } from "@cooper/db/schema";
import {
CompaniesToLocations,
Company,
CreateCompanySchema,
Location,
Review,
} from "@cooper/db/schema";

import { protectedProcedure, publicProcedure } from "../trpc";

Expand All @@ -30,6 +36,16 @@ export const companyRouter = {
});
}),

getLocationsById: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ ctx, input }) => {
return ctx.db
.select()
.from(CompaniesToLocations)
.innerJoin(Location, eq(CompaniesToLocations.locationId, Location.id))
.where(eq(CompaniesToLocations.companyId, input.id));
}),

create: protectedProcedure
.input(CreateCompanySchema)
.mutation(({ ctx, input }) => {
Expand Down
16 changes: 16 additions & 0 deletions packages/api/src/router/companytoLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { TRPCRouterRecord } from "@trpc/server";

import {
CompaniesToLocations,
CreateCompanyToLocationSchema,
} from "@cooper/db/schema";

import { protectedProcedure } from "../trpc";

export const companyToLocationRouter = {
create: protectedProcedure
.input(CreateCompanyToLocationSchema)
.mutation(({ ctx, input }) => {
return ctx.db.insert(CompaniesToLocations).values(input);
}),
} satisfies TRPCRouterRecord;
Loading
Loading