From 7fa6332edbc26efc6d4046d48fcb322eb349856f Mon Sep 17 00:00:00 2001 From: Stefan Niclas Heun Date: Sat, 21 Sep 2024 09:24:30 +0200 Subject: [PATCH 1/4] updating server dependencies --- server/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/build.gradle b/server/build.gradle index 55902a7b..8b4ed466 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -35,7 +35,7 @@ dependencies { implementation 'com.github.fge:json-patch:1.7' implementation 'com.konghq:unirest-java:3.14.2' implementation 'com.konghq:unirest-objectmapper-jackson:3.14.2' - implementation 'org.eclipse.jgit:org.eclipse.jgit:3.0.3.201309161630-r' + implementation 'org.eclipse.jgit:org.eclipse.jgit:7.0.0.202409031743-r' implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:7.6.0' // Liquibase From dc2e4eecbe823b20ceab8368b24d259b8e77d1fc Mon Sep 17 00:00:00 2001 From: Stefan Niclas Heun Date: Sat, 21 Sep 2024 11:20:36 +0200 Subject: [PATCH 2/4] deep removal of thesis functionality from client --- client/src/App.tsx | 14 - client/src/forms/ThesisApplicationForm.tsx | 969 ------------------ client/src/interface/thesisApplication.ts | 78 -- .../MailingManagementConsole.tsx | 33 - .../components/TemplateInstructions.tsx | 149 --- ...sApplicationsManagementConsole.module.scss | 3 - .../ThesisApplicationsManagementConsole.tsx | 131 --- .../ThesisApplicationsDatatable.tsx | 307 ------ client/src/network/mailingService.ts | 5 - client/src/network/thesisApplication.ts | 305 ------ client/src/state/query.ts | 2 - .../zustand/useThesisApplicationStore.ts | 21 - docker-compose.prod.yml | 1 - 13 files changed, 2018 deletions(-) delete mode 100644 client/src/forms/ThesisApplicationForm.tsx delete mode 100644 client/src/interface/thesisApplication.ts delete mode 100644 client/src/management/ThesisApplicationsManagement/ThesisApplicationsManagementConsole.module.scss delete mode 100644 client/src/management/ThesisApplicationsManagement/ThesisApplicationsManagementConsole.tsx delete mode 100644 client/src/management/ThesisApplicationsManagement/components/ThesisApplicationsDatatable.tsx delete mode 100644 client/src/network/thesisApplication.ts delete mode 100644 client/src/state/zustand/useThesisApplicationStore.ts diff --git a/client/src/App.tsx b/client/src/App.tsx index f0e62754..52dfc3bf 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -14,10 +14,8 @@ import { ApplicationFormAccessMode } from './forms/DefaultApplicationForm' import { CoachApplicationForm } from './forms/CoachApplicationForm' import { TutorApplicationForm } from './forms/TutorApplicationForm' import { LandingPage } from './utilities/LandingPage/LandingPage' -import { ThesisApplicationForm } from './forms/ThesisApplicationForm' import { IntroCourseConsole } from './management/IntroCourse/IntroCourseConsole' import type Keycloak from 'keycloak-js' -import { ThesisApplicationsManagementConsole } from './management/ThesisApplicationsManagement/ThesisApplicationsManagementConsole' import { DevelopmentProfileSubmission } from './student/DevelopmentProfileSubmission/DevelopmentProfileSubmission' import { MailingManagementConsole } from './management/MailingManagement/MailingManagementConsole' import { GradingManagementConsole } from './management/Grading/GradingManagementConsole' @@ -57,10 +55,6 @@ export const App = (): JSX.Element => { - } - /> { /> } /> - } - /> - } - /> } /> } /> diff --git a/client/src/forms/ThesisApplicationForm.tsx b/client/src/forms/ThesisApplicationForm.tsx deleted file mode 100644 index 51c13645..00000000 --- a/client/src/forms/ThesisApplicationForm.tsx +++ /dev/null @@ -1,969 +0,0 @@ -import { isEmail, isNotEmpty, useForm } from '@mantine/form' -import { Dropzone, PDF_MIME_TYPE } from '@mantine/dropzone' -import moment from 'moment' -import countries from 'i18n-iso-countries' -import enLocale from 'i18n-iso-countries/langs/en.json' -import { - ActionIcon, - Box, - Button, - Card, - Center, - Checkbox, - Divider, - Group, - Image, - Loader, - LoadingOverlay, - Select, - Spoiler, - Stack, - Text, - Textarea, - Title, - rem, - useMantineTheme, -} from '@mantine/core' -import { Gender, StudyDegree, StudyProgram } from '../interface/application' -import { ApplicationFormAccessMode } from './DefaultApplicationForm' -import { DeclarationOfDataConsent } from './DeclarationOfDataConsent' -import { IconCalendar, IconPhoto, IconUpload, IconX } from '@tabler/icons-react' -import LS1Logo from '../static/ls1logo.png' -import { DatePickerInput } from '@mantine/dates' -import { notifications } from '@mantine/notifications' -import { useDisclosure } from '@mantine/hooks' -import { ApplicationSuccessfulSubmission } from '../student/StudentApplicationSubmissionPage/ApplicationSuccessfulSubmission' -import { useEffect, useState } from 'react' -import { FormTextField } from './components/FormTextField' -import { FormSelectField } from './components/FormSelectField' -import { useThesisApplicationStore } from '../state/zustand/useThesisApplicationStore' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { - getThesisApplicationBachelorReportFile, - getThesisApplicationCvFile, - getThesisApplicationExaminationFile, - postThesisApplicatioAcceptance, - postThesisApplication, - postThesisApplicationAssessment, - postThesisApplicationRejection, - postThesisApplicationThesisAdvisorAssignment, -} from '../network/thesisApplication' -import { FocusTopic, ResearchArea, ThesisApplication } from '../interface/thesisApplication' -import { Query } from '../state/query' - -countries.registerLocale(enLocale) -const countriesArr = Object.entries(countries.getNames('en', { select: 'alias' })).map( - ([key, value]) => { - return { - label: value, - value: key, - } - }, -) - -interface ThesisApplicationFormProps { - accessMode: ApplicationFormAccessMode - application?: ThesisApplication -} - -export const ThesisApplicationForm = ({ - application, - accessMode, -}: ThesisApplicationFormProps): JSX.Element => { - const theme = useMantineTheme() - const queryClient = useQueryClient() - const { thesisAdvisors } = useThesisApplicationStore() - const [loadingOverlayVisible, loadingOverlayHandlers] = useDisclosure(false) - const [applicationSuccessfullySubmitted, setApplicationSuccessfullySubmitted] = useState(false) - const [notifyStudent, setNotifyStudent] = useState(true) - const uploads = useForm<{ - examinationReport: File | undefined - cv: File | undefined - bachelorReport: File | undefined - }>({ - initialValues: { - examinationReport: undefined, - cv: undefined, - bachelorReport: undefined, - }, - validate: { - examinationReport: (value) => { - if (!value) { - return 'Please upload your examination report.' - } else if (value && value.size > 1 * 1024 ** 2) { - return 'The file should not exceed 3mb' - } - }, - cv: (value) => { - if (!value) { - return 'Please upload your CV.' - } else if (value && value.size > 1 * 1024 ** 2) { - return 'The file should not exceed 3mb' - } - }, - }, - }) - const form = useForm({ - initialValues: application - ? { - ...application, - desiredThesisStart: new Date(application.desiredThesisStart), - assessmentComment: application.assessmentComment ?? '', - } - : { - id: '', - student: { - id: '', - tumId: '', - matriculationNumber: '', - isExchangeStudent: false, - email: '', - firstName: '', - lastName: '', - nationality: '', - gender: undefined, - suggestedAsCoach: false, - suggestedAsTutor: false, - blockedByPm: false, - reasonForBlockedByPm: '', - }, - studyDegree: undefined, - studyProgram: undefined, - currentSemester: '', - start: '', - specialSkills: '', - researchAreas: [], - focusTopics: [], - motivation: '', - projects: '', - interests: '', - thesisTitle: '', - desiredThesisStart: new Date(), - applicationStatus: 'NOT_ASSESSED', - assessmentComment: '', - }, - validateInputOnChange: ['student.tumId'], - validateInputOnBlur: true, - validate: { - student: { - tumId: (value, values) => - /^[A-Za-z]{2}[0-9]{2}[A-Za-z]{3}$/.test(value ?? '') || values.student?.isExchangeStudent - ? null - : 'This is not a valid TUM ID', - matriculationNumber: (value, values) => - /^\d{8}$/.test(value ?? '') || values.student?.isExchangeStudent - ? null - : 'This is not a valid matriculation number.', - firstName: isNotEmpty('Please state your first name.'), - lastName: isNotEmpty('Please state your last name'), - email: isEmail('Invalid email'), - gender: isNotEmpty('Please state your gender.'), - nationality: isNotEmpty('Please state your nationality.'), - }, - motivation: (value) => { - if (!value || !isNotEmpty(value)) { - return 'Please state your motivation for the thesis.' - } else if (value.length > 500) { - return 'The maximum allowed number of characters is 500.' - } - }, - specialSkills: (value) => { - if (!value || !isNotEmpty(value)) { - return 'Please state your special skills.' - } else if (value.length > 500) { - return 'The maximum allowed number of characters is 500.' - } - }, - interests: (value) => { - if (!value || !isNotEmpty(value)) { - return 'Please state your interests.' - } else if (value.length > 500) { - return 'The maximum allowed number of characters is 500.' - } - }, - projects: (value) => { - if (!value || !isNotEmpty(value)) { - return 'Please state your projects.' - } else if (value.length > 500) { - return 'The maximum allowed number of characters is 500.' - } - }, - thesisTitle: (value) => { - if (!value || !isNotEmpty(value)) { - return 'Please state your thesis title suggestion.' - } else if (value.length > 200) { - return 'The maximum allowed number of characters is 200.' - } - }, - studyDegree: isNotEmpty('Please state your study degree.'), - studyProgram: isNotEmpty('Please state your study program.'), - currentSemester: (value) => { - return !value || value.length === 0 || !/\b([1-9]|[1-9][0-9])\b/.test(value) - ? 'Please state your current semester.' - : null - }, - }, - }) - const consentForm = useForm({ - initialValues: { - declarationOfConsentAccepted: false, - }, - validateInputOnChange: true, - validate: { - declarationOfConsentAccepted: (value) => !value, - }, - }) - const [thesisAdvisorId, setThesisAdvisorId] = useState( - application?.thesisAdvisor?.id ?? null, - ) - - const assessThesisApplication = useMutation({ - mutationFn: () => - postThesisApplicationAssessment(application?.id ?? '', { - status: form.values.applicationStatus, - assessmentComment: form.values.assessmentComment ?? '', - }), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [Query.THESIS_APPLICATION] }) - }, - }) - - const assignThesisApplicationToThesisAdvisor = useMutation({ - mutationFn: () => - postThesisApplicationThesisAdvisorAssignment(application?.id ?? '', thesisAdvisorId ?? ''), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [Query.THESIS_APPLICATION] }) - }, - }) - - const acceptThesisApplication = useMutation({ - mutationFn: () => postThesisApplicatioAcceptance(application?.id ?? '', notifyStudent), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [Query.THESIS_APPLICATION] }) - }, - }) - - const rejectThesisApplication = useMutation({ - mutationFn: () => postThesisApplicationRejection(application?.id ?? '', notifyStudent), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [Query.THESIS_APPLICATION] }) - }, - }) - - useEffect(() => { - setThesisAdvisorId(application?.thesisAdvisor?.id ?? null) - }, [application]) - - return ( - - - {applicationSuccessfullySubmitted ? ( - - ) : ( - - {accessMode === ApplicationFormAccessMode.STUDENT && ( - -
- LS1 Logo -
-
- Thesis Application at LS1 Chair -
-
- )} -
- - - - - - - - - - { - return { - label: Gender[key as keyof typeof Gender], - value: key, - } - })} - selectProps={form.getInputProps('student.gender')} - /> - c.value == form.values.student.nationality)?.label ?? '' - } - data={countriesArr} - selectProps={form.getInputProps('student.nationality')} - /> - - - - { - return { - label: StudyDegree[key as keyof typeof StudyDegree], - value: key, - } - })} - selectProps={form.getInputProps('studyDegree')} - /> - { - return { - label: StudyProgram[key as keyof typeof StudyProgram], - value: key, - } - })} - selectProps={form.getInputProps('studyProgram')} - /> - - - -
- - {!form.errors.specialSkills && - accessMode !== ApplicationFormAccessMode.INSTRUCTOR && ( - {`${ - form.values.specialSkills?.length ?? 0 - } / 500`} - )} -
-
- - {!form.errors.motivation && accessMode !== ApplicationFormAccessMode.INSTRUCTOR && ( - {`${form.values.motivation?.length ?? 0} / 500`} - )} -
-
- -
- - {!form.errors.interests && accessMode !== ApplicationFormAccessMode.INSTRUCTOR && ( - {`${form.values.interests?.length ?? 0} / 500`} - )} -
-
- - {!form.errors.projects && accessMode !== ApplicationFormAccessMode.INSTRUCTOR && ( - {`${form.values.projects?.length ?? 0} / 500`} - )} -
-
-
- - {!form.errors.thesisTitle && accessMode !== ApplicationFormAccessMode.INSTRUCTOR && ( - {`${form.values.thesisTitle?.length ?? 0} / 200`} - )} -
- {accessMode === ApplicationFormAccessMode.INSTRUCTOR ? ( - - - Desired Thesis Start Date - - - {moment(form.values.desiredThesisStart).format('DD. MMMM YYYY')} - - - ) : ( - } - label='Desired Thesis Start Date' - {...form.getInputProps('desiredThesisStart')} - /> - )} - - { - return { - label: ResearchArea[key as keyof typeof ResearchArea], - value: key, - } - })} - label='Research Areas' - placeholder='Research areas' - readValue={form.values.researchAreas - .map((ra) => ResearchArea[ra as unknown as keyof typeof ResearchArea]) - .join(', ')} - multiselectProps={form.getInputProps('researchAreas')} - /> - { - return { - label: FocusTopic[key as keyof typeof FocusTopic], - value: key, - } - })} - label='Focus Topics' - placeholder='Focus topics' - readValue={form.values.focusTopics - .map((ft) => FocusTopic[ft as unknown as keyof typeof FocusTopic]) - .join(', ')} - multiselectProps={form.getInputProps('focusTopics')} - /> - - {accessMode === ApplicationFormAccessMode.STUDENT && ( - - - - Examination Report - - * - - {uploads.values.examinationReport && ( - - - - {uploads.values.examinationReport.name} - - { - uploads.setValues({ examinationReport: undefined }) - }} - > - - - - - )} - {!uploads.values.examinationReport && ( - { - if (files[0]) { - uploads.setValues({ - examinationReport: files[0], - }) - } - }} - onReject={() => { - notifications.show({ - color: 'red', - autoClose: 5000, - title: 'Error', - message: `Failed upload file. Please make sure the file is a PDF and does not exceed 1mb.`, - }) - }} - maxSize={1 * 1024 ** 2} - accept={PDF_MIME_TYPE} - > - - - - - - - - - - - -
- - Drag the file here or click to select file - - - The file should not exceed 1mb - -
-
-
- )} - - - CV - - * - - {uploads.values.cv && ( - - - - {uploads.values.cv.name} - - { - uploads.setValues({ cv: undefined }) - }} - > - - - - - )} - {!uploads.values.cv && ( - { - if (files[0]) { - uploads.setValues({ - cv: files[0], - }) - } - }} - onReject={() => { - notifications.show({ - color: 'red', - autoClose: 5000, - title: 'Error', - message: `Failed upload file. Please make sure the file is a PDF and does not exceed 1mb.`, - }) - }} - maxSize={1 * 1024 ** 2} - accept={PDF_MIME_TYPE} - > - - - - - - - - - - - -
- - Drag the file here or click to select file - - - The file should not exceed 1mb - -
-
-
- )} - - Bachelor Report - - {uploads.values.bachelorReport && ( - - - - {uploads.values.bachelorReport.name} - - { - uploads.setValues({ bachelorReport: undefined }) - }} - > - - - - - )} - {!uploads.values.bachelorReport && ( - { - if (files[0]) { - uploads.setValues({ - bachelorReport: files[0], - }) - } - }} - onReject={() => { - notifications.show({ - color: 'red', - autoClose: 5000, - title: 'Error', - message: `Failed upload file. Please make sure the file is a PDF and does not exceed 1mb.`, - }) - }} - maxSize={1 * 1024 ** 2} - accept={PDF_MIME_TYPE} - > - - - - - - - - - - - -
- - Drag the file here or click to select file - - - The file should not exceed 1mb - -
-
-
- )} -
- )} - - {accessMode === ApplicationFormAccessMode.INSTRUCTOR && ( - <> - - Uploaded Files - - } - labelPosition='center' - /> - - {application?.examinationReportFilename && ( - - )} - {application?.cvFilename && ( - - )} - {application?.bachelorReportFilename && ( - - )} - - - - )} - {accessMode === ApplicationFormAccessMode.INSTRUCTOR && ( - <> -