From d1d576712e592d52abbb860467995253d14e4d06 Mon Sep 17 00:00:00 2001 From: Daniil Palagin Date: Thu, 24 Oct 2024 11:36:29 +0200 Subject: [PATCH 1/6] [#205] Implement action to call allowed_reject_message variable --- src/actions/RecordsActions.js | 29 +++++++++++++++++++++++++++++ src/constants/ActionConstants.js | 5 +++++ src/reducers/RecordsReducer.js | 28 ++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/src/actions/RecordsActions.js b/src/actions/RecordsActions.js index f821182a..edd2e69c 100644 --- a/src/actions/RecordsActions.js +++ b/src/actions/RecordsActions.js @@ -187,6 +187,35 @@ export function loadRecordsPhases() { }; } +export function loadAllowedRejectMessage() { + return function (dispatch) { + dispatch(loadAllowedRejectMessagePending()); + return axiosBackend + .get(`${API_URL}/rest/records/allowedRejectMessage`) + .then((response) => { + dispatch(loadAllowedRejectMessageSuccess(response.data)); + }) + .catch((error) => { + dispatch(loadAllowedRejectMessageError(error)); + }); + }; +} + +export function loadAllowedRejectMessageSuccess(isAllowedRejectMessage) { + return { + type: ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_SUCCESS, + isAllowedRejectMessage, + }; +} + +export function loadAllowedRejectMessageError(error) { + return asyncError(ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_ERROR, error); +} + +export function loadAllowedRejectMessagePending() { + return asyncRequest(ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_PENDING); +} + export function loadRecordsPhasesPending() { return asyncRequest(ActionConstants.LOAD_RECORDS_PHASES_PENDING); } diff --git a/src/constants/ActionConstants.js b/src/constants/ActionConstants.js index 71338ef7..0d17486f 100644 --- a/src/constants/ActionConstants.js +++ b/src/constants/ActionConstants.js @@ -142,3 +142,8 @@ export const DISMISS_MESSAGE = "DISMISS_MESSAGE"; export const LOAD_RECORDS_PHASES_PENDING = "LOAD_PHASES_PENDING"; export const LOAD_RECORDS_PHASES_SUCCESS = "LOAD_PHASES_SUCCESS"; export const LOAD_RECORDS_PHASES_ERROR = "LOAD_PHASES_ERROR"; + +// ActionConstants.js +export const LOAD_ALLOWED_REJECT_MESSAGE_PENDING = "LOAD_ALLOWED_REJECT_MESSAGE_PENDING"; +export const LOAD_ALLOWED_REJECT_MESSAGE_SUCCESS = "LOAD_ALLOWED_REJECT_MESSAGE_SUCCESS"; +export const LOAD_ALLOWED_REJECT_MESSAGE_ERROR = "LOAD_ALLOWED_REJECT_MESSAGE_ERROR"; diff --git a/src/reducers/RecordsReducer.js b/src/reducers/RecordsReducer.js index cfef3932..949a9f1b 100644 --- a/src/reducers/RecordsReducer.js +++ b/src/reducers/RecordsReducer.js @@ -4,6 +4,7 @@ import { ACTION_STATUS } from "../constants/DefaultConstants"; const initialState = { recordsLoaded: {}, recordsPhases: {}, + isAllowedRejectMessage: {}, }; export default function (state = initialState, action) { @@ -59,6 +60,33 @@ export default function (state = initialState, action) { error: action.error, }, }; + case ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_PENDING: + return { + ...state, + isAllowedRejectMessage: { + status: ACTION_STATUS.PENDING, + ...state.isAllowedRejectMessage, + }, + }; + + case ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_SUCCESS: + return { + ...state, + isAllowedRejectMessage: { + status: ACTION_STATUS.PENDING, + data: state.isAllowedRejectMessage, + error: "", + }, + }; + case ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_ERROR: + return { + ...state, + isAllowedRejectMessage: { + status: ACTION_STATUS.ERROR, + error: action.error, + }, + }; + default: return state; } From b952cbeaaa93a0a0d13cefc681b185c86af63161 Mon Sep 17 00:00:00 2001 From: Daniil Palagin Date: Thu, 24 Oct 2024 11:39:57 +0200 Subject: [PATCH 2/6] [#205] Implement RejectButton with modal window --- src/components/button/RejectButton.jsx | 86 ++++++++++++++++++++++++++ src/components/record/Record.jsx | 5 +- src/i18n/cs.js | 3 + src/i18n/en.js | 3 + 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/components/button/RejectButton.jsx diff --git a/src/components/button/RejectButton.jsx b/src/components/button/RejectButton.jsx new file mode 100644 index 00000000..7d697102 --- /dev/null +++ b/src/components/button/RejectButton.jsx @@ -0,0 +1,86 @@ +import React, { useEffect, useState } from "react"; +import PropTypes from "prop-types"; // Import PropTypes +import { Button, Modal, Form } from "react-bootstrap"; +import { useI18n } from "../../hooks/useI18n.jsx"; +import { useDispatch, useSelector } from "react-redux"; +import { loadAllowedRejectMessage } from "../../actions/RecordsActions.js"; +import "bootstrap/dist/css/bootstrap.min.css"; + +const RejectButton = ({ + children, + className = "", + variant = "danger", + size = "sm", + disabled = true, + onClick = () => {}, +}) => { + const { i18n } = useI18n(); + const dispatch = useDispatch(); + const isAllowedRejectMessage = useSelector((state) => state.records.isAllowedRejectMessage.data); + const [showModal, setShowModal] = useState(false); + const [rejectionReason, setRejectionReason] = useState(""); + + useEffect(() => { + dispatch(loadAllowedRejectMessage()); + }, [dispatch]); + + const handleShow = () => setShowModal(true); + const handleClose = () => setShowModal(false); + const handleInputChange = (event) => setRejectionReason(event.target.value); + const handleReject = () => { + setShowModal(false); + onClick(rejectionReason); + }; + + return ( + <> + {isAllowedRejectMessage ? ( + + ) : ( + + )} + + + + {i18n("reject-dialog-title")} + + +
+ + + +
+
+ + + + +
+ + ); +}; + +RejectButton.propTypes = { + children: PropTypes.node.isRequired, + className: PropTypes.string, + variant: PropTypes.string, + size: PropTypes.string, + disabled: PropTypes.bool, + onClick: PropTypes.func, +}; + +export default RejectButton; diff --git a/src/components/record/Record.jsx b/src/components/record/Record.jsx index ffb1b6c4..81c2058b 100644 --- a/src/components/record/Record.jsx +++ b/src/components/record/Record.jsx @@ -15,6 +15,7 @@ import { isAdmin } from "../../utils/SecurityUtils"; import PromiseTrackingMask from "../misc/PromiseTrackingMask"; import { Constants as SConstants, FormUtils } from "@kbss-cvut/s-forms"; import FormValidationDialog from "../FormValidationDialog.jsx"; +import RejectButton from "../button/RejectButton.jsx"; class Record extends React.Component { static propTypes = { @@ -188,7 +189,7 @@ class Record extends React.Component { {EXTENSIONS === EXTENSION_CONSTANTS.SUPPLIER && !record.isNew && (record.phase === RECORD_PHASE.OPEN || this._isAdmin()) && ( - + )} {(EXTENSIONS === EXTENSION_CONSTANTS.SUPPLIER || EXTENSIONS === EXTENSION_CONSTANTS.OPERATOR) && diff --git a/src/i18n/cs.js b/src/i18n/cs.js index d005ce1e..e44a505d 100644 --- a/src/i18n/cs.js +++ b/src/i18n/cs.js @@ -36,6 +36,7 @@ export default { reject: "Odmítnout", complete: "Dokončit", publish: "Publikovat", + reason: "Důvod", "select.placeholder": "Vyberte...", "login.title": Constants.APP_NAME + " - Přihlášení", @@ -121,6 +122,8 @@ export default { "user.impersonate": "Impersonace", "user.impersonate-error": "Impersonace se nepodařila. {error}", "user.edit": "Upravit profil", + "reject-dialog-title": "Proč chcete odmítnout?", + "records.rejection-reason-placeholder": "Zadejte důvod odmítnutí", "institutions.panel-title": "Instituce", "institutions.create-institution": "Vytvořit instituci", diff --git a/src/i18n/en.js b/src/i18n/en.js index 1f0f2d37..f20b3de3 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -36,6 +36,7 @@ export default { reject: "Reject", complete: "Complete", publish: "Publish", + reason: "Reason", "select.placeholder": "Select...", "login.title": Constants.APP_NAME + " - Login", @@ -178,6 +179,8 @@ export default { "records.import.success.message": "Successfully imported all {importedCount, plural, one {# record} other {# records}}.", "records.import.error.message": "Record import failed. Check the server log for details.", + "reject-dialog-title": "Why do you want to reject?", + "records.rejection-reason-placeholder": "Enter your reason for rejection", "record.panel-title": "Form {identifier}", "record.form-title": "Details", From fdbbe197471dc69e8523ab0c44e6a1d59f7f75aa Mon Sep 17 00:00:00 2001 From: Daniil Palagin Date: Thu, 24 Oct 2024 11:41:46 +0200 Subject: [PATCH 3/6] [#205] Implement handleRejectMessage to handle reject message updates --- src/components/record/RecordController.jsx | 12 +++++++++++- src/components/record/RecordRow.jsx | 15 +++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/components/record/RecordController.jsx b/src/components/record/RecordController.jsx index 48dfa32f..51dabed4 100644 --- a/src/components/record/RecordController.jsx +++ b/src/components/record/RecordController.jsx @@ -134,11 +134,21 @@ class RecordController extends React.Component { this._transitionToRecords(); }; - _onReject = () => { + _onReject = (reason) => { this._handlePhaseChange(RECORD_PHASE.REJECTED); + this._handleRejectMessage(reason); this._transitionToRecords(); }; + _handleRejectMessage(reason) { + const currentUser = this.props.currentUser; + const update = { ...this.state.record, rejectMessage: reason }; + console.log(this.state.record); + this.setState({ record: update }); + + this.props.updateRecord(update, currentUser); + } + _handlePhaseChange = (newPhase) => { const currentUser = this.props.currentUser; const update = { ...this.state.record, phase: newPhase }; diff --git a/src/components/record/RecordRow.jsx b/src/components/record/RecordRow.jsx index 65638011..20695612 100644 --- a/src/components/record/RecordRow.jsx +++ b/src/components/record/RecordRow.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { formatDate } from "../../utils/Utils"; import HelpIcon from "../HelpIcon"; import { Button } from "react-bootstrap"; @@ -26,7 +26,7 @@ StatusInfo[RECORD_PHASE.REJECTED] = { tooltip: "records.completion-status-tooltip.rejected", }; -let RecordRow = (props) => { +const RecordRow = (props) => { const { i18n } = useI18n(); const record = props.record, formTemplateOptions = props.formTemplateOptions, @@ -37,6 +37,14 @@ let RecordRow = (props) => { ); const statusInfo = StatusInfo[record.phase]; + const statusInfoText = () => { + if (record.rejectMessage) { + return `${i18n(statusInfo.tooltip)}\n${i18n("reason")}: ${record.rejectMessage}`; + } else { + return `${i18n(statusInfo.tooltip)}`; + } + }; + return ( @@ -61,9 +69,8 @@ let RecordRow = (props) => { {formatDate(new Date(record.lastModified ? record.lastModified : record.dateCreated))} - {statusInfo ? : "N/A"} + {statusInfo ? : "N/A"} - diff --git a/src/components/record/RecordController.jsx b/src/components/record/RecordController.jsx index 881effad..ee10db3e 100644 --- a/src/components/record/RecordController.jsx +++ b/src/components/record/RecordController.jsx @@ -131,7 +131,7 @@ class RecordController extends React.Component { _onComplete = () => { this._handlePhaseChange(RECORD_PHASE.COMPLETED, () => { - this._handleRejectMessage(null, () => { + this._handleRejectReason(null, () => { this.props.updateRecord(this.state.record, this.props.currentUser); this._transitionToRecords(); }); @@ -140,15 +140,15 @@ class RecordController extends React.Component { _onReject = (reason) => { this._handlePhaseChange(RECORD_PHASE.REJECTED, () => { - this._handleRejectMessage(reason, () => { + this._handleRejectReason(reason, () => { this.props.updateRecord(this.state.record, this.props.currentUser); this._transitionToRecords(); }); }); }; - _handleRejectMessage(reason, callback) { - const update = { ...this.state.record, rejectMessage: reason }; + _handleRejectReason(reason, callback) { + const update = { ...this.state.record, rejectReason: reason }; this.setState({ record: update }, callback); } diff --git a/src/components/record/RecordRow.jsx b/src/components/record/RecordRow.jsx index 20695612..702d16d8 100644 --- a/src/components/record/RecordRow.jsx +++ b/src/components/record/RecordRow.jsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React from "react"; import { formatDate } from "../../utils/Utils"; import HelpIcon from "../HelpIcon"; import { Button } from "react-bootstrap"; @@ -38,8 +38,8 @@ const RecordRow = (props) => { const statusInfo = StatusInfo[record.phase]; const statusInfoText = () => { - if (record.rejectMessage) { - return `${i18n(statusInfo.tooltip)}\n${i18n("reason")}: ${record.rejectMessage}`; + if (record.rejectReason) { + return `${i18n(statusInfo.tooltip)}\n${i18n("reason")}: ${record.rejectReason}`; } else { return `${i18n(statusInfo.tooltip)}`; } diff --git a/src/constants/ActionConstants.js b/src/constants/ActionConstants.js index 0d17486f..b0ec88a9 100644 --- a/src/constants/ActionConstants.js +++ b/src/constants/ActionConstants.js @@ -143,7 +143,7 @@ export const LOAD_RECORDS_PHASES_PENDING = "LOAD_PHASES_PENDING"; export const LOAD_RECORDS_PHASES_SUCCESS = "LOAD_PHASES_SUCCESS"; export const LOAD_RECORDS_PHASES_ERROR = "LOAD_PHASES_ERROR"; -// ActionConstants.js -export const LOAD_ALLOWED_REJECT_MESSAGE_PENDING = "LOAD_ALLOWED_REJECT_MESSAGE_PENDING"; -export const LOAD_ALLOWED_REJECT_MESSAGE_SUCCESS = "LOAD_ALLOWED_REJECT_MESSAGE_SUCCESS"; -export const LOAD_ALLOWED_REJECT_MESSAGE_ERROR = "LOAD_ALLOWED_REJECT_MESSAGE_ERROR"; +// server configuration +export const LOAD_ALLOWED_REJECT_REASON_PENDING = "LOAD_ALLOWED_REJECT_REASON_PENDING"; +export const LOAD_ALLOWED_REJECT_REASON_SUCCESS = "LOAD_ALLOWED_REJECT_REASON_SUCCESS"; +export const LOAD_ALLOWED_REJECT_REASON_ERROR = "LOAD_ALLOWED_REJECT_REASON_ERROR"; diff --git a/src/reducers/RecordsReducer.js b/src/reducers/RecordsReducer.js index 949a9f1b..c53b8f9c 100644 --- a/src/reducers/RecordsReducer.js +++ b/src/reducers/RecordsReducer.js @@ -4,7 +4,7 @@ import { ACTION_STATUS } from "../constants/DefaultConstants"; const initialState = { recordsLoaded: {}, recordsPhases: {}, - isAllowedRejectMessage: {}, + isAllowedRejectReason: {}, }; export default function (state = initialState, action) { @@ -60,28 +60,28 @@ export default function (state = initialState, action) { error: action.error, }, }; - case ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_PENDING: + case ActionConstants.LOAD_ALLOWED_REJECT_REASON_PENDING: return { ...state, - isAllowedRejectMessage: { + isAllowedRejectReason: { status: ACTION_STATUS.PENDING, - ...state.isAllowedRejectMessage, + ...state.isAllowedRejectReason, }, }; - case ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_SUCCESS: + case ActionConstants.LOAD_ALLOWED_REJECT_REASON_SUCCESS: return { ...state, - isAllowedRejectMessage: { + isAllowedRejectReason: { status: ACTION_STATUS.PENDING, - data: state.isAllowedRejectMessage, + data: state.isAllowedRejectReason, error: "", }, }; - case ActionConstants.LOAD_ALLOWED_REJECT_MESSAGE_ERROR: + case ActionConstants.LOAD_ALLOWED_REJECT_REASON_ERROR: return { ...state, - isAllowedRejectMessage: { + isAllowedRejectReason: { status: ACTION_STATUS.ERROR, error: action.error, }, From 726b60a91ec6b9353284feeac17205ad24d88201 Mon Sep 17 00:00:00 2001 From: Daniil Palagin Date: Tue, 5 Nov 2024 13:47:20 +0100 Subject: [PATCH 6/6] [#205] Fix grammar dot suggestion --- src/i18n/cs.js | 2 +- src/i18n/en.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/cs.js b/src/i18n/cs.js index e44a505d..29296389 100644 --- a/src/i18n/cs.js +++ b/src/i18n/cs.js @@ -158,7 +158,7 @@ export default { "records.completion-status.rejected": "Odmítnutý", "records.completion-status-tooltip.complete": "Všechny povinné informace ze záznamu byly vyplněny.", "records.completion-status-tooltip.incomplete": "Některé povinné informace ze záznamu ještě nebyly vyplněny.", - "records.completion-status-tooltip.rejected": "Formulář byl odmítnut", + "records.completion-status-tooltip.rejected": "Formulář byl odmítnut.", "records.completion-status-tooltip.published": "Formulář byl zveřejněn", "records.last-modified": "Naposledy upraveno", "records.open-tooltip": "Zobrazit či upravit tento záznam", diff --git a/src/i18n/en.js b/src/i18n/en.js index f20b3de3..ef095a16 100644 --- a/src/i18n/en.js +++ b/src/i18n/en.js @@ -157,7 +157,7 @@ export default { "records.completion-status-tooltip.complete": "All required fields of the record have been filled out.", "records.completion-status-tooltip.incomplete": "Some of the required fields of the record have not yet been filled out.", - "records.completion-status-tooltip.rejected": "The form was rejected", + "records.completion-status-tooltip.rejected": "The form was rejected.", "records.completion-status-tooltip.published": "The form has been published", "records.last-modified": "Last modified", "records.open-tooltip": "View and edit the record",