diff --git a/.gitignore b/.gitignore index 1d2863ba..efa7d011 100644 --- a/.gitignore +++ b/.gitignore @@ -83,4 +83,5 @@ celerybeat-schedule.db node_modules # local tmp folder -tmp \ No newline at end of file +tmp +scripts \ No newline at end of file diff --git a/assets/js/components/record_details/CreatePublicRecordModal.js b/assets/js/components/record_details/CreatePublicRecordModal.js new file mode 100644 index 00000000..d12293c3 --- /dev/null +++ b/assets/js/components/record_details/CreatePublicRecordModal.js @@ -0,0 +1,248 @@ +// This file is part of CDS RDM +// Copyright (C) 2025 CERN. +// +// CDS RDM is free software; you can redistribute it and/or modify it +// under the terms of the GPL-2.0 License; see LICENSE file for more details. + +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { + Button, + Checkbox, + Header, + Icon, + Message, + Modal, +} from "semantic-ui-react"; +import { http } from "react-invenio-forms"; +import { i18next } from "@translations/invenio_rdm_records/i18next"; + +export class CreatePublicRecordModal extends Component { + constructor(props) { + super(props); + this.state = { + submitting: false, + error: null, + publicRecord: null, + alreadyExists: false, + agreedToTerms: false, + agreedToCommunity: false, + }; + } + + handleCreate = async () => { + const { record, onSuccess } = this.props; + this.setState({ submitting: true, error: null, alreadyExists: false }); + try { + const response = await http.post( + `/api/records/${record.id}/ep-approval/publish-public`, + {}, + { headers: { "Content-Type": "application/json" } } + ); + this.setState({ publicRecord: response.data }); + onSuccess(response.data); + } catch (err) { + if (err?.response?.status === 409 && err?.response?.data?.id) { + // A public record already exists — show a warning and surface the link. + const existingRecord = err.response.data; + this.setState({ publicRecord: existingRecord, alreadyExists: true }); + onSuccess(existingRecord); + } else { + const msg = + err?.response?.data?.message || + i18next.t("An error occurred. Please try again."); + this.setState({ error: msg }); + } + } finally { + this.setState({ submitting: false }); + } + }; + + handleClose = () => { + const { onClose } = this.props; + this.setState({ + error: null, + publicRecord: null, + agreedToTerms: false, + agreedToCommunity: false, + }); + onClose(); + }; + + render() { + const { open, record } = this.props; + const { + submitting, + error, + publicRecord, + alreadyExists, + agreedToTerms, + agreedToCommunity, + } = this.state; + + const versionIndex = record?.versions?.index; + const canPublish = agreedToTerms && agreedToCommunity; + + const epApprovalEl = document.getElementById("recordManagement"); + const epApproval = epApprovalEl + ? JSON.parse(epApprovalEl.dataset.epApproval || "null") + : null; + const communityId = epApproval?.cern_scientific_community_id; + + return ( + +
+ + {error && } + + {publicRecord && alreadyExists ? ( + + + {i18next.t("A public record already exists")} + + + {i18next.t( + "A public record for this approval has already been created." + )}{" "} + + {i18next.t("View public record")} + + + + + ) : publicRecord ? ( + + + {i18next.t("Public record created")} + + + {i18next.t("The public record has been created successfully.")}{" "} + + {i18next.t("View public record")} + + + + + ) : ( + <> + +

+ {" "} + {i18next.t( + "The metadata and files of Version v{{v}} will be copied to the new public record. Once published, the files can no longer be changed.", + { v: versionIndex ?? "?" } + )} +

+
+ + + this.setState({ agreedToTerms: checked }) + } + label={ + + } + /> +
+ + this.setState({ agreedToCommunity: checked }) + } + label={ + + } + /> +
+ + )} +
+ + + {!publicRecord && ( + + )} + + + + {canResubmit && ( + this.setState({ submitModalOpen: false })} + onSuccess={this.handleSubmitSuccess} + /> + )} + + + {/* Step 2 — EP Board review */} + + +
+
+ {i18next.t("EP Board review")} + + {step2Completed ? ( + requestLink ? ( + + {i18next.t("Approved as {{rn}}", { + rn: approved_report_number, + })} + + + ) : ( + i18next.t("Approved as {{rn}}", { + rn: approved_report_number, + }) + ) + ) : ( + i18next.t( + "The EP secretariat will review the submission." + ) + )} + +
+ {step2Active && requestLink && ( + + )} +
+
+
+ + {/* Step 3 — Create final public version */} + + +
+
+ + {i18next.t("Create final public version")} + + + {step3Completed ? ( + resolvedPublicRecordUrl ? ( + + {i18next.t("View public record")} + + + ) : ( + i18next.t("Public record created.") + ) + ) : ( + i18next.t( + "Publish the EP-approved record publicly on CDS." + ) + )} + +
+ {step3Active && ( + + )} +
+
+ {step3Active && ( + this.setState({ createPublicModalOpen: false })} + onSuccess={this.handlePublicRecordCreated} + /> + )} +
+ + + {/* New-version warning modal — shown when user clicks "New version" while a request is pending */} + this.setState({ newVersionModalOpen: false })} + size="small" + > + + + {i18next.t("EP approval request pending")} + + +

+ {i18next.t( + "An EP approval request is currently pending for this record. " + + "Creating a new version is not recommended while the request is open. " + + "If you need to create a new version, please cancel the approval request first." + )} +

+
+ + + +
+ + ); + } +} + +EPApprovalManageSection.propTypes = { + record: PropTypes.object.isRequired, +}; diff --git a/assets/js/components/record_details/EPApprovalSubmitModal.js b/assets/js/components/record_details/EPApprovalSubmitModal.js new file mode 100644 index 00000000..5a98a9e5 --- /dev/null +++ b/assets/js/components/record_details/EPApprovalSubmitModal.js @@ -0,0 +1,242 @@ +// This file is part of CDS RDM +// Copyright (C) 2025 CERN. +// +// CDS RDM is free software; you can redistribute it and/or modify it +// under the terms of the GPL-2.0 License; see LICENSE file for more details. + +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { + Button, + Checkbox, + Form, + Header, + Message, + Modal, +} from "semantic-ui-react"; +import { http } from "react-invenio-forms"; +import { i18next } from "@translations/invenio_app_rdm/i18next"; + +const buildInitialForm = (record) => ({ + experiment: "", + submitted_by: "", + role: "", + publication_title: record?.metadata?.title || "", + latest_version_url: record?.links?.self_html || "", + rapid_approval: false, + cb_review_completed: false, + cb_process_type: "", + paper_signed: true, + num_non_signers: 0, + controversy: false, + additional_communication: "", +}); + +export class EPApprovalSubmitModal extends Component { + constructor(props) { + super(props); + this.state = { + form: buildInitialForm(props.record), + submitting: false, + error: null, + }; + } + + handleChange = (e, { name, value, checked, type }) => { + this.setState((prev) => ({ + form: { + ...prev.form, + [name]: type === "checkbox" ? checked : value, + }, + })); + }; + + handleSubmit = async () => { + const { record, receiverGroup, onSuccess } = this.props; + const { form } = this.state; + + this.setState({ submitting: true, error: null }); + try { + const payload = { + title: `Request approval for ${record["title"]}`, + receiver_group: receiverGroup, + payload: { ...form }, + }; + const response = await http.post( + `/api/records/${record.id}/ep-approval`, + payload, + { headers: { "Content-Type": "application/json" } } + ); + onSuccess(response.data); + } catch (err) { + const msg = + err?.response?.data?.message || + i18next.t("An error occurred. Please try again."); + this.setState({ error: msg }); + } finally { + this.setState({ submitting: false }); + } + }; + + handleClose = () => { + const { onClose, record } = this.props; + this.setState({ form: buildInitialForm(record), error: null }); + onClose(); + }; + + render() { + const { open } = this.props; + const { form, submitting, error } = this.state; + + return ( + +
+ + {error && } +
+ + + + + + + + + + + + {form.cb_review_completed && ( + + + + + + )} + + + + {!form.paper_signed && ( + + )} + {!form.paper_signed && ( + + + + )} + + +
+ + +