mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-10-08 21:06:24 +08:00
Add tags input to task page [SCI-12286]
This commit is contained in:
parent
fe17f2256c
commit
1baec4f47d
10 changed files with 216 additions and 113 deletions
|
@ -98,87 +98,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function initTagsSelector() {
|
|
||||||
var myModuleTagsSelector = '#module-tags-selector';
|
|
||||||
|
|
||||||
dropdownSelector.init($(myModuleTagsSelector), {
|
|
||||||
closeOnSelect: true,
|
|
||||||
tagClass: 'my-module-white-tags sci-tag',
|
|
||||||
labelHTML: true,
|
|
||||||
tagStyle: (data) => {
|
|
||||||
return `background: ${data.params.color}`;
|
|
||||||
},
|
|
||||||
customDropdownIcon: () => {
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
optionLabel: (data) => {
|
|
||||||
if (data.value > 0) {
|
|
||||||
return `<span class="sci-tag max-w-80 truncate text-sn-white "
|
|
||||||
style="background:${data.params.color}">${data.label}</span>`;
|
|
||||||
}
|
|
||||||
return `<span class="my-module-tags-color new"><i class="sn-icon sn-icon-new-task"></i></span>
|
|
||||||
${data.label + ' '}
|
|
||||||
<span class="my-module-tags-create-new"> ${I18n.t('my_modules.details.create_new_tag')}</span>`;
|
|
||||||
},
|
|
||||||
onOpen: function() {
|
|
||||||
$('.select-container .edit-button-container').removeClass('hidden');
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
$('.select-container .edit-button-container').addClass('hidden');
|
|
||||||
},
|
|
||||||
onSelect: function() {
|
|
||||||
var selectElement = $(myModuleTagsSelector);
|
|
||||||
var lastTag = selectElement.next().find('.ds-tags').last();
|
|
||||||
var lastTagId = lastTag.find('.tag-label').data('ds-tag-id');
|
|
||||||
var newTag;
|
|
||||||
|
|
||||||
if (lastTagId > 0) {
|
|
||||||
newTag = { my_module_tag: { tag_id: lastTagId } };
|
|
||||||
$.post(selectElement.data('update-module-tags-url'), newTag)
|
|
||||||
.fail(function(response) {
|
|
||||||
dropdownSelector.removeValue(myModuleTagsSelector, lastTagId, '', true);
|
|
||||||
if (response.status === 403) {
|
|
||||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (lastTag.length > 0) {
|
|
||||||
newTag = {
|
|
||||||
tag: {
|
|
||||||
name: lastTag.find('.tag-label').text(),
|
|
||||||
project_id: selectElement.data('project-id'),
|
|
||||||
color: null
|
|
||||||
},
|
|
||||||
my_module_id: selectElement.data('module-id'),
|
|
||||||
simple_creation: true
|
|
||||||
};
|
|
||||||
|
|
||||||
$.post(selectElement.data('tags-create-url'), newTag, function(result) {
|
|
||||||
dropdownSelector.removeValue(myModuleTagsSelector, 0, '', true);
|
|
||||||
dropdownSelector.addValue(myModuleTagsSelector, {
|
|
||||||
value: result.tag.id,
|
|
||||||
label: result.tag.name,
|
|
||||||
params: {
|
|
||||||
color: result.tag.color
|
|
||||||
}
|
|
||||||
}, true);
|
|
||||||
}).fail(function() {
|
|
||||||
dropdownSelector.removeValue(myModuleTagsSelector, lastTagId, '', true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onUnSelect: (id) => {
|
|
||||||
$.post(`${$(myModuleTagsSelector).data('update-module-tags-url')}/${id}/destroy_by_tag_id`)
|
|
||||||
.done(() => {
|
|
||||||
dropdownSelector.closeDropdown(myModuleTagsSelector);
|
|
||||||
})
|
|
||||||
.fail(function(r) {
|
|
||||||
if (r.status === 403) {
|
|
||||||
HelperModule.flashAlertMsg(I18n.t('general.no_permissions'), 'danger');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).getContainer(myModuleTagsSelector).addClass('my-module-tags-container');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function initAssignedUsersSelector() {
|
function initAssignedUsersSelector() {
|
||||||
|
@ -258,7 +178,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
initTaskCollapseState();
|
initTaskCollapseState();
|
||||||
initTagsSelector();
|
|
||||||
initStartDatePicker();
|
initStartDatePicker();
|
||||||
initDueDatePicker();
|
initDueDatePicker();
|
||||||
initAssignedUsersSelector();
|
initAssignedUsersSelector();
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
@layer components {
|
@layer components {
|
||||||
.sci-tag {
|
.sci-tag {
|
||||||
@apply text-xs !rounded-full !px-2 !py-1 inline-flex items-center gap-1;
|
@apply text-xs !rounded-full !pl-2 !pr-1.5 h-6 !py-0.5 inline-flex items-center gap-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sci-tag .sn-icon {
|
.sci-tag .sn-icon {
|
||||||
@apply -ml-2;
|
@apply leading-4 !text-base cursor-pointer shrink-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sci-tag .sn-icon.sn-icon-close {
|
||||||
|
@apply cursor-pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
39
app/controllers/concerns/taggable_actions.rb
Normal file
39
app/controllers/concerns/taggable_actions.rb
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module TaggableActions
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
included do
|
||||||
|
before_action :load_taggable_item, only: %i(link_tag unlink_tag)
|
||||||
|
before_action :load_tag, only: %i(link_tag unlink_tag)
|
||||||
|
end
|
||||||
|
|
||||||
|
def link_tag
|
||||||
|
tagging = @taggable_item.taggings.new(tag: @tag, created_by: current_user)
|
||||||
|
if tagging.save
|
||||||
|
render json: { tag: [@tag.id, @tag.name, @tag.color] }
|
||||||
|
else
|
||||||
|
render json: { status: :error }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlink_tag
|
||||||
|
tagging = @taggable_item.taggings.find_by(tag_id: @tag.id)
|
||||||
|
if tagging&.destroy
|
||||||
|
render json: { status: :ok }
|
||||||
|
else
|
||||||
|
render json: { status: :error }, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_taggable_item
|
||||||
|
@taggable_item = controller_name.singularize.camelize.constantize.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_tag
|
||||||
|
@tag = @taggable_item.team.tags.find_by(id: params[:tag_id])
|
||||||
|
render_404 unless @tag
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,6 +8,7 @@ class MyModulesController < ApplicationController
|
||||||
include MyModulesHelper
|
include MyModulesHelper
|
||||||
include Breadcrumbs
|
include Breadcrumbs
|
||||||
include FavoritesActions
|
include FavoritesActions
|
||||||
|
include TaggableActions
|
||||||
|
|
||||||
before_action :load_vars, except: %i(index restore_group create new save_table_state
|
before_action :load_vars, except: %i(index restore_group create new save_table_state
|
||||||
inventory_assigning_my_module_filter actions_toolbar)
|
inventory_assigning_my_module_filter actions_toolbar)
|
||||||
|
@ -17,6 +18,7 @@ class MyModulesController < ApplicationController
|
||||||
before_action :check_manage_permissions, only: %i(
|
before_action :check_manage_permissions, only: %i(
|
||||||
description due_date update_description update_protocol_description update_protocol
|
description due_date update_description update_protocol_description update_protocol
|
||||||
)
|
)
|
||||||
|
before_action :check_tag_manage_permissions, only: %i(link_tag unlink_tag)
|
||||||
before_action :check_read_permissions, except: %i(create new update update_description
|
before_action :check_read_permissions, except: %i(create new update update_description
|
||||||
inventory_assigning_my_module_filter
|
inventory_assigning_my_module_filter
|
||||||
update_protocol_description restore_group
|
update_protocol_description restore_group
|
||||||
|
@ -514,6 +516,10 @@ class MyModulesController < ApplicationController
|
||||||
render_404 unless @my_module.my_module_status
|
render_404 unless @my_module.my_module_status
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def check_tag_manage_permissions
|
||||||
|
render_403 && return unless can_manage_my_module_tags?(@my_module)
|
||||||
|
end
|
||||||
|
|
||||||
def set_inline_name_editing
|
def set_inline_name_editing
|
||||||
if action_name == 'index'
|
if action_name == 'index'
|
||||||
return unless can_manage_experiment?(@experiment)
|
return unless can_manage_experiment?(@experiment)
|
||||||
|
|
|
@ -128,7 +128,7 @@
|
||||||
{{ i18n.t('my_modules.details.completed_date') }}
|
{{ i18n.t('my_modules.details.completed_date') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 mb-6 mt-2.5">
|
<div class="flex gap-2 mt-2.5">
|
||||||
<span class="sn-icon sn-icon-users"></span>
|
<span class="sn-icon sn-icon-users"></span>
|
||||||
<span class="tw-hidden lg:block shrink-0">
|
<span class="tw-hidden lg:block shrink-0">
|
||||||
{{ i18n.t('my_modules.details.assigned_users') }}
|
{{ i18n.t('my_modules.details.assigned_users') }}
|
||||||
|
@ -148,6 +148,15 @@
|
||||||
</SelectDropdown>
|
</SelectDropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex gap-2 mb-6 mt-2.5">
|
||||||
|
<span class="sn-icon sn-icon-tag"></span>
|
||||||
|
<span class="tw-hidden lg:block shrink-0">
|
||||||
|
{{ i18n.t('my_modules.details.tags') }}
|
||||||
|
</span>
|
||||||
|
<div class="grow -mt-1.5">
|
||||||
|
<TagsInput :subject="myModule" v-if="myModule" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -156,6 +165,7 @@
|
||||||
import GeneralDropdown from '../shared/general_dropdown.vue';
|
import GeneralDropdown from '../shared/general_dropdown.vue';
|
||||||
import DateTimePicker from '../shared/date_time_picker.vue';
|
import DateTimePicker from '../shared/date_time_picker.vue';
|
||||||
import SelectDropdown from '../shared/select_dropdown.vue';
|
import SelectDropdown from '../shared/select_dropdown.vue';
|
||||||
|
import TagsInput from '../shared/tags_input.vue';
|
||||||
import axios from '../../packs/custom_axios.js';
|
import axios from '../../packs/custom_axios.js';
|
||||||
import escapeHtml from '../shared/escape_html.js';
|
import escapeHtml from '../shared/escape_html.js';
|
||||||
import {
|
import {
|
||||||
|
@ -175,7 +185,8 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
GeneralDropdown,
|
GeneralDropdown,
|
||||||
DateTimePicker,
|
DateTimePicker,
|
||||||
SelectDropdown
|
SelectDropdown,
|
||||||
|
TagsInput
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
133
app/javascript/vue/shared/tags_input.vue
Normal file
133
app/javascript/vue/shared/tags_input.vue
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<GeneralDropdown @open="opened = true" @close="opened = false">
|
||||||
|
<template v-slot:field>
|
||||||
|
<div class="w-full flex flex-wrap rounded gap-2 p-1 border border-solid cursor-pointer"
|
||||||
|
:class="{
|
||||||
|
'!border-sn-science-blue': opened,
|
||||||
|
'hover:!border-sn-light-grey !border-transparent': !opened,
|
||||||
|
}">
|
||||||
|
<template v-if="tags.length > 0">
|
||||||
|
<div v-for="tag in tags" :key="tag[0]" class="sci-tag text-white" :style="{ backgroundColor: tag[2] }" >
|
||||||
|
{{ tag[1] }}
|
||||||
|
<i @click.stop="unlinkTag(tag)" class="sn-icon sn-icon-close"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="sci-tag bg-sn-super-light-grey">
|
||||||
|
{{ i18n.t('tags.tags_input.add_tag') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-slot:flyout>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div v-for="tag in allTags" :key="tag[0]" @click="linkTag(tag)" class="py-2 cursor-pointer hover:bg-sn-super-light-grey px-3 flex items-center gap-2" >
|
||||||
|
<div class="sci-checkbox-container pointer-events-none" >
|
||||||
|
<input type="checkbox" :checked="tags.map(t => t[0]).includes(tag[0])" class="sci-checkbox" />
|
||||||
|
<span class="sci-checkbox-label"></span>
|
||||||
|
</div>
|
||||||
|
<div class="sci-tag text-white" :style="{ backgroundColor: tag[2] }" >
|
||||||
|
{{ tag[1] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr class="my-0 border-t w-full mb-1">
|
||||||
|
<button class="btn btn-light btn-black w-32">
|
||||||
|
<span class="sn-icon sn-icon-edit"></span>
|
||||||
|
{{ i18n.t('tags.tags_input.edit_tags') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</GeneralDropdown>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from '../../packs/custom_axios.js';
|
||||||
|
import GeneralDropdown from './general_dropdown.vue';
|
||||||
|
import {
|
||||||
|
list_users_settings_team_tags_path,
|
||||||
|
} from '../../routes.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TagsInput',
|
||||||
|
props: {
|
||||||
|
subject: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
GeneralDropdown,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
tagsUrl() {
|
||||||
|
return list_users_settings_team_tags_path({team_id: this.subject.attributes.team_id});
|
||||||
|
},
|
||||||
|
linkTagUrl() {
|
||||||
|
return this.subject.attributes.urls.link_tag;
|
||||||
|
},
|
||||||
|
unlinkTagUrl() {
|
||||||
|
return this.subject.attributes.urls.unlink_tag;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.loadAllTags();
|
||||||
|
this.tags = this.subject.attributes.tags || [];
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tags: [],
|
||||||
|
allTags: [],
|
||||||
|
linkingTag: false,
|
||||||
|
opened: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadAllTags() {
|
||||||
|
axios.get(this.tagsUrl).then((response) => {
|
||||||
|
this.allTags = response.data.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
linkTag(tag) {
|
||||||
|
if (this.tags.map(t => t[0]).includes(tag[0])) {
|
||||||
|
this.unlinkTag(tag);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.linkingTag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.linkingTag = true;
|
||||||
|
|
||||||
|
axios.post(this.linkTagUrl, {
|
||||||
|
tag_id: tag[0],
|
||||||
|
}).then((response) => {
|
||||||
|
this.tags.push(response.data.tag);
|
||||||
|
this.linkingTag = false;
|
||||||
|
}).catch(() => {
|
||||||
|
this.linkingTag = false;
|
||||||
|
HelperModule.flashAlertMsg(I18n.t('errors.general'), 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
unlinkTag(tag) {
|
||||||
|
if (this.linkingTag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.linkingTag = true;
|
||||||
|
|
||||||
|
axios.post(this.unlinkTagUrl, {
|
||||||
|
tag_id: tag[0],
|
||||||
|
}).then((response) => {
|
||||||
|
this.tags = this.tags.filter(t => t[0] !== tag[0]);
|
||||||
|
this.linkingTag = false;
|
||||||
|
}).catch(() => {
|
||||||
|
this.linkingTag = false;
|
||||||
|
HelperModule.flashAlertMsg(I18n.t('errors.general'), 'danger');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -6,7 +6,7 @@ class MyModuleSerializer < ActiveModel::Serializer
|
||||||
include ApplicationHelper
|
include ApplicationHelper
|
||||||
include ActionView::Helpers::TextHelper
|
include ActionView::Helpers::TextHelper
|
||||||
|
|
||||||
attributes :name, :description, :permissions, :description_view, :urls, :last_modified_by_name, :created_at, :updated_at,
|
attributes :name, :description, :permissions, :description_view, :urls, :last_modified_by_name, :created_at, :updated_at, :tags, :team_id,
|
||||||
:project_name, :experiment_name, :created_by_name, :is_creator_current_user, :code, :designated_user_ids, :due_date_cell, :start_date_cell, :completed_on
|
:project_name, :experiment_name, :created_by_name, :is_creator_current_user, :code, :designated_user_ids, :due_date_cell, :start_date_cell, :completed_on
|
||||||
|
|
||||||
def project_name
|
def project_name
|
||||||
|
@ -17,6 +17,10 @@ class MyModuleSerializer < ActiveModel::Serializer
|
||||||
object.experiment.name
|
object.experiment.name
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def team_id
|
||||||
|
object.team.id
|
||||||
|
end
|
||||||
|
|
||||||
def created_by_name
|
def created_by_name
|
||||||
object.created_by&.full_name
|
object.created_by&.full_name
|
||||||
end
|
end
|
||||||
|
@ -53,7 +57,9 @@ class MyModuleSerializer < ActiveModel::Serializer
|
||||||
def urls
|
def urls
|
||||||
{
|
{
|
||||||
show_access: access_permissions_my_module_path(object),
|
show_access: access_permissions_my_module_path(object),
|
||||||
show_user_group_assignments_access: show_user_group_assignments_access_permissions_my_module_path(object)
|
show_user_group_assignments_access: show_user_group_assignments_access_permissions_my_module_path(object),
|
||||||
|
link_tag: link_tag_my_module_path(object),
|
||||||
|
unlink_tag: unlink_tag_my_module_path(object)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -100,4 +106,10 @@ class MyModuleSerializer < ActiveModel::Serializer
|
||||||
def description
|
def description
|
||||||
sanitize_input(object.tinymce_render('description'))
|
sanitize_input(object.tinymce_render('description'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def tags
|
||||||
|
object.tags.map do |tag|
|
||||||
|
[tag.id, tag.name, tag.color]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -97,30 +97,6 @@
|
||||||
<%= render partial: 'my_modules/repositories/consume_stock_modal'%>
|
<%= render partial: 'my_modules/repositories/consume_stock_modal'%>
|
||||||
|
|
||||||
<!-- Tags modal -->
|
<!-- Tags modal -->
|
||||||
<div id="tagsModalContainer" class="vue-tags-modal">
|
|
||||||
<div ref="tagsModal" id="tagsModalComponent"></div>
|
|
||||||
<teleport to="body">
|
|
||||||
<tags-modal v-if="tagsModalOpen"
|
|
||||||
:params="<%=
|
|
||||||
{
|
|
||||||
id: @my_module.id,
|
|
||||||
permissions: {
|
|
||||||
manage_tags: can_manage_my_module_tags?(@my_module)
|
|
||||||
},
|
|
||||||
urls: {
|
|
||||||
assigned_tags: assigned_tags_my_module_my_module_tags_path(@my_module),
|
|
||||||
assign_tags: my_module_my_module_tags_path(@my_module)
|
|
||||||
}
|
|
||||||
}.to_json
|
|
||||||
%>"
|
|
||||||
:tags-colors="<%= Constants::TAG_COLORS.to_json %>"
|
|
||||||
project-name="<%= @experiment.project.name %>"
|
|
||||||
project-tags-url="<%= project_tags_path(@experiment.project) %>"
|
|
||||||
@close="close"
|
|
||||||
@tags-loaded="syncTags"
|
|
||||||
/>
|
|
||||||
</teleport>
|
|
||||||
</div>
|
|
||||||
<div id="accessModalContainer" class="vue-access-modal"
|
<div id="accessModalContainer" class="vue-access-modal"
|
||||||
data-url="<%= my_module_path(@my_module, format: :json) %>"
|
data-url="<%= my_module_path(@my_module, format: :json) %>"
|
||||||
>
|
>
|
||||||
|
@ -137,8 +113,6 @@
|
||||||
<%= stylesheet_link_tag 'datatables' %>
|
<%= stylesheet_link_tag 'datatables' %>
|
||||||
<%= javascript_include_tag "handsontable.full" %>
|
<%= javascript_include_tag "handsontable.full" %>
|
||||||
<%= render partial: "shared/formulas_libraries" %>
|
<%= render partial: "shared/formulas_libraries" %>
|
||||||
<%= javascript_include_tag("my_modules/protocols") %>
|
|
||||||
<%= javascript_include_tag("my_modules/tags") %>
|
|
||||||
<%= javascript_include_tag 'emoji_button' %>
|
<%= javascript_include_tag 'emoji_button' %>
|
||||||
<%= javascript_include_tag("my_modules/repositories") %>
|
<%= javascript_include_tag("my_modules/repositories") %>
|
||||||
<%= javascript_include_tag("my_modules/pwa_mobile_app") %>
|
<%= javascript_include_tag("my_modules/pwa_mobile_app") %>
|
||||||
|
|
|
@ -4236,6 +4236,9 @@ en:
|
||||||
merge: "Merge"
|
merge: "Merge"
|
||||||
merge_success: "Tags merged successfully."
|
merge_success: "Tags merged successfully."
|
||||||
merge_error: "There was an error merging tags."
|
merge_error: "There was an error merging tags."
|
||||||
|
tags_input:
|
||||||
|
add_tag: "Add tag"
|
||||||
|
edit_tags: "Edit tags"
|
||||||
user_groups:
|
user_groups:
|
||||||
promo:
|
promo:
|
||||||
head_title: 'User groups'
|
head_title: 'User groups'
|
||||||
|
|
|
@ -525,6 +525,8 @@ Rails.application.routes.draw do
|
||||||
post :favorite
|
post :favorite
|
||||||
post :unfavorite
|
post :unfavorite
|
||||||
get :assigned_users
|
get :assigned_users
|
||||||
|
post :link_tag
|
||||||
|
post :unlink_tag
|
||||||
end
|
end
|
||||||
resources :user_my_modules, path: '/users', only: %i(index create destroy) do
|
resources :user_my_modules, path: '/users', only: %i(index create destroy) do
|
||||||
collection do
|
collection do
|
||||||
|
|
Loading…
Add table
Reference in a new issue