Skip to content

Commit

Permalink
Added functionality for course_admins to make projects visible to stu…
Browse files Browse the repository at this point in the history
…dents after a chosen timestamp
  • Loading branch information
arnedierick committed May 18, 2024
1 parent 19ef550 commit 23300f3
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 107 deletions.
1 change: 1 addition & 0 deletions frontend/src/@types/requests.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export type GET_Responses = {
testsUrl: string
maxScore: number | null
visible: boolean
visibleAfter?: Timestamp
status?: ProjectStatus
progress: {
completed: number
Expand Down
136 changes: 85 additions & 51 deletions frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,93 @@
import { DatePicker, Form, FormInstance, Input, Switch, Typography } from "antd"
import { useTranslation } from "react-i18next"
import { FC } from "react"
import { FC, useEffect, useState } from "react"
import MarkdownEditor from "../../input/MarkdownEditor"

const GeneralFormTab: FC<{ form: FormInstance }> = ({ form }) => {
const { t } = useTranslation()
const description = Form.useWatch("description", form)

return (
<>
<Form.Item
label={t("project.change.name")}
name="name"
rules={[{ required: true, message: t("project.change.nameMessage") }]}
>
<Input />
</Form.Item>

<Typography.Text>
{t("project.change.description")}
</Typography.Text>
<MarkdownEditor value={description} maxLength={5000} />

<Form.Item
label={t("project.change.visible")}
required
name="visible"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("project.change.maxScore")}
name="maxScore"
tooltip={t("project.change.maxScoreHelp")}
rules={[{ required: false, message: t("project.change.maxScoreMessage") }]}
>
<Input
min={1}
max={1000}
type="number"
/>
</Form.Item>
<Form.Item
label={t("project.change.deadline")}
name="deadline"
rules={[{ required: true }]}
>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
/>
</Form.Item>
</>
)
const { t } = useTranslation()
const description = Form.useWatch("description", form)
const visible = Form.useWatch("visible", form)
const [isVisible, setIsVisible] = useState(visible)
const [savedVisibleAfter, setSavedVisibleAfter] = useState<string | null>(null)

useEffect(() => {
setIsVisible(visible)
if (visible && savedVisibleAfter) {
form.setFieldsValue({ visibleAfter: null })
}
}, [visible])

const handleVisibleChange = (checked: boolean) => {
setIsVisible(checked)
if (checked) {
setSavedVisibleAfter(form.getFieldValue("visibleAfter"))
form.setFieldsValue({ visibleAfter: null })
} else {
form.setFieldsValue({ visibleAfter: savedVisibleAfter })
}
}

return (
<>
<Form.Item
label={t("project.change.name")}
name="name"
rules={[{ required: true, message: t("project.change.nameMessage") }]}
>
<Input />
</Form.Item>

<Typography.Text>
{t("project.change.description")}
</Typography.Text>
<MarkdownEditor value={description} maxLength={5000} />

<Form.Item
label={t("project.change.visible")}
required
name="visible"
valuePropName="checked"
>
<Switch onChange={handleVisibleChange} />
</Form.Item>

{!isVisible && (
<Form.Item
label={t("project.change.visibleAfter")}
name="visibleAfter"
>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
/>
</Form.Item>
)}

<Form.Item
label={t("project.change.maxScore")}
name="maxScore"
tooltip={t("project.change.maxScoreHelp")}
rules={[{ required: false, message: t("project.change.maxScoreMessage") }]}
>
<Input
min={1}
max={1000}
type="number"
/>
</Form.Item>

<Form.Item
label={t("project.change.deadline")}
name="deadline"
rules={[{ required: true }]}
>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
/>
</Form.Item>
</>
)
}

export default GeneralFormTab
1 change: 1 addition & 0 deletions frontend/src/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"groupClusterId": "Groups",
"groupClusterIdMessage": "Please enter the group cluster",
"visible": "Make the project visible",
"visibleAfter": "Choose when the project will be made visible to students, leaving this empty will keep the project invisible",
"maxScore": "Maximum score",
"maxScoreMessage": "Please enter the maximum score for the project",
"maxScoreHelp": "What is the maximum achievable score for this project? Leaving it empty means the project won't be graded.",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/i18n/nl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@
"groupClusterId": "Groepen",
"groupClusterIdMessage": "Vul de Groep cluster in",
"visible": "Project zichtbaar maken",
"visibleAfter": "Kies wanneer het project automatisch zichtbaar wordt voor studenten. Als je niets invult, blijft het project onzichtbaar",
"maxScore": "Maximum score",
"maxScoreMessage": "Vul de maximum score van het project in",
"maxScoreHelp": "Wat is de maximale score die je kunt behalen voor dit project? Als je het leeg laat, wordt het project niet beoordeeld",
Expand Down
112 changes: 56 additions & 56 deletions frontend/src/pages/editProject/EditProject.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useEffect, useState } from "react"
import { useParams, useNavigate, useLocation } from "react-router-dom"
import { Button, Form, Card, UploadProps } from "antd"
import { Button, Form, UploadProps } from "antd"
import { useTranslation } from "react-i18next"
import ProjectForm from "../../components/forms/ProjectForm"
import { EditFilled } from "@ant-design/icons"
Expand All @@ -20,12 +20,12 @@ const EditProject: React.FC = () => {
const { courseId, projectId } = useParams()
const [loading, setLoading] = useState(false)
const API = useApi()
const [error, setError] = useState<JSX.Element | null>(null) // Gebruik ProjectError type voor error state
const [error, setError] = useState<JSX.Element | null>(null)
const navigate = useNavigate()
const project = useProject()
const { updateProject } = useContext(ProjectContext)
const [initialDockerValues, setInitialDockerValues] = useState<POST_Requests[ApiRoutes.PROJECT_TESTS] | null>(null)
const location = useLocation()
const location = useLocation()

const updateDockerForm = async () => {
if (!projectId) return
Expand All @@ -42,21 +42,20 @@ const EditProject: React.FC = () => {
const tests = response.response.data
console.log(tests)

if(tests.extraFilesName) {
if (tests.extraFilesName) {
const downloadLink = AppRoutes.DOWNLOAD_PROJECT_TESTS.replace(":projectId", projectId).replace(":courseId", courseId!)
const uploadVal:UploadProps["defaultFileList"] = [{

const uploadVal: UploadProps["defaultFileList"] = [{
uid: '1',
name: tests.extraFilesName,
status: 'done',
url: downloadLink,
type: "file",
type: "file",
}]

form.setFieldValue("dockerTestDir", uploadVal)
}


formVals = {
structureTest: tests.structureTest ?? "",
dockerTemplate: tests.dockerTemplate ?? "",
Expand Down Expand Up @@ -86,28 +85,28 @@ const EditProject: React.FC = () => {
setLoading(true)

const response = await API.PUT(
ApiRoutes.PROJECT,
{
body: values,
pathValues: { id: projectId },
},
"alert"
ApiRoutes.PROJECT,
{
body: values,
pathValues: { id: projectId },
},
"alert"
)
if (!response.success) {
setError(response.alert || null)
setLoading(false)
return
}

let promisses = []
let promises = []

promisses.push(saveDockerForm(form, initialDockerValues, API, projectId))
promises.push(saveDockerForm(form, initialDockerValues, API, projectId))

if (form.isFieldTouched("groups") && values.groupClusterId && values.groups) {
promisses.push(API.PUT(ApiRoutes.CLUSTER_FILL, { body: values.groups, pathValues: { id: values.groupClusterId } }, "message"))
promises.push(API.PUT(ApiRoutes.CLUSTER_FILL, { body: values.groups, pathValues: { id: values.groupClusterId } }, "message"))
}

await Promise.all(promisses)
await Promise.all(promises)

const result = response.response.data
updateProject(result)
Expand All @@ -124,45 +123,46 @@ const EditProject: React.FC = () => {

if (!project) return <></>
return (
<>
<Form
initialValues={{
name: project.name,
description: project.description,
groupClusterId: project.clusterId,
visible: project.visible,
maxScore: project.maxScore,
deadline: dayjs(project.deadline),
}}
form={form}
onFinishFailed={onInvalid}
onFinish={handleCreation}
layout="vertical"
requiredMark="optional"
>
<div style={{ width: "100%", display: "flex", justifyContent: "center" }}>
<ProjectForm
form={form}
error={error}
cardProps={{
title: t("project.change.updateTitle", { name: project.name }),
extra: (
<Form.Item style={{ textAlign: "center", margin: 0 }}>
<Button
type="primary"
htmlType="submit"
icon={<EditFilled />}
loading={loading}
>
{t("project.change.update")}
</Button>
</Form.Item>
),
<>
<Form
initialValues={{
name: project.name,
description: project.description,
groupClusterId: project.clusterId,
visible: project.visible,
visibleAfter: project.visible ? null : (project.visibleAfter ? dayjs(project.visibleAfter) : null),
maxScore: project.maxScore,
deadline: dayjs(project.deadline),
}}
/>
</div>
</Form>
</>
form={form}
onFinishFailed={onInvalid}
onFinish={handleCreation}
layout="vertical"
requiredMark="optional"
>
<div style={{ width: "100%", display: "flex", justifyContent: "center" }}>
<ProjectForm
form={form}
error={error}
cardProps={{
title: t("project.change.updateTitle", { name: project.name }),
extra: (
<Form.Item style={{ textAlign: "center", margin: 0 }}>
<Button
type="primary"
htmlType="submit"
icon={<EditFilled />}
loading={loading}
>
{t("project.change.update")}
</Button>
</Form.Item>
),
}}
/>
</div>
</Form>
</>
)
}

Expand Down
1 change: 1 addition & 0 deletions frontend/src/pages/projectCreate/ProjectCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const ProjectCreate: React.FC = () => {
description: "",
groupClusterId: undefined,
visible: false, // Stel de standaardwaarde in op false
visibleAfter: null,
maxScore: 20,
deadline: null,
}}
Expand Down

0 comments on commit 23300f3

Please sign in to comment.