- Implemented the customer portal workflow progress component with detailed service progress tracking, including current status, workflow steps, and contact information. - Developed a management workflow analytics dashboard featuring key performance indicators, charts for revenue by branch, labor utilization, and recent quality issues. - Created tests for admin-only middleware to ensure proper access control for admin routes. - Added tests for customer portal view rendering and workflow integration, ensuring the workflow service operates correctly through various stages. - Introduced a .gitignore file for the debugbar storage directory to prevent unnecessary files from being tracked.
727 lines
24 KiB
PHP
727 lines
24 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Users;
|
|
|
|
use Livewire\Component;
|
|
use Livewire\WithPagination;
|
|
use App\Models\User;
|
|
use App\Models\Role;
|
|
use App\Models\Branch;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
class Index extends Component
|
|
{
|
|
use WithPagination;
|
|
|
|
// Search and filtering
|
|
public $search = '';
|
|
public $roleFilter = '';
|
|
public $statusFilter = '';
|
|
public $departmentFilter = '';
|
|
public $branchFilter = '';
|
|
public $customerFilter = '';
|
|
public $hireYearFilter = '';
|
|
|
|
// Sorting
|
|
public $sortField = 'name';
|
|
public $sortDirection = 'asc';
|
|
|
|
// Display options
|
|
public $perPage = 25;
|
|
public $showInactive = false;
|
|
public $showDetails = false;
|
|
|
|
// Bulk operations
|
|
public $selectedUsers = [];
|
|
public $selectAll = false;
|
|
|
|
// Modal states
|
|
public $showDeleteModal = false;
|
|
public $userToDelete = null;
|
|
public $showBulkDeleteModal = false;
|
|
|
|
protected $queryString = [
|
|
'search' => ['except' => ''],
|
|
'roleFilter' => ['except' => ''],
|
|
'statusFilter' => ['except' => ''],
|
|
'departmentFilter' => ['except' => ''],
|
|
'branchFilter' => ['except' => ''],
|
|
'customerFilter' => ['except' => ''],
|
|
'hireYearFilter' => ['except' => ''],
|
|
'sortField' => ['except' => 'name'],
|
|
'sortDirection' => ['except' => 'asc'],
|
|
'perPage' => ['except' => 25],
|
|
'showInactive' => ['except' => false],
|
|
'page' => ['except' => 1],
|
|
];
|
|
|
|
protected $listeners = [
|
|
'userUpdated' => '$refresh',
|
|
'userCreated' => '$refresh',
|
|
'userDeleted' => '$refresh',
|
|
];
|
|
|
|
public function updatingSearch()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingRoleFilter()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingStatusFilter()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingDepartmentFilter()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingBranchFilter()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingCustomerFilter()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingHireYearFilter()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingPerPage()
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$query = User::query()
|
|
->with([
|
|
'roles' => function($query) {
|
|
$query->where('user_roles.is_active', true)
|
|
->where(function ($q) {
|
|
$q->whereNull('user_roles.expires_at')
|
|
->orWhere('user_roles.expires_at', '>', now());
|
|
});
|
|
},
|
|
'customer',
|
|
'branch',
|
|
'jobCards' => function($query) {
|
|
$query->select('id', 'service_advisor_id', 'created_at');
|
|
}
|
|
])
|
|
->withCount([
|
|
'roles as active_roles_count' => function($query) {
|
|
$query->where('user_roles.is_active', true)
|
|
->where(function ($q) {
|
|
$q->whereNull('user_roles.expires_at')
|
|
->orWhere('user_roles.expires_at', '>', now());
|
|
});
|
|
},
|
|
'jobCards as job_cards_count'
|
|
])
|
|
->when($this->search, function ($q) {
|
|
$searchTerm = '%' . $this->search . '%';
|
|
$q->where(function ($query) use ($searchTerm) {
|
|
$query->where('name', 'like', $searchTerm)
|
|
->orWhere('email', 'like', $searchTerm)
|
|
->orWhere('employee_id', 'like', $searchTerm)
|
|
->orWhere('phone', 'like', $searchTerm)
|
|
->orWhere('national_id', 'like', $searchTerm)
|
|
->orWhereHas('branch', function($q) use ($searchTerm) {
|
|
$q->where('name', 'like', $searchTerm);
|
|
});
|
|
});
|
|
})
|
|
->when($this->roleFilter, function ($q) {
|
|
$q->whereHas('roles', function ($query) {
|
|
$query->where('roles.name', $this->roleFilter)
|
|
->where('user_roles.is_active', true)
|
|
->where(function ($subQ) {
|
|
$subQ->whereNull('user_roles.expires_at')
|
|
->orWhere('user_roles.expires_at', '>', now());
|
|
});
|
|
});
|
|
})
|
|
->when($this->statusFilter, function ($q) {
|
|
$q->where('status', $this->statusFilter);
|
|
})
|
|
->when($this->departmentFilter, function ($q) {
|
|
$q->where('department', $this->departmentFilter);
|
|
})
|
|
->when($this->branchFilter, function ($q) {
|
|
$q->where('branch_code', $this->branchFilter);
|
|
})
|
|
->when($this->hireYearFilter, function ($q) {
|
|
$q->whereYear('hire_date', $this->hireYearFilter);
|
|
})
|
|
->when($this->customerFilter, function ($q) {
|
|
if ($this->customerFilter === 'customers_only') {
|
|
$q->whereHas('customer');
|
|
} elseif ($this->customerFilter === 'non_customers') {
|
|
$q->whereDoesntHave('customer');
|
|
}
|
|
})
|
|
->when(!$this->showInactive, function ($q) {
|
|
$q->where('status', '!=', 'inactive');
|
|
})
|
|
->orderBy($this->sortField, $this->sortDirection);
|
|
|
|
$users = $query->paginate($this->perPage);
|
|
|
|
// Filter options data
|
|
$roles = Role::where('is_active', true)->orderBy('display_name')->get();
|
|
$departments = User::select('department')
|
|
->distinct()
|
|
->whereNotNull('department')
|
|
->where('department', '!=', '')
|
|
->orderBy('department')
|
|
->pluck('department');
|
|
$branches = Branch::where('is_active', true)
|
|
->orderBy('name')
|
|
->get();
|
|
$hireYears = User::selectRaw('YEAR(hire_date) as year')
|
|
->whereNotNull('hire_date')
|
|
->distinct()
|
|
->orderByDesc('year')
|
|
->pluck('year')
|
|
->filter();
|
|
|
|
// Enhanced statistics
|
|
$stats = [
|
|
'total' => User::count(),
|
|
'active' => User::where('status', 'active')->count(),
|
|
'inactive' => User::where('status', 'inactive')->count(),
|
|
'suspended' => User::where('status', 'suspended')->count(),
|
|
'customers' => User::whereHas('customer')->count(),
|
|
'staff' => User::whereDoesntHave('customer')->count(),
|
|
'recent_hires' => User::where('hire_date', '>=', now()->subDays(30))->count(),
|
|
'no_roles' => User::whereDoesntHave('roles', function($q) {
|
|
$q->where('user_roles.is_active', true);
|
|
})->count(),
|
|
];
|
|
|
|
// Branch distribution for stats
|
|
$branchStats = User::select('branch_code')
|
|
->selectRaw('count(*) as count')
|
|
->with('branch:code,name')
|
|
->groupBy('branch_code')
|
|
->get()
|
|
->mapWithKeys(function($item) {
|
|
$branchName = $item->branch ? $item->branch->name : $item->branch_code;
|
|
return [$branchName => $item->count];
|
|
});
|
|
|
|
return view('livewire.users.index', compact(
|
|
'users',
|
|
'roles',
|
|
'departments',
|
|
'branches',
|
|
'hireYears',
|
|
'stats',
|
|
'branchStats'
|
|
));
|
|
}
|
|
|
|
public function sortBy($field)
|
|
{
|
|
if ($this->sortField === $field) {
|
|
$this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
$this->sortField = $field;
|
|
$this->sortDirection = 'asc';
|
|
}
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function clearFilters()
|
|
{
|
|
$this->search = '';
|
|
$this->roleFilter = '';
|
|
$this->statusFilter = '';
|
|
$this->departmentFilter = '';
|
|
$this->branchFilter = '';
|
|
$this->customerFilter = '';
|
|
$this->hireYearFilter = '';
|
|
$this->showInactive = false;
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function toggleShowInactive()
|
|
{
|
|
$this->showInactive = !$this->showInactive;
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function toggleShowDetails()
|
|
{
|
|
$this->showDetails = !$this->showDetails;
|
|
}
|
|
|
|
public function selectAllUsers()
|
|
{
|
|
if ($this->selectAll) {
|
|
$this->selectedUsers = [];
|
|
$this->selectAll = false;
|
|
} else {
|
|
// Only select users from current page for performance
|
|
$currentPageUsers = User::when($this->search, function ($q) {
|
|
$searchTerm = '%' . $this->search . '%';
|
|
$q->where(function ($query) use ($searchTerm) {
|
|
$query->where('name', 'like', $searchTerm)
|
|
->orWhere('email', 'like', $searchTerm);
|
|
});
|
|
})->pluck('id')->toArray();
|
|
|
|
$this->selectedUsers = $currentPageUsers;
|
|
$this->selectAll = true;
|
|
}
|
|
}
|
|
|
|
public function confirmDelete($userId)
|
|
{
|
|
$this->userToDelete = $userId;
|
|
$this->showDeleteModal = true;
|
|
}
|
|
|
|
public function confirmBulkDelete()
|
|
{
|
|
if (empty($this->selectedUsers)) {
|
|
session()->flash('error', 'No users selected.');
|
|
return;
|
|
}
|
|
$this->showBulkDeleteModal = true;
|
|
}
|
|
|
|
public function bulkActivate()
|
|
{
|
|
if (empty($this->selectedUsers)) {
|
|
session()->flash('error', 'No users selected.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$count = User::whereIn('id', $this->selectedUsers)
|
|
->where('id', '!=', auth()->id())
|
|
->update(['status' => 'active']);
|
|
|
|
// Log bulk action
|
|
activity()
|
|
->causedBy(auth()->user())
|
|
->log('Bulk activated ' . $count . ' users');
|
|
|
|
$this->selectedUsers = [];
|
|
$this->selectAll = false;
|
|
|
|
session()->flash('success', "Successfully activated {$count} users.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to activate users: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function bulkDeactivate()
|
|
{
|
|
if (empty($this->selectedUsers)) {
|
|
session()->flash('error', 'No users selected.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$count = User::whereIn('id', $this->selectedUsers)
|
|
->where('id', '!=', auth()->id())
|
|
->update(['status' => 'inactive']);
|
|
|
|
// Log bulk action
|
|
activity()
|
|
->causedBy(auth()->user())
|
|
->log('Bulk deactivated ' . $count . ' users');
|
|
|
|
$this->selectedUsers = [];
|
|
$this->selectAll = false;
|
|
|
|
session()->flash('success', "Successfully deactivated {$count} users.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to deactivate users: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function bulkSuspend()
|
|
{
|
|
if (empty($this->selectedUsers)) {
|
|
session()->flash('error', 'No users selected.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$count = User::whereIn('id', $this->selectedUsers)
|
|
->where('id', '!=', auth()->id())
|
|
->update(['status' => 'suspended']);
|
|
|
|
// Log bulk action
|
|
activity()
|
|
->causedBy(auth()->user())
|
|
->log('Bulk suspended ' . $count . ' users');
|
|
|
|
$this->selectedUsers = [];
|
|
$this->selectAll = false;
|
|
|
|
session()->flash('success', "Successfully suspended {$count} users.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to suspend users: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function bulkAssignRole($roleId)
|
|
{
|
|
if (empty($this->selectedUsers)) {
|
|
session()->flash('error', 'No users selected.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$role = Role::findOrFail($roleId);
|
|
$count = 0;
|
|
|
|
foreach ($this->selectedUsers as $userId) {
|
|
$user = User::find($userId);
|
|
if ($user && !$user->hasRole($role->name)) {
|
|
$user->assignRole($role);
|
|
$count++;
|
|
}
|
|
}
|
|
|
|
// Log bulk action
|
|
activity()
|
|
->causedBy(auth()->user())
|
|
->log('Bulk assigned role "' . $role->display_name . '" to ' . $count . ' users');
|
|
|
|
$this->selectedUsers = [];
|
|
$this->selectAll = false;
|
|
|
|
session()->flash('success', "Successfully assigned role '{$role->display_name}' to {$count} users.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to assign role: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function deactivateUser($userId)
|
|
{
|
|
try {
|
|
$user = User::findOrFail($userId);
|
|
|
|
if ($user->id === auth()->id()) {
|
|
session()->flash('error', 'You cannot deactivate your own account.');
|
|
return;
|
|
}
|
|
|
|
$user->update(['status' => 'inactive']);
|
|
|
|
// Log the action
|
|
activity()
|
|
->performedOn($user)
|
|
->causedBy(auth()->user())
|
|
->log('User deactivated');
|
|
|
|
session()->flash('success', "User '{$user->name}' deactivated successfully.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to deactivate user: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function activateUser($userId)
|
|
{
|
|
try {
|
|
$user = User::findOrFail($userId);
|
|
$user->update(['status' => 'active']);
|
|
|
|
// Log the action
|
|
activity()
|
|
->performedOn($user)
|
|
->causedBy(auth()->user())
|
|
->log('User activated');
|
|
|
|
session()->flash('success', "User '{$user->name}' activated successfully.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to activate user: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function suspendUser($userId)
|
|
{
|
|
try {
|
|
$user = User::findOrFail($userId);
|
|
|
|
if ($user->id === auth()->id()) {
|
|
session()->flash('error', 'You cannot suspend your own account.');
|
|
return;
|
|
}
|
|
|
|
$user->update(['status' => 'suspended']);
|
|
|
|
// Log the action
|
|
activity()
|
|
->performedOn($user)
|
|
->causedBy(auth()->user())
|
|
->log('User suspended');
|
|
|
|
session()->flash('success', "User '{$user->name}' suspended successfully.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to suspend user: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function deleteUser($userId = null)
|
|
{
|
|
$userId = $userId ?? $this->userToDelete;
|
|
|
|
try {
|
|
$user = User::findOrFail($userId);
|
|
|
|
if ($user->id === auth()->id()) {
|
|
session()->flash('error', 'You cannot delete your own account.');
|
|
return;
|
|
}
|
|
|
|
// Check if user has dependencies
|
|
$jobCardsCount = $user->jobCards()->count();
|
|
if ($jobCardsCount > 0) {
|
|
session()->flash('error', "Cannot delete user '{$user->name}'. User has {$jobCardsCount} associated job cards.");
|
|
return;
|
|
}
|
|
|
|
// Log before deletion
|
|
activity()
|
|
->performedOn($user)
|
|
->causedBy(auth()->user())
|
|
->log('User deleted');
|
|
|
|
$userName = $user->name;
|
|
$user->delete();
|
|
|
|
session()->flash('success', "User '{$userName}' deleted successfully.");
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to delete user: ' . $e->getMessage());
|
|
} finally {
|
|
$this->showDeleteModal = false;
|
|
$this->userToDelete = null;
|
|
}
|
|
}
|
|
|
|
public function bulkDelete()
|
|
{
|
|
if (empty($this->selectedUsers)) {
|
|
session()->flash('error', 'No users selected.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$users = User::whereIn('id', $this->selectedUsers)
|
|
->where('id', '!=', auth()->id())
|
|
->get();
|
|
|
|
$deletedCount = 0;
|
|
$skippedCount = 0;
|
|
|
|
foreach ($users as $user) {
|
|
// Check dependencies
|
|
if ($user->jobCards()->count() > 0) {
|
|
$skippedCount++;
|
|
continue;
|
|
}
|
|
|
|
// Log before deletion
|
|
activity()
|
|
->performedOn($user)
|
|
->causedBy(auth()->user())
|
|
->log('User deleted (bulk)');
|
|
|
|
$user->delete();
|
|
$deletedCount++;
|
|
}
|
|
|
|
$this->selectedUsers = [];
|
|
$this->selectAll = false;
|
|
$this->showBulkDeleteModal = false;
|
|
|
|
$message = "Deleted {$deletedCount} users successfully.";
|
|
if ($skippedCount > 0) {
|
|
$message .= " {$skippedCount} users were skipped due to dependencies.";
|
|
}
|
|
|
|
session()->flash('success', $message);
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Failed to delete users: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function exportUsers()
|
|
{
|
|
try {
|
|
$users = User::with(['roles', 'branch'])
|
|
->when($this->search, function ($q) {
|
|
$searchTerm = '%' . $this->search . '%';
|
|
$q->where(function ($query) use ($searchTerm) {
|
|
$query->where('name', 'like', $searchTerm)
|
|
->orWhere('email', 'like', $searchTerm);
|
|
});
|
|
})
|
|
->when($this->roleFilter, function ($q) {
|
|
$q->whereHas('roles', function ($query) {
|
|
$query->where('roles.name', $this->roleFilter);
|
|
});
|
|
})
|
|
->when($this->statusFilter, function ($q) {
|
|
$q->where('status', $this->statusFilter);
|
|
})
|
|
->when($this->branchFilter, function ($q) {
|
|
$q->where('branch_code', $this->branchFilter);
|
|
})
|
|
->get();
|
|
|
|
// Create CSV content
|
|
$csvData = [];
|
|
$csvData[] = [
|
|
'Name', 'Email', 'Employee ID', 'Phone', 'Department',
|
|
'Position', 'Branch', 'Status', 'Hire Date', 'Roles'
|
|
];
|
|
|
|
foreach ($users as $user) {
|
|
$csvData[] = [
|
|
$user->name,
|
|
$user->email,
|
|
$user->employee_id,
|
|
$user->phone,
|
|
$user->department,
|
|
$user->position,
|
|
$user->branch ? $user->branch->name : $user->branch_code,
|
|
$user->status,
|
|
$user->hire_date ? $user->hire_date->format('Y-m-d') : '',
|
|
$user->roles->pluck('display_name')->join(', ')
|
|
];
|
|
}
|
|
|
|
// Store CSV file
|
|
$fileName = 'users_export_' . now()->format('Y_m_d_H_i_s') . '.csv';
|
|
$csv = '';
|
|
foreach ($csvData as $row) {
|
|
$csv .= '"' . implode('","', $row) . '"' . "\n";
|
|
}
|
|
|
|
Storage::put('exports/' . $fileName, $csv);
|
|
|
|
session()->flash('success', 'Export completed! Downloaded ' . $users->count() . ' users to ' . $fileName);
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', 'Export failed: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function getUserRoles($user)
|
|
{
|
|
return $user->roles()
|
|
->where('user_roles.is_active', true)
|
|
->where(function ($q) {
|
|
$q->whereNull('user_roles.expires_at')
|
|
->orWhere('user_roles.expires_at', '>', now());
|
|
})
|
|
->pluck('display_name')
|
|
->join(', ');
|
|
}
|
|
|
|
public function getUserPermissionCount($user)
|
|
{
|
|
return $user->getAllPermissions()->count();
|
|
}
|
|
|
|
public function hasActiveFilters()
|
|
{
|
|
return !empty($this->search) ||
|
|
!empty($this->roleFilter) ||
|
|
!empty($this->statusFilter) ||
|
|
!empty($this->departmentFilter) ||
|
|
!empty($this->branchFilter) ||
|
|
!empty($this->customerFilter) ||
|
|
!empty($this->hireYearFilter) ||
|
|
$this->showInactive;
|
|
}
|
|
|
|
public function getSelectedCount()
|
|
{
|
|
return count($this->selectedUsers);
|
|
}
|
|
|
|
public function resetFilters()
|
|
{
|
|
$this->clearFilters();
|
|
}
|
|
|
|
public function getRoleBadgeClass($roleName)
|
|
{
|
|
return match($roleName) {
|
|
'super_admin' => 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
|
|
'administrator' => 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200',
|
|
'manager' => 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200',
|
|
'service_coordinator' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200',
|
|
'service_supervisor' => 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200',
|
|
'technician' => 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200',
|
|
'receptionist' => 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200',
|
|
'parts_clerk' => 'bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200',
|
|
'service_advisor' => 'bg-indigo-100 dark:bg-indigo-900 text-indigo-800 dark:text-indigo-200',
|
|
'cashier' => 'bg-pink-100 dark:bg-pink-900 text-pink-800 dark:text-pink-200',
|
|
'customer_portal' => 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200',
|
|
'customer' => 'bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200',
|
|
default => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200',
|
|
};
|
|
}
|
|
|
|
public function getStatusBadgeClass($status)
|
|
{
|
|
return match($status) {
|
|
'active' => 'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200',
|
|
'inactive' => 'bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200',
|
|
'suspended' => 'bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200',
|
|
default => 'bg-zinc-100 dark:bg-zinc-700 text-zinc-800 dark:text-zinc-200',
|
|
};
|
|
}
|
|
|
|
public function getUserActivityClass($user)
|
|
{
|
|
$lastSeen = $user->last_seen_at;
|
|
if (!$lastSeen) return 'text-gray-500';
|
|
|
|
$minutesAgo = now()->diffInMinutes($lastSeen);
|
|
if ($minutesAgo < 5) return 'text-green-500';
|
|
if ($minutesAgo < 60) return 'text-yellow-500';
|
|
if ($minutesAgo < 1440) return 'text-orange-500';
|
|
return 'text-red-500';
|
|
}
|
|
|
|
public function getUserLastSeenText($user)
|
|
{
|
|
$lastSeen = $user->last_seen_at;
|
|
if (!$lastSeen) return 'Never';
|
|
|
|
return $lastSeen->diffForHumans();
|
|
}
|
|
|
|
public function canDeleteUser($user)
|
|
{
|
|
return $user->id !== auth()->id() &&
|
|
$user->jobCards()->count() === 0 &&
|
|
!$user->hasRole('super_admin');
|
|
}
|
|
|
|
public function canModifyUser($user)
|
|
{
|
|
return $user->id !== auth()->id() || auth()->user()->hasRole('super_admin');
|
|
}
|
|
}
|