diff --git a/app/calendar/page.tsx b/app/calendar/page.tsx index 5f24686..8b4c4c3 100644 --- a/app/calendar/page.tsx +++ b/app/calendar/page.tsx @@ -58,13 +58,13 @@ export default async function CalendarPage() { ], startTime: - meetingTimes?.beginTime.slice(0, 2) + + meetingTimes?.beginTime?.slice(0, 2) + ":" + - meetingTimes?.beginTime.slice(2), + meetingTimes?.beginTime?.slice(2), endTime: - meetingTimes?.endTime.slice(0, 2) + + meetingTimes?.endTime?.slice(0, 2) + ":" + - meetingTimes?.endTime.slice(2), + meetingTimes?.endTime?.slice(2), }); } } @@ -72,11 +72,40 @@ export default async function CalendarPage() { return output; } + async function getUniqueStartEndTimes() { + const maxstart = await prisma.meetingTime.findFirst({ + where: { + beginTime: { not: "" }, + }, + orderBy: { + beginTime: "desc", + }, + }); + const maxend = await prisma.meetingTime.findFirst({ + where: { + endTime: { not: "" }, + }, + orderBy: { + beginTime: "desc", + }, + }); + + let times = { + minTime: + maxstart?.beginTime.slice(0, 2) + ":" + maxstart?.beginTime.slice(2), + maxTime: + maxstart?.endTime.slice(0, 2) + ":" + maxstart?.beginTime.slice(2), + }; + console.log(times); + return times; + } + let events = await getEvents(); + let times = await getUniqueStartEndTimes(); return (
- +
diff --git a/app/page.tsx b/app/page.tsx index 61c0dfd..aeaaf9b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -9,6 +9,7 @@ import { auth } from "../lib/auth"; import prisma from "../lib/prisma"; import { Course, CoursePlan } from "@prisma/client"; import { getPlanCookie } from "../app/actions"; +import { PlanCardList } from "../components/PlanCardList"; async function getCourses() { const courses = await prisma.course.findMany(); @@ -22,16 +23,45 @@ async function getCourses() { return output; } +async function getUniqueStartEndTimes() { + const meetingTimes = await prisma.meetingTime.findMany({ + where: { + beginTime: { not: "" }, + }, + orderBy: { + beginTime: "asc", + }, + }); + let startTimes: any = []; + let endTimes: any = []; + + for (let i = 0; i < meetingTimes.length; i++) { + if (!startTimes.includes(meetingTimes[i].beginTime)) { + startTimes.push(meetingTimes[i].beginTime); + } + if (!endTimes.includes(meetingTimes[i].endTime)) { + endTimes.push(meetingTimes[i].endTime); + } + } + + let times = { startTimes: startTimes, endTimes: endTimes }; + return times; +} + export default async function Page(props: { searchParams?: Promise<{ query?: string; page?: string; term?: string; + dotw?: Array; + stime?: Array; }>; }) { const searchParams = await props.searchParams; const query = searchParams?.query || ""; const term = searchParams?.term || ""; + const dotw = searchParams?.dotw || []; + const stime = searchParams?.stime || []; var homePageProps: any = {}; homePageProps["fullCourseList"] = ( @@ -40,7 +70,7 @@ export default async function Page(props: { } > - + ); @@ -58,6 +88,7 @@ export default async function Page(props: { } async function Home(props: any) { const terms = await getCourses(); + const uniqueTimes = await getUniqueStartEndTimes(); return ( <> @@ -65,7 +96,7 @@ async function Home(props: any) {
- +
{props.fullCourseList} diff --git a/components/Calendar.tsx b/components/Calendar.tsx index 1d4da6f..2b43b10 100644 --- a/components/Calendar.tsx +++ b/components/Calendar.tsx @@ -46,11 +46,13 @@ export default function Calendar(props: any) { expandRows slotDuration="01:00:00" slotMinTime="08:00:00" - slotMaxTime="22:00:00" + slotMaxTime="23:00:00" + weekends={false} dayHeaderFormat={dayHeaderContent} events={props.events} headerToolbar={false} eventContent={renderEventContent} + editable={false} /> ); } diff --git a/components/CourseCard.tsx b/components/CourseCard.tsx index df414ff..75d0d1b 100644 --- a/components/CourseCard.tsx +++ b/components/CourseCard.tsx @@ -18,7 +18,7 @@ import AddIcon from "@mui/icons-material/Add"; import axios from "axios"; export const card = tv({ slots: { - base: "bg-light_foreground min-h-32 max-h-32 w-[98%] rounded-sm scroll-none drop-shadow-lg transition-colors", + base: "bg-light_foreground min-h-48 max-h-48 w-[98%] rounded-sm scroll-none drop-shadow-lg transition-colors", role: "font-bold text-primary ", }, }); @@ -124,6 +124,36 @@ export default function CourseCard(props: any) { ))}
+ {props.course.facultyMeet.meetingTimes ? ( +
+

Days

+
+ {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} +
+
+ ) : null} + {props.course.facultyMeet.meetingTimes ? ( +
+

Time

+
+ {" "} + {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)} +
+
+ ) : null} + {/*
diff --git a/components/CreatePlan.tsx b/components/CreatePlan.tsx index 71d7e18..80f288d 100644 --- a/components/CreatePlan.tsx +++ b/components/CreatePlan.tsx @@ -1,27 +1,12 @@ "use client"; import { Card, - CardBody, Divider, - Link, - User, - Popover, - PopoverTrigger, - PopoverContent, Input, Button, Skeleton, CardHeader, } from "@nextui-org/react"; -import { - Dropdown, - DropdownTrigger, - DropdownMenu, - DropdownItem, - RadioGroup, - Radio, -} from "@nextui-org/react"; -import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; import AddIcon from "@mui/icons-material/Add"; import DeleteIcon from "@mui/icons-material/Delete"; import IosShareIcon from "@mui/icons-material/IosShare"; @@ -33,11 +18,13 @@ import { useRouter } from "next/navigation"; import { InstructorCard } from "./InstructorCard"; import { usePathname } from "next/navigation"; import { useSession } from "next-auth/react"; -import { useEffect, useState } from "react"; +import { Suspense, useEffect, useState } from "react"; import useSWR from "swr"; import { setPlanCookie } from "../app/actions"; import { useCookies } from "next-client-cookies"; import { generateColorFromName } from "../components/primitives"; +import { FullCourseList } from "./FullCourseList"; +import { PlanCardList } from "./PlanCardList"; export default function CreatePlan(props: any) { const cookies = useCookies(); const router = useRouter(); @@ -57,7 +44,7 @@ export default function CreatePlan(props: any) { isLoading: coursePlansIsLoading, error: coursePlansError, } = useSWR("/api/getcourseplans", fetcher, { - refreshInterval: 1000, + refreshInterval: 2000, }); async function createPlan() { diff --git a/components/FullCourseList.tsx b/components/FullCourseList.tsx index c58d44d..8e5958a 100644 --- a/components/FullCourseList.tsx +++ b/components/FullCourseList.tsx @@ -1,9 +1,22 @@ -import { Course } from "@prisma/client"; +import { Course, Prisma } from "@prisma/client"; import prisma from "../lib/prisma"; import CourseCard from "./CourseCard"; import { getPlanCookie } from "../app/actions"; -async function getCourses(query: string, term: string) { + +async function getCourses( + query: string, + term: string, + dotw: Array, + stime: Array +) { + //let DOTW: Array = dotw.split(","); + + let startTime = stime.toString().split(",").filter(Number); + + console.log(startTime); return await prisma.course.findMany({ + relationLoadStrategy: "join", // or 'query' + include: { sectionAttributes: true, facultyMeet: { @@ -14,39 +27,53 @@ async function getCourses(query: string, term: string) { instructor: true, }, - orderBy: { - courseTitle: "desc", - }, + orderBy: [ + { + _relevance: { + fields: ["courseTitle", "subject", "courseNumber"], + search: query.replace(/[\s\n\t]/g, "_"), + sort: "desc", + }, + }, + {}, + ], where: { - ...(query + ...(term ? { year: term, - 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, "_"), - }, + } + : {}), + //year: term, + + ...(startTime.length > 0 + ? { + facultyMeet: { + meetingTimes: { + beginTime: { + in: startTime, }, }, - ], + }, } : {}), + + OR: [ + { + facultyMeet: { + meetingTimes: { + is: { + monday: dotw.includes("monday") ? true : Prisma.skip, + tuesday: dotw.includes("tuesday") ? true : Prisma.skip, + wednesday: dotw.includes("wednesday") ? true : Prisma.skip, + thursday: dotw.includes("thursday") ? true : Prisma.skip, + friday: dotw.includes("friday") ? true : Prisma.skip, + saturday: dotw.includes("saturday") ? true : Prisma.skip, + sunday: dotw.includes("sunday") ? true : Prisma.skip, + }, + }, + }, + }, + ], }, }); } @@ -54,11 +81,15 @@ async function getCourses(query: string, term: string) { export async function FullCourseList({ query, term, + dotw, + stime, }: { query: string; term: string; + dotw: Array; + stime: Array; }) { - const courseList: Course[] = await getCourses(query, term); + const courseList: Course[] = await getCourses(query, term, dotw, stime); return ( <> diff --git a/components/PlanCard.tsx b/components/PlanCard.tsx new file mode 100644 index 0000000..c212480 --- /dev/null +++ b/components/PlanCard.tsx @@ -0,0 +1,41 @@ +"use client"; +import { Card, Button, CardHeader } from "@nextui-org/react"; +import { generateColorFromName } from "../components/primitives"; + +export default function PlanCard(props: any) { + return ( + +
+ + +
+ {props.course.courseTitle.replace(/&/g, "&")} +
+ +
+ + ); +} diff --git a/components/PlanCardList.tsx b/components/PlanCardList.tsx new file mode 100644 index 0000000..66a959b --- /dev/null +++ b/components/PlanCardList.tsx @@ -0,0 +1,38 @@ +import { Course, Prisma } from "@prisma/client"; +import prisma from "../lib/prisma"; +import PlanCard from "./PlanCard"; +import { getPlanCookie } from "../app/actions"; + +async function getPlanCourses(planID: any) { + //let DOTW: Array = dotw.split(","); + + return await prisma.course.findMany({ + relationLoadStrategy: "join", // or 'query' + where: { + CoursePlan: { + some: { + id: parseInt(planID), + }, + }, + }, + include: { + CoursePlan: true, + }, + }); +} + +export async function PlanCardList({ planID }: { planID: any }) { + const courseList: Course[] = await getPlanCourses(planID); + + return ( + <> +
+ {courseList?.map((course: any) => ( +
+ +
+ ))} +
+ + ); +} diff --git a/components/Search.tsx b/components/Search.tsx index a2c4dac..c562940 100644 --- a/components/Search.tsx +++ b/components/Search.tsx @@ -4,13 +4,14 @@ import { Input } from "@nextui-org/input"; import { useSearchParams, usePathname, useRouter } from "next/navigation"; import { useDebouncedCallback } from "use-debounce"; import { Select, SelectItem } from "@nextui-org/react"; -import { useState } from "react"; +import { useEffect, useState } from "react"; export default function Search(props: any) { let router = useRouter(); const searchParams = useSearchParams(); - const [selectedTerm, setSelectedTerm]: any = useState(["S2025"]); - + const [selectedTerm, setSelectedTerm]: any = useState([]); + const [selectedDOTW, setSelectedDOTW]: any = useState([]); + const [selectedStartTime, setSelectedStartTime]: any = useState([]); const pathname = usePathname(); const { replace } = useRouter(); @@ -18,16 +19,13 @@ export default function Search(props: any) { const params = new URLSearchParams(searchParams); if (term) { params.set("query", term); - params.set("term", selectedTerm[0]); } else { params.delete("query"); - params.delete("term"); } replace(`${pathname}?${params.toString()}`); }); const handleSelectionChange = (e: any) => { - console.log(e.target.value); setSelectedTerm([e.target.value]); const params = new URLSearchParams(searchParams); if (e.target.value) { @@ -42,6 +40,38 @@ export default function Search(props: any) { //setPlanCookie(e.target.value); }; + useEffect(() => { + // Update the document title using the browser API + setSelectedDOTW(searchParams.get("dotw")?.toString().split(",")); + setSelectedStartTime(searchParams.get("stime")?.toString().split(",")); + //handleSelectionChange({ target: { value: selectedTerm } }); + }, [ + searchParams.get("term")?.toString(), + searchParams.get("stime")?.toString(), + ]); + + const handleDOTWChange = (e: any) => { + setSelectedDOTW(...[e]); + const params = new URLSearchParams(searchParams); + if (e.size > 0) { + params.set("dotw", Array.from(e).join(",")); + } else { + params.delete("dotw"); + } + replace(`${pathname}?${params.toString()}`); + }; + + const handleSTimeChange = (e: any) => { + setSelectedStartTime(...[e]); + const params = new URLSearchParams(searchParams); + if (e.size > 0) { + params.set("stime", Array.from(e).join(",")); + } else { + params.delete("stime"); + } + replace(`${pathname}?${params.toString()}`); + }; + const RenderSelectOptions = () => { let output = []; @@ -61,17 +91,19 @@ export default function Search(props: any) { }) .map((term: any) => {term.title}); }; - console.log(props.terms); return ( -
+
{ + handleSearch(""); + }} onChange={(e) => { handleSearch(e.target.value); }} @@ -79,7 +111,8 @@ export default function Search(props: any) { + + +
); } diff --git a/package-lock.json b/package-lock.json index 622da11..dab83bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@nextui-org/system": "2.2.6", "@nextui-org/tabs": "^2.0.37", "@nextui-org/theme": "2.2.11", - "@prisma/client": "^5.21.1", + "@prisma/client": "^5.22.0", "@react-aria/ssr": "3.9.6", "@react-aria/visually-hidden": "3.8.17", "@types/node": "22.8.1", @@ -62,8 +62,7 @@ "tailwindcss": "3.4.14", "typescript": "5.6.3", "use-debounce": "^10.0.4" - }, - "devDependencies": {} + } }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", @@ -3151,9 +3150,9 @@ } }, "node_modules/@prisma/client": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", - "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { diff --git a/package.json b/package.json index b2f3ad8..0b8d7dd 100644 --- a/package.json +++ b/package.json @@ -29,26 +29,15 @@ "@nextui-org/system": "2.2.6", "@nextui-org/tabs": "^2.0.37", "@nextui-org/theme": "2.2.11", - "@prisma/client": "^5.21.1", + "@prisma/client": "^5.22.0", "@react-aria/ssr": "3.9.6", "@react-aria/visually-hidden": "3.8.17", - "axios": "^1.7.7", - "clsx": "2.1.1", - "framer-motion": "~11.11.10", - "intl-messageformat": "^10.7.3", - "moment": "^2.30.1", - "next": "^15.0.1", - "next-auth": "^4.24.10", - "next-client-cookies": "^2.0.0", - "next-themes": "^0.3.0", - "react": "18.3.1", - "react-dom": "18.3.1", - "swr": "^2.2.5", - "use-debounce": "^10.0.4", "@types/node": "22.8.1", "@types/react": "18.3.12", "@types/react-dom": "18.3.1", "autoprefixer": "10.4.20", + "axios": "^1.7.7", + "clsx": "2.1.1", "eslint": "^9.13.0", "eslint-config-next": "15.0.1", "eslint-config-prettier": "^9.1.0", @@ -59,12 +48,20 @@ "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-unused-imports": "^4.1.4", + "framer-motion": "~11.11.10", + "intl-messageformat": "^10.7.3", + "moment": "^2.30.1", + "next": "^15.0.1", + "next-auth": "^4.24.10", + "next-client-cookies": "^2.0.0", + "next-themes": "^0.3.0", "postcss": "8.4.47", + "react": "18.3.1", + "react-dom": "18.3.1", + "swr": "^2.2.5", "tailwind-variants": "^0.2.1", "tailwindcss": "3.4.14", - "typescript": "5.6.3" - - }, - "devDependencies": { + "typescript": "5.6.3", + "use-debounce": "^10.0.4" } } diff --git a/prisma/migrations/20241106191528_reset/migration.sql b/prisma/migrations/20241106191528_reset/migration.sql new file mode 100644 index 0000000..cc0e067 --- /dev/null +++ b/prisma/migrations/20241106191528_reset/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - The `beginTime` column on the `MeetingTime` table would be dropped and recreated. This will lead to data loss if there is data in the column. + - The `endTime` column on the `MeetingTime` table would be dropped and recreated. This will lead to data loss if there is data in the column. + +*/ +-- AlterTable +ALTER TABLE "MeetingTime" DROP COLUMN "beginTime", +ADD COLUMN "beginTime" TIME(2), +DROP COLUMN "endTime", +ADD COLUMN "endTime" TIME(2); diff --git a/prisma/migrations/20241106191607_reset_strings/migration.sql b/prisma/migrations/20241106191607_reset_strings/migration.sql new file mode 100644 index 0000000..8d6116b --- /dev/null +++ b/prisma/migrations/20241106191607_reset_strings/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "MeetingTime" ALTER COLUMN "beginTime" SET DATA TYPE TEXT, +ALTER COLUMN "endTime" SET DATA TYPE TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index df3b374..77dc829 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,6 @@ generator client { provider = "prisma-client-js" - previewFeatures = ["fullTextSearch", "relationJoins"] + previewFeatures = ["fullTextSearch", "relationJoins", "strictUndefinedChecks"] } datasource db { @@ -70,14 +70,14 @@ model MeetingsFaculty { model MeetingTime { id Int @id @default(autoincrement()) - beginTime String + beginTime String? building String buildingDescription String room String category String courseReferenceNumber String @unique endDate String - endTime String + endTime String? startDate String hoursWeek Float meetingType String