diff --git a/app/assets/javascripts/samples/sample_datatable.js b/app/assets/javascripts/samples/sample_datatable.js index fe7725be7..362b90234 100644 --- a/app/assets/javascripts/samples/sample_datatable.js +++ b/app/assets/javascripts/samples/sample_datatable.js @@ -26,7 +26,8 @@ function dataTableInit() { processing: true, serverSide: true, colReorder: { - fixedColumnsLeft: 2 + fixedColumnsLeft: 2, + realtime: false }, destroy: true, ajax: { @@ -89,55 +90,54 @@ function dataTableInit() { }, preDrawCallback: function() { animateSpinner(this); - $(".sample_info").off("click"); + $('.sample_info').off('click'); }, - stateLoadCallback: function (settings) { + stateLoadCallback: function(settings) { // Send an Ajax request to the server to get the data. Note that // this is a synchronous request since the data is expected back from the // function - var org = $("#samples").attr("data-organization-id") - var user = $("#samples").attr("data-user-id") + var org = $('#samples').attr('data-organization-id'); + var user = $('#samples').attr('data-user-id'); - $.ajax( { - url: '/state_load/'+org+'/'+user, + $.ajax({ + url: '/state_load/' + org + '/' + user, data: {org: org}, async: false, - dataType: "json", - type: "POST", - success: function (json) { + dataType: 'json', + type: 'POST', + success: function(json) { myData = json.state; } - } ); - return myData + }); + return myData; }, - stateSaveCallback: function (settings, data) { + stateSaveCallback: function(settings, data) { // Send an Ajax request to the server with the state object - var org = $("#samples").attr("data-organization-id") - var user = $("#samples").attr("data-user-id") - + var org = $('#samples').attr('data-organization-id'); + var user = $('#samples').attr('data-user-id'); // Save correct data if (loadFirstTime == true) { data = myData; } - $.ajax( { - url: '/state_save/'+org+'/'+user, + $.ajax({ + url: '/state_save/' + org + '/' + user, data: {org: org, state: data}, - dataType: "json", - type: "POST" - } ); + dataType: 'json', + type: 'POST' + }); loadFirstTime = false; }, fnInitComplete: function(oSettings, json) { // Reload correct column order and visibility (if you refresh page) - oSettings._colReorder.fnOrder(myData.ColReorder); for (var i = 0; i < table.columns()[0].length; i++) { var visibility = myData.columns[i].visible; - if (typeof(visibility) === "string"){ - var visibility = (visibility === "true"); + if (typeof (visibility) === 'string') { + visibility = (visibility === 'true'); } table.column(i).visible(visibility); } + oSettings._colReorder.fnOrder(myData.ColReorder); } }); @@ -664,7 +664,7 @@ function onClickAddSample() { else if ($(th).attr("id") == "sample-type") { var colIndex = getColumnIndex("#sample-type") if (colIndex) { - var selectType = createSampleTypeSelect(data["sample_types"], -1); + var selectType = createSampleTypeSelect(data["sample_types"]); var td = createTdElement(""); td.appendChild(selectType[0]); tr.appendChild(td); @@ -673,7 +673,7 @@ function onClickAddSample() { else if ($(th).attr("id") == "sample-group") { var colIndex = getColumnIndex("#sample-group") if (colIndex) { - var selectGroup = createSampleGroupSelect(data["sample_groups"], -1); + var selectGroup = createSampleGroupSelect(data["sample_groups"]); var td = createTdElement(""); td.appendChild(selectGroup[0]); tr.appendChild(td); @@ -736,14 +736,21 @@ function createTdElement(content) { /** * Creates select dropdown for sample type - * @param data List of sample types - * @param selected Selected sample type id + * @param {Object[]} data List of sample types + * @param {number} selected Selected sample type id + * @return {Object} select dropdown */ function createSampleTypeSelect(data, selected) { + selected = _.isUndefined(selected) ? 1 : selected + 1; + var $selectType = $('') .attr('name', 'sample_type_id').addClass('show-tick'); - var $option = $('') + var $option = $("") + .attr('value', -2) + .text(I18n.t('samples.table.add_sample_type')); + $selectType.append($option); + $option = $('') .attr('value', -1).text(I18n.t('samples.table.no_type')) $selectType.append($option); @@ -752,7 +759,7 @@ function createSampleTypeSelect(data, selected) { .attr('value', val.id).text(val.name); $selectType.append($option); }); - $selectType.val(selected); + $selectType.makeDropdownOptionsLinks(selected, 'add-mode'); return $selectType; } @@ -762,11 +769,15 @@ function createSampleTypeSelect(data, selected) { * @param selected Selected sample group id */ function createSampleGroupSelect(data, selected) { + selected = _.isUndefined(selected) ? 1 : selected + 1; + var $selectGroup = $('') .attr('name', 'sample_group_id').addClass('show-tick'); - var $span = $("").addClass('glyphicon glyphicon-asterisk'); - var $option = $('') + var $option = $("") + .text(I18n.t('samples.table.add_sample_group')); + $selectGroup.append($option); + $option = $('') .attr('value', -1).text(I18n.t('samples.table.no_group')) .attr('data-icon', 'glyphicon glyphicon-asterisk'); $selectGroup.append($option); @@ -780,7 +791,7 @@ function createSampleGroupSelect(data, selected) { $selectGroup.append($option); }); - $selectGroup.val(selected); + $selectGroup.makeDropdownOptionsLinks(selected, 'add-mode'); return $selectGroup; } @@ -802,10 +813,15 @@ function changeToEditMode() { table.button(0).enable(false); } +/* + * Sample columns dropdown + */ (function(table) { 'use strict'; + var dropdown = $('#samples-columns-dropdown'); var dropdownList = $('#samples-columns-list'); + var columnEditMode = false; function createNewColumn() { // Make an Ajax request to custom_fields_controller @@ -837,11 +853,14 @@ function changeToEditMode() { $('#samples').data('num-columns', $('#samples').data('num-columns') + 1); originalHeader.append( - '' + + '' + data.name + ''); var colOrder = table.colReorder.order(); colOrder.push(colOrder.length); - table.colReorder.reset(); // Remove all event handlers as we re-initialize them later with // new table $('#samples').off(); @@ -852,8 +871,9 @@ function changeToEditMode() { $('div.toolbarButtons').appendTo('div.samples-table'); $('div.toolbarButtons').hide(); table = dataTableInit(); - table.colReorder.order(colOrder, true); - loadColumnsNames(); + table.on('init.dt', function() { + loadColumnsNames(); + }); }, url: url }); @@ -900,15 +920,34 @@ function changeToEditMode() { if (index > 1) { var colIndex = $(el).attr('data-column-index'); var visible = table.column(colIndex).visible(); + var editable = $(el).is('[data-editable]'); + var deletable = $(el).is('[data-deletable]'); + var visClass = (visible) ? 'glyphicon-eye-open' : 'glyphicon-eye-close'; var visLi = (visible) ? '' : 'col-invisible'; - var html = '
  • ' + - el.innerText + ' ' + - ' ' + - ' ' + - '' + - '
  • '; + var editClass = (editable) ? '' : 'disabled'; + var delClass = (deletable) ? '' : 'disabled'; + var html = + '
  • ' + + ' ' + + '' + el.innerText + ' ' + + '' + + '' + + '' + + '' + + ' ' + + '' + + ' ' + + '' + + '' + + '' + + '
  • '; dropdownList.append(html); } }); @@ -971,12 +1010,202 @@ function changeToEditMode() { }); } + function initEditColumns() { + function cancelEditMode() { + dropdownList.find('.text-edit').hide(); + dropdownList.find('.controls .ok,.cancel').hide(); + dropdownList.find('.text').css('display', ''); // show() doesn't work + dropdownList.find('.controls .vis,.edit,.del').css('display', ''); // show() doesn't work + columnEditMode = false; + } + + function editColumn(li) { + var id = li.attr('data-id'); + var text = li.find('.text'); + var textEdit = li.find('.text-edit'); + var newName = textEdit.val().trim(); + var url = li.attr('data-edit-url'); + + $.ajax({ + url: url, + type: 'PUT', + data: {custom_field: {name: newName}}, + dataType: 'json', + success: function() { + text.text(newName); + $(table.columns().header()).filter('#' + id).text(newName); + cancelEditMode(); + }, + error: function(xhr) { + // TODO + } + }); + } + + // On edit buttons click (bind onto master dropdown list) + dropdownList.on('click', '.edit:not(.disabled)', function(event) { + event.stopPropagation(); + + cancelEditMode(); + + var self = $(this); + var li = self.closest('li'); + var text = li.find('.text'); + var textEdit = li.find('.text-edit'); + var controls = li.find('.controls .vis,.edit,.del'); + var controlsEdit = li.find('.controls .ok,.cancel'); + + // Toggle edit mode + columnEditMode = true; + li.addClass('editing'); + + // Set the text-edit's value + textEdit.val(text.text().trim()); + + // Toggle elements + text.hide(); + controls.hide(); + textEdit.css('display', ''); // show() doesn't work + controlsEdit.css('display', ''); // show() doesn't work + + // Focus input + textEdit.focus(); + }); + + // On hiding dropdown, cancel edit mode throughout dropdown + dropdown.on('hidden.bs.dropdown', function() { + cancelEditMode(); + }); + + // On ok buttons click + dropdownList.on('click', '.ok', function(event) { + event.stopPropagation(); + var self = $(this); + var li = self.closest('li'); + editColumn(li); + }); + + // On enter click while editing column text + dropdownList.on('keydown', 'input.text-edit', function(event) { + if (event.keyCode === 13) { + event.preventDefault(); + var self = $(this); + var li = self.closest('li'); + editColumn(li); + } + }); + + // On cancel buttons click + dropdownList.on('click', '.cancel', function(event) { + event.stopPropagation(); + var self = $(this); + var li = self.closest('li'); + + columnEditMode = false; + li.removeClass('editing'); + + li.find('.text-edit').hide(); + li.find('.controls .ok,.cancel').hide(); + li.find('.text').css('display', ''); // show() doesn't work + li.find('.controls .vis,.edit,.del').css('display', ''); // show() doesn't work + }); + } + + function initDeleteColumns() { + var modal = $('#deleteCustomField'); + + dropdownList.on('click', '.del', function(event) { + event.stopPropagation(); + + var self = $(this); + var li = self.closest('li'); + var url = li.attr('data-destroy-html-url'); + + $.ajax({ + url: url, + type: 'GET', + dataType: 'json', + success: function(data) { + var modalBody = modal.find('.modal-body'); + + // Inject the body's HTML into modal + modalBody.html(data.html); + + // Show the modal + modal.modal('show'); + }, + error: function(xhr) { + // TODO + } + }); + }); + + modal.find('.modal-footer [data-action=delete]').on('click', function() { + var modalBody = modal.find('.modal-body'); + var form = modalBody.find('[data-role=destroy-custom-field-form]'); + var id = form.attr('data-id'); + + form + .on('ajax:success', function() { + // Destroy datatable + table.destroy(); + + // Subtract number of columns + $('#samples').data( + 'num-columns', + $('#samples').data('num-columns') - 1 + ); + + // Remove column from table (=table header) & rows + var th = originalHeader.find('#' + id); + var index = th.index(); + th.remove(); + $('#samples tbody td:nth-child(' + (index + 1) + ')').remove(); + + // Remove all event handlers as we re-initialize them later with + // new table + $('#samples').off(); + $('#samples thead').empty(); + $('#samples thead').append(originalHeader); + + // Preserve save/delete buttons as we need them after new table + // will be created + $('div.toolbarButtons').appendTo('div.samples-table'); + $('div.toolbarButtons').hide(); + + // Re-initialize datatable + table = dataTableInit(); + loadColumnsNames(); + + // Hide modal + modal.modal('hide'); + }) + .on('ajax:error', function() { + // TODO + }); + + form.submit(); + }); + + modal.on('hidden.bs.modal', function() { + // Remove event handlers, clear contents + var modalBody = modal.find('.modal-body'); + modalBody.off(); + modalBody.html(''); + }); + } + // initialze dropdown after the table is loaded function initDropdown() { table.on('init.dt', function() { initNewColumnForm(); - loadColumnsNames(); initSorting(); + toggleColumnVisibility(); + initEditColumns(); + initDeleteColumns(); + }); + $('#samples-columns-dropdown').on('show.bs.dropdown', function() { + loadColumnsNames(); }); } diff --git a/app/assets/javascripts/samples/sample_types_groups.js b/app/assets/javascripts/samples/sample_types_groups.js index a54d72fa4..9caad1f20 100644 --- a/app/assets/javascripts/samples/sample_types_groups.js +++ b/app/assets/javascripts/samples/sample_types_groups.js @@ -233,6 +233,16 @@ }); } +/** + * Opens adding mode when redirected from samples page, when clicking link for + * adding sample type or group link + */ + function sampleTypeGroupEditMode() { + if (getParam('add-mode')) { + $('#create-resource').click(); + } + } + function initSampleTypesGroups() { showNewSampleTypeGroupForm(); newSampleTypeFormCancel(); @@ -245,6 +255,7 @@ initSampleGroupColor(); bindNewSampleGroupAction(); appendCarretToColorPickerDropdown(); + sampleTypeGroupEditMode(); } // initialize sample types/groups actions diff --git a/app/assets/javascripts/samples/samples.js b/app/assets/javascripts/samples/samples.js index ca17e8200..e648e1872 100644 --- a/app/assets/javascripts/samples/samples.js +++ b/app/assets/javascripts/samples/samples.js @@ -1,28 +1,5 @@ //= require datatables -// Create custom field ajax -$("#modal-create-custom-field").on("show.bs.modal", function(event) { - // Clear input when modal is opened - input = $(this).find("input#name-input"); - input.val(""); - input.closest(".form-group").removeClass("has-error"); - input.closest(".form-group").find(".help-block").remove(); -}); -$("#modal-create-custom-field").on("shown.bs.modal", function(event) { - $(this).find("input#name-input").focus(); -}); - -$("form#new_custom_field").on("ajax:success", function(ev, data, status) { - $("#modal-create-custom-field").modal("hide"); - - // Reload page with URL parameter of newly created field - window.location.href = addParam(window.location.href, "new_col"); -}); - -$("form#new_custom_field").on("ajax:error", function(e, data, status, xhr) { - $('form').renderFormErrors('custom_field', data.responseJSON, true, e); -}); - // Create import samples ajax $("#modal-import-samples").on("show.bs.modal", function(event) { formGroup = $(this).find(".form-group"); diff --git a/app/assets/javascripts/sitewide/utils.js b/app/assets/javascripts/sitewide/utils.js index f42f515c2..50224bb57 100644 --- a/app/assets/javascripts/sitewide/utils.js +++ b/app/assets/javascripts/sitewide/utils.js @@ -215,3 +215,32 @@ function initPageTutorialSteps(pageFirstStepN, pageLastStepN, nextPagePath, }); } } + +/** + * Add redirection links on dropdown elements. You must specify 'href' + * attribute yourself, and the dropdown elments which don't have them, will get + * '#' by default. + * @param {number} selectedIdx Index of element to be selected + * @param {string} urlParam URL parameter to pass to the link URLs + * @return {Object} This + */ +$.fn.makeDropdownOptionsLinks = function(selectedIdx, urlParam) { + selectedIdx = _.isUndefined(selectedIdx) ? 1 : selectedIdx; + + $(this).change(function() { + window.location.href = addParam($(this).find('option:selected') + .attr('href'), urlParam); + }); + + $(this).find('option') + .each(function() { + if ($(this).is('[href]')) { + $(this).addClass('link-look'); + } else { + $(this).attr('href', '#'); + } + }) + .eq(selectedIdx).attr('selected', true); + + return this; +}; diff --git a/app/assets/stylesheets/constants.scss b/app/assets/stylesheets/constants.scss index 838ca5e31..b887e7c14 100644 --- a/app/assets/stylesheets/constants.scss +++ b/app/assets/stylesheets/constants.scss @@ -35,6 +35,9 @@ $color-mojo: #cf4b48; $color-apple-blossom: #a94442; $color-milano-red: #a70b05; +// Colors for specific intents +$color-visited-link: #23527c; + //============================================================================== // Other //============================================================================== diff --git a/app/assets/stylesheets/themes/scinote.scss b/app/assets/stylesheets/themes/scinote.scss index 9014f3ef1..cb1e0c311 100644 --- a/app/assets/stylesheets/themes/scinote.scss +++ b/app/assets/stylesheets/themes/scinote.scss @@ -235,6 +235,18 @@ body { a { color: $color-theme-primary; + + // Override to make link look and behave as a normal link (e.g. used for + // elements which have links) + &.link-look { + color: $color-theme-primary !important; + + :hover, + :focus { + color: $color-visited-link !important; + text-decoration: underline !important; + } + } } .jumbotron { @@ -1611,15 +1623,6 @@ textarea.textarea-sm { margin-left: 10px; } - .vis { - cursor: pointer; - margin-right: 5px; - } - - .edit { - margin-right: 5px; - } - .grippy { background-repeat: none; display: inline-block; @@ -1628,7 +1631,7 @@ textarea.textarea-sm { width: 10px; } - .grippy-img { + li:not(.editing) .grippy-img { background: url(asset-path('custom/grippy.png')); } @@ -1639,6 +1642,14 @@ textarea.textarea-sm { position: absolute; } + .text-edit { + display: inline-block; + margin-left: 2px; + margin-top: -2.5px; + position: absolute; + width: 180px; + } + .col-invisible { color: $color-alto; } @@ -1646,5 +1657,37 @@ textarea.textarea-sm { .controls { display: inline-block; margin-top: 5px; + + span { + cursor: pointer; + + &.disabled { + visibility: hidden; + } + + &:hover { + color: $color-emperor; + } + } + + .ok { + color: $color-theme-secondary; + margin-right: 5px; + } + + .cancel { + margin-right: 28px; + } + + .vis { + margin-right: 5px; + } + + .edit { + margin-right: 5px; + } + + .del { + } } } diff --git a/app/controllers/custom_fields_controller.rb b/app/controllers/custom_fields_controller.rb index 19b6d1dba..f549e60a2 100644 --- a/app/controllers/custom_fields_controller.rb +++ b/app/controllers/custom_fields_controller.rb @@ -1,6 +1,9 @@ class CustomFieldsController < ApplicationController - before_action :load_vars_nested, only: [:create] - before_action :check_create_permissions, only: [:create] + before_action :load_vars, only: [:update, :destroy, :destroy_html] + before_action :load_vars_nested, only: [:create, :destroy_html] + before_action :check_create_permissions, only: :create + before_action :check_update_permissions, only: :update + before_action :check_destroy_permissions, only: [:destroy, :destroy_html] def create @custom_field = CustomField.new(custom_field_params) @@ -9,12 +12,19 @@ class CustomFieldsController < ApplicationController respond_to do |format| if @custom_field.save - format.json { + format.json do render json: { id: @custom_field.id, - name: @custom_field.name + name: @custom_field.name, + edit_url: + organization_custom_field_path(@organization, @custom_field), + destroy_html_url: + organization_custom_field_destroy_html_path( + @organization, @custom_field + ) }, - status: :ok } + status: :ok + end else format.json do render json: @custom_field.errors.to_json, @@ -24,22 +34,71 @@ class CustomFieldsController < ApplicationController end end - private - - def load_vars_nested - @organization = Organization.find_by_id(params[:organization_id]) - - unless @organization - render_404 + def update + respond_to do |format| + format.json do + @custom_field.update_attributes(custom_field_params) + if @custom_field.save + render json: { status: :ok } + else + render json: @custom_field.errors.to_json, + status: :unprocessable_entity + end + end end end - def check_create_permissions - unless can_create_custom_field_in_organization(@organization) - render_403 + def destroy_html + respond_to do |format| + format.json do + render json: { + html: render_to_string( + partial: 'samples/delete_custom_field_modal_body.html.erb' + ) + } + end end end + def destroy + respond_to do |format| + format.json do + if @custom_field.destroy + render json: { status: :ok } + else + render json: { status: :unprocessable_entity } + end + end + end + end + + private + + def load_vars + @custom_field = CustomField.find_by_id(params[:id]) + @custom_field = CustomField.find_by_id( + params[:custom_field_id] + ) unless @custom_field + render_404 unless @custom_field + end + + def load_vars_nested + @organization = Organization.find_by_id(params[:organization_id]) + render_404 unless @organization + end + + def check_create_permissions + render_403 unless can_create_custom_field_in_organization(@organization) + end + + def check_update_permissions + render_403 unless can_edit_custom_field(@custom_field) + end + + def check_destroy_permissions + render_403 unless can_delete_custom_field(@custom_field) + end + def custom_field_params params.require(:custom_field).permit(:name) end diff --git a/app/helpers/permission_helper.rb b/app/helpers/permission_helper.rb index 43972e678..175afa9e7 100644 --- a/app/helpers/permission_helper.rb +++ b/app/helpers/permission_helper.rb @@ -674,6 +674,16 @@ module PermissionHelper is_normal_user_or_admin_of_organization(organization) end + def can_edit_custom_field(custom_field) + custom_field.user == current_user || + is_admin_of_organization(custom_field.organization) + end + + def can_delete_custom_field(custom_field) + custom_field.user == current_user || + is_admin_of_organization(custom_field.organization) + end + # ---- PROTOCOL PERMISSIONS ---- def can_view_organization_protocols(organization) diff --git a/app/models/custom_field.rb b/app/models/custom_field.rb index e06ac8baa..f43975f57 100644 --- a/app/models/custom_field.rb +++ b/app/models/custom_field.rb @@ -13,7 +13,7 @@ class CustomField < ActiveRecord::Base belongs_to :last_modified_by, foreign_key: 'last_modified_by_id', class_name: 'User' - has_many :sample_custom_fields, inverse_of: :custom_field + has_many :sample_custom_fields, inverse_of: :custom_field, dependent: :destroy after_create :update_samples_table_state diff --git a/app/models/samples_table.rb b/app/models/samples_table.rb index 5d4f92fde..40ce10181 100644 --- a/app/models/samples_table.rb +++ b/app/models/samples_table.rb @@ -14,7 +14,7 @@ class SamplesTable < ActiveRecord::Base index = org_status['columns'].count org_status['columns'][index] = SampleDatatable:: SAMPLES_TABLE_DEFAULT_STATE['columns'].first - org_status['ColReorder'] << index + org_status['ColReorder'] << index.to_s samples_table.first.update(status: org_status) end diff --git a/app/views/samples/_delete_custom_field_modal.html.erb b/app/views/samples/_delete_custom_field_modal.html.erb new file mode 100644 index 000000000..61ab9bc6d --- /dev/null +++ b/app/views/samples/_delete_custom_field_modal.html.erb @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/app/views/samples/_delete_custom_field_modal_body.html.erb b/app/views/samples/_delete_custom_field_modal_body.html.erb new file mode 100644 index 000000000..0df63cde8 --- /dev/null +++ b/app/views/samples/_delete_custom_field_modal_body.html.erb @@ -0,0 +1,12 @@ +<%= bootstrap_form_for @custom_field, url: organization_custom_field_path(@organization, @custom_field, format: :json), remote: :true, method: :delete, data: { role: "destroy-custom-field-form", id: @custom_field.id } do |f| %> +

    <%= t("samples.modal_delete_custom_field.message", cf: @custom_field.name) %>

    + +<% end %> \ No newline at end of file diff --git a/app/views/shared/_samples.html.erb b/app/views/shared/_samples.html.erb index 866101ed9..b25b3ad1e 100644 --- a/app/views/shared/_samples.html.erb +++ b/app/views/shared/_samples.html.erb @@ -1,5 +1,7 @@ <%= render partial: "samples/import_samples_modal" %> <%= render partial: "samples/delete_samples_modal" %> +<%= render partial: "samples/delete_custom_field_modal" %> + @@ -138,7 +140,15 @@ <%= t("samples.table.added_on") %> <%= t("samples.table.added_by") %> <% all_custom_fields.each do |cf| %> - <%= cf.name %> + + <%= 'data-deletable' if can_delete_custom_field(cf) %> + <%= "data-edit-url='#{organization_custom_field_path(@organization, cf)}'" %> + <%= "data-destroy-html-url='#{organization_custom_field_destroy_html_path(@organization, cf)}'" %> + > + <%= cf.name %> + <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index c02101663..4193789d4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -832,6 +832,13 @@ en: modal_add_custom_field: title_html: "Add new column to team %{organization}" create: "Add new column" + modal_delete_custom_field: + title: "Delete a column" + message: "Are you sure you wish to permanently delete selected column %{cf}? This action is irreversible." + alert_heading: "Deleting a column has following consequences:" + alert_line_1: "you will lose information in this column for %{nr} samples;" + alert_line_2: "the column will be deleted for all team members." + delete: "Delete column" modal_add_new_sample_group: title_html: "Add new sample group to team %{organization}" create: "Add new sample group" @@ -847,6 +854,8 @@ en: added_by: "Added by" no_group: "No sample group" no_type: "No sample type" + add_sample_type: "Add new sample type" + add_sample_group: "Add new sample group" new: head_title: "%{organization} | Add new sample" title: "Add new sample to team %{organization}" diff --git a/config/routes.rb b/config/routes.rb index 83f6e2158..8bf857f52 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -79,7 +79,9 @@ Rails.application.routes.draw do get 'sample_group_element', to: 'sample_groups#sample_group_element' get 'destroy_confirmation', to: 'sample_groups#destroy_confirmation' end - resources :custom_fields, only: [:create] + resources :custom_fields, only: [:create, :update, :destroy] do + get 'destroy_html' + end member do post 'parse_sheet' post 'import_samples'