diff --git a/client/components/Results/components/Content/index.js b/client/components/Results/components/Content/index.js index 0f75358..3e0bab1 100644 --- a/client/components/Results/components/Content/index.js +++ b/client/components/Results/components/Content/index.js @@ -24,7 +24,7 @@ const Spinner = styled(Spin)` display: block !important; `; -export default function Content({ section, relatedSections, loadingSection, handleRelatedSectionClick }) { +export default function Content({ section, relatedSections, loadingSection, handleRelatedSectionClick, error }) { if (section) { return ; } else if (loadingSection) { @@ -33,6 +33,12 @@ export default function Content({ section, relatedSections, loadingSection, hand ); + } else if (error) { + return ( + + We had trouble loading that for you, please try again. + + ); } else { return ( diff --git a/client/components/Results/components/List/index.js b/client/components/Results/components/List/index.js index c77d80d..8c8086a 100644 --- a/client/components/Results/components/List/index.js +++ b/client/components/Results/components/List/index.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { List, Spin, Popover as AntPopover } from 'antd'; import { FrownTwoTone, UserOutlined } from '@ant-design/icons'; import general from '../../../../utils/general'; @@ -91,14 +91,20 @@ const IconText = ({ icon, text }) => ( ); -export default function ResultsList({ loading, id, data, onClick }) { +export default function ResultsList({ loading, id, data, onClick, error }) { + const [page, setPage] = useState(1); + + useEffect(() => { + setPage(1); + }, [data]); + const popover = (

Because of FERPA restrictions, grade data for certain classes — in particular, classes with a small number of students — is unavailable.

); - const empty = ( + const emptyMessage = ( We weren't able to find that. Try searching for something else! @@ -110,6 +116,13 @@ export default function ResultsList({ loading, id, data, onClick }) { ); + const errorMessage = ( + + + We had trouble getting that for you, please try again. + + ); + if (data) { if (data.length < 1) { return empty; @@ -123,7 +136,9 @@ export default function ResultsList({ loading, id, data, onClick }) { style: { marginRight: '10px' }, - showSizeChanger: false + showSizeChanger: false, + current: page, + onChange: (page) => setPage(page) }} dataSource={data} renderItem={item => { @@ -159,7 +174,9 @@ export default function ResultsList({ loading, id, data, onClick }) { ); + } else if (error) { + return errorMessage; } else { - return empty; + return emptyMessage; } } diff --git a/client/components/Results/index.js b/client/components/Results/index.js index a1bf8fd..62aa37d 100644 --- a/client/components/Results/index.js +++ b/client/components/Results/index.js @@ -2,10 +2,11 @@ import React, { useEffect, useState, useRef } from 'react'; import { List, Content } from './components'; import { Form } from '../'; import { Row, Col } from 'antd'; -import * as sectionModule from '../../modules/section'; +import { fetchSections, fetchSection } from '../../modules/section'; import { useRouter } from 'next/router'; import styled from 'styled-components'; import { animateScroll as scroll } from 'react-scroll'; +import { useQuery } from 'react-query'; const Container = styled.div` display: block; @@ -45,84 +46,40 @@ export default function Results() { const router = useRouter(); const { search, sectionId } = router.query; - const [sections, setSections] = useState(null); - const [loadingSections, setLoadingSections] = useState(true); - - const [relatedSections, setRelatedSections] = useState(null); - - const [section, setSection] = useState(null); - const [loadingSection, setLoadingSection] = useState(false); - - const [sortParams, setSortParams] = useState({ - sortField: 'year', - sortDirection: 'DESC' - }); - - useEffect(() => { - async function fetchSections() { - setSections(null); - setSection(null); - - if (search) { - setLoadingSections(true); - - const response = await sectionModule.fetchSections({ search, ...sortParams }); - - setSections(response); - setLoadingSections(false); - } - } - - fetchSections(); - }, [search]); - - useEffect(() => { - async function fetchSection() { - if (sectionId != null) { - setSection(null); - setLoadingSection(true); - - const response = await sectionModule.fetchSection(sectionId); - - setSection(response); - setLoadingSection(false); - } - } - - fetchSection(); - }, [sectionId]); - - useEffect(() => { - async function fetchRelatedSections() { - if (section) { - setRelatedSections(null); - - const response = await sectionModule.fetchSections({ - courseNumber: section.course.number, - coursePrefix: section.course.prefix, - }); - - setRelatedSections(response); - } - } - - fetchRelatedSections(); - }, [section]); + const { data: sections, status: sectionsStatus, error: sectionsError } = useQuery( + search && [ + 'sections', + { search, sortField: 'year', sortDirection: 'DESC' }, + ], + fetchSections + ); + const { data: section, status: sectionStatus, error: sectionError } = useQuery(sectionId, fetchSection); + const { data: relatedSections } = useQuery( + section && [ + 'relatedSections', + { + courseNumber: section.course.number, + coursePrefix: section.course.prefix, + }, + ], + fetchSections + ); function handleSubmit({ search }) { router.push({ pathname: '/results', - query: { search } + query: { search }, }); } function handleClick(id) { router.push({ pathname: '/results', - query: { search, sectionId: id } + query: { search, sectionId: id }, }); - const scrollDistance = window.pageYOffset + scrollRef.current.getBoundingClientRect().top + const scrollDistance = + window.pageYOffset + scrollRef.current.getBoundingClientRect().top; scroll.scrollTo(scrollDistance); } @@ -130,34 +87,52 @@ export default function Results() { function handleRelatedSectionClick(search, id) { router.push({ pathname: '/results', - query: { search, sectionId: id } + query: { search, sectionId: id }, }); - const scrollDistance = window.pageYOffset + scrollRef.current.getBoundingClientRect().top + const scrollDistance = + window.pageYOffset + scrollRef.current.getBoundingClientRect().top; scroll.scrollTo(scrollDistance); } - return ( + return ( - +
- + - + - + -
- +
diff --git a/client/modules/section/index.js b/client/modules/section/index.js index 8f29566..df3afe2 100644 --- a/client/modules/section/index.js +++ b/client/modules/section/index.js @@ -1,26 +1,18 @@ import data from '../../utils/data'; import utils from './utils'; -export async function fetchSections(params) { - try { - let response = await data.request('section', 'get', null, params); +export async function fetchSections(key, params) { + let response = await data.request('section', 'get', null, params); - response = utils.buildSectionNames(response); + response = utils.buildSectionNames(response); - return response; - } catch (e) { - throw e; - } + return response; } export async function fetchSection(id) { - try { - let response = await data.request('section', 'get', id); + let response = await data.request('section', 'get', id); - response = utils.buildSectionName(response); + response = utils.buildSectionName(response); - return response; - } catch (e) { - throw e; - } + return response; } diff --git a/client/package-lock.json b/client/package-lock.json index b184a0d..b259068 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "utd-grades-client", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1337,6 +1337,11 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "@scarf/scarf": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.0.4.tgz", + "integrity": "sha512-lkhjzeYyYAG4VvdrjvbZCOYzXH5vCwfzYj9xJ4zHDgyYIOzObZwcsbW6W1q5Z4tywrb14oG/tfsFAMMQPCTFqw==" + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -6617,6 +6622,15 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-query": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-1.2.9.tgz", + "integrity": "sha512-f4o9USEuWNIfQblWjWHXUvTTTRvvoEVKv++ZDLTSKGFzicxeEXSmQtEzIeT9l7z4VptJ6nQB8ypga6aTrKqYYw==", + "requires": { + "@scarf/scarf": "^1.0.0", + "ts-toolbelt": "^6.4.2" + } + }, "react-scroll": { "version": "1.7.16", "resolved": "https://registry.npmjs.org/react-scroll/-/react-scroll-1.7.16.tgz", @@ -7970,6 +7984,11 @@ "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" }, + "ts-toolbelt": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.5.1.tgz", + "integrity": "sha512-zjnZ/vy1eUA0li3H0JXecl0R3jiP42snpLimsrppt9V3LLbM4NM4jMgjXQ4S67hvehq+r9CxpX4Wj6RnFRzReA==" + }, "tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", diff --git a/client/package.json b/client/package.json index a55a666..9239875 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "utd-grades-client", - "version": "1.0.0", + "version": "1.1.0", "private": true, "scripts": { "dev": "next dev", @@ -17,6 +17,7 @@ "next": "9.3.5", "react": "16.13.1", "react-dom": "16.13.1", + "react-query": "^1.2.9", "react-scroll": "^1.7.16", "react-transition-group": "^4.3.0", "styled-components": "^5.1.0"