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