From 9971de5c4c66ffb842c2542b4e20b99afd82fe66 Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 1 Dec 2023 00:01:08 +0100 Subject: [PATCH] Finalize projects table --- .../shared_styles/constants/shadows.scss | 3 + app/assets/stylesheets/tailwind/inputs.css | 9 + app/controllers/project_folders_controller.rb | 24 +- app/controllers/projects_controller.rb | 12 +- app/helpers/projects_helper.rb | 27 +-- app/javascript/vue/projects/card.vue | 76 ++++++ app/javascript/vue/projects/list.vue | 228 ++++++++++++++---- app/javascript/vue/projects/modals/edit.vue | 83 +++++++ .../vue/projects/modals/edit_folder.vue | 64 +++++ app/javascript/vue/projects/modals/move.vue | 125 ++++++++++ .../vue/projects/modals/move_tree.vue | 37 +++ app/javascript/vue/projects/modals/new.vue | 85 +++++++ .../vue/projects/modals/new_folder.vue | 72 ++++++ .../repository_values/RepositoryListValue.vue | 2 +- .../RepositoryStatusValue.vue | 2 +- .../shared/datatable/mixins/card_selector.js | 17 ++ .../shared/datatable/row_menu_renderer.vue | 12 +- app/javascript/vue/shared/datatable/table.vue | 36 ++- .../vue/shared/datatable/toolbar.vue | 39 ++- .../shared/filters/inputs/select_filter.vue | 37 ++- app/javascript/vue/shared/menu_dropdown.vue | 3 +- app/javascript/vue/shared/modal_mixin.js | 17 ++ app/javascript/vue/shared/select_dropdown.vue | 20 +- app/javascript/vue/shared/select_search.vue | 104 -------- .../lists/project_and_folder_serializer.rb | 23 +- app/services/lists/projects_service.rb | 4 +- app/services/toolbars/projects_service.rb | 53 ++-- app/views/projects/index.html.erb | 71 +----- .../modals/_move_to_modal_contents.html.erb | 2 - app/views/shared/sidebar/_projects.html.erb | 1 - config/locales/en.yml | 6 +- config/routes.rb | 2 + 32 files changed, 996 insertions(+), 300 deletions(-) create mode 100644 app/javascript/vue/projects/card.vue create mode 100644 app/javascript/vue/projects/modals/edit.vue create mode 100644 app/javascript/vue/projects/modals/edit_folder.vue create mode 100644 app/javascript/vue/projects/modals/move.vue create mode 100644 app/javascript/vue/projects/modals/move_tree.vue create mode 100644 app/javascript/vue/projects/modals/new.vue create mode 100644 app/javascript/vue/projects/modals/new_folder.vue create mode 100644 app/javascript/vue/shared/datatable/mixins/card_selector.js create mode 100644 app/javascript/vue/shared/modal_mixin.js delete mode 100644 app/javascript/vue/shared/select_search.vue diff --git a/app/assets/stylesheets/shared_styles/constants/shadows.scss b/app/assets/stylesheets/shared_styles/constants/shadows.scss index faf50adbb..25a99b51c 100644 --- a/app/assets/stylesheets/shared_styles/constants/shadows.scss +++ b/app/assets/stylesheets/shared_styles/constants/shadows.scss @@ -1,6 +1,9 @@ $flyout-shadow: 0px 1px 4px rgba(35, 31, 32, 0.15); $modal-shadow: 0px 4px 16px rgba(35, 31, 32, 0.15); +.sn-shadow-flyout { + box-shadow: $flyout-shadow; +} .sn-shadow-menu-sm { box-shadow: 0px 16px 32px 0px rgba(16, 24, 40, 0.07); diff --git a/app/assets/stylesheets/tailwind/inputs.css b/app/assets/stylesheets/tailwind/inputs.css index 1b65d7d72..b913004f4 100644 --- a/app/assets/stylesheets/tailwind/inputs.css +++ b/app/assets/stylesheets/tailwind/inputs.css @@ -77,4 +77,13 @@ .sci-input-container-v2 .history-flyout li:hover { @apply bg-sn-super-light-grey; } + + .sci-input-container-v2.error input { + @apply border-sn-alert-passion; + } + + .sci-input-container-v2.error::after { + @apply absolute -bottom-5 text-sn-alert-passion text-xs; + content: attr(data-error); + } } diff --git a/app/controllers/project_folders_controller.rb b/app/controllers/project_folders_controller.rb index bd4577ec9..dd4b34fd5 100644 --- a/app/controllers/project_folders_controller.rb +++ b/app/controllers/project_folders_controller.rb @@ -13,6 +13,10 @@ class ProjectFoldersController < ApplicationController before_action :check_create_permissions, only: %i(new create) before_action :check_manage_permissions, only: %i(archive move_to) + def tree + render json: folders_tree(current_team, current_user) + end + def new @project_folder = current_team.project_folders.new(parent_folder: current_folder, archived: projects_view_mode_archived?) @@ -48,11 +52,11 @@ class ProjectFoldersController < ApplicationController move_projects(destination_folder) move_folders(destination_folder) end - render json: { flash: I18n.t('projects.move.success_flash') } + render json: { message: I18n.t('projects.move.success_flash') } rescue StandardError => e Rails.logger.error e.message Rails.logger.error e.backtrace.join("\n") - render json: { flash: I18n.t('projects.move.error_flash') }, status: :bad_request + render json: { error: I18n.t('projects.move.error_flash') }, status: :bad_request end def move_to_modal @@ -109,7 +113,7 @@ class ProjectFoldersController < ApplicationController if counter.positive? render json: { message: t('projects.delete_folders.success_flash', number: counter) } else - render json: { message: t('projects.delete_folders.error_flash') }, status: :unprocessable_entity + render json: { error: t('projects.delete_folders.error_flash') }, status: :unprocessable_entity end end @@ -132,13 +136,9 @@ class ProjectFoldersController < ApplicationController end def move_params - parsed_params = ActionController::Parameters.new( - movables: JSON.parse(params[:movables]), - destination_folder_id: params[:destination_folder_id] - ) - parsed_params.require(:destination_folder_id) - parsed_params.require(:movables) - parsed_params.permit(:destination_folder_id, movables: %i(id type)) + params.require(:destination_folder_id) + params.require(:movables) + params.permit(:destination_folder_id, movables: %i(id type)) end def check_create_permissions @@ -150,7 +150,7 @@ class ProjectFoldersController < ApplicationController end def move_projects(destination_folder) - project_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project' }.compact + project_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'projects' }.compact return if project_ids.blank? current_team.projects.where(id: project_ids).each do |project| @@ -167,7 +167,7 @@ class ProjectFoldersController < ApplicationController end def move_folders(destination_folder) - folder_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project_folder' }.compact + folder_ids = move_params[:movables].collect { |movable| movable[:id] if movable[:type] == 'project_folders' }.compact return if folder_ids.blank? current_team.project_folders.where(id: folder_ids).each do |folder| diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 1c7577314..29496dfb9 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -8,6 +8,7 @@ class ProjectsController < ApplicationController include CardsViewHelper include ExperimentsHelper include Breadcrumbs + include UserRolesHelper attr_reader :current_folder @@ -19,7 +20,7 @@ class ProjectsController < ApplicationController before_action :load_current_folder, only: %i(index cards new show) before_action :check_view_permissions, except: %i(index cards new create edit update archive_group restore_group users_filter actions_dropdown inventory_assigning_project_filter - actions_toolbar) + actions_toolbar user_roles) before_action :check_create_permissions, only: %i(new create) before_action :check_manage_permissions, only: :edit before_action :load_exp_sort_var, only: :show @@ -363,10 +364,10 @@ class ProjectsController < ApplicationController def users_filter users = current_team.users.search(false, params[:query]).map do |u| - { value: u.id, label: escape_input(u.name), params: { avatar_url: avatar_path(u, :icon_small) } } + [u.id, u.name, { avatar_url: avatar_path(u, :icon_small) }] end - render json: users, status: :ok + render json: { data: users }, status: :ok end def view_type @@ -388,6 +389,11 @@ class ProjectsController < ApplicationController end end + def user_roles + render json: { data: user_roles_collection(Project.new).map(&:reverse) } + end + + def actions_toolbar render json: { actions: diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a9eefeb6b..4b1351bc1 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -30,34 +30,13 @@ module ProjectsHelper conns.to_s[1..-2] end - def sidebar_folders_tree(team, user, sort, folders_only: false) + def folders_tree(team, user) sort ||= team.current_view_state(user).state.dig('projects', 'active', 'sort') if projects_view_mode_archived? - records = ProjectFolder.archived.inner_folders(team) - records += team.projects.archived.visible_to(user, team) unless folders_only + records = ProjectFolder.archived.inner_folders(team).order(:name).select(:id, :name, :parent_folder_id) else - records = ProjectFolder.active.inner_folders(team) - records += team.projects.active.visible_to(user, team) unless folders_only - sort = 'new' if %w(archived_old archived_new).include?(sort) + records = ProjectFolder.active.inner_folders(team).order(:name).select(:id, :name, :parent_folder_id) end - records = case sort - when 'new' - records.sort_by(&:created_at).reverse! - when 'old' - records.sort_by(&:created_at) - when 'atoz' - records.sort_by { |c| c.name.downcase } - when 'ztoa' - records.sort_by { |c| c.name.downcase }.reverse! - when 'id_asc' - records.sort_by(&:id) - when 'id_desc' - records.sort_by(&:id).reverse! - when 'archived_old' - records.sort_by(&:archived_on) - when 'archived_new' - records.sort_by(&:archived_on).reverse! - end folders_recursive_builder(nil, records) end diff --git a/app/javascript/vue/projects/card.vue b/app/javascript/vue/projects/card.vue new file mode 100644 index 000000000..d68f61296 --- /dev/null +++ b/app/javascript/vue/projects/card.vue @@ -0,0 +1,76 @@ + + + diff --git a/app/javascript/vue/projects/list.vue b/app/javascript/vue/projects/list.vue index e85f66b7c..68a6fccd3 100644 --- a/app/javascript/vue/projects/list.vue +++ b/app/javascript/vue/projects/list.vue @@ -1,19 +1,58 @@ diff --git a/app/javascript/vue/projects/modals/edit_folder.vue b/app/javascript/vue/projects/modals/edit_folder.vue new file mode 100644 index 000000000..bd761ce6b --- /dev/null +++ b/app/javascript/vue/projects/modals/edit_folder.vue @@ -0,0 +1,64 @@ + + + diff --git a/app/javascript/vue/projects/modals/move.vue b/app/javascript/vue/projects/modals/move.vue new file mode 100644 index 000000000..4f4c15837 --- /dev/null +++ b/app/javascript/vue/projects/modals/move.vue @@ -0,0 +1,125 @@ + + + diff --git a/app/javascript/vue/projects/modals/move_tree.vue b/app/javascript/vue/projects/modals/move_tree.vue new file mode 100644 index 000000000..d85f6e1b1 --- /dev/null +++ b/app/javascript/vue/projects/modals/move_tree.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/javascript/vue/projects/modals/new.vue b/app/javascript/vue/projects/modals/new.vue new file mode 100644 index 000000000..68bbc386a --- /dev/null +++ b/app/javascript/vue/projects/modals/new.vue @@ -0,0 +1,85 @@ + + + diff --git a/app/javascript/vue/projects/modals/new_folder.vue b/app/javascript/vue/projects/modals/new_folder.vue new file mode 100644 index 000000000..ec5b72a77 --- /dev/null +++ b/app/javascript/vue/projects/modals/new_folder.vue @@ -0,0 +1,72 @@ + + + diff --git a/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryListValue.vue b/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryListValue.vue index 092cb1644..16a4bcc81 100644 --- a/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryListValue.vue +++ b/app/javascript/vue/repository_item_sidebar/repository_values/RepositoryListValue.vue @@ -35,7 +35,7 @@ diff --git a/app/javascript/vue/shared/menu_dropdown.vue b/app/javascript/vue/shared/menu_dropdown.vue index feaf638e6..1d3e3f2b8 100644 --- a/app/javascript/vue/shared/menu_dropdown.vue +++ b/app/javascript/vue/shared/menu_dropdown.vue @@ -114,7 +114,8 @@ export default { } if (item.emit) { - this.$emit(item.emit, item.params) + this.$emit(item.emit, item.params); + this.$emit('dtEvent', item.emit, item); } this.closeMenu(); diff --git a/app/javascript/vue/shared/modal_mixin.js b/app/javascript/vue/shared/modal_mixin.js new file mode 100644 index 000000000..9eaa3fdfc --- /dev/null +++ b/app/javascript/vue/shared/modal_mixin.js @@ -0,0 +1,17 @@ +export default { + mounted() { + $(this.$refs.modal).modal('show'); + $(this.$refs.modal).on('hidden.bs.modal', () => { + this.$emit('close'); + }); + }, + beforeUnmount() { + $(this.$refs.modal).modal('hide'); + }, + methods: { + close() { + this.$emit('close'); + $(this.$refs.modal).modal('hide'); + } + }, +} diff --git a/app/javascript/vue/shared/select_dropdown.vue b/app/javascript/vue/shared/select_dropdown.vue index 7ef90b655..feb41876f 100644 --- a/app/javascript/vue/shared/select_dropdown.vue +++ b/app/javascript/vue/shared/select_dropdown.vue @@ -153,6 +153,13 @@ export default { return `${this.newValue.length} ${this.fewOptionsPlaceholder || this.i18n.t('general.select_dropdown.few_options_placeholder')}` } }, + valueChanged() { + if (this.multiple) { + return !this.compareArrays(this.newValue, this.value) + } else { + return this.newValue != this.value + } + } }, mounted() { document.addEventListener('scroll', this.setPosition); @@ -205,8 +212,10 @@ export default { this.$emit('change', this.newValue) }, close() { + if (!this.isOpen) return; + this.isOpen = false - if (this.newValue != this.value) { + if (this.valueChanged) { this.$emit('change', this.newValue) } this.query = ''; @@ -274,6 +283,15 @@ export default { }) }) } + }, + compareArrays(arr1, arr2) { + if (!arr1 || !arr2) return false; + if (arr1.length !== arr2.length) return false; + + for (let i = 0; i < arr1.length; i++) { + if (!arr2.includes(arr1[i])) return false; + } + return true; } }, } diff --git a/app/javascript/vue/shared/select_search.vue b/app/javascript/vue/shared/select_search.vue deleted file mode 100644 index 5d72dd646..000000000 --- a/app/javascript/vue/shared/select_search.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - diff --git a/app/serializers/lists/project_and_folder_serializer.rb b/app/serializers/lists/project_and_folder_serializer.rb index cb47ad562..bfdedb4ce 100644 --- a/app/serializers/lists/project_and_folder_serializer.rb +++ b/app/serializers/lists/project_and_folder_serializer.rb @@ -2,12 +2,16 @@ module Lists class ProjectAndFolderSerializer < ActiveModel::Serializer include Rails.application.routes.url_helpers - attributes :name, :code, :created_at, :archived_on, :users, :hidden, :urls, :folder + attributes :name, :code, :created_at, :archived_on, :users, :hidden, :urls, :folder, :folder_info, :default_public_user_role_id def folder !project? end + def default_public_user_role_id + object.default_public_user_role_id if project? + end + def code object.code if project? end @@ -36,11 +40,26 @@ module Lists end def urls - { + urls_list = { 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) } + + if project? + urls_list[:update] = project_path(object) + else + urls_list[:update] = project_folder_path(object) + end + + + urls_list + end + + def folder_info + if folder + I18n.t('projects.index.folder.description', projects_count: object.projects_count, folders_count: object.folders_count) + end end private diff --git a/app/services/lists/projects_service.rb b/app/services/lists/projects_service.rb index fab926639..899cd690a 100644 --- a/app/services/lists/projects_service.rb +++ b/app/services/lists/projects_service.rb @@ -80,8 +80,8 @@ module Lists end def filter_project_folder_records(records) - records = records.archived if @view_mode == 'archived' - records = records.active if @view_mode == 'active' + records = records.archived if @params[:view_mode] == 'archived' + records = records.active if @params[:view_mode] == 'active' records = records.where_attributes_like('project_folders.name', @filters[:query]) if @filters[:query].present? records end diff --git a/app/services/toolbars/projects_service.rb b/app/services/toolbars/projects_service.rb index fa6e99a6d..bdddfeb1d 100644 --- a/app/services/toolbars/projects_service.rb +++ b/app/services/toolbars/projects_service.rb @@ -51,33 +51,21 @@ module Toolbars def edit_action return unless @single + action = { + name: 'edit', + label: I18n.t('projects.index.edit_option'), + icon: 'sn-icon sn-icon-edit', + button_class: 'edit-btn', + type: :emit + } + if @items.first.is_a?(Project) - project = @items.first - - return unless can_manage_project?(project) - - { - name: 'edit', - label: I18n.t('projects.index.edit_option'), - icon: 'sn-icon sn-icon-edit', - button_class: 'edit-btn', - path: edit_project_path(project), - type: :emit - } + return unless can_manage_project?(@items.first) else - project_folder = @items.first - - return unless can_create_project_folders?(project_folder.team) - - { - name: 'edit', - label: I18n.t('projects.index.edit_option'), - icon: 'sn-icon sn-icon-edit', - button_class: 'edit-btn', - path: edit_project_folder_path(project_folder), - type: :emit - } + return unless can_create_project_folders?(@items.first.team) end + + action end def access_action @@ -120,11 +108,24 @@ module Toolbars def export_action return unless @items.all? { |item| item.is_a?(Project) ? can_export_project?(item) : true } + num_projects = @items.length + limit = TeamZipExport.exports_limit + num_of_requests_left = @current_user.exports_left - 1 + team = @items.first.team + + message = "

#{I18n.t('projects.export_projects.modal_text_p1_html', num_projects: num_projects, team: team)}

+

#{I18n.t('projects.export_projects.modal_text_p2_html')}

" + unless limit.zero? + message += "

#{I18n.t('projects.export_projects.modal_text_p3_html', limit: limit, num: num_of_requests_left)}

" + end + { + items: @items, name: 'export', label: I18n.t('projects.export_projects.export_button'), icon: 'sn-icon sn-icon-export', - path: export_projects_modal_team_path(@items.first.team), + message: message, + path: export_projects_team_path(team), type: :emit } end @@ -167,7 +168,7 @@ module Toolbars name: 'delete_folders', label: I18n.t('general.delete'), icon: 'sn-icon sn-icon-delete', - path: destroy_modal_project_folders_path(project_folder_ids: @items.map(&:id)), + path: destroy_project_folders_path, type: :emit } end diff --git a/app/views/projects/index.html.erb b/app/views/projects/index.html.erb index b80c5da30..3b5cb1e19 100644 --- a/app/views/projects/index.html.erb +++ b/app/views/projects/index.html.erb @@ -7,74 +7,21 @@
<%= javascript_include_tag 'vue_projects_list' %> - - -
- <%= render partial: 'projects/index/toolbar' %> -
- - - <%= render partial: 'projects/index/modals/edit_modal' %> - <%= render partial: 'projects/index/modals/move_to_modal' %> - <%= render partial: 'projects/index/modals/manage_users' %> - <%= render partial: 'projects/index/modals/export_projects' %> - -
-
-
-
-
- - -
-
-
<%= t('.card.name') %>
-
<%= t('.card.id') %>
-
<%= t('.card.start_date') %>
-
<%= t('.card.archived_date') %>
-
<%= t('.card.visibility') %>
-
<%= t('.card.users') %>
-
-
-
- -
- -
-
- - - - -<%= javascript_include_tag "vue_components_action_toolbar" %> -<%= javascript_include_tag "projects/index" %> diff --git a/app/views/projects/index/modals/_move_to_modal_contents.html.erb b/app/views/projects/index/modals/_move_to_modal_contents.html.erb index 84d92cac1..fbcf25740 100644 --- a/app/views/projects/index/modals/_move_to_modal_contents.html.erb +++ b/app/views/projects/index/modals/_move_to_modal_contents.html.erb @@ -19,8 +19,6 @@ diff --git a/app/views/shared/sidebar/_projects.html.erb b/app/views/shared/sidebar/_projects.html.erb index 79a833990..35284f942 100644 --- a/app/views/shared/sidebar/_projects.html.erb +++ b/app/views/shared/sidebar/_projects.html.erb @@ -6,7 +6,6 @@ <% end %> <% end %> - <%= render partial: 'shared/sidebar/projects_tree_branch', locals: { records: sidebar_folders_tree(team, current_user, sort) } %> <% if !projects_view_mode_archived? %>