From 8ddb24fc27eac0f659ed6881b2f20eef9de34dea Mon Sep 17 00:00:00 2001 From: Damian <37555910+DCRepublic@users.noreply.github.com> Date: Wed, 6 Nov 2024 15:24:03 -0500 Subject: [PATCH] search, filters, calendar --- app/calendar/page.tsx | 39 ++- app/page.tsx | 33 ++- components/Calendar.tsx | 4 +- components/CourseCard.tsx | 2 +- components/CreatePlan.tsx | 21 +- components/FullCourseList.tsx | 258 +++--------------- components/PlanCard.tsx | 41 +++ components/PlanCardList.tsx | 38 +++ components/Search.tsx | 42 ++- package-lock.json | 3 +- .../20241106191528_reset/migration.sql | 12 + .../migration.sql | 3 + prisma/schema.prisma | 4 +- 13 files changed, 248 insertions(+), 252 deletions(-) create mode 100644 components/PlanCard.tsx create mode 100644 components/PlanCardList.tsx create mode 100644 prisma/migrations/20241106191528_reset/migration.sql create mode 100644 prisma/migrations/20241106191607_reset_strings/migration.sql 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 64f4ebd..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,18 +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"] = ( @@ -42,7 +70,7 @@ export default async function Page(props: { } > - + ); @@ -60,6 +88,7 @@ export default async function Page(props: { } async function Home(props: any) { const terms = await getCourses(); + const uniqueTimes = await getUniqueStartEndTimes(); return ( <> @@ -67,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 c19d977..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 ", }, }); 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 bed5fef..8e5958a 100644 --- a/components/FullCourseList.tsx +++ b/components/FullCourseList.tsx @@ -3,10 +3,17 @@ import prisma from "../lib/prisma"; import CourseCard from "./CourseCard"; import { getPlanCookie } from "../app/actions"; -async function getCourses(query: string, term: string, dotw: Array) { +async function getCourses( + query: string, + term: string, + dotw: Array, + stime: Array +) { //let DOTW: Array = dotw.split(","); - let times = ["0955"]; + let startTime = stime.toString().split(",").filter(Number); + + console.log(startTime); return await prisma.course.findMany({ relationLoadStrategy: "join", // or 'query' @@ -20,15 +27,35 @@ async function getCourses(query: string, term: string, dotw: Array) { instructor: true, }, - orderBy: { - _relevance: { - fields: ["courseTitle", "subject", "courseNumber"], - search: query.replace(/[\s\n\t]/g, "_"), - sort: "desc", + orderBy: [ + { + _relevance: { + fields: ["courseTitle", "subject", "courseNumber"], + search: query.replace(/[\s\n\t]/g, "_"), + sort: "desc", + }, }, - }, + {}, + ], where: { - year: term, + ...(term + ? { + year: term, + } + : {}), + //year: term, + + ...(startTime.length > 0 + ? { + facultyMeet: { + meetingTimes: { + beginTime: { + in: startTime, + }, + }, + }, + } + : {}), OR: [ { @@ -48,215 +75,6 @@ async function getCourses(query: string, term: string, dotw: Array) { }, ], }, - - /* - - { - 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, "_"), - }, - }, - }, - - facultyMeet: { - meetingTimes: { - OR: [ - { - monday: dotw.includes("monday"), - }, - { - tuesday: dotw.includes("tuesday"), - }, - { - wednesday: dotw.includes("wednesday"), - }, - { - thursday: dotw.includes("thursday"), - }, - { - friday: dotw.includes("friday"), - }, - { - saturday: dotw.includes("saturday"), - }, - { - sunday: dotw.includes("sunday"), - }, - ], - }, - }, - where: { - AND: [ - { - OR: [ - { year: term }, - { - - facultyMeet: { - OR: [ - { - meetingTimes: { - monday: dotw.includes("monday"), - }, - }, - { - meetingTimes: { - tuesday: dotw.includes("tuesday"), - }, - }, - { - meetingTimes: { - wednesday: dotw.includes("wednesday"), - }, - }, - { - meetingTimes: { - thursday: dotw.includes("thursday"), - }, - }, - { - meetingTimes: { - friday: dotw.includes("friday"), - }, - }, - { - meetingTimes: { - saturday: dotw.includes("saturday"), - }, - }, - { - meetingTimes: { - sunday: dotw.includes("sunday"), - }, - }, - ], - }, - } - - ],} - - - - /* - times.length > 0 - ? { - facultyMeet: { - OR: [ - { - meetingTimes: { - monday: dotw.includes("monday"), - }, - }, - { - meetingTimes: { - tuesday: dotw.includes("tuesday"), - }, - }, - { - meetingTimes: { - wednesday: dotw.includes("wednesday"), - }, - }, - { - meetingTimes: { - thursday: dotw.includes("thursday"), - }, - }, - { - meetingTimes: { - friday: dotw.includes("friday"), - }, - }, - { - meetingTimes: { - saturday: dotw.includes("saturday"), - }, - }, - { - meetingTimes: { - sunday: dotw.includes("sunday"), - }, - }, - ], - }, - } - : {}, - { - 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, "_"), - }, - }, - }, - ], - }, - ], - }, - - */ - /* Old where - where: { - ...(query || 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, "_"), - }, - }, - }, - ], - } - : {}), - - },*/ }); } @@ -264,12 +82,14 @@ export async function FullCourseList({ query, term, dotw, + stime, }: { query: string; term: string; dotw: Array; + stime: Array; }) { - const courseList: Course[] = await getCourses(query, term, dotw); + 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 e53746d..c562940 100644 --- a/components/Search.tsx +++ b/components/Search.tsx @@ -9,8 +9,9 @@ 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(); @@ -42,7 +43,12 @@ export default function Search(props: any) { useEffect(() => { // Update the document title using the browser API setSelectedDOTW(searchParams.get("dotw")?.toString().split(",")); - }, [searchParams.get("term")?.toString()]); + setSelectedStartTime(searchParams.get("stime")?.toString().split(",")); + //handleSelectionChange({ target: { value: selectedTerm } }); + }, [ + searchParams.get("term")?.toString(), + searchParams.get("stime")?.toString(), + ]); const handleDOTWChange = (e: any) => { setSelectedDOTW(...[e]); @@ -55,6 +61,17 @@ export default function Search(props: any) { 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 = []; @@ -84,6 +101,9 @@ export default function Search(props: any) { label="Search" labelPlacement="inside" variant="bordered" + onClear={() => { + handleSearch(""); + }} onChange={(e) => { handleSearch(e.target.value); }} @@ -91,7 +111,7 @@ export default function Search(props: any) { + +
); } diff --git a/package-lock.json b/package-lock.json index d9c7367..dab83bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", 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 968f9c2..77dc829 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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