Skip to content

Commit

Permalink
query db with prisma, impliment search, start card refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
DCRepublic committed Oct 13, 2024
1 parent f655fa9 commit 31be113
Show file tree
Hide file tree
Showing 16 changed files with 10,127 additions and 152 deletions.
2 changes: 1 addition & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
package-lock=false

32 changes: 32 additions & 0 deletions app/api/course/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// api/test.ts

import { NextResponse, NextRequest } from "next/server";

import prisma from "@/lib/prisma";

export async function GET(request: NextRequest) {
const courses = await prisma.course.findMany({
include: {
sectionAttributes: true,
facultyMeet: {
include: {
meetingTimes: true,
},
},
instructor: true,
},
orderBy: {
courseTitle: "asc",
},
});

return NextResponse.json(courses, { status: 200 });
}

// To handle a POST request to /api
/*
export async function POST(request: NextRequest) {
return NextResponse.json(output, { status: 200 });
}
*/
19 changes: 0 additions & 19 deletions app/api/test/route.ts

This file was deleted.

55 changes: 29 additions & 26 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
"use client";
import * as React from "react";
import { Suspense } from "react";
import { Skeleton } from "@nextui-org/skeleton";

import { Input } from "@nextui-org/react";
import SearchIcon from "@mui/icons-material/Search";
import Search from "@/components/Search";
import { FullCourseList } from "@/components/FullCourseList";

import { CourseBig } from "@/components/course-big";
export default async function Page({
searchParams,
}: {
searchParams?: {
query?: string;
page?: string;
};
}) {
const query = searchParams?.query || "";
var homePageProps: any = {};

export default function Home() {
return (
<div className=" h-[550px] grid grid-cols-1 gap-4 -mt-10">
<Input
classNames={{
base: "max-w-full sm:max-w-[10rem] h-12 mt-0",
mainWrapper: "h-full",
input: "text-small",
inputWrapper:
"h-full font-normal text-default-500 bg-default-400/20 dark:bg-default-500/20",
}}
placeholder="Type to search..."
size="sm"
startContent={<SearchIcon />}
type="search"
/>
homePageProps["fullCourseList"] = (
<Suspense fallback={<Skeleton className="rounded-lg w-7/12" />}>
<FullCourseList query={query} />
</Suspense>
);

<div className="grid gap-4 overflow-scroll w-7/12">
<CourseBig color="bg-red-500" />
<CourseBig color="bg-yellow-500" />
<CourseBig color="bg-green-500" />
<CourseBig color="bg-green-500" />
return <Home {...homePageProps} />; // return with no events
}
async function Home(props: any) {
return (
<>
<div className=" h-[550px] grid grid-cols-1 gap-4 -mt-10">
<Search />
{props.fullCourseList}
</div>
</div>
</>
);
}
150 changes: 150 additions & 0 deletions components/CourseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"use client";
import { Card, CardBody } from "@nextui-org/react";
import { tv } from "tailwind-variants";

export const card = tv({
slots: {
base: "bg-light_foreground min-h-40 max-h-96 w-full rounded-sm scroll-none",
avatar:
"w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto drop-shadow-lg",
wrapper: "flex-1 pt-6 md:p-8 text-center md:text-left space-y-4",
description: "text-md font-medium ",
infoWrapper: "font-medium",
name: "text-sm text-sky-500 dark:text-sky-400",
role: "font-bold text-primary ",
sideColor: "w-2 h-full bg-red-500",
},
});

const {
base,
avatar,
wrapper,
description,
infoWrapper,
name,
role,
sideColor,
} = card();

export default function CourseCard(props: any) {
return (
<>
<Card key={props.course.id} className={base()} shadow="sm">
<div
className={`absolute top-0 left-0 h-full w-3 bg-red-500 rounded-full`}
/>
<CardBody className="ml-4 overflow-clip ">
<div className="grid grid-cols-1 grid-rows-2">
<div className="flex justify-between w-11/12">
<span>
<p className={role()}>
{props.course.subject +
" " +
props.course.courseNumber +
" - " +
props.course.courseTitle}
</p>
</span>
<span className="text-center">
<div className="text-xs">
{props.course.creditHours} credit(s)
</div>
<div className="text-xs ">
{props.course.courseReferenceNumber.replace(
props.course.year,
""
)}
</div>
</span>
</div>

<div className="flex w-11/12">
<div>
<p className={role()}>Availability</p>
<div className="text-xs">
{props.course.enrollment}/{props.course.maximumEnrollment}
</div>
</div>
<div>
<p className={role()}>Attributes</p>
{props.course.sectionAttributes.map((attribute: any) => (
<div
key={attribute.courseReferenceNumber}
className="text-xs "
>
{attribute.code}
</div>
))}
</div>

{props.course.facultyMeet.meetingTimes ? (
<div>
<p className={role()}>Days</p>
<div className="text-xs mt-2">
{props.course.facultyMeet.meetingTimes.monday ? "M" : null}{" "}
{props.course.facultyMeet.meetingTimes.tuesday ? "T" : null}{" "}
{props.course.facultyMeet.meetingTimes.wednesday
? "W"
: null}{" "}
{props.course.facultyMeet.meetingTimes.thursday
? "TH"
: null}{" "}
{props.course.facultyMeet.meetingTimes.friday ? "F" : null}{" "}
{props.course.facultyMeet.meetingTimes.saturday
? "Sat"
: null}{" "}
{props.course.facultyMeet.meetingTimes.sunday
? "Sun"
: null}
</div>
</div>
) : null}

{props.course.facultyMeet.meetingTimes ? (
<div className="mt-2">
<p className={role()}>Time</p>
<div className="text-xs ">
{" "}
{props.course.facultyMeet.meetingTimes.beginTime.slice(
0,
2
) +
":" +
props.course.facultyMeet.meetingTimes.beginTime.slice(
2
)}{" "}
-{" "}
{props.course.facultyMeet.meetingTimes.endTime.slice(0, 2) +
":" +
props.course.facultyMeet.meetingTimes.endTime.slice(2)}
</div>
</div>
) : null}
</div>
<div className="grid-rows-2 grid-cols-1 ">
{props.course.instructor ? (
<div>
<p className={role()}>Instructor</p>
<div className="text-xs">
{props.course.instructor.displayName}
</div>
</div>
) : null}
{props.course.facultyMeet.meetingTimes ? (
<div className="mt-2">
<p className={role()}>Room</p>
<p className="text-xs">
{props.course.facultyMeet.meetingTimes.buildingDescription}
{<br />}
{props.course.facultyMeet.meetingTimes.room}
</p>
</div>
) : null}
</div>
</div>
</CardBody>
</Card>
</>
);
}
69 changes: 69 additions & 0 deletions components/FullCourseList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Course } from "@prisma/client";

import prisma from "../lib/prisma";

import CourseCard from "./CourseCard";

async function getCourses(query: string) {
return await prisma.course.findMany({
include: {
sectionAttributes: true,
facultyMeet: {
include: {
meetingTimes: true,
},
},
instructor: true,
},

orderBy: {
courseTitle: "desc",
},
where: {
...(query
? {
OR: [
{
courseTitle: {
search: query.replace(/[\s\n\t]/g, "_"),
},
},
{
subject: {
search: query.replace(/[\s\n\t]/g, "_"),
},
},
{
courseNumber: {
search: query.replace(/[\s\n\t]/g, "_"),
},
},
{
instructor: {
displayName: {
search: query.replace(/[\s\n\t]/g, "_"),
},
},
},
],
}
: {}),
},
});
}

export async function FullCourseList({ query }: { query: string }) {
const courseList: Course[] = await getCourses(query);

return (
<>
<div className="grid gap-4 overflow-scroll w-7/12">
{courseList?.map((course: any) => (
<div key={course.id}>
<CourseCard course={course} />
</div>
))}
</div>
</>
);
}
38 changes: 38 additions & 0 deletions components/Search.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import { Input } from "@nextui-org/input";
import { useSearchParams, usePathname, useRouter } from "next/navigation";
import { useDebouncedCallback } from "use-debounce";
export default function Search(props: any) {
const searchParams = useSearchParams();

const pathname = usePathname();
const { replace } = useRouter();

const handleSearch = useDebouncedCallback((term: string) => {
const params = new URLSearchParams(searchParams);

if (term) {
params.set("query", term);
} else {
params.delete("query");
}
replace(`${pathname}?${params.toString()}`);
});

return (
<>
<Input
isClearable
className="max-w-xs"
defaultValue={searchParams.get("query")?.toString()}
label="Search"
labelPlacement="inside"
variant="bordered"
onChange={(e) => {
handleSearch(e.target.value);
}}
/>
</>
);
}
Loading

0 comments on commit 31be113

Please sign in to comment.