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..8eeadccd --- /dev/null +++ b/assets/js/components/record_details/CreatePublicRecordModal.js @@ -0,0 +1,227 @@ +// 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 && ( + + )} + + {/* Declined — warning message + link to request + allow re-submission */} + {isDeclined && ( + + + {i18next.t("The approval request was declined.")} + {open_request.links?.self_html && ( + <> + {" "} + + {i18next.t("View request")} + + + + )} + + + )} + + {/* Submit / re-submit button */} + {canResubmit && ( + <> + + this.setState({ submitModalOpen: false })} + onSuccess={this.handleSubmitSuccess} + /> + + )} + {/* 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." + )} +

+
+ + + +
+ + ); + } +} diff --git a/assets/js/components/record_details/EPApprovalSubmitModal.js b/assets/js/components/record_details/EPApprovalSubmitModal.js new file mode 100644 index 00000000..14d969b2 --- /dev/null +++ b/assets/js/components/record_details/EPApprovalSubmitModal.js @@ -0,0 +1,233 @@ +// 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 && ( + + + + )} + + +
+ + +