Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,29 @@
"d3": "^7.9.0",
"docx": "^9.1.1",
"gl-matrix": "^3.4.3",
"html-react-parser": "^5.1.18",
"mapbox-gl": "^3.8.0",
"html-react-parser": "^5.2.2",
"mapbox-gl": "^3.9.3",
"po2json": "^0.4.5",
"postcss": "^8.4.49",
"postcss": "^8.5.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-focus-lock": "^2.13.2",
"react-focus-lock": "^2.13.5",
"react-google-recaptcha-v3": "^1.10.1",
"react-icons": "^5.3.0",
"react-inlinesvg": "^4.1.5",
"react-map-gl": "^7.1.7",
"react-redux": "^9.1.2",
"react-router-dom": "^7.0.1",
"react-icons": "^5.4.0",
"react-inlinesvg": "^4.1.7",
"react-map-gl": "^7.1.8",
"react-redux": "^9.2.0",
"react-router-dom": "^7.1.1",
"react-scripts": "^5.0.1",
"recharts": "^2.13.3",
"recharts": "^2.15.0",
"redux": "^5.0.1",
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"reselect": "^5.1.1",
"sass": "^1.81.0",
"typescript": "^5.7.2",
"sass": "^1.83.4",
"typescript": "^5.7.3",
"web-vitals": "^4.2.4",
"whatwg-fetch": "^3.6.2",
"whatwg-fetch": "^3.6.20",
"yet-another-abortcontroller-polyfill": "^0.0.4"
},
"scripts": {
Expand Down Expand Up @@ -69,17 +69,18 @@
"fork-ts-checker-webpack-plugin": "^6.5.3"
},
"devDependencies": {
"@babel/eslint-parser": "^7.25.9",
"@babel/eslint-parser": "^7.26.5",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@testing-library/jest-dom": "^6.1.3",
"babel-eslint": "^10.1.0",
"babel-plugin-transform-remove-console": "^6.9.4",
"buffer": "^6.0.3",
"customize-cra": "^1.0.0",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react": "^7.37.4",
"husky": "^9.1.7",
"lint-staged": "^15.2.10",
"process": "^0.11.10",
"lint-staged": "^15.3.0",
"react-gettext-parser": "^1.16.0",
"stylelint": "^15.2.0",
"stylelint-config-standard": "^30.0.1",
Expand Down
15 changes: 15 additions & 0 deletions src/assets/images/update-confirm.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/components/ConfirmationModal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const ConfirmationModal = (props) => {
confirmButtonText,
cancelButtonText,
confirmButtonProps,
imageSrc=confirmImg,
...modalProps
} = props;

Expand All @@ -36,7 +37,7 @@ const ConfirmationModal = (props) => {
<div className={styles.contentMain}>
<img
className={styles.image}
src={confirmImg}
src={imageSrc}
alt={_('Confirm action')}
/>
<p className={styles.description}>
Expand Down
1 change: 0 additions & 1 deletion src/components/Inputs/Option/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const Option = ({option, checked, setChecked, multiple}) => {
</div>
</div>
</div>

);
};

Expand Down
14 changes: 7 additions & 7 deletions src/components/SurveyModuleReport/ReportOptionsDropdown/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ const ReportOptionsDropdown = ({moduleCode}) => {
</div>
)}
{/* TODO: Help action
<div className={styles.optionItem}>
<BsQuestionCircle size={18} className={styles.optionIcon} />
<span className={styles.optionText}>
<Localize>Help</Localize>
</span>
</div>
*/}
<div className={styles.optionItem}>
<BsQuestionCircle size={18} className={styles.optionIcon} />
<span className={styles.optionText}>
<Localize>Help</Localize>
</span>
</div>
*/}
</div>
</Dropdown>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
width: 20%;
}

&:nth-child(n):not(:first-child):not(:last-child) {
&:nth-child(n):not(:first-child, :last-child) {
width: 20%;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
text-align: left;
}

&:nth-child(n):not(:first-child):not(:last-child) {
&:nth-child(n):not(:first-child, :last-child) {
width: 8%;
text-align: right;
}
Expand Down
123 changes: 110 additions & 13 deletions src/containers/Surveys/Feedback/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {useCallback, useMemo, useEffect} from 'react';
import {useCallback, useMemo, useEffect, useState} from 'react';
import {useNavigate, useLocation, useParams} from 'react-router';
import {useSelector, useDispatch} from 'react-redux';
import SVG from 'react-inlinesvg';
Expand All @@ -14,8 +14,10 @@ import useInitActiveProject from 'hooks/useInitActiveProject';
import useInitActiveSurvey from 'hooks/useInitActiveSurvey';

import topicIconPlaceholder from 'assets/icons/topic-icon-placeholder.svg';
import updateConfirmImg from 'assets/images/update-confirm.svg';
import {setAdvancedFeedbacks} from 'store/actions/survey';

import cs from '@ra/cs';
import Api from 'services/api';
import usePromise from '@ra/hooks/usePromise';
import Toast from 'services/toast';
Expand All @@ -24,6 +26,8 @@ import {_} from 'services/i18n';

import FeedbackTopicTable from './FeedbackTable';
import styles from './styles.scss';
import CheckboxInput from 'vendor/react-arsenal/components/Form/CheckboxInput';
import ConfirmationModal from 'components/ConfirmationModal';

const keyExtractor = item => item.id;

Expand Down Expand Up @@ -70,9 +74,11 @@ const SurveyFeedback = () => {
const dispatch = useDispatch();
const {projectId, surveyId} = useParams();

const {user, isAuthenticated} = useSelector(state => state.auth);
const {topics} = useSelector(state => state.statement);
const {modules} = useSelector(state => state.context);
const {advancedFeedbacks} = useSelector(state => state.survey);
const {activeProject} = useSelector(state => state.project);
const {advancedFeedbacks, activeSurvey} = useSelector(state => state.survey);

const navigate = useNavigate();
const location = useLocation();
Expand All @@ -81,6 +87,16 @@ const SurveyFeedback = () => {

const [{loading}, submitFeedbacks] = usePromise(Api.postFeedback);
const [{loading: baselineLoading}, submitBaselineFeedbacks] = usePromise(Api.addBaselineFeedback);
const [{loading: updatingResults}, addSurveyResults] = usePromise(Api.addSurveyResults);

const [shouldUpdateSurveyResults, setShouldUpdateSurveyResults] = useState(false);
const handleChangeShouldUpdateResults = useCallback(({checked}) => {
setShouldUpdateSurveyResults(Boolean(checked));
}, []);

const [isOverwriteModalVisible, setIsOverwriteModalVisible] = useState(false);
const handleShowOverwriteModal = useCallback(() => setIsOverwriteModalVisible(true), []);
const handleHideOverwriteModal = useCallback(() => setIsOverwriteModalVisible(false), []);

useEffect(() => {
if(!location?.state?.moduleCode) {
Expand Down Expand Up @@ -126,10 +142,13 @@ const SurveyFeedback = () => {
}, [initializeFeedbackData, activeModule, surveyId, isBaselineFeedback, advancedFeedbacks]);

const handleSubmit = useCallback(async () => {
if(advancedFeedbacks.some(fdback => !fdback.expectedScore)) {
return Toast.show(_('Feedback with comments only are not valid. Please make sure that all changed rows have expected value filled!'), Toast.DANGER);
}
if(shouldUpdateSurveyResults) {
return handleShowOverwriteModal();
}
try {
if(advancedFeedbacks.some(fdback => !fdback.expectedScore)) {
return Toast.show(_('Feedback with comments only are not valid. Please make sure that all changed rows have expected value filled!'), Toast.DANGER);
}
if(isBaselineFeedback) {
await submitBaselineFeedbacks(advancedFeedbacks);
} else {
Expand All @@ -141,12 +160,56 @@ const SurveyFeedback = () => {
} catch(error) {
Toast.show(getErrorMessage(error) ?? _('An error occurred while submitting your feedbacks!'), Toast.DANGER);
}
}, [submitFeedbacks, advancedFeedbacks, dispatch, isBaselineFeedback, submitBaselineFeedbacks, navigate]);
}, [
submitFeedbacks,
advancedFeedbacks,
dispatch,
isBaselineFeedback,
submitBaselineFeedbacks,
shouldUpdateSurveyResults,
handleShowOverwriteModal,
navigate
]);

const handleUpdateResults = useCallback(async () => {
try {
const updatedSurveyResults = (advancedFeedbacks || []).map(feedback => {
const feedbackResult = activeSurvey?.results?.find(result => result.id === feedback.surveyResult);
return {
score: feedback.expectedScore,
statement: feedbackResult.statement,
module: feedbackResult.module
};
});
await submitFeedbacks(advancedFeedbacks);
await addSurveyResults(activeSurvey?.id, updatedSurveyResults);
Toast.show(_('Your feedback has been successfully submitted'), Toast.SUCCESS);
handleHideOverwriteModal();
dispatch(setAdvancedFeedbacks([]));
navigate('..');
} catch(error) {
Toast.show(getErrorMessage(error) ?? _('An error occurred while submitting your feedbacks!'), Toast.DANGER);
}
}, [advancedFeedbacks, submitFeedbacks, dispatch, navigate, activeSurvey, addSurveyResults, handleHideOverwriteModal]);

const renderTopicItem = useCallback(listProps => (
<TopicItem {...listProps} activeModule={activeModule} isBaselineFeedback={isBaselineFeedback} />
), [activeModule, isBaselineFeedback]);

const hasSurveyWritePermission = useMemo(() => {
if(!isAuthenticated) {
return false;
}
if(!user) {
return false;
}
return (
activeSurvey?.createdBy === user.username
|| activeProject?.isAdminOrOwner
|| ['owner', 'write'].includes(activeProject?.accessLevel)
);
}, [isAuthenticated, user, activeProject, activeSurvey]);

return (
<div className={styles.container}>
<div className={styles.header}>
Expand All @@ -172,20 +235,54 @@ const SurveyFeedback = () => {
: _('Advanced feedbacks are supposed to be provided by environmental experts and helps on improving the NEAT+ weightage system. The score of the statements ranges from 0 to 1 with 0 as the lowest impact and 1 as the highest impact. Current values are scores generated by the system and expected values are scores the user expected to see.')}
/>
</div>
<Button
onClick={handleSubmit}
loading={loading || baselineLoading}
disabled={advancedFeedbacks?.length===0}
>
<Localize>Submit</Localize>
</Button>
<div className={styles.controls}>
{!isBaselineFeedback && hasSurveyWritePermission && (
<div className={styles.updateResultsToggle}>
<CheckboxInput
id="shouldUpdateCheckbox"
checkboxClassName={cs(styles.checkbox, {
[styles.checkboxChecked]: shouldUpdateSurveyResults
})}
onChange={handleChangeShouldUpdateResults}
/>
<label htmlFor="shouldUpdateCheckbox" className={styles.checkboxLabel}>
<Localize>Update survey results?</Localize>
</label>
<InfoTooltip
icon={BsQuestionCircle}
iconClassName={styles.updateTooltip}
iconSize={16}
tooltipClassName={styles.updateTooltipContent}
message={_('If you choose to update survey results, the survey result scores will be updated based on your feedback, and the survey report will change accordingly.')}
/>
</div>
)}
<Button
onClick={handleSubmit}
loading={loading || baselineLoading}
disabled={advancedFeedbacks?.length===0}
>
<Localize>Submit</Localize>
</Button>
</div>
</div>
<List
loading={loadingFeedbacks}
keyExtractor={keyExtractor}
data={topics}
renderItem={renderTopicItem}
/>
{isOverwriteModalVisible && (
<ConfirmationModal
imageSrc={updateConfirmImg}
titleText={_('Overwrite survey results?')}
DescriptionComponent={<Localize>Your feedback submission will overwrite existing survey results. Do you want to proceed?</Localize>}
confirmButtonText={_('Overwrite')}
confirmButtonProps={{loading: updatingResults || loading || baselineLoading}}
onClose={handleHideOverwriteModal}
onConfirm={handleUpdateResults}
/>
)}
</div>
);
};
Expand Down
Loading
Loading