diff --git a/src/components/molecules/CurrentDirectoryRow/index.tsx b/src/components/molecules/CurrentDirectoryRow/index.tsx index c5ee5b682b40db60ea5b9c97bd4edacebff4dc05..e3d51b5cde7f0e9a928bde5f23c07b06335465a0 100644 --- a/src/components/molecules/CurrentDirectoryRow/index.tsx +++ b/src/components/molecules/CurrentDirectoryRow/index.tsx @@ -21,7 +21,7 @@ const CurrentDirectoryRow: React.FC<{ select: SelectionProps }> = ({ > {select.selectionItems.map(({ title, type, tags, id }) => { let normalIcon: IconDefinition; - if (type === undefined || tags === undefined) return; + if (type === undefined || tags === undefined) return null; switch (type) { case "pdf": normalIcon = faFilePdf; diff --git a/src/components/molecules/QuickAccessRow/index.tsx b/src/components/molecules/QuickAccessRow/index.tsx index 126eeb7b46dc0d36d40170938f4f539b602b411e..884f21a2065eccb18b9476d379b33b4d6cea4544 100644 --- a/src/components/molecules/QuickAccessRow/index.tsx +++ b/src/components/molecules/QuickAccessRow/index.tsx @@ -28,7 +28,7 @@ const QuickAccessRow: React.FC<{ select: SelectionProps }> = ({ > {select.selectionItems.map(({ title, type, tags, id }) => { let normalIcon: IconDefinition; - if (type === undefined || tags === undefined) return; + if (type === undefined || tags === undefined) return null; switch (type) { case "pdf": normalIcon = faFilePdf; diff --git a/src/components/molecules/SelectionView/index.tsx b/src/components/molecules/SelectionView/index.tsx index 1e74ac2adb4de966a0e33d111b1aeb1af211a785..869d7af833062eda8327a64ce39983fb3400ce82 100644 --- a/src/components/molecules/SelectionView/index.tsx +++ b/src/components/molecules/SelectionView/index.tsx @@ -1,7 +1,4 @@ import React from "react"; -import classNames from "classnames"; -import { api, methods } from "../../../constants/routes"; -import { request } from "../../../utils/api"; import ResourceSectionHeader from "../ResourceSectionHeader"; import { faSquare, faCheckSquare } from "@fortawesome/free-regular-svg-icons"; diff --git a/src/components/pages/Exams/PastPapers/index.tsx b/src/components/pages/Exams/PastPapers/index.tsx index 9eb07af9f7fc38bf298d4cae48e1d6707afe2239..d1ead9c11daaf49feabf1c4ea8a7630849884fb8 100644 --- a/src/components/pages/Exams/PastPapers/index.tsx +++ b/src/components/pages/Exams/PastPapers/index.tsx @@ -1,91 +1,97 @@ import React from "react"; import styles from "./style.module.scss"; +import classNames from "classnames"; import MyBreadcrumbs from "components/atoms/MyBreadcrumbs"; +import graphIllustration from "assets/images/graph-illustration.svg"; import InputGroup from "react-bootstrap/InputGroup"; import FormControl from "react-bootstrap/FormControl"; import Button from "react-bootstrap/Button"; +import Badge from "react-bootstrap/Badge"; +import Card from "react-bootstrap/Card"; +import Row from "react-bootstrap/esm/Row"; +import Col from "react-bootstrap/esm/Col"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; -import QuickAccessView from "components/molecules/QuickAccessView"; -import ResourcesFolderView from "components/molecules/ResourcesFolderView"; -import CurrentDirectoryView from "components/molecules/CurrentDirectoryView"; +import { faInfoCircle, faFile, faFolder } from "@fortawesome/free-solid-svg-icons"; -class ExamPastPapers extends React.Component<{}, {}> { - render() { - let scope = "" - let resources = resourceItems; +const PastPapers: React.FC = () => { + return ( + <> + <MyBreadcrumbs /> + <InputGroup> + <FormControl + className={styles.searchBar} + aria-label="Search" + placeholder="Search..." + /> + <InputGroup.Append> + <Button className={styles.searchBarIcon}> + <FontAwesomeIcon size="1x" icon={faInfoCircle} /> + </Button> + </InputGroup.Append> + </InputGroup> - let quickAccessItems = resources.filter( - ({ tags, folder }) => - tags.includes("new") && (scope === "" || scope === folder) - ); + <h5 + style={{ marginTop: "30px", marginBottom: "10px" }} + className={classNames(styles.moduleSectionHeader)} + > + Folders + </h5> + <Row style={{marginRight:"-10px", marginLeft:"-10px"}}> + {[...Array(3)].map((e, i) => ( + <Col xs={6} sm={6} md={3} key={i} style={{paddingLeft: "10px", paddingRight: "10px"}}> + <Card className={styles.folderCard}> + <Card.Body style={{ padding: ".6rem" }}> + <Card.Text style={{ marginBottom: 0 }}>Folder {i}</Card.Text> + <FontAwesomeIcon + style={{ fontSize: "1.125rem" }} + icon={faFolder} + /> + </Card.Body> + </Card> + </Col> + ))} + </Row> - let currentDirectoryFiles = resources.filter( - ({ folder }) => folder === scope - ); + <h5 className={classNames(styles.moduleSectionHeader)}>Quick Access</h5> - let folders: { title: string; id: number }[] = Array.from( - new Set<string>(resources.map((res) => res.folder)) - ).map((title: string, id: number) => ({ - title: title, - id: id, - })); + <Row style={{marginRight:"-10px", marginLeft:"-10px"}}> + {[...Array(4)].map((e, i) => ( + <Col xs={12} sm={6} md={6} lg={4} xl={3} key={i} style={{paddingLeft: "10px", paddingRight: "10px"}}> + <Card className={styles.quickViewCard}> + <Card.Header> + <span className={styles.assessmentResult}>40 / 50</span> + </Card.Header> + <Card.Img variant="top" src={graphIllustration} /> + <Card.Body> + <Card.Title>Paper {i}</Card.Title> + <FontAwesomeIcon + style={{ fontSize: "1.125rem" }} + icon={faFile} + /> + </Card.Body> + <Card.Footer> + <Badge + pill + className={classNames(styles.quickViewTag, styles.tagTeal)} + > + New + </Badge> + <Badge + pill + className={classNames(styles.quickViewTag, styles.tagBlue)} + > + Week 1 + </Badge> + </Card.Footer> + </Card> + </Col> + ))} + </Row> - return ( - <> - <MyBreadcrumbs /> - <InputGroup> - <FormControl - className={styles.searchBar} - aria-label="Search" - placeholder="Search..." - /> - <InputGroup.Append> - <Button variant="secondary" className={styles.searchBarIcon}> - <FontAwesomeIcon size="1x" icon={faInfoCircle} /> - </Button> - </InputGroup.Append> - </InputGroup> - <> - {scope === "" && folders.length > 0 ? ( - <ResourcesFolderView folderItems={folders} /> - ) : null} - {scope !== "" && currentDirectoryFiles.length > 0 ? ( - <CurrentDirectoryView documentItems={currentDirectoryFiles} /> - ) : null} - {scope === "" && quickAccessItems.length > 0 ? ( - <QuickAccessView quickAccessItems={quickAccessItems} /> - ) : null} - </> - </> - ); - } -} + </> + ); +}; -export default ExamPastPapers; - -let resourceItems = [ - { - title: "C304: Logic-Based Learning", - type: "pdf", - tags: ["new", "Summer"], - folder: "2018 - 2019", - id: 14, - }, - { - title: "C316: Computer Vision", - type: "pdf", - tags: ["new", "Summer"], - folder: "2017 - 2018", - id: 8, - }, - { - title: "C317: Graphics", - type: "pdf", - tags: ["new", "Summer"], - folder: "2016 - 2017", - id: 9, - }, -]; +export default PastPapers; diff --git a/src/components/pages/Exams/PastPapers/style.module.scss b/src/components/pages/Exams/PastPapers/style.module.scss index 574a813d9ecb840c1694991ae8afe94ea83c0efe..c8af7cc6743a063c490e7b765e0d811b2b419716 100644 --- a/src/components/pages/Exams/PastPapers/style.module.scss +++ b/src/components/pages/Exams/PastPapers/style.module.scss @@ -1,12 +1,21 @@ @import "assets/scss/custom"; -$button-background: transparentize($gray-200, 0.75); -$button-border: transparentize($gray-300, 1); +.moduleSectionHeader { + margin-top: 1.875rem; +} + +.moduleParagraph { + margin-top: 1rem; +} + +.assessmentResult{ + float: right; +} .searchBar { border-radius: 0.5rem; - background-color: $button-background; - border-color: $button-background; + background-color: $gray-100; + border-color: $gray-100; transition: 0.2s background-color; -webkit-transition: 0.2s background-color; -moz-transition: 0.2s back-ground-color; @@ -55,3 +64,114 @@ $button-border: transparentize($gray-300, 1); border-color: $black; } +.quickViewCard { + margin-top: 1.25rem; + border-radius: 0.5rem; + transition: transform 0.2s, box-shadow 0.2s; + background-color: $white; + height: 96%; + border: 1px solid $gray-200; +} + +.quickViewCard:hover { + transform: scale(1.03); + box-shadow: 0 0.125rem 0.625rem 0 rgba(0, 0, 0, 0.1); + border-color: $gray-300; +} + +.quickViewCard :global(.card-body) { + padding: 0.5rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.quickViewCard :global(.card-header) { + background: $white; + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; + border-width: 0rem; + color: #000; + font-weight: 500; + text-transform: uppercase; + text-align: right; + // padding: 0.5rem; + font-size: 1.125rem; + display: flex; + justify-content: space-between; +} + +.quickViewCard :global(.card-footer) { + display: flex; + align-items: flex-end; + border-radius: 0.5rem !important; + background: $white; + border-width: 0rem; + padding: 0.5rem; +} + +.quickViewCard :global(.card-title) { + font-size: 1.125rem; + font-weight: 400; + white-space: normal; + margin-bottom: 0px; + width: 90%; + overflow-wrap: break-word; + word-wrap: break-word; + -ms-word-break: break-all; + word-break: break-all; + word-break: break-word; +} + +.quickViewCard :global(.card-img-top) { + border-radius: 0px; +} + +$blue-tag-background: transparentize($blue-100, 0.5); +$teal-tag-background: transparentize($teal-100, 0.5); + +.quickViewTag { + text-transform: uppercase; + font-size: 0.8rem; + font-weight: 500; + border-radius: 0.33rem; + margin-right: 0.5rem; + align-items: center; + display: flex; +} + +.tagBlue { + color: $blue-700; + background: $blue-tag-background; +} + +.tagTeal { + color: $teal-700; + background: $teal-tag-background; +} + +.folderCard { + border-radius: 0.5rem; + transition: transform 0.2s, box-shadow 0.2s; + border: 1px solid $gray-200; + background-color: $white; + margin-top: 0.67rem; +} + +.folderCard:hover { + transform: scale(1.03); + box-shadow: 0 0.125rem 0.625rem 0 rgba(0, 0, 0, 0.1); + border-color: $gray-300; +} + +.folderCard :global(.card-body) { + display: flex; + justify-content: space-between; + align-items: center; +} + +@media (max-width: 62rem) { +} + +@media (min-width: 62rem) { +} diff --git a/src/components/pages/ModuleResources/components/CurrentDirectoryView.tsx b/src/components/pages/ModuleResources/components/CurrentDirectoryView.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5365452571b127f1960367aa53ad1d69bc5ccb3a --- /dev/null +++ b/src/components/pages/ModuleResources/components/CurrentDirectoryView.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { Resource } from "../index"; +import SelectionView, { + SelectionProps, +} from "components/molecules/SelectionView"; +import CurrentDirectoryRow from "components/molecules/CurrentDirectoryRow"; + +export interface CurrentDirectoryViewProps { + resources: Resource[]; + scope: string; + searchText: string; + onDownloadClick: (identifiers: number[]) => void; + onItemClick: (identifier: number) => void; +} + +function includeInSearchResult(item: Resource, searchText: string) { + let rx = /([a-z]+)\(([^)]+)\)/gi; + let match: RegExpExecArray | null; + + let title = item.title.toLowerCase(); + let tags = item.tags.map((tag) => tag.toLowerCase()); + let type = item.type.toLowerCase(); + + while ((match = rx.exec(searchText)) !== null) { + switch (match[1]) { + case "type": + if (type !== match[2]) { + return false; + } + break; + case "tag": + let matchSafe = match as RegExpExecArray; + if (!tags.some((tag) => tag === matchSafe[2])) { + return false; + } + break; + default: + break; + } + } + let rest = searchText.replace(rx, "").trim(); + if (tags.some((tag) => tag.indexOf(rest) !== -1)) { + return false; + } + return title.indexOf(rest) !== -1; +} + +const CurrentDirectoryView: React.FC<CurrentDirectoryViewProps> = ({ + resources, + scope, + searchText, + onDownloadClick, + onItemClick, +}) => { + let filesContent: Resource[] = resources; + if (scope !== "") { + filesContent = filesContent.filter(({ folder }) => folder === scope); + } + if (searchText !== "") { + filesContent = filesContent.filter((item) => + includeInSearchResult(item, searchText.toLowerCase()) + ); + } + + if (scope !== "" || searchText !== "") { + return ( + <SelectionView + heading="Files" + onItemClick={onItemClick} + onDownloadClick={onDownloadClick} + selectionItems={filesContent} + render={(select: SelectionProps) => ( + <CurrentDirectoryRow select={select} /> + )} + /> + ); + } + return null; +}; + +export default CurrentDirectoryView; \ No newline at end of file diff --git a/src/components/pages/ModuleResources/components/FoldersView.tsx b/src/components/pages/ModuleResources/components/FoldersView.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5aa86b14e6fb04e78cce9694e1e5b98684459419 --- /dev/null +++ b/src/components/pages/ModuleResources/components/FoldersView.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { Resource } from "../index"; +import SelectionView, { + SelectionProps, +} from "components/molecules/SelectionView"; +import FoldersRow from "components/molecules/FoldersRow"; + +export interface FoldersViewProps { + resources: Resource[]; + scope: string; + searchText: string; +} + +const FoldersView: React.FC<FoldersViewProps> = ({ + resources, + scope, + searchText, +}) => { + let folders: { title: string; id: number }[] = Array.from( + new Set<string>(resources.map((res: Resource) => res.folder)) + ).map((title: string, id: number) => ({ + title: title, + id: id, + })); + + if (searchText === "" && scope === "" && folders.length > 0) { + return ( + <SelectionView + heading="Folders" + onDownloadClick={() => {}} + onItemClick={() => {}} + selectionItems={folders} + render={(select: SelectionProps) => <FoldersRow select={select} />} + /> + ); + } + return null; +}; + +export default FoldersView; diff --git a/src/components/pages/ModuleResources/components/QuickAccessView.tsx b/src/components/pages/ModuleResources/components/QuickAccessView.tsx new file mode 100644 index 0000000000000000000000000000000000000000..856f3f9a9eb681f455594afaefb00b370d31b203 --- /dev/null +++ b/src/components/pages/ModuleResources/components/QuickAccessView.tsx @@ -0,0 +1,42 @@ +import React from "react"; +import { Resource } from "../index"; +import SelectionView, { + SelectionProps, +} from "components/molecules/SelectionView"; +import QuickAccessRow from "components/molecules/QuickAccessRow"; + +export interface QuickAccessViewProps { + resources: Resource[]; + scope: string; + searchText: string; + onDownloadClick: (identifiers: number[]) => void; + onItemClick: (identifier: number) => void; +} + +const QuickAccessView: React.FC<QuickAccessViewProps> = ({ + resources, + scope, + searchText, + onDownloadClick, + onItemClick, +}) => { + let quickAccessItems = resources.filter( + ({ tags, folder }) => + tags.includes("new") && (scope === "" || scope === folder) + ); + + if (searchText === "" && scope === "" && quickAccessItems.length > 0) { + return ( + <SelectionView + heading="Quick Access" + onItemClick={onItemClick} + onDownloadClick={onDownloadClick} + selectionItems={quickAccessItems} + render={(select: SelectionProps) => <QuickAccessRow select={select} />} + /> + ); + } + return null; +}; + +export default QuickAccessView; diff --git a/src/components/pages/ModuleResources/index.tsx b/src/components/pages/ModuleResources/index.tsx index 89242227b1b16dd29342aa9e046c3c0acec934f7..03b8bc6b8cb7c5e2b5e85b98a5a193ec852fa868 100644 --- a/src/components/pages/ModuleResources/index.tsx +++ b/src/components/pages/ModuleResources/index.tsx @@ -3,16 +3,12 @@ import React from "react"; import { request } from "../../../utils/api"; import { api, methods } from "../../../constants/routes"; import MyBreadcrumbs from "components/atoms/MyBreadcrumbs"; - -import QuickAccessRow from "components/molecules/QuickAccessRow"; import SearchBox from "components/molecules/SearchBox"; -import SelectionView, { - SelectionProps, -} from "components/molecules/SelectionView"; -import CurrentDirectoryRow from "components/molecules/CurrentDirectoryRow"; -import FoldersRow from "components/molecules/FoldersRow"; +import QuickAccessView from "./components/QuickAccessView"; +import CurrentDirectoryView from "./components/CurrentDirectoryView"; +import FoldersView from "./components/FoldersView"; -interface Resource { +export interface Resource { title: string; type: string; tags: string[]; @@ -79,38 +75,6 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { }); } - includeInSearchResult(item: Resource, searchText: string) { - let rx = /([a-z]+)\(([^)]+)\)/gi; - let match: RegExpExecArray | null; - - let title = item.title.toLowerCase(); - let tags = item.tags.map((tag) => tag.toLowerCase()); - let type = item.type.toLowerCase(); - - while ((match = rx.exec(searchText)) !== null) { - switch (match[1]) { - case "type": - if (type !== match[2]) { - return false; - } - break; - case "tag": - let matchSafe = match as RegExpExecArray; - if (!tags.some((tag) => tag === matchSafe[2])) { - return false; - } - break; - default: - break; - } - } - let rest = searchText.replace(rx, "").trim(); - if (tags.some((tag) => tag.indexOf(rest) !== -1)) { - return false; - } - return title.indexOf(rest) !== -1; - } - handleFileDownload(indices: number[]) { const onSuccess = (filename: string, data: any) => { // TODO: Try to navigate straight to the endpoint url instead of creating an object url @@ -159,10 +123,10 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { } ); } - } - - handleFileClick(id: number){ - const onSuccess = (data: any) => { + } + + handleFileClick(id: number) { + const onSuccess = (data: any) => { // TODO: Try to navigate straight to the endpoint url instead of creating an object url data.blob().then((blob: any) => { let url = URL.createObjectURL(blob); @@ -177,99 +141,24 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { error.text().then((errorText) => { console.log(errorText); }); - }; - request(api.MATERIALS_RESOURCES_FILE(id), methods.GET, onSuccess, onFailure); - } - - getResourcesFolderView(scope: any) { - let folders: { title: string; id: number }[] = Array.from( - new Set<string>(this.state.resources.map((res: Resource) => res.folder)) - ).map((title: string, id: number) => ({ - title: title, - id: id, - })); - - if (this.state.searchText === "" && scope === "" && folders.length > 0) { - return ( - <SelectionView - heading="Folders" - onDownloadClick={() => {}} - onItemClick={() => {}} - selectionItems={folders} - render={(select: SelectionProps) => <FoldersRow select={select} />} - /> - ); - } - } - - getCurrentDirectoryView(scope: any) { - let filesContent: Resource[] = this.state.resources; - if (scope !== "") { - filesContent = filesContent.filter(({ folder }) => folder === scope); - } - if (this.state.searchText !== "") { - filesContent = filesContent.filter((item) => - this.includeInSearchResult(item, this.state.searchText.toLowerCase()) - ); - } - - if (scope !== "" || this.state.searchText !== "") { - return ( - <SelectionView - heading="Files" - onItemClick={(id) => this.handleFileClick(id)} - onDownloadClick={(ids) => this.handleFileDownload(ids)} - selectionItems={filesContent} - render={(select: SelectionProps) => ( - <CurrentDirectoryRow select={select} /> - )} - /> - ); - } - } - - getQuickAccessView(scope: any) { - let quickAccessItems = this.state.resources.filter( - ({ tags, folder }) => - tags.includes("new") && (scope === "" || scope === folder) + }; + request( + api.MATERIALS_RESOURCES_FILE(id), + methods.GET, + onSuccess, + onFailure ); - - if ( - this.state.searchText === "" && - scope === "" && - quickAccessItems.length > 0 - ) { - return ( - <SelectionView - heading="Quick Access" - onItemClick={(id) => this.handleFileClick(id)} - onDownloadClick={(ids) => this.handleFileDownload(ids)} - selectionItems={quickAccessItems} - render={(select: SelectionProps) => ( - <QuickAccessRow select={select} /> - )} - /> - ); - } } - getloadedItems(pageItems: JSX.Element) { + getloadedItems() { if (!this.state.isLoaded) return <>Loading...</>; if (this.state.error) return <> Error retrieving data: {this.state.error} </>; - return pageItems; + return null; } render() { let scope = this.props.scope || ""; - let pageItems = ( - <> - {this.getResourcesFolderView(scope)} - {this.getCurrentDirectoryView(scope)} - {this.getQuickAccessView(scope)} - </> - ); - return ( <> <MyBreadcrumbs /> @@ -277,7 +166,31 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { searchText={this.state.searchText} onSearchTextChange={(text) => this.setState({ searchText: text })} /> - {this.getloadedItems(pageItems)} + {this.getloadedItems() || ( + <> + <FoldersView + resources={this.state.resources} + scope={scope} + searchText={this.state.searchText} + /> + + <CurrentDirectoryView + resources={this.state.resources} + scope={scope} + searchText={this.state.searchText} + onDownloadClick={(ids) => this.handleFileDownload(ids)} + onItemClick={(id) => this.handleFileClick(id)} + /> + + <QuickAccessView + resources={this.state.resources} + scope={scope} + searchText={this.state.searchText} + onDownloadClick={(ids) => this.handleFileDownload(ids)} + onItemClick={(id) => this.handleFileClick(id)} + /> + </> + )} </> ); }