create form object for role assigment, create helper object for project member, added new user_projects modal

This commit is contained in:
zmagoD 2021-04-24 18:43:28 +02:00
parent 04ef7a4478
commit c136d953d0
20 changed files with 280 additions and 66 deletions

View file

@ -44,6 +44,7 @@
//= require shared/remote_modal
//= require shared/swap_remote_container
//= require shared/autosave_form
//= require shared/replace_form
//= require activestorage
//= require global_activities/side_pane
//= require protocols/header

View file

@ -229,23 +229,6 @@
manageProjectUsersModal.find('.modal-footer').html(data.html_footer);
}
// Initialize manage project users modal remote loading.
function initManageProjectUsersLink() {
$(projectsWrapper).on('ajax:success', '.manage-project-users-link', function(e, data) {
initManageProjectUsersModalBody(data);
manageProjectUsersModal.modal('show');
});
}
// Initialize view project users modal remote loading.
function initViewProjectUsersLink() {
$('#cardsWrapper').on('ajax:success', '.view-project-users-link', function(e, data) {
let viewProjectUsersModal = $('#viewProjectUsersModal');
viewProjectUsersModal.find('.modal-title').html(data.html_title);
viewProjectUsersModal.find('.modal-body').html(data.html_body);
viewProjectUsersModal.modal('show');
});
}
// Initialize reloading manage user modal content after posting new
// user.
@ -637,8 +620,6 @@
initExportProjectsModal();
initExportProjects();
initArchiveRestoreToolbarButtons();
initViewProjectUsersLink();
initManageProjectUsersLink();
initAddUserForm();
initRemoveUserLinks();
initUserRoleForms();

View file

@ -7,5 +7,5 @@
})
}
$(document).on('turbolinks:load', initAutosaveListeners);
$(document).one('turbolinks:load', initAutosaveListeners);
})();

View file

@ -2,10 +2,11 @@
'use strict';
function initRemoteModalListeners() {
$(document).on('click', 'a[data-action="remote-modal"]', function(el) {
el.stopPropagation();
el.preventDefault();
$.get(el.target.getAttribute('href')).then(function({modal}) {
$(document).on('click', 'a[data-action="remote-modal"]', function(ev) {
ev.stopImmediatePropagation();
ev.stopPropagation();
ev.preventDefault();
$.get(ev.currentTarget.getAttribute('href')).then(function({modal}) {
$(modal).modal('show')
.on("shown.bs.modal", function() {
$(this).find(".selectpicker").selectpicker();
@ -14,5 +15,5 @@
})
}
$(document).on('turbolinks:load', initRemoteModalListeners);
$(document).one('turbolinks:load', initRemoteModalListeners);
})();

View file

@ -0,0 +1,13 @@
(function () {
'use strict';
function initReplaceFormListeners() {
$(document).on('ajax:success', 'form[data-action="replace-form"]', function({ form }) {
let newForm = $(form)
$(this).replaceWith(newForm);
newForm.find('.selectpicker').selectpicker();
})
}
$(document).one('turbolinks:load', initReplaceFormListeners);
})();

View file

@ -2,17 +2,21 @@
'use strict';
function initSwapRemoteContainerListeners() {
$(document).on('click', 'a[data-action="swap-remote-container"]', function(el) {
let element = el.target;
el.stopPropagation();
el.preventDefault();
$(document).on('click', 'a[data-action="swap-remote-container"]', function(ev) {
let element = ev.currentTarget;
ev.stopImmediatePropagation();
ev.stopPropagation();
ev.preventDefault();
$.get(element.getAttribute('href')).then(function({html}) {
let target = element.getAttribute('data-target')
document.getElementById(target).insertAdjacentHTML(html)
let targetID = element.getAttribute('data-target')
let targetElement = $(element).closest(targetID)
let newContainer = $(html)
targetElement.replaceWith(newContainer)
newContainer.find('.selectpicker').selectpicker();
})
})
}
$(document).on('turbolinks:load', initSwapRemoteContainerListeners);
$(document).one('turbolinks:load', initSwapRemoteContainerListeners);
})();

View file

@ -6,6 +6,19 @@ module AccessPermissions
before_action :check_read_permissions, only: %i[show]
before_action :check_manage_permissions, only: %i[new create edit update destroy]
def new
available_users = current_team.users.where.not(id: @project.users.pluck(:id))
@form = AccessPermissions::NewUserProjectForm.new(
current_user,
@project,
users: available_users
)
respond_to do |format|
format.json
end
end
def show
respond_to do |format|
format.json
@ -30,6 +43,17 @@ module AccessPermissions
end
end
def create
@form = AccessPermissions::NewUserProjectForm.new(current_user, @project)
@form.resource_members = permitted_create_params
flash[:notice] = "Success" if @form.save
respond_to do |format|
format.json :new
end
end
private
def permitted_update_params
@ -37,6 +61,11 @@ module AccessPermissions
.permit(user_assignments_attributes: %i[user_role_id _destroy id])
end
def permitted_create_params
params.require(:access_permissions_new_user_project_form)
.permit(resource_members: %i[assign user_id user_role_id])
end
def set_project
@project = Project.includes(user_assignments: [:user, :user_role]).find_by(id: params[:id])

View file

@ -0,0 +1,48 @@
# frozen_string_literal: true
module AccessPermissions
class NewUserProjectForm
include ActiveModel::Model
attr_accessor :project, :resource_members
attr_reader :current_user
def initialize(current_user, project, attributes = {})
@project = project
@users = attributes[:users]
@current_user = current_user
set_defaults if @users
@error = false
end
def save
if @error
false
else
@resource_members.map(&:save)
true
end
end
def resource_members=(attributes)
@resource_members ||= []
attributes.fetch(:resource_members).each do |i, resource_member|
user = User.find(resource_member[:user_id])
project_member = ProjectMember.new(user, @project, current_user)
project_member.assign = resource_member[:assign]
project_member.user_role_id = resource_member[:user_role_id]
byebug
@error = true unless project_member.valid?
@resource_members << project_member
end
end
private
def set_defaults
@resource_members ||= @users.order(:full_name).map { |u| ProjectMember.new(u, @project, current_user) }
end
end
end

View file

@ -3,7 +3,7 @@
module UserRolesHelper
def user_roles_collection
Rails.cache.fetch([current_user, 'available_user_roles']) do
@user_roles_collection ||= UserRole.all.pluck(:name, :id)
@user_roles_collection ||= [[t('user_assignment.select.default_option'), nil]] + UserRole.all.pluck(:name, :id)
end
end
end

View file

@ -0,0 +1,54 @@
# frozen_string_literal: true
class ProjectMember
include ActiveModel::Model
attr_accessor :user, :project, :assign, :user_role_id, :user_id
attr_reader :current_user
validates :user, :project, :user_role_id, presence: true
validate :role_presence
validate :validate_user_project_relation_presence
validate :validate_user_project_assignment_presence
def initialize(user, project, current_user)
@user = user
@project = project
@current_user = current_user
end
def save
return unless assign
ActiveRecord::Base.transaction do
UserProject.create!(project: @project, user: @user)
UserAssignment.create!(assignable: @project, user: @user, user_role: set_user_role, assigned_by: current_user)
end
end
def assign=(value)
@assign = ActiveModel::Type::Boolean.new.cast(value)
end
private
def set_user_role
UserRole.find!(user_role_id)
end
def role_presence
errors.add(:user_role_id) if UserRole.find(user_role_id).nil?
end
def validate_user_project_relation_presence
if UserProject.find_by(project: @project, user: @user).present?
errors.add(:user)
end
end
def validate_user_project_assignment_presence
if UserAssignment.find_by(assignable: @project, user: @user).present?
errors.add(:user_role_id)
end
end
end

View file

@ -1,7 +1,7 @@
<% # frozen_string_literal: true %>
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="user_assignments_modal">
<%= form_with(model: resource, url: update_path, remote: true, html: { data: { action: 'autosave-form' } }) do |f| %>
<div class="modal-header">
@ -10,7 +10,7 @@
</div>
<div class="modal-body">
<%= f.nested_fields_for :user_assignments, resource.user_assignments, wrapper_options: { class: 'row' } do |user_assignment_form| %>
<%= render('access_permissions/partials/user_assignment_field.html.erb', f: user_assignment_form) if user_assignment_form.object.persisted? %>
<%= render('access_permissions/partials/user_permission_field.html.erb', f: user_assignment_form) if user_assignment_form.object.persisted? %>
<% end %>
</div>
<div class="modal-footer">

View file

@ -1,7 +1,7 @@
<% # frozen_string_literal: true %>
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="user_assignments_modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
@ -11,7 +11,12 @@
<%= render partial: 'access_permissions/partials/user_assignment', collection: resource.user_assignments, cached: ->(user_assignment) { [user_assignment. resource, current_user] } %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
<% if can_manage_resource %>
<%= link_to new_resource_path, class: 'btn btn-default pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
<i class="fas fa-plus"></i>
<%= t '.new_resource_assignments', resource: resource.model_name.human.downcase %>
<% end %>
<% end %>
</div>
</div>
</div>

View file

@ -0,0 +1,25 @@
<% # frozen_string_literal: true %>
<div class="modal-content" id="user_assignments_modal">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
<%= link_to resource_path, class: 'pull-left', data: { action: 'swap-remote-container', target: '#user_assignments_modal' } do %>
<i class="fas fa-arrow-left"></i>
<% end %>
<%= t '.title', resource_name: resource.name %>
</h4>
</div>
<%= form_with(model: form_object, url: create_path, method: :post, remote: true, html: { data: { action: 'replace-form' } }) do |f| %>
<div class="modal-body">
<% f.object.resource_members.each do |member| %>
<%= f.fields_for :resource_members, member do |member_form| %>
<%= render 'access_permissions/partials/user_assignment_field.html.erb', f: member_form %>
<% end %>
<% end %>
</div>
<div class="modal-footer">
<%= f.submit t('.submit'), class: "btn btn-primary" %>
</div>
<% end %>
</div>

View file

@ -1,31 +1,28 @@
<% # frozen_string_literal: true %>
<% user_assignment = f.object %>
<% project_member = f.object %>
<% user = project_member.user %>
<div class="col-xs-2">
<span class="global-avatar-container">
<%= image_tag avatar_path(user_assignment.user, :icon_small), title: current_assignee_name(user_assignment.user), class: 'img-circle pull-left' %>
</span>
</div>
<div class="row">
<%= f.hidden_field :user_id, value: user.id, name:"access_permissions_new_user_project_form[resource_members][#{user.id}][user_id]" %>
<div class="col-xs-1 checkbox text-center">
<label>
<%= f.check_box :assign, name: "access_permissions_new_user_project_form[resource_members][#{user.id}][assign]" %>
</label>
</div>
<div class="col-xs-1">
<span class="global-avatar-container">
<%= image_tag avatar_path(user, :icon_small), title: current_assignee_name(user), class: 'img-circle pull-left' %>
</span>
</div>
<div class="col-xs-6">
<span><%= current_assignee_name(user) %></span>
</div>
<div class="col-xs-3">
<span><%= current_assignee_name(user_assignment.user) %></span>
<br>
<small class="text-muted"><%= user_assignment.user_role.name %></small>
</div>
<div class="col-xs-7">
<div class="row">
<div class="col-xs-7">
<% unless user_assignment.user == current_user %>
<%= f.select :user_role_id, options_for_select(user_roles_collection), {}, class: 'form-control selectpicker' %>
<% end %>
</div>
<div class="col-xs-5">
<%= f.remove_nested_fields_link class: 'btn btn-link' do %>
<span class="fas fa-times"></span>
<%= t 'general.remove' %>
<% end %>
</div>
<div class="col-xs-4 form-group">
<% unless user == current_user %>
<%= f.select :user_role_id, options_for_select(user_roles_collection), {}, name: "access_permissions_new_user_project_form[resource_members][#{user.id}][user_role_id]", class: 'form-control selectpicker pull-right' %>
<% end %>
</div>
</div>
<hr />

View file

@ -0,0 +1,28 @@
<% # frozen_string_literal: true %>
<% user_assignment = f.object %>
<div class="col-xs-2">
<span class="global-avatar-container">
<%= image_tag avatar_path(user_assignment.user, :icon_small), title: current_assignee_name(user_assignment.user), class: 'img-circle pull-left' %>
</span>
</div>
<div class="col-xs-3">
<span><%= current_assignee_name(user_assignment.user) %></span>
<br>
<small class="text-muted"><%= user_assignment.user_role.name %></small>
</div>
<div class="col-xs-7">
<div class="row">
<div class="col-xs-7">
<% unless user_assignment.user == current_user %>
<%= f.select :user_role_id, options_for_select(user_roles_collection), {}, class: 'form-control selectpicker' %>
<% end %>
</div>
<div class="col-xs-5">
<%= f.remove_nested_fields_link class: 'btn btn-link' do %>
<span class="fas fa-times"></span>
<%= t 'general.remove' %>
<% end %>
</div>
</div>
</div>

View file

@ -3,6 +3,10 @@
json.modal controller.render_to_string(
partial: 'access_permissions/modals/edit_modal',
formats: [:html],
locals: { resource: @project, update_path: access_permissions_project_path(@project, format: :json), new_resource_path: new_access_permissions_project_path },
locals: {
resource: @project,
update_path: access_permissions_project_path(@project, format: :json),
new_resource_path: new_access_permissions_project_path(id: @project, format: :json)
},
layout: false
)

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
json.html controller.render_to_string(
partial: 'access_permissions/partials/new_assignments_form',
formats: [:html],
locals: {
resource: @project,
form_object: @form,
create_path: access_permissions_projects_path(id: @project.id, format: :json),
resource_path: edit_access_permissions_project_path(@project, format: :json)
},
layout: false
)

View file

@ -3,6 +3,9 @@
json.modal controller.render_to_string(
partial: 'access_permissions/modals/show_modal',
formats: [:html],
locals: { resource: @project },
locals: {
resource: @project,
can_manage_resource: can_manage_project?(@project)
},
layout: false
)

View file

@ -51,14 +51,14 @@
<span class="card-label"><%= t('projects.index.card.users') %></span>
<div class="value">
<% if can_manage_project?(project) %>
<%= link_to edit_project_users_path(project), class: 'project-users-link manage-project-users-link', remote: true do %>
<%= link_to edit_access_permissions_project_path(project, format: :json), class: 'project-users-link', data: { action: 'remote-modal' } do %>
<%= render partial: 'projects/index/users_list.html.erb', locals: { project: project } %>
<span class="new-user global-avatar-container">
<i class="fas fa-plus"></i>
</span>
<% end %>
<% else %>
<%= link_to project_users_path(project), class: 'project-users-link view-project-users-link', remote: true do %>
<%= link_to access_permissions_project_path(project, format: :json), class: 'project-users-link', data: { action: 'remote-modal' } do %>
<%= render partial: 'projects/index/users_list.html.erb', locals: { project: project } %>
<% end %>
<% end %>

View file

@ -2316,14 +2316,21 @@ en:
default_task_name: "New Task"
user_assignment:
select:
default_option: "Select User role"
current_assignee: "(you)"
access_permissions:
partials:
new_assignments_form:
title: "Select members"
submit: "Grand access"
modals:
edit_modal:
title: "Manage access for to %{resource_name}"
new_resource_assignments: "Grant new access to %{resource}"
show_modal:
title: "Access to %{resource_name}"
new_resource_assignments: "Grant new access to %{resource}"
zip_export:
modal_label: 'Export inventory'
notification_title: 'Your requested export package is ready!'