Skip to content
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

Update geneds page to use tanstack #181

Merged
merged 3 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 23 additions & 31 deletions apps/frontend/src/app/api/geneds.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { RootState } from "~/store";
import axios from "axios";
import { Gened } from "~/app/types";
import { GetToken } from "@clerk/types";

export const fetchGenedsBySchool = createAsyncThunk<
Gened[],
string,
{ state: RootState }
>("fetchGenedsBySchool", async (school: string, thunkAPI) => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/geneds?`;
const state = thunkAPI.getState();
export const fetchGenedsBySchool = async (selectedSchool: string, isSignedIn: boolean | undefined, getToken: GetToken): Promise<Gened[]> => {
const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/geneds`;

const params = new URLSearchParams();
params.append("school", school);
params.append("school", selectedSchool);

if (state.user.loggedIn && state.user.token) {
return (
await fetch(url + params.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
token: state.user.token,
}),
})
).json();
const token = await getToken()

if (isSignedIn && token) {
const response = await axios.post(url, { token }, {
headers: {
"Content-Type": "application/json",
},
params,
});
return response.data;
} else {
return (
await fetch(url + params.toString(), {
method: "GET",
headers: {
"Content-Type": "application/json",
},
})
).json();
const response = await axios.get(url, {
headers: {
"Content-Type": "application/json",
},
params,
});
return response.data;
}
});
};
10 changes: 0 additions & 10 deletions apps/frontend/src/app/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
import { fetchFCEInfosByCourse, fetchFCEInfosByInstructor } from "./api/fce";
import { fetchAllInstructors } from "./api/instructors";
import Fuse, { FuseIndex } from "fuse.js";
import { fetchGenedsBySchool } from "./api/geneds";

/**
* This cache lasts for the duration of the user session
Expand All @@ -37,7 +36,6 @@ interface CacheState {
instructorsLoading: boolean;
instructorPage: number;
selectedInstructors: { instructor: string }[];
geneds: Gened[];
}

const initialState: CacheState = {
Expand All @@ -59,7 +57,6 @@ const initialState: CacheState = {
instructorsLoading: false,
instructorPage: 1,
selectedInstructors: [],
geneds: [],
};

export const selectCourseResults =
Expand Down Expand Up @@ -236,13 +233,6 @@ export const cacheSlice = createSlice({
state.selectedInstructors = action.payload;
}
});

builder
.addCase(fetchGenedsBySchool.fulfilled, (state, action) => {
if (action.payload) {
state.geneds = action.payload;
}
});
},
});

Expand Down
105 changes: 67 additions & 38 deletions apps/frontend/src/components/GenedsViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,79 @@ import { ChevronUpDownIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { Gened } from "~/app/types";
import { classNames } from "~/app/utils";
import { CheckIcon } from "@heroicons/react/20/solid";
import { SignInButton } from "@clerk/nextjs";
import { SignInButton, useAuth } from "@clerk/nextjs";
import { userSlice } from "~/app/user";
import { GENED_SCHOOLS, GENED_SOURCES } from "~/app/constants";
import Link from "~/components/Link";
import { useQuery } from "@tanstack/react-query";

const GenedsViewer = () => {
const dispatch = useAppDispatch();
const loggedIn = useAppSelector((state) => state.user.loggedIn);
const geneds = useAppSelector((state) => state.cache.geneds);
const selectedSchool = useAppSelector((state) => state.user.selectedSchool);
const selectedTags = useAppSelector((state) => state.user.selectedTags);
const aggregationOptions = useAppSelector((state) => state.user.fceAggregation);
const { isSignedIn, getToken } = useAuth();

const [tagQuery, setTagQuery] = useState("");
const [tags, setTags] = useState<string[]>([]);
const [data, setData] = useState<Gened[]>([]);
const [query, setQuery] = useState("");
const [searchQuery, setSearchQuery] = useState("");

const {isPending, error, data: geneds} = useQuery({
queryKey: ['geneds', selectedSchool, isSignedIn],
queryFn: () => fetchGenedsBySchool(selectedSchool, isSignedIn, getToken)
});

const deleteTag = (tagToDelete: string) => {
dispatch(userSlice.actions.setSelectedTags(selectedTags.filter((tag) => tag !== tagToDelete)));
};

useEffect(() => {
if (selectedSchool)
void dispatch(fetchGenedsBySchool(selectedSchool));
}, [dispatch, loggedIn, selectedSchool]);
const getGenedTable = () => {
if (isPending) {
return (
<div className="py-4 text-center text-gray-500 dark:text-zinc-400">
Loading...
</div>
);
}

if (error) {
console.log(error.message);

return (
<div className="py-4 text-center text-gray-500 dark:text-zinc-400">
Sorry there was an error in displaying this, please refresh!
</div>
);
}

if (!geneds || geneds.length === 0) {
return (
<div className="py-4 text-center text-gray-500 dark:text-zinc-400">
No geneds found for {selectedSchool}. If you would like to map geneds for your school, please fill in the feedback form in the sidebar!
</div>
);
}

return (
<>
<div className="px-3 pt-2 text-gray-600">
<b>{data?.length}</b> geneds found
</div>
<div className="p-2 mt-4 ml-2 mr-2 overflow-x-auto bg-gray-50 rounded-md">
<GenedsDataTable
data={data}
/>
</div>
</>
);
}

useEffect(() => {
if (geneds && geneds.map) {
if (geneds && geneds?.map) {
let mappedGeneds = geneds.map(gened => {
const instructor = gened.fces[0]?.instructor;

if (instructor === "SIRBU, MARVIN") console.log(gened)

const filtered = filterFCEs(gened.fces, aggregationOptions);
const aggregated = aggregateFCEs(filtered);
return {
Expand All @@ -55,24 +94,29 @@ const GenedsViewer = () => {
selectedTags.some((tag) => gened.tags?.includes(tag))
);
}
if (query.length > 0) {
if (searchQuery.length > 0) {
mappedGeneds = mappedGeneds.filter((gened) =>
gened.name?.toLowerCase().includes(query.toLowerCase()) ||
gened.instructor?.toLowerCase().includes(query.toLowerCase()) ||
gened.tags?.some((tag) => tag.toLowerCase().includes(query.toLowerCase())) ||
gened.desc?.toLowerCase().includes(query.toLowerCase()) ||
gened.courseID?.toLowerCase().includes(query.toLowerCase())
gened.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
gened.instructor?.toLowerCase().includes(searchQuery.toLowerCase()) ||
gened.tags?.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase())) ||
gened.desc?.toLowerCase().includes(searchQuery.toLowerCase()) ||
gened.courseID?.toLowerCase().includes(searchQuery.toLowerCase())
);
}
setData(mappedGeneds);
setTags([...new Set(geneds.map(gened => gened.tags).flat().filter((tag) => tag !== undefined))]);
}
}, [geneds, geneds.map, selectedTags, query]);
}, [geneds, geneds?.map, selectedTags, searchQuery]);

return (
<div className="p-3 m-2 bg-white rounded">
<div className="relative m-2">
<Combobox value={selectedSchool} onChange={(payload) => dispatch(userSlice.actions.setSelectedSchool(payload))}>
<Combobox value={selectedSchool}
onChange={(payload) => {
dispatch(userSlice.actions.setSelectedSchool(payload));
dispatch(userSlice.actions.setSelectedTags([]));
setSearchQuery("");
}}>
<Combobox.Label className="flex">
School
</Combobox.Label>
Expand Down Expand Up @@ -190,13 +234,13 @@ const GenedsViewer = () => {
</Combobox.Options>
</div>
</Combobox>
<Combobox value={query} onChange={(payload) => setQuery(payload)}>
<Combobox value={searchQuery} onChange={(payload) => setSearchQuery(payload)}>
<Combobox.Label className="flex pt-2">
Search
</Combobox.Label>
<Combobox.Input
className="relative mt-2 w-full cursor-default rounded border py-1 pl-1 pr-10 text-left transition duration-150 ease-in-out border-gray-200 sm:text-sm sm:leading-5"
onChange={(e) => setQuery(e.target.value)}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search courses by ID, description, instructor, name, or tag..."
/>
</Combobox>
Expand All @@ -212,11 +256,8 @@ const GenedsViewer = () => {
form in the sidebar!
</span>
</div>
<div className="px-3 pt-2 text-gray-600">
<b>{data.length}</b> geneds found
</div>
{
!loggedIn && (
!isSignedIn && (
<div className="flex justify-center">
<button
className="px-4 py-2 mt-4 text-sm font-medium text-white bg-gray-500 rounded-md hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
Expand All @@ -228,19 +269,7 @@ const GenedsViewer = () => {
</div>
)
}
{
data.length === 0 ? (
<div className="py-4 text-center text-gray-500 dark:text-zinc-400">
Loading...
</div>
) : (
<div className="p-2 mt-4 ml-2 mr-2 overflow-x-auto bg-gray-50 rounded-md">
<GenedsDataTable
data={data}
/>
</div>
)
}
{getGenedTable()}
</div>
);
}
Expand Down
49 changes: 28 additions & 21 deletions apps/frontend/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,36 @@ import Head from "next/head";
import { ClerkProvider } from "@clerk/nextjs";
import { PHProvider } from "~/app/providers";
import PostHogPageView from "~/app/PostHogPageView";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'

const queryClient = new QueryClient()

export default function MyApp({ Component, pageProps }: AppProps) {
return (
<PHProvider>
<ClerkProvider>
<Head>
<title>CMU Courses</title>
<meta
name="description"
content="CMU Courses brings together course information, schedules and FCE data to help you plan your semesters."
/>
<meta
name="keywords"
content="ScottyLabs, CMU, Carnegie Mellon, Courses, Course Tool, CMU Courses, FCEs, Ratings, Schedules, Scheduling"
/>
</Head>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<PostHogPageView />
<Component {...pageProps} />
</PersistGate>
</Provider>
</ClerkProvider>
</PHProvider>
<QueryClientProvider client={queryClient}>
<PHProvider>
<ClerkProvider>
<Head>
<title>CMU Courses</title>
<meta
name="description"
content="CMU Courses brings together course information, schedules and FCE data to help you plan your semesters."
/>
<meta
name="keywords"
content="ScottyLabs, CMU, Carnegie Mellon, Courses, Course Tool, CMU Courses, FCEs, Ratings, Schedules, Scheduling"
/>
</Head>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<PostHogPageView />
<Component {...pageProps} />
</PersistGate>
</Provider>
</ClerkProvider>
</PHProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Binary file modified bun.lockb
Binary file not shown.
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"devDependencies": {
"@nx/next": "19.6.3",
"@tanstack/eslint-plugin-query": "^5.61.3",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"eslint-config-prettier": "^8.8.0",
Expand All @@ -19,5 +20,9 @@
"workspaces": [
"apps/*",
"packages/*"
]
],
"dependencies": {
"@tanstack/react-query": "^5.61.3",
"@tanstack/react-query-devtools": "^5.61.3"
}
}
Loading