From a12be714f0145d6952d2c14db6a67ddde1e806c8 Mon Sep 17 00:00:00 2001
From: Bjarke Madsen <bjarke@nordu.net>
Date: Mon, 17 Feb 2025 10:57:39 +0100
Subject: [PATCH] Fix model mutation and improve feedback for survey validation

---
 .../src/survey/SurveyContainerComponent.tsx   | 52 ++++++++++++-------
 .../management/SurveyManagementComponent.tsx  | 14 +++--
 2 files changed, 44 insertions(+), 22 deletions(-)

diff --git a/compendium-frontend/src/survey/SurveyContainerComponent.tsx b/compendium-frontend/src/survey/SurveyContainerComponent.tsx
index 1b080acb..99491208 100644
--- a/compendium-frontend/src/survey/SurveyContainerComponent.tsx
+++ b/compendium-frontend/src/survey/SurveyContainerComponent.tsx
@@ -18,6 +18,18 @@ import { userContext } from "compendium/providers/UserProvider";
 Serializer.addProperty("itemvalue", "customDescription:text");
 Serializer.addProperty("question", "hideCheckboxLabels:boolean");
 
+function modifyModel(surveyModel: Model) {
+    // Used to create a new reference to the survey model to force a rerender.
+    // This is a hack to get around the fact that the survey model state is not part of the react state.
+    const newModel = Object.create(surveyModel);
+
+    if (!newModel.css.question.title.includes("sv-header-flex")) {
+        newModel.css.question.title = "sv-title sv-question__title sv-header-flex";
+        newModel.css.question.titleOnError = "sv-question__title--error sv-error-color-fix";
+    }
+    return newModel
+}
+
 function SurveyContainerComponent({ loadFrom }) {
     const [surveyModel, setSurveyModel] = useState<Model>();  // note that this is never updated and we abuse that fact by adding extra state to the surveyModel
     const { year, nren } = useParams();  // nren stays empty for inspect and try
@@ -122,9 +134,11 @@ function SurveyContainerComponent({ loadFrom }) {
             if (!response.ok) {
                 return json['message'];
             }
-            surveyModel.mode = json['mode'];
-            surveyModel['lockedBy'] = json['locked_by'];
-            surveyModel['status'] = json['status'];
+            const newModel = modifyModel(survey);
+            newModel.mode = json['mode'];
+            newModel['lockedBy'] = json['locked_by'];
+            newModel['status'] = json['status'];
+            setSurveyModel(newModel);
         } catch (e) {
             return "Unknown Error: " + (e as Error).message;
         }
@@ -204,12 +218,14 @@ function SurveyContainerComponent({ loadFrom }) {
             for (const questionName in json["verification_status"]) {
                 surveyModel['verificationStatus'].set(questionName, json["verification_status"][questionName]);
             }
-            surveyModel.data = json['data'];
-            surveyModel.clearIncorrectValues(true);
-            surveyModel.mode = json['mode'];
-            surveyModel['lockedBy'] = json['locked_by']
-            surveyModel['lockUUID'] = json['lock_uuid'];
-            surveyModel['status'] = json['status'];
+            const newModel = modifyModel(surveyModel);
+            newModel.data = json['data'];
+            newModel.clearIncorrectValues(true);
+            newModel.mode = json['mode'];
+            newModel['lockedBy'] = json['locked_by']
+            newModel['lockUUID'] = json['lock_uuid'];
+            newModel['status'] = json['status'];
+            setSurveyModel(newModel);
             // Validate when we start editing to ensure invalid fields are corrected by the user
             const allFieldsValid = validateWithAnswerVerification(surveyModel.validate.bind(surveyModel, true, true), false);
             if (!allFieldsValid) {
@@ -225,9 +241,11 @@ function SurveyContainerComponent({ loadFrom }) {
                 toast("Failed releasing lock: " + json['message']);
                 return;
             }
-            surveyModel.mode = json['mode'];
-            surveyModel['lockedBy'] = json['locked_by'];
-            surveyModel['status'] = json['status'];
+            const newModel = modifyModel(surveyModel);
+            newModel.mode = json['mode'];
+            newModel['lockedBy'] = json['locked_by'];
+            newModel['status'] = json['status'];
+            setSurveyModel(newModel);
         },
         'validatePage': () => {
             const validSurvey = validateWithAnswerVerification(surveyModel.validatePage.bind(surveyModel));
@@ -237,15 +255,11 @@ function SurveyContainerComponent({ loadFrom }) {
         }
     }
 
-    if (!surveyModel.css.question.title.includes("sv-header-flex")) {
-        surveyModel.css.question.title = "sv-title sv-question__title sv-header-flex";
-        surveyModel.css.question.titleOnError = "sv-question__title--error sv-error-color-fix";
-    }
-
     const onPageChange = (page) => {
         if (!surveyModel) return;
-        surveyModel.currentPageNo = page;
-        setSurveyModel(Object.create(surveyModel));
+        const newModel = modifyModel(surveyModel);
+        newModel.currentPageNo = page;
+        setSurveyModel(newModel);
     }
 
     return (
diff --git a/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx b/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx
index f31ef549..fa56f448 100644
--- a/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx
+++ b/compendium-frontend/src/survey/management/SurveyManagementComponent.tsx
@@ -168,11 +168,19 @@ function SurveyManagementComponent() {
                                                     helpText="Validate all survey responses. This will check if all required questions are answered and if the answers are in the correct format."
                                                     enabled={survey.status == SurveyStatus.preview || survey.status == SurveyStatus.published}
                                                     onClick={async () => {
+                                                        let allValid = true;
                                                         const responses = [...survey.responses].sort((a, b) => a.nren.name.localeCompare(b.nren.name));
-                                                        const values = await Promise.all(responses.map(response => validateSurvey(survey.year, response.nren.name)));
-                                                        const valid = values.every(value => value);
+                                                        for (const response of responses) {
+                                                            const valid = await validateSurvey(survey.year, response.nren.name);
+                                                            if (!valid) {
+                                                                toast.error(`${response.nren.name} ${survey.year} survey is not valid.`);
+                                                            } else {
+                                                                toast.success(`${response.nren.name} ${survey.year} survey is valid.`);
+                                                            }
+                                                            allValid = allValid && valid;   
+                                                        }
 
-                                                        if (!valid) {
+                                                        if (!allValid) {
                                                             toast.error("Some surveys are not valid.\nPlease check the responses")
                                                         } else {
                                                             toast.success("All surveys are valid")
-- 
GitLab