mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-12-09 13:46:21 +08:00
Merge pull request #6864 from aignatov-bio/ai-sci-9802-migrate-protocols-table
Migrate protocols table [SCI-9802]
This commit is contained in:
commit
e93635f061
27 changed files with 1321 additions and 187 deletions
|
|
@ -1,7 +1,8 @@
|
|||
//= require protocols/import_export/import
|
||||
/* eslint-disable no-use-before-define, no-underscore-dangle, max-len, no-param-reassign */
|
||||
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile _ PerfectSb protocolsIO
|
||||
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
|
||||
/* global ProtocolRepositoryHeader PdfPreview DataTableHelpers importProtocolFromFile
|
||||
protocolFileImportModal PerfectSb protocolsIO
|
||||
protocolSteps dropdownSelector filterDropdown I18n animateSpinner initHandsOnTable inlineEditing HelperModule */
|
||||
|
||||
// Global variables
|
||||
var ProtocolsIndex = (function() {
|
||||
|
|
@ -27,10 +28,10 @@ var ProtocolsIndex = (function() {
|
|||
* Initializes page
|
||||
*/
|
||||
function init() {
|
||||
window.initActionToolbar();
|
||||
window.actionToolbarComponent.setReloadCallback(reloadTable);
|
||||
// window.initActionToolbar();
|
||||
// window.actionToolbarComponent.setReloadCallback(reloadTable);
|
||||
// make room for pagination
|
||||
window.actionToolbarComponent.setBottomOffset(68);
|
||||
// window.actionToolbarComponent.setBottomOffset(68);
|
||||
updateButtons();
|
||||
initProtocolsTable();
|
||||
initKeywordFiltering();
|
||||
|
|
@ -38,6 +39,7 @@ var ProtocolsIndex = (function() {
|
|||
initLinkedChildrenModal();
|
||||
initModals();
|
||||
initVersionsModal();
|
||||
initLocalFileImport();
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
|
|
@ -267,7 +269,6 @@ var ProtocolsIndex = (function() {
|
|||
let protocolFilters = $($('#protocolFilters').html());
|
||||
$(protocolFilters).appendTo('.protocols-container .protocol-filters');
|
||||
|
||||
initLocalFileImport();
|
||||
initProtocolsFilters();
|
||||
initRowSelection();
|
||||
},
|
||||
|
|
@ -649,8 +650,6 @@ var ProtocolsIndex = (function() {
|
|||
}
|
||||
|
||||
function updateButtons() {
|
||||
window.actionToolbarComponent.fetchActions({ protocol_ids: rowsSelected });
|
||||
$('.dataTables_scrollBody').css('margin-bottom', `${rowsSelected.length > 0 ? 46 : 0}px`);
|
||||
}
|
||||
|
||||
function initLocalFileImport() {
|
||||
|
|
@ -673,7 +672,6 @@ var ProtocolsIndex = (function() {
|
|||
var importUrl = fileInput.attr('data-import-url');
|
||||
var teamId = fileInput.attr('data-team-id');
|
||||
var type = fileInput.attr('data-type');
|
||||
|
||||
if(ev.target.files[0].name.split('.').pop() === 'eln') {
|
||||
importProtocolFromFile(
|
||||
ev.target.files[0],
|
||||
|
|
@ -691,14 +689,14 @@ var ProtocolsIndex = (function() {
|
|||
|
||||
if (nrSuccessful) {
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success');
|
||||
reloadTable();
|
||||
window.protocolsTable.$refs.table.updateTable();
|
||||
} else {
|
||||
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger');
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
protocolFileImportModal.init(ev.target.files, reloadTable);
|
||||
protocolFileImportModal.init(ev.target.files, window.protocolsTable.$refs.table.updateTable());
|
||||
}
|
||||
// $(this).val('');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ var protocolsIO = function() {
|
|||
animateSpinner(modal, false);
|
||||
modal.modal('hide');
|
||||
HelperModule.flashAlertMsg(data.message, 'success');
|
||||
ProtocolsIndex.reloadTable();
|
||||
window.protocolsTable.$refs.table.updateTable();
|
||||
},
|
||||
error: function(data) {
|
||||
showFormErrors(modal, data.responseJSON.validation_errors);
|
||||
|
|
|
|||
|
|
@ -2,18 +2,20 @@
|
|||
|
||||
module AccessPermissions
|
||||
class ProtocolsController < ApplicationController
|
||||
include InputSanitizeHelper
|
||||
|
||||
before_action :set_protocol
|
||||
before_action :check_read_permissions, only: %i(show)
|
||||
before_action :check_manage_permissions, except: %i(show)
|
||||
before_action :available_users, only: %i(new create)
|
||||
|
||||
def show; end
|
||||
def show
|
||||
render json: @protocol.user_assignments.includes(:user_role, :user).order('users.full_name ASC'),
|
||||
each_serializer: UserAssignmentSerializer
|
||||
end
|
||||
|
||||
def new
|
||||
@user_assignment = UserAssignment.new(
|
||||
assignable: @protocol,
|
||||
assigned_by: current_user,
|
||||
team: current_team
|
||||
)
|
||||
render json: @available_users, each_serializer: UserSerializer
|
||||
end
|
||||
|
||||
def edit; end
|
||||
|
|
@ -21,38 +23,34 @@ module AccessPermissions
|
|||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
created_count = 0
|
||||
permitted_create_params[:resource_members].each do |_k, user_assignment_params|
|
||||
next unless user_assignment_params[:assign] == '1'
|
||||
|
||||
if user_assignment_params[:user_id] == 'all'
|
||||
@protocol.update!(default_public_user_role_id: user_assignment_params[:user_role_id])
|
||||
log_activity(:protocol_template_access_granted_all_team_members,
|
||||
if permitted_create_params[:user_id] == 'all'
|
||||
@protocol.update!(visibility: :visible, default_public_user_role_id: permitted_create_params[:user_role_id])
|
||||
log_activity(:protocol_template_access_granted_all_team_members,
|
||||
{ team: @protocol.team.id, role: @protocol.default_public_user_role&.name })
|
||||
else
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @protocol,
|
||||
user_id: user_assignment_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
else
|
||||
user_assignment = UserAssignment.find_or_initialize_by(
|
||||
assignable: @protocol,
|
||||
user_id: permitted_create_params[:user_id],
|
||||
team: current_team
|
||||
)
|
||||
|
||||
user_assignment.update!(
|
||||
user_role_id: user_assignment_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
user_assignment.update!(
|
||||
user_role_id: permitted_create_params[:user_role_id],
|
||||
assigned_by: current_user,
|
||||
assigned: :manually
|
||||
)
|
||||
|
||||
created_count += 1
|
||||
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
|
||||
log_activity(:protocol_template_access_granted, { user_target: user_assignment.user.id,
|
||||
role: user_assignment.user_role.name })
|
||||
end
|
||||
created_count += 1
|
||||
end
|
||||
|
||||
@message = if created_count.zero?
|
||||
t('access_permissions.create.success', count: t('access_permissions.all_team'))
|
||||
t('access_permissions.create.success', member_name: t('access_permissions.all_team'))
|
||||
else
|
||||
t('access_permissions.create.success', count: created_count)
|
||||
t('access_permissions.create.success', member_name: escape_input(user_assignment.user.name))
|
||||
end
|
||||
render :edit
|
||||
render json: { message: @message }
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
Rails.logger.error e.message
|
||||
errors = @protocol.errors.present? ? @protocol.errors&.map(&:message)&.join(',') : e.message
|
||||
|
|
@ -146,8 +144,17 @@ module AccessPermissions
|
|||
end
|
||||
|
||||
def permitted_create_params
|
||||
params.require(:access_permissions_new_user_form)
|
||||
.permit(resource_members: %i(assign user_id user_role_id))
|
||||
params.require(:user_assignment)
|
||||
.permit(%i(user_id user_role_id))
|
||||
end
|
||||
|
||||
def available_users
|
||||
# automatically assigned or not assigned to project
|
||||
@available_users = current_team.users.where(
|
||||
id: @protocol.user_assignments.automatically_assigned.select(:user_id)
|
||||
).or(
|
||||
current_team.users.where.not(id: @protocol.users.select(:id))
|
||||
).order('users.full_name ASC')
|
||||
end
|
||||
|
||||
def set_protocol
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class ProtocolsController < ApplicationController
|
|||
protocol_status_bar
|
||||
linked_children
|
||||
linked_children_datatable
|
||||
versions_list
|
||||
permissions
|
||||
)
|
||||
before_action :switch_team_with_param, only: %i(index protocolsio_index)
|
||||
|
|
@ -75,7 +76,20 @@ class ProtocolsController < ApplicationController
|
|||
|
||||
layout 'fluid'
|
||||
|
||||
def index; end
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
protocols = Lists::ProtocolsService.new(Protocol.latest_available_versions(@current_team), params).call
|
||||
render json: protocols,
|
||||
each_serializer: Lists::ProtocolSerializer,
|
||||
user: current_user,
|
||||
meta: pagination_dict(protocols)
|
||||
end
|
||||
format.html do
|
||||
render 'index'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def datatable
|
||||
render json: ::ProtocolsDatatable.new(
|
||||
|
|
@ -90,8 +104,17 @@ class ProtocolsController < ApplicationController
|
|||
return render_403 unless @protocol.in_repository_published_original? || @protocol.initial_draft?
|
||||
|
||||
@published_versions = @protocol.published_versions_with_original.order(version_number: :desc)
|
||||
|
||||
if @protocol.draft.present?
|
||||
draft = @protocol.initial_draft? ? @protocol : @protocol.draft
|
||||
draft_hash = ProtocolDraftSerializer.new(draft, scope: current_user).as_json
|
||||
end
|
||||
|
||||
render json: {
|
||||
html: render_to_string(partial: 'protocols/index/protocol_versions_modal')
|
||||
draft: draft_hash,
|
||||
versions: @published_versions.map do |version|
|
||||
ProtocolVersionSerializer.new(version, scope: current_user).as_json
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -100,14 +123,35 @@ class ProtocolsController < ApplicationController
|
|||
end
|
||||
|
||||
def linked_children
|
||||
render json: {
|
||||
title: I18n.t('protocols.index.linked_children.title',
|
||||
protocol: escape_input(@protocol.name)),
|
||||
html: render_to_string(partial: 'protocols/index/linked_children_modal_body',
|
||||
locals: { protocol: @protocol })
|
||||
if params[:version].present?
|
||||
records = @protocol.published_versions_with_original
|
||||
.find_by!(version_number: params[:version])
|
||||
.linked_children
|
||||
else
|
||||
records = Protocol.where(protocol_type: Protocol.protocol_types[:linked])
|
||||
records = records.where(parent_id: @protocol.published_versions)
|
||||
.or(records.where(parent_id: @protocol.id))
|
||||
end
|
||||
records.preload(my_module: { experiment: :project }).distinct
|
||||
|
||||
render json: records.map { |record|
|
||||
{
|
||||
my_module_name: record.my_module.name,
|
||||
experiment_name: record.my_module.experiment.name,
|
||||
project_name: record.my_module.experiment.project.name,
|
||||
my_module_url: protocols_my_module_path(record.my_module),
|
||||
experiment_url: my_modules_path(experiment_id: record.my_module.experiment.id),
|
||||
project_url: experiments_path(project_id: record.my_module.experiment.project.id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def versions_list
|
||||
render json: { versions: (@protocol.parent || @protocol).published_versions_with_original
|
||||
.order(version_number: :desc)
|
||||
.map(&:version_number) }
|
||||
end
|
||||
|
||||
def linked_children_datatable
|
||||
render json: ::ProtocolLinkedChildrenDatatable.new(
|
||||
view_context,
|
||||
|
|
@ -155,8 +199,12 @@ class ProtocolsController < ApplicationController
|
|||
nil,
|
||||
protocol: @protocol.id)
|
||||
|
||||
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
|
||||
redirect_to protocols_path
|
||||
if params[:version_modal]
|
||||
render json: { message: I18n.t('protocols.delete_draft_modal.success') }
|
||||
else
|
||||
flash[:success] = I18n.t('protocols.delete_draft_modal.success')
|
||||
redirect_to protocols_path
|
||||
end
|
||||
rescue ActiveRecord::RecordNotDestroyed => e
|
||||
Rails.logger.error e.message
|
||||
render json: { message: e.message }, status: :unprocessable_entity
|
||||
|
|
@ -328,17 +376,15 @@ class ProtocolsController < ApplicationController
|
|||
draft = @protocol.save_as_draft(current_user)
|
||||
|
||||
if draft.invalid?
|
||||
flash[:error] = draft.errors.full_messages.join(', ')
|
||||
redirect_to protocols_path
|
||||
render json: { error: draft.errors.messages.map { |_, value| value }.join(' ') }, status: :unprocessable_entity
|
||||
else
|
||||
log_activity(:protocol_template_draft_created, nil, protocol: @protocol.id)
|
||||
redirect_to protocol_path(draft)
|
||||
render json: { url: protocol_path(draft) }
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error(e.message)
|
||||
Rails.logger.error(e.backtrace.join("\n"))
|
||||
flash[:error] = I18n.t('errors.general')
|
||||
redirect_to protocols_path
|
||||
render json: { error: I18n.t('errors.general') }, status: :unprocessable_entity
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
|
@ -823,7 +869,7 @@ class ProtocolsController < ApplicationController
|
|||
actions:
|
||||
Toolbars::ProtocolsService.new(
|
||||
current_user,
|
||||
protocol_ids: params[:protocol_ids].split(',')
|
||||
protocol_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
|
|
|||
12
app/javascript/packs/vue/protocols_list.js
Normal file
12
app/javascript/packs/vue/protocols_list.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import ProtocolsTable from '../../vue/protocols/table.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('ProtocolsTable', ProtocolsTable);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
window.protocolsTable = mountWithTurbolinks(app, '#ProtocolsTable', () => {
|
||||
delete window.protocolsTable;
|
||||
});
|
||||
|
|
@ -61,8 +61,8 @@
|
|||
py-2.5 hover:bg-sn-super-light-grey"
|
||||
@click="createTag()"
|
||||
>
|
||||
<div class="h-8 w-8 rounded relative border-sn-gray border-solid">
|
||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white border-sn-gray border-solid"></div>
|
||||
<div class="h-8 w-8 rounded relative border-sn-grey border-solid">
|
||||
<div class="absolute top-1 left-1 rounded-full w-1 h-1 bg-white border-sn-grey border-solid"></div>
|
||||
</div>
|
||||
<div>{{ i18n.t('experiments.canvas.modal_manage_tags.create_new') }}</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -332,8 +332,8 @@ export default {
|
|||
},
|
||||
move(event, rows) {
|
||||
this.objectsToMove = rows;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
<div class="protocol-section protocol-information mb-4">
|
||||
<div id="protocol-details" class="protocol-section-header">
|
||||
<div class="protocol-details-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#details-container" aria-expanded="false" aria-controls="details-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse"
|
||||
href="#details-container" aria-expanded="false" aria-controls="details-container">
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span id="protocolDetailsLabel" class="protocol-section-title">
|
||||
<h2>
|
||||
|
|
@ -13,12 +14,21 @@
|
|||
</a>
|
||||
</div>
|
||||
<div class="actions-block">
|
||||
<a class="btn btn-light icon-btn pull-right" :href="protocol.attributes.urls.print_protocol_url" target="_blank">
|
||||
<a class="btn btn-light icon-btn pull-right"
|
||||
:href="protocol.attributes.urls.print_protocol_url" target="_blank">
|
||||
<span class="sn-icon sn-icon-printer" aria-hidden="true"></span>
|
||||
</a>
|
||||
<button class="btn btn-light" @click="openVersionsModal">{{ i18n.t("protocols.header.versions") }}</button>
|
||||
<button v-if="protocol.attributes.urls.publish_url" @click="$emit('publish')" class="btn btn-primary">{{ i18n.t("protocols.header.publish") }}</button>
|
||||
<button v-if="protocol.attributes.urls.save_as_draft_url" v-bind:disabled="protocol.attributes.has_draft" @click="saveAsdraft" class="btn btn-secondary">{{ i18n.t("protocols.header.save_as_draft") }}</button>
|
||||
<button class="btn btn-light" @click="openVersionsModal">
|
||||
{{ i18n.t("protocols.header.versions") }}
|
||||
</button>
|
||||
<button v-if="protocol.attributes.urls.publish_url"
|
||||
@click="$emit('publish')" class="btn btn-primary">
|
||||
{{ i18n.t("protocols.header.publish") }}</button>
|
||||
<button v-if="protocol.attributes.urls.save_as_draft_url"
|
||||
v-bind:disabled="protocol.attributes.has_draft"
|
||||
@click="saveAsdraft" class="btn btn-secondary">
|
||||
{{ i18n.t("protocols.header.save_as_draft") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="details-container" class="protocol-details collapse in">
|
||||
|
|
@ -87,77 +97,95 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<VersionsModal v-if="VersionsModalObject" :protocol="VersionsModalObject"
|
||||
@close="VersionsModalObject = null"
|
||||
@reloadPage="reloadPage"
|
||||
@redirectToProtocols="redirectToProtocols"/>
|
||||
</Teleport>
|
||||
</template>
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
import InlineEdit from '../shared/inline_edit.vue';
|
||||
import DropdownSelector from '../shared/legacy/dropdown_selector.vue';
|
||||
import VersionsModal from '../protocols/modals/versions.vue';
|
||||
|
||||
import InlineEdit from '../shared/inline_edit.vue'
|
||||
import DropdownSelector from '../shared/legacy/dropdown_selector.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProtocolMetadata',
|
||||
components: { InlineEdit, DropdownSelector },
|
||||
props: {
|
||||
protocol: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
export default {
|
||||
name: 'ProtocolMetadata',
|
||||
components: { InlineEdit, DropdownSelector, VersionsModal },
|
||||
props: {
|
||||
protocol: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
computed: {
|
||||
titleVersion() {
|
||||
const createdFromVersion = this.protocol.attributes.created_from_version;
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
VersionsModalObject: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
titleVersion() {
|
||||
const createdFromVersion = this.protocol.attributes.created_from_version;
|
||||
|
||||
if (this.protocol.attributes.published) {
|
||||
return this.protocol.attributes.version;
|
||||
}
|
||||
|
||||
if (!createdFromVersion) {
|
||||
return this.i18n.t('protocols.draft');
|
||||
}
|
||||
|
||||
return this.i18n.t('protocols.header.draft_with_from_version', {nr: createdFromVersion});
|
||||
if (this.protocol.attributes.published) {
|
||||
return this.protocol.attributes.version;
|
||||
}
|
||||
|
||||
if (!createdFromVersion) {
|
||||
return this.i18n.t('protocols.draft');
|
||||
}
|
||||
|
||||
return this.i18n.t('protocols.header.draft_with_from_version', { nr: createdFromVersion });
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveAsdraft() {
|
||||
$.post(this.protocol.attributes.urls.save_as_draft_url);
|
||||
},
|
||||
methods: {
|
||||
saveAsdraft() {
|
||||
$.post(this.protocol.attributes.urls.save_as_draft_url)
|
||||
},
|
||||
updateAuthors(authors) {
|
||||
$.ajax({
|
||||
type: 'PATCH',
|
||||
url: this.protocol.attributes.urls.update_protocol_authors_url,
|
||||
data: { protocol: { authors: authors } },
|
||||
success: (result) => {
|
||||
this.$emit('update', result.data.attributes)
|
||||
},
|
||||
error: (data) => {
|
||||
HelperModule.flashAlertMsg(data.responseJSON ? Object.values(data.responseJSON).join(', ') : I18n.t('errors.general'), 'danger');
|
||||
updateAuthors(authors) {
|
||||
$.ajax({
|
||||
type: 'PATCH',
|
||||
url: this.protocol.attributes.urls.update_protocol_authors_url,
|
||||
data: { protocol: { authors } },
|
||||
success: (result) => {
|
||||
this.$emit('update', result.data.attributes);
|
||||
},
|
||||
error: (data) => {
|
||||
let message;
|
||||
if (data.responseJSON) {
|
||||
message = Object.values(data.responseJSON).join(', ');
|
||||
} else {
|
||||
message = this.i18n.t('errors.general');
|
||||
}
|
||||
});
|
||||
},
|
||||
updateKeywords(keywords) {
|
||||
$.ajax({
|
||||
type: 'PATCH',
|
||||
url: this.protocol.attributes.urls.update_protocol_keywords_url,
|
||||
data: { keywords: keywords },
|
||||
success: (result) => {
|
||||
this.$emit('update', result.data.attributes)
|
||||
}
|
||||
});
|
||||
},
|
||||
openVersionsModal() {
|
||||
$.get(this.protocol.attributes.urls.versions_modal_url, (data) => {
|
||||
let versionsModal = '#protocol-versions-modal'
|
||||
$('.protocols-show').append($.parseHTML(data.html));
|
||||
$(versionsModal).modal('show');
|
||||
inlineEditing.init();
|
||||
$(versionsModal).find('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
// Remove modal when it gets closed
|
||||
$(versionsModal).on('hidden.bs.modal', () => {
|
||||
$(versionsModal).remove();
|
||||
});
|
||||
});
|
||||
}
|
||||
HelperModule.flashAlertMsg(message);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateKeywords(keywords) {
|
||||
$.ajax({
|
||||
type: 'PATCH',
|
||||
url: this.protocol.attributes.urls.update_protocol_keywords_url,
|
||||
data: { keywords },
|
||||
success: (result) => {
|
||||
this.$emit('update', result.data.attributes);
|
||||
}
|
||||
});
|
||||
},
|
||||
openVersionsModal() {
|
||||
this.VersionsModalObject = {
|
||||
id: this.protocol.id,
|
||||
urls: {
|
||||
versions_modal: this.protocol.attributes.urls.versions_modal
|
||||
}
|
||||
};
|
||||
},
|
||||
reloadPage() {
|
||||
window.location.reload();
|
||||
},
|
||||
redirectToProtocols() {
|
||||
window.location.href = this.protocol.attributes.urls.redirect_to_protocols;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
104
app/javascript/vue/protocols/modals/linked_my_modules.vue
Normal file
104
app/javascript/vue/protocols/modals/linked_my_modules.vue
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block">
|
||||
{{ i18n.t('protocols.index.linked_children.title', { protocol: protocol.name }) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="max-h-96 overflow-y-auto">
|
||||
<div v-for="myModule in linkedMyModules" class="flex items-center gap-2 px-3 py-2">
|
||||
<a :href="myModule.project_url"
|
||||
:title="myModule.project_name"
|
||||
class="hover:no-underline flex items-center gap-1 shrink-0">
|
||||
<i class="sn-icon sn-icon-projects"></i>
|
||||
<span class="truncate max-w-[160px]">{{ myModule.project_name }}</span>
|
||||
</a>
|
||||
<span>/</span>
|
||||
<a :href="myModule.experiment_url"
|
||||
:title="myModule.experiment_name"
|
||||
class="hover:no-underline flex items-center gap-1 shrink-0">
|
||||
<i class="sn-icon sn-icon-experiment"></i>
|
||||
<span class="truncate max-w-[160px]">{{ myModule.experiment_name }}</span>
|
||||
</a>
|
||||
<span>/</span>
|
||||
<a :href="myModule.my_module_url"
|
||||
:title="myModule.my_module_name"
|
||||
class="hover:no-underline flex items-center gap-1 shrink-0">
|
||||
<i class="sn-icon sn-icon-task"></i>
|
||||
<span class="truncate max-w-[160px]">{{ myModule.my_module_name }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer items-center">
|
||||
{{ i18n.t("protocols.index.linked_children.show_version") }}
|
||||
<div class="w-48 mr-auto">
|
||||
<SelectDropdown
|
||||
:options="versionsList"
|
||||
:value="selectedVersion"
|
||||
@change="changeSelectedVersion"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'NewProtocolModal',
|
||||
props: {
|
||||
protocol: Object
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
linkedMyModules: [],
|
||||
versionsList: [],
|
||||
selectedVersion: 'All'
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadLinkedMyModules();
|
||||
this.loadVersions();
|
||||
},
|
||||
methods: {
|
||||
loadLinkedMyModules() {
|
||||
const urlParams = {};
|
||||
if (this.selectedVersion !== 'All') {
|
||||
urlParams.version = this.selectedVersion;
|
||||
}
|
||||
axios.get(this.protocol.urls.linked_my_modules, { params: urlParams })
|
||||
.then((response) => {
|
||||
this.linkedMyModules = response.data;
|
||||
});
|
||||
},
|
||||
loadVersions() {
|
||||
axios.get(this.protocol.urls.versions_list)
|
||||
.then((response) => {
|
||||
this.versionsList = [['All', 'All']].concat(response.data.versions.map((version) => [version, version]));
|
||||
});
|
||||
},
|
||||
changeSelectedVersion(version) {
|
||||
this.selectedVersion = version;
|
||||
this.loadLinkedMyModules();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
90
app/javascript/vue/protocols/modals/new.vue
Normal file
90
app/javascript/vue/protocols/modals/new.vue
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
{{ i18n.t("protocols.new_protocol_modal.title_new") }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.name_label") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name"
|
||||
class="sci-input-field"
|
||||
autofocus="true"
|
||||
:placeholder="i18n.t('protocols.new_protocol_modal.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 text-xs items-center">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible"/>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<span v-html="i18n.t('protocols.new_protocol_modal.access_label')"></span>
|
||||
</div>
|
||||
<div class="mt-6" :class="{'hidden': !visible}">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.role_label") }}</label>
|
||||
<SelectDropdown :optionsUrl="userRolesUrl" :value="defaultRole" @change="changeRole" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" @click="submit" type="submit">
|
||||
{{ i18n.t('protocols.new_protocol_modal.create_new') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'NewProtocolModal',
|
||||
props: {
|
||||
createUrl: String,
|
||||
userRolesUrl: String
|
||||
},
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
visible: false,
|
||||
defaultRole: null,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
axios.post(this.createUrl, {
|
||||
protocol: {
|
||||
name: this.name,
|
||||
visibility: (this.visible ? 'visible' : 'hidden'),
|
||||
default_public_user_role_id: this.defaultRole
|
||||
}
|
||||
}).then(() => {
|
||||
this.error = null;
|
||||
this.$emit('create');
|
||||
}).catch((error) => {
|
||||
this.error = error.response.data.name;
|
||||
});
|
||||
},
|
||||
changeRole(role) {
|
||||
this.defaultRole = role;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
181
app/javascript/vue/protocols/modals/versions.vue
Normal file
181
app/javascript/vue/protocols/modals/versions.vue
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block">
|
||||
{{ i18n.t('protocols.index.versions.title', { protocol: protocol.name }) }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="max-h-[400px] overflow-y-auto">
|
||||
<div v-if="draft">
|
||||
<div class="flex items-center gap-4">
|
||||
<a :href="draft.urls.show" class="hover:no-underline cursor-pointer shrink-0">
|
||||
<span v-if="draft.previous_number"
|
||||
v-html="i18n.t('protocols.index.versions.draft_html', {
|
||||
parent_version: draft.previous_number
|
||||
})"
|
||||
></span>
|
||||
<span v-else v-html="i18n.t('protocols.index.versions.first_draft_html')"></span>
|
||||
</a>
|
||||
<span class="text-xs" v-if="draft.modified_by">
|
||||
{{
|
||||
i18n.t('protocols.index.versions.draft_full_modification_info', {
|
||||
modified_on: draft.modified_on,
|
||||
modified_by: draft.modified_by
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span class="text-xs" v-else>
|
||||
{{
|
||||
i18n.t('protocols.index.versions.draft_update_modification_info', {
|
||||
modified_on: draft.modified_on
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<div class="flex items-center gap-2 ml-auto">
|
||||
<button v-if="draft.urls.publish" class="btn btn-light" @click="publishDraft">
|
||||
{{ i18n.t('protocols.index.versions.publish') }}
|
||||
</button>
|
||||
<button v-if="draft.urls.destroy" @click="destroyDraft" class="btn btn-light icon-btn">
|
||||
<i class="sn-icon sn-icon-delete"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<InlineEdit
|
||||
:class="{ 'pointer-events-none': !draft.urls.comment }"
|
||||
class="mb-4"
|
||||
:value="draft.comment"
|
||||
:characterLimit="10000"
|
||||
:placeholder="i18n.t('protocols.index.versions.comment_placeholder')"
|
||||
:allowBlank="true"
|
||||
:singleLine="false"
|
||||
:attributeName="`${i18n.t('Draft')} ${i18n.t('comment')}`"
|
||||
@update="updateComment"
|
||||
/>
|
||||
</div>
|
||||
<div v-for="version in publishedVersions" :key="version.number">
|
||||
<div class="flex items-center gap-4">
|
||||
<a :href="version.urls.show" class="hover:no-underline cursor-pointer shrink-0">
|
||||
<b>
|
||||
{{ i18n.t('protocols.index.versions.revision', { version: version.number }) }}
|
||||
</b>
|
||||
</a>
|
||||
<span class="text-xs">
|
||||
{{
|
||||
i18n.t('protocols.index.versions.revision_publishing_info', {
|
||||
published_on: version.published_on,
|
||||
published_by: version.published_by
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<button
|
||||
v-if="version.urls.save_as_draft"
|
||||
class="btn btn-light icon-btn ml-auto"
|
||||
:title="i18n.t('protocols.index.versions.save_as_draft')"
|
||||
@click="saveAsDraft(version.urls.save_as_draft)"
|
||||
:disabled="draft"
|
||||
>
|
||||
<i class="sn-icon sn-icon-duplicate"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
{{ version.comment }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ConfirmationModal
|
||||
:title="i18n.t('protocols.delete_draft_modal.title')"
|
||||
:description="`
|
||||
<p>${i18n.t('protocols.delete_draft_modal.description_1')}</p>
|
||||
<p><b>${i18n.t('protocols.delete_draft_modal.description_2')}</b></p>
|
||||
`"
|
||||
:confirmClass="'btn btn-danger'"
|
||||
:confirmText="i18n.t('protocols.delete_draft_modal.confirm')"
|
||||
ref="destroyModal"
|
||||
></ConfirmationModal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
/* global HelperModule */
|
||||
|
||||
import SelectDropdown from '../../shared/select_dropdown.vue';
|
||||
import InlineEdit from '../../shared/inline_edit.vue';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import modalMixin from '../../shared/modal_mixin';
|
||||
import ConfirmationModal from '../../shared/confirmation_modal.vue';
|
||||
|
||||
export default {
|
||||
name: 'VersionsModal',
|
||||
props: {
|
||||
protocol: Object
|
||||
},
|
||||
emits: ['refresh', 'close'],
|
||||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
InlineEdit,
|
||||
ConfirmationModal
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
draft: null,
|
||||
publishedVersions: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadModalData();
|
||||
},
|
||||
methods: {
|
||||
loadModalData() {
|
||||
axios.get(this.protocol.urls.versions_modal).then((response) => {
|
||||
this.publishedVersions = response.data.versions;
|
||||
this.draft = response.data.draft;
|
||||
});
|
||||
},
|
||||
updateComment(comment) {
|
||||
axios.put(this.draft.urls.comment, { protocol: { version_comment: comment } }).then(() => {
|
||||
this.draft.comment = comment;
|
||||
});
|
||||
},
|
||||
async destroyDraft() {
|
||||
const ok = await this.$refs.destroyModal.show();
|
||||
if (ok) {
|
||||
axios.post(this.draft.urls.destroy, {
|
||||
version_modal: true
|
||||
}).then((response) => {
|
||||
document.body.style.overflow = 'hidden';
|
||||
this.$emit('refresh');
|
||||
this.$emit('redirectToProtocols');
|
||||
this.loadModalData();
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
});
|
||||
} else {
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
},
|
||||
saveAsDraft(url) {
|
||||
axios.post(url).then((response) => {
|
||||
window.location.href = response.data.url;
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
publishDraft() {
|
||||
axios.post(this.draft.urls.publish).then(() => {
|
||||
this.loadModalData();
|
||||
this.$emit('reloadPage');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
23
app/javascript/vue/protocols/renderers/keywords.vue
Normal file
23
app/javascript/vue/protocols/renderers/keywords.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="flex items-center gap-2 truncate h-full">
|
||||
<div v-for="keyword in params.data.keywords" :key="keyword.id"
|
||||
@click="params.dtComponent.setSearchValue(keyword)"
|
||||
class="px-2 py-1 rounded-sm bg-sn-super-light-grey gap-1 leading-5 cursor-pointer hover:bg-sn-light-grey">
|
||||
{{ keyword }}
|
||||
</div>
|
||||
<div v-if="params.data.keywords.length === 0">
|
||||
—
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'KeywordsRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
22
app/javascript/vue/protocols/renderers/linked_my_modules.vue
Normal file
22
app/javascript/vue/protocols/renderers/linked_my_modules.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div v-if="params.data.linked_tasks > 0"
|
||||
@click.stop="params.dtComponent.$emit('linked_my_modules', {}, [params.data])"
|
||||
class="cursor-pointer text-sn-blue"
|
||||
>
|
||||
{{ params.data.linked_tasks }}
|
||||
</div>
|
||||
<div v-else>
|
||||
0
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LinkedMyModulesRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
33
app/javascript/vue/protocols/renderers/versions.vue
Normal file
33
app/javascript/vue/protocols/renderers/versions.vue
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div v-if="params.data.urls.show" class="flex items-center gap-2 text-sn-blue">
|
||||
<span class="cursor-pointer"
|
||||
@click.stop="params.dtComponent.$emit('versions', {}, [params.data])"
|
||||
v-if="params.data.nr_of_versions > 0">
|
||||
{{ params.data.nr_of_versions }}
|
||||
</span>
|
||||
<span v-if="params.data.nr_of_versions > 0 && params.data.has_draft">/</span>
|
||||
<a v-if="params.data.has_draft" :href="params.data.urls.show_draft" class="hover:no-underline">
|
||||
{{ i18n.t("protocols.index.table.draft") }}
|
||||
</a>
|
||||
</div>
|
||||
<div v-else class="flex items-center gap-2 text-sn-grey">
|
||||
<span v-if="params.data.nr_of_versions > 0">
|
||||
{{ params.data.nr_of_versions }}
|
||||
</span>
|
||||
<span v-if="params.data.nr_of_versions > 0 && params.data.has_draft">/</span>
|
||||
<span v-if="params.data.has_draft" class="hover:no-underline ">
|
||||
{{ i18n.t("protocols.index.table.draft") }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'VersionsRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
298
app/javascript/vue/protocols/table.vue
Normal file
298
app/javascript/vue/protocols/table.vue
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
<template>
|
||||
<div class="h-full">
|
||||
<DataTable :columnDefs="columnDefs"
|
||||
:tableId="'protocolTemplates'"
|
||||
:dataUrl="dataSource"
|
||||
:reloadingTable="reloadingTable"
|
||||
:currentViewMode="currentViewMode"
|
||||
:toolbarActions="toolbarActions"
|
||||
:activePageUrl="activePageUrl"
|
||||
:archivedPageUrl="archivedPageUrl"
|
||||
:actionsUrl="actionsUrl"
|
||||
@create="create"
|
||||
@archive="archive"
|
||||
@restore="restore"
|
||||
@export="exportProtocol"
|
||||
@duplicate="duplicate"
|
||||
@versions="versions"
|
||||
@tableReloaded="reloadingTable = false"
|
||||
@import_file="importFile"
|
||||
@import_protocols_io="importProtocolsIo"
|
||||
@import_docx="importDocx"
|
||||
@access="access"
|
||||
@linked_my_modules="linkedMyModules"
|
||||
/>
|
||||
</div>
|
||||
<NewProtocolModal v-if="newProtocol" :createUrl="createUrl"
|
||||
:userRolesUrl="userRolesUrl"
|
||||
@close="newProtocol = false" @create="updateTable" />
|
||||
<AccessModal v-if="accessModalParams" :params="accessModalParams"
|
||||
@close="accessModalParams = null" @refresh="this.reloadingTable = true" />
|
||||
<LinkedMyModulesModal v-if="linkedMyModulesModalObject" :protocol="linkedMyModulesModalObject"
|
||||
@close="linkedMyModulesModalObject = null"/>
|
||||
<VersionsModal v-if="VersionsModalObject" :protocol="VersionsModalObject"
|
||||
@close="VersionsModalObject = null" @refresh="this.reloadingTable = true"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
|
||||
import DataTable from '../shared/datatable/table.vue';
|
||||
import UsersRenderer from '../projects/renderers/users.vue';
|
||||
import NewProtocolModal from './modals/new.vue';
|
||||
import AccessModal from '../shared/access_modal/modal.vue';
|
||||
import KeywordsRenderer from './renderers/keywords.vue';
|
||||
import LinkedMyModulesRenderer from './renderers/linked_my_modules.vue';
|
||||
import LinkedMyModulesModal from './modals/linked_my_modules.vue';
|
||||
import VersionsRenderer from './renderers/versions.vue';
|
||||
import VersionsModal from './modals/versions.vue';
|
||||
|
||||
export default {
|
||||
name: 'LabelTemplatesTable',
|
||||
components: {
|
||||
DataTable,
|
||||
UsersRenderer,
|
||||
NewProtocolModal,
|
||||
AccessModal,
|
||||
KeywordsRenderer,
|
||||
LinkedMyModulesRenderer,
|
||||
LinkedMyModulesModal,
|
||||
VersionsRenderer,
|
||||
VersionsModal
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
actionsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createUrl: {
|
||||
type: String
|
||||
},
|
||||
currentViewMode: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
docxParserEnabled: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
activePageUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
archivedPageUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
userRolesUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reloadingTable: false,
|
||||
newProtocol: false,
|
||||
accessModalParams: null,
|
||||
linkedMyModulesModalObject: null,
|
||||
VersionsModalObject: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
columnDefs() {
|
||||
const columns = [{
|
||||
field: 'name',
|
||||
headerName: this.i18n.t('protocols.index.thead.name'),
|
||||
sortable: true,
|
||||
notSelectable: true,
|
||||
cellRenderer: this.nameRenderer
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
headerName: this.i18n.t('protocols.index.thead.id'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'versions',
|
||||
headerName: this.i18n.t('protocols.index.thead.versions'),
|
||||
sortable: true,
|
||||
cellRenderer: 'VersionsRenderer',
|
||||
notSelectable: true
|
||||
},
|
||||
{
|
||||
field: 'keywords',
|
||||
headerName: this.i18n.t('protocols.index.thead.keywords'),
|
||||
sortable: true,
|
||||
cellRenderer: 'KeywordsRenderer',
|
||||
notSelectable: true
|
||||
},
|
||||
{
|
||||
field: 'linked_tasks',
|
||||
headerName: this.i18n.t('protocols.index.thead.nr_of_linked_children'),
|
||||
sortable: true,
|
||||
cellRenderer: 'LinkedMyModulesRenderer'
|
||||
},
|
||||
{
|
||||
field: 'assigned_users',
|
||||
headerName: this.i18n.t('protocols.index.thead.access'),
|
||||
sortable: true,
|
||||
cellRenderer: 'UsersRenderer',
|
||||
minWidth: 210,
|
||||
notSelectable: true
|
||||
},
|
||||
{
|
||||
field: 'published_by',
|
||||
headerName: this.i18n.t('protocols.index.thead.published_by'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'published_on',
|
||||
headerName: this.i18n.t('protocols.index.thead.published_on'),
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
field: 'updated_at',
|
||||
headerName: this.i18n.t('protocols.index.thead.updated_at'),
|
||||
sortable: true
|
||||
}];
|
||||
|
||||
if (this.currentViewMode === 'archived') {
|
||||
columns.push({
|
||||
field: 'archived_by',
|
||||
headerName: this.i18n.t('protocols.index.thead.archived_by'),
|
||||
sortable: true
|
||||
});
|
||||
columns.push({
|
||||
field: 'archived_on',
|
||||
headerName: this.i18n.t('protocols.index.thead.archived_on'),
|
||||
sortable: true
|
||||
});
|
||||
}
|
||||
|
||||
return columns;
|
||||
},
|
||||
toolbarActions() {
|
||||
const left = [];
|
||||
if (this.createUrl) {
|
||||
left.push({
|
||||
name: 'create',
|
||||
icon: 'sn-icon sn-icon-new-task',
|
||||
label: this.i18n.t('protocols.index.create_new'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary'
|
||||
});
|
||||
const importMenu = {
|
||||
name: 'import',
|
||||
icon: 'sn-icon sn-icon-import',
|
||||
label: this.i18n.t('protocols.index.import'),
|
||||
type: 'menu',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-light',
|
||||
menuItems: [
|
||||
{
|
||||
emit: 'import_file',
|
||||
text: this.i18n.t('protocols.index.import_eln')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (this.docxParserEnabled) {
|
||||
importMenu.menuItems.push({
|
||||
emit: 'import_docx',
|
||||
text: this.i18n.t('protocols.index.import_docx')
|
||||
});
|
||||
}
|
||||
|
||||
importMenu.menuItems.push({
|
||||
emit: 'import_protocols_io',
|
||||
text: this.i18n.t('protocols.index.import_protocols_io')
|
||||
});
|
||||
|
||||
left.push(importMenu);
|
||||
}
|
||||
return {
|
||||
left,
|
||||
right: []
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateTable() {
|
||||
this.newProtocol = false;
|
||||
this.reloadingTable = true;
|
||||
},
|
||||
create() {
|
||||
this.newProtocol = true;
|
||||
},
|
||||
duplicate(event, rows) {
|
||||
axios.post(event.path, { protocol_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.updateTable();
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
versions(_event, rows) {
|
||||
[this.VersionsModalObject] = rows;
|
||||
},
|
||||
archive(event, rows) {
|
||||
axios.post(event.path, { protocol_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.updateTable();
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
restore(event, rows) {
|
||||
axios.post(event.path, { protocol_ids: rows.map((row) => row.id) }).then((response) => {
|
||||
this.updateTable();
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
}).catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.error, 'danger');
|
||||
});
|
||||
},
|
||||
exportProtocol(event) {
|
||||
const link = document.createElement('a');
|
||||
link.href = event.path;
|
||||
link.click();
|
||||
},
|
||||
importFile() {
|
||||
const fileInput = document.querySelector('#importFileInput');
|
||||
fileInput.click();
|
||||
},
|
||||
importProtocolsIo() {
|
||||
const protocolIoButton = document.querySelector('#importProtocolsIo');
|
||||
protocolIoButton.click();
|
||||
},
|
||||
importDocx() {
|
||||
const docxButton = document.querySelector('#importDocx');
|
||||
docxButton.click();
|
||||
},
|
||||
access(_event, rows) {
|
||||
this.accessModalParams = {
|
||||
object: rows[0],
|
||||
roles_path: this.userRolesUrl
|
||||
};
|
||||
},
|
||||
linkedMyModules(_event, rows) {
|
||||
[this.linkedMyModulesModalObject] = rows;
|
||||
},
|
||||
// renderers
|
||||
nameRenderer(params) {
|
||||
const { urls, name } = params.data;
|
||||
if (urls.show) {
|
||||
return `<a href="${urls.show}">${name}</a>`;
|
||||
}
|
||||
return `<span class="text-sn-grey">${name}</span>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
|
@ -1,18 +1,30 @@
|
|||
<template>
|
||||
<div class="flex py-4 items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<a v-for="action in toolbarActions.left" :key="action.label"
|
||||
:class="action.buttonStyle"
|
||||
:href="action.path"
|
||||
@click="doAction(action, $event)">
|
||||
<i :class="action.icon"></i>
|
||||
{{ action.label }}
|
||||
</a>
|
||||
<template v-for="action in toolbarActions.left" :key="action.label">
|
||||
<a v-if="action.type === 'emit'"
|
||||
:class="action.buttonStyle"
|
||||
:href="action.path"
|
||||
@click="doAction(action, $event)">
|
||||
<i :class="action.icon"></i>
|
||||
{{ action.label }}
|
||||
</a>
|
||||
<MenuDropdown
|
||||
v-if="action.type === 'menu'"
|
||||
:listItems="action.menuItems"
|
||||
:btnClasses="action.buttonStyle"
|
||||
:btnText="action.label"
|
||||
:btnIcon="action.icon"
|
||||
:caret="true"
|
||||
:position="'right'"
|
||||
@dtEvent="handleEvent"
|
||||
></MenuDropdown>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<MenuDropdown
|
||||
v-if="archivedPageUrl"
|
||||
v-if="viewRenders"
|
||||
:listItems="this.viewRendersMenu"
|
||||
:btnClasses="'btn btn-light icon-btn'"
|
||||
:btnText="i18n.t(`toolbar.${currentViewRender}_view`)"
|
||||
|
|
@ -59,7 +71,9 @@
|
|||
:placeholder="'Search...'"
|
||||
@change="$emit('search:change', $event.target.value)"
|
||||
/>
|
||||
<i class="sn-icon sn-icon-search !m-2.5 !ml-auto right-0"></i>
|
||||
<i v-if="searchValue.length === 0" class="sn-icon sn-icon-search !m-2.5 !ml-auto right-0"></i>
|
||||
<i v-else class="sn-icon sn-icon-close !m-2.5 !ml-auto right-0 cursor-pointer z-10"
|
||||
@click="$emit('search:change', '')"></i>
|
||||
</div>
|
||||
<FilterDropdown v-else :filters="filters" @applyFilters="applyFilters" />
|
||||
</div>
|
||||
|
|
@ -105,7 +119,6 @@ export default {
|
|||
default: () => []
|
||||
},
|
||||
viewRenders: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
currentViewRender: {
|
||||
|
|
@ -184,6 +197,9 @@ export default {
|
|||
},
|
||||
applyFilters(filters) {
|
||||
this.$emit('applyFilters', filters);
|
||||
},
|
||||
handleEvent(event) {
|
||||
this.$emit('toolbar:action', { name: event });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ class Protocol < ApplicationRecord
|
|||
dependent: :destroy
|
||||
has_many :protocol_keywords, through: :protocol_protocol_keywords
|
||||
has_many :steps, inverse_of: :protocol, dependent: :destroy
|
||||
has_many :users, through: :user_assignments
|
||||
|
||||
def self.search(user,
|
||||
include_archived,
|
||||
|
|
|
|||
104
app/serializers/lists/protocol_serializer.rb
Normal file
104
app/serializers/lists/protocol_serializer.rb
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Lists
|
||||
class ProtocolSerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :name, :code, :keywords, :linked_tasks, :nr_of_versions, :assigned_users, :published_by,
|
||||
:published_on, :updated_at, :archived_by, :archived_on, :urls, :default_public_user_role_id,
|
||||
:hidden, :top_level_assignable, :has_draft, :team
|
||||
|
||||
def keywords
|
||||
object.protocol_keywords.map(&:name)
|
||||
end
|
||||
|
||||
def team
|
||||
object.team.name
|
||||
end
|
||||
|
||||
def linked_tasks
|
||||
object.nr_of_linked_tasks
|
||||
end
|
||||
|
||||
def assigned_users
|
||||
object.user_assignments.map do |ua|
|
||||
{
|
||||
avatar: avatar_path(ua.user, :icon_small),
|
||||
full_name: ua.user_name_with_role
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def has_draft
|
||||
if object.in_repository_published_original? || object.in_repository_published_version?
|
||||
parent = object.parent || object
|
||||
parent.draft.present?
|
||||
else
|
||||
object.in_repository_draft?
|
||||
end
|
||||
end
|
||||
|
||||
def published_by
|
||||
object.published_by&.full_name
|
||||
end
|
||||
|
||||
def published_on
|
||||
I18n.l(object.published_on, format: :full) if object.published_on
|
||||
end
|
||||
|
||||
def updated_at
|
||||
I18n.l(object.updated_at, format: :full) if object.updated_at
|
||||
end
|
||||
|
||||
def archived_by
|
||||
object.archived_by&.full_name
|
||||
end
|
||||
|
||||
def archived_on
|
||||
I18n.l(object.archived_on, format: :full) if object.archived_on
|
||||
end
|
||||
|
||||
delegate :default_public_user_role_id, to: :object
|
||||
|
||||
def hidden
|
||||
object.hidden?
|
||||
end
|
||||
|
||||
def top_level_assignable
|
||||
true
|
||||
end
|
||||
|
||||
def urls
|
||||
urls_list = {
|
||||
show_access: access_permissions_protocol_path(object),
|
||||
versions_list: versions_list_protocol_path(object),
|
||||
linked_my_modules: linked_children_protocol_path(object.parent || object),
|
||||
versions_modal: versions_modal_protocol_path(object.parent || object)
|
||||
}
|
||||
|
||||
if can_read_protocol_in_repository?(object)
|
||||
urls_list[:show] = if object.in_repository_published_original? && object.latest_published_version.present?
|
||||
protocol_path(object.latest_published_version)
|
||||
else
|
||||
protocol_path(object)
|
||||
end
|
||||
end
|
||||
|
||||
if has_draft
|
||||
object.initial_draft? ? object : object.draft
|
||||
urls_list[:show_draft] = protocol_path(object)
|
||||
end
|
||||
|
||||
if can_manage_protocol_users?(object)
|
||||
urls_list[:update_access] = access_permissions_protocol_path(object)
|
||||
urls_list[:new_access] = new_access_permissions_protocol_path(id: object.id)
|
||||
urls_list[:create_access] = access_permissions_protocols_path(id: object.id)
|
||||
urls_list[:default_public_user_role_path] =
|
||||
update_default_public_user_role_access_permissions_protocol_path(object)
|
||||
end
|
||||
|
||||
urls_list
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/serializers/protocol_draft_serializer.rb
Normal file
43
app/serializers/protocol_draft_serializer.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProtocolDraftSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
attributes :id, :previous_number, :modified_on, :modified_by, :comment, :urls
|
||||
|
||||
def previous_number
|
||||
object.previous_version&.version_number
|
||||
end
|
||||
|
||||
def modified_on
|
||||
I18n.l(object.updated_at, format: :full_date) if object.updated_at
|
||||
end
|
||||
|
||||
def modified_by
|
||||
object.last_modified_by&.full_name
|
||||
end
|
||||
|
||||
def comment
|
||||
object.version_comment
|
||||
end
|
||||
|
||||
def urls
|
||||
current_user = scope
|
||||
urls_list = {
|
||||
show: protocol_path(object)
|
||||
}
|
||||
|
||||
urls_list[:publish] = publish_protocol_path(object) if can_publish_protocol_in_repository?(current_user, object)
|
||||
if can_delete_protocol_draft_in_repository?(current_user, object)
|
||||
urls_list[:destroy] = destroy_draft_protocol_path(object)
|
||||
end
|
||||
|
||||
if can_manage_protocol_draft_in_repository?(current_user, object) &&
|
||||
can_publish_protocol_in_repository?(current_user, object)
|
||||
urls_list[:comment] = update_version_comment_protocol_path(object)
|
||||
end
|
||||
|
||||
urls_list
|
||||
end
|
||||
end
|
||||
|
|
@ -96,7 +96,9 @@ class ProtocolSerializer < ActiveModel::Serializer
|
|||
save_as_draft_url: save_as_draft_url,
|
||||
versions_modal_url: versions_modal_url,
|
||||
version_comment_url: version_comment_url,
|
||||
print_protocol_url: print_protocol_url
|
||||
print_protocol_url: print_protocol_url,
|
||||
versions_modal: versions_modal_protocol_path(object.parent || object),
|
||||
redirect_to_protocols: protocols_path
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
36
app/serializers/protocol_version_serializer.rb
Normal file
36
app/serializers/protocol_version_serializer.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProtocolVersionSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
|
||||
attributes :id, :number, :published_on, :published_by, :comment, :urls
|
||||
|
||||
def urls
|
||||
current_user = scope
|
||||
urls_list = {
|
||||
show: protocol_path(object)
|
||||
}
|
||||
if can_save_protocol_version_as_draft?(current_user, object)
|
||||
urls_list[:save_as_draft] = save_as_draft_protocol_path(object)
|
||||
end
|
||||
|
||||
urls_list
|
||||
end
|
||||
|
||||
def number
|
||||
object.version_number
|
||||
end
|
||||
|
||||
def published_on
|
||||
I18n.l(object.published_on, format: :full_date) if object.published_on
|
||||
end
|
||||
|
||||
def published_by
|
||||
object.published_by&.full_name
|
||||
end
|
||||
|
||||
def comment
|
||||
object.version_comment
|
||||
end
|
||||
end
|
||||
|
|
@ -47,6 +47,8 @@ class UserAssignmentSerializer < ActiveModel::Serializer
|
|||
parent.user_assignments.find_by(user: object.user)
|
||||
when Experiment
|
||||
parent_assignment(parent.permission_parent)
|
||||
when Team
|
||||
object
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
88
app/services/lists/protocols_service.rb
Normal file
88
app/services/lists/protocols_service.rb
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Lists
|
||||
class ProtocolsService < BaseService
|
||||
PREFIXED_ID_SQL = "('#{Protocol::ID_PREFIX}' || COALESCE(\"protocols\".\"parent_id\", \"protocols\".\"id\"))".freeze
|
||||
|
||||
private
|
||||
|
||||
def fetch_records
|
||||
@records = @raw_data.preload(:parent, :latest_published_version, :draft,
|
||||
:protocol_keywords, user_assignments: %i(user user_role))
|
||||
.joins("LEFT OUTER JOIN protocols protocol_versions " \
|
||||
"ON protocol_versions.protocol_type = #{
|
||||
Protocol.protocol_types[:in_repository_published_version]} " \
|
||||
"AND protocol_versions.parent_id = protocols.parent_id")
|
||||
.joins("LEFT OUTER JOIN protocols self_linked_task_protocols " \
|
||||
"ON self_linked_task_protocols.protocol_type = #{
|
||||
Protocol.protocol_types[:linked]} " \
|
||||
"AND self_linked_task_protocols.parent_id = protocols.id")
|
||||
.joins("LEFT OUTER JOIN protocols parent_linked_task_protocols " \
|
||||
"ON parent_linked_task_protocols.protocol_type = #{
|
||||
Protocol.protocol_types[:linked]} " \
|
||||
"AND parent_linked_task_protocols.parent_id = protocols.parent_id")
|
||||
.joins("LEFT OUTER JOIN protocols version_linked_task_protocols " \
|
||||
"ON version_linked_task_protocols.protocol_type = #{
|
||||
Protocol.protocol_types[:linked]} " \
|
||||
"AND version_linked_task_protocols.parent_id = protocol_versions.id " \
|
||||
"AND version_linked_task_protocols.parent_id != protocols.id")
|
||||
.joins('LEFT OUTER JOIN "protocol_protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_id" = "protocols"."id"')
|
||||
.joins('LEFT OUTER JOIN "protocol_keywords" ' \
|
||||
'ON "protocol_protocol_keywords"."protocol_keyword_id" = "protocol_keywords"."id"')
|
||||
.joins('LEFT OUTER JOIN "users" "archived_users"
|
||||
ON "archived_users"."id" = "protocols"."archived_by_id"')
|
||||
.joins('LEFT OUTER JOIN "users" ON "users"."id" = "protocols"."published_by_id"')
|
||||
.joins('LEFT OUTER JOIN "user_assignments" "all_user_assignments" ' \
|
||||
'ON "all_user_assignments"."assignable_type" = \'Protocol\' ' \
|
||||
'AND "all_user_assignments"."assignable_id" = "protocols"."id"')
|
||||
.group('"protocols"."id"')
|
||||
.select(
|
||||
'"protocols".*',
|
||||
'COALESCE("protocols"."parent_id", "protocols"."id") AS adjusted_parent_id',
|
||||
'STRING_AGG(DISTINCT("protocol_keywords"."name"), \', \') AS "protocol_keywords_str"',
|
||||
"CASE WHEN protocols.protocol_type = #{Protocol.protocol_types[:in_repository_draft]} " \
|
||||
"THEN 0 ELSE COUNT(DISTINCT(\"protocol_versions\".\"id\")) + 1 " \
|
||||
"END AS nr_of_versions",
|
||||
'(COUNT(DISTINCT("self_linked_task_protocols"."id")) + ' \
|
||||
'COUNT(DISTINCT("parent_linked_task_protocols"."id")) + ' \
|
||||
'COUNT(DISTINCT("version_linked_task_protocols"."id"))) AS nr_of_linked_tasks',
|
||||
'COUNT(DISTINCT("all_user_assignments"."id")) AS "nr_of_assigned_users"',
|
||||
'MAX("users"."full_name") AS "full_username_str"', # "Hack" to get single username
|
||||
'MAX("archived_users"."full_name") AS "archived_full_username_str"'
|
||||
)
|
||||
|
||||
view_mode = @params[:view_mode] || 'active'
|
||||
|
||||
@records = @records.archived if view_mode == 'archived'
|
||||
@records = @records.active if view_mode == 'active'
|
||||
end
|
||||
|
||||
def filter_records
|
||||
return if @params[:search].blank?
|
||||
|
||||
@records = @records.where(
|
||||
"LOWER(\"protocols\".\"name\") LIKE :search OR
|
||||
LOWER(\"protocol_keywords\".\"name\") LIKE :search OR
|
||||
LOWER(#{PREFIXED_ID_SQL}) LIKE :search",
|
||||
search: "%#{@params[:search].to_s.downcase}%"
|
||||
)
|
||||
end
|
||||
|
||||
def sortable_columns
|
||||
@sortable_columns ||= {
|
||||
name: 'name',
|
||||
parent_id: 'adjusted_parent_id',
|
||||
versions: 'nr_of_versions',
|
||||
keywords: 'protocol_keywords_str',
|
||||
linked_tasks: 'nr_of_linked_tasks',
|
||||
assigned_users: 'nr_of_assigned_users',
|
||||
published_by: 'full_username_str',
|
||||
published_on: 'published_on',
|
||||
udpated_at: 'updated_at',
|
||||
archived_by: 'archived_full_username_str',
|
||||
archived_on: 'archived_on'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -39,8 +39,7 @@ module Toolbars
|
|||
name: 'versions',
|
||||
label: I18n.t('protocols.index.toolbar.versions'),
|
||||
icon: 'sn-icon sn-icon-versions',
|
||||
button_id: 'protocolVersions',
|
||||
type: :legacy
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -55,29 +54,18 @@ module Toolbars
|
|||
label: I18n.t('protocols.index.toolbar.duplicate'),
|
||||
icon: 'sn-icon sn-icon-duplicate',
|
||||
path: clone_protocols_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
def access_action
|
||||
return unless @single
|
||||
|
||||
protocol = @protocols.first
|
||||
|
||||
path = if can_manage_protocol_users?(protocol)
|
||||
edit_access_permissions_protocol_path(protocol)
|
||||
else
|
||||
access_permissions_protocol_path(protocol)
|
||||
end
|
||||
|
||||
{
|
||||
name: 'access',
|
||||
label: I18n.t('protocols.index.toolbar.access'),
|
||||
icon: 'sn-icon sn-icon-project-member-access',
|
||||
button_class: 'access-btn',
|
||||
path: path,
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -91,7 +79,7 @@ module Toolbars
|
|||
label: I18n.t('protocols.index.toolbar.export'),
|
||||
icon: 'sn-icon sn-icon-export',
|
||||
path: export_protocols_path(protocol_ids: @protocols.pluck(:id)),
|
||||
type: :download
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -103,8 +91,7 @@ module Toolbars
|
|||
label: I18n.t('protocols.index.toolbar.archive'),
|
||||
icon: 'sn-icon sn-icon-archive',
|
||||
path: archive_protocols_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -112,12 +99,11 @@ module Toolbars
|
|||
return unless @protocols.all? { |p| can_restore_protocol_in_repository?(p) }
|
||||
|
||||
{
|
||||
name: 'archive',
|
||||
name: 'restore',
|
||||
label: I18n.t('protocols.index.toolbar.restore'),
|
||||
icon: 'sn-icon sn-icon-restore',
|
||||
path: restore_protocols_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,42 +25,54 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="protocols-container">
|
||||
<%= render partial: "protocols/index/protocols_datatable" %>
|
||||
|
||||
<div id="actionToolbar" data-behaviour="vue">
|
||||
<action-toolbar actions-url="<%= actions_toolbar_protocols_url %>" />
|
||||
<div id="ProtocolsTable" class="fixed-content-body">
|
||||
<protocols-table
|
||||
ref="table"
|
||||
:actions-url="'<%= actions_toolbar_protocols_url %>'"
|
||||
:data-source="'<%= protocols_path(format: :json) %>'"
|
||||
:active-page-url="'<%= protocols_path %>'"
|
||||
:archived-page-url="'<%= protocols_path(view_mode: :archived) %>'"
|
||||
current-view-mode="<%= params[:view_mode] || :active %>"
|
||||
:docx-parser-enabled="<%= Protocol.docx_parser_enabled? %>"
|
||||
user-roles-url="<%= user_roles_projects_path %>"
|
||||
:create-url="'<%= protocols_path if can_create_protocols_in_repository?(current_team) %>'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= javascript_include_tag 'vue_protocols_list' %>
|
||||
|
||||
<!-- Legacy code -->
|
||||
<a class="btn-open-file tw-hidden" data-action='import' >
|
||||
<input type="file" ref="importFileBtn" value="" id="importFileInput" accept=".eln" data-role="import-file-input"
|
||||
data-team-id="<%= current_team.id %>" data-import-url="<%= import_protocols_path %>">
|
||||
</a>
|
||||
<a class="btn-open-file tw-hidden" data-action='import' >
|
||||
<input type="file" value="" id="importDocx" accept=".docx" data-role="import-file-input"
|
||||
data-team-id="<%= @current_team.id %>" data-import-url="<%= import_protocols_path %>">
|
||||
</a>
|
||||
<%= link_to t("protocols.index.import_protocols_io"), '',
|
||||
id: "importProtocolsIo",
|
||||
class: 'tw-hidden',
|
||||
data: { target: '#protocolsioModal', toggle: 'modal' } %>
|
||||
|
||||
<%= render partial: "protocols/index/protocolsio_modal" %>
|
||||
<div id="protocolsio-preview-modal-target"></div>
|
||||
<%= javascript_include_tag "protocols/index" %>
|
||||
<%= javascript_include_tag "handsontable.full" %>
|
||||
<%= render partial: "shared/formulas_libraries" %>
|
||||
|
||||
<div id="protocolFileImportModal">
|
||||
<protocol-file-import-modal
|
||||
import-url="<%= import_docx_protocols_path %>"
|
||||
protocol-template-table-url="<%= protocols_path %>"
|
||||
/>
|
||||
</div>
|
||||
<%= javascript_include_tag 'vue_protocol_file_import_modal' %>
|
||||
|
||||
<div id="protocolsio-preview-modal-target"></div>
|
||||
<%= render partial: "protocols/index/general_toolbar" %>
|
||||
<%= render partial: "protocols/index/protocol_filters" %>
|
||||
<%= render partial: "protocols/index/delete_draft_modal" %>
|
||||
<%= render partial: "protocols/index/linked_children_modal" %>
|
||||
<%= render partial: "protocols/index/protocol_preview_modal" %>
|
||||
<%= render partial: "protocols/index/protocolsio_modal" %>
|
||||
<%= render partial: "protocols/index/new_protocol_modal", locals: {type: 'new'} %>
|
||||
|
||||
<%= render partial: "protocols/import_export/import_elements" %>
|
||||
<%= javascript_include_tag "vue_components_action_toolbar" %>
|
||||
<%= javascript_include_tag "handsontable.full" %>
|
||||
|
||||
<!-- Libraries for formulas -->
|
||||
<%= render partial: "shared/formulas_libraries" %>
|
||||
|
||||
<%= stylesheet_link_tag 'datatables' %>
|
||||
<%= javascript_include_tag 'vue_protocol_file_import_modal' %>
|
||||
<%= javascript_include_tag "assets/wopi/create_wopi_file" %>
|
||||
<%= javascript_include_tag "protocols/index" %>
|
||||
<%= javascript_include_tag "protocols/new_protocol" %>
|
||||
<%= javascript_include_tag 'pdf_js' %>
|
||||
<%= stylesheet_link_tag 'pdf_js_styles' %>
|
||||
|
|
|
|||
|
|
@ -633,6 +633,7 @@ Rails.application.routes.draw do
|
|||
post :publish
|
||||
post :destroy_draft
|
||||
post :save_as_draft
|
||||
get :versions_list
|
||||
get 'version_comment', to: 'protocols#version_comment'
|
||||
get 'print', to: 'protocols#print'
|
||||
get 'linked_children', to: 'protocols#linked_children'
|
||||
|
|
|
|||
|
|
@ -51,8 +51,9 @@ const entryList = {
|
|||
vue_projects_list: './app/javascript/packs/vue/projects_list.js',
|
||||
vue_experiments_list: './app/javascript/packs/vue/experiments_list.js',
|
||||
vue_my_modules_list: './app/javascript/packs/vue/my_modules_list.js',
|
||||
vue_design_system_select: './app/javascript/packs/vue/design_system/select.js'
|
||||
}
|
||||
vue_design_system_select: './app/javascript/packs/vue/design_system/select.js',
|
||||
vue_protocols_list: './app/javascript/packs/vue/protocols_list.js'
|
||||
};
|
||||
|
||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||
// Get paths to all engines' folders
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue