1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-19 13:54:25 +01:00

[feature] partial load of users list

This commit is contained in:
Sylvain 2016-05-30 15:39:19 +02:00
parent 26f0aa5e0d
commit c8e9c6dae5
10 changed files with 151 additions and 24 deletions

View File

@ -105,8 +105,15 @@ class MembersController
##
# Controller used in the members/groups management page
##
Application.Controllers.controller "AdminMembersController", ["$scope", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t'
, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t) ->
Application.Controllers.controller "AdminMembersController", ["$scope", 'membersPromise', 'adminsPromise', 'growl', 'Admin', 'dialogs', '_t', 'Member'
, ($scope, membersPromise, adminsPromise, growl, Admin, dialogs, _t, Member) ->
### PRIVATE STATIC CONSTANTS ###
# 10 users loaded each time we click on 'load more...'
USERS_PER_PAGE = 10
@ -115,12 +122,19 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
## members list
$scope.members = membersPromise
$scope.member =
## Members plain-text filtering. Default: not filtered
searchText: ''
## Members ordering/sorting. Default: not sorted
order: 'id'
## current displayed page of members
page: 1
## true when all members where loaded
noMore: false
## admins list
$scope.admins = adminsPromise.admins
## Members ordering/sorting. Default: not sorted
$scope.orderMember = null
## Admins ordering/sorting. Default: not sorted
$scope.orderAdmin = null
@ -131,10 +145,13 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
# @param orderBy {string} ordering criterion
##
$scope.setOrderMember = (orderBy)->
if $scope.orderMember == orderBy
$scope.orderMember = '-'+orderBy
if $scope.member.order == orderBy
$scope.member.order = '-'+orderBy
else
$scope.orderMember = orderBy
$scope.member.order = orderBy
resetSearchMember()
memberSearch()
@ -169,6 +186,14 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
growl.error(_t('unable_to_delete_the_administrator'))
$scope.showNextMembers = ->
$scope.member.page += 1
memberSearch(true)
$scope.updateTextSearch = ->
resetSearchMember()
memberSearch()
### PRIVATE SCOPE ###
@ -182,6 +207,32 @@ Application.Controllers.controller "AdminMembersController", ["$scope", 'members
(admins.map (admin)->
admin.id
).indexOf(id)
##
# Reinitialize the context of members's search to display new results set
##
resetSearchMember = ->
$scope.member.noMore = false
$scope.member.page = 1
##
# Run a search query with the current parameters set ($scope.member[searchText,order,page])
# and affect or append the result in $scope.members, depending on the concat parameter
# @param concat {boolean} if true, the result will be append to $scope.members instead of being affected
##
memberSearch = (concat) ->
Member.list { query: { search: $scope.member.searchText, order_by: $scope.member.order, page: $scope.member.page, size: USERS_PER_PAGE } }, (members) ->
if (members.length < USERS_PER_PAGE)
$scope.member.noMore = true
if concat
$scope.members = $scope.members.concat(members)
else
$scope.members = members;
]

View File

@ -692,7 +692,7 @@ angular.module('application.router', ['ui.router']).
controller: 'AuthentificationController'
resolve:
membersPromise: ['Member', (Member)->
Member.query({requested_attributes:'[profile,group,subscription]'}).$promise
Member.list({ query: { search: '', order_by: 'id', page: 1, size: 10 } }).$promise
]
adminsPromise: ['Admin', (Admin)->
Admin.query().$promise

View File

@ -13,4 +13,8 @@ Application.Services.factory 'Member', ["$resource", ($resource)->
merge:
method: 'PUT'
url: '/api/members/:id/merge'
list:
url: '/api/members/list'
method: 'POST'
isArray: true
]

View File

@ -24,7 +24,7 @@
<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="searchMember" class="form-control" placeholder="{{ 'search_for_an_user' | translate }}">
<input type="text" ng-model="member.searchText" class="form-control" placeholder="{{ 'search_for_an_user' | translate }}" ng-change="updateTextSearch()">
</div>
</div>
</div>
@ -46,32 +46,32 @@
<table class="table">
<thead>
<tr>
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='last_name', 'fa fa-sort-alpha-desc': orderMember=='-last_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('last_name')">{{ 'surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='last_name', 'fa fa-sort-alpha-desc': member.order=='-last_name', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='first_name', 'fa fa-sort-alpha-desc': orderMember=='-first_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('first_name')">{{ 'first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='first_name', 'fa fa-sort-alpha-desc': member.order=='-first_name', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='email', 'fa fa-sort-alpha-desc': orderMember=='-email', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('email')">{{ 'email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='email', 'fa fa-sort-alpha-desc': member.order=='-email', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderMember('profile.phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': orderMember=='profile.phone', 'fa fa-sort-numeric-desc': orderMember=='-profile.phone', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:10%"><a href="" ng-click="setOrderMember('phone')">{{ 'phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': member.order=='phone', 'fa fa-sort-numeric-desc': member.order=='-phone', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderMember('group.name')">{{ 'user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='group.name', 'fa fa-sort-alpha-desc': orderMember=='-group.name', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:20%"><a href="" ng-click="setOrderMember('group')">{{ 'user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='group', 'fa fa-sort-alpha-desc': member.order=='-group', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('subscribed_plan.base_name')">{{ 'subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': orderMember=='subscribed_plan.base_name', 'fa fa-sort-alpha-desc': orderMember=='-subscribed_plan.base_name', 'fa fa-arrows-v': orderMember }"></i></a></th>
<th style="width:15%"><a href="" ng-click="setOrderMember('plan')">{{ 'subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='plan', 'fa fa-sort-alpha-desc': member.order=='-plan', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:10%"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="member in members | filter:searchMember | orderBy:orderMember">
<td class="text-c">{{ member.profile.last_name }}</td>
<td class="text-c">{{ member.profile.first_name }}</td>
<td>{{ member.email }}</td>
<td>{{ member.profile.phone }}</td>
<td class="text-u-c text-sm">{{ member.group.name }}</td>
<td>{{ member.subscribed_plan | humanReadablePlanName }}</td>
<tr ng-repeat="m in members">
<td class="text-c">{{ m.profile.last_name }}</td>
<td class="text-c">{{ m.profile.first_name }}</td>
<td>{{ m.email }}</td>
<td>{{ m.profile.phone }}</td>
<td class="text-u-c text-sm">{{ m.group.name }}</td>
<td>{{ m.subscribed_plan | humanReadablePlanName }}</td>
<td>
<div class="buttons">
<button class="btn btn-default" ui-sref="app.admin.members_edit({id: member.id})">
<button class="btn btn-default" ui-sref="app.admin.members_edit({id: m.id})">
<i class="fa fa-edit"></i> {{ 'edit' | translate }}
</button>
</div>
@ -79,6 +79,9 @@
</tr>
</tbody>
</table>
<div class="text-center">
<button class="btn btn-warning" ng-click="showNextMembers()" ng-hide="member.noMore"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'display_more_users' | translate }}</button>
</div>
</div>
</uib-tab>

View File

@ -131,6 +131,53 @@ class API::MembersController < API::ApiController
end
end
def list
authorize User
p = params.require(:query).permit(:search, :order_by, :page, :size)
unless p[:page].is_a? Integer
render json: {error: 'page must be an integer'}, status: :unprocessable_entity
end
unless p[:size].is_a? Integer
render json: {error: 'size must be an integer'}, status: :unprocessable_entity
end
direction = (p[:order_by][0] == '-' ? 'DESC' : 'ASC')
order_key = (p[:order_by][0] == '-' ? p[:order_by][1, p[:order_by].size] : p[:order_by])
case order_key
when 'last_name'
order_key = 'profiles.last_name'
when 'first_name'
order_key = 'profiles.first_name'
when 'email'
order_key = 'users.email'
when 'phone'
order_key = 'profiles.phone'
when 'group'
order_key = 'groups.name'
when 'plan'
order_key = 'plans.base_name'
else
order_key = 'users.id'
end
@members = User.includes(:profile, :group)
.joins(:profile, :group, 'LEFT JOIN "subscriptions" ON "subscriptions"."user_id" = "users"."id" LEFT JOIN "plans" ON "plans"."id" = "subscriptions"."plan_id"')
.order("#{order_key} #{direction}")
.page(p[:page])
.per(p[:size])
# ILIKE => PostgreSQL case-insensitive LIKE
@members = @members.where('profiles.first_name ILIKE :search OR profiles.last_name ILIKE :search OR profiles.phone ILIKE :search OR email ILIKE :search OR groups.name ILIKE :search OR plans.base_name ILIKE :search', search: "%#{p[:search]}%") if p[:search].size > 0
@members
end
private
def set_member
@member = User.find(params[:id])

View File

@ -28,4 +28,8 @@ class UserPolicy < ApplicationPolicy
def merge?
user.id == record.id
end
def list?
user.is_admin?
end
end

View File

@ -0,0 +1,15 @@
json.array!(@members) do |member|
json.id member.id
json.email member.email if current_user
json.profile do
json.first_name member.profile.first_name
json.last_name member.profile.last_name
json.phone member.profile.phone
end
json.group do
json.name member.group.name
end
json.subscribed_plan do
json.partial! 'api/shared/plan', plan: member.subscribed_plan
end if member.subscribed_plan
end

View File

@ -265,6 +265,7 @@ en:
email: "Email"
phone: "Phone"
user_type: "User type"
display_more_users: "Display more users..."
administrators: "Administrators"
search_for_an_administrator: "Search for an administrator"
add_a_new_administrator: "Add a new administrator"

View File

@ -265,6 +265,7 @@ fr:
email: "Courriel"
phone: "Tel."
user_type: "Type utilisateur"
display_more_users: "Afficher plus d'utilisateurs ..."
administrators: "Administrateurs"
search_for_an_administrator: "Recherchez un administrateur"
add_a_new_administrator: "Ajouter un nouvel administrateur"

View File

@ -40,6 +40,7 @@ Rails.application.routes.draw do
get '/export_reservations', action: 'export_reservations', on: :collection
get '/export_members', action: 'export_members', on: :collection
put ':id/merge', action: 'merge', on: :collection
post 'list', action: 'list', on: :collection
end
resources :reservations, only: [:show, :create, :index, :update]
resources :notifications, only: [:index, :show, :update] do