Skip to content

Commit

Permalink
Merge branch 'main' into UpdateSearchBar
Browse files Browse the repository at this point in the history
  • Loading branch information
banushi-a authored Nov 23, 2024
2 parents 87f9d61 + 65750da commit 82c0553
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 15 deletions.
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@trpc/react-query": "11.0.0-rc.441",
"@trpc/server": "11.0.0-rc.441",
"dayjs": "^1.11.13",
"fuse.js": "^7.0.0",
"geist": "^1.3.0",
"lucide-react": "^0.436.0",
"next": "^14.2.4",
Expand Down
17 changes: 15 additions & 2 deletions apps/web/src/app/(pages)/(dashboard)/roles/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default function Roles({
searchParams,
}: {
searchParams?: {
search?: string;
cycle?: WorkTermType;
term?: WorkEnvironmentType;
};
Expand Down Expand Up @@ -59,19 +60,31 @@ export default function Roles({
variant: "destructive",
});
}
}, [toast, mounted]);
}, [
toast,
mounted,
validationResult.success,
validationResult.error?.issues,
]);

const reviews = api.review.list.useQuery({
search: searchParams?.search,
options: validationResult.success ? validationResult.data : {},
});

const [selectedReview, setSelectedReview] = useState<ReviewType | undefined>(
reviews.data ? reviews.data[0] : undefined,
);

useEffect(() => {
if (reviews.data) {
setSelectedReview(reviews.data[0]);
}
}, [reviews.data]);

return (
<>
<SearchFilter />
<SearchFilter search={searchParams?.search} />
{reviews.data && (
<div className="mb-8 grid h-[70dvh] w-4/5 grid-cols-5 gap-4 lg:w-3/4">
<div className="col-span-2 gap-3 overflow-scroll pr-4">
Expand Down
13 changes: 11 additions & 2 deletions apps/web/src/app/_components/search/search-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,20 @@ const formSchema = z.object({

export type SearchFilterFormType = typeof formSchema;

export default function SearchFilter() {
interface SearchFilterProps {
search?: string;
}

/**
* Handles searching logic, updates the search param base on user search and passes the text to backend with fuzzy searching.
* @param param0 user input text that's passed to the fuzzy search
* @returns the search bar with the user inputted text
*/
export default function SearchFilter({ search }: SearchFilterProps) {
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
searchText: "",
searchText: search ?? "",
},
});

Expand Down
1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest",
"clean": "rm -rf .turbo dist node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint",
Expand Down
18 changes: 16 additions & 2 deletions packages/api/src/router/review.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { TRPCRouterRecord } from "@trpc/server";
import Fuse from "fuse.js";
import { z } from "zod";

import { and, desc, eq } from "@cooper/db";
Expand All @@ -10,6 +11,7 @@ export const reviewRouter = {
list: publicProcedure
.input(
z.object({
search: z.string().optional(),
options: z
.object({
cycle: z.enum(["SPRING", "FALL", "SUMMER"]).optional(),
Expand All @@ -18,18 +20,30 @@ export const reviewRouter = {
.optional(),
}),
)
.query(({ ctx, input }) => {
.query(async ({ ctx, input }) => {
const { options } = input;

const conditions = [
options?.cycle && eq(Review.workTerm, options.cycle),
options?.term && eq(Review.workEnvironment, options.term),
].filter(Boolean);

return ctx.db.query.Review.findMany({
const reviews = await ctx.db.query.Review.findMany({
orderBy: desc(Review.id),
where: conditions.length > 0 ? and(...conditions) : undefined,
});

if (!input.search) {
return reviews;
}

const fuseOptions = {
keys: ["reviewHeadline", "textReview", "location"],
};

const fuse = new Fuse(reviews, fuseOptions);

return fuse.search(input.search).map((result) => result.item);
}),

getByRole: publicProcedure
Expand Down
5 changes: 5 additions & 0 deletions packages/api/tests/mocks/review.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const data = [
{ id: "1", workTerm: "SPRING", workEnvironment: "REMOTE" },
{ id: "2", workTerm: "FALL", workEnvironment: "INPERSON" },
{ id: "3", workTerm: "SUMMER", workEnvironment: "HYBRID" },
];
102 changes: 102 additions & 0 deletions packages/api/tests/review.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/unbound-method */
import { beforeEach, describe, expect, test, vi } from "vitest";

import type { Session } from "@cooper/auth";
import type { ReviewType } from "@cooper/db/schema";
import { and, eq } from "@cooper/db";
import { db } from "@cooper/db/client";
import { Review } from "@cooper/db/schema";

import { appRouter } from "../src/root";
import { createCallerFactory, createTRPCContext } from "../src/trpc";
import { data } from "./mocks/review";

vi.mock("@cooper/db/client", () => ({
db: {
query: {
Review: {
findMany: vi.fn(),
},
},
},
}));

vi.mock("@cooper/auth", () => ({
auth: vi.fn(),
}));

describe("Review Router", async () => {
beforeEach(() => {
vi.restoreAllMocks();
vi.mocked(db.query.Review.findMany).mockResolvedValue(data as ReviewType[]);
});

const session: Session = {
user: {
id: "1",
},
expires: "1",
};

const ctx = await createTRPCContext({
session,
headers: new Headers(),
});

const caller = createCallerFactory(appRouter)(ctx);

test("list endpoint returns all reviews", async () => {
const reviews = await caller.review.list({});

expect(reviews).toEqual(data);

expect(db.query.Review.findMany).toHaveBeenCalledWith({
orderBy: expect.anything(),
where: undefined,
});
});

test("list endpoint with cycle filter", async () => {
await caller.review.list({
options: {
cycle: "SPRING",
},
});

expect(db.query.Review.findMany).toHaveBeenCalledWith({
orderBy: expect.anything(),
where: and(eq(Review.workTerm, "SPRING")),
});
});

test("list endpoint with term filter", async () => {
await caller.review.list({
options: {
term: "REMOTE",
},
});

expect(db.query.Review.findMany).toHaveBeenCalledWith({
orderBy: expect.anything(),
where: and(eq(Review.workEnvironment, "REMOTE")),
});
});

test("list endpoint with cycle and term filter", async () => {
await caller.review.list({
options: {
cycle: "SPRING",
term: "REMOTE",
},
});

expect(db.query.Review.findMany).toHaveBeenCalledWith({
orderBy: expect.anything(),
where: and(
eq(Review.workTerm, "SPRING"),
eq(Review.workEnvironment, "REMOTE"),
),
});
});
});
2 changes: 1 addition & 1 deletion packages/api/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"outDir": "dist",
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
},
"include": ["src"],
"include": ["src", "tests"],
"exclude": ["node_modules"]
}
16 changes: 8 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
"start": {
"cache": false,
"interactive": true
},
"test": {
"cache": true,
"interactive": true
}
},
"globalEnv": [
Expand Down

0 comments on commit 82c0553

Please sign in to comment.