mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-17 06:52:27 +01:00
Interface to manage partners
This commit is contained in:
parent
d717ed704c
commit
f88472eeb3
@ -1,5 +1,6 @@
|
||||
# Changelog Fab-manager
|
||||
|
||||
- Interface to manage partners
|
||||
- Ability to define, per availability, a custom duration for the reservation slots
|
||||
|
||||
## v4.3.4 2020 April 14
|
||||
|
@ -126,8 +126,8 @@ class MembersController {
|
||||
/**
|
||||
* Controller used in the members/groups management page
|
||||
*/
|
||||
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export', 'uiTourService',
|
||||
function ($scope, $sce, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member, Export, uiTourService) {
|
||||
Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', '$uibModal', 'membersPromise', 'adminsPromise', 'partnersPromise', 'managersPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member', 'Export', 'User', 'uiTourService',
|
||||
function ($scope, $sce, $uibModal, membersPromise, adminsPromise, partnersPromise, managersPromise, growl, Admin, dialogs, _t, Member, Export, User, uiTourService) {
|
||||
/* PRIVATE STATIC CONSTANTS */
|
||||
|
||||
// number of users loaded each time we click on 'load more...'
|
||||
@ -163,8 +163,20 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
// Admins ordering/sorting. Default: not sorted
|
||||
$scope.orderAdmin = null;
|
||||
|
||||
// partners list
|
||||
$scope.partners = partnersPromise.users;
|
||||
|
||||
// Partners ordering/sorting. Default: not sorted
|
||||
$scope.orderPartner = null;
|
||||
|
||||
// managers list
|
||||
$scope.managers = managersPromise.users;
|
||||
|
||||
// Managers ordering/sorting. Default: not sorted
|
||||
$scope.orderManager = null;
|
||||
|
||||
// default tab: members list
|
||||
$scope.tabs = { active: 0 };
|
||||
$scope.tabs = { active: 0, sub: 0 };
|
||||
|
||||
/**
|
||||
* Change the members ordering criterion to the one provided
|
||||
@ -193,6 +205,55 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the partners ordering criterion to the one provided
|
||||
* @param orderPartner {string} ordering criterion
|
||||
*/
|
||||
$scope.setOrderPartner = function (orderPartner) {
|
||||
if ($scope.orderPartner === orderPartner) {
|
||||
return $scope.orderPartner = `-${orderPartner}`;
|
||||
} else {
|
||||
return $scope.orderPartner = orderPartner;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Open a modal dialog allowing the admin to create a new partner user
|
||||
*/
|
||||
$scope.openPartnerNewModal = function () {
|
||||
const modalInstance = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: '<%= asset_path "shared/_partner_new_modal.html" %>',
|
||||
size: 'lg',
|
||||
controller: ['$scope', '$uibModalInstance', 'User', function ($scope, $uibModalInstance, User) {
|
||||
$scope.partner = {};
|
||||
|
||||
$scope.ok = function () {
|
||||
User.save(
|
||||
{},
|
||||
{ user: $scope.partner },
|
||||
function (user) {
|
||||
$scope.partner.id = user.id;
|
||||
$scope.partner.name = `${user.first_name} ${user.last_name}`;
|
||||
$uibModalInstance.close($scope.partner);
|
||||
},
|
||||
function (error) {
|
||||
growl.error(_t('app.admin.plans.new.unable_to_save_this_user_check_that_there_isnt_an_already_a_user_with_the_same_name'));
|
||||
console.error(error);
|
||||
}
|
||||
);
|
||||
};
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
}]
|
||||
});
|
||||
// once the form was validated successfully ...
|
||||
return modalInstance.result.then(function (partner) {
|
||||
$scope.partners.push(partner);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Ask for confirmation then delete the specified user
|
||||
* @param memberId {number} identifier of the user to delete
|
||||
@ -252,6 +313,36 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Ask for confirmation then delete the specified partner
|
||||
* @param partners {Array} full list of partners
|
||||
* @param partner {Object} partner to delete
|
||||
*/
|
||||
$scope.destroyPartner = function (partners, partner) {
|
||||
dialogs.confirm(
|
||||
{
|
||||
resolve: {
|
||||
object () {
|
||||
return {
|
||||
title: _t('app.admin.members.confirmation_required'),
|
||||
msg: $sce.trustAsHtml(_t('app.admin.members.delete_this_partner') + '<br/><br/>' + _t('app.admin.members.this_may_take_a_while_please_wait'))
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
function () { // cancel confirmed
|
||||
User.delete(
|
||||
{ id: partner.id },
|
||||
function () {
|
||||
partners.splice(findItemIdxById(partners, partner.id), 1);
|
||||
return growl.success(_t('app.admin.members.partner_successfully_deleted'));
|
||||
},
|
||||
function (error) { growl.error(_t('app.admin.members.unable_to_delete_the_partner')); }
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for the 'load more' button.
|
||||
* Will load the next results of the current search, if any
|
||||
@ -405,18 +496,20 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce',
|
||||
uitour.on('stepChanged', function (nextStep) {
|
||||
if (nextStep.stepId === 'list' || nextStep.stepId === 'import') {
|
||||
$scope.tabs.active = 0;
|
||||
$scope.tabs.sub = 0;
|
||||
}
|
||||
if (nextStep.stepId === 'admins') {
|
||||
$scope.tabs.active = 1;
|
||||
$scope.tabs.active = 0;
|
||||
$scope.tabs.sub = 1;
|
||||
}
|
||||
if (nextStep.stepId === 'groups') {
|
||||
$scope.tabs.active = 2;
|
||||
$scope.tabs.active = 1;
|
||||
}
|
||||
if (nextStep.stepId === 'labels') {
|
||||
$scope.tabs.active = 3;
|
||||
$scope.tabs.active = 2;
|
||||
}
|
||||
if (nextStep.stepId === 'sso') {
|
||||
$scope.tabs.active = 4;
|
||||
$scope.tabs.active = 3;
|
||||
}
|
||||
});
|
||||
// on tour end, save the status in database
|
||||
|
@ -866,6 +866,8 @@ angular.module('application.router', ['ui.router'])
|
||||
resolve: {
|
||||
membersPromise: ['Member', function (Member) { return Member.list({ query: { search: '', order_by: 'id', page: 1, size: 20 } }).$promise; }],
|
||||
adminsPromise: ['Admin', function (Admin) { return Admin.query().$promise; }],
|
||||
partnersPromise: ['User', function (User) { return User.query({ role: 'partner' }).$promise; }],
|
||||
managersPromise: ['User', function (User) { return User.query({ role: 'manager' }).$promise; }],
|
||||
groupsPromise: ['Group', function (Group) { return Group.query().$promise; }],
|
||||
tagsPromise: ['Tag', function (Tag) { return Tag.query().$promise; }],
|
||||
authProvidersPromise: ['AuthProvider', function (AuthProvider) { return AuthProvider.query().$promise; }]
|
||||
|
@ -1,8 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
Application.Services.factory('User', ['$resource', function ($resource) {
|
||||
return $resource('/api/users',
|
||||
{}, {
|
||||
return $resource('/api/users/:id',
|
||||
{ id: '@id' }, {
|
||||
query: {
|
||||
isArray: false
|
||||
}
|
||||
|
@ -535,3 +535,14 @@
|
||||
display: inline-block !important;
|
||||
padding: 11px 44px !important;
|
||||
}
|
||||
|
||||
li.level-2-tab > a {
|
||||
background: #eee;
|
||||
line-height: 14px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
li.active.level-2-tab > a {
|
||||
background: linear-gradient(#eee, #fff);
|
||||
}
|
||||
|
||||
|
@ -32,23 +32,19 @@
|
||||
<div class="col-md-12">
|
||||
<uib-tabset justified="true" active="tabs.active">
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.members.members' | translate }}" index="0">
|
||||
<ng-include src="'<%= asset_path "admin/members/members.html" %>'"></ng-include>
|
||||
<uib-tab heading="{{ 'app.admin.members.users' | translate }}" index="0">
|
||||
<ng-include src="'<%= asset_path "admin/members/users.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.members.administrators' | translate }}" class="admins-tab" index="1">
|
||||
<ng-include src="'<%= asset_path "admin/members/administrators.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.members.groups' | translate }}" class="groups-tab" index="2">
|
||||
<uib-tab heading="{{ 'app.admin.members.groups' | translate }}" class="groups-tab" index="1">
|
||||
<div ui-view="groups"></div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.members.tags' | translate }}" class="labels-tab" index="3">
|
||||
<uib-tab heading="{{ 'app.admin.members.tags' | translate }}" class="labels-tab" index="2">
|
||||
<div ui-view="tags"></div>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab heading="{{ 'app.admin.members.authentication' | translate }}" class="sso-tab" index="4">
|
||||
<uib-tab heading="{{ 'app.admin.members.authentication' | translate }}" class="sso-tab" index="3">
|
||||
<div ui-view="authentification"></div>
|
||||
</uib-tab>
|
||||
</uib-tabset>
|
||||
|
39
app/assets/templates/admin/members/managers.html.erb
Normal file
39
app/assets/templates/admin/members/managers.html.erb
Normal file
@ -0,0 +1,39 @@
|
||||
<div class="col-md-5 m-t-lg">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'app.admin.members.search_for_an_administrator' | translate }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.admins_new" translate>{{ 'app.admin.members.add_a_new_administrator' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.last_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.last_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('profile_attributes.first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='profile_attributes.first_name', 'fa fa-sort-alpha-desc': orderAdmin =='-profile_attributes.first_name', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderAdmin('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderAdmin =='email', 'fa fa-sort-alpha-desc': orderAdmin =='-email', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderAdmin('profile_attributes.phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderAdmin =='profile_attributes.phone', 'fa fa-sort-numeric-desc': orderAdmin =='-profile_attributes.phone', 'fa fa-arrows-v': orderAdmin }"></i></a></th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="admin in admins | filter:searchFilter | orderBy: orderAdmin">
|
||||
<td class="text-c">{{ admin.profile_attributes.last_name }}</td>
|
||||
<td class="text-c">{{ admin.profile_attributes.first_name }}</td>
|
||||
<td>{{ admin.email }}</td>
|
||||
<td>{{ admin.profile_attributes.phone }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger" ng-if="admin.id != currentUser.id" ng-click="destroyAdmin(admins, admin)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
43
app/assets/templates/admin/members/partners.html.erb
Normal file
43
app/assets/templates/admin/members/partners.html.erb
Normal file
@ -0,0 +1,43 @@
|
||||
<p class="alert alert-info m-t-lg" translate>
|
||||
{{ 'app.admin.members.partners_info' }}
|
||||
</p>
|
||||
|
||||
<div class="col-md-5">
|
||||
<div class="form-group">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-filter"></i></span>
|
||||
<input type="text" ng-model="searchFilter" class="form-control" placeholder="{{ 'app.admin.members.search_for_a_partner' | translate }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<button type="button" class="btn btn-warning m-t m-b" ng-click="openPartnerNewModal()" translate>{{ 'app.admin.members.add_a_new_partner' }}</button>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:15%"><a href="" ng-click="setOrderPartner('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='last_name', 'fa fa-sort-alpha-desc': orderPartner =='-last_name', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderPartner('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='first_name', 'fa fa-sort-alpha-desc': orderPartner =='-first_name', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||
|
||||
<th style="width:15%"><a href="" ng-click="setOrderPartner('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderPartner =='email', 'fa fa-sort-alpha-desc': orderPartner =='-email', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||
|
||||
<th style="width:10%"><a href="" ng-click="setOrderPartner('resource')">{{ 'app.admin.members.associated_plan' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderPartner =='resource', 'fa fa-sort-numeric-desc': orderPartner =='-resource', 'fa fa-arrows-v': orderPartner }"></i></a></th>
|
||||
<th style="width:10%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="partner in partners | filter:searchFilter | orderBy: orderPartner">
|
||||
<td class="text-c">{{ partner.last_name }}</td>
|
||||
<td class="text-c">{{ partner.first_name }}</td>
|
||||
<td>{{ partner.email }}</td>
|
||||
<td><a ui-sref="app.admin.plans.edit({id:partner.resource.id})">{{ partner.resource ? partner.resource.base_name : '' }}</a></td>
|
||||
<td>
|
||||
<button class="btn btn-danger" ng-if="partner.id != currentUser.id" ng-click="destroyPartner(partners, partner)">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
19
app/assets/templates/admin/members/users.html.erb
Normal file
19
app/assets/templates/admin/members/users.html.erb
Normal file
@ -0,0 +1,19 @@
|
||||
<uib-tabset justified="true" active="tabs.sub" class="m-t">
|
||||
|
||||
<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.members' | translate }}" index="0">
|
||||
<ng-include src="'<%= asset_path "admin/members/members.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.administrators' | translate }}" class="admins-tab" index="1">
|
||||
<ng-include src="'<%= asset_path "admin/members/administrators.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
<!--<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.managers' | translate }}" class="admins-tab" index="2">
|
||||
<ng-include src="'<%= asset_path "admin/members/managers.html" %>'"></ng-include>
|
||||
</uib-tab>-->
|
||||
|
||||
<uib-tab classes="level-2-tab" heading="{{ 'app.admin.members.partners' | translate }}" class="admins-tab" index="3">
|
||||
<ng-include src="'<%= asset_path "admin/members/partners.html" %>'"></ng-include>
|
||||
</uib-tab>
|
||||
|
||||
</uib-tabset>
|
@ -1,12 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# API Controller for resources of type Users with role :partner
|
||||
# API Controller for resources of type Users with role :partner or :manager
|
||||
class API::UsersController < API::ApiController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_user, only: %i[destroy]
|
||||
|
||||
def index
|
||||
if current_user.admin? && params[:role] == 'partner'
|
||||
@users = User.with_role(:partner).includes(:profile)
|
||||
if current_user.admin? && %w[partner manager].include?(params[:role])
|
||||
@users = User.with_role(params[:role].to_sym).includes(:profile)
|
||||
else
|
||||
head 403
|
||||
end
|
||||
@ -19,13 +20,23 @@ class API::UsersController < API::ApiController
|
||||
if res[:saved]
|
||||
@user = res[:user]
|
||||
render status: :created
|
||||
else
|
||||
else²
|
||||
render json: res[:user].errors.full_messages, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize User
|
||||
@user.destroy
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_user
|
||||
@user = User.find(params[:id])
|
||||
end
|
||||
|
||||
def partner_params
|
||||
params.require(:user).permit(:email, :first_name, :last_name)
|
||||
end
|
||||
|
@ -1,4 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
json.users @users do |user|
|
||||
json.extract! user, :id, :email, :first_name, :last_name
|
||||
json.name user.profile.full_name
|
||||
json.resource user.roles.last.resource
|
||||
end
|
||||
|
@ -588,6 +588,7 @@ en:
|
||||
#management of users, labels, groups, and so on
|
||||
members:
|
||||
users_management: "Users management"
|
||||
users: "Users"
|
||||
members: "Members"
|
||||
subscriptions: "Subscriptions"
|
||||
search_for_an_user: "Search for an user"
|
||||
@ -603,6 +604,15 @@ en:
|
||||
administrators: "Administrators"
|
||||
search_for_an_administrator: "Search for an administrator"
|
||||
add_a_new_administrator: "Add a new administrator"
|
||||
partners: "Partners"
|
||||
partners_info: "A partner is a special user that can be associated with the «Partner» plans. These users will only receive notifications about subscriptions to these plans."
|
||||
search_for_a_partner: "Search for a partner"
|
||||
add_a_new_partner: "Add a new partner"
|
||||
delete_this_partner: "Do you really want to delete this partner? This cannot be undone."
|
||||
partner_successfully_deleted: "Partner successfully deleted."
|
||||
unable_to_delete_the_partner: "Unable to delete the partner."
|
||||
associated_plan: "Associated plan"
|
||||
managers: "Managers"
|
||||
groups: "Groups"
|
||||
tags: "Tags"
|
||||
authentication: "Authentication"
|
||||
|
@ -45,7 +45,7 @@ Rails.application.routes.draw do
|
||||
patch '/bulk_update', action: 'bulk_update', on: :collection
|
||||
put '/reset/:name', action: 'reset', on: :collection
|
||||
end
|
||||
resources :users, only: %i[index create]
|
||||
resources :users, only: %i[index create destroy]
|
||||
resources :members, only: %i[index show create update destroy] do
|
||||
get '/export_subscriptions', action: 'export_subscriptions', on: :collection
|
||||
get '/export_reservations', action: 'export_reservations', on: :collection
|
||||
|
Loading…
x
Reference in New Issue
Block a user