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 @@
+
+
+
+
+
+
+
+
{{ params.code }}
+
+
+
+ {{ params.name }}
+
+
+ {{ i18n.t('projects.index.card.start_date') }}
+ {{ params.created_at }}
+
+
+ {{ i18n.t('projects.index.card.archived_date') }}
+ {{ params.archived_on }}
+
+ {{ i18n.t('projects.index.card.visibility') }}
+ {{ params.hidden ? i18n.t('projects.index.hidden') : i18n.t('projects.index.visible') }}
+
+ {{ i18n.t('projects.index.card.users') }}
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
{{ i18n.t("projects.index.modal_edit_folder.folder_name_field") }}
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
{{ this.description }}
+
+
+
+
+ {{ i18n.t('projects.index.modal_move_folder.projects') }}
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+ {{ object.folder.name }}
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
{{ i18n.t("projects.index.modal_new_project.name") }}
+
+
+
+
+
+
+ {{ i18n.t("user_assignment.select_default_user_role") }}
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
{{ i18n.t('projects.index.modal_new_project_folder.name') }}
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
-
-
-
- {{ valueLabel || (placeholder || i18n.t('general.select')) }}
-
-
-
-
-
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' %>
-
-
-
-
- <% 4.times do |i| %>
-
- <% end %>
- <% 3.times do |i| %>
-
- <% end %>
-
-
-
-
-
-
- <%= t('.end_of_list_placeholder') %>
-
-
-
-
-<%= 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 @@
Projects
- <%= render partial: 'projects/index/modals/move_to_folders_tree',
- locals: { records: sidebar_folders_tree(current_team, current_user, @current_sort, folders_only: true) } %>
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? %>