Skip to content
Snippets Groups Projects
Select Git revision
  • 51eaad76f960be929aca3fb03382acdfba77f3d5
  • develop default
  • master protected
  • feature/frontend-tests
  • 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
  • 0.86
  • 0.85
  • 0.84
  • 0.83
  • 0.82
  • 0.81
  • 0.80
24 results

SurveyContainerComponent.tsx

Blame
  • Bjarke Madsen's avatar
    Bjarke Madsen authored
    Uses code snippets partly from matomo-tracker-react since it's no longer maintained
    51eaad76
    History
    SurveyContainerComponent.tsx 8.82 KiB
    import React, { useEffect, 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 Prompt from "./Prompt";
    import "survey-core/modern.min.css";
    import './survey.scss';
    import useMatomo from "../matomo/UseMatomo";
    
    Serializer.addProperty("itemvalue", "customDescription:text");
    Serializer.addProperty("question", "hideCheckboxLabels:boolean");
    
    
    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
        const [error, setError] = useState<string>('loading survey...');
    
        const { trackPageView } = useMatomo();
    
        const beforeUnloadListener = useCallback((event) => {
            event.preventDefault();
            return (event.returnValue = "");
        }, []);
    
        const pageHideListener = useCallback(() => {
            window.navigator.sendBeacon('/api/response/unlock/' + year + '/' + nren);
        }, []);
    
        const onPageExitThroughRouter = useCallback(() => {
            window.navigator.sendBeacon('/api/response/unlock/' + year + '/' + nren);
            removeEventListener("beforeunload", beforeUnloadListener, { capture: true });
            removeEventListener("pagehide", pageHideListener);
        }, []);
    
        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}`);
                    }
                }
    
                const survey = new Model(json['model']);
                survey.setVariable('surveyyear', year);
                survey.setVariable('previousyear', parseInt(year!) - 1);
    
                survey.showNavigationButtons = false;
                survey.requiredText = '';
    
                survey.verificationStatus = new Map<string, VerificationStatus>();
                for (const questionName in json["verification_status"]) {
                    survey.verificationStatus.set(questionName, json["verification_status"][questionName]);
                }
    
                survey.data = json['data'];
                survey.clearIncorrectValues(true);
                survey.currentPageNo = json['page'];
                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)).then(() => {
                trackPageView({ documentTitle: `Survey for ${nren} (${year})` });
            })
        }, []);
    
        if (!surveyModel) {
            return <>{error}</>
        }
    
        const saveSurveyData = async (survey, newState) => {
            if (!nren) {
                return "Saving not available in inpect/try mode";
            }
    
            const saveData = {
                lock_uuid: survey.lockUUID,
                new_state: newState,
                data: survey.data,
                page: survey.currentPageNo,
                verification_status: Object.fromEntries(survey.verificationStatus)
            };
    
            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 = survey.verificationStatus.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) {
                toast("Validation failed!");
            }
            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 });
                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'];
            },
            '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 />
                <Prompt message="Are you sure you want to leave this page? Information you've entered may not be saved." when={() => { return surveyModel.mode == 'edit' && !!nren; }} onPageExit={onPageExitThroughRouter} />
                <SurveyNavigationComponent surveyModel={surveyModel} surveyActions={surveyActions} year={year} nren={nren}>
                    <SurveyComponent surveyModel={surveyModel} />
                </SurveyNavigationComponent>
            </Container>
        );
    }
    
    export default SurveyContainerComponent;