Skip to content
Snippets Groups Projects
Select Git revision
  • 13b459ac752092c37f54d9df83e7fdb407894a56
  • develop default
  • master protected
  • feature/frontend-tests
  • 0.106
  • 0.105
  • 0.104
  • 0.103
  • 0.102
  • 0.101
  • 0.100
  • 0.99
  • 0.98
  • 0.97
  • 0.96
  • 0.95
  • 0.94
  • 0.93
  • 0.92
  • 0.91
  • 0.90
  • 0.89
  • 0.88
  • 0.87
24 results

SurveyContainerComponent.tsx

Blame
  • SurveyContainerComponent.tsx 8.35 KiB
    import React, { useEffect, useRef, useState, useCallback } from "react";
    import { Container } from "react-bootstrap";
    import toast, { Toaster } from "react-hot-toast";
    import { Model, Serializer } from "survey-core";
    import { useParams } from "react-router-dom";
    import SurveyComponent from "./SurveyComponent";
    import SurveyNavigationComponent from "./SurveyNavigationComponent";
    import { VerificationStatus } from './Schema';
    import "survey-core/modern.min.css";
    import './survey.scss';
    
    Serializer.addProperty("itemvalue", "customDescription:text");
    Serializer.addProperty("question", "hideCheckboxLabels:boolean");
    
    
    function SurveyContainerComponent({ loadFrom }) {
        // NB This component should only rerender after the model is retrieved.
        //    If you change this to rerender more often, stuff will probably break.
    
        const [surveyModel, setSurveyModel] = useState<Model>();
        const verificationStatus = useRef<Map<string, VerificationStatus>>(new Map());
        const { year, nren } = useParams();  // year is always set, nren stays empty for inspect and try
        const [error, setError] = useState<string>('loading survey...');
        const lockUUID = useRef<string>();
    
        const beforeUnloadListener = useCallback((event) => {
            event.preventDefault();
            return (event.returnValue = "");
        }, []);
    
        const pageHideListener = useCallback(() => {
            if (!nren) {
                return;
            }
            window.navigator.sendBeacon('/api/response/unlock/' + year + '/' + nren);
        }, []);
    
        useEffect(() => {
            async function getModel() {
                const response = await fetch(loadFrom + year + (nren ? '/' + nren : ''))
                const json = await response.json();
    
                if (!response.ok) {
    
                    if ('message' in json) {
                        throw new Error(json.message);
                    } else {
                        throw new Error(`Request failed with status ${response.status}`);
                    }
                }
    
    
                for (const questionName in json["verification_status"]) {
                    verificationStatus.current.set(questionName, json["verification_status"][questionName]);
                }
    
                const survey = new Model(json['model']);
                survey.setVariable('surveyyear', year);
                survey.setVariable('previousyear', parseInt(year!) - 1);
    
                survey.data = json['data'];
                survey.clearIncorrectValues(true);  // TODO test if this really removes all old values and such
                survey.currentPageNo = json['page'];
                survey.showNavigationButtons = false;
                survey.showTOC = false;
                survey.mode = json['mode'];
                survey.lockedBy = json['locked_by'];
                survey.status = json['status'];
                survey.editAllowed = json['edit_allowed'];
    
                setSurveyModel(survey);
            }
    
            getModel().catch(error => setError('Error when loading survey: ' + error.message))
    
        }, []);
    
        if (!surveyModel) {
            return error
        }
    
        const saveSurveyData = async (survey, newState) => {
            if (!nren) {
                return "Saving not available in inpect/try mode";
            }
    
            const saveData = {
                lock_uuid: lockUUID.current,
                new_state: newState,
                data: survey.data,
                page: survey.currentPageNo,
                verification_status: Object.fromEntries(verificationStatus.current)
            };
    
            try {
                const response = await fetch(
                    '/api/response/save/' + year + '/' + nren,
                    {method: "POST", headers: { "Content-Type": "application/json; charset=utf-8"}, body: JSON.stringify(saveData)}
                );
                const json = await response.json();
                if (!response.ok) {
                    return json['message'];
                }
                surveyModel.mode = json['mode'];
                surveyModel.lockedBy = json['locked_by'];
                surveyModel.status = json['status'];
            } catch (e) {
                return "Unknown Error: " + (e as Error).message;
            }
        }
    
        const validateWithAnswerVerification = (validatorFunction) => {
            let firstValidationError = '';
            const verificationValidator = (survey, options) => {
                const status = verificationStatus.current.get(options.name);
                if (status == VerificationStatus.Unverified) {
                    if (firstValidationError == '') {
                        firstValidationError = options.name;
                    }
                    options.error = 'Please verify that last years data is correct by editing the answer or pressing the "No change from previous year" button!';
                }
            };
            surveyModel.onValidateQuestion.add(verificationValidator);
            const validSurvey = validatorFunction();
            surveyModel.onValidateQuestion.remove(verificationValidator);
            if (!validSurvey) {
                // perhaps first focus normal validation errors and only afterwards the verification validation errors?
                toast("Validation failed!");
                surveyModel.focusQuestion(firstValidationError);
            }
            return validSurvey;
        }
    
        const surveyActions = {
            'save': async () => {
                const errorMessage = await saveSurveyData(surveyModel, "editing");
                if (errorMessage) {
                    toast("Failed saving survey: " + errorMessage);
                } else {
                    toast("Survey saved!");
                }
            },
            'complete': async () => {
                const validSurvey = validateWithAnswerVerification(surveyModel.validate.bind(surveyModel, true, true));
                if (validSurvey) {
                    const errorMessage = await saveSurveyData(surveyModel, "completed");
                    if (errorMessage) {
                        toast("Failed completing survey: " + errorMessage);
                    } else {
                        toast("Survey completed!");
                        removeEventListener("beforeunload", beforeUnloadListener, { capture: true });
                        removeEventListener("pagehide", pageHideListener);
                    }
                }
            },
            'saveAndStopEdit': async () => {
                const errorMessage = await saveSurveyData(surveyModel, "readonly");
                if (errorMessage) {
                    toast("Failed saving survey: " + errorMessage);
                } else {
                    toast("Survey saved!");
                    removeEventListener("beforeunload", beforeUnloadListener, { capture: true });
                    removeEventListener("pagehide", pageHideListener);
                }
            },
            'startEdit': async () => {
                const response = await fetch('/api/response/lock/' + year + '/' + nren, {method: "POST"});
                const json = await response.json();
                if (!response.ok) {
                    toast("Failed starting edit: " + json['message']);
                    return;
                }
                addEventListener("pagehide", pageHideListener);
                addEventListener("beforeunload", beforeUnloadListener, { capture: true });
                surveyModel.data = json['data'];
                surveyModel.clearIncorrectValues(true);
                surveyModel.mode = json['mode'];
                surveyModel.lockedBy = json['locked_by']
                surveyModel.status = json['status'];
                lockUUID.current = json['lock_uuid'];
            },
            'releaseLock': async () => {
                const response = await fetch('/api/response/unlock/' + year + '/' + nren, { method: 'POST' });
                const json = await response.json();
                if (!response.ok) {
                    toast("Failed releasing lock: " + json['message']);
                    return;
                }
                surveyModel.mode = json['mode'];
                surveyModel.lockedBy = json['locked_by'];
                surveyModel.status = json['status'];
            },
            'validatePage': () => {
                const validSurvey = validateWithAnswerVerification(surveyModel.validatePage.bind(surveyModel));
                if (validSurvey) {
                    toast("Page validation successful!");
                }
            }
        }
    
        return (
            <Container className="survey-container">
                <Toaster />
                <SurveyNavigationComponent surveyModel={surveyModel} surveyActions={surveyActions} year={year} nren={nren}>
                    <SurveyComponent surveyModel={surveyModel} verificationStatus={verificationStatus} />
                </SurveyNavigationComponent>
            </Container>
        );
    }
    
    export default SurveyContainerComponent;