Dan Brown

Added pagination, sorting & searching to users list

As requested on #113
...@@ -31,14 +31,21 @@ class UserController extends Controller ...@@ -31,14 +31,21 @@ class UserController extends Controller
31 31
32 /** 32 /**
33 * Display a listing of the users. 33 * Display a listing of the users.
34 + * @param Request $request
34 * @return Response 35 * @return Response
35 */ 36 */
36 - public function index() 37 + public function index(Request $request)
37 { 38 {
38 $this->checkPermission('users-manage'); 39 $this->checkPermission('users-manage');
39 - $users = $this->userRepo->getAllUsers(); 40 + $listDetails = [
41 + 'order' => $request->has('order') ? $request->get('order') : 'asc',
42 + 'search' => $request->has('search') ? $request->get('search') : '',
43 + 'sort' => $request->has('sort') ? $request->get('sort') : 'name',
44 + ];
45 + $users = $this->userRepo->getAllUsersPaginatedAndSorted(20, $listDetails);
40 $this->setPageTitle('Users'); 46 $this->setPageTitle('Users');
41 - return view('users/index', ['users' => $users]); 47 + $users->appends($listDetails);
48 + return view('users/index', ['users' => $users, 'listDetails' => $listDetails]);
42 } 49 }
43 50
44 /** 51 /**
......
...@@ -52,6 +52,27 @@ class UserRepo ...@@ -52,6 +52,27 @@ class UserRepo
52 } 52 }
53 53
54 /** 54 /**
55 + * Get all the users with their permissions in a paginated format.
56 + * @param int $count
57 + * @param $sortData
58 + * @return \Illuminate\Database\Eloquent\Builder|static
59 + */
60 + public function getAllUsersPaginatedAndSorted($count = 20, $sortData)
61 + {
62 + $query = $this->user->with('roles', 'avatar')->orderBy($sortData['sort'], $sortData['order']);
63 +
64 + if ($sortData['search']) {
65 + $term = '%' . $sortData['search'] . '%';
66 + $query->where(function($query) use ($term) {
67 + $query->where('name', 'like', $term)
68 + ->orWhere('email', 'like', $term);
69 + });
70 + }
71 +
72 + return $query->paginate($count);
73 + }
74 +
75 + /**
55 * Creates a new user and attaches a role to them. 76 * Creates a new user and attaches a role to them.
56 * @param array $data 77 * @param array $data
57 * @return User 78 * @return User
......
...@@ -59,3 +59,35 @@ function setting($key, $default = false) ...@@ -59,3 +59,35 @@ function setting($key, $default = false)
59 $settingService = app('BookStack\Services\SettingService'); 59 $settingService = app('BookStack\Services\SettingService');
60 return $settingService->get($key, $default); 60 return $settingService->get($key, $default);
61 } 61 }
62 +
63 +/**
64 + * Generate a url with multiple parameters for sorting purposes.
65 + * Works out the logic to set the correct sorting direction
66 + * Discards empty parameters and allows overriding.
67 + * @param $path
68 + * @param array $data
69 + * @param array $overrideData
70 + * @return string
71 + */
72 +function sortUrl($path, $data, $overrideData = [])
73 +{
74 + $queryStringSections = [];
75 + $queryData = array_merge($data, $overrideData);
76 +
77 + // Change sorting direction is already sorted on current attribute
78 + if (isset($overrideData['sort']) && $overrideData['sort'] === $data['sort']) {
79 + $queryData['order'] = ($data['order'] === 'asc') ? 'desc' : 'asc';
80 + } else {
81 + $queryData['order'] = 'asc';
82 + }
83 +
84 + foreach ($queryData as $name => $value) {
85 + $trimmedVal = trim($value);
86 + if ($trimmedVal === '') continue;
87 + $queryStringSections[] = urlencode($name) . '=' . urlencode($trimmedVal);
88 + }
89 +
90 + if (count($queryStringSections) === 0) return $path;
91 +
92 + return $path . '?' . implode('&', $queryStringSections);
93 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -266,6 +266,7 @@ ul.pagination { ...@@ -266,6 +266,7 @@ ul.pagination {
266 display: inline-block; 266 display: inline-block;
267 list-style: none; 267 list-style: none;
268 margin: $-m 0; 268 margin: $-m 0;
269 + padding-left: 1px;
269 li { 270 li {
270 float: left; 271 float: left;
271 } 272 }
...@@ -300,6 +301,10 @@ ul.pagination { ...@@ -300,6 +301,10 @@ ul.pagination {
300 } 301 }
301 } 302 }
302 303
304 +.compact ul.pagination {
305 + margin: 0;
306 +}
307 +
303 .entity-list { 308 .entity-list {
304 >div { 309 >div {
305 padding: $-m 0; 310 padding: $-m 0;
......
...@@ -297,6 +297,12 @@ span.sep { ...@@ -297,6 +297,12 @@ span.sep {
297 display: block; 297 display: block;
298 } 298 }
299 299
300 +.action-header {
301 + h1 {
302 + margin-top: $-m;
303 + }
304 +}
305 +
300 /** 306 /**
301 * Icons 307 * Icons
302 */ 308 */
......
...@@ -6,11 +6,15 @@ ...@@ -6,11 +6,15 @@
6 6
7 <div class="container small"> 7 <div class="container small">
8 8
9 - <h1>User Roles</h1> 9 + <div class="row action-header">
10 - 10 + <div class="col-sm-8">
11 - <p> 11 + <h1>User Roles</h1>
12 - <a href="/settings/roles/new" class="text-pos"><i class="zmdi zmdi-lock-open"></i>Add new role</a> 12 + </div>
13 - </p> 13 + <div class="col-sm-4">
14 + <p></p>
15 + <a href="/settings/roles/new" class="button float right pos"><i class="zmdi zmdi-lock-open"></i>Add new role</a>
16 + </div>
17 + </div>
14 18
15 <table class="table"> 19 <table class="table">
16 <tr> 20 <tr>
......
...@@ -7,17 +7,42 @@ ...@@ -7,17 +7,42 @@
7 7
8 8
9 <div class="container small" ng-non-bindable> 9 <div class="container small" ng-non-bindable>
10 - <h1>Users</h1> 10 + <div class="row action-header">
11 - @if(userCan('users-manage')) 11 + <div class="col-sm-8">
12 - <p> 12 + <h1>Users</h1>
13 - <a href="/settings/users/create" class="text-pos"><i class="zmdi zmdi-account-add"></i>Add new user</a> 13 + </div>
14 - </p> 14 + <div class="col-sm-4">
15 - @endif 15 + <p></p>
16 + @if(userCan('users-manage'))
17 + <a href="/settings/users/create" class="pos button float right"><i class="zmdi zmdi-account-add"></i>Add new user</a>
18 + @endif
19 + </div>
20 + </div>
21 +
22 + <div class="row">
23 + <div class="col-sm-8">
24 + <div class="compact">
25 + {!! $users->links() !!}
26 + </div>
27 + </div>
28 + <div class="col-sm-4">
29 + <form method="get" class="float right" action="/settings/users">
30 + @foreach(collect($listDetails)->except('search') as $name => $val)
31 + <input type="hidden" name="{{$name}}" value="{{$val}}">
32 + @endforeach
33 + <input type="text" name="search" placeholder="Search Users" @if($listDetails['search']) value="{{$listDetails['search']}}" @endif>
34 + </form>
35 + </div>
36 + </div>
37 + <div class="text-center">
38 +
39 + </div>
40 +
16 <table class="table"> 41 <table class="table">
17 <tr> 42 <tr>
18 <th></th> 43 <th></th>
19 - <th>Name</th> 44 + <th><a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'name']) }}">Name</a></th>
20 - <th>Email</th> 45 + <th><a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'email']) }}">Email</a></th>
21 <th>User Roles</th> 46 <th>User Roles</th>
22 </tr> 47 </tr>
23 @foreach($users as $user) 48 @foreach($users as $user)
...@@ -42,11 +67,17 @@ ...@@ -42,11 +67,17 @@
42 @endif 67 @endif
43 </td> 68 </td>
44 <td> 69 <td>
45 - <small> {{ $user->roles->implode('display_name', ', ') }}</small> 70 + @foreach($user->roles as $index => $role)
71 + <small><a href="/settings/roles/{{$role->id}}">{{$role->display_name}}</a>@if($index !== count($user->roles) -1),@endif</small>
72 + @endforeach
46 </td> 73 </td>
47 </tr> 74 </tr>
48 @endforeach 75 @endforeach
49 </table> 76 </table>
77 +
78 + <div>
79 + {!! $users->links() !!}
80 + </div>
50 </div> 81 </div>
51 82
52 @stop 83 @stop
......