diff --git a/frontend/src/assets/scss/custom.scss b/frontend/src/assets/scss/custom.scss index a9a3b6d4335a9f040d47a0ff8de3acbbdb598b50..ca99c8d623f5b1fdab0c5fdbb8141798de667267 100644 --- a/frontend/src/assets/scss/custom.scss +++ b/frontend/src/assets/scss/custom.scss @@ -1,7 +1,19 @@ // Override default variables before the import $nav-pills-link-active-color: black; + +//* bootstrap 5's grid. To use, import the following line in the .module.scss */ +// @import "assets/scss/bootstrap5/bootstrap-grid"; +$grid-breakpoints: ( + xs: 0, + sm: 576px, + md: 768px, + lg: 992px, + xl: 1200px, + xxl: 2000px +); + //Bootstrap 5 scss from: https://github.com/twbs/bootstrap/tree/main/scss @import "bootstrap5/functions"; @import "bootstrap5/variables"; -@import "bootstrap5/mixins"; \ No newline at end of file +@import "bootstrap5/mixins"; diff --git a/frontend/src/components/atoms/FileCard/index.tsx b/frontend/src/components/atoms/FileCard/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..afe8ceff9feea14a683cd9ae4e4c0b5e6d28f346 --- /dev/null +++ b/frontend/src/components/atoms/FileCard/index.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import styles from "./style.module.scss"; + +import classNames from "classnames"; + +import graphIllustration from "assets/images/graph-illustration.svg"; +import Badge from "react-bootstrap/Badge"; +import Card from "react-bootstrap/Card"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconDefinition } from "@fortawesome/free-solid-svg-icons"; + +export interface FileCardProps { + title: string; + type: string; + tags: string[]; + icon: IconDefinition; + onIconClick: (event: React.MouseEvent) => void; + onClick: (event: React.MouseEvent) => void; + onMouseOver: (event: React.MouseEvent) => void; + onMouseOut: (event: React.MouseEvent) => void; +} + +const FileCard: React.FC<FileCardProps> = ({ + title, + type, + tags, + icon, + onIconClick, + onClick, + onMouseOver, + onMouseOut, +}: FileCardProps) => { + return ( + <Card className={styles.quickViewCard} onClick={onClick}> + <Card.Img variant="top" src={graphIllustration} /> + <Card.Body> + <Card.Title>{title}</Card.Title> + <FontAwesomeIcon + style={{ fontSize: "1.125rem" }} + icon={icon} + onMouseOver={onMouseOver} + onMouseOut={onMouseOut} + onClick={onIconClick} + /> + </Card.Body> + <Card.Footer> + {tags.map((tag) => ( + <Badge + pill + key={tag} + className={classNames( + styles.quickViewTag, + tag === "new" ? styles.tagTeal : styles.tagBlue + )} + > + {tag} + </Badge> + ))} + </Card.Footer> + </Card> + ); +}; + +export default FileCard; diff --git a/frontend/src/components/atoms/FileCard/style.module.scss b/frontend/src/components/atoms/FileCard/style.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..4c3395e6c843edd5b13f65899a3b9c1a02b525f7 --- /dev/null +++ b/frontend/src/components/atoms/FileCard/style.module.scss @@ -0,0 +1,69 @@ +@import "assets/scss/custom"; + +.quickViewCard { + border-radius: 0.5rem; + border-color: $gray-300; + transition: transform 0.2s; + margin-top: 10px; + margin-bottom: 10px; +} + +.quickViewCard:hover { + transform: scale(1.03); +} + +.quickViewCard :global(.card-body) { + padding: 0.5rem; + display: flex; + justify-content: space-between; + align-items: center; + border-top: 1px solid $gray-300; +} + +.quickViewCard :global(.card-footer) { + border-radius: 0.5rem !important; + background: #fff; + border-width: 0rem; + padding: 0.5rem; + display: flex; + align-items: flex-end; +} + +.quickViewCard :global(.card-title) { + font-size: 1.125rem; + font-weight: 400; + margin-bottom: 0px; +} + +.quickViewCard :global(.card-img-top) { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + +$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; +} + +.tagBlue { + color: $blue-700; + background: $blue-tag-background; +} + +.tagTeal { + color: $teal-700; + background: $teal-tag-background; +} + +.quickAccessRow { + scrollbar-width: thin; + scrollbar-color: $white $white; + margin-top: 10px; + // margin-left: 0; // leave space before card +} \ No newline at end of file diff --git a/frontend/src/components/atoms/FolderCard/index.tsx b/frontend/src/components/atoms/FolderCard/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e6ff4e515fa7413e69893e4eeb596614aa145cb2 --- /dev/null +++ b/frontend/src/components/atoms/FolderCard/index.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import styles from "./style.module.scss"; +import Card from "react-bootstrap/Card"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconDefinition } from "@fortawesome/free-solid-svg-icons"; + +export interface FolderCardProps { + title: string; + icon: IconDefinition; + onIconClick: (event: React.MouseEvent) => void; + onClick: (event: React.MouseEvent) => void; + onMouseOver: (event: React.MouseEvent) => void; + onMouseOut: (event: React.MouseEvent) => void; +} + +const FolderCard: React.FC<FolderCardProps> = ({ + title, + icon, + onIconClick, + onClick, + onMouseOver, + onMouseOut, +}: FolderCardProps) => { + return ( + <Card className={styles.folderCard} onClick={onClick}> + <Card.Body style={{ padding: ".6rem" }}> + <Card.Text style={{ marginBottom: 0 }}>{title}</Card.Text> + <FontAwesomeIcon + style={{ fontSize: "1.125rem" }} + icon={icon} + onMouseOver={onMouseOver} + onMouseOut={onMouseOut} + onClick={onIconClick} + /> + </Card.Body> + </Card> + ); +}; + +export default FolderCard; diff --git a/frontend/src/components/atoms/FolderCard/style.module.scss b/frontend/src/components/atoms/FolderCard/style.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..7b3d0722401f5b249cff1203cddf59920fc385a6 --- /dev/null +++ b/frontend/src/components/atoms/FolderCard/style.module.scss @@ -0,0 +1,17 @@ +@import "assets/scss/custom"; + +.folderCard { + border-radius: 0.5rem; + transition: transform 0.2s; + margin-top: 0.67rem; +} + +.folderCard:hover { + transform: scale(1.03); +} + +.folderCard :global(.card-body) { + display: flex; + justify-content: space-between; + align-items: center; +} \ No newline at end of file diff --git a/frontend/src/components/molecules/QuickAccess/index.tsx b/frontend/src/components/molecules/QuickAccess/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c11ad6b1321dd6ab9a7f3ec2765f5f33d595d82f --- /dev/null +++ b/frontend/src/components/molecules/QuickAccess/index.tsx @@ -0,0 +1,161 @@ +import React from "react"; +import styles from "./style.module.scss"; + +import classNames from "classnames"; +import Row from "react-bootstrap/esm/Row"; +import Col from "react-bootstrap/esm/Col"; +import ResourceSectionHeader from "../ResourceSectionHeader"; +import FileCard from "components/atoms/FileCard"; +import { faSquare, faCheckSquare } from "@fortawesome/free-regular-svg-icons"; +import { faFile } from "@fortawesome/free-solid-svg-icons"; + +export interface QuickAccessProps { + quickAccessItems: { + title: string; + type: string; + tags: string[]; + id: number; + }[]; +} + +type idBooleanMap = { [key: number]: boolean }; +interface MyState { + isSelected: idBooleanMap; + isHoveringOver: idBooleanMap; + isHoveringTitle: Boolean; +} + +class QuickAccess extends React.Component<QuickAccessProps, MyState> { + constructor(props: QuickAccessProps) { + super(props); + this.state = { isSelected: [], isHoveringOver: [], isHoveringTitle: false }; + } + + componentDidMount() { + let isSelected: idBooleanMap = []; + let isHoveringOver: idBooleanMap = []; + this.props.quickAccessItems.forEach(({ id }: { id: number }) => { + isSelected[id] = false; + isHoveringOver[id] = false; + }); + this.setState({ isSelected, isHoveringOver }); + } + + isAnySelected(): boolean { + let items = this.props.quickAccessItems; + let isSelected = this.state.isSelected; + for (let item in items) { + if (isSelected[items[item].id]) { + return true; + } + } + return false; + } + + isAllSelected(): boolean { + let items = this.props.quickAccessItems; + let isSelected = this.state.isSelected; + for (let item in items) { + if (!isSelected[items[item].id]) { + return false; + } + } + return true; + } + + handleIconClick(id: number) { + let isSelected = JSON.parse(JSON.stringify(this.state.isSelected)); + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isSelected[id] = !isSelected[id]; + isHoveringOver[id] = false; + this.setState({ isSelected, isHoveringOver }); + } + + handleSelectAllClick() { + let items = this.props.quickAccessItems; + let isSelected = JSON.parse(JSON.stringify(this.state.isSelected)); + let setValue = !this.isAllSelected(); + for (let item in items) { + isSelected[items[item].id] = setValue; + } + this.setState({ isSelected ,isHoveringTitle: false}); + } + + handleCardClick(id: number) { + if (this.isAnySelected()) { + this.handleIconClick(id); + } + } + + handleMouseOver(id: number) { + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isHoveringOver[id] = true; + this.setState({ isHoveringOver }); + } + + handleMouseOut(id: number) { + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isHoveringOver[id] = false; + this.setState({ isHoveringOver }); + } + + render() { + return ( + <> + <ResourceSectionHeader + heading="Quick Access" + onMouseOver={() => { + this.setState({ isHoveringTitle: true }); + }} + onMouseOut={() => { + this.setState({ isHoveringTitle: false }); + }} + showDownload={this.isAnySelected()} + showSelectAll={this.state.isHoveringTitle || this.isAnySelected()} + onSelectAllClick={() => this.handleSelectAllClick()} + selectAllIcon={this.isAllSelected() ? faCheckSquare : faSquare} + /> + + <Row + className={classNames( + "d-flex", + "flex-row", + "flex-nowrap", + styles.quickAccessRow + )} + > + {this.props.quickAccessItems.map(({ title, type, tags, id }) => ( + <Col + xs={7} + sm={5} + md={5} + lg={4} + xl={3} + key={id} + style={{ marginBottom: ".5rem" }} + > + <FileCard + title={title} + type={type} + tags={tags} + icon={ + this.isAnySelected() || this.state.isHoveringOver[id] + ? this.state.isSelected[id] + ? faCheckSquare + : faSquare + : faFile + } + onClick={() => this.handleCardClick(id)} + onIconClick={() => this.handleIconClick(id)} + onMouseOver={() => this.handleMouseOver(id)} + onMouseOut={() => this.handleMouseOut(id)} + /> + </Col> + ))} + </Row> + </> + ); + } +} + +export default QuickAccess; diff --git a/frontend/src/components/molecules/QuickAccess/style.module.scss b/frontend/src/components/molecules/QuickAccess/style.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..00092d3680fa85bc218a34621a95d91e6db7ef36 --- /dev/null +++ b/frontend/src/components/molecules/QuickAccess/style.module.scss @@ -0,0 +1,37 @@ +@import "assets/scss/custom"; + +.quickAccessRow { + scrollbar-width: thin; + scrollbar-color: $white $white; + margin-top: 10px; + // margin-left: 0; // leave space before card +} + +.quickAccessRow::-webkit-scrollbar { + width: 1rem; + height: 0.5rem; +} +.quickAccessRow::-webkit-scrollbar-track { + background: $white; + margin-left: 15px; + margin-right: 15px; +} +.quickAccessRow::-webkit-scrollbar-thumb { + background-color: $white; + border-radius: .5rem; +} + +.quickAccessRow:hover { + scrollbar-color: $gray-400 $white; +} + +.quickAccessRow:hover::-webkit-scrollbar-thumb { + background-color: $gray-400; +} + +.quickAccessRow { + overflow-y: visible; + overflow-x: auto; + white-space: nowrap; + height: max-content; +} diff --git a/frontend/src/components/molecules/ResourceFolders/index.tsx b/frontend/src/components/molecules/ResourceFolders/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fa0fae8dff3260e13c85a044738e1ae3fad88c00 --- /dev/null +++ b/frontend/src/components/molecules/ResourceFolders/index.tsx @@ -0,0 +1,136 @@ +import React from "react"; +import Row from "react-bootstrap/esm/Row"; +import Col from "react-bootstrap/esm/Col"; +import ResourceSectionHeader from "../ResourceSectionHeader"; +import FolderCard from "components/atoms/FolderCard"; +import { faSquare, faCheckSquare } from "@fortawesome/free-regular-svg-icons"; +import { faFolder } from "@fortawesome/free-solid-svg-icons"; +export interface ResourceFoldersProps { + folderItems: { + title: string; + id: number; + }[]; +} + +type idBooleanMap = { [key: number]: boolean }; +interface MyState { + isSelected: idBooleanMap; + isHoveringOver: idBooleanMap; + isHoveringTitle: Boolean; +} + +class ResourceFolders extends React.Component<ResourceFoldersProps, MyState> { + constructor(props: ResourceFoldersProps) { + super(props); + this.state = { isSelected: [], isHoveringOver: [], isHoveringTitle: false }; + } + + componentDidMount() { + let isSelected: idBooleanMap = []; + this.props.folderItems.forEach(({ id }: { id: number }) => { + isSelected[id] = false; + }); + this.setState({ isSelected }); + } + + isAnySelected(): boolean { + let items = this.props.folderItems; + let isSelected = this.state.isSelected; + for (let item in items) { + if (isSelected[items[item].id]) { + return true; + } + } + return false; + } + + isAllSelected(): boolean { + let items = this.props.folderItems; + let isSelected = this.state.isSelected; + for (let item in items) { + if (!isSelected[items[item].id]) { + return false; + } + } + return true; + } + + handleIconClick(id: number) { + let isSelected = JSON.parse(JSON.stringify(this.state.isSelected)); + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isSelected[id] = !isSelected[id]; + isHoveringOver[id] = false; + this.setState({ isSelected, isHoveringOver }); + } + + handleSelectAllClick() { + let items = this.props.folderItems; + let isSelected = JSON.parse(JSON.stringify(this.state.isSelected)); + let setValue = !this.isAllSelected(); + for (let item in items) { + isSelected[items[item].id] = setValue; + } + this.setState({ isSelected, isHoveringTitle: false }); + } + + handleCardClick(id: number) { + if (this.isAnySelected()) { + this.handleIconClick(id); + } + } + + handleMouseOver(id: number) { + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isHoveringOver[id] = true; + this.setState({ isHoveringOver }); + } + + handleMouseOut(id: number) { + let isHoveringOver = JSON.parse(JSON.stringify(this.state.isHoveringOver)); + isHoveringOver[id] = false; + this.setState({ isHoveringOver }); + } + + render() { + return ( + <> + <ResourceSectionHeader + heading="Folders" + onMouseOver={() => { + this.setState({ isHoveringTitle: true }); + }} + onMouseOut={() => { + this.setState({ isHoveringTitle: false }); + }} + showDownload={this.isAnySelected()} + showSelectAll={this.isAnySelected() || this.state.isHoveringTitle} + onSelectAllClick={() => this.handleSelectAllClick()} + selectAllIcon={this.isAllSelected() ? faCheckSquare : faSquare} + /> + + <Row style={{ marginTop: "10px" }}> + {this.props.folderItems.map(({ title, id }) => ( + <Col xs={6} sm={6} md={3} key={id}> + <FolderCard + title={title} + icon={ + this.isAnySelected() || this.state.isHoveringOver[id] + ? this.state.isSelected[id] + ? faCheckSquare + : faSquare + : faFolder + } + onIconClick={() => this.handleIconClick(id)} + onClick={() => this.handleCardClick(id)} + onMouseOver={() => this.handleMouseOver(id)} + onMouseOut={() => this.handleMouseOut(id)} + /> + </Col> + ))} + </Row> + </> + ); + } +} + +export default ResourceFolders; diff --git a/frontend/src/components/molecules/ResourceFolders/style.module.scss b/frontend/src/components/molecules/ResourceFolders/style.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..90ea856afd314ce5f5fcb15385a193cded7090ca --- /dev/null +++ b/frontend/src/components/molecules/ResourceFolders/style.module.scss @@ -0,0 +1 @@ +@import "assets/scss/custom"; \ No newline at end of file diff --git a/frontend/src/components/molecules/ResourceSectionHeader/index.tsx b/frontend/src/components/molecules/ResourceSectionHeader/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cfcb4c2e0bf1fa54d2a507c5edba4d435213aaea --- /dev/null +++ b/frontend/src/components/molecules/ResourceSectionHeader/index.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import styles from "./style.module.scss"; + +import Button from "react-bootstrap/Button"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faDownload, IconDefinition } from "@fortawesome/free-solid-svg-icons"; + +export interface SectionHeaderProps { + heading: string; + selectAllIcon: IconDefinition; + showDownload: Boolean; + showSelectAll: Boolean; + onSelectAllClick: (event: React.MouseEvent) => void; + onMouseOver: (event: React.MouseEvent) => void; + onMouseOut: (event: React.MouseEvent) => void; +} + +const ResourceSectionHeader: React.FC<SectionHeaderProps> = ({ + heading, + showDownload, + selectAllIcon, + showSelectAll, + onSelectAllClick, + onMouseOver, + onMouseOut, +}: SectionHeaderProps) => { + return ( + <> + <div + className={styles.sectionHeaderContainer} + onMouseOut={onMouseOut} + onMouseOver={onMouseOver} + > + <span className={styles.sectionHeader} onClick={onSelectAllClick}> + {heading} + </span> + <div className={styles.sectionHeaderButtonGroup}> + <Button + className={styles.sectionHeaderButton} + onClick= {() => {}} + style={{ visibility: showDownload ? "visible" : "hidden" }} + > + <FontAwesomeIcon className={styles.buttonIcon} icon={faDownload} /> + </Button> + + <Button + style={{ visibility: showSelectAll ? "visible" : "hidden" }} + className={styles.sectionHeaderButton} + onClick={onSelectAllClick} + > + <FontAwesomeIcon + className={styles.buttonIcon} + icon={selectAllIcon} + /> + </Button> + </div> + </div> + </> + ); +}; + +export default ResourceSectionHeader; diff --git a/frontend/src/components/molecules/ResourceSectionHeader/style.module.scss b/frontend/src/components/molecules/ResourceSectionHeader/style.module.scss new file mode 100644 index 0000000000000000000000000000000000000000..8ffeafbce6770a1d8c967b7ce31eeac040a11751 --- /dev/null +++ b/frontend/src/components/molecules/ResourceSectionHeader/style.module.scss @@ -0,0 +1,64 @@ +@import "assets/scss/custom"; + +.sectionHeaderContainer { + display: flex; + justify-content: space-between; + margin-top: 1.875rem; + align-items: center; +} + +.sectionHeader { + font-weight: 500; + font-size: 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.sectionHeaderButtonGroup { + // display: none; +} + +.sectionHeaderButton { + background: $white; + color: #000; + border-width: 0px; + border-radius: 8px; + margin-left: 20px; + justify-content: space-between; + height: 2.25rem; + transition: 0.2s background; + -webkit-transition: 0.2s background; + -moz-transition: 0.2s background; + font-size: 1.05rem; +} + +.buttonIcon { + margin-top: 0.22rem; +} + +.buttonCheckbox :global(.form-check-input) { + margin: 0px; +} + +.sectionHeaderButton:active:hover, +.sectionHeaderButton:global(.active):hover { + background-color: $gray-900 !important; +} + +.sectionHeaderButton:global(.active), +.sectionHeaderButton:active { + color: #fff; + background: #000 !important; + font-weight: 500; + text-align: left; + border-width: 0rem; + height: 2.25rem; + line-height: 1.375rem; +} + +.sectionHeaderButton:hover { + background: #e5e5e5; + border-color: #fff; + color: #000; +} \ No newline at end of file diff --git a/frontend/src/components/organisms/LeftBarModule/index.tsx b/frontend/src/components/organisms/LeftBarModule/index.tsx index c621898ed6d4538ed470d7a4a82b8956ec04c8f0..80c2b54f4c428d19241932b6dd46bfc7e3394748 100644 --- a/frontend/src/components/organisms/LeftBarModule/index.tsx +++ b/frontend/src/components/organisms/LeftBarModule/index.tsx @@ -13,7 +13,12 @@ const LeftBarModule: React.FC = () => { CO140: "k0r3c04qwhj3e", CO142: "k0r3c156mj35b", CO112: "k0r3by316kp6", - CO145: "k0r3c1h4zik5y", + CO145: "k0r3c1h4zik5y", + "CO120.2": "k0r3bzfpcno23", + CO150: "k0r3c1t4x8k6l", + CO113: "k0r3byq0f68t", + CO141: "k0r3c0t7dak4o", + CO130: "k0r3bzsith2r", }; let piazzaLink = "https://piazza.com/class/"; diff --git a/frontend/src/components/pages/ModuleOverview/index.tsx b/frontend/src/components/pages/ModuleOverview/index.tsx index d963e52d388a74e61067ead124342ee24d5fad54..8e8013ff3e6f0dee1807f96601ad639968027aa6 100644 --- a/frontend/src/components/pages/ModuleOverview/index.tsx +++ b/frontend/src/components/pages/ModuleOverview/index.tsx @@ -4,9 +4,54 @@ import { useParams } from "react-router-dom"; const ModuleOverview: React.FC = () => { let { id } = useParams(); + let modules = [ + { + title: "Introduction to Logic", + code: "CO140", + }, + { + title: "Discrete Mathematics", + code: "CO142", + }, + { + title: "Introduction to Computer Systems", + code: "CO112", + }, + { + title: "Mathematical Methods", + code: "CO145", + }, + { + title: "Java", + code: "CO120.2", + }, + { + title: "Graphs and Algorithms", + code: "CO150", + }, + { + title: "Introduction to Computer Architecture", + code: "CO113", + }, + { + title: "Reasoning About Programs", + code: "CO141", + }, + { + title: "Introduction to Databases", + code: "CO130", + }, + ]; + let heading = id; + for (let i in modules) { + if (modules[i].code === id) { + heading = modules[i].title; + break; + } + } return ( <> - <Dandruff heading={id} /> + <Dandruff heading={heading} /> </> ); }; diff --git a/frontend/src/components/pages/ModuleResources/index.tsx b/frontend/src/components/pages/ModuleResources/index.tsx index 6d79f369be683f5dc5ce1424dd626b2c5a7f9136..1a699e3702d7538207b191add3f33ef67ea3cea5 100644 --- a/frontend/src/components/pages/ModuleResources/index.tsx +++ b/frontend/src/components/pages/ModuleResources/index.tsx @@ -6,21 +6,13 @@ import { request } from "../../../utils/api" import { api } from "../../../constants/routes" 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 Container from "react-bootstrap/Container"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - faInfoCircle, - faFile, - faFolder -} from "@fortawesome/free-solid-svg-icons"; +import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; +import QuickAccess from "components/molecules/QuickAccess"; +import ResourceFolders from "components/molecules/ResourceFolders"; const ModuleResources: React.FC<{ year: string, module_code: string }> = ({ year, module_code }) => { // TODO: Use these state variables in component @@ -44,6 +36,72 @@ const ModuleResources: React.FC<{ year: string, module_code: string }> = ({ year "course": module_code }) }, [year, module_code]); + + let folderItems = [ + { + title: "Lecture Materials", + id: 0, + }, + { + title: "Panopto Videos", + id: 1, + }, + { + title: "Tutorial Sheets", + id: 2, + }, + { + title: "Code", + id: 3, + }, + { + title: "Useful Links", + id: 4, + }, + { + title: "Other", + id: 5, + }, + ]; + + let quickAccessItems = [ + { + title: "Notes 1", + type: "File", + tags:["new", "Week 1"], + id: 0, + }, + { + title: "Slides 1 - 1UP", + type: "File", + tags:["new", "Week 2"], + id: 1, + }, + { + title: "Circuit simulator", + type: "Link", + tags:["new", "Week 2"], + id: 2, + }, + { + title: "C - Lecture 1", + type: "Panopto", + tags:["new"], + id: 3, + }, + { + title: "Translation Validity", + type: "Link", + tags:["Week 2"], + id: 4, + }, + { + title: "Revision Exercises", + type: "File", + tags:["Week 3"], + id: 5, + }, + ] return ( <> @@ -61,58 +119,10 @@ const ModuleResources: React.FC<{ year: string, module_code: string }> = ({ year </InputGroup.Append> </InputGroup> - <h5 className={classNames(styles.moduleSectionHeader)}>Quick Access</h5> - - {/* TODO: add scroll listener once code is refactored */} - <Container className={classNames(styles.quickAccessRow)}> - {[...Array(6)].map((e, i) => ( - <Card className={styles.quickViewCard}> - <Card.Img variant="top" src={graphIllustration} /> - <Card.Body> - <Card.Title>Document {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> - ))} - </Container> - - <h5 - style={{ marginTop: "30px", marginBottom: "10px" }} - className={classNames(styles.moduleSectionHeader)} - > - Folders - </h5> - <Row> - {[...Array(10)].map((e, i) => ( - <Col xs={6} sm={6} md={3} key={i}> - <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> + <QuickAccess quickAccessItems={quickAccessItems}/> + <ResourceFolders folderItems={folderItems}/> </> ); }; -export default ModuleResources; +export default ModuleResources; \ No newline at end of file diff --git a/frontend/src/components/pages/ModuleResources/style.module.scss b/frontend/src/components/pages/ModuleResources/style.module.scss index 4d6bcf81b7b3d7e7589ffa5e0a3ed6a566057b37..c0e45a84077da719e17ebd16b93bb433ae0e964b 100644 --- a/frontend/src/components/pages/ModuleResources/style.module.scss +++ b/frontend/src/components/pages/ModuleResources/style.module.scss @@ -1,12 +1,5 @@ @import "assets/scss/custom"; -.moduleSectionHeader { - margin-top: 1.875rem; -} - -.moduleParagraph { - margin-top: 1rem; -} .searchBar { border-radius: 0.5rem; @@ -59,141 +52,3 @@ color: $white !important; border-color: $black; } - -.quickViewCard { - margin-top: 1.25rem; - border-radius: 0.5rem; - border-color: $gray-300; - transition: transform 0.2s; -} - -.quickViewCard:hover { - transform: scale(1.03); -} - -.quickViewCard :global(.card-body) { - padding: 0.5rem; - display: flex; - justify-content: space-between; - align-items: center; - border-top: 1px solid $gray-300; -} - -.quickViewCard :global(.card-footer) { - border-radius: 0.5rem !important; - background: #fff; - border-width: 0rem; - padding: 0.5rem; - display: flex; - align-items: flex-end; -} - -.quickViewCard :global(.card-title) { - font-size: 1.125rem; - font-weight: 400; - margin-bottom: 0px; -} - -.quickViewCard :global(.card-img-top) { - border-top-left-radius: 0.5rem; - border-top-right-radius: 0.5rem; -} - -$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; -} - -.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; - margin-top: 0.67rem; -} - -.folderCard:hover { - transform: scale(1.03); -} - -.folderCard :global(.card-body) { - display: flex; - justify-content: space-between; - align-items: center; -} - -.quickAccessRow::-webkit-scrollbar { - width: 1rem; - height: 0.5rem; -} -.quickAccessRow::-webkit-scrollbar-track { - background: $white; -} -.quickAccessRow::-webkit-scrollbar-thumb { - background-color: $white; - border-radius: 0.5rem; -} - -.quickAccessRow:hover { - scrollbar-color: $gray-400 $white; -} - -.quickAccessRow:hover::-webkit-scrollbar-thumb { - background-color: $gray-400; -} - -.quickAccessRow { - overflow-y: hidden; - overflow-x: scroll; - height: max-content; - - scrollbar-width: thin; - scrollbar-color: $white $white; - - margin-top: 1rem; - display: grid; - grid-gap: 20px; - grid-template-rows: minmax(max-width, 1fr); - grid-auto-flow: column; - padding-left: 0px; - - overflow-x: scroll; - scroll-snap-type: x proximity; - padding-bottom: calc(0.75 * 20px); - margin-bottom: calc(-0.25 * 20px); - // margin-left: 0; // leave space before card -} - -.quickAccessRow::before, -.quickAccessRow::after { - content: ‘’; - width: 10px; -} - -@media (max-width: 62rem) { - .quickAccessRow { - grid-auto-columns: minmax(44%, 1fr); - grid-template-columns: repeat(auto-fill, minmax(44%, 1fr)); - } -} - -@media (min-width: 62rem) { - .quickAccessRow { - grid-auto-columns: minmax(20%, 1fr); - grid-template-columns: repeat(auto-fill, minmax(20%, 1fr)); - } -} diff --git a/frontend/src/components/pages/StandardView/index.tsx b/frontend/src/components/pages/StandardView/index.tsx index c79b69679cbce6d0acae110878b479f9da4910f9..80d405a48d3a9f228cc61d938ccf9f30f71a4742 100644 --- a/frontend/src/components/pages/StandardView/index.tsx +++ b/frontend/src/components/pages/StandardView/index.tsx @@ -52,7 +52,7 @@ const StandardView: React.FC<StandardViewProps> = ({ </Switch> <div id="sidenav-overlay" onClick={(e) => onOverlayClick(e)}></div> - <Container className={classNames("px-3", "pageContainer")}> + <Container className={classNames("pageContainer")}> <Switch> <Route path="/dashboard"> <ExamplePage name="Dashboard" /> diff --git a/frontend/src/components/pages/StandardView/style.scss b/frontend/src/components/pages/StandardView/style.scss index 0193bf2e8f257c1bd2a3a3c5752ef54da7aec5b9..e8899f725e65007c4777b92f4ab2f346002cc174 100644 --- a/frontend/src/components/pages/StandardView/style.scss +++ b/frontend/src/components/pages/StandardView/style.scss @@ -51,4 +51,6 @@ .pageContainer { padding-top: 1.875rem; margin-bottom: 10rem !important; + padding-left: 30px; + padding-right: 30px; }