Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maak projecten automatisch zichtbaar op een bepaald tijdstip #269

Merged
merged 10 commits into from
May 19, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ public ResponseEntity<ApiErrorReponse> handleNoHandlerFoundException(HttpServlet
"Resource/endpoint doesn't exist", path));
}

@ExceptionHandler(NoResourceFoundException.class)
public ResponseEntity<ApiErrorReponse> handleNoResourceFoundException(HttpServletRequest request, Exception ex) {
logError(ex);
String path = request.getRequestURI();
HttpStatus status = HttpStatus.NOT_FOUND;
return ResponseEntity.status(status).body(new ApiErrorReponse(OffsetDateTime.now(), status.value(), status.getReasonPhrase(),
"Resource/endpoint doesn't exist", path));
}

/* Gets thrown when the method is not allowed */
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseEntity<ApiErrorReponse> handleMethodNotSupportedException(HttpServletRequest request, Exception ex) {
Expand Down
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 })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik denk dat het gemakkelijker is om dit te doen net voor je de POST/PUT request maakt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik heb deze logica verplaatst naar de handleCreation functies van EditProject en ProjectCreate en de overbodige code uit GeneralFormTab verwijderd

}
}, [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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hier moet nog allowClear staan omdat je het veld leeg mag laten.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ik heb het erbij gezet maar AllowClear is true by default

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
8 changes: 8 additions & 0 deletions frontend/src/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
"showMore": "Show more",
"submit": "Submit",
"projectStatus": "Status",
"visibility": "Visibility",
"visibleStatus": {
"visible": "Visible",
"invisible": "Invisible",
"visibleFrom": "Visible from ",
"scheduled": "Scheduled"
},
"status": {
"completed": "Completed",
"failed": "Failed",
Expand Down Expand Up @@ -135,6 +142,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
9 changes: 8 additions & 1 deletion frontend/src/i18n/nl/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,14 @@
"deadline": "Deadline",
"deadlineNotPassed": "Toon enkel actieve projecten",
"showMore": "Toon meer",

"projectStatus": "Status",
"visibility": "Zichtbaarheid",
"visibleStatus": {
"visible": "Zichtbaar",
"invisible": "Onzichtbaar",
"visibleFrom": "Zichtbaar vanaf ",
"scheduled" : "gepland"
},
"status": {
"completed": "Voltooid",
"failed": "Verkeerd",
Expand Down Expand Up @@ -139,6 +145,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
Loading
Loading