Skip to content
Snippets Groups Projects
Commit dbbe439f authored by Bjarke Madsen's avatar Bjarke Madsen
Browse files

Merge branch 'feature/refactor_survey_frontend' into 'develop'

Feature/refactor survey frontend

See merge request !54
parents 83969d6f 43071ac8
Branches
Tags
1 merge request!54Feature/refactor survey frontend
......@@ -3,6 +3,7 @@ from enum import Enum
from typing import Any, TypedDict, List, Dict
from flask import Blueprint, jsonify, request
from flask_login import login_required
from sqlalchemy import select
from sqlalchemy.orm import joinedload, load_only
......@@ -10,6 +11,7 @@ from compendium_v2.db import db
from compendium_v2.db.model import NREN
from compendium_v2.db.survey_model import Survey, SurveyResponse, SurveyStatus, ResponseStatus
from compendium_v2.routes import common
from compendium_v2.auth.session_management import admin_required
routes = Blueprint('survey', __name__)
......@@ -66,9 +68,9 @@ class VerificationStatus(str, Enum):
Edited = "edited" # a question for which last years answer was edited
# TODO admin only
@routes.route('/list', methods=['GET'])
@common.require_accepts_json
@admin_required
def list_surveys() -> Any:
"""
retrieve a list of surveys and responses, including their status
......@@ -119,9 +121,9 @@ def list_surveys() -> Any:
return jsonify(entries)
# TODO admin only
@routes.route('/new', methods=['POST'])
@common.require_accepts_json
@admin_required
def start_new_survey() -> Any:
"""
endpoint to initiate a new survey
......@@ -148,9 +150,9 @@ def start_new_survey() -> Any:
return {'success': True}
# TODO admin only
@routes.route('/open/<int:year>', methods=['POST'])
@common.require_accepts_json
@admin_required
def open_survey(year) -> Any:
"""
endpoint to open a survey to the nrens
......@@ -174,9 +176,9 @@ def open_survey(year) -> Any:
return {'success': True}
# TODO admin only
@routes.route('/close/<int:year>', methods=['POST'])
@common.require_accepts_json
@admin_required
def close_survey(year) -> Any:
"""
endpoint to close a survey to the nrens
......@@ -196,9 +198,9 @@ def close_survey(year) -> Any:
return {'success': True}
# TODO admin only
@routes.route('/publish/<int:year>', methods=['POST'])
@common.require_accepts_json
@admin_required
def publish_survey(year) -> Any:
"""
endpoint to publish a survey to the compendium website
......@@ -223,9 +225,9 @@ def publish_survey(year) -> Any:
return {'success': True}
# TODO admin only
@routes.route('/try/<int:year>', methods=['GET'])
@common.require_accepts_json
@admin_required
def try_survey(year) -> Any:
"""
Get a survey without any associated nren for trying out the survey.
......@@ -249,9 +251,9 @@ def try_survey(year) -> Any:
})
# TODO admin only
@routes.route('/inspect/<int:year>', methods=['GET'])
@common.require_accepts_json
@admin_required
def inspect_survey(year) -> Any:
"""
Get a survey without any associated nren for inspecting all questions.
......@@ -289,6 +291,7 @@ def inspect_survey(year) -> Any:
@routes.route('/load/<int:year>/<string:nren_name>', methods=['GET'])
@common.require_accepts_json
@login_required
def load_survey(year, nren_name) -> Any:
"""
Get a survey for an nren.
......@@ -343,6 +346,7 @@ def load_survey(year, nren_name) -> Any:
@routes.route('/save/<int:year>/<string:nren_name>', methods=['POST'])
@common.require_accepts_json
@login_required
def save_survey(year, nren_name) -> Any:
"""
endpoint to save a survey response
......
This diff is collapsed.
This diff is collapsed.
......@@ -4,7 +4,7 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import ShowUser from './ShowUser';
import SurveyManagementComponent from './SurveyManagementComponent';
import UserManagementComponent from './UserManagementComponent';
import SurveyComponent from "./SurveyComponent";
import SurveyContainerComponent from "./SurveyContainerComponent";
function App(): ReactElement {
......@@ -14,10 +14,10 @@ function App(): ReactElement {
<Routes>
<Route path="survey/admin/surveys" element={<SurveyManagementComponent />} />
<Route path="survey/admin/users" element={<UserManagementComponent />} />
<Route path="survey/admin/inspect/:year" element={<SurveyComponent loadFrom={'/api/survey/inspect/'} />}/>
<Route path="survey/admin/try/:year" element={<SurveyComponent loadFrom={'/api/survey/try/'} />}/>
<Route path="survey/respond/:year/:nren" element={<SurveyComponent loadFrom={'/api/survey/load/'} saveTo={'/api/survey/save/'} />}/>
<Route path="survey/show/:year/:nren" element={<SurveyComponent loadFrom={'/api/survey/load/'} readonly />}/>
<Route path="survey/admin/inspect/:year" element={<SurveyContainerComponent loadFrom={'/api/survey/inspect/'} />}/>
<Route path="survey/admin/try/:year" element={<SurveyContainerComponent loadFrom={'/api/survey/try/'} />}/>
<Route path="survey/respond/:year/:nren" element={<SurveyContainerComponent loadFrom={'/api/survey/load/'} saveTo={'/api/survey/save/'} />}/>
<Route path="survey/show/:year/:nren" element={<SurveyContainerComponent loadFrom={'/api/survey/load/'} readonly />}/>
<Route path="*" element={<ShowUser />} />
</Routes>
</Router>
......
import React from "react";
function ProgressBar({
completionPercentage,
unansweredPercentage,
pages,
pageTitle,
}) {
const progressBarContainerStyle: React.CSSProperties = {
display: "flex",
flexWrap: "wrap",
height: "10px",
margin: "5px",
width: `${100 / pages}%`,
};
const progressBarFillStyle: React.CSSProperties = {
height: "100%",
transition: "width 0.3s ease",
};
const progressBarFillStyleCopy: React.CSSProperties = {
...progressBarFillStyle,
width: `${completionPercentage}%`,
backgroundColor: "#1ab394",
};
const unansweredProgressBarFillStyle: React.CSSProperties = {
...progressBarFillStyle,
width: `${unansweredPercentage}%`,
backgroundColor: "#9d9d9d",
};
const pageTitleStyle: React.CSSProperties ={
width: "100%",
textAlign: "center"
}
return (
<div style={progressBarContainerStyle}>
<div style={progressBarFillStyleCopy} />
<div style={unansweredProgressBarFillStyle} />
<div style={pageTitleStyle}>{pageTitle}</div>
</div>
);
import React, { useEffect, useState } from "react";
interface Progress {
completionPercentage: number;
unansweredPercentage: number;
totalPages: number;
pageTitle: string;
}
function ProgressBar({ surveyModel, pageNoSetter }) {
const [progress, setProgress] = useState<Progress[]>([]);
const filterCallback = (question) => {
return question.value !== null && question.value !== undefined;
};
const calculateProgress = (survey) => {
if (survey && survey.pages) {
const progressArray: Progress[] = [];
survey.pages.forEach((page) => {
const sectionQuestions = page.questions.filter(
(question) => question.startWithNewLine
);
const questionCount = sectionQuestions.length;
const answeredCount = sectionQuestions.filter(filterCallback).length;
const unansweredCount = questionCount - answeredCount;
const completionPercentage = answeredCount / questionCount;
progressArray.push({
completionPercentage: completionPercentage * 100,
unansweredPercentage: (unansweredCount / questionCount) * 100,
totalPages: survey.pages.length,
pageTitle: page.title,
});
});
setProgress(progressArray);
}
};
useEffect(() => {
surveyModel.onValueChanged.add((sender) => {
calculateProgress(sender);
});
calculateProgress(surveyModel);
}, [surveyModel]);
function progressBarContainerStyle(sectionProgress): React.CSSProperties {
return {
display: "flex",
flexWrap: "wrap",
height: "10px",
margin: "5px",
width: `${100 / sectionProgress.totalPages}%`,
}
}
const progressBarFillStyle: React.CSSProperties = {
height: "100%",
transition: "width 0.3s ease",
};
function progressBarFillStyleCopy(sectionProgress): React.CSSProperties {
return {
...progressBarFillStyle,
width: `${sectionProgress.completionPercentage}%`,
backgroundColor: "#1ab394",
}
}
function unansweredProgressBarFillStyle(sectionProgress): React.CSSProperties {
return {
...progressBarFillStyle,
width: `${sectionProgress.unansweredPercentage}%`,
backgroundColor: "#9d9d9d",
}
}
function pageTitleStyle(index): React.CSSProperties {
if (surveyModel.currentPageNo == index) {
return {
width: "100%",
textAlign: "center",
fontWeight: "bold"
}
} else {
return {
width: "100%",
textAlign: "center"
}
}
}
return (
<div className="survey-progress">
{progress.map((sectionProgress, index) => (
<div key={index} style={progressBarContainerStyle(sectionProgress)} onClick={() => pageNoSetter(index)}>
<div style={progressBarFillStyleCopy(sectionProgress)} />
<div style={unansweredProgressBarFillStyle(sectionProgress)} />
<div style={pageTitleStyle(index)}>{sectionProgress.pageTitle}</div>
</div>
))}
</div>
);
}
export default ProgressBar;
export enum VerificationStatus {
New = "new", // a question that was not answered last year
Answered = "answered", // a question that was not answered last year but has an answer now
Unverified = "unverified", // a question that has its answered copied from last year
Verified = "verified", // a question for which last years answer was verified
Edited = "edited" // a question for which last years answer was edited
}
import React, { useState, useEffect, useRef } from "react";
import { Model, Serializer, ComputedUpdater, Question, FunctionFactory } from "survey-core";
import React, { useEffect } from "react";
import { Question, FunctionFactory } from "survey-core";
import { Survey } from "survey-react-ui";
import "survey-core/modern.min.css";
import './survey.scss';
import ProgressBar from "./ProgressBar";
import { useParams } from "react-router-dom";
import { VerificationStatus } from './Schema';
Serializer.addProperty("itemvalue", "customDescription:text");
Serializer.addProperty("question", "hideCheckboxLabels:boolean");
function SurveyComponent({ surveyModel, verificationStatus }) {
interface Progress {
completionPercentage: number;
unansweredPercentage: number;
totalPages: number;
pageTitle: string;
}
enum VerificationStatus {
New = "new", // a question that was not answered last year
Answered = "answered", // a question that was not answered last year but has an answer now
Unverified = "unverified", // a question that has its answered copied from last year
Verified = "verified", // a question for which last years answer was verified
Edited = "edited" // a question for which last years answer was edited
}
function SurveyComponent({ loadFrom, saveTo = '', readonly = false}) {
const [surveyModel, setSurveyModel] = useState<Model>();
const [progress, setProgress] = useState<Progress[]>([]);
const verificationStatus = useRef<Map<string, VerificationStatus>>(new Map());
const { year, nren } = useParams();
function setVerifyButton(question: Question, state: VerificationStatus) {
verificationStatus.current.set(question.name, state);
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sv-action-bar-item verification";
btn.innerHTML = state;
if (state == VerificationStatus.Unverified) {
btn.innerHTML = "Verify last years data";
btn.className += " verification-required";
btn.onclick = function () {
question.validate();
setVerifyButton(question, VerificationStatus.Verified)
}
} else {
btn.className += " verification-ok";
}
const selector = '[data-name="' + question.name + '"]';
const header = document.querySelector(selector)?.querySelector('h5');
const old = header?.querySelector(".verification");
if (old) {
old.replaceWith(btn);
} else {
header?.appendChild(btn);
}
}
// const surveyComplete = useCallback((sender) => {
// console.log(sender.data);
// }, []);
function saveSurveyData (survey, success?, failure?) {
if (saveTo == '') {
return;
}
const xhr = new XMLHttpRequest();
xhr.open("POST", saveTo + year + '/' + nren);
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xhr.onload = xhr.onerror = () => {
if (xhr.status == 200 && success) {
success();
} else if (xhr.status != 200 && failure) {
failure();
}
}
const saveData = {
data: survey.data,
page: survey.currentPageNo,
verification_status: Object.fromEntries(verificationStatus.current)
}
xhr.send(JSON.stringify(saveData));
}
async function getModel() {
const response = await fetch(loadFrom + year + (nren ? '/' + nren : '')) // year is always set, nren stays empty for inspect and try
const json = await response.json();
for (const questionName in json["verification_status"]) {
verificationStatus.current.set(questionName, json["verification_status"][questionName]);
}
const survey = new Model(json['model']);
if (readonly) {
survey.mode = 'display';
}
function setVerifyButton(question: Question, state: VerificationStatus) {
function validateWebsiteUrl (params) {
const value = params[0];
if (value === undefined || value == null || value == '') {
return true;
}
try {
const url = new URL(value);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (err) {
return false;
}
}
FunctionFactory.Instance.register("validateWebsiteUrl", validateWebsiteUrl);
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
verificationStatus.current.set(question.name, state);
survey.currentPageNo = json['page'];
const btn = document.createElement("button");
btn.type = "button";
btn.className = "sv-action-bar-item verification";
btn.innerHTML = state;
survey.addNavigationItem({
id: "sv-nav-compendium-complete",
title: "Complete",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error: visible may be a ComputedUpdater but the types are not (yet?) aware of this
visible: new ComputedUpdater(() => survey.isLastPage),
action: () => {
let firstValidationError = '';
const verificationValidator = (survey, options) => {
const status = verificationStatus.current.get(options.name);
if (status == VerificationStatus.Unverified) {
if (firstValidationError == '') {
firstValidationError = options.name;
if (state == VerificationStatus.Unverified) {
btn.innerHTML = "Verify last years data";
btn.className += " verification-required";
btn.onclick = function () {
question.validate();
setVerifyButton(question, VerificationStatus.Verified)
}
options.error = 'Please verify that last years data is correct by editing the value or pressing the verification button!';
}
};
survey.onValidateQuestion.add(verificationValidator);
const validSurvey = survey.validate();
survey.onValidateQuestion.remove(verificationValidator);
if (validSurvey) {
survey.completeLastPage(); // continue with usual completion process
} else {
survey.focusQuestion(firstValidationError);
btn.className += " verification-ok";
}
},
innerCss: "sv-btn sv-btn--navigation sv-footer__complete-btn"
});
survey.addNavigationItem({
id: "sv-nav-compendium-save",
title: "Save",
action: (context) => {
console.log(context);
saveSurveyData(survey);
// notification doesnt show up in the right place, maybe fix with CSS. Also see settings.notifications.lifetime if you want to fix this
// but probably easier/better to just use react popup everywhere instead
// survey.notify('Saved!', "success");
},
innerCss: "sv-btn sv-btn--navigation sv-footer__complete-btn"
});
survey.onComplete.add((sender, options) => {
options.showSaveInProgress();
saveSurveyData(sender, () => options.showSaveSuccess(), () => options.showSaveError());
});
survey.onAfterRenderQuestion.add(function (survey, options) {
const status = verificationStatus.current.get(options.question.name);
if (status) {
setVerifyButton(options.question, status);
}
});
survey.onValueChanged.add(function (survey, options) {
const currentStatus = verificationStatus.current.get(options.question.name);
if (currentStatus == VerificationStatus.New) {
setVerifyButton(options.question, VerificationStatus.Answered);
} else if (currentStatus == VerificationStatus.Unverified) {
setVerifyButton(options.question, VerificationStatus.Edited);
}
});
survey.onUpdateQuestionCssClasses.add(function (_, options) {
if (options.question.hideCheckboxLabels) {
const classes = options.cssClasses;
classes.root += " hidden-checkbox-labels";
}
});
survey.onMatrixAfterCellRender.add((survey, options) => {
// get the customDescription for matrix rows and set it in the title
// attribute so that it shows up as a hover popup
// NB I would have preferred using onAfterRenderQuestion, but unfortunately that is
// not always triggered on re-renders (specifically when extra column become visble or invisible)
const selector = '[data-name="' + question.name + '"]';
const header = document.querySelector(selector)?.querySelector('h5');
const old = header?.querySelector(".verification");
if (old) {
old.replaceWith(btn);
} else {
header?.appendChild(btn);
}
}
if (options.column['indexValue'] == 0 && 'item' in options.row) {
const item = options.row['item'] as object;
if (item['customDescription'] !== undefined) {
options.htmlElement.parentElement?.children[0].setAttribute(
"title",
item['customDescription']
);
function validateWebsiteUrl(params) {
const value = params[0];
if (value === undefined || value == null || value == '') {
return true;
}
}
});
try {
const url = new URL(value);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (err) {
return false;
}
}
survey.onCurrentPageChanged.add((sender) => {
console.log("sender--> " + sender);
calculateProgress(sender);
});
useEffect(() => {
setSurveyModel(survey);
}
const filterCallback = (question) => {
return question.value !== null && question.value !== undefined;
};
FunctionFactory.Instance.register("validateWebsiteUrl", validateWebsiteUrl);
const calculateProgress = (survey) => {
// console.log("survey--> "+ survey);
if (survey && survey.pages) {
console.log("survey.page--> " + survey.pages);
const progressArray: Progress[] = [];
survey.pages.forEach((page) => {
const sectionQuestions = page.questions.filter(
(question) => question.startWithNewLine
);
const questionCount = sectionQuestions.length;
const answeredCount = sectionQuestions.filter(filterCallback).length;
const unansweredCount = questionCount - answeredCount;
const completionPercentage = answeredCount / questionCount;
surveyModel.onAfterRenderQuestion.add(function (survey, options) {
const status = verificationStatus.current.get(options.question.name);
if (status) {
setVerifyButton(options.question, status);
}
});
surveyModel.onValueChanged.add(function (survey, options) {
const currentStatus = verificationStatus.current.get(options.question.name);
if (currentStatus == VerificationStatus.New) {
setVerifyButton(options.question, VerificationStatus.Answered);
} else if (currentStatus == VerificationStatus.Unverified) {
setVerifyButton(options.question, VerificationStatus.Edited);
}
});
progressArray.push({
completionPercentage: completionPercentage * 100,
unansweredPercentage: (unansweredCount / questionCount) * 100,
totalPages: survey.pages.length,
pageTitle: page.title,
surveyModel.onUpdateQuestionCssClasses.add(function (_, options) {
if (options.question.hideCheckboxLabels) {
const classes = options.cssClasses;
classes.root += " hidden-checkbox-labels";
}
});
});
setProgress(progressArray);
}
};
useEffect(() => {
getModel();
}, []);
surveyModel.onMatrixAfterCellRender.add((survey, options) => {
// get the customDescription for matrix rows and set it in the title
// attribute so that it shows up as a hover popup
// NB I would have preferred using onAfterRenderQuestion, but unfortunately that is
// not always triggered on re-renders (specifically when extra column become visble or invisible)
if (options.column['indexValue'] == 0 && 'item' in options.row) {
const item = options.row['item'] as object;
if (item['customDescription'] !== undefined) {
options.htmlElement.parentElement?.children[0].setAttribute("title", item['customDescription']);
}
}
});
useEffect(() => {
if (surveyModel) {
calculateProgress(surveyModel);
}
}, [surveyModel]);
}, [surveyModel]);
if (surveyModel) {
return (
<div className="survey-container">
<div className="survey-progress">
{progress.map((sectionProgress, index) => (
<ProgressBar
key={index}
completionPercentage={sectionProgress.completionPercentage}
unansweredPercentage={sectionProgress.unansweredPercentage}
pages={sectionProgress.totalPages}
pageTitle={sectionProgress.pageTitle}
/>
))}
<div className="survey-container">
<Survey model={surveyModel} />
</div>
<Survey model={surveyModel} />
</div>
);
} else {
return <span>loading...</span>;
}
}
export default SurveyComponent;
import React, { useEffect, useRef, useState } from "react";
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, saveTo = '', readonly = false }) {
const [surveyModel, setSurveyModel] = useState<Model>();
const verificationStatus = useRef<Map<string, VerificationStatus>>(new Map());
const { year, nren } = useParams();
useEffect(() => {
getModel();
}, []);
if (surveyModel === undefined) {
return 'loading...'
}
async function getModel() {
const response = await fetch(loadFrom + year + (nren ? '/' + nren : '')) // year is always set, nren stays empty for inspect and try
const json = await response.json();
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;
if (readonly) {
survey.mode = 'display';
}
setSurveyModel(survey);
}
function saveSurveyData(survey, success?, failure?) {
if (saveTo == '') {
return;
}
const xhr = new XMLHttpRequest();
xhr.open("POST", saveTo + year + '/' + nren);
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xhr.onload = xhr.onerror = () => {
if (xhr.status == 200 && success) {
success();
} else if (xhr.status != 200 && failure) {
failure();
}
}
const saveData = {
data: survey.data,
page: survey.currentPageNo,
verification_status: Object.fromEntries(verificationStatus.current)
}
xhr.send(JSON.stringify(saveData));
}
const saveSurvey = () => { saveSurveyData(surveyModel); };
const endSurvey = () => {
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 value or pressing the verification button!';
}
};
surveyModel.onValidateQuestion.add(verificationValidator);
const validSurvey = surveyModel.validate();
surveyModel.onValidateQuestion.remove(verificationValidator);
if (validSurvey) {
// TODO replace the following with our own notifications:
surveyModel.options.showSaveInProgress();
saveSurveyData(surveyModel, () => surveyModel.showSaveSuccess(), () => surveyModel.showSaveError());
} else {
surveyModel.focusQuestion(firstValidationError);
}
};
const validatePage = () => {
// TODO remove duplication with funtion above and perhaps also first focus normal validation errors and only afterwards the verification validation errors
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 value or pressing the verification button!';
}
};
surveyModel.onValidateQuestion.add(verificationValidator);
const validSurvey = surveyModel.validateCurrentPage();
surveyModel.onValidateQuestion.remove(verificationValidator);
if (validSurvey) {
// TODO some notification
} else {
surveyModel.focusQuestion(firstValidationError);
}
}
return (
<div>
<SurveyNavigationComponent surveyModel={surveyModel} endSurvey={endSurvey} saveSurvey={saveSurvey} validatePage={validatePage}>
<SurveyComponent surveyModel={surveyModel} verificationStatus={verificationStatus} />
</SurveyNavigationComponent>
</div>
);
}
export default SurveyContainerComponent;
import React, { useEffect, useState } from "react";
import ProgressBar from './ProgressBar';
function SurveyNavigationComponent({ surveyModel, endSurvey, saveSurvey, validatePage, children }) {
const [pageNo, setPageNo] = useState(0);
useEffect(() => {
setPageNo(surveyModel.currentPageNo)
}, [surveyModel]);
const pageNoSetter = (page) => {
setPageNo(page);
surveyModel.currentPageNo = page;
}
const decrementPageNo = () => { pageNoSetter(surveyModel.currentPageNo - 1); };
const incrementPageNo = () => { pageNoSetter(surveyModel.currentPageNo + 1); };
const renderButton = (text, func) => {
// TODO add sv-footer__complete-btn for complete button, and save button perhaps too?
return (
<button className="sv-btn sv-btn--navigation" onClick={func}>
{text}
</button>
);
};
const renderExternalNavigation = () => {
return (
<div className="navigation-block">
<div className="navigation-progress-container">
<div className="navigation-buttons-container">
{(pageNo === surveyModel.visiblePages.length - 1) && renderButton('Complete', endSurvey)}
{renderButton('Save', saveSurvey)}
{(pageNo !== surveyModel.visiblePages.length - 1) && renderButton('Next Page', incrementPageNo)}
{renderButton('Validate Page', validatePage)}
{pageNo !== 0 && renderButton('Previous Page', decrementPageNo)}
</div>
</div>
</div>
);
};
return (
<div>
<ProgressBar surveyModel={surveyModel} pageNoSetter={pageNoSetter} />
{renderExternalNavigation()}
{children}
{renderExternalNavigation()}
</div>
);
}
export default SurveyNavigationComponent;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment