Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reject Message #233

Merged
merged 6 commits into from
Nov 11, 2024
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
29 changes: 29 additions & 0 deletions src/actions/RecordsActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,35 @@ export function loadRecordsPhases() {
};
}

export function loadAllowedRejectReason() {
return function (dispatch) {
dispatch(loadAllowedRejectReasonPending());
return axiosBackend
.get(`${API_URL}/rest/records/allowedRejectReason`)
.then((response) => {
dispatch(loadAllowedRejectReasonSuccess(response.data));
})
.catch((error) => {
dispatch(loadAllowedRejectReasonError(error));
});
};
}

export function loadAllowedRejectReasonSuccess(isAllowedRejectReason) {
return {
type: ActionConstants.LOAD_ALLOWED_REJECT_REASON_SUCCESS,
isAllowedRejectReason,
};
}

export function loadAllowedRejectReasonError(error) {
return asyncError(ActionConstants.LOAD_ALLOWED_REJECT_REASON_ERROR, error);
}

export function loadAllowedRejectReasonPending() {
return asyncRequest(ActionConstants.LOAD_ALLOWED_REJECT_REASON_PENDING);
}

export function loadRecordsPhasesPending() {
return asyncRequest(ActionConstants.LOAD_RECORDS_PHASES_PENDING);
}
Expand Down
86 changes: 86 additions & 0 deletions src/components/button/RejectButton.jsx
Original file line number Diff line number Diff line change
@@ -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 { loadAllowedRejectReason } 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 isAllowedRejectReason = useSelector((state) => state.records.isAllowedRejectReason.data);
const [showModal, setShowModal] = useState(false);
const [rejectionReason, setRejectionReason] = useState("");

useEffect(() => {
dispatch(loadAllowedRejectReason());
}, [dispatch]);

const handleShow = () => setShowModal(true);
const handleClose = () => setShowModal(false);
const handleInputChange = (event) => setRejectionReason(event.target.value);
const handleReject = () => {
setShowModal(false);
onClick(rejectionReason);
};

return (
<>
{isAllowedRejectReason ? (
<Button className={className} size={size} disabled={disabled} variant={variant} onClick={handleShow}>
{children}
</Button>
) : (
<Button className={className} size={size} disabled={disabled} variant={variant} onClick={onClick}>
{children}
</Button>
)}

<Modal show={showModal} onHide={handleClose}>
<Modal.Header className="bg-warning">
<Modal.Title className="h5">{i18n("reject-dialog-title")}</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form>
<Form.Group controlId="formRejectionReason">
<Form.Control
as="textarea"
rows={4}
placeholder={i18n("records.rejection-reason-placeholder")}
value={rejectionReason}
onChange={handleInputChange}
/>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
{i18n("close")}
</Button>
<Button variant="danger" onClick={handleReject}>
{i18n("reject")}
</Button>
</Modal.Footer>
</Modal>
</>
);
};

RejectButton.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
variant: PropTypes.string,
size: PropTypes.string,
disabled: PropTypes.bool,
onClick: PropTypes.func,
};

export default RejectButton;
5 changes: 3 additions & 2 deletions src/components/record/Record.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -188,7 +189,7 @@ class Record extends React.Component {
{EXTENSIONS === EXTENSION_CONSTANTS.SUPPLIER &&
!record.isNew &&
(record.phase === RECORD_PHASE.OPEN || this._isAdmin()) && (
<Button
<RejectButton
className="mx-1 action-button"
variant="danger"
size="sm"
Expand All @@ -203,7 +204,7 @@ class Record extends React.Component {
>
{this.i18n("reject")}
{recordSaved.status === ACTION_STATUS.PENDING && <LoaderSmall />}
</Button>
</RejectButton>
)}

{(EXTENSIONS === EXTENSION_CONSTANTS.SUPPLIER || EXTENSIONS === EXTENSION_CONSTANTS.OPERATOR) &&
Expand Down
32 changes: 20 additions & 12 deletions src/components/record/RecordController.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,27 +130,35 @@ class RecordController extends React.Component {
};

_onComplete = () => {
this._handlePhaseChange(RECORD_PHASE.COMPLETED);
this._transitionToRecords();
this._handlePhaseChange(RECORD_PHASE.COMPLETED, () => {
this._handleRejectReason(null, () => {
this.props.updateRecord(this.state.record, this.props.currentUser);
this._transitionToRecords();
});
});
};

_onReject = () => {
this._handlePhaseChange(RECORD_PHASE.REJECTED);
this._transitionToRecords();
_onReject = (reason) => {
this._handlePhaseChange(RECORD_PHASE.REJECTED, () => {
this._handleRejectReason(reason, () => {
this.props.updateRecord(this.state.record, this.props.currentUser);
this._transitionToRecords();
});
});
};

_handlePhaseChange = (newPhase) => {
const currentUser = this.props.currentUser;
const update = { ...this.state.record, phase: newPhase };

this.setState({ record: update });
_handleRejectReason(reason, callback) {
const update = { ...this.state.record, rejectReason: reason };
this.setState({ record: update }, callback);
}

this.props.updateRecord(update, currentUser);
_handlePhaseChange = (newPhase, callback) => {
const update = { ...this.state.record, phase: newPhase };
this.setState({ record: update }, callback);
};

_getLocalName() {
if (EXTENSIONS.split(",").includes("kodi")) {
// return name of the record based on answer of specific question
return this._getKodiLocaLName();
}
return "record-" + Date.now();
Expand Down
13 changes: 10 additions & 3 deletions src/components/record/RecordRow.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -37,6 +37,14 @@ let RecordRow = (props) => {
);
const statusInfo = StatusInfo[record.phase];

const statusInfoText = () => {
if (record.rejectReason) {
return `${i18n(statusInfo.tooltip)}\n${i18n("reason")}: ${record.rejectReason}`;
} else {
return `${i18n(statusInfo.tooltip)}`;
}
};

return (
<tr className="position-relative">
<IfGranted expected={ROLE.ADMIN} actual={props.currentUser.role}>
Expand All @@ -61,9 +69,8 @@ let RecordRow = (props) => {
{formatDate(new Date(record.lastModified ? record.lastModified : record.dateCreated))}
</td>
<td className="report-row content-center">
{statusInfo ? <HelpIcon text={i18n(statusInfo.tooltip)} glyph={statusInfo.glyph} /> : "N/A"}
{statusInfo ? <HelpIcon text={statusInfoText()} glyph={statusInfo.glyph} /> : "N/A"}
</td>

<td className="report-row actions">
<PromiseTrackingMask area={`record-${record.key}`} />
<Button variant="primary" size="sm" title={i18n("records.open-tooltip")} onClick={() => props.onEdit(record)}>
Expand Down
5 changes: 5 additions & 0 deletions src/constants/ActionConstants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

// 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";
5 changes: 4 additions & 1 deletion src/i18n/cs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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í",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -155,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",
Expand Down
5 changes: 4 additions & 1 deletion src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export default {
reject: "Reject",
complete: "Complete",
publish: "Publish",
reason: "Reason",
"select.placeholder": "Select...",

"login.title": Constants.APP_NAME + " - Login",
Expand Down Expand Up @@ -156,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",
Expand All @@ -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",
Expand Down
28 changes: 28 additions & 0 deletions src/reducers/RecordsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ACTION_STATUS } from "../constants/DefaultConstants";
const initialState = {
recordsLoaded: {},
recordsPhases: {},
isAllowedRejectReason: {},
};

export default function (state = initialState, action) {
Expand Down Expand Up @@ -59,6 +60,33 @@ export default function (state = initialState, action) {
error: action.error,
},
};
case ActionConstants.LOAD_ALLOWED_REJECT_REASON_PENDING:
return {
...state,
isAllowedRejectReason: {
status: ACTION_STATUS.PENDING,
...state.isAllowedRejectReason,
},
};

case ActionConstants.LOAD_ALLOWED_REJECT_REASON_SUCCESS:
return {
...state,
isAllowedRejectReason: {
status: ACTION_STATUS.PENDING,
data: state.isAllowedRejectReason,
error: "",
},
};
case ActionConstants.LOAD_ALLOWED_REJECT_REASON_ERROR:
return {
...state,
isAllowedRejectReason: {
status: ACTION_STATUS.ERROR,
error: action.error,
},
};

default:
return state;
}
Expand Down
Loading