adds sample groups index page

This commit is contained in:
zmagod 2016-12-02 09:33:09 +01:00
parent 1859df0319
commit 83aae59eed
20 changed files with 588 additions and 231 deletions

View file

@ -1,139 +0,0 @@
(function() {
'use strict';
function showNewSampleTypeForm() {
$('#create-sample-type').on('click', function() {
$('.new-sample-type-form').slideDown();
});
}
function newSampleTypeFormCancel() {
$('#remove').on('click', function() {
$('#name-input').val('');
$('.new-sample-type-form').slideUp();
});
}
function newSampleTypeFormSubmit() {
$('#submit').on('click', function() {
$('#new_sample_type').submit();
});
}
function editSampleTypeForm() {
$('.edit').on('click', function() {
var li = $(this).closest('li');
$.ajax({
url: li.attr('data-sample-type-edit'),
success: function(data) {
$(li).replaceWith($.parseHTML(data.html));
submitEditSampleTypeForm();
abortEditSampleTypeAction();
destroySampleType();
$('#edit_sample_type_' + data.id)
.bind('ajax:success', function(ev, data) {
$(this).closest('li').replaceWith($.parseHTML(data.html));
editSampleTypeForm();
destroySampleType();
}).bind('ajax:error', function(ev, error){
$(this).clearFormErrors();
var msg = $.parseJSON(error.responseText);
renderFormError(ev,
$(this).find('#sample_type_name'),
Object.keys(msg)[0] + ' '+ msg.name.toString());
});
}
});
});
}
function submitEditSampleTypeForm() {
$('.edit-sample-type').on('click', function() {
var form = $(this).closest('form');
form.submit();
});
}
function abortEditSampleTypeAction() {
$('.abort').on('click', function() {
var li = $(this).closest('li');
var href = $(this).attr('data-sample-type-element');
var id = $(li).attr('data-sample-type-id');
$.ajax({
url: href,
data: { id: id },
success: function(data) {
$(li).replaceWith($.parseHTML(data.html));
editSampleTypeForm();
destroySampleType();
}
});
});
}
function destroySampleType() {
$('.delete').on('click', function() {
var li = $(this).closest('li');
var href = li.attr('data-sample-type-delete');
var id = $(li).attr('data-sample-type-id');
$.ajax({
url: href,
data: { id: id },
success: function(data) {
$('body').append($.parseHTML(data.html));
$('#modal-delete-sample-type').modal('show',{
backdrop: true,
keyboard: false,
});
clearModal('#modal-delete-sample-type');
}
});
});
}
function clearModal(id) {
$(id).on('hidden.bs.modal', function() {
$(id).remove();
});
}
function bindNewSampleTypeAction() {
$('#new_sample_type').bind('ajax:success', function(ev, data) {
var li = '<li data-sample-type-id="' + data.id + '"' +
' data-sample-type-edit="' + data.edit + '"' +
' data-sample-type-delete="' + data.destroy + '">' + data.name +
'<span class="pull-right"><span class="edit glyphicon ' +
'glyphicon-pencil"></span><span class="delete glyphicon ' +
'glyphicon-trash"></span></span></li>';
$('#name-input').val('');
$('.new-sample-type-form').slideUp();
$(li).insertAfter('.new-sample-type-form');
editSampleTypeForm();
destroySampleType();
}).bind('ajax:error', function(ev, error) {
$(this).clearFormErrors();
var msg = $.parseJSON(error.responseText);
renderFormError(ev,
$(this).find('#name-input'),
Object.keys(msg)[0] + ' '+ msg.name.toString());
});
}
function initSampleTypes() {
showNewSampleTypeForm();
newSampleTypeFormCancel();
newSampleTypeFormSubmit();
bindNewSampleTypeAction();
editSampleTypeForm();
destroySampleType();
}
// initialize sample types actions
initSampleTypes();
})();

View file

@ -0,0 +1,226 @@
(function() {
'use strict';
function showNewSampleTypeGroupForm() {
$('#create-resource').on('click', function() {
$('.new-resource-form').slideDown();
});
}
function newSampleTypeFormCancel() {
$('#remove').on('click', function() {
$('#name-input').val('');
$('.new-resource-form').slideUp();
});
}
function newSampleTypeGroupFormSubmit() {
$('#submit').on('click', function() {
var form = $(this).closest('form');
form.submit();
});
}
function submitEditSampleTypeGroupForm(button) {
$('.edit-confirm').on('click', function() {
var form = $(this).closest('form');
form.submit();
});
}
function abortEditSampleTypeGroupAction() {
$('.abort').on('click', function() {
var li = $(this).closest('li');
var href = $(this).attr('data-element');
var id = $(li).attr('data-id');
$.ajax({
url: href,
data: { id: id },
success: function(data) {
$(li).replaceWith($.parseHTML(data.html));
editSampleTypeForm();
destroySampleTypeGroup();
initSampleGroupColor();
appendCarretToColorPickerDropdown();
}
});
});
}
function destroySampleTypeGroup() {
$('.delete').on('click', function() {
var li = $(this).closest('li');
var href = li.attr('data-delete');
var id = $(li).attr('data-id');
$.ajax({
url: href,
data: { id: id },
success: function(data) {
$('body').append($.parseHTML(data.html));
$('#modal-delete').modal('show',{
backdrop: true,
keyboard: false,
});
clearModal('#modal-delete');
}
});
});
}
function clearModal(id) {
$(id).on('hidden.bs.modal', function() {
$(id).remove();
});
}
function bindNewSampleTypeAction() {
$('#new_sample_type').bind('ajax:success', function(ev, data) {
var li = $.parseHTML(data.html);
$('#name-input').val('');
$('.new-resource-form').slideUp();
$(li).insertAfter('.new-resource-form');
editSampleTypeForm();
destroySampleTypeGroup();
}).bind('ajax:error', function(ev, error) {
$(this).clearFormErrors();
var msg = $.parseJSON(error.responseText);
renderFormError(ev,
$(this).find('#name-input'),
Object.keys(msg)[0] + ' '+ msg.name.toString());
});
}
function appendCarretToColorPickerDropdown() {
$(document).ready(function() {
_.each($('.btn-colorselector'), function(el){
if(!$(el).next().is('span.caret')) {
$(el).after($.parseHTML('<span class="caret"></span>'));
}
});
});
}
function editSampleGroupColor() {
$(document).ready(function() {
$('.color-btn').on('click', function() {
var color = $(this).attr('data-value');
var form = $(this).closest('form');
$('select[name="sample_group[color]"]')
.val(color);
form.submit();
});
});
}
function bindNewSampleGroupAction() {
$('#new_sample_group').bind('ajax:success', function(ev, data) {
var li = $.parseHTML(data.html);
$('#name-input').val('');
$('.new-resource-form').slideUp();
$(li).insertAfter('.new-resource-form');
initSampleGroupColor();
appendCarretToColorPickerDropdown();
editSampleGroupColor();
}).bind('ajax:error', function(ev, error) {
$(this).clearFormErrors();
var msg = $.parseJSON(error.responseText);
renderFormError(ev,
$(this).find('#name-input'),
Object.keys(msg)[0] + ' '+ msg.name.toString());
});
}
function editSampleTypeForm() {
$('.edit').on('click', function() {
var li = $(this).closest('li');
$.ajax({
url: li.attr('data-edit'),
success: function(data) {
$(li).replaceWith($.parseHTML(data.html));
submitEditSampleTypeGroupForm();
abortEditSampleTypeGroupAction();
destroySampleTypeGroup();
$('#edit_sample_type_' + data.id)
.bind('ajax:success', function(ev, data) {
$(this).closest('li').replaceWith($.parseHTML(data.html));
editSampleTypeForm();
destroySampleTypeGroup();
}).bind('ajax:error', function(ev, error){
$(this).clearFormErrors();
var msg = $.parseJSON(error.responseText);
renderFormError(ev,
$(this).find('#sample_type_name'),
Object.keys(msg)[0] + ' '+ msg.name.toString());
});
}
});
});
}
function editSampleGroupForm() {
$('.edit').on('click', function() {
var li = $(this).closest('li');
$.ajax({
url: li.attr('data-edit'),
success: function(data) {
$(li).replaceWith($.parseHTML(data.html));
submitEditSampleTypeGroupForm();
abortEditSampleTypeGroupAction();
destroySampleTypeGroup();
initSampleGroupColor();
appendCarretToColorPickerDropdown();
editSampleGroupColor();
$('#edit_sample_group_' + data.id)
.bind('ajax:success', function(ev, data) {
$(this).closest('li').replaceWith($.parseHTML(data.html));
editSampleGroupForm();
destroySampleTypeGroup();
initSampleGroupColor();
appendCarretToColorPickerDropdown();
editSampleGroupColor();
}).bind('ajax:error', function(ev, error){
$(this).clearFormErrors();
var msg = $.parseJSON(error.responseText);
renderFormError(ev,
$(this).find('#sample_group_name'),
Object.keys(msg)[0] + ' '+ msg.name.toString());
});
}
});
});
}
function initSampleGroupColor() {
var elements = $('.edit-sample-group-color');
_.each(elements, function(el) {
var color = $(el).closest('[data-color]')
.attr('data-color');
$(el).colorselector('setColor', color);
});
}
function initSampleTypesGroups() {
showNewSampleTypeGroupForm();
newSampleTypeFormCancel();
newSampleTypeGroupFormSubmit();
bindNewSampleTypeAction();
editSampleTypeForm();
destroySampleTypeGroup();
editSampleGroupForm();
editSampleGroupColor();
initSampleGroupColor();
bindNewSampleGroupAction();
appendCarretToColorPickerDropdown();
}
// initialize sample types/groups actions
initSampleTypesGroups();
})();

View file

@ -9,7 +9,7 @@
}
.btn-colorselector{
background-color: transparent !important;
background-color: transparent;
}
.step-container .row {

View file

@ -1,46 +0,0 @@
@import 'constants';
#sample_types_list {
margin-top: 50px;
li {
border-bottom: 1px solid $color-alto;
padding: 15px 5px;
&:nth-child(2) {
border-top: 1px solid $color-alto;
}
.edit {
margin-right: 5px;
}
.abort {
margin-left: 5px;
}
#remove {
margin-left: 5px;
}
}
#name-input {
padding: 5px;
width: 203px;
}
.new-sample-type-form {
border-bottom: 0;
border-top: 1px solid $color-alto;
display: none;
}
.glyphicon-ok {
color: $color-theme-secondary;
}
.glyphicon {
cursor: pointer;
font-size: 15px;
}
}

View file

@ -0,0 +1,96 @@
@import 'constants';
.sample_types_groups_list {
margin-top: 50px;
li {
border-bottom: 1px solid $color-alto;
padding: 15px 5px;
&:nth-child(2) {
border-top: 1px solid $color-alto;
}
.edit {
margin-right: 5px;
}
.abort {
margin-left: 5px;
}
#remove {
margin-left: 5px;
}
}
#name-input {
padding: 5px;
width: 203px;
}
.new-resource-form {
border-bottom: 0;
border-top: 1px solid $color-alto;
display: none;
}
.glyphicon-ok {
color: $color-theme-secondary;
}
.glyphicon {
cursor: pointer;
font-size: 15px;
}
.sample-group-colorselector {
border-radius: 0;
display: inline-block;
width: 20px;
height: 20px;
vertical-align: middle;
}
.color-picker {
float: left;
margin-right: 15px;
.btn-colorselector {
border: 1px solid $color-black;
}
.dropdown-colorselector > .dropdown-menu > li {
border-bottom: 0;
padding: 0;
}
.dropdown-colorselector > .dropdown-menu {
left: -80px;
}
.dropdown-menu.dropdown-caret:before {
left: 80px;
}
.dropdown-menu.dropdown-caret:after {
left: 81px;
}
.caret {
color: $color-black;
}
}
.sample-group-controls {
.edit {
margin-top: 3px;
}
.delete {
margin-top: 3px;
}
}
}

View file

@ -1,6 +1,7 @@
class SampleGroupsController < ApplicationController
before_action :load_vars_nested, only: [:create]
before_action :check_create_permissions, only: [:create]
before_action :load_vars_nested
before_action :check_create_permissions
before_action :set_sample_group, except: [:create, :index]
def create
@sample_group = SampleGroup.new(sample_group_params)
@ -10,28 +11,111 @@ class SampleGroupsController < ApplicationController
respond_to do |format|
if @sample_group.save
format.json {
format.json do
render json: {
id: @sample_group.id,
flash: t(
'sample_groups.create.success_flash',
sample_group: @sample_group.name,
organization: @organization.name
html: render_to_string(
partial: 'sample_group.html.erb',
locals: { sample_group: @sample_group,
organization: @organization }
)
},
status: :ok
}
end
else
format.json {
format.json do
render json: @sample_group.errors,
status: :unprocessable_entity
end
end
end
end
def index
render_404 unless current_organization
@sample_groups = current_organization.sample_groups
end
def update
@sample_group.update_attributes(sample_group_params)
respond_to do |format|
format.json do
if @sample_group.save
render json: {
html: render_to_string(
partial: 'sample_group.html.erb',
locals: { sample_group: @sample_group,
organization: @organization }
)
}
else
render json: @sample_group.errors,
status: :unprocessable_entity
end
end
end
end
def edit
respond_to do |format|
format.json do
render json: {
html:
render_to_string(
partial: 'edit.html.erb',
locals: { sample_group: @sample_group,
organization: @organization }
),
id: @sample_group.id
}
end
end
end
def sample_group_element
respond_to do |format|
format.json do
render json: {
html: render_to_string(
partial: 'sample_group.html.erb',
locals: { sample_group: @sample_group,
organization: @organization }
)
}
end
end
end
def destroy_confirmation
respond_to do |format|
format.json do
render json: {
html: render_to_string(
partial: 'delete_sample_group_modal.html.erb',
locals: { sample_group: @sample_group,
organization: @organization }
)
}
end
end
end
def destroy
flash[:success] = t 'sample_groups.index.destroy_flash',
name: @sample_group.name
Sample.where(sample_group: @sample_group).find_each do |sample|
sample.update(sample_group_id: nil)
end
@sample_group.destroy
redirect_to :back
end
private
def set_sample_group
@sample_group = SampleGroup.find_by_id(params[:id])
end
def load_vars_nested
@organization = Organization.find_by_id(params[:organization_id])

View file

@ -6,11 +6,7 @@ class SampleTypesController < ApplicationController
:sample_type_element,
:destroy,
:destroy_confirmation]
before_action :check_create_permissions, only: [:create,
:edit,
:update,
:destroy,
:destroy_confirmation]
before_action :check_create_permissions
before_action :set_sample_type, only: [:edit,
:update,
:destroy,
@ -28,12 +24,11 @@ class SampleTypesController < ApplicationController
if @sample_type.save
format.json do
render json: {
id: @sample_type.id,
name: @sample_type.name,
edit: edit_organization_sample_type_path(current_organization,
@sample_type),
destroy: organization_sample_type_path(current_organization,
@sample_type)
html: render_to_string(
partial: 'sample_type.html.erb',
locals: { sample_type: @sample_type,
organization: @organization }
)
},
status: :ok
end

View file

@ -9,9 +9,9 @@ module SamplesHelper
end
def can_add_sample_related_things_to_organization
can_create_custom_field_in_organization(@organization) \
or can_create_sample_type_in_organization(@organization) \
or can_create_sample_group_in_organization(@organization)
can_create_custom_field_in_organization(@organization) &&
can_create_sample_type_in_organization(@organization) &&
can_create_sample_group_in_organization(@organization)
end
def all_custom_fields

View file

@ -0,0 +1,31 @@
<div class="modal fade"
id="modal-delete"
tabindex="-1"
role="dialog"
aria-labelledby="modal-delete-label">
<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"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title"><%= t('sample_groups.index.destroy_modal_title',
name: sample_group.name) %></h4>
</div>
<div class="modal-body">
<%= t'sample_groups.index.destroy_modal_body' %>
</div>
<%= bootstrap_form_for [organization, sample_group], html: { method: :delete } do |f| %>
<div class="modal-footer">
<input type="submit"
class="btn btn-danger"
value="<%= t'sample_groups.index.destroy_modal_submit' %>">
<button type="button"
class="btn btn-default"
data-dismiss="modal"><%= t("general.cancel")%></button>
</div>
<% end %>
</div>
</div>
</div>

View file

@ -0,0 +1,31 @@
<li data-id="<%= sample_group.id %>"
data-delete="<%=
organization_sample_group_destroy_confirmation_path(organization,
sample_group)%>"
data-color="<%= sample_group.color %>">
<div class="row">
<div class="col-xs-6">
<%= form_for [organization, sample_group], html: { class: 'form-inline' }, remote: true do |f| %>
<div class="form-group">
<%= f.text_field :name, value: sample_group.name , class: 'form-control' %>
<span class="edit-confirm glyphicon glyphicon-ok"></span>
<span class="abort glyphicon glyphicon-remove"
data-element="<%= organization_sample_group_sample_group_element_path(organization,
sample_group) %>"></span>
</div>
<% end %>
</div>
<div class="col-xs-6 text-right">
<span class="pull-right sample-group-controls">
<span class="color-picker">
<%= bootstrap_form_for [organization, sample_group], remote: true do |f| %>
<%= f.color_picker_select :color,
Constants::TAG_COLORS,
class: 'edit-sample-group-color' %>
<% end %>
</span>
<span class="delete glyphicon glyphicon-trash"></span>
</span>
</div>
</div>
</li>

View file

@ -0,0 +1,20 @@
<li data-id="<%= sample_group.id %>"
data-edit="<%=
edit_organization_sample_group_path(organization, sample_group) %>"
data-delete="<%=
organization_sample_group_destroy_confirmation_path(organization,
sample_group) %>"
data-color="<%= sample_group.color %>">
<%= sample_group.name %>
<span class="pull-right sample-group-controls">
<span class="color-picker">
<%= bootstrap_form_for [organization, sample_group], remote: true do |f| %>
<%= f.color_picker_select :color,
Constants::TAG_COLORS,
class: 'edit-sample-group-color' %>
<% end %>
</span>
<span class="edit glyphicon glyphicon-pencil"></span>
<span class="delete glyphicon glyphicon-trash"></span>
</span>
</li>

View file

@ -0,0 +1,45 @@
<% provide(:head_title, t('sample_groups.index.head_title')) %>
<% if current_organization %>
<ul class="nav nav-tabs nav-settings" data-position="bottom">
<li role="presentation">
<%= link_to t('sample_types.index.sample_types'),
organization_sample_types_path(current_organization) %>
</li>
<li role="presentation" class="active">
<%= link_to t('sample_types.index.sample_groups'),
organization_sample_groups_path(current_organization) %>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane tab-pane-settings tab-pane-protocols active" role="tabpanel">
<div class="pull-right">
<button id="create-resource"
class="btn btn-primary pull-right"
><%= t 'sample_groups.index.new' %></button>
</div>
<ul class="list-unstyled sample_types_groups_list"
id="sample_groups_list">
<li class="new-resource-form">
<%= form_for SampleGroup.new,
remote: true,
html: { class: 'form-inline' },
url: organization_sample_groups_path(current_organization) do |f| %>
<div class="form-group">
<%= f.text_field :name, id: 'name-input', class: 'form-control' %>
<span id="submit" class="glyphicon glyphicon-ok"></span>
<span id="remove" class="glyphicon glyphicon-remove"></span>
</div>
<% end %>
</li>
<% @sample_groups.each do |sample_group| %>
<%= render partial: 'sample_group',
locals: { sample_group: sample_group,
organization: current_organization } %>
<% end %>
</ul>
</div>
</div>
<% end %>
<%= javascript_include_tag 'samples/sample_types_groups' %>

View file

@ -1,8 +1,8 @@
<div class="modal fade"
id="modal-delete-sample-type"
id="modal-delete"
tabindex="-1"
role="dialog"
aria-labelledby="modal-delete-sample-type-label">
aria-labelledby="modal-delete-label">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">

View file

@ -1,5 +1,5 @@
<li data-sample-type-id="<%= sample_type.id %>"
data-sample-type-delete="<%=
<li data-id="<%= sample_type.id %>"
data-delete="<%=
organization_sample_type_destroy_confirmation_path(organization, sample_type)
%>">
<div class="row">
@ -7,10 +7,11 @@
<%= form_for [organization, sample_type], html: { class: 'form-inline' }, remote: true do |f| %>
<div class="form-group">
<%= f.text_field :name, value: sample_type.name , class: 'form-control' %>
<span class="edit-sample-type glyphicon glyphicon-ok"></span>
<span class="edit-confirm glyphicon glyphicon-ok"></span>
<span class="abort glyphicon glyphicon-remove"
data-sample-type-element="<%= organization_sample_type_sample_type_element_path(organization,
sample_type) %>"></span>
data-element="<%=
organization_sample_type_sample_type_element_path(organization,
sample_type) %>"></span>
</div>
<% end %>
</div>

View file

@ -1,9 +1,9 @@
<li data-sample-type-id="<%= sample_type.id %>"
data-sample-type-edit="<%=
<li data-id="<%= sample_type.id %>"
data-edit="<%=
edit_organization_sample_type_path(organization,
sample_type)
%>"
data-sample-type-delete="<%=
data-delete="<%=
organization_sample_type_destroy_confirmation_path(organization, sample_type)
%>">
<%= sample_type.name %>

View file

@ -7,17 +7,19 @@
organization_sample_types_path(current_organization) %>
</li>
<li role="presentation">
<%= link_to t('sample_types.index.sample_groups'), '#' %>
<%= link_to t('sample_types.index.sample_groups'),
organization_sample_groups_path(current_organization) %>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane tab-pane-settings tab-pane-protocols active" role="tabpanel">
<div class="pull-right">
<button id="create-sample-type"
<button id="create-resource"
class="btn btn-primary pull-right"><%= t 'sample_types.index.new' %></button>
</div>
<ul class="list-unstyled" id="sample_types_list">
<li class="new-sample-type-form">
<ul class="list-unstyled sample_types_groups_list"
id="sample_types_list">
<li class="new-resource-form">
<%= form_for SampleType.new,
remote: true,
html: { class: 'form-inline' },
@ -39,4 +41,4 @@
</div>
<% end %>
<%= javascript_include_tag 'samples/sample_types' %>
<%= javascript_include_tag 'samples/sample_types_groups' %>

View file

@ -50,12 +50,13 @@
</div>
</div>
<% if can_add_sample_related_things_to_organization %>
<div class="dropdown pull-right" style="display: inline;">
<%= link_to_if(can_add_sample_related_things_to_organization,
t('samples.types_and_groups'),
organization_sample_types_path(@organization),
class: 'btn btn-default' ) %>
<%= link_to(t('samples.types_and_groups'),
organization_sample_types_path(@organization),
class: 'btn btn-default' ) %>
</div>
<% end %>
</div>
<div class="btn-group" id="saveCancel" data-toggle="buttons" style="display:none">

View file

@ -60,7 +60,7 @@ Rails.application.config.assets.precompile += %w(comments.js)
Rails.application.config.assets.precompile += %w(projects/show.js)
Rails.application.config.assets.precompile += %w(notifications.js)
Rails.application.config.assets.precompile += %w(users/invite_users_modal.js)
Rails.application.config.assets.precompile += %w(samples/sample_types.js)
Rails.application.config.assets.precompile += %w(samples/sample_types_groups.js)
# Libraries needed for Handsontable formulas
Rails.application.config.assets.precompile += %w(lodash.js)

View file

@ -925,6 +925,13 @@ en:
duplicated_values: "Two or more columns have the same mapping."
sample_groups:
index:
head_title: 'Sample Groups'
new: 'Create sample group'
destroy_modal_title: "Permanently delete the \"%{name}\" sample group?"
destroy_modal_body: 'This action is irreversible and makes the sample group inaccessible to all team members.'
destroy_modal_submit: 'Permanently delete sample group'
destroy_flash: "\"%{name}\" sample group was successfully deleted!"
color_label: "Sample group color"
create:
success_flash: "Successfully added sample group <strong>%{sample_group}</strong> to team <strong>%{organization}</strong>."

View file

@ -64,7 +64,10 @@ Rails.application.routes.draw do
get 'sample_type_element', to: 'sample_types#sample_type_element'
get 'destroy_confirmation', to: 'sample_types#destroy_confirmation'
end
resources :sample_groups, only: [:create]
resources :sample_groups, except: [:show, :new] 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]
member do
post 'parse_sheet'