Compare commits

...

3 Commits

Author SHA1 Message Date
snipe
b8b60b1d17 Additional tweaks for recursion
Signed-off-by: snipe <snipe@snipe.net>
2025-06-22 17:28:56 +01:00
snipe
7dbd8e99ba Moved dropdown list
Signed-off-by: snipe <snipe@snipe.net>
2025-06-18 16:04:38 +01:00
snipe
b5a8b29af5 Use show_in_list instead of activated
Signed-off-by: snipe <snipe@snipe.net>
2025-06-18 16:04:31 +01:00
8 changed files with 107 additions and 136 deletions

View File

@@ -336,6 +336,11 @@ class UsersController extends Controller
] ]
)->where('show_in_list', '=', '1'); )->where('show_in_list', '=', '1');
if (($request->filled('subordinates')) && (!auth()->user()->isSuperUser())) {
// Regular manager sees only their subordinates + self
$users = auth()->user()->allSubordinates();
}
if ($request->filled('search')) { if ($request->filled('search')) {
$users = $users->where(function ($query) use ($request) { $users = $users->where(function ($query) use ($request) {
$query->SimpleNameSearch($request->get('search')) $query->SimpleNameSearch($request->get('search'))

View File

@@ -47,62 +47,6 @@ class ViewAssetsController extends Controller
return array_unique($fieldArray); return array_unique($fieldArray);
} }
/**
* Get list of users viewable by the current user.
*
* @param User $authUser
* @return \Illuminate\Support\Collection
*/
private function getViewableUsers(User $authUser): \Illuminate\Support\Collection
{
// SuperAdmin sees all users
if ($authUser->isSuperUser()) {
return User::select('id', 'first_name', 'last_name', 'username')
->where('activated', 1)
->orderBy('last_name')
->orderBy('first_name')
->get();
}
// Regular manager sees only their subordinates + self
$managedUsers = $authUser->getAllSubordinates();
// If user has subordinates, show them with self at beginning
if ($managedUsers->count() > 0) {
return collect([$authUser])->merge($managedUsers)
->sortBy('last_name')
->sortBy('first_name');
}
// User has no subordinates, only sees themselves
return collect([$authUser]);
}
/**
* Get the selected user ID from request or default to current user.
*
* @param Request $request
* @param \Illuminate\Support\Collection $subordinates
* @param int $defaultUserId
* @return int
*/
private function getSelectedUserId(Request $request, \Illuminate\Support\Collection $subordinates, int $defaultUserId): int
{
// If no subordinates or no user_id in request, return default
if ($subordinates->count() <= 1 || !$request->filled('user_id')) {
return $defaultUserId;
}
$requestedUserId = (int) $request->input('user_id');
// Validate if the requested user is allowed
if ($subordinates->contains('id', $requestedUserId)) {
return $requestedUserId;
}
// If invalid ID or not authorized, return default
return $defaultUserId;
}
/** /**
* Show user's assigned assets with optional manager view functionality. * Show user's assigned assets with optional manager view functionality.
@@ -110,18 +54,29 @@ class ViewAssetsController extends Controller
*/ */
public function getIndex(Request $request) : View | RedirectResponse public function getIndex(Request $request) : View | RedirectResponse
{ {
$authUser = auth()->user();
$settings = Setting::getSettings(); $settings = Setting::getSettings();
$subordinates = collect(); $subordinate_count = 0;
$selectedUserId = $authUser->id; $viewingUser = auth()->user();
$userToView = auth()->user();
$userToViewId = auth()->id();
// Process manager view if enabled // Only do the (heavy) recursive subordinate count if the manager view is enabled AND
if ($settings->manager_view_enabled) { // the user is trying to view a different user than themselves.
$subordinates = $this->getViewableUsers($authUser); if (($settings->manager_view_enabled == 1) || (request()->get('user_id') != auth()->id())) {
$selectedUserId = $this->getSelectedUserId($request, $subordinates, $authUser->id); $subordinate_count = auth()->user()->allSubordinates()->count();
if (auth()->user()->allSubordinates()->find($userToViewId)) {
}
} }
// Load the data for the user to be viewed (either auth user or selected subordinate)
\Log::error('Sub count: '.$subordinate_count);
\Log::error('$viewingUser '.$viewingUser);
\Log::error('$userToView '.$userToView);
\Log::error('$userToViewId '.$userToViewId);
\Log::error(print_r(auth()->user()->allSubordinates()->get(), true));
$userToView = User::with([ $userToView = User::with([
'assets', 'assets',
'assets.model', 'assets.model',
@@ -129,23 +84,21 @@ class ViewAssetsController extends Controller
'consumables', 'consumables',
'accessories', 'accessories',
'licenses' 'licenses'
])->find($selectedUserId); ])->find($userToViewId);
// If the user to view couldn't be found (shouldn't happen with proper logic), redirect with error
if (!$userToView) {
return redirect()->route('view-assets')->with('error', trans('admin/users/message.user_not_found'));
}
// Process custom fields for the user being viewed // Process custom fields for the user being viewed
$fieldArray = $this->extractCustomFields($userToView); $fieldArray = $this->extractCustomFields($userToView);
// Pass the necessary data to the view // Pass the necessary data to the view
return view('account/view-assets', [ return view('account/view-assets', [
'user' => $userToView, // Use 'user' for compatibility with the existing view 'user' => $userToView, // Use 'user' for compatibility with the existing view
'field_array' => $fieldArray, 'field_array' => $fieldArray,
'settings' => $settings, 'settings' => $settings,
'subordinates' => $subordinates, 'subordinates' => $subordinate_count > 0 ?? false,
'selectedUserId' => $selectedUserId 'selectedUserId' => $userToViewId
]); ]);
} }

View File

@@ -971,10 +971,6 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
->orWhere('users.employee_num', 'LIKE', '%' . $search . '%') ->orWhere('users.employee_num', 'LIKE', '%' . $search . '%')
->orWhere('users.username', 'LIKE', '%' . $search . '%') ->orWhere('users.username', 'LIKE', '%' . $search . '%')
->orwhereRaw('CONCAT(users.first_name," ",users.last_name) LIKE \''.$search.'%\''); ->orwhereRaw('CONCAT(users.first_name," ",users.last_name) LIKE \''.$search.'%\'');
} }
/** /**
@@ -1022,6 +1018,22 @@ class User extends SnipeModel implements AuthenticatableContract, AuthorizableCo
} }
} }
public function directReports()
{
return $this->hasMany(User::class, 'manager_id');
}
public function allSubordinates()
{
return $this->directReports()->with('allSubordinates');
}
/** /**
* Check if the current user is a direct or indirect manager of the given user. * Check if the current user is a direct or indirect manager of the given user.
* *

View File

@@ -32369,7 +32369,8 @@ $(function () {
search: params.term, search: params.term,
page: params.page || 1, page: params.page || 1,
assetStatusType: link.data("asset-status-type"), assetStatusType: link.data("asset-status-type"),
companyId: link.data("company-id") companyId: link.data("company-id"),
subordinates: link.data("subordinates")
}; };
return data; return data;
}, },

View File

@@ -91302,7 +91302,8 @@ $(function () {
search: params.term, search: params.term,
page: params.page || 1, page: params.page || 1,
assetStatusType: link.data("asset-status-type"), assetStatusType: link.data("asset-status-type"),
companyId: link.data("company-id") companyId: link.data("company-id"),
subordinates: link.data("subordinates")
}; };
return data; return data;
}, },

View File

@@ -1,5 +1,5 @@
{ {
"/js/build/app.js": "/js/build/app.js?id=19253af36b58ed3fb6770c7bb944f079", "/js/build/app.js": "/js/build/app.js?id=eeff00aa3bf3497d4ba36c3fd7ca0c20",
"/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=78bfb1c7b5782df4fb0ac7e36f80f847", "/css/dist/skins/skin-black-dark.css": "/css/dist/skins/skin-black-dark.css?id=78bfb1c7b5782df4fb0ac7e36f80f847",
"/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=503d0b09e157a22f555e3670d1ec9bb5", "/css/dist/skins/_all-skins.css": "/css/dist/skins/_all-skins.css?id=503d0b09e157a22f555e3670d1ec9bb5",
"/css/build/overrides.css": "/css/build/overrides.css?id=2bfc7b71d951c5ac026dbc034f7373b1", "/css/build/overrides.css": "/css/build/overrides.css?id=2bfc7b71d951c5ac026dbc034f7373b1",
@@ -111,5 +111,5 @@
"/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=54d676a6ea8677dd48f6c4b3041292cf", "/css/dist/bootstrap-table.css": "/css/dist/bootstrap-table.css?id=54d676a6ea8677dd48f6c4b3041292cf",
"/js/build/vendor.js": "/js/build/vendor.js?id=89dffa552c6e3abe3a2aac6c9c7b466b", "/js/build/vendor.js": "/js/build/vendor.js?id=89dffa552c6e3abe3a2aac6c9c7b466b",
"/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=60097b6b56d80cbc76257d4ecf6f57b4", "/js/dist/bootstrap-table.js": "/js/dist/bootstrap-table.js?id=60097b6b56d80cbc76257d4ecf6f57b4",
"/js/dist/all.js": "/js/dist/all.js?id=8c6d7286f667eeb62a0a28a09851a6c3" "/js/dist/all.js": "/js/dist/all.js?id=fe4be0933b6c05df1d795f53da658dc7"
} }

View File

@@ -199,7 +199,10 @@ $(function () {
page: params.page || 1, page: params.page || 1,
assetStatusType: link.data("asset-status-type"), assetStatusType: link.data("asset-status-type"),
companyId: link.data("company-id"), companyId: link.data("company-id"),
subordinates: link.data("subordinates"),
}; };
return data; return data;
}, },
/* processResults: function (data, params) { /* processResults: function (data, params) {

View File

@@ -6,6 +6,25 @@
@parent @parent
@stop @stop
@section('header_right')
{{-- Manager View Dropdown --}}
@if (isset($settings) && ($settings->manager_view_enabled==1) && ($subordinates))
<form method="GET" action="{{ route('view-assets') }}" class="pull-right" role="form">
<div class="form-group" style="margin-bottom: 0;">
<label for="user_id" class="control-label" style="margin-right: 10px;">
{{ trans('general.select_user') }}:
</label>
<select class="js-data-ajax form-control select2" data-endpoint="users" data-subordinates="true" data-placeholder="{{ trans('general.select_user') }}" name="user_id" style="width: 450px" id="user_id" aria-label="user_id" onchange="this.form.submit()">
</select>
</div>
</form>
@endif
@stop
{{-- Account page content --}} {{-- Account page content --}}
@section('content') @section('content')
@@ -25,30 +44,7 @@
</div> </div>
@endif @endif
{{-- Manager View Dropdown --}}
@if (isset($settings) && $settings->manager_view_enabled && isset($subordinates) && $subordinates->count() > 1)
<div class="row hidden-print" style="margin-bottom: 15px;">
<div class="col-md-12">
<form method="GET" action="{{ route('view-assets') }}" class="pull-right" role="form">
<div class="form-group" style="margin-bottom: 0;">
<label for="user_id" class="control-label" style="margin-right: 10px;">
{{ trans('general.view_user_assets') }}:
</label>
<select name="user_id" id="user_id" class="form-control select2" onchange="this.form.submit()" style="width: 250px; display: inline-block;">
@foreach ($subordinates as $subordinate)
<option value="{{ $subordinate->id }}" {{ (int)$selectedUserId === (int)$subordinate->id ? ' selected' : '' }}>
{{ $subordinate->present()->fullName() }}
@if ($subordinate->id == auth()->id())
({{ trans('general.me') }})
@endif
</option>
@endforeach
</select>
</div>
</form>
</div>
</div>
@endif
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
@@ -133,44 +129,44 @@
<div class="col-md-3 col-xs-12 col-sm-push-9"> <div class="col-md-3 col-xs-12 col-sm-push-9">
<div class="col-md-12 text-center"> <div class="col-md-12 text-center">
<img src="{{ $user->present()->gravatar() }}" class=" img-thumbnail hidden-print" style="margin-bottom: 20px;" alt="{{ $user->present()->fullName() }}" alt="User avatar"> <img src="{{ $user->present()->gravatar() }}" class="img-thumbnail hidden-print" style="margin-bottom: 20px;" alt="{{ $user->present()->fullName() }}" alt="User avatar">
</div> </div>
@can('self.profile') @if ((isset($selectedUserId)) && ($selectedUserId == auth()->id()))
<div class="col-md-12"> @can('self.profile')
<a href="{{ route('profile') }}" style="width: 100%;" class="btn btn-sm btn-warning btn-social btn-block hidden-print"> <div class="col-md-12">
<x-icon type="edit" /> <a href="{{ route('profile') }}" style="width: 100%;" class="btn btn-sm btn-warning btn-social btn-block hidden-print">
{{ trans('general.editprofile') }} <x-icon type="edit" />
</a> {{ trans('general.editprofile') }}
</div> </a>
@endcan </div>
@endcan
@if ($user->ldap_import!='1') @if ($user->ldap_import!='1')
<div class="col-md-12" style="padding-top: 5px;"> <div class="col-md-12" style="padding-top: 5px;">
<a href="{{ route('account.password.index') }}" style="width: 100%;" class="btn btn-sm btn-primary btn-social btn-block hidden-print" target="_blank" rel="noopener"> <a href="{{ route('account.password.index') }}" style="width: 100%;" class="btn btn-sm btn-primary btn-social btn-block hidden-print" target="_blank" rel="noopener">
<x-icon type="password" class="fa-fw" /> <x-icon type="password" class="fa-fw" />
{{ trans('general.changepassword') }} {{ trans('general.changepassword') }}
</a> </a>
</div> </div>
@endif
@can('self.api')
<div class="col-md-12" style="padding-top: 5px;">
<a href="{{ route('user.api') }}" style="width: 100%;" class="btn btn-sm btn-primary btn-social btn-block hidden-print" target="_blank" rel="noopener">
<x-icon type="api-key" class="fa-fw" />
{{ trans('general.manage_api_keys') }}
</a>
</div>
@endcan
@endif @endif
@can('self.api') @if ($user->allAssignedCount() > 0)
<div class="col-md-12" style="padding-top: 5px;">
<a href="{{ route('user.api') }}" style="width: 100%;" class="btn btn-sm btn-primary btn-social btn-block hidden-print" target="_blank" rel="noopener">
<x-icon type="api-key" class="fa-fw" />
{{ trans('general.manage_api_keys') }}
</a>
</div>
@endcan
<div class="col-md-12" style="padding-top: 5px;"> <div class="col-md-12" style="padding-top: 5px;">
<a href="{{ route('profile.print') }}" style="width: 100%;" class="btn btn-sm btn-primary btn-social btn-block hidden-print" target="_blank" rel="noopener"> <a href="{{ route('profile.print') }}" style="width: 100%;" class="btn btn-sm btn-primary btn-social btn-block hidden-print" target="_blank" rel="noopener">
<x-icon type="print" class="fa-fw" /> <x-icon type="print" class="fa-fw" />
{{ trans('admin/users/general.print_assigned') }} {{ trans('admin/users/general.print_assigned') }}
</a> </a>
</div> </div>
<div class="col-md-12" style="padding-top: 5px;"> <div class="col-md-12" style="padding-top: 5px;">
@if (!empty($user->email)) @if (!empty($user->email))
<form action="{{ route('profile.email_assets') }}" method="POST"> <form action="{{ route('profile.email_assets') }}" method="POST">
@@ -187,8 +183,8 @@
</button> </button>
@endif @endif
</div> </div>
@endif
<br><br>
</div> </div>
<!-- End button column --> <!-- End button column -->