mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-03-03 19:24:48 +08:00
Merge pull request #8247 from aignatov-bio/ai-sci-11561-add-assigned-items-vue-component
Add assigned items vue component [SCI-11561]
This commit is contained in:
commit
0fe6228bc1
12 changed files with 432 additions and 8 deletions
|
@ -54,6 +54,10 @@ var MyModuleRepositories = (function() {
|
|||
}
|
||||
|
||||
function reloadRepositoriesList(repositoryId, expand = false) {
|
||||
|
||||
window.assignedItemsTable.$refs.assignedItems.loadAssingedRepositories();
|
||||
|
||||
/*
|
||||
var repositoriesContainer = $('#assigned-items-container');
|
||||
$.get(repositoriesContainer.data('repositories-list-url'), function(result) {
|
||||
repositoriesContainer.html(result.html);
|
||||
|
@ -64,6 +68,7 @@ var MyModuleRepositories = (function() {
|
|||
$('#assigned-repository-items-container-' + repositoryId).collapse('show');
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function tableColumns(tableContainer, skipCheckbox = false) {
|
||||
|
@ -608,7 +613,7 @@ var MyModuleRepositories = (function() {
|
|||
}
|
||||
|
||||
function initRepositoryFullView() {
|
||||
$('#assigned-items-container').on('click', '.action-buttons .full-screen', function(e) {
|
||||
$('#assignedItems').on('click', '.full-screen', function(e) {
|
||||
var repositoryNameObject = $(this).closest('.assigned-repository-caret')
|
||||
.find('.assigned-repository-title');
|
||||
|
||||
|
@ -699,7 +704,7 @@ var MyModuleRepositories = (function() {
|
|||
}
|
||||
|
||||
function initRepositoryAssignView() {
|
||||
$('.repositories-dropdown-menu').on('click', '.repository', function(e) {
|
||||
$('#assignedItems').on('click', '.repository-assign', function(e) {
|
||||
var assignUrlModal = $(this).data('assign-url-modal');
|
||||
var updateUrlModal = $(this).data('update-url-modal');
|
||||
FULL_VIEW_MODAL.modal('show');
|
||||
|
|
|
@ -4,9 +4,9 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
include ApplicationHelper
|
||||
|
||||
before_action :load_my_module, except: :assign_my_modules
|
||||
before_action :load_repository, except: %i(repositories_dropdown_list repositories_list_html create)
|
||||
before_action :load_repository, except: %i(repositories_dropdown_list repositories_list_html repositories_list create)
|
||||
before_action :check_my_module_view_permissions, except: %i(update consume_modal update_consumption assign_my_modules)
|
||||
before_action :check_repository_view_permissions, except: %i(repositories_dropdown_list repositories_list_html create)
|
||||
before_action :check_repository_view_permissions, except: %i(repositories_dropdown_list repositories_list_html repositories_list create)
|
||||
before_action :check_repository_row_consumption_permissions, only: %i(consume_modal update_consumption)
|
||||
before_action :check_assign_repository_records_permissions, only: %i(update create)
|
||||
before_action :load_my_modules, only: :assign_my_modules
|
||||
|
@ -149,6 +149,11 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
}
|
||||
end
|
||||
|
||||
def repositories_list
|
||||
@assigned_repositories = @my_module.readable_live_and_snapshot_repositories_list(current_user)
|
||||
render json: @assigned_repositories, each_serializer: AssignedRepositorySerializer, scope: {user: current_user, my_module: @my_module }
|
||||
end
|
||||
|
||||
def full_view_table
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
|
@ -170,7 +175,20 @@ class MyModuleRepositoriesController < ApplicationController
|
|||
.having('COUNT(my_module_repository_rows.id) > 0 OR repositories.archived = FALSE')
|
||||
.order(:name)
|
||||
|
||||
render json: { html: render_to_string(partial: 'my_modules/repositories/repositories_dropdown_list') }
|
||||
#render json: { html: render_to_string(partial: 'my_modules/repositories/repositories_dropdown_list') }
|
||||
render json: {
|
||||
repositories: @repositories.map do |repository|
|
||||
{
|
||||
id: repository.id,
|
||||
name: repository.name,
|
||||
rows_count: repository.rows_count,
|
||||
shared: repository.shared_with?(current_team),
|
||||
table_url: full_view_table_my_module_repository_path(@my_module, repository),
|
||||
assign_url: assign_modal_my_module_repository_path(@my_module, repository),
|
||||
update_url: update_modal_my_module_repository_path(@my_module, repository),
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def export_repository
|
||||
|
|
|
@ -84,7 +84,7 @@ module MyModulesHelper
|
|||
date_time: l(repository.created_at, format: :full))
|
||||
end
|
||||
|
||||
t('my_modules.repository.snapshots.simple_view.live_bottom_label')
|
||||
I18n.t('my_modules.repository.snapshots.simple_view.live_bottom_label')
|
||||
end
|
||||
|
||||
def assigned_repository_simple_view_name_column_id(repository)
|
||||
|
|
13
app/javascript/packs/vue/my_module_assigned_items.js
Normal file
13
app/javascript/packs/vue/my_module_assigned_items.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import AssignedItems from '../../vue/my_module/assigned_items.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('AssignedItems', AssignedItems);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
|
||||
window.assignedItemsTable = mountWithTurbolinks(app, '#assignedItems', () => {
|
||||
delete window.assignedItemsTable;
|
||||
});
|
129
app/javascript/vue/my_module/assigned_items.vue
Normal file
129
app/javascript/vue/my_module/assigned_items.vue
Normal file
|
@ -0,0 +1,129 @@
|
|||
<template>
|
||||
<div class="bg-white px-4 my-4">
|
||||
<div class="py-4 flex items-center gap-4">
|
||||
<i ref="openHandler" @click="toggleContainer" class="sn-icon sn-icon-right cursor-pointer"></i>
|
||||
<h2 class="my-0 flex items-center gap-1">
|
||||
{{ i18n.t('my_modules.assigned_items.title') }}
|
||||
<span class="text-sn-grey-500 font-normal text-base">[{{ totalRows }}]</span>
|
||||
</h2>
|
||||
<div class="ml-auto">
|
||||
<!-- Next block just for legacy support, JQuery not good works with Teleport -->
|
||||
<div class="hidden repository-assign"
|
||||
v-for="repository in availableRepositories"
|
||||
:key="repository.id"
|
||||
:data-table-url="repository.table_url"
|
||||
:data-assign-url-modal="repository.assign_url"
|
||||
:data-update-url-modal="repository.update_url"
|
||||
:data-repository-id="repository.id"
|
||||
:ref="`repository_${repository.id}`"
|
||||
></div>
|
||||
<!-- End of block -->
|
||||
<GeneralDropdown position="right" @open="loadAvailableRepositories">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-light">
|
||||
{{ i18n.t('my_modules.assigned_items.assign_from') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-slot:flyout>
|
||||
<div v-if="loadingAvailableRepositories" class="flex items-center justify-center w-full h-32">
|
||||
<img src="/images/medium/loading.svg" alt="Loading" />
|
||||
</div>
|
||||
<div v-else v-for="repository in availableRepositories" :key="repository.id">
|
||||
<div class="px-3 py-2.5 hover:bg-sn-super-light-grey max-w-[320px] cursor-pointer overflow-hidden flex items-center gap-1" @click="openAssignModal(repository.id)">
|
||||
<i v-if="repository.shared" class="sn-icon sn-icon sn-icon-users shrink-0"></i>
|
||||
<span class="truncate">{{ repository.name }}</span>
|
||||
<span v-if="repository.rows_count > 0" class="text-sn-grey-500">
|
||||
<i class="fas fa-file-signature"></i>
|
||||
{{ repository.rows_count }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="repositoriesContainer" class="overflow-hidden transition-all" style="max-height: 0px;">
|
||||
<div class="pl-[2.375rem] py-2.5 mb-4 flex flex-col gap-4">
|
||||
<AssignedRepository
|
||||
v-for="repository in assignedRepositories"
|
||||
:key="repository.id"
|
||||
:repository="repository"
|
||||
@recalculateContainerSize="recalculateContainerSize"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
import GeneralDropdown from '../shared/general_dropdown.vue';
|
||||
import AssignedRepository from './assigned_items/repository.vue';
|
||||
|
||||
export default {
|
||||
name: 'AssignedItems',
|
||||
props: {
|
||||
avaialableRepositoriesUrl: String,
|
||||
assignedRepositoriesUrl: String
|
||||
},
|
||||
components: {
|
||||
GeneralDropdown,
|
||||
AssignedRepository
|
||||
},
|
||||
created() {
|
||||
this.loadAssingedRepositories();
|
||||
},
|
||||
computed: {
|
||||
totalRows() {
|
||||
return this.assignedRepositories.reduce((acc, repository) => acc + repository.attributes.assigned_rows_count, 0);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
availableRepositories: [],
|
||||
assignedRepositories: [],
|
||||
loadingAvailableRepositories: false,
|
||||
sectionOpened: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
recalculateContainerSize(offset = 0) {
|
||||
const container = this.$refs.repositoriesContainer;
|
||||
const handler = this.$refs.openHandler;
|
||||
|
||||
if (this.sectionOpened) {
|
||||
container.style.maxHeight = `${container.scrollHeight + offset}px`;
|
||||
handler.classList.remove('sn-icon-right');
|
||||
handler.classList.add('sn-icon-down');
|
||||
} else {
|
||||
container.style.maxHeight = '0px';
|
||||
handler.classList.remove('sn-icon-down');
|
||||
handler.classList.add('sn-icon-right');
|
||||
}
|
||||
},
|
||||
toggleContainer() {
|
||||
this.sectionOpened = !this.sectionOpened;
|
||||
this.recalculateContainerSize();
|
||||
},
|
||||
loadAssingedRepositories() {
|
||||
axios.get(this.assignedRepositoriesUrl)
|
||||
.then((response) => {
|
||||
this.assignedRepositories = response.data.data;
|
||||
});
|
||||
},
|
||||
openAssignModal(repositoryId) {
|
||||
const [repository] = this.$refs[`repository_${repositoryId}`];
|
||||
repository.click();
|
||||
},
|
||||
loadAvailableRepositories() {
|
||||
this.loadingAvailableRepositories = true;
|
||||
axios.get(this.avaialableRepositoriesUrl)
|
||||
.then((response) => {
|
||||
this.availableRepositories = response.data.repositories;
|
||||
this.loadingAvailableRepositories = false;
|
||||
this.recalculateContainerSize();
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
<template>
|
||||
<a class="hover:no-underline flex items-center gap-1 record-info-link"
|
||||
:title="params.data[0]"
|
||||
:href="params.data.recordInfoUrl"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ params.data[0] }}
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
params: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
180
app/javascript/vue/my_module/assigned_items/repository.vue
Normal file
180
app/javascript/vue/my_module/assigned_items/repository.vue
Normal file
|
@ -0,0 +1,180 @@
|
|||
<template>
|
||||
<div ref="container" class="border rounded transition-all overflow-hidden" :style="{height: (sectionOpened ? '448px' : '48px')}">
|
||||
<div class="flex items-center h-12 px-4 gap-4 assigned-repository-title">
|
||||
<i ref="openHandler" @click="toggleContainer" class="sn-icon sn-icon-right cursor-pointer"></i>
|
||||
<h3 class="my-0 flex items-center gap-1 ">
|
||||
<span class="assigned-repository-title">{{ repository.attributes.name }}</span>
|
||||
<span class="text-sn-grey-500 font-normal text-base">
|
||||
[{{ repository.attributes.assigned_rows_count }}]
|
||||
</span>
|
||||
</h3>
|
||||
<button
|
||||
class="btn btn-light icon-btn ml-auto full-screen"
|
||||
:data-table-url="repository.attributes.urls.full_view"
|
||||
>
|
||||
<i class="sn-icon sn-icon-expand"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div style="height: 400px">
|
||||
<ag-grid-vue
|
||||
class="ag-theme-alpine w-full flex-grow h-[340px] z-10"
|
||||
:columnDefs="columnDefs"
|
||||
:rowData="preparedAssignedItems"
|
||||
:rowSelection="false"
|
||||
:suppressRowTransform="true"
|
||||
:suppressRowClickSelection="true"
|
||||
:enableCellTextSelection="true"
|
||||
@grid-ready="onGridReady"
|
||||
@sortChanged="setOrder"
|
||||
>
|
||||
</ag-grid-vue>
|
||||
<div class="h-[60px] flex items-center border-transparent border-t border-t-sn-light-grey border-solid grey px-6">
|
||||
<div>
|
||||
{{ repository.attributes.footer_label }}
|
||||
</div>
|
||||
<div class="ml-auto">
|
||||
<Pagination
|
||||
:totalPage="Math.ceil(assignedItems.recordsTotal / perPage)"
|
||||
:currentPage="page"
|
||||
@setPage="setPage"
|
||||
></Pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { AgGridVue } from 'ag-grid-vue3';
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
import CustomHeader from '../../shared/datatable/tableHeader';
|
||||
import Pagination from '../../shared/datatable/pagination.vue';
|
||||
import nameRenderer from './renderers/name.vue';
|
||||
|
||||
export default {
|
||||
name: 'AssignedRepository',
|
||||
props: {
|
||||
repository: Object
|
||||
},
|
||||
components: {
|
||||
AgGridVue,
|
||||
agColumnHeader: CustomHeader,
|
||||
Pagination,
|
||||
nameRenderer
|
||||
},
|
||||
data: () => ({
|
||||
assignedItems: {
|
||||
data: [],
|
||||
recordsTotal: 0
|
||||
},
|
||||
order: { column: 0, dir: 'asc' },
|
||||
sectionOpened: false,
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
gridApi: null,
|
||||
columnApi: null,
|
||||
gridReady: false
|
||||
}),
|
||||
created() {
|
||||
},
|
||||
computed: {
|
||||
preparedAssignedItems() {
|
||||
return this.assignedItems.data;
|
||||
},
|
||||
columnDefs() {
|
||||
const columns = [{
|
||||
field: '0',
|
||||
flex: 1,
|
||||
headerName: this.i18n.t('repositories.table.row_name'),
|
||||
sortable: true,
|
||||
cellRenderer: 'nameRenderer',
|
||||
comparator: () => null
|
||||
}];
|
||||
|
||||
if (this.repository.attributes.has_stock && this.repository.attributes.has_stock_consumption) {
|
||||
columns.push({
|
||||
field: 'stock',
|
||||
headerName: this.repository.attributes.stock_column_name,
|
||||
sortable: true,
|
||||
comparator: () => null
|
||||
});
|
||||
columns.push({
|
||||
field: 'consumedStock',
|
||||
headerName: this.i18n.t('repositories.table.row_consumption'),
|
||||
sortable: true,
|
||||
comparator: () => null
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
recalculateContainerSize() {
|
||||
const { container, openHandler } = this.$refs;
|
||||
|
||||
if (this.sectionOpened) {
|
||||
container.style.height = '448px';
|
||||
openHandler.classList.remove('sn-icon-right');
|
||||
openHandler.classList.add('sn-icon-down');
|
||||
this.$emit('recalculateContainerSize', 400);
|
||||
|
||||
if (this.assignedItems.data.length === 0) this.getRows();
|
||||
} else {
|
||||
container.style.height = '48px';
|
||||
openHandler.classList.remove('sn-icon-down');
|
||||
openHandler.classList.add('sn-icon-right');
|
||||
this.$emit('recalculateContainerSize', 0);
|
||||
}
|
||||
},
|
||||
toggleContainer() {
|
||||
this.sectionOpened = !this.sectionOpened;
|
||||
this.recalculateContainerSize();
|
||||
},
|
||||
setOrder() {
|
||||
const orderState = this.getOrder(this.columnApi.getColumnState());
|
||||
const [order] = orderState;
|
||||
if (order.column === 'stock') {
|
||||
order.column = 1;
|
||||
} else if (order.column === 'consumedStock') {
|
||||
order.column = 2;
|
||||
} else if (order.column === '0') {
|
||||
order.column = 0;
|
||||
}
|
||||
this.order = order;
|
||||
|
||||
this.getRows();
|
||||
},
|
||||
getOrder(columnsState) {
|
||||
if (!columnsState) return null;
|
||||
|
||||
return columnsState.filter((column) => column.sort)
|
||||
.map((column) => ({
|
||||
column: column.colId,
|
||||
dir: column.sort
|
||||
}));
|
||||
},
|
||||
onGridReady(params) {
|
||||
this.gridApi = params.api;
|
||||
this.columnApi = params.columnApi;
|
||||
this.gridReady = true;
|
||||
},
|
||||
getRows() {
|
||||
axios.post(this.repository.attributes.urls.assigned_rows, {
|
||||
assigned: 'assigned_simple',
|
||||
draw: this.page,
|
||||
length: this.perPage,
|
||||
order: [this.order],
|
||||
search: { value: '', regex: false },
|
||||
simple_view: true,
|
||||
start: (this.page - 1) * this.perPage,
|
||||
view_mode: true
|
||||
}).then((response) => {
|
||||
this.assignedItems = response.data;
|
||||
});
|
||||
},
|
||||
setPage(page) {
|
||||
this.page = page;
|
||||
this.getRows();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
48
app/serializers/assigned_repository_serializer.rb
Normal file
48
app/serializers/assigned_repository_serializer.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AssignedRepositorySerializer < ActiveModel::Serializer
|
||||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
include MyModulesHelper
|
||||
|
||||
attributes :id, :name
|
||||
|
||||
attribute :assigned_rows_count do
|
||||
object['assigned_rows_count']
|
||||
end
|
||||
|
||||
attribute :is_snapshot do
|
||||
object.is_a?(RepositorySnapshot)
|
||||
end
|
||||
|
||||
attribute :has_stock do
|
||||
object.has_stock_management?
|
||||
end
|
||||
|
||||
attribute :has_stock_consumption do
|
||||
object.has_stock_consumption?
|
||||
end
|
||||
|
||||
attribute :can_manage_consumption do
|
||||
can_update_my_module_stock_consumption?(scope[:user], scope[:my_module])
|
||||
end
|
||||
|
||||
attribute :stock_column_name do
|
||||
object.repository_stock_column.name if object.has_stock_management?
|
||||
end
|
||||
|
||||
attribute :footer_label do
|
||||
assigned_repository_simple_view_footer_label(object)
|
||||
end
|
||||
|
||||
attribute :name_column_id do
|
||||
assigned_repository_simple_view_name_column_id(object)
|
||||
end
|
||||
|
||||
attribute :urls do
|
||||
{
|
||||
full_view: assigned_repository_full_view_table_path(scope[:my_module], object),
|
||||
assigned_rows: assigned_repository_simple_view_index_path(scope[:my_module], object)
|
||||
}
|
||||
end
|
||||
end
|
|
@ -79,6 +79,13 @@
|
|||
<%= render partial: "my_module_notes" %>
|
||||
</div>
|
||||
</div>
|
||||
<div id="assignedItems" >
|
||||
<assigned-items
|
||||
ref="assignedItems"
|
||||
avaialable-repositories-url="<%= my_module_repositories_dropdown_list_path(@my_module) %>"
|
||||
assigned-repositories-url="<%= my_module_repositories_list_path(@my_module) %>"
|
||||
/>
|
||||
</div>
|
||||
<!-- Assigned items -->
|
||||
<div class="task-section hidden">
|
||||
<div class="task-section-header">
|
||||
|
@ -208,5 +215,6 @@
|
|||
<%= javascript_include_tag "protocols/new_protocol" %>
|
||||
|
||||
<%= javascript_include_tag 'vue_protocol' %>
|
||||
<%= javascript_include_tag 'vue_my_module_assigned_items' %>
|
||||
<%= javascript_include_tag 'vue_legacy_tags_modal' %>
|
||||
<%= javascript_include_tag 'vue_legacy_access_modal' %>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<li class="repository"
|
||||
data-table-url="<%= full_view_table_my_module_repository_path(@my_module, repository) %>"
|
||||
data-assign-url-modal="<%= assign_modal_my_module_repository_path(@my_module, repository) %>"
|
||||
data-update-url-modal="<%= update_modal_my_module_repository_path(@my_module, repository)%>" data-repository-id="<%= repository.id %>"
|
||||
data-update-url-modal="<%= update_modal_my_module_repository_path(@my_module, repository) %>" data-repository-id="<%= repository.id %>"
|
||||
data-rows-count="<%= repository.rows_count %>" >
|
||||
<span class="!px-3 !py-2.5 rounded hover:!bg-sn-super-light-grey !text-sn-blue block cursor-pointer">
|
||||
<% if repository.shared_with?(current_team) %>
|
||||
|
|
|
@ -493,6 +493,7 @@ Rails.application.routes.draw do
|
|||
|
||||
get :repositories_dropdown_list, controller: :my_module_repositories
|
||||
get :repositories_list_html, controller: :my_module_repositories
|
||||
get :repositories_list, controller: :my_module_repositories
|
||||
|
||||
resources :repositories, controller: :my_module_repositories, only: %i(update create) do
|
||||
member do
|
||||
|
|
|
@ -70,7 +70,8 @@ const entryList = {
|
|||
vue_storage_locations_table: './app/javascript/packs/vue/storage_locations_table.js',
|
||||
vue_storage_locations_container: './app/javascript/packs/vue/storage_locations_container.js',
|
||||
vue_form_show: './app/javascript/packs/vue/forms_show.js',
|
||||
vue_form_table: './app/javascript/packs/vue/forms_table.js'
|
||||
vue_form_table: './app/javascript/packs/vue/forms_table.js',
|
||||
vue_my_module_assigned_items: './app/javascript/packs/vue/my_module_assigned_items.js'
|
||||
};
|
||||
|
||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||
|
|
Loading…
Reference in a new issue