Commit ec1dfac0 authored by GaryGao's avatar GaryGao
Browse files

Merge remote-tracking branch 'origin/production' into testing_features

parents 807d6216 f565a7ee
Pipeline #160041 canceled with stages
import React, { useEffect, useState } from 'react'
import {
faFileAlt,
faFilePdf,
faLink,
faFileCode,
faFileExcel,
faFilePowerpoint,
faFileVideo,
faFileWord,
IconDefinition,
faFile
} from '@fortawesome/free-solid-svg-icons'
import { EnumDictionary } from 'constants/types'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
enum FileCategory {
CODE = "code",
EXCEL = "excel",
LINK = "link",
PDF = "pdf",
PLAIN_TEXT = "plain text",
POWERPOINT = "powerpoint",
VIDEO = "video",
WORD = "word",
OTHER = "other",
}
const categoryExtensions: EnumDictionary<FileCategory, string[]> = {
[FileCategory.CODE]: [
"c", "cpp", "h", "hpp",
"java",
"kt",
"hs", "lhs",
"sh",
"py",
],
[FileCategory.EXCEL]: ["xls", "xlsx"],
[FileCategory.LINK]: [],
[FileCategory.PDF]: ["pdf"],
[FileCategory.POWERPOINT]: ["ppt", "pptx"],
[FileCategory.PLAIN_TEXT]: ["txt", ""],
[FileCategory.VIDEO]: ["flv", "avi", "mp4"],
[FileCategory.WORD]: ["doc", "docx"],
[FileCategory.OTHER]: [],
}
const categoryIcons: EnumDictionary<FileCategory, IconDefinition> = {
[FileCategory.CODE]: faFileCode,
[FileCategory.EXCEL]: faFileExcel,
[FileCategory.LINK]: faLink,
[FileCategory.PDF]: faFilePdf,
[FileCategory.POWERPOINT]: faFilePowerpoint,
[FileCategory.PLAIN_TEXT]: faFileAlt,
[FileCategory.VIDEO]: faFileVideo,
[FileCategory.WORD]: faFileWord,
[FileCategory.OTHER]: faFile,
}
const extensionDictionary: { [suffix: string]: FileCategory } = (() => {
const dic: { [suffix: string]: FileCategory } = {}
for (const key in categoryExtensions) {
const value = categoryExtensions[key as FileCategory]
value.forEach(extension => dic[extension] = key as FileCategory)
}
return dic
})()
interface Props {
suffixes: string[]
style?: React.CSSProperties
onClick?: ((event: React.MouseEvent) => void)
}
const FileExtensionIcon: React.FC<Props> = ({
suffixes,
style,
onClick,
}) => {
const [category, setCategory] = useState<FileCategory>(FileCategory.OTHER)
// Set icon as per suffixes' categories
useEffect(() => {
let category: FileCategory | undefined = undefined
for (const suffix of suffixes) {
const currentCategory = extensionDictionary[suffix] ?? FileCategory.OTHER
if (category && (category !== currentCategory)) {
setCategory(FileCategory.OTHER)
return
}
category = currentCategory
}
setCategory(category ?? FileCategory.OTHER)
}, [suffixes])
return (
<FontAwesomeIcon
style={style}
icon={categoryIcons[category]}
onClick={e => {
e.stopPropagation()
onClick?.(e)
}}
fixedWidth
/>
)
}
export default FileExtensionIcon
\ No newline at end of file
......@@ -14,7 +14,7 @@ const SideBarOutlineGroup: React.FC = () => {
let outlineButtons = [
{
title: "Overview",
title: "Dashboard",
activeURL: `/modules/${id}/dashboard`,
icon: faHome,
},
......
import React, { useRef, useState } from 'react'
import styles from './style.module.scss'
import FileItemRow from 'components/rows/FileItemRow'
import { Resource, ResourceUploadRequirement, ResourceUploadStatus } from 'constants/types'
import UploadResourceItemRow from 'components/rows/UploadResourceItemRow'
import { faDownload, faTrash, faUpload } from '@fortawesome/free-solid-svg-icons'
interface Props {
requiredResources: ResourceUploadRequirement[]
uploadedResources: ResourceUploadStatus[]
uploadFile: (file: File, index: number) => void
}
const SubmissionFileUpload: React.FC<Props> = ({
requiredResources,
uploadedResources,
uploadFile,
}) => {
const uploadRef = useRef<HTMLInputElement>(null)
const [uploadIndex, setUploadIndex] = useState<number>(0)
const onFileSelection = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
file && uploadFile(file, uploadIndex)
}
const clickUpload = (index: number) => {
setUploadIndex(index)
uploadRef.current?.click()
}
return (
<div>
<span>Requirements: </span>
<input type="file" ref={uploadRef} onChange={e => onFileSelection(e)} style={{ display: "none" }}></input>
{requiredResources.map(({ title, allowedSuffixes, }, index) => {
return (
<div>
<UploadResourceItemRow
title={title}
suffixes={allowedSuffixes}
colour="pink"
respondingIcons={[
[faUpload, _ => clickUpload(index)],
]}
/>
</div>
)
})}
<span>Uploaded resources: </span>
{uploadedResources.map(({ title, suffix, }) => (
<UploadResourceItemRow
title={title}
suffixes={[suffix]}
colour="teal"
respondingIcons={[
[faDownload, e => alert("downloading")],
[faTrash, e => alert("deleting")],
]}
/>
))}
</div>
)
}
export default SubmissionFileUpload
\ No newline at end of file
import React, {useState} from "react"
import Button from "react-bootstrap/Button"
import styles from "./style.module.scss"
import {EnumDictionary, ResourceUploadRequirement, ResourceUploadStatus, TimelineEvent} from "constants/types"
import ButtonGroup from "react-bootstrap/esm/ButtonGroup"
import Form from "react-bootstrap/Form"
import Col from "react-bootstrap/Col"
import SubmissionFileUpload from "./SubmissionFileUpload"
enum Stage {
DECLARATION = "Declaration",
GROUP_FORMATION = "Group Formation",
FILE_UPLOAD = "File Upload",
}
const allStages: Stage[] = [
Stage.DECLARATION,
Stage.GROUP_FORMATION,
Stage.FILE_UPLOAD
]
interface SubmitDeclarationProps {
}
const SubmitDeclarationSection: React.FC<SubmitDeclarationProps> = ({}) => {
return (
<Form>
<Form.Group
className={styles.submitDeclParagraph}
>
<Form.Label>
We declare that this final submitted version is our unaided work.
</Form.Label>
<Form.Label>
We acknowledge the following people for help through our original discussions:
</Form.Label>
</Form.Group>
<Form.Group>
<Form.Row>
<Col>
<Form.Label className={styles.submitDeclTitle}>Name</Form.Label>
<Form.Control placeholder="Name"/>
<Form.Control placeholder="Name"/>
<Form.Control placeholder="Name"/>
<Form.Control placeholder="Name"/>
<Form.Control placeholder="Name"/>
</Col>
<Col>
<Form.Label className={styles.submitDeclTitle}>Login (if member of DOC)</Form.Label>
<Form.Control placeholder="Login"/>
<Form.Control placeholder="Login"/>
<Form.Control placeholder="Login"/>
<Form.Control placeholder="Login"/>
<Form.Control placeholder="Login"/>
</Col>
</Form.Row>
<Form.Row>
<Col>
<Button
variant="secondary"
className={styles.submitDeclButton}
>
Change Declaration
</Button>
</Col>
<Col>
<Button
variant="secondary"
className={styles.submitDeclButton}
>
Reset Form
</Button>
</Col>
</Form.Row>
</Form.Group>
<Form.Group
className={styles.submitDeclParagraph}
>
<p>
Fill in or modify these boxes as appropriate.
</p>
<p>
Names must contain only alphabetic characters [A-Za-z],
spaces and hyphens (-); logins must contain only lower case
letters and/or digits [a-z0-9]. Name-login pairs with incorrect
syntax will be ignored.
</p>
</Form.Group>
<Form.Group>
<Form.Row>
<Col>
<Button
variant="secondary"
className={styles.submitDeclButton}
>
Delete Declaration
</Button>
</Col>
<Col>
<p>
Only possible when no submission record exists
(neither hardcopy nor electronic).
</p>
</Col>
</Form.Row>
</Form.Group>
</Form>
)
}
interface Props {
event?: TimelineEvent
activeDay: Date
}
const SubmissionSection: React.FC<Props> = ({
event,
activeDay,
}) => {
const [stage, setStage] = useState(Stage.DECLARATION)
const [requirements, setRequirements] = useState<ResourceUploadRequirement[]>(dummyRUR)
const [uploaded, setUploaded] = useState<ResourceUploadStatus[]>([])
const uploadFile = (file: File, index: number) => {
const requirement = requirements[index]
const suffix = file.name.substr(file.name.lastIndexOf('.') + 1)
console.log(suffix);
if (requirement.allowedSuffixes.includes(suffix)) {
const title = requirement.title
const newFile = new File([file], `${title}${suffix === "" ? "" : `.${suffix}`}`, { type: file.type })
const newStatus: ResourceUploadStatus = {
file: newFile,
title: title,
suffix: suffix,
timestamp: new Date(),
oldRequirement: requirement
}
console.log(newFile);
setRequirements(requirements.filter((_, i) => i !== index))
setUploaded([...uploaded, newStatus])
} else {
alert("u kidding?")
}
}
const removeFile = (index: number) => {
const status = uploaded[index]
setRequirements([...requirements, status.oldRequirement])
setUploaded(uploaded.filter((_, i) => i !== index))
}
const mainSectionDic: EnumDictionary<Stage, JSX.Element> = {
[Stage.DECLARATION]: <SubmitDeclarationSection/>,
[Stage.GROUP_FORMATION]: <></>,
[Stage.FILE_UPLOAD]: (
<SubmissionFileUpload
requiredResources={requirements}
uploadedResources={uploaded}
uploadFile={uploadFile}
/>
),
}
const buttonOf = (s: Stage) => {
return (
<Button
className={styles.sectionButton}
onClick={() => setStage(s)}
active={s === stage}
>
{s}
</Button>
)
}
return (
<>
<div className={styles.sectionSwitcher}>
<ButtonGroup>
{allStages.map(buttonOf)}
</ButtonGroup>
</div>
{mainSectionDic[stage]}
</>
)
}
const dummyRUR: ResourceUploadRequirement[] = [
{
title: "foo",
allowedSuffixes: ["pdf"]
},
{
title: "report",
allowedSuffixes: ["pdf", "txt", "pptx", "ppt"]
},
{
title: "HaskellCoursework",
allowedSuffixes: ["hs", "lhs"]
},
{
title: "allow_empty_suffix",
allowedSuffixes: ["", "txt"]
},
]
export default SubmissionSection
.sectionButton {
background-color: var(--primary-button);
color: var(--primary-button-text);
border-width: 0rem;
border-radius: 0.5rem;
margin: 0;
justify-content: space-between;
height: 2.25rem;
font-size: 1.05rem;
transition: 0.2s background-color;
-webkit-transition: 0.2s background-color;
-moz-transition: 0.2s background-color;
}
.sectionButton:active,
.sectionButton:global(.active) {
background: var(--primary-button-active) !important;
color: var(--primary-button-text-active) !important;
font-weight: 500;
border-width: 0rem;
height: 2.25rem;
line-height: 1.375rem;
box-shadow: none !important;
}
.sectionButton:hover {
background-color: var(--primary-button-hover);
color: var(--primary-button-text);
box-shadow: none !important;
}
.sectionButton:focus {
outline: none !important;
outline-offset: none !important;
box-shadow: none !important;
}
.sectionSwitcher {
top: 0;
left: 0;
position: sticky;
z-index: 5;
background: var(--background-color);
height: 2.5rem !important;
}
.sectionSwitcher > * {
width: 100%;
}
.submitDeclButton.focus,
.submitDeclButton:focus {
background: var(--primary-button);
color: var(--primary-button-text);
box-shadow: none;
}
.submitDeclButton {
color: var(--primary-button-text);
background: var(--primary-button);
font-weight: 500;
letter-spacing: 0;
border-width: 0rem;
line-height: 1.375rem;
height: 2.25rem;
border-radius: 0.5rem !important;
transition: 0.2s background;
-webkit-transition: 0.2s background;
-moz-transition: 0.2s background;
text-align: center;
margin: 0;
width: 100%;
}
.submitDeclButton.active,
.submitDeclButton:active {
background: var(--primary-button-active) !important;
color: var(--primary-button-text-active) !important;
font-weight: 500;
border-width: 0rem;
height: 2.25rem;
line-height: 1.375rem;
}
.submitDeclButton:hover {
background-color: var(--primary-button-hover);
color: var(--primary-button-text);
box-shadow: none !important;
}
import React from "react"
import React, {useState} from "react"
import Modal from "react-bootstrap/Modal"
import Button from "react-bootstrap/Button"
import styles from "./style.module.scss"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import {
faTimes,
faEnvelope,
faBullhorn,
faExclamationCircle,
faCheckCircle,
faEnvelope,
faExclamationCircle,
faTimes,
IconDefinition,
} from "@fortawesome/free-solid-svg-icons"
import FileItemRow from "components/rows/FileItemRow"
import { resourceTypeToIcon } from "components/pages/modulePages/ModuleResources/utils"
import { TimelineEvent } from "constants/types"
import { toDayCount, toEventDateTime } from "utils/functions"
import { Link, NavLink } from "react-router-dom"
import {resourceTypeToIcon} from "components/pages/modulePages/ModuleResources/utils"
import {TimelineEvent} from "constants/types"
import {toDayCount, toEventDateTime} from "utils/functions"
import SubmissionSection from "./SubmissionSection"
interface Props {
event?: TimelineEvent
show: boolean
onHide: any
onHide: () => void
activeDay: Date
}
const EventModal: React.FC<Props> = ({ event, show, onHide, activeDay }) => {
const EventModal: React.FC<Props> = ({event, show, onHide, activeDay}) => {
const [viewSubmission, setViewSubmission] = useState<boolean>(false);
if (!event) return null
const timeLeft = toDayCount(event.endDate) - toDayCount(activeDay)
let assessmentStyle = styles.blueCard
let icon = undefined
let icon: IconDefinition | undefined = undefined
let borderColour = "transparent"
let displayText: string
switch (event.assessment) {
......@@ -68,78 +73,112 @@ const EventModal: React.FC<Props> = ({ event, show, onHide, activeDay }) => {
icon = faCheckCircle
break
}
const ModalInfo = (
<>
{dummy.map(({title, type, tags, id}: any) => (
<FileItemRow
title={title}
tags={tags}
icon={resourceTypeToIcon(type)}
onClick={() => {
}}
key={id}
/>
))}
</>
)
const footerAttributes = ((): [string, () => void] | undefined => {
if (viewSubmission) {
return ["Back", () => setViewSubmission(false)]
}
if (event.status !== "unreleased" && timeLeft >= -1) {
return ["Submit", () => setViewSubmission(true)]
}
return undefined
})()
const mainSection = viewSubmission ?
(