From 19d750248999031c57a3728e4cb346666dd9c565 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Sat, 27 Apr 2024 22:14:15 +0200 Subject: [PATCH 01/68] paar fixes alvast --- frontend/src/queries/Project.ts | 19 +++++++++++++++- frontend/src/services/project.ts | 12 ++++++++++ frontend/src/views/CreateProjectView.vue | 28 ++++++++++++++---------- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index 144ad603..0ea537ed 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -13,7 +13,7 @@ import { createSubmission, getSubmissions, createProject, - getProjects, + getProjects, uploadProjectFiles, } from "@/services/project"; import { type Ref, computed } from "vue"; @@ -80,6 +80,23 @@ export function useCreateProjectMutation(): UseMutationReturnType< }); } +// Hook for uploading files to a project +export function useUploadProjectFilesMutation(projectId: Ref): UseMutationReturnType { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (formData) => uploadProjectFiles(projectId.value!, formData), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: projectQueryKey(projectId.value!) }); + console.log("Files uploaded successfully"); + }, + onError: (error) => { + console.error("File upload failed", error); + alert("Could not upload files. Please try again."); + }, + }); +} + + export function useSubmissionQuery(): UseQueryReturnType { return useQuery({ queryKey: SUBMISSIONS_QUERY_KEY(), diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index ed562e29..3e0a9bbd 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -33,6 +33,18 @@ export async function getProjects(): Promise { return result.projects; } +// Function to upload test files to a specific project +export async function uploadProjectFiles(projectId: number, formData: FormData): Promise { + return authorized_fetch(`/api/projects/${projectId}/test_files`, { + method: "POST", + body: formData, + }, + true + ); +} + + + // Function to create a new submission for a specific group export async function createSubmission(groupId: number, formData: FormData): Promise { return authorized_fetch( diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index ccbde220..b0276a1c 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -23,7 +23,7 @@ Loading subjects... Error loading subjects: {{ subjectsError!.message }}Error loading subjects: {{ subjectsError!.message }} - - - - - Assignment - - - - + + + Submit @@ -88,13 +82,14 @@ import CheckBoxList from "@/components/project/CheckboxList.vue"; import type { CheckBoxItem } from "@/components/project/CheckboxList.vue"; import DatePicker from "@/components/project/DatePicker.vue"; import RadioButtonList from "@/components/project/RadiobuttonList.vue"; +import FilesInput from '@/components/form_elements/FilesInput.vue'; import { QuillEditor } from "@vueup/vue-quill"; import "@vueup/vue-quill/dist/vue-quill.snow.css"; import BackgroundContainer from "@/components/BackgroundContainer.vue"; import { useRoute } from "vue-router"; import { useSubjectInstructorsQuery, useSubjectStudentsQuery } from "@/queries/Subject"; import { useMySubjectsQuery } from "@/queries/User"; -import { useCreateProjectMutation } from "@/queries/Project"; +import {useCreateProjectMutation, useUploadProjectFilesMutation} from "@/queries/Project"; import { useCreateGroupsMutation, useJoinGroupMutation } from "@/queries/Group"; import { ref, computed, reactive } from "vue"; import type User from "@/models/User"; @@ -112,6 +107,7 @@ const deadline = ref(new Date()); const publishDate = ref(new Date()); const selectedGroupProject = ref("student"); const capacity = ref(1); +const files = ref([]); const quillEditor = ref(null); const { @@ -139,6 +135,7 @@ const groupProjectOptions = [ { label: "Student Picked Groups", value: "student" }, ]; + const createProjectMutation = useCreateProjectMutation(); const createGroupsMutation = useCreateGroupsMutation(); const joinGroupMutation = useJoinGroupMutation(); @@ -188,8 +185,15 @@ async function submitForm() { }); }); } + const formData = new FormData(); + files.value.forEach(file => { + formData.append('files[]', file); + }); + + await useUploadProjectFilesMutation(createdProjectId).mutateAsync(formData); + console.log("Files uploaded successfully"); } catch (error) { - console.error("Error during project or group creation:", error); + console.error("Error during project or group creation or file upload:", error); } } From e7244ceab3fee2cbdf8be815d912bae97fb762c7 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Sat, 27 Apr 2024 22:50:50 +0200 Subject: [PATCH 02/68] almost --- frontend/src/queries/Project.ts | 16 +++++++++++----- frontend/src/views/CreateProjectView.vue | 5 +++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index 0ea537ed..ef55d943 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -81,12 +81,18 @@ export function useCreateProjectMutation(): UseMutationReturnType< } // Hook for uploading files to a project -export function useUploadProjectFilesMutation(projectId: Ref): UseMutationReturnType { +export function useUploadProjectFilesMutation(): UseMutationReturnType< + void, // Type of data returned on success + Error, // Type of error + { projectId: number; formData: FormData }, // Arguments the mutation function accepts + void // Context or rollback information on error +> { const queryClient = useQueryClient(); - return useMutation({ - mutationFn: (formData) => uploadProjectFiles(projectId.value!, formData), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: projectQueryKey(projectId.value!) }); + return useMutation({ + mutationFn: ({ projectId, formData }) => + uploadProjectFiles(projectId, formData), + onSuccess: (_, { projectId }) => { + queryClient.invalidateQueries({ queryKey: projectQueryKey(projectId) }); console.log("Files uploaded successfully"); }, onError: (error) => { diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index b0276a1c..604638b8 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -139,6 +139,7 @@ const groupProjectOptions = [ const createProjectMutation = useCreateProjectMutation(); const createGroupsMutation = useCreateGroupsMutation(); const joinGroupMutation = useJoinGroupMutation(); +const uploadProjectFilesMutation = useUploadProjectFilesMutation(); async function submitForm() { const projectData: ProjectForm = { @@ -153,6 +154,7 @@ async function submitForm() { try { const createdProjectId = await createProjectMutation.mutateAsync(projectData); + console.log("project created with ID:",createdProjectId); if (selectedGroupProject.value === "student") { const emptyGroup: GroupForm = { @@ -189,8 +191,7 @@ async function submitForm() { files.value.forEach(file => { formData.append('files[]', file); }); - - await useUploadProjectFilesMutation(createdProjectId).mutateAsync(formData); + await uploadProjectFilesMutation.mutateAsync({ createdProjectId, formData }); console.log("Files uploaded successfully"); } catch (error) { console.error("Error during project or group creation or file upload:", error); From 21b95d323df244830574a3fff4c2dcd45739385e Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 29 Apr 2024 12:23:24 +0200 Subject: [PATCH 03/68] files uploaden werkt --- frontend/src/services/project.ts | 2 +- frontend/src/views/CreateProjectView.vue | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index 3e0a9bbd..f6f73121 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -36,7 +36,7 @@ export async function getProjects(): Promise { // Function to upload test files to a specific project export async function uploadProjectFiles(projectId: number, formData: FormData): Promise { return authorized_fetch(`/api/projects/${projectId}/test_files`, { - method: "POST", + method: "PUT", body: formData, }, true diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 604638b8..205440a9 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -189,9 +189,9 @@ async function submitForm() { } const formData = new FormData(); files.value.forEach(file => { - formData.append('files[]', file); + formData.append("files", file); }); - await uploadProjectFilesMutation.mutateAsync({ createdProjectId, formData }); + await uploadProjectFilesMutation.mutateAsync({ projectId: createdProjectId, formData }); console.log("Files uploaded successfully"); } catch (error) { console.error("Error during project or group creation or file upload:", error); From 385c07057523e2291ae264f1d60110e45a7613c4 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 29 Apr 2024 13:10:21 +0200 Subject: [PATCH 04/68] formatting fixes --- .../src/components/project/DatePicker.vue | 38 ++++++++++++++----- frontend/src/queries/Project.ts | 9 ++--- frontend/src/services/project.ts | 10 ++--- frontend/src/views/CreateProjectView.vue | 11 +++--- 4 files changed, 43 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/project/DatePicker.vue b/frontend/src/components/project/DatePicker.vue index d5b04c22..5c5b9174 100644 --- a/frontend/src/components/project/DatePicker.vue +++ b/frontend/src/components/project/DatePicker.vue @@ -16,29 +16,49 @@ > + diff --git a/frontend/src/models/Project.ts b/frontend/src/models/Project.ts index ca9b4f73..6be47031 100644 --- a/frontend/src/models/Project.ts +++ b/frontend/src/models/Project.ts @@ -8,6 +8,8 @@ export default interface Project { requirements: []; description: String; capacity: number; + enroll_deadline: Date; + publish_date: Date; } export interface ProjectForm { diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index b33785c7..0f88e431 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -52,9 +52,9 @@ @@ -91,7 +91,7 @@ import { useSubjectInstructorsQuery, useSubjectStudentsQuery } from "@/queries/S import { useMySubjectsQuery } from "@/queries/User"; import { useCreateProjectMutation, useUploadProjectFilesMutation } from "@/queries/Project"; import { useCreateGroupsMutation, useJoinGroupMutation } from "@/queries/Group"; -import { ref, computed, reactive } from "vue"; +import {ref, computed, reactive, watch} from "vue"; import type User from "@/models/User"; import type { ProjectForm } from "@/models/Project"; import type { GroupForm } from "@/models/Group"; @@ -105,11 +105,16 @@ const selectedTeachers = reactive([]); const selectedAssitants = reactive([]); const deadline = ref(new Date()); const publishDate = ref(new Date()); +const enrollDeadline = ref(null); const selectedGroupProject = ref("student"); const capacity = ref(1); const files = ref([]); const quillEditor = ref(null); +watch(deadline, (newValue, oldValue) => { + console.log(`Deadline changed from ${oldValue.toISOString()} to ${newValue.toISOString()}`); +}); + const { data: mySubjectsData, isLoading: isSubjectsLoading, @@ -140,15 +145,25 @@ const createGroupsMutation = useCreateGroupsMutation(); const joinGroupMutation = useJoinGroupMutation(); const uploadProjectFilesMutation = useUploadProjectFilesMutation(); +function handleRadioDateChange(newDate) { + enrollDeadline.value = newDate; +} + async function submitForm() { + const formattedDeadline = deadline.value.toISOString(); + const formattedPublishDate = publishDate.value.toISOString(); + const formattedEnrollDeadline = !enrollDeadline.value ? null : enrollDeadline.value.toISOString(); + const projectData: ProjectForm = { name: project_title.value, - deadline: deadline.value, + deadline: formattedDeadline, description: quillEditor.value?.getQuill().root.innerHTML || "", subject_id: selectedSubject.value, is_visible: true, capacity: capacity.value, requirements: [], + publish_date: formattedPublishDate, + enroll_deadline: formattedEnrollDeadline }; try { @@ -186,12 +201,14 @@ async function submitForm() { }); }); } - const formData = new FormData(); - files.value.forEach((file) => { - formData.append("files", file); - }); - await uploadProjectFilesMutation.mutateAsync({ projectId: createdProjectId, formData }); - console.log("Files uploaded successfully"); + if (files.value.length > 0) { + const formData = new FormData(); + files.value.forEach((file) => { + formData.append("files", file); + }); + await uploadProjectFilesMutation.mutateAsync({projectId: createdProjectId, formData}); + console.log("Files uploaded successfully"); + } } catch (error) { console.error("Error during project or group creation or file upload:", error); } From c46472302c73f688ce3b051fe11b662e4e0e88f4 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Thu, 2 May 2024 17:21:12 +0200 Subject: [PATCH 06/68] formatting --- .../src/components/project/DatePicker.vue | 20 +++++++++++-------- frontend/src/views/CreateProjectView.vue | 10 ++++++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/project/DatePicker.vue b/frontend/src/components/project/DatePicker.vue index 902d0e5d..eec89b53 100644 --- a/frontend/src/components/project/DatePicker.vue +++ b/frontend/src/components/project/DatePicker.vue @@ -40,13 +40,17 @@ function formatTime(date: Date): string { return `${date.getHours()}:${date.getMinutes()}`; } -watch([date, time], () => { - const [hours, minutes] = time.value.split(":").map(Number); - const localDate = new Date(date.value); - localDate.setHours(hours); - localDate.setMinutes(minutes); - emit('update:modelValue', new Date(localDate)); -}, { deep: true }); +watch( + [date, time], + () => { + const [hours, minutes] = time.value.split(":").map(Number); + const localDate = new Date(date.value); + localDate.setHours(hours); + localDate.setMinutes(minutes); + emit("update:modelValue", new Date(localDate)); + }, + { deep: true } +); // Computed to format the display value in the text field const displayDate = computed(() => { @@ -55,7 +59,7 @@ const displayDate = computed(() => { const [hours, minutes] = time.value.split(":").map(Number); selectedDate.setHours(hours, minutes); // Format manually to avoid timezone conversion issues - return `${selectedDate.getFullYear()}-${(selectedDate.getMonth()+1).toString().padStart(2, '0')}-${selectedDate.getDate().toString().padStart(2, '0')} ${selectedDate.getHours().toString().padStart(2, '0')}:${selectedDate.getMinutes().toString().padStart(2, '0')}`; + return `${selectedDate.getFullYear()}-${(selectedDate.getMonth() + 1).toString().padStart(2, "0")}-${selectedDate.getDate().toString().padStart(2, "0")} ${selectedDate.getHours().toString().padStart(2, "0")}:${selectedDate.getMinutes().toString().padStart(2, "0")}`; } return ""; }); diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 0f88e431..28e63b49 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -91,7 +91,7 @@ import { useSubjectInstructorsQuery, useSubjectStudentsQuery } from "@/queries/S import { useMySubjectsQuery } from "@/queries/User"; import { useCreateProjectMutation, useUploadProjectFilesMutation } from "@/queries/Project"; import { useCreateGroupsMutation, useJoinGroupMutation } from "@/queries/Group"; -import {ref, computed, reactive, watch} from "vue"; +import { ref, computed, reactive, watch } from "vue"; import type User from "@/models/User"; import type { ProjectForm } from "@/models/Project"; import type { GroupForm } from "@/models/Group"; @@ -152,7 +152,9 @@ function handleRadioDateChange(newDate) { async function submitForm() { const formattedDeadline = deadline.value.toISOString(); const formattedPublishDate = publishDate.value.toISOString(); - const formattedEnrollDeadline = !enrollDeadline.value ? null : enrollDeadline.value.toISOString(); + const formattedEnrollDeadline = !enrollDeadline.value + ? null + : enrollDeadline.value.toISOString(); const projectData: ProjectForm = { name: project_title.value, @@ -163,7 +165,7 @@ async function submitForm() { capacity: capacity.value, requirements: [], publish_date: formattedPublishDate, - enroll_deadline: formattedEnrollDeadline + enroll_deadline: formattedEnrollDeadline, }; try { @@ -206,7 +208,7 @@ async function submitForm() { files.value.forEach((file) => { formData.append("files", file); }); - await uploadProjectFilesMutation.mutateAsync({projectId: createdProjectId, formData}); + await uploadProjectFilesMutation.mutateAsync({ projectId: createdProjectId, formData }); console.log("Files uploaded successfully"); } } catch (error) { From c5161cb5caba5a615eab7748342a4aca16ddde69 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Sun, 5 May 2024 11:46:47 +0200 Subject: [PATCH 07/68] background container gone + no instructors found fix --- frontend/src/views/CreateProjectView.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 28e63b49..5254beda 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -1,5 +1,4 @@ diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index f44690a5..af38053d 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -86,6 +86,7 @@ export function useCreateProjectMutation(): UseMutationReturnType< } export function useProjectFilesQuery(projectId: number): UseQueryReturnType { + console.log("projectid"+projectId); return useQuery({ queryKey: projectFilesQueryKey(projectId), queryFn: () => fetchProjectFiles(projectId), diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 06dc4471..180ea3ae 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -77,7 +77,7 @@ import { useRoute } from "vue-router"; import { useSubjectStudentsQuery } from "@/queries/Subject"; import { useMySubjectsQuery } from "@/queries/User"; import { - useCreateProjectMutation, + useCreateProjectMutation, useProjectFilesQuery, useProjectQuery, useUpdateProjectMutation, useUploadProjectFilesMutation @@ -111,6 +111,12 @@ const { isLoading: isProjectLoading, isError: isProjectError, } = useProjectQuery(projectId); +console.log(projectId); +const { + data: filesData, + isLoading: isFilesLoading, + isError: isFilesError, +} = useProjectFilesQuery(projectId.value); function htmlDecode(input) { const doc = new DOMParser().parseFromString(input, "text/html"); @@ -136,6 +142,15 @@ watch(projectData, (project) => { } }, { deep: true }); +watch(filesData, (newFiles) => { + if (newFiles) { + // Assuming newFiles is an array of File objects or similar + // Directly update the files reactive variable to the new list + files.value = newFiles; + } +}, { deep: true }); + + const deadlineModel = computed({ get: () => deadline.value, set: (newValue) => { From 9d0a622c4c0fec0467f1fa53d0b111e6bd6a7d68 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Sat, 11 May 2024 22:49:14 +0200 Subject: [PATCH 17/68] edit (improvements) --- backend/src/project/dependencies.py | 1 + frontend/src/queries/Project.ts | 2 - frontend/src/services/project.ts | 26 ++++++--- frontend/src/views/CreateProjectView.vue | 67 ++++++++++++++++++++---- 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/backend/src/project/dependencies.py b/backend/src/project/dependencies.py index d6d86da8..4dd2eecf 100644 --- a/backend/src/project/dependencies.py +++ b/backend/src/project/dependencies.py @@ -22,6 +22,7 @@ async def retrieve_project(project_id: int, async def retrieve_test_files_uuid(project: Project = Depends(retrieve_project)): + print(project.test_files_uuid) if project.test_files_uuid is None: raise TestsNotFound return project.test_files_uuid diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index af38053d..41ba9fcb 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -93,9 +93,7 @@ export function useProjectFilesQuery(projectId: number): UseQueryReturnType { console.error("Error fetching project files:", error); - // Optionally, handle errors with a more sophisticated approach or user feedback }, - // Additional options can be set here, such as refetchInterval for periodic updates }); } // Hook for uploading files to a project diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index dfdc2f2d..8ecc50a8 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -55,15 +55,27 @@ export async function uploadProjectFiles(projectId: number, formData: FormData): } export async function fetchProjectFiles(projectId: number): Promise { - return authorized_fetch( - `/api/projects/${projectId}/test_files`, - { - method: "GET" + try { + const response = await authorized_fetch(`/api/projects/${projectId}/test_files`, { method: "GET" }); + console.log("Response received:", response); // Log the entire response object + + if (!Array.isArray(response) || response.length === 0) { + throw new Error("No files found or invalid response format"); } - ).then(response => response.json()) - .catch(error => console.error("Failed to fetch project files:", error)); -} + // You can map through the response if needed, here's how to access elements + return response.map(file => ({ + path: file.path, + filename: file.filename, + contentType: file.media_type, + headers: file._headers, + statusCode: file.status_code + })); + } catch (error) { + console.error("Failed to fetch project files:", error); + throw error; + } +} export async function deleteProjectFiles(projectId: number, filesToDelete: string[]): Promise { return authorized_fetch( `/api/projects/${projectId}/test_files`, diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 180ea3ae..4a30c64a 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -77,7 +77,7 @@ import { useRoute } from "vue-router"; import { useSubjectStudentsQuery } from "@/queries/Subject"; import { useMySubjectsQuery } from "@/queries/User"; import { - useCreateProjectMutation, useProjectFilesQuery, + useCreateProjectMutation, useDeleteProjectFilesMutation, useProjectFilesQuery, useProjectQuery, useUpdateProjectMutation, useUploadProjectFilesMutation @@ -144,9 +144,12 @@ watch(projectData, (project) => { watch(filesData, (newFiles) => { if (newFiles) { - // Assuming newFiles is an array of File objects or similar - // Directly update the files reactive variable to the new list - files.value = newFiles; + files.value = newFiles.map(file => ({ + name: file.filename, + path: file.path, + contentType: file.contentType, + size: file.size // Ensure the size is available or handle its absence + })); } }, { deep: true }); @@ -215,6 +218,7 @@ const createGroupsMutation = useCreateGroupsMutation(); const joinGroupMutation = useJoinGroupMutation(); const uploadProjectFilesMutation = useUploadProjectFilesMutation(); const updateProjectMutation = useUpdateProjectMutation(); +const deleteProjectFilesMutation = useDeleteProjectFilesMutation(); function handleRadioDateChange(newDate) { enrollDeadline.value = newDate; @@ -241,11 +245,38 @@ async function submitForm() { try { if(isEditMode.value){ - try{ - updateProjectMutation.mutate({ projectId: projectId.value, projectData }); - } - catch(error){ - console.log("failed to update project"); + try { + // Wait for the project update to complete before proceeding + await updateProjectMutation.mutateAsync({ projectId: projectId.value, projectData }); + console.log("Project updated successfully."); + + const { removedUUIDs, addedFiles } = getFileChanges(); + // console.log(removedUUIDs); + // console.log(addedFiles); + // // Handle file deletions + // if (removedUUIDs.length > 0) { + // await deleteProjectFilesMutation.mutateAsync({ + // projectId: projectId.value, + // fileUUIDs: removedUUIDs + // }); + // console.log("Removed files successfully"); + // } + // + // // Handle file uploads + // if (addedFiles.length > 0) { + // const formData = new FormData(); + // addedFiles.forEach(file => { + // formData.append("files", file); // Assuming 'file' is a File object + // }); + // await uploadProjectFilesMutation.mutateAsync({ + // projectId: projectId.value, + // formData + // }); + // console.log("Uploaded new files successfully"); + // } + } catch (error) { + console.error("Failed during project update or file handling:", error); + throw error; // Re-throw the error if you need to handle it further up the chain. } } else { @@ -297,6 +328,24 @@ async function submitForm() { } } +function getFileChanges() { + console.log("Full project data:", projectData.value); // Check the structure and properties + const currentUUIDs = files.value.map(file => file.uuid); + console.log("Current UUIDs:", currentUUIDs); + + // Assuming the correct property name is `test_files_uuids` and it's an array + const originalUUIDs = projectData.value?.test_files_uuid ?? []; + console.log("Original UUIDs:", originalUUIDs); + + // If originalUUIDs is not an array, this will prevent the function from breaking + const removedUUIDs = Array.isArray(originalUUIDs) ? originalUUIDs.filter(uuid => !currentUUIDs.includes(uuid)) : []; + const addedFiles = files.value.filter(file => !originalUUIDs.includes(file.uuid)); + + console.log("Removed UUIDs:", removedUUIDs); + console.log("Added Files:", addedFiles); + + return { removedUUIDs, addedFiles }; +} function shuffle(array: any[]) { let shuffledArray = [...array]; let currentIndex = shuffledArray.length, From 20fba3facfc5e6a72ba35e78fe65b3d7e13043f6 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Sun, 12 May 2024 12:49:40 +0200 Subject: [PATCH 18/68] updates --- backend/src/project/dependencies.py | 1 + frontend/src/services/project.ts | 5 +- frontend/src/views/CreateProjectView.vue | 71 ++++++++++++++++++++---- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/backend/src/project/dependencies.py b/backend/src/project/dependencies.py index d6d86da8..4dd2eecf 100644 --- a/backend/src/project/dependencies.py +++ b/backend/src/project/dependencies.py @@ -22,6 +22,7 @@ async def retrieve_project(project_id: int, async def retrieve_test_files_uuid(project: Project = Depends(retrieve_project)): + print(project.test_files_uuid) if project.test_files_uuid is None: raise TestsNotFound return project.test_files_uuid diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index dfdc2f2d..9614d0d0 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -60,8 +60,9 @@ export async function fetchProjectFiles(projectId: number): Promise { { method: "GET" } - ).then(response => response.json()) - .catch(error => console.error("Failed to fetch project files:", error)); + ).then(response => { + return response; + }).catch(error => console.error("Failed to fetch project files:", error)); } export async function deleteProjectFiles(projectId: number, filesToDelete: string[]): Promise { diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 180ea3ae..d89b4037 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -77,7 +77,7 @@ import { useRoute } from "vue-router"; import { useSubjectStudentsQuery } from "@/queries/Subject"; import { useMySubjectsQuery } from "@/queries/User"; import { - useCreateProjectMutation, useProjectFilesQuery, + useCreateProjectMutation, useDeleteProjectFilesMutation, useProjectFilesQuery, useProjectQuery, useUpdateProjectMutation, useUploadProjectFilesMutation @@ -100,6 +100,7 @@ const enrollDeadline = ref(null); const selectedGroupProject = ref("student"); const capacity = ref(1); const files = ref([]); +const serverFiles = ref([]); const quillEditor = ref(null); const projectId = ref(route.params.projectId); @@ -143,13 +144,40 @@ watch(projectData, (project) => { }, { deep: true }); watch(filesData, (newFiles) => { - if (newFiles) { - // Assuming newFiles is an array of File objects or similar - // Directly update the files reactive variable to the new list - files.value = newFiles; + if (newFiles && Array.isArray(newFiles)) { + const updatedServerFiles = newFiles.map(fileData => ({ + name: fileData.filename, + path: fileData.path, + })); + updateFilesBasedOnServerData(updatedServerFiles); } }, { deep: true }); +function updateFilesBasedOnServerData(updatedServerFiles) { + const newFiles = updatedServerFiles.filter(sf => !serverFiles.value.some(f => f.path === sf.path)); + const removedFiles = serverFiles.value.filter(f => !updatedServerFiles.some(sf => sf.path === f.path)); + + serverFiles.value = updatedServerFiles; // Update the server files to the latest state + + // Add new files to local files + files.value = [...files.value, ...newFiles]; + + // Remove deleted files from local files + files.value = files.value.filter(f => !removedFiles.some(rf => rf.path === f.path)); +} + + +function calculateAddedFiles() { + // Files that are in the local 'files' but not in the 'serverFiles' + return files.value.filter(localFile => !serverFiles.value.some(serverFile => serverFile.path === localFile.path)); +} + +// Function to find deleted files +function calculateDeletedFiles() { + // Files that are in the 'serverFiles' but not in the local 'files' + return serverFiles.value.filter(serverFile => !files.value.some(localFile => localFile.path === serverFile.path)); +} + const deadlineModel = computed({ get: () => deadline.value, @@ -214,6 +242,7 @@ const createProjectMutation = useCreateProjectMutation(); const createGroupsMutation = useCreateGroupsMutation(); const joinGroupMutation = useJoinGroupMutation(); const uploadProjectFilesMutation = useUploadProjectFilesMutation(); +const deleteProjectFilesMutation = useDeleteProjectFilesMutation(); const updateProjectMutation = useUpdateProjectMutation(); function handleRadioDateChange(newDate) { @@ -241,11 +270,33 @@ async function submitForm() { try { if(isEditMode.value){ - try{ - updateProjectMutation.mutate({ projectId: projectId.value, projectData }); - } - catch(error){ - console.log("failed to update project"); + try { + const addedFiles = calculateAddedFiles(); + const deletedFiles = calculateDeletedFiles(); + + // Update the project details first + await updateProjectMutation.mutateAsync({ + projectId: projectId.value, + projectData + }); + + // Handle file uploads + if (addedFiles.length > 0) { + const formData = new FormData(); + addedFiles.forEach(file => formData.append('files[]', file, file.name)); + await uploadProjectFilesMutation.mutateAsync({ projectId: projectId.value, formData }); + } + + // Handle file deletions + if (deletedFiles.length > 0) { + const filesToDelete = deletedFiles.map(file => file.path); + await deleteProjectFilesMutation.mutateAsync({ projectId: projectId.value, filesToDelete }); + } + + console.log("Project and files updated successfully"); + } catch (error) { + console.error("Failed to update project and files", error); + alert("Failed to update project and files. Please try again."); } } else { From ecc38de052fddcb71a20a60206f96e6dc1d434ab Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Sun, 12 May 2024 18:04:37 +0200 Subject: [PATCH 19/68] filestructure visible --- frontend/src/App.vue | 2 +- .../components/project/DisplayTestFiles.vue | 120 ++++++++++++++++++ frontend/src/views/CreateProjectView.vue | 90 ++++++++----- 3 files changed, 181 insertions(+), 31 deletions(-) create mode 100644 frontend/src/components/project/DisplayTestFiles.vue diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6e1511c1..3ea8587b 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -33,7 +33,7 @@ onBeforeMount(() => { const { storedTheme } = useThemeStore(); const theme = useTheme(); locale.value = selectedLocale; - theme.global.name.value = storedTheme; + // theme.global.name.value = storedTheme; }); diff --git a/frontend/src/components/project/DisplayTestFiles.vue b/frontend/src/components/project/DisplayTestFiles.vue new file mode 100644 index 00000000..28d3de7e --- /dev/null +++ b/frontend/src/components/project/DisplayTestFiles.vue @@ -0,0 +1,120 @@ + + + + + + + + diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index d89b4037..6b12133b 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -52,6 +52,11 @@ + + + + + @@ -87,6 +92,7 @@ import { ref, computed, reactive, watch } from "vue"; import type User from "@/models/User"; import type { ProjectForm } from "@/models/Project"; import type { GroupForm } from "@/models/Group"; +import DisplayTestFiles from "@/components/project/DisplayTestFiles.vue"; const route = useRoute(); console.log("Route params:", route.params); @@ -143,40 +149,64 @@ watch(projectData, (project) => { } }, { deep: true }); +// files blijven voorlopig even in comments watch(filesData, (newFiles) => { + console.log("New files:", newFiles); if (newFiles && Array.isArray(newFiles)) { const updatedServerFiles = newFiles.map(fileData => ({ name: fileData.filename, path: fileData.path, + size: fileData.size // Assuming size is also available })); - updateFilesBasedOnServerData(updatedServerFiles); + + //display test files needs to be updated here + + // updateFilesBasedOnServerData(updatedServerFiles); } }, { deep: true }); function updateFilesBasedOnServerData(updatedServerFiles) { - const newFiles = updatedServerFiles.filter(sf => !serverFiles.value.some(f => f.path === sf.path)); - const removedFiles = serverFiles.value.filter(f => !updatedServerFiles.some(sf => sf.path === f.path)); + serverFiles.value = updatedServerFiles; // Update the reactive server files + console.log(updatedServerFiles); + updateDirectoryStructure(); +} - serverFiles.value = updatedServerFiles; // Update the server files to the latest state - // Add new files to local files - files.value = [...files.value, ...newFiles]; - // Remove deleted files from local files - files.value = files.value.filter(f => !removedFiles.some(rf => rf.path === f.path)); +function updateDirectoryStructure() { + directoryStructure.value = organizeFilesByDirectory(serverFiles.value); } - -function calculateAddedFiles() { - // Files that are in the local 'files' but not in the 'serverFiles' - return files.value.filter(localFile => !serverFiles.value.some(serverFile => serverFile.path === localFile.path)); +function organizeFilesByDirectory(files) { + const root = {}; + files.forEach(file => { + const parts = file.path.split('/').slice(1); // Skip the first empty part from split + let current = root; + parts.forEach((part, index) => { + if (index === parts.length - 1) { + // Assign the file to the last part + current[part] = { ...file }; + } else { + // Create a subdirectory if it doesn't exist + current[part] = current[part] || {}; + current = current[part]; + } + }); + }); + return root; } -// Function to find deleted files -function calculateDeletedFiles() { - // Files that are in the 'serverFiles' but not in the local 'files' - return serverFiles.value.filter(serverFile => !files.value.some(localFile => localFile.path === serverFile.path)); -} + +// function calculateAddedFiles() { +// // Files that are in the local 'files' but not in the 'serverFiles' +// return files.value.filter(localFile => !serverFiles.value.some(serverFile => serverFile.path === localFile.path)); +// } +// +// // Function to find deleted files +// function calculateDeletedFiles() { +// // Files that are in the 'serverFiles' but not in the local 'files' +// return serverFiles.value.filter(serverFile => !files.value.some(localFile => localFile.path === serverFile.path)); +// } const deadlineModel = computed({ @@ -271,8 +301,8 @@ async function submitForm() { try { if(isEditMode.value){ try { - const addedFiles = calculateAddedFiles(); - const deletedFiles = calculateDeletedFiles(); + // const addedFiles = calculateAddedFiles(); + // const deletedFiles = calculateDeletedFiles(); // Update the project details first await updateProjectMutation.mutateAsync({ @@ -281,17 +311,17 @@ async function submitForm() { }); // Handle file uploads - if (addedFiles.length > 0) { - const formData = new FormData(); - addedFiles.forEach(file => formData.append('files[]', file, file.name)); - await uploadProjectFilesMutation.mutateAsync({ projectId: projectId.value, formData }); - } - - // Handle file deletions - if (deletedFiles.length > 0) { - const filesToDelete = deletedFiles.map(file => file.path); - await deleteProjectFilesMutation.mutateAsync({ projectId: projectId.value, filesToDelete }); - } + // if (addedFiles.length > 0) { + // const formData = new FormData(); + // addedFiles.forEach(file => formData.append('files[]', file, file.name)); + // await uploadProjectFilesMutation.mutateAsync({ projectId: projectId.value, formData }); + // } + // + // // Handle file deletions + // if (deletedFiles.length > 0) { + // const filesToDelete = deletedFiles.map(file => file.path); + // await deleteProjectFilesMutation.mutateAsync({ projectId: projectId.value, filesToDelete }); + // } console.log("Project and files updated successfully"); } catch (error) { From 994b6f750c3b240d14302ec5b4945f2df0d1ee87 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Sun, 12 May 2024 19:14:02 +0200 Subject: [PATCH 20/68] werkt --- .../components/form_elements/FilesInput.vue | 6 +- .../src/components/project/DatePicker.vue | 32 ++- .../components/project/DisplayTestFiles.vue | 91 +++----- frontend/src/queries/Project.ts | 55 +++-- frontend/src/router/index.ts | 2 +- frontend/src/services/project.ts | 27 ++- frontend/src/views/CreateProjectView.vue | 215 ++++++++---------- 7 files changed, 200 insertions(+), 228 deletions(-) diff --git a/frontend/src/components/form_elements/FilesInput.vue b/frontend/src/components/form_elements/FilesInput.vue index f5391b85..e0b46fef 100644 --- a/frontend/src/components/form_elements/FilesInput.vue +++ b/frontend/src/components/form_elements/FilesInput.vue @@ -3,7 +3,9 @@ - {{ $t("submit.add_files_button") }} + {{ + $t("submit.add_files_button") + }} @@ -36,7 +38,7 @@ const props = defineProps({ modelValue: { type: Array, required: true, - } + }, }); const emits = defineEmits(["update:modelValue"]); diff --git a/frontend/src/components/project/DatePicker.vue b/frontend/src/components/project/DatePicker.vue index b39548d1..0c429e67 100644 --- a/frontend/src/components/project/DatePicker.vue +++ b/frontend/src/components/project/DatePicker.vue @@ -35,22 +35,30 @@ const emit = defineEmits(["update:modelValue"]); const menuVisible = ref(false); const date = ref(new Date(props.modelValue || Date.now())); // Ensures date is always initialized properly const time = ref(formatTime(props.modelValue || new Date())); -watch(() => props.modelValue, (newValue) => { - if (newValue) { - date.value = new Date(newValue); - } -}, { immediate: true }); +watch( + () => props.modelValue, + (newValue) => { + if (newValue) { + date.value = new Date(newValue); + } + }, + { immediate: true } +); function formatTime(date: Date): string { // Always format based on local time, since that's what users interact with - return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`; + return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`; } -watch([date, time], () => { - const [hours, minutes] = time.value.split(":").map(Number); - const updatedDate = new Date(date.value); - updatedDate.setHours(hours, minutes, 0, 0); - emit("update:modelValue", new Date(updatedDate)); // Ensure a new Date object is emitted -}, { deep: true }); +watch( + [date, time], + () => { + const [hours, minutes] = time.value.split(":").map(Number); + const updatedDate = new Date(date.value); + updatedDate.setHours(hours, minutes, 0, 0); + emit("update:modelValue", new Date(updatedDate)); // Ensure a new Date object is emitted + }, + { deep: true } +); // Computed to format the display value in the text field const displayDate = computed(() => { diff --git a/frontend/src/components/project/DisplayTestFiles.vue b/frontend/src/components/project/DisplayTestFiles.vue index 28d3de7e..0781b0e2 100644 --- a/frontend/src/components/project/DisplayTestFiles.vue +++ b/frontend/src/components/project/DisplayTestFiles.vue @@ -1,46 +1,30 @@ - - - - - diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index 41ba9fcb..2e378871 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -14,7 +14,10 @@ import { getSubmissions, createProject, getProjects, - uploadProjectFiles, updateProject, deleteProjectFiles, fetchProjectFiles, + uploadProjectFiles, + updateProject, + deleteProjectFiles, + fetchProjectFiles, } from "@/services/project"; import { type Ref, computed } from "vue"; @@ -32,7 +35,7 @@ function SUBMISSIONS_QUERY_KEY(): string[] { } function projectFilesQueryKey(projectId: number): (string | number)[] { - return ['projectFiles', projectId]; + return ["projectFiles", projectId]; } // Hook for fetching project details @@ -86,11 +89,11 @@ export function useCreateProjectMutation(): UseMutationReturnType< } export function useProjectFilesQuery(projectId: number): UseQueryReturnType { - console.log("projectid"+projectId); + console.log("projectid" + projectId); return useQuery({ queryKey: projectFilesQueryKey(projectId), queryFn: () => fetchProjectFiles(projectId), - enabled: !!projectId, // Only fetch when a projectId is provided + enabled: !!projectId, // Only fetch when a projectId is provided onError: (error) => { console.error("Error fetching project files:", error); }, @@ -117,14 +120,19 @@ export function useUploadProjectFilesMutation(): UseMutationReturnType< }); } -export function useDeleteProjectFilesMutation(): UseMutationReturnType { +export function useDeleteProjectFilesMutation(): UseMutationReturnType< + void, + Error, + { projectId: number; filesToDelete: string[] }, + void +> { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ projectId, filesToDelete }) => deleteProjectFiles(projectId, filesToDelete), onSuccess: (_, { projectId }) => { // Invalidate and refetch project file queries to reflect updated data - queryClient.invalidateQueries(['projectFiles', projectId]); + queryClient.invalidateQueries(["projectFiles", projectId]); console.log("Files deleted successfully"); }, @@ -135,23 +143,30 @@ export function useDeleteProjectFilesMutation(): UseMutationReturnType }, void> { +export function useUpdateProjectMutation(): UseMutationReturnType< + void, + Error, + { projectId: number; projectData: Partial }, + void +> { const queryClient = useQueryClient(); - return useMutation }, void>({ - mutationFn: ({ projectId, projectData }) => updateProject(projectId, projectData), + return useMutation }, void>( + { + mutationFn: ({ projectId, projectData }) => updateProject(projectId, projectData), - onSuccess: (_, variables) => { - // Invalidate and refetch project-related queries to reflect updated data - queryClient.invalidateQueries(projectQueryKey(variables.projectId)); - queryClient.invalidateQueries(PROJECTS_QUERY_KEY()); - console.log("Project updated successfully."); - }, + onSuccess: (_, variables) => { + // Invalidate and refetch project-related queries to reflect updated data + queryClient.invalidateQueries(projectQueryKey(variables.projectId)); + queryClient.invalidateQueries(PROJECTS_QUERY_KEY()); + console.log("Project updated successfully."); + }, - onError: (error) => { - console.error("Project update failed", error); - alert("Failed to update project. Please try again."); - }, - }); + onError: (error) => { + console.error("Project update failed", error); + alert("Failed to update project. Please try again."); + }, + } + ); } export function useSubmissionQuery(): UseQueryReturnType { diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index b9453939..bb6d1a45 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -80,7 +80,7 @@ const router = createRouter({ path: "/project/:projectId(\\d+)/edit", name: "edit-project", component: () => import("../views/CreateProjectView.vue"), // Ensure this is correct - props: (route) => ({ projectId: Number(route.params.projectId), isEditMode: true }) + props: (route) => ({ projectId: Number(route.params.projectId), isEditMode: true }), }, { path: "/subjects/register/:uuid", diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index 9614d0d0..12667e09 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -21,7 +21,10 @@ export async function createProject(projectData: ProjectForm): Promise { } } -export async function updateProject(projectId: number, projectData: Partial): Promise { +export async function updateProject( + projectId: number, + projectData: Partial +): Promise { const response = await authorized_fetch(`/api/projects/${projectId}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, @@ -55,22 +58,24 @@ export async function uploadProjectFiles(projectId: number, formData: FormData): } export async function fetchProjectFiles(projectId: number): Promise { - return authorized_fetch( - `/api/projects/${projectId}/test_files`, - { - method: "GET" - } - ).then(response => { - return response; - }).catch(error => console.error("Failed to fetch project files:", error)); + return authorized_fetch(`/api/projects/${projectId}/test_files`, { + method: "GET", + }) + .then((response) => { + return response; + }) + .catch((error) => console.error("Failed to fetch project files:", error)); } -export async function deleteProjectFiles(projectId: number, filesToDelete: string[]): Promise { +export async function deleteProjectFiles( + projectId: number, + filesToDelete: string[] +): Promise { return authorized_fetch( `/api/projects/${projectId}/test_files`, { method: "DELETE", - headers: { 'Content-Type': 'application/json' }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ files: filesToDelete }), }, true diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 6b12133b..d4093ce4 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -23,6 +23,16 @@ placeholder="Select Subject" /> + + + @@ -34,15 +44,7 @@ - - - @@ -53,12 +55,22 @@ - - + + - + +
+ + Note: Uploading new files will overwrite the existing ones. + +
+ +
@@ -68,9 +80,8 @@
- diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 76333d32..7a010fe1 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -33,6 +33,7 @@ :options="groupProjectOptions" @update:radio_date="handleRadioDateChange" @update:capacity="handleCapacityChange" + @update:selected-option="handleOptionChange" required /> @@ -236,116 +237,133 @@ const createProjectMutation = useCreateProjectMutation(); const createGroupsMutation = useCreateGroupsMutation(); const joinGroupMutation = useJoinGroupMutation(); const uploadProjectFilesMutation = useUploadProjectFilesMutation(); -const deleteProjectFilesMutation = useDeleteProjectFilesMutation(); const updateProjectMutation = useUpdateProjectMutation(); function handleRadioDateChange(newDate) { + console.log(newDate); enrollDeadline.value = newDate; } +function handleOptionChange(newVal){ + selectedGroupProject.value = newVal; + console.log(newVal); +} + +function setSuccessAlert(message) { + successMessage.value = message; + showSuccessAlert.value = true; +} + async function submitForm() { - const formattedDeadline = deadline.value.toISOString(); - const formattedPublishDate = publishDate.value.toISOString(); - const formattedEnrollDeadline = !enrollDeadline.value - ? null - : enrollDeadline.value.toISOString(); + const projectData = formatProjectData(); - const projectData = { + try { + if (isEditMode.value) { + await updateProject(projectData); + } else { + await createProject(projectData); + } + handleFiles(projectData.project_id); + navigateToProject(projectData.project_id); + } catch (error) { + console.error("Error during project or group creation or file upload:", error); + showErrorAlert("An unexpected error occurred. Please try again."); + } +} + +function formatProjectData() { + return { name: project_title.value, - deadline: formattedDeadline, + deadline: deadline.value.toISOString(), description: quillEditor.value?.getQuill().root.innerHTML || "", subject_id: selectedSubject.value, is_visible: true, capacity: capacity.value, requirements: [], - publish_date: formattedPublishDate, - enroll_deadline: formattedEnrollDeadline, + publish_date: publishDate.value.toISOString(), + enroll_deadline: enrollDeadline.value ? enrollDeadline.value.toISOString() : null, }; +} - try { - if (isEditMode.value) { - try { - await updateProjectMutation.mutateAsync({ - projectId: projectId.value, - projectData, - }); - - if (files.value.length > 0) { - const formData = new FormData(); - files.value.forEach((file) => { - formData.append("files", file); - }); - await uploadProjectFilesMutation.mutateAsync({ - projectId: projectId.value, - formData, - }); - } +async function updateProject(projectData) { + await updateProjectMutation.mutateAsync({ + projectId: projectId.value, + projectData, + }); + setSuccessAlert("Project updated successfully."); +} - router.push({ name: "project", params: { projectId: projectId.value } }); - successMessage.value = "Project updated successfully."; - showSuccessAlert.value = true; - } catch (error) { - console.error("Failed to update project and files", error); - errorMessage.value = "Failed to update project and files. Please try again."; - showErrorAlert.value = true; - } - } else { - const createdProjectId = await createProjectMutation.mutateAsync(projectData); - if (selectedGroupProject.value === "student") { - const emptyGroup = { - project_id: createdProjectId, - score: 0, - team_name: "Group 1", - }; - await createGroupsMutation.mutateAsync({ - projectId: createdProjectId, - groups: [emptyGroup], - }); - } else if (selectedGroupProject.value === "random") { - const groups = divideStudentsIntoGroups(studentsData.value || [], capacity.value); - const groupsToCreate = groups.map((_, i) => ({ - project_id: createdProjectId, - score: 0, - team_name: "Group " + (i + 1), - })); - const createdGroups = await createGroupsMutation.mutateAsync({ - projectId: createdProjectId, - groups: groupsToCreate, - }); - - createdGroups.forEach((group, index) => { - groups[index].forEach((student) => { - joinGroupMutation.mutateAsync({ - groupId: group.id, - uid: student.uid, - }); - }); - }); - } - - if (files.value.length > 0) { - const formData = new FormData(); - files.value.forEach((file) => { - formData.append("files", file); - }); - await uploadProjectFilesMutation.mutateAsync({ - projectId: createdProjectId, - formData, - }); - successMessage.value += " and files uploaded successfully."; - showSuccessAlert.value = true; - } - router.push({ name: "project", params: { projectId: createdProjectId } }); - successMessage.value = "Project created successfully."; - showSuccessAlert.value = true; +async function createProject(projectData) { + const createdProjectId = await createProjectMutation.mutateAsync(projectData); + projectData.project_id = createdProjectId; + await handleGroupCreation(createdProjectId); + setSuccessAlert("Project created successfully."); +} + +async function handleGroupCreation(projectId) { + console.log(selectedGroupProject.value); + if (selectedGroupProject.value === "student" && capacity.value != 1) { + const emptyGroups = generateEmptyGroups(projectId); + await createGroupsMutation.mutateAsync({ projectId, groups: emptyGroups }); + } else if (selectedGroupProject.value === "random" || capacity.value === 1) { + const groups = divideStudentsIntoGroups(studentsData.value || [], capacity.value); + console.log(groups); + const groupsToCreate = groups.map((_, i) => ({ + project_id: projectId, + score: 0, + team_name: "Group " + (i + 1), + })); + const createdGroups = await createGroupsMutation.mutateAsync({ + projectId: projectId, + groups: groupsToCreate, + }); + joinStudentsToGroups(createdGroups, groups); + } +} + +async function joinStudentsToGroups(createdGroups, studentGroups) { + for (let i = 0; i < createdGroups.length; i++) { + const group = createdGroups[i]; + const students = studentGroups[i]; + for (const student of students) { + await joinGroupMutation.mutateAsync({ + groupId: group.id, + uid: student.uid, + }); } - } catch (error) { - console.error("Error during project or group creation or file upload:", error); - errorMessage.value = "An unexpected error occurred. Please try again."; - showErrorAlert.value = true; } } +function generateEmptyGroups(projectId) { + const numberOfGroups = Math.ceil(studentsData.value.length / capacity.value); + const emptyGroups = []; + for (let i = 0; i < numberOfGroups; i++) { + emptyGroups.push({ + project_id: projectId, + score: 0, + team_name: `Group ${i + 1}`, + }); + } + return emptyGroups; +} + +async function handleFiles(projectId) { + if (files.value.length > 0) { + const formData = new FormData(); + files.value.forEach((file) => { + formData.append("files", file); + }); + await uploadProjectFilesMutation.mutateAsync({ + projectId: projectId, + formData, + }); + } +} + +function navigateToProject(projectId) { + router.push({ name: "project", params: { projectId } }); +} + function shuffle(array: any[]) { let shuffledArray = [...array]; let currentIndex = shuffledArray.length, @@ -367,6 +385,8 @@ function shuffle(array: any[]) { function divideStudentsIntoGroups(students: User[], capacity: number) { students = shuffle(students); let groups = []; + console.log(students.length); + console.log(capacity); const numberOfGroups = Math.ceil(students.length / capacity); for (let i = 0; i < numberOfGroups; i++) { groups.push(students.slice(i * capacity, (i + 1) * capacity)); @@ -375,13 +395,9 @@ function divideStudentsIntoGroups(students: User[], capacity: number) { return groups; } -function formatInstructor(user: User): CheckBoxItem { - const checked = false; - return { id: user.uid, label: user.given_name, checked }; -} - const handleCapacityChange = (newCapacity: number) => { capacity.value = newCapacity; + console.log(capacity.value); }; From 453304af6f65bf6c63f3a9fc9c21bf18676b92b8 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Thu, 16 May 2024 23:44:34 +0200 Subject: [PATCH 30/68] requirements --- frontend/src/components/RequirementsInput.vue | 77 +++++++++++++++++++ frontend/src/models/Project.ts | 11 ++- frontend/src/views/CreateProjectView.vue | 29 ++++--- frontend/src/views/ProjectView.vue | 1 - 4 files changed, 102 insertions(+), 16 deletions(-) create mode 100644 frontend/src/components/RequirementsInput.vue diff --git a/frontend/src/components/RequirementsInput.vue b/frontend/src/components/RequirementsInput.vue new file mode 100644 index 00000000..972fc194 --- /dev/null +++ b/frontend/src/components/RequirementsInput.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/frontend/src/models/Project.ts b/frontend/src/models/Project.ts index 9d4cbb2f..afceb2ae 100644 --- a/frontend/src/models/Project.ts +++ b/frontend/src/models/Project.ts @@ -2,10 +2,8 @@ export default interface Project { id: number; name: string; deadline: Date; - // groupProjectType: string; - // selectedTeachers: string[]; // Assuming you store only teacher IDs subject_id: number; - requirements: []; + requirements: Requirement[]; description: string; capacity: number; enroll_deadline: Date; @@ -20,7 +18,7 @@ export interface ProjectForm { test_files_uuid: string; is_visible: boolean; capacity: number; - requirements: []; + requirements: Requirement[]; } export interface Deadline { @@ -33,3 +31,8 @@ export enum FilterOptions { Completed = "Completed", Active = "Active", } + +export interface Requirement { + mandatory: boolean; + value: string; +} diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 7a010fe1..837548d5 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -75,6 +75,9 @@ + + + @@ -91,7 +94,7 @@ diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 5d88f0fe..969bea00 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -33,6 +33,8 @@ export default { new_submission: "Nieuwe indiening", status_submission: "Indiening is: {status}", no_submission_files: "Geen indieningen gevonden", + files_usage_note: "Info voor het gebruik van testbestanden", + files_disclaimer: "Voor info over het gebruik van testbestanden, bezoek onze ", }, submission: { status: "Indiening status: {status}", @@ -80,6 +82,9 @@ export default { forbidden: "Verboden", invalid_format: "Voer een geldig bestandstype in, inclusief de extensie (bv. 'afbeelding.png')", + files_will_be_overwritten: + "Bij het uploaden van een nieuw testbestand zullen alle huidige testbestanden verwijderd worden.", + testfiles: "Testbestanden", }, navigation: { home: "Hoofdscherm", From 18caa7628bda862afb2ffff386b23b161ef015e3 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 17:50:10 +0200 Subject: [PATCH 49/68] Update frontend/src/components/RequirementsInput.vue Co-authored-by: Pieter Janin --- frontend/src/components/RequirementsInput.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/RequirementsInput.vue b/frontend/src/components/RequirementsInput.vue index 9e8afe4e..386b25de 100644 --- a/frontend/src/components/RequirementsInput.vue +++ b/frontend/src/components/RequirementsInput.vue @@ -40,7 +40,7 @@ diff --git a/frontend/src/components/project/RequirementsInput.vue b/frontend/src/components/project/RequirementsInput.vue new file mode 100644 index 00000000..38d7fc99 --- /dev/null +++ b/frontend/src/components/project/RequirementsInput.vue @@ -0,0 +1,108 @@ + + + + diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 46384af4..d1f3613a 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -82,6 +82,7 @@ export default { "Please enter a valid file type, including the extension (e.g., 'image.png')", files_will_be_overwritten: "By uploading another file current files will be overwritten.", testfiles: "Testfiles", + requirements_disclaimer: "For info on the usage of requirements please visit our", }, navigation: { home: "Home", diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 969bea00..26464668 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -85,6 +85,7 @@ export default { files_will_be_overwritten: "Bij het uploaden van een nieuw testbestand zullen alle huidige testbestanden verwijderd worden.", testfiles: "Testbestanden", + requirements_disclaimer: "Voor info over het gebruik van bestandsvereisten, bezoek onze ", }, navigation: { home: "Hoofdscherm", diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index 20a807a1..811c04e4 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -1,4 +1,4 @@ -import { computed, toValue } from "vue"; +import { computed, type Ref, toValue } from "vue"; import type { MaybeRefOrGetter } from "vue"; import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; import type { UseMutationReturnType, UseQueryReturnType } from "@tanstack/vue-query"; @@ -33,7 +33,7 @@ function SUBMISSIONS_QUERY_KEY(): string[] { return ["submissions"]; } -function projectFilesQueryKey(projectId: number): (string | number)[] { +function TEST_FILES_QUERY_KEY(projectId: number): (string | number)[] { return ["projectFiles", projectId]; } @@ -99,24 +99,29 @@ export function useUpdateProjectMutation(): UseMutationReturnType< void > { const queryClient = useQueryClient(); - return useMutation }, void>( - { - mutationFn: ({ projectId, projectData }) => updateProject(projectId, projectData), + return useMutation< + Project, + Error, + { projectId: number; projectData: Partial }, + void + >({ + mutationFn: ({ projectId, projectData }) => updateProject(projectId, projectData), - onSuccess: (_, variables) => { - queryClient.invalidateQueries({ queryKey: PROJECT_QUERY_KEY(variables.projectId) }); - console.log("Project updated successfully."); - }, + onSuccess: (_, variables) => { + queryClient.invalidateQueries({ queryKey: PROJECT_QUERY_KEY(variables.projectId) }); + console.log("Project updated successfully."); + }, - onError: (error) => { - console.error("Project update failed", error); - alert("Failed to update project. Please try again."); - }, - } - ); + onError: (error) => { + console.error("Project update failed", error); + alert("Failed to update project. Please try again."); + }, + }); } -export function useProjectFilesQuery(projectId: MaybeRefOrGetter): UseQueryReturnType { +export function useProjectFilesQuery( + projectId: MaybeRefOrGetter +): UseQueryReturnType { return useQuery({ queryKey: TEST_FILES_QUERY_KEY(toValue(projectId)!), queryFn: () => fetchProjectFiles(toValue(projectId)!), diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index ea6ebdcd..6fad86ce 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -1,6 +1,7 @@ import type Project from "@/models/Project"; import type { ProjectForm, UserProjectList } from "@/models/Project"; import { authorized_fetch } from "@/services"; +import type Submission from "@/models/Submission"; function initProjectDate(project: Project): Project { return { ...project, deadline: new Date(project.deadline) }; @@ -75,11 +76,7 @@ export async function uploadProjectFiles(projectId: number, formData: FormData): export async function fetchProjectFiles(projectId: number): Promise { return authorized_fetch(`/api/projects/${projectId}/test_files`, { method: "GET", - }) - .then((response) => { - return response; - }) - .catch((error) => console.error("Failed to fetch project files:", error)); + }); } export async function deleteProjectFiles( diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 8752b1d8..fc9c2a8c 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -115,11 +115,7 @@ import FilesInput from "@/components/form_elements/FilesInput.vue"; import { QuillEditor } from "@vueup/vue-quill"; import "@vueup/vue-quill/dist/vue-quill.snow.css"; import { useRoute } from "vue-router"; -import { - useSubjectsQuery, - useSubjectInstructorsQuery, - useSubjectStudentsQuery, -} from "@/queries/Subject"; +import { useSubjectsQuery, useSubjectStudentsQuery } from "@/queries/Subject"; import { useCreateGroupsMutation, useAddToGroupMutation } from "@/queries/Group"; import { ref, reactive, computed } from "vue"; @@ -133,7 +129,7 @@ import { import type User from "@/models/User"; import DisplayTestFiles from "@/components/project/DisplayTestFiles.vue"; import router from "@/router"; -import RequirementsInput from "@/components/RequirementsInput.vue"; +import RequirementsInput from "@/components/project/RequirementsInput.vue"; import { useI18n } from "vue-i18n"; const route = useRoute(); const { t } = useI18n(); @@ -157,23 +153,21 @@ const requirements = ref([]); const projectId = ref(route.params.projectId); const isEditMode = computed(() => projectId.value !== undefined); +// Query to fetch and handle files associated with a project +const { + data: filesData, + isError: filesError, + isLoading: filesLoading, +} = useProjectFilesQuery(projectId.value); + +// Query to fetch and handle project details const { data: projectData, isLoading: isProjectLoading, isError: isProjectError, } = useProjectQuery(projectId); -const { - data: filesData, - isLoading: isFilesLoading, - isError: isFilesError, -} = useProjectFilesQuery(projectId.value); - -function htmlDecode(input) { - const doc = new DOMParser().parseFromString(input, "text/html"); - return doc.documentElement.textContent; -} - +// Watching projectData for changes to update the form data watch( projectData, (project) => { @@ -187,8 +181,7 @@ watch( nextTick(() => { if (quillEditor.value && quillEditor.value.getQuill) { let quill = quillEditor.value.getQuill(); - quill.root.innerHTML = ""; - quill.clipboard.dangerouslyPasteHTML(description); + quill.root.innerHTML = description; } else { console.error("Quill Editor is not initialized"); } @@ -198,11 +191,12 @@ watch( { deep: true } ); +// Computed properties for deadline and publish date to handle their updates const deadlineModel = computed({ get: () => deadline.value, set: (newValue) => { if (newValue.toISOString() !== deadline.value.toISOString()) { - deadline.value = new Date(newValue); // Make sure newValue is correctly formatted + deadline.value = new Date(newValue); } }, }); @@ -211,11 +205,12 @@ const publishDateModel = computed({ get: () => publishDate.value, set: (newValue) => { if (newValue.toISOString() !== publishDate.value.toISOString()) { - publishDate.value = new Date(newValue); // Make sure newValue is correctly formatted + publishDate.value = new Date(newValue); } }, }); +// Functions to update deadline and publish date based on user interaction function updateDeadline(val) { deadlineModel.value = val; } @@ -224,6 +219,7 @@ function updatePublishDate(val) { publishDateModel.value = val; } +// Setting up reactive properties for selected subject, subject options, and group project options const selectedSubject = ref( isEditMode.value ? projectSubjectId.value ?? Number(route.params.subjectId) @@ -232,17 +228,21 @@ const selectedSubject = ref( const { data: subjectsData, - isLoading: isSubjectsLoading, - isError: isSubjectsError, - error: subjectsError, + isLoading: subjectsLoading, + isError: subjectsError, } = useSubjectsQuery(); -const { data: instructorsData } = useSubjectInstructorsQuery(selectedSubject); -const { data: studentsData } = useSubjectStudentsQuery(selectedSubject); + +const { + data: studentsData, + isLoading: studentsLoading, + isError: studentsError, +} = useSubjectStudentsQuery(selectedSubject); const subjects = computed( () => subjectsData.value?.as_instructor.map(({ name, id }) => ({ text: name, value: id })) || [] ); +//Option that are passed to radiobuttonlist (selection for group creation) const groupProjectOptions = computed(() => [ { label: t("project.random"), value: "random" }, { label: t("project.student_groups"), value: "student" }, @@ -266,14 +266,15 @@ const handleCapacityChange = (newCapacity: number) => { capacity.value = newCapacity; }; +// Functions to display success and error messages function setSuccessAlert(message) { successMessage.value = message; showSuccessAlert.value = true; } function setErrorAlert(message) { - errorMessage.value = message; // Set the error message - showErrorAlert.value = true; // Show the error alert + errorMessage.value = message; + showErrorAlert.value = true; } async function submitForm() { @@ -284,11 +285,12 @@ async function submitForm() { } else { await createProject(projectData); } - navigateToProject(projectData.project_id); + handleFiles(projectData.project_id); } catch (error) { console.error("Error during project or group creation or file upload:", error); setErrorAlert("An unexpected error occurred. Please try again."); } + navigateToProject(projectData.project_id); } function formatProjectData() { @@ -306,12 +308,11 @@ function formatProjectData() { } async function updateProject(projectData) { - projectData.id = projectId.value; + projectData.project_id = projectId.value; await updateProjectMutation.mutateAsync({ projectId: projectId.value, projectData, }); - handleFiles(projectId.value); setSuccessAlert("Project updated successfully."); } @@ -319,10 +320,9 @@ async function createProject(projectData) { const createdProjectId = await createProjectMutation.mutateAsync(projectData); projectData.project_id = createdProjectId; await handleGroupCreation(createdProjectId); - handleFiles(projectData.project_id); setSuccessAlert("Project created successfully."); } - +// Group creation and management functions async function handleGroupCreation(projectId) { if (selectedGroupProject.value === "student" && capacity.value != 1) { const emptyGroups = generateEmptyGroups(projectId); @@ -341,7 +341,7 @@ async function handleGroupCreation(projectId) { joinStudentsToGroups(createdGroups, groups); } } - +// Function to join students to groups async function joinStudentsToGroups(createdGroups, studentGroups) { for (let i = 0; i < createdGroups.length; i++) { const group = createdGroups[i]; @@ -354,7 +354,7 @@ async function joinStudentsToGroups(createdGroups, studentGroups) { } } } - +// Function to generate empty groups for a project function generateEmptyGroups(projectId) { const numberOfGroups = Math.ceil(studentsData.value.length / capacity.value); const emptyGroups = []; @@ -367,7 +367,7 @@ function generateEmptyGroups(projectId) { } return emptyGroups; } - +// Function to handle file uploads for a project async function handleFiles(projectId) { if (files.value.length > 0) { const formData = new FormData(); @@ -380,11 +380,12 @@ async function handleFiles(projectId) { }); } } - +//navigate to project after creation/editing function navigateToProject(projectId) { router.push({ name: "project", params: { projectId } }); } +//shuffle function (used for randomization in groups) function shuffle(array: any[]) { let shuffledArray = [...array]; let currentIndex = shuffledArray.length, From a6307395404997c56f10d7076811c5826fa6ba57 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 18:54:17 +0200 Subject: [PATCH 57/68] loading fix --- frontend/src/views/CreateProjectView.vue | 297 ++++++++++++----------- 1 file changed, 159 insertions(+), 138 deletions(-) diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index fc9c2a8c..89f305c2 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -1,109 +1,118 @@ @@ -153,6 +162,13 @@ const requirements = ref([]); const projectId = ref(route.params.projectId); const isEditMode = computed(() => projectId.value !== undefined); +//mutations +const createProjectMutation = useCreateProjectMutation(); +const createGroupsMutation = useCreateGroupsMutation(); +const joinGroupMutation = useAddToGroupMutation(); +const uploadProjectFilesMutation = useUploadProjectFilesMutation(); +const updateProjectMutation = useUpdateProjectMutation(); + // Query to fetch and handle files associated with a project const { data: filesData, @@ -167,6 +183,46 @@ const { isError: isProjectError, } = useProjectQuery(projectId); +const { + data: subjectsData, + isLoading: subjectsLoading, + isError: subjectsError, +} = useSubjectsQuery(); + +const selectedSubject = ref( + isEditMode.value + ? projectSubjectId.value ?? Number(route.params.subjectId) + : Number(route.params.subjectId) +); + +const { + data: studentsData, + isLoading: studentsLoading, + isError: studentsError, +} = useSubjectStudentsQuery(selectedSubject); + +const isDataLoading = computed(() => { + return isEditMode.value + ? isProjectLoading.value || filesLoading.value || studentsLoading.value + : subjectsLoading.value || studentsLoading.value; +}); + +const isDataError = computed(() => { + return isEditMode.value + ? isProjectError.value || filesError.value || studentsError.value + : subjectsError.value || studentsError.value; +}); + +const subjects = computed( + () => subjectsData.value?.as_instructor.map(({ name, id }) => ({ text: name, value: id })) || [] +); + +//Option that are passed to radiobuttonlist (selection for group creation) +const groupProjectOptions = computed(() => [ + { label: t("project.random"), value: "random" }, + { label: t("project.student_groups"), value: "student" }, +]); + // Watching projectData for changes to update the form data watch( projectData, @@ -219,41 +275,6 @@ function updatePublishDate(val) { publishDateModel.value = val; } -// Setting up reactive properties for selected subject, subject options, and group project options -const selectedSubject = ref( - isEditMode.value - ? projectSubjectId.value ?? Number(route.params.subjectId) - : Number(route.params.subjectId) -); - -const { - data: subjectsData, - isLoading: subjectsLoading, - isError: subjectsError, -} = useSubjectsQuery(); - -const { - data: studentsData, - isLoading: studentsLoading, - isError: studentsError, -} = useSubjectStudentsQuery(selectedSubject); - -const subjects = computed( - () => subjectsData.value?.as_instructor.map(({ name, id }) => ({ text: name, value: id })) || [] -); - -//Option that are passed to radiobuttonlist (selection for group creation) -const groupProjectOptions = computed(() => [ - { label: t("project.random"), value: "random" }, - { label: t("project.student_groups"), value: "student" }, -]); - -const createProjectMutation = useCreateProjectMutation(); -const createGroupsMutation = useCreateGroupsMutation(); -const joinGroupMutation = useAddToGroupMutation(); -const uploadProjectFilesMutation = useUploadProjectFilesMutation(); -const updateProjectMutation = useUpdateProjectMutation(); - function handleRadioDateChange(newDate) { enrollDeadline.value = newDate; } From 607153be15ea7c4c0bccecc0f52ca2e6f154e842 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 19:53:18 +0200 Subject: [PATCH 58/68] Update frontend/src/components/project/DatePicker.vue Co-authored-by: Pieter Janin --- frontend/src/components/project/DatePicker.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/project/DatePicker.vue b/frontend/src/components/project/DatePicker.vue index b957305f..0e539148 100644 --- a/frontend/src/components/project/DatePicker.vue +++ b/frontend/src/components/project/DatePicker.vue @@ -31,9 +31,9 @@ const props = defineProps<{ }>(); const emit = defineEmits(["update:modelValue"]); -const menuVisible = ref(false); -const date = ref(new Date(props.modelValue || Date.now())); // Initialize with current date or modelValue -const time = ref(formatTime(props.modelValue || new Date())); // Initialize with current time or modelValue +const menuVisible = ref(false); +const date = ref(new Date(props.modelValue || Date.now())); // Initialize with current date or modelValue +const time = ref(formatTime(props.modelValue || new Date())); // Initialize with current time or modelValue // Watcher to sync changes in modelValue to date and time pickers watch( From 93964d1f382b591424d0e108d81d48920a20ecc4 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 20:04:29 +0200 Subject: [PATCH 59/68] fixes vooral cleanup --- .../components/form_elements/FilesInput.vue | 12 ------ frontend/src/queries/Project.ts | 39 +------------------ frontend/src/services/project.ts | 31 --------------- frontend/src/views/CreateProjectView.vue | 17 +++++++- 4 files changed, 17 insertions(+), 82 deletions(-) diff --git a/frontend/src/components/form_elements/FilesInput.vue b/frontend/src/components/form_elements/FilesInput.vue index cc52e8ee..a44d8405 100644 --- a/frontend/src/components/form_elements/FilesInput.vue +++ b/frontend/src/components/form_elements/FilesInput.vue @@ -1,17 +1,5 @@ + From a833f1c1cb073b3310a9a5d8d95c4acce69eb608 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 20:20:06 +0200 Subject: [PATCH 61/68] fixes vooral cleanup --- frontend/src/components/project/RadiobuttonList.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/project/RadiobuttonList.vue b/frontend/src/components/project/RadiobuttonList.vue index 6aa15e24..312f60b7 100644 --- a/frontend/src/components/project/RadiobuttonList.vue +++ b/frontend/src/components/project/RadiobuttonList.vue @@ -72,6 +72,4 @@ function handleCapacityInput(event: Event) { } emit("update:capacity", capacity.value); } - - From ada4baea53dcbfa0a816a4a7bf85f17e5c27e7f3 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 20:24:04 +0200 Subject: [PATCH 62/68] Revert "upgrade @vue/test-utils (#181)" This reverts commit 87ed14205bbd85eb57565562496ac64ef72b969f. --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ef0fd8e7..7e82aefc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,7 +36,7 @@ "@vitest/coverage-v8": "^1.5.0", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "@vue/test-utils": "^2.4.6", + "@vue/test-utils": "^2.4.4", "@vue/tsconfig": "^0.5.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -1882,9 +1882,9 @@ "integrity": "sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg==" }, "node_modules/@vue/test-utils": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", - "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz", + "integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==", "dev": true, "dependencies": { "js-beautify": "^1.14.9", diff --git a/frontend/package.json b/frontend/package.json index 192c4289..07f5d091 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,7 @@ "@vitest/coverage-v8": "^1.5.0", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "@vue/test-utils": "^2.4.6", + "@vue/test-utils": "^2.4.4", "@vue/tsconfig": "^0.5.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", From d9a0a64fbaed19d5cc847c0f1780dd248fd0b889 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 20:25:56 +0200 Subject: [PATCH 63/68] luxon weg? --- frontend/package-lock.json | 17 ++++++++++++----- frontend/package.json | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7e82aefc..31892a16 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -36,7 +36,7 @@ "@vitest/coverage-v8": "^1.5.0", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "@vue/test-utils": "^2.4.4", + "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.5.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -1882,9 +1882,9 @@ "integrity": "sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg==" }, "node_modules/@vue/test-utils": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.5.tgz", - "integrity": "sha512-oo2u7vktOyKUked36R93NB7mg2B+N7Plr8lxp2JBGwr18ch6EggFjixSCdIVVLkT6Qr0z359Xvnafc9dcKyDUg==", + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.6.tgz", + "integrity": "sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==", "dev": true, "dependencies": { "js-beautify": "^1.14.9", @@ -4398,7 +4398,14 @@ "engines": { "node": "14 || >=16.14" } - } + }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } }, "node_modules/magic-string": { "version": "0.30.10", diff --git a/frontend/package.json b/frontend/package.json index 07f5d091..192c4289 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,7 @@ "@vitest/coverage-v8": "^1.5.0", "@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-typescript": "^12.0.0", - "@vue/test-utils": "^2.4.4", + "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.5.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", From e49a43317aeb6c2bd173987526bb2692293188a6 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 20:30:47 +0200 Subject: [PATCH 64/68] luxon echt weg? --- frontend/package-lock.json | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 31892a16..4da72b02 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,7 +15,6 @@ "@vueup/vue-quill": "^1.2.0", "@vueuse/core": "^10.9.0", "clonedeep": "^2.0.0", - "luxon": "^3.4.4", "pinia": "^2.1.7", "roboto-fontface": "*", "vue": "^3.4.15", @@ -4398,14 +4397,7 @@ "engines": { "node": "14 || >=16.14" } - }, - "node_modules/luxon": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", - "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", - "engines": { - "node": ">=12" - } + } }, "node_modules/magic-string": { "version": "0.30.10", From 3e4d6921ea2f3ddace121c9da62eddc21fc13236 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 20:37:44 +0200 Subject: [PATCH 65/68] luxon echt echt weg? --- frontend/package-lock.json | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4da72b02..fc7158db 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4397,7 +4397,6 @@ "engines": { "node": "14 || >=16.14" } - } }, "node_modules/magic-string": { "version": "0.30.10", From 7a48a68017c7ce3e3af1b785a00e650c680f9443 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Mon, 20 May 2024 22:00:19 +0200 Subject: [PATCH 66/68] renaming + unused gone --- frontend/src/queries/Project.ts | 26 +++++++----------------- frontend/src/services/project.ts | 4 ++-- frontend/src/views/CreateProjectView.vue | 8 ++++---- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index 921056cf..c4543867 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -9,9 +9,9 @@ import { getProject, createProject, getProjects, - uploadProjectFiles, + uploadTestFiles, updateProject, - fetchProjectFiles, + fetchTestFiles, } from "@/services/project"; function PROJECT_QUERY_KEY(projectId: number): (string | number)[] { @@ -27,7 +27,7 @@ function PROJECTS_QUERY_KEY(): string[] { */ function TEST_FILES_QUERY_KEY(projectId: number): (string | number)[] { - return ["projectFiles", projectId]; + return ["testFiles", projectId]; } // Hook for fetching project details @@ -51,18 +51,6 @@ export function useProjectsQuery(): UseQueryReturnType { }); } -// Hook for creating a new submission -export function useCreateSubmissionMutation( - groupId: Ref -): UseMutationReturnType { - return useMutation({ - mutationFn: (formData) => createSubmission(groupId.value!, formData), - onError: (error) => { - console.error("Submission creation failed", error); - alert("Could not create submission. Please try again."); - }, - }); -} /** * Mutation composable for creating a project */ @@ -112,17 +100,17 @@ export function useUpdateProjectMutation(): UseMutationReturnType< }); } -export function useProjectFilesQuery( +export function useTestFilesQuery( projectId: MaybeRefOrGetter ): UseQueryReturnType { return useQuery({ queryKey: TEST_FILES_QUERY_KEY(toValue(projectId)!), - queryFn: () => fetchProjectFiles(toValue(projectId)!), + queryFn: () => fetchTestFiles(toValue(projectId)!), enabled: () => !!toValue(projectId), // Only fetch when a projectId is provided }); } // Hook for uploading files to a project -export function useUploadProjectFilesMutation(): UseMutationReturnType< +export function useUploadTestFilesMutation(): UseMutationReturnType< void, // Type of data returned on success Error, // Type of error { projectId: number; formData: FormData }, @@ -130,7 +118,7 @@ export function useUploadProjectFilesMutation(): UseMutationReturnType< > { const queryClient = useQueryClient(); return useMutation({ - mutationFn: ({ projectId, formData }) => uploadProjectFiles(projectId, formData), + mutationFn: ({ projectId, formData }) => uploadTestFiles(projectId, formData), onSuccess: (_, { projectId }) => { queryClient.invalidateQueries({ queryKey: PROJECT_QUERY_KEY(projectId) }); console.log("Files uploaded successfully"); diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index fbfdfdf1..310ce65f 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -62,7 +62,7 @@ export async function updateProject( } // Function to upload test files to a specific project -export async function uploadProjectFiles(projectId: number, formData: FormData): Promise { +export async function uploadTestFiles(projectId: number, formData: FormData): Promise { await authorized_fetch( `/api/projects/${projectId}/test_files`, { @@ -73,7 +73,7 @@ export async function uploadProjectFiles(projectId: number, formData: FormData): ); } -export async function fetchProjectFiles(projectId: number): Promise { +export async function fetchTestFiles(projectId: number): Promise { return authorized_fetch(`/api/projects/${projectId}/test_files`, { method: "GET", }); diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 8fff8fa9..648e6fab 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -141,10 +141,10 @@ import { useCreateGroupsMutation, useAddToGroupMutation } from "@/queries/Group" import { ref, reactive, computed } from "vue"; import { useCreateProjectMutation, - useProjectFilesQuery, + useTestFilesQuery, useProjectQuery, useUpdateProjectMutation, - useUploadProjectFilesMutation, + useUploadTestFilesMutation, } from "@/queries/Project"; import type User from "@/models/User"; import DisplayTestFiles from "@/components/project/DisplayTestFiles.vue"; @@ -177,7 +177,7 @@ const isEditMode = computed(() => projectId.value !== undefined); const createProjectMutation = useCreateProjectMutation(); const createGroupsMutation = useCreateGroupsMutation(); const joinGroupMutation = useAddToGroupMutation(); -const uploadProjectFilesMutation = useUploadProjectFilesMutation(); +const uploadProjectFilesMutation = useUploadTestFilesMutation(); const updateProjectMutation = useUpdateProjectMutation(); // Query to fetch and handle files associated with a project @@ -185,7 +185,7 @@ const { data: filesData, isError: filesError, isLoading: filesLoading, -} = useProjectFilesQuery(projectId.value); +} = useTestFilesQuery(projectId.value); // Query to fetch and handle project details const { From 2ea6979ea59740f08feb51df4f3d8a5a6f000864 Mon Sep 17 00:00:00 2001 From: Bram Reyniers Date: Mon, 20 May 2024 23:29:54 +0200 Subject: [PATCH 67/68] some small fixes --- .../components/project/RequirementsInput.vue | 2 +- frontend/src/views/CreateProjectView.vue | 54 +++++++++---------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/project/RequirementsInput.vue b/frontend/src/components/project/RequirementsInput.vue index 38d7fc99..56264aa4 100644 --- a/frontend/src/components/project/RequirementsInput.vue +++ b/frontend/src/components/project/RequirementsInput.vue @@ -3,7 +3,7 @@ {{ $t("project.requirements_disclaimer") }} diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 648e6fab..11707c1e 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -138,7 +138,7 @@ import { useRoute } from "vue-router"; import { useSubjectsQuery, useSubjectStudentsQuery } from "@/queries/Subject"; import { useCreateGroupsMutation, useAddToGroupMutation } from "@/queries/Group"; -import { ref, reactive, computed } from "vue"; +import { ref, computed } from "vue"; import { useCreateProjectMutation, useTestFilesQuery, @@ -151,6 +151,7 @@ import DisplayTestFiles from "@/components/project/DisplayTestFiles.vue"; import router from "@/router"; import RequirementsInput from "@/components/project/RequirementsInput.vue"; import { useI18n } from "vue-i18n"; +import type Project from "@/models/Project"; const route = useRoute(); const { t } = useI18n(); @@ -168,9 +169,9 @@ const showErrorAlert = ref(false); const showSuccessAlert = ref(false); const errorMessage = ref(""); const successMessage = ref(""); -const requirements = ref([]); +const requirements = ref<{ mandatory: boolean; value: string }[]>([]); -const projectId = ref(route.params.projectId); +const projectId = ref(Number(route.params.projectId)); const isEditMode = computed(() => projectId.value !== undefined); //mutations @@ -185,7 +186,7 @@ const { data: filesData, isError: filesError, isLoading: filesLoading, -} = useTestFilesQuery(projectId.value); +} = useTestFilesQuery(projectId); // Query to fetch and handle project details const { @@ -236,24 +237,21 @@ const groupProjectOptions = computed(() => [ // Watching projectData for changes to update the form data watch( - projectData, - (project) => { - if (project) { - project_title.value = project.name; - deadline.value = new Date(project.deadline); - publishDate.value = new Date(project.publish_date); - requirements.value = project.requirements.map((req) => ({ ...req })); - const description = project.description; - selectedSubject.value = project.subject_id; - nextTick(() => { - if (quillEditor.value && quillEditor.value.getQuill) { - let quill = quillEditor.value.getQuill(); - quill.root.innerHTML = description; - } else { - console.error("Quill Editor is not initialized"); - } - }); - } + [projectData, quillEditor], + (/* project: Project | undefined */) => { + if (!projectData.value) return; + const project: Project = projectData.value; + project_title.value = project.name; + deadline.value = new Date(project.deadline); + publishDate.value = new Date(project.publish_date); + requirements.value = project.requirements.map((req) => ({ ...req })); + selectedSubject.value = project.subject_id; + nextTick(() => { + if (quillEditor.value && quillEditor.value.getQuill) { + let quill = quillEditor.value.getQuill(); + quill.root.innerHTML = project.description; + } + }); }, { deep: true } ); @@ -317,12 +315,12 @@ async function submitForm() { } else { await createProject(projectData); } - handleFiles(projectData.project_id); + handleFiles(projectId.value); } catch (error) { console.error("Error during project or group creation or file upload:", error); setErrorAlert("An unexpected error occurred. Please try again."); } - navigateToProject(projectData.project_id); + navigateToProject(projectId.value); } function formatProjectData() { @@ -387,8 +385,8 @@ async function joinStudentsToGroups(createdGroups, studentGroups) { } } // Function to generate empty groups for a project -function generateEmptyGroups(projectId) { - const numberOfGroups = Math.ceil(studentsData.value.length / capacity.value); +function generateEmptyGroups(projectId: number) { + const numberOfGroups = Math.ceil(studentsData.value!.length / capacity.value); const emptyGroups = []; for (let i = 0; i < numberOfGroups; i++) { emptyGroups.push({ @@ -400,7 +398,7 @@ function generateEmptyGroups(projectId) { return emptyGroups; } // Function to handle file uploads for a project -async function handleFiles(projectId) { +async function handleFiles(projectId: number) { if (files.value.length > 0) { const formData = new FormData(); files.value.forEach((file) => { @@ -413,7 +411,7 @@ async function handleFiles(projectId) { } } //navigate to project after creation/editing -function navigateToProject(projectId) { +function navigateToProject(projectId: number) { router.push({ name: "project", params: { projectId } }); } From 4dd6ff5c91115c1f815c812cbe255f18fd091815 Mon Sep 17 00:00:00 2001 From: drieshuybens Date: Tue, 21 May 2024 11:58:57 +0200 Subject: [PATCH 68/68] bramfixes --- frontend/src/components/project/RadiobuttonList.vue | 13 +++++++------ .../src/components/project/RequirementsInput.vue | 3 +-- frontend/src/i18n/locales/en.ts | 2 -- frontend/src/i18n/locales/nl.ts | 2 -- frontend/src/queries/Project.ts | 7 +++---- frontend/src/views/CreateProjectView.vue | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/project/RadiobuttonList.vue b/frontend/src/components/project/RadiobuttonList.vue index 312f60b7..6df6cf28 100644 --- a/frontend/src/components/project/RadiobuttonList.vue +++ b/frontend/src/components/project/RadiobuttonList.vue @@ -30,13 +30,14 @@