mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-24 08:43:13 +08:00
Add initial table [SCI-9680]
This commit is contained in:
parent
8a0dd0258e
commit
38024d563a
19 changed files with 463 additions and 49 deletions
|
@ -568,11 +568,6 @@ li.module-hover {
|
|||
// New projects page
|
||||
|
||||
.projects-index {
|
||||
--content-header-size: 9em;
|
||||
.content-header {
|
||||
height: var(--content-header-size);
|
||||
}
|
||||
|
||||
.project-users-list {
|
||||
hr {
|
||||
margin: .5em 0;
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// scss-lint:disable SelectorFormat
|
||||
|
||||
.cards-wrapper {
|
||||
--content-header-size: 9em;
|
||||
--card-min-width: 200px;
|
||||
--list-columns-number: 5;
|
||||
align-items: center;
|
||||
|
|
|
@ -146,7 +146,7 @@ class LabelTemplatesController < ApplicationController
|
|||
actions:
|
||||
Toolbars::LabelTemplatesService.new(
|
||||
current_user,
|
||||
label_template_ids: params[:item_ids].split(',')
|
||||
label_template_ids: JSON.parse(params[:items]).map { |i| i['id'] }
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
|
|
@ -30,13 +30,21 @@ class ProjectsController < ApplicationController
|
|||
before_action :set_current_projects_view_type, only: %i(index cards)
|
||||
layout 'fluid'
|
||||
|
||||
def index; end
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
projects = Lists::ProjectsService.new(current_team, current_user, current_folder, params).call
|
||||
render json: projects, each_serializer: Lists::ProjectAndFolderSerializer, user: current_user, meta: pagination_dict(projects)
|
||||
end
|
||||
format.html do; end
|
||||
end
|
||||
end
|
||||
|
||||
def cards
|
||||
overview_service = ProjectsOverviewService.new(current_team, current_user, current_folder, params)
|
||||
title = params[:view_mode] == 'archived' ? t('projects.index.head_title_archived') : t('projects.index.head_title')
|
||||
|
||||
if filters_included?
|
||||
if false && filters_included?
|
||||
render json: {
|
||||
toolbar_html: render_to_string(partial: 'projects/index/toolbar'),
|
||||
filtered: true,
|
||||
|
@ -385,8 +393,7 @@ class ProjectsController < ApplicationController
|
|||
actions:
|
||||
Toolbars::ProjectsService.new(
|
||||
current_user,
|
||||
project_ids: params[:project_ids].split(','),
|
||||
project_folder_ids: params[:project_folder_ids].split(',')
|
||||
items: JSON.parse(params[:items]),
|
||||
).actions
|
||||
}
|
||||
end
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import LabelTemplatesTable from '../../vue/label_template/table.vue';
|
||||
import { handleTurbolinks } from './helpers/turbolinks.js';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('LabelTemplatesTable', LabelTemplatesTable);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
app.mount('#labelTemplatesTable');
|
||||
handleTurbolinks(app);
|
||||
|
||||
mountWithTurbolinks(app, '#labelTemplatesTable');
|
||||
|
|
11
app/javascript/packs/vue/projects_list.js
Normal file
11
app/javascript/packs/vue/projects_list.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { createApp } from 'vue/dist/vue.esm-bundler.js';
|
||||
import PerfectScrollbar from 'vue3-perfect-scrollbar';
|
||||
import ProjectsList from '../../vue/projects/list.vue';
|
||||
import { mountWithTurbolinks } from './helpers/turbolinks.js';
|
||||
|
||||
const app = createApp();
|
||||
app.component('ProjectsList', ProjectsList);
|
||||
app.config.globalProperties.i18n = window.I18n;
|
||||
app.use(PerfectScrollbar);
|
||||
mountWithTurbolinks(app, '#ProjectsList');
|
||||
|
100
app/javascript/vue/projects/list.vue
Normal file
100
app/javascript/vue/projects/list.vue
Normal file
|
@ -0,0 +1,100 @@
|
|||
<template>
|
||||
<div class="h-full">
|
||||
<DataTable :columnDefs="columnDefs"
|
||||
tableId="ProjectsList"
|
||||
:dataUrl="dataSource"
|
||||
:reloadingTable="reloadingTable"
|
||||
:toolbarActions="toolbarActions"
|
||||
:actionsUrl="actionsUrl"
|
||||
:withRowMenu="true"
|
||||
@tableReloaded="reloadingTable = false"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '../../packs/custom_axios.js';
|
||||
|
||||
import DataTable from '../shared/datatable/table.vue'
|
||||
import UsersRenderer from './renderers/users.vue'
|
||||
|
||||
export default {
|
||||
name: 'ProjectsList',
|
||||
components: {
|
||||
DataTable,
|
||||
UsersRenderer,
|
||||
},
|
||||
props: {
|
||||
dataSource: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
actionsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
createUrl: {
|
||||
type: String,
|
||||
},
|
||||
createFolderUrl: {
|
||||
type: String,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reloadingTable: false,
|
||||
columnDefs: [
|
||||
{ field: "name", flex: 1, headerName: this.i18n.t('projects.index.card.name'), sortable: true, cellRenderer: this.nameRenderer },
|
||||
{ field: "code", headerName: this.i18n.t('projects.index.card.id'), sortable: true },
|
||||
{ field: "created_at", headerName: this.i18n.t('projects.index.card.start_date'), sortable: true },
|
||||
{ field: "hidden", headerName: this.i18n.t('projects.index.card.visibility'), cellRenderer: this.visibiltyRenderer, sortable: false },
|
||||
{ field: "users", headerName: this.i18n.t('projects.index.card.users'), cellRenderer: 'UsersRenderer', sortable: false, minWidth: 210 }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
toolbarActions() {
|
||||
let left = []
|
||||
if (this.createUrl) {
|
||||
left.push({
|
||||
name: 'create',
|
||||
icon: 'sn-icon sn-icon-new-task',
|
||||
label: this.i18n.t('projects.index.new'),
|
||||
type: 'emit',
|
||||
path: this.createUrl,
|
||||
buttonStyle: 'btn btn-primary'
|
||||
})
|
||||
}
|
||||
if (this.createFolderUrl) {
|
||||
left.push({
|
||||
name: 'create_folder',
|
||||
icon: 'sn-icon sn-icon-folder',
|
||||
label: this.i18n.t('projects.index.new_folder'),
|
||||
type: 'emit',
|
||||
path: this.createFolderUrl,
|
||||
buttonStyle: 'btn btn-light',
|
||||
})
|
||||
}
|
||||
return {
|
||||
left: left,
|
||||
right: []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
nameRenderer(params) {
|
||||
let showUrl = params.data.urls.show;
|
||||
return `<a href="${showUrl}" class="flex items-center gap-1">
|
||||
${params.data.folder ? 'sn-icon mini sn-icon-mini-folder-left' : ''}
|
||||
${params.data.name}
|
||||
</a>`
|
||||
},
|
||||
visibiltyRenderer(params) {
|
||||
if (params.data.type !== 'projects') return ''
|
||||
|
||||
return params.data.hidden ? this.i18n.t('projects.index.hidden') : this.i18n.t('projects.index.visible');
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
39
app/javascript/vue/projects/renderers/users.vue
Normal file
39
app/javascript/vue/projects/renderers/users.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<div class="flex items-center gap-1 cursor pointer">
|
||||
<div v-for="(user, i) in visibleUsers" :key="i" :title="user.full_name">
|
||||
<img :src="user.avatar" class="w-7 h-7" />
|
||||
</div>
|
||||
<div v-if="hiddenUsers.length > 0" :title="hiddenUsersTitle" class="flex shrink-0 items-center justify-center w-7 h-7 rounded-full bg-sn-dark-grey text-sn-white">
|
||||
+{{ hiddenUsers.length }}
|
||||
</div>
|
||||
<div class="flex items-center shrink-0 justify-center w-7 h-7 rounded-full bg-sn-light-grey text-sn-dark-grey">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UsersRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
users() {
|
||||
return this.params.value || []
|
||||
},
|
||||
visibleUsers() {
|
||||
return this.users.slice(0, 4)
|
||||
},
|
||||
hiddenUsers() {
|
||||
return this.users.slice(4)
|
||||
},
|
||||
hiddenUsersTitle() {
|
||||
return this.hiddenUsers.map((user) => user.full_name).join("\u000d")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
|
@ -19,7 +19,7 @@
|
|||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'deleteStepModal',
|
||||
name: 'confirmationModal',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
|
62
app/javascript/vue/shared/datatable/row_menu_renderer.vue
Normal file
62
app/javascript/vue/shared/datatable/row_menu_renderer.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<div class="flex items-center">
|
||||
<MenuDropdown
|
||||
:listItems="this.formattedList"
|
||||
:btnClasses="'btn btn-light icon-btn'"
|
||||
:position="'right'"
|
||||
:alwaysShow="true"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
@open="loadActions"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MenuDropdown from '../menu_dropdown.vue'
|
||||
import axios from '../../../packs/custom_axios.js';
|
||||
|
||||
export default {
|
||||
name: 'RowMenuRenderer',
|
||||
props: {
|
||||
params: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actionsMenu: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MenuDropdown
|
||||
},
|
||||
computed: {
|
||||
formattedList() {
|
||||
return this.actionsMenu.map((item) => {
|
||||
let newItem = { text: item.label }
|
||||
if (item.type == 'emit') {
|
||||
newItem.emit = item.name
|
||||
}
|
||||
if (item.type == 'link') {
|
||||
newItem.url = item.path
|
||||
}
|
||||
|
||||
return newItem
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadActions() {
|
||||
if (this.actionsMenu.length > 0) return
|
||||
|
||||
axios.get(this.params.data.urls.actions)
|
||||
.then((response) => {
|
||||
this.actionsMenu = response.data.actions
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,21 +1,24 @@
|
|||
<template>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="relative flex flex-col flex-grow">
|
||||
<div class="relative flex flex-col flex-grow z-10">
|
||||
<Toolbar :toolbarActions="toolbarActions" @toolbar:action="emitAction" :searchValue="searchValue" @search:change="setSearchValue" />
|
||||
<ag-grid-vue
|
||||
class="ag-theme-alpine w-full flex-grow h-full"
|
||||
class="ag-theme-alpine w-full flex-grow h-full z-10"
|
||||
:class="{'opacity-0': initializing}"
|
||||
:columnDefs="columnDefs"
|
||||
:rowData="rowData"
|
||||
:defaultColDef="defaultColDef"
|
||||
:rowSelection="'multiple'"
|
||||
:suppressRowTransform="true"
|
||||
:gridOptions="gridOptions"
|
||||
:suppressRowClickSelection="true"
|
||||
@grid-ready="onGridReady"
|
||||
@first-data-rendered="onFirstDataRendered"
|
||||
@sortChanged="setOrder"
|
||||
@columnResized="saveColumnsState"
|
||||
@columnMoved="saveColumnsState"
|
||||
@rowSelected="setSelectedRows"
|
||||
@cellClicked="clickCell"
|
||||
:CheckboxSelectionCallback="withCheckboxes"
|
||||
>
|
||||
</ag-grid-vue>
|
||||
|
@ -52,6 +55,7 @@ import Pagination from './pagination.vue';
|
|||
import CustomHeader from './tableHeader';
|
||||
import ActionToolbar from './action_toolbar.vue';
|
||||
import Toolbar from './toolbar.vue';
|
||||
import RowMenuRenderer from './row_menu_renderer.vue';
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
|
@ -60,6 +64,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
withRowMenu: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
tableId: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -108,7 +116,8 @@ export default {
|
|||
Pagination,
|
||||
agColumnHeader: CustomHeader,
|
||||
ActionToolbar,
|
||||
Toolbar
|
||||
Toolbar,
|
||||
RowMenuRenderer
|
||||
},
|
||||
computed: {
|
||||
perPageOptions() {
|
||||
|
@ -121,7 +130,7 @@ export default {
|
|||
},
|
||||
actionsParams() {
|
||||
return {
|
||||
item_ids: this.selectedRows.map(row => row.id).join(',')
|
||||
items: JSON.stringify(this.selectedRows.map(row => { return {id: row.id, type: row.type} }))
|
||||
}
|
||||
},
|
||||
gridOptions() {
|
||||
|
@ -139,7 +148,23 @@ export default {
|
|||
checkboxSelection: true,
|
||||
width: 48,
|
||||
minWidth: 48,
|
||||
resizable: false
|
||||
resizable: false,
|
||||
pinned: 'left'
|
||||
});
|
||||
}
|
||||
|
||||
if (this.withRowMenu) {
|
||||
this.columnDefs.push({
|
||||
field: "rowMenu",
|
||||
headerName: '',
|
||||
width: 72,
|
||||
minWidth: 72,
|
||||
resizable: false,
|
||||
sortable: false,
|
||||
cellRenderer: 'RowMenuRenderer',
|
||||
cellStyle: {overflow: 'visible'},
|
||||
pinned: 'right'
|
||||
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -159,7 +184,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
formatData(data) {
|
||||
return data.map( (item) => Object.assign({}, item.attributes, { id: item.id }) );
|
||||
return data.map( (item) => Object.assign({}, item.attributes, { id: item.id, type: item.type }) );
|
||||
},
|
||||
resize() {
|
||||
if (this.tableState) return;
|
||||
|
@ -234,6 +259,11 @@ export default {
|
|||
setSearchValue(value) {
|
||||
this.searchValue = value;
|
||||
this.loadData();
|
||||
},
|
||||
clickCell(e) {
|
||||
if (e.column.colId !== 'rowMenu') {
|
||||
e.node.setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="relative" v-if="listItems.length > 0" v-click-outside="closeMenu">
|
||||
<div class="relative" v-if="listItems.length > 0 || alwaysShow" >
|
||||
<button ref="openBtn" :class="btnClasses" @click="showMenu = !showMenu">
|
||||
<i v-if="btnIcon" :class="btnIcon"></i>
|
||||
{{ btnText }}
|
||||
|
@ -23,7 +23,7 @@
|
|||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
:data-toggle="item.modalTarget && 'modal'"
|
||||
:data-target="item.modalTarget"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, item)"
|
||||
>
|
||||
{{ item.text }}
|
||||
|
@ -49,7 +49,7 @@
|
|||
:href="sub_item.url"
|
||||
:traget="sub_item.url_target || '_self'"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, sub_item)"
|
||||
>
|
||||
{{ sub_item.text }}
|
||||
|
@ -75,6 +75,7 @@ export default {
|
|||
btnText: { type: String, required: false },
|
||||
btnIcon: { type: String, required: false },
|
||||
caret: { type: Boolean, default: false },
|
||||
alwaysShow: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -93,6 +94,7 @@ export default {
|
|||
this.$refs.flyout.style.marginBottom = `${this.$refs.openBtn.offsetHeight}px`;
|
||||
this.updateOpenDirectoin();
|
||||
})
|
||||
this.$emit('open');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -39,6 +39,11 @@ class UserAssignment < ApplicationRecord
|
|||
user_assignments.none?
|
||||
end
|
||||
|
||||
def user_name_with_role
|
||||
"#{user.name} - #{user_role.name}"
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def set_assignable_team
|
||||
|
|
51
app/serializers/lists/project_and_folder_serializer.rb
Normal file
51
app/serializers/lists/project_and_folder_serializer.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
module Lists
|
||||
class ProjectAndFolderSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :name, :code, :created_at, :archived_on, :users, :hidden, :urls, :folder
|
||||
|
||||
def folder
|
||||
!project?
|
||||
end
|
||||
|
||||
def code
|
||||
object.code if project?
|
||||
end
|
||||
|
||||
def created_at
|
||||
I18n.l(object.created_at, format: :full) if project?
|
||||
end
|
||||
|
||||
def archived_on
|
||||
I18n.l(object.archived_on, format: :full) if project? && object.archived_on
|
||||
end
|
||||
|
||||
def hidden
|
||||
object.hidden? if project?
|
||||
end
|
||||
|
||||
def users
|
||||
if project?
|
||||
object.user_assignments.map do |ua|
|
||||
{
|
||||
avatar: avatar_path(ua.user, :icon_small),
|
||||
full_name: ua.user_name_with_role
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def urls
|
||||
{
|
||||
show: project? ? project_path(object) : project_folder_path(object),
|
||||
actions: actions_toolbar_projects_path(items: [{id: object.id, type: project? ? 'projects' : 'project_folders'}].to_json)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def project?
|
||||
object.class == Project
|
||||
end
|
||||
end
|
||||
end
|
110
app/services/lists/projects_service.rb
Normal file
110
app/services/lists/projects_service.rb
Normal file
|
@ -0,0 +1,110 @@
|
|||
module Lists
|
||||
class ProjectsService < BaseService
|
||||
def initialize(team, user, folder, params)
|
||||
@team = team
|
||||
@user = user
|
||||
@current_folder = folder
|
||||
@params = params
|
||||
@view_mode = ''
|
||||
end
|
||||
|
||||
def call
|
||||
projects = fetch_projects
|
||||
folders = fetch_project_folders
|
||||
|
||||
projects = filter_project_records(projects)
|
||||
folders = filter_project_folder_records(folders)
|
||||
|
||||
projects = projects.where(project_folder: @current_folder)
|
||||
folders = folders.where(parent_folder: @current_folder)
|
||||
|
||||
records = projects + folders
|
||||
|
||||
records = sort_records(records)
|
||||
paginate_records(records)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_projects
|
||||
@team.projects
|
||||
.includes(:team, user_assignments: %i(user user_role))
|
||||
.includes(:project_comments, experiments: { my_modules: { my_module_status: :my_module_status_implications } })
|
||||
.visible_to(@user, @team)
|
||||
.left_outer_joins(:project_comments)
|
||||
.select('projects.*')
|
||||
.select('COUNT(DISTINCT comments.id) AS comment_count')
|
||||
.group('projects.id')
|
||||
end
|
||||
|
||||
def fetch_project_folders
|
||||
project_folders = @team.project_folders
|
||||
.includes(:team)
|
||||
.joins('LEFT OUTER JOIN project_folders child_folders
|
||||
ON child_folders.parent_folder_id = project_folders.id')
|
||||
.left_outer_joins(:projects)
|
||||
project_folders.select('project_folders.*')
|
||||
.select('COUNT(DISTINCT projects.id) AS projects_count')
|
||||
.select('COUNT(DISTINCT child_folders.id) AS folders_count')
|
||||
.group('project_folders.id')
|
||||
end
|
||||
|
||||
def filter_project_records(records)
|
||||
return records
|
||||
|
||||
records = records.archived if @view_mode == 'archived'
|
||||
records = records.active if @view_mode == 'active'
|
||||
if @params[:search].present?
|
||||
records = records.where_attributes_like(['projects.name', Project::PREFIXED_ID_SQL], @params[:search])
|
||||
end
|
||||
if @params[:members].present?
|
||||
records = records.joins(:user_assignments).where(user_assignments: { user_id: @params[:members] })
|
||||
end
|
||||
records = records.where('projects.created_at > ?', @params[:created_on_from]) if @params[:created_on_from].present?
|
||||
records = records.where('projects.created_at < ?', @params[:created_on_to]) if @params[:created_on_to].present?
|
||||
records = records.where('projects.archived_on < ?', @params[:archived_on_to]) if @params[:archived_on_to].present?
|
||||
if @params[:archived_on_from].present?
|
||||
records = records.where('projects.archived_on > ?', @params[:archived_on_from])
|
||||
end
|
||||
records
|
||||
end
|
||||
|
||||
def filter_project_folder_records(records)
|
||||
return records
|
||||
|
||||
records = records.archived if @view_mode == 'archived'
|
||||
records = records.active if @view_mode == 'active'
|
||||
records = records.where_attributes_like('project_folders.name', @params[:search]) if @params[:search].present?
|
||||
records
|
||||
end
|
||||
|
||||
def sort_records(records)
|
||||
return records unless @params[:order]
|
||||
|
||||
sort = "#{order_params[:column]}_#{sort_direction(order_params)}"
|
||||
|
||||
case sort
|
||||
when 'created_at_ASC'
|
||||
records.sort_by(&:created_at).reverse!
|
||||
when 'created_at_DESC'
|
||||
records.sort_by(&:created_at)
|
||||
when 'name_ASC'
|
||||
records.sort_by { |c| c.name.downcase }
|
||||
when 'name_DESC'
|
||||
records.sort_by { |c| c.name.downcase }.reverse!
|
||||
when 'code_ASC'
|
||||
records.sort_by(&:id)
|
||||
when 'code_DESC'
|
||||
records.sort_by(&:id).reverse!
|
||||
when 'archived_on_ASC'
|
||||
records.sort_by(&:archived_on)
|
||||
when 'archived_on_DESC'
|
||||
records.sort_by(&:archived_on).reverse!
|
||||
end
|
||||
end
|
||||
|
||||
def paginate_records(records)
|
||||
Kaminari.paginate_array(records).page(@params[:page]).per(@params[:per_page])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -7,8 +7,11 @@ module Toolbars
|
|||
include Canaid::Helpers::PermissionsHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def initialize(current_user, project_ids: [], project_folder_ids: [])
|
||||
def initialize(current_user, items: [])
|
||||
@current_user = current_user
|
||||
project_ids = items.select { |i| i['type'] == 'projects' }.map { |i| i['id'] }
|
||||
project_folder_ids = items.select { |i| i['type'] == 'project_folders' }.map { |i| i['id'] }
|
||||
|
||||
@projects = current_user.current_team.projects.where(id: project_ids)
|
||||
@project_folders = current_user.current_team.project_folders.where(id: project_folder_ids)
|
||||
|
||||
|
@ -59,7 +62,7 @@ module Toolbars
|
|||
icon: 'sn-icon sn-icon-edit',
|
||||
button_class: 'edit-btn',
|
||||
path: edit_project_path(project),
|
||||
type: :legacy
|
||||
type: :emit
|
||||
}
|
||||
else
|
||||
project_folder = @items.first
|
||||
|
@ -72,7 +75,7 @@ module Toolbars
|
|||
icon: 'sn-icon sn-icon-edit',
|
||||
button_class: 'edit-btn',
|
||||
path: edit_project_folder_path(project_folder),
|
||||
type: :legacy
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
end
|
||||
|
@ -96,9 +99,8 @@ module Toolbars
|
|||
name: 'access',
|
||||
label: I18n.t('general.access'),
|
||||
icon: 'sn-icon sn-icon-project-member-access',
|
||||
button_class: 'access-btn',
|
||||
path: path,
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -110,9 +112,8 @@ module Toolbars
|
|||
name: 'move',
|
||||
label: I18n.t('projects.index.move_button'),
|
||||
icon: 'sn-icon sn-icon-move',
|
||||
button_class: 'move-projects-btn',
|
||||
path: move_to_modal_project_folders_path,
|
||||
type: :legacy
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -123,9 +124,8 @@ module Toolbars
|
|||
name: 'export',
|
||||
label: I18n.t('projects.export_projects.export_button'),
|
||||
icon: 'sn-icon sn-icon-export',
|
||||
button_class: 'export-projects-btn',
|
||||
path: export_projects_modal_team_path(@items.first.team),
|
||||
type: :legacy
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -138,10 +138,8 @@ module Toolbars
|
|||
name: 'archive',
|
||||
label: I18n.t('projects.index.archive_button'),
|
||||
icon: 'sn-icon sn-icon-archive',
|
||||
button_class: 'archive-projects-btn',
|
||||
path: archive_group_projects_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit,
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -156,8 +154,7 @@ module Toolbars
|
|||
icon: 'sn-icon sn-icon-restore',
|
||||
button_class: 'restore-projects-btn',
|
||||
path: restore_group_projects_path,
|
||||
type: :request,
|
||||
request_method: :post
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -170,9 +167,8 @@ module Toolbars
|
|||
name: 'delete_folders',
|
||||
label: I18n.t('general.delete'),
|
||||
icon: 'sn-icon sn-icon-delete',
|
||||
button_class: 'delete-folders-btn',
|
||||
path: destroy_modal_project_folders_path(project_folder_ids: @items.map(&:id)),
|
||||
type: 'remote-modal'
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -189,10 +185,7 @@ module Toolbars
|
|||
name: 'comments',
|
||||
label: I18n.t('Comments'),
|
||||
icon: 'sn-icon sn-icon-comments',
|
||||
button_class: 'open-comments-sidebar',
|
||||
item_type: 'Project',
|
||||
item_id: project.id,
|
||||
type: :legacy
|
||||
type: :emit
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -9,6 +9,20 @@
|
|||
<div id="projectsWrapper" class="content-pane flexible projects-index <%= projects_view_mode %>" data-view-mode="<%= projects_view_mode %>" data-e2e="e2e-projects-container">
|
||||
<%= render partial: 'projects/index/header', locals: { current_folder: current_folder} %>
|
||||
|
||||
<div id="ProjectsList" class="fixed-content-body">
|
||||
<projects-list
|
||||
actions-url="<%= actions_toolbar_projects_url %>"
|
||||
data-source="<%= projects_path(format: :json) %>"
|
||||
create-url="<%= projects_path if can_create_projects?(current_team) %>"
|
||||
create-folder-url="<%= project_folders_path if can_create_project_folders?(current_team) %>"
|
||||
/>
|
||||
</div>
|
||||
<%= javascript_include_tag 'vue_projects_list' %>
|
||||
|
||||
|
||||
<div id="toolbarWrapper" class="toolbar-row" data-width-breakpoint="750">
|
||||
<%= render partial: 'projects/index/toolbar' %>
|
||||
</div>
|
||||
<span style="display: none;" data-hook="projects-index-html"></span>
|
||||
|
||||
<%= render partial: 'projects/index/modals/edit_modal' %>
|
||||
|
|
|
@ -19,7 +19,4 @@
|
|||
<span class="projects-title name-readonly-placeholder"><%= current_folder&.name || t('projects.index.head_title_archived') %></span>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="toolbarWrapper" class="toolbar-row" data-width-breakpoint="750">
|
||||
<%= render partial: 'projects/index/toolbar' %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -46,7 +46,8 @@ const entryList = {
|
|||
vue_components_export_stock_consumption_modal: './app/javascript/packs/vue/export_stock_consumption_modal.js',
|
||||
vue_components_manage_stock_value_modal: './app/javascript/packs/vue/manage_stock_value_modal.js',
|
||||
vue_legacy_datetime_picker: './app/javascript/packs/vue/legacy/datetime_picker.js',
|
||||
vue_label_templates_table: './app/javascript/packs/vue/label_templates_table.js'
|
||||
vue_label_templates_table: './app/javascript/packs/vue/label_templates_table.js',
|
||||
vue_projects_list: './app/javascript/packs/vue/projects_list.js',
|
||||
}
|
||||
|
||||
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949
|
||||
|
|
Loading…
Reference in a new issue