Replace project table [SCI-9680]

This commit is contained in:
Anton 2023-11-24 11:08:28 +01:00
parent 38024d563a
commit 7fdbc73098
14 changed files with 185 additions and 48 deletions

View file

@ -393,7 +393,7 @@ class ProjectsController < ApplicationController
actions:
Toolbars::ProjectsService.new(
current_user,
items: JSON.parse(params[:items]),
items: JSON.parse(params[:items])
).actions
}
end

View file

@ -7,6 +7,10 @@
:toolbarActions="toolbarActions"
:actionsUrl="actionsUrl"
:withRowMenu="true"
:activePageUrl="activePageUrl"
:archivedPageUrl="archivedPageUrl"
:currentViewMode="currentViewMode"
:filters="filters"
@tableReloaded="reloadingTable = false"
/>
</div>
@ -38,6 +42,16 @@ export default {
},
createFolderUrl: {
type: String,
},
activePageUrl: {
type: String,
},
archivedPageUrl: {
type: String,
},
currentViewMode: {
type: String,
required: true
}
},
data() {
@ -79,13 +93,40 @@ export default {
left: left,
right: []
}
},
filters() {
let filters = [{
key: 'query',
type: 'Text'
},
{
key: 'created_at',
type: 'DateRange',
label: this.i18n.t("filters_modal.created_on.label"),
}]
if (this.currentViewMode === 'archived') {
filters.push({
key: 'archived_at',
type: 'DateRange',
label: this.i18n.t("filters_modal.archived_on.label"),
})
}
filters.push({
key: 'folder_search',
type: 'Checkbox',
label: this.i18n.t("projects.index.filters_modal.folders.label"),
})
return filters
}
},
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.folder ? '<i class="sn-icon mini sn-icon-mini-folder-left"></i>' : ''}
${params.data.name}
</a>`
},

View file

@ -1,9 +1,9 @@
<template>
<div class="flex items-center gap-1 cursor pointer">
<div v-if="!params.data.folder" class="flex items-center gap-1 cursor pointer h-10">
<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">
<div v-if="hiddenUsers.length > 0" :title="hiddenUsersTitle" class="flex shrink-0 items-center justify-center w-7 h-7 text-xs 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">

View file

@ -1,7 +1,17 @@
<template>
<div class="flex flex-col h-full">
<div class="relative flex flex-col flex-grow z-10">
<Toolbar :toolbarActions="toolbarActions" @toolbar:action="emitAction" :searchValue="searchValue" @search:change="setSearchValue" />
<Toolbar
:toolbarActions="toolbarActions"
@toolbar:action="emitAction"
:searchValue="searchValue"
@search:change="setSearchValue"
:activePageUrl="activePageUrl"
:archivedPageUrl="archivedPageUrl"
:currentViewMode="currentViewMode"
:filters="filters"
@applyFilters="applyFilters"
/>
<ag-grid-vue
class="ag-theme-alpine w-full flex-grow h-full z-10"
:class="{'opacity-0': initializing}"
@ -90,7 +100,22 @@ export default {
reloadingTable: {
type: Boolean,
default: false
}
},
activePageUrl: {
type: String,
},
archivedPageUrl: {
type: String,
},
currentViewMode: {
type: String,
default: 'active'
},
filters: {
type: Array,
default: () => []
},
},
data() {
return {
@ -106,7 +131,8 @@ export default {
totalPage: 0,
selectedRows: [],
searchValue: '',
initializing: true
initializing: true,
activeFilters: {}
};
},
components: {
@ -198,7 +224,9 @@ export default {
per_page: this.perPage,
page: this.page,
order: this.order,
search: this.searchValue
search: this.searchValue,
view_mode: this.currentViewMode,
filters: this.activeFilters
}
})
.then((response) => {
@ -264,6 +292,10 @@ export default {
if (e.column.colId !== 'rowMenu') {
e.node.setSelected(true);
}
},
applyFilters(filters) {
this.activeFilters = filters;
this.loadData();
}
}
};

View file

@ -1,5 +1,5 @@
<template>
<div class="flex py-4 items-center">
<div class="flex py-4 items-center justify-between">
<div class="flex items-center gap-4">
<a v-for="action in toolbarActions.left" :key="action.label"
:class="action.buttonStyle"
@ -9,7 +9,19 @@
{{ action.label }}
</a>
</div>
<div class="ml-auto flex items-center gap-4">
<div>
<div v-if="archivedPageUrl" class="flex items-center gap-4">
<MenuDropdown
:listItems="this.viewModes"
:btnClasses="'btn btn-light icon-btn'"
:btnText="i18n.t(`projects.index.${currentViewMode}`)"
:caret="true"
:position="'right'"
@open="loadActions"
></MenuDropdown>
</div>
</div>
<div class="flex items-center gap-4">
<a v-for="action in toolbarActions.right" :key="action.label"
:class="action.buttonStyle"
:href="action.path"
@ -17,7 +29,7 @@
<i :class="action.icon"></i>
{{ action.label }}
</a>
<div class="sci-input-container-v2" :class="{'w-48': showSearch, 'w-11': !showSearch}">
<div v-if="filters.length == 0" class="sci-input-container-v2" :class="{'w-48': showSearch, 'w-11': !showSearch}">
<input
ref="searchInput"
class="sci-input-field !pr-8"
@ -30,11 +42,15 @@
/>
<i class="sn-icon sn-icon-search !m-2.5 !ml-auto right-0"></i>
</div>
<FilterDropdown v-else :filters="filters" @applyFilters="applyFilters" />
</div>
</div>
</template>
<script>
import MenuDropdown from '../menu_dropdown.vue'
import FilterDropdown from '../filters/filter_dropdown.vue';
export default {
name: 'Toolbar',
props: {
@ -44,6 +60,32 @@ export default {
},
searchValue: {
type: String,
},
activePageUrl: {
type: String,
},
archivedPageUrl: {
type: String,
},
currentViewMode: {
type: String,
default: 'active'
},
filters: {
type: Array,
default: () => []
}
},
components: {
MenuDropdown,
FilterDropdown
},
computed: {
viewModes() {
return [
{ text: this.i18n.t('projects.index.active'), url: this.activePageUrl},
{ text: this.i18n.t('projects.index.archived'), url: this.archivedPageUrl }
]
}
},
data() {
@ -76,7 +118,10 @@ export default {
case 'link':
break;
}
}
},
applyFilters(filters) {
this.$emit('applyFilters', filters);
},
}
}
</script>

View file

@ -9,7 +9,7 @@
<i class="sn-icon sn-icon-close"></i>
</button>
</div>
<div class="sci-flyout-body max-h-[400px] overflow-y-auto perfect-scrollbar relative w-[calc(100%_+_1.125rem)] pr-5">
<div class="sci-flyout-body max-h-[400px] relative w-[calc(100%_+_1.125rem)] pr-5">
<div v-for="filter in filters" :key="filter.key + resetFilters" class="">
<Component :is="`${filter.type}Filter`" :filter="filter" :value="filterValues[filter.key]" @update="updateFilter" />
</div>

View file

@ -1,5 +1,11 @@
<template>
<div class="mb-6">TODO</div>
<div class="mb-6 flex gap-2 items-center">
<span class="sci-checkbox-container">
<input type="checkbox" v-model="value" class="sci-checkbox" />
<span class="sci-checkbox-label"></span>
</span>
<span class="sci-label">{{ filter.label }}</span>
</div>
</template>
<script>
@ -8,7 +14,15 @@
props: {
filter: { type: Object, required: true }
},
methods: {
data: function() {
return {
value: false
}
},
watch: {
value: function() {
this.$emit('update', { key: this.filter.key, value: this.value });
}
},
}
</script>

View file

@ -1,8 +1,8 @@
<template>
<div class="mb-6">
<div class="flex items-center gap-6 flex-col">
<div class="w-full">
<label class="sci-label">{{ filter.label }} ({{ i18n.t('general.from') }})</label>
<div class="flex flex-col">
<label class="sci-label">{{ filter.label }}</label>
<div class="w-full mb-2">
<DateTimePicker
class="w-full"
@change="updateDateFrom"
@ -12,7 +12,6 @@
/>
</div>
<div class="w-full">
<label class="sci-label">{{ filter.label }} ({{ i18n.t('general.to') }})</label>
<DateTimePicker
class="w-full"
@change="updateDateTo"

View file

@ -1,6 +1,11 @@
<template>
<div class="mb-6">
<inputWithHistory :modelValue="value" :label="filter.label" @update:modelValue="update" :placeholder="filter.placeholder" :id="'textSearch' + filter.key"/>
<inputWithHistory
:modelValue="value"
:label="filter.label || i18n.t('filters_modal.text.label')"
@update:modelValue="update"
:placeholder="filter.placeholder || i18n.t('filters_modal.text.placeholder')"
:id="'textSearch' + filter.key"/>
</div>
</template>

View file

@ -1,5 +1,5 @@
<template>
<div class="relative" v-if="listItems.length > 0 || alwaysShow" >
<div class="relative" v-if="listItems.length > 0 || alwaysShow" v-click-outside="closeMenu" >
<button ref="openBtn" :class="btnClasses" @click="showMenu = !showMenu">
<i v-if="btnIcon" :class="btnIcon"></i>
{{ btnText }}

View file

@ -43,7 +43,6 @@ class UserAssignment < ApplicationRecord
"#{user.name} - #{user_role.name}"
end
private
def set_assignable_team

View file

@ -38,14 +38,15 @@ module Lists
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)
actions: actions_toolbar_projects_path(items: [{ id: object.id,
type: project? ? 'projects' : 'project_folders' }].to_json)
}
end
private
def project?
object.class == Project
object.instance_of?(Project)
end
end
end

View file

@ -5,7 +5,7 @@ module Lists
@user = user
@current_folder = folder
@params = params
@view_mode = ''
@filters = params[:filters] || {}
end
def call
@ -15,10 +15,14 @@ module Lists
projects = filter_project_records(projects)
folders = filter_project_folder_records(folders)
if @filters[:folder_search].present? && @filters[:folder_search] == 'true'
records = projects
else
projects = projects.where(project_folder: @current_folder)
folders = folders.where(parent_folder: @current_folder)
records = projects + folders
end
records = sort_records(records)
paginate_records(records)
@ -50,31 +54,29 @@ module Lists
end
def filter_project_records(records)
return records
records = records.archived if @params[:view_mode] == 'archived'
records = records.active if @params[:view_mode] == 'active'
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])
if @filters[:query].present?
records = records.where_attributes_like(['projects.name', Project::PREFIXED_ID_SQL], @filters[:query])
end
if @params[:members].present?
records = records.joins(:user_assignments).where(user_assignments: { user_id: @params[:members] })
if @filters[:members].present?
records = records.joins(:user_assignments).where(user_assignments: { user_id: @filters[: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])
records = records.where('projects.created_at > ?', @filters[:created_on_from]) if @filters[:created_on_from].present?
records = records.where('projects.created_at < ?', @filters[:created_on_to]) if @filters[:created_on_to].present?
records = records.where('projects.archived_on < ?', @filters[:archived_on_to]) if @filters[:archived_on_to].present?
if @filters[:archived_on_from].present?
records = records.where('projects.archived_on > ?', @filters[: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 = records.where_attributes_like('project_folders.name', @filters[:query]) if @filters[:query].present?
records
end

View file

@ -2,10 +2,6 @@
<% provide(:sidebar_url, sidebar_team_path(current_team, project_folder_id: current_folder&.id)) %>
<% provide(:container_class, 'no-second-nav-container') %>
<%= content_for :sidebar do %>
<%= render partial: 'shared/sidebar/projects', locals: { team: current_team, sort: nil, view_mode: projects_view_mode } if current_team %>
<% end %>
<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} %>
@ -13,6 +9,9 @@
<projects-list
actions-url="<%= actions_toolbar_projects_url %>"
data-source="<%= projects_path(format: :json) %>"
active-page-url="<%= projects_path(view_mode: :active) %>"
archived-page-url="<%= projects_path(view_mode: :archived) %>"
current-view-mode="<%= params[:view_mode] || :active %>"
create-url="<%= projects_path if can_create_projects?(current_team) %>"
create-folder-url="<%= project_folders_path if can_create_project_folders?(current_team) %>"
/>