sortField === $field) { $this->sortAsc = !$this->sortAsc; } else { $this->sortField = $field; $this->sortAsc = true; } $this->resetPage(); } protected $rules = [ 'search' => 'nullable|string|min:2|max:100', ]; protected $messages = [ 'search.min' => 'Search must be at least 2 characters.', 'search.max' => 'Search cannot exceed 100 characters.', ]; /** * Apply search and filter when button is clicked. */ public function applySearch() { // Validate search input if provided if (!empty($this->searchInput) && strlen($this->searchInput) < 2) { $this->addError('search', 'Search must be at least 2 characters.'); return; } if (!empty($this->searchInput) && strlen($this->searchInput) > 100) { $this->addError('search', 'Search cannot exceed 100 characters.'); return; } // Apply the input values to the actual search properties $this->search = $this->searchInput; $this->filterType = $this->filterTypeInput; // Validate filterRole against the allowed set to prevent arbitrary value injection $allowedRoles = ['any', 'organization-coordinator', 'organization-manager', 'bank-coordinator', 'bank-manager', 'administrator']; $this->filterRole = in_array($this->filterRoleInput, $allowedRoles, true) ? $this->filterRoleInput : ''; // Reset to first page when searching $this->resetPage(); } /** * Get all contacts (profiles) the active profile has interacted with. * Includes interactions from: * - Laravel-love reactions (bookmarks, stars) * - Transactions (sent to or received from) * - WireChat private conversations * * @return \Illuminate\Pagination\LengthAwarePaginator */ public function getContacts() { $activeProfile = getActiveProfile(); if (!$activeProfile) { // Return empty paginator instead of null return new \Illuminate\Pagination\LengthAwarePaginator( collect([]), 0, $this->perPage, 1, ['path' => request()->url(), 'pageName' => 'page'] ); } // CRITICAL SECURITY: Validate user has ownership/access to this profile \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile); // Initialize contacts collection $contactsData = collect(); // Get the reacter_id and reactant_id for the active profile $reacterId = $activeProfile->love_reacter_id; $reactantId = $activeProfile->love_reactant_id; // If no filters selected, show all $showAll = empty($this->filterType); // 1. Get profiles the active profile has reacted to (stars, bookmarks) if ($showAll || in_array('stars', $this->filterType) || in_array('bookmarks', $this->filterType)) { if ($reacterId) { $reactedProfiles = $this->getReactedProfiles($reacterId); // Filter by specific reaction types if selected if (!$showAll && (in_array('stars', $this->filterType) || in_array('bookmarks', $this->filterType))) { $reactedProfiles = $reactedProfiles->filter(function ($profile) { if (in_array('stars', $this->filterType) && $profile['interaction_type'] === 'star') { return true; } if (in_array('bookmarks', $this->filterType) && $profile['interaction_type'] === 'bookmark') { return true; } return false; }); } $contactsData = $contactsData->merge($reactedProfiles); } } // 2. Get profiles that have transacted with the active profile if ($showAll || in_array('transactions', $this->filterType)) { $transactionProfiles = $this->getTransactionProfiles($activeProfile); $contactsData = $contactsData->merge($transactionProfiles); } // 3. Get profiles from private WireChat conversations if ($showAll || in_array('conversations', $this->filterType)) { $conversationProfiles = $this->getConversationProfiles($activeProfile); $contactsData = $contactsData->merge($conversationProfiles); } // 4. For Organization, Bank, and Admin profiles: include linked users $activeProfileType = getActiveProfileType(); if (in_array($activeProfileType, ['Organization', 'Bank', 'Admin'])) { if ($showAll || in_array('linked', $this->filterType)) { $linkedProfiles = $this->getLinkedProfiles($activeProfile, $activeProfileType); $contactsData = $contactsData->merge($linkedProfiles); } } // Group by profile and merge interaction data $contacts = $contactsData->groupBy('profile_key')->map(function ($group) { $first = $group->first(); // Construct profile path using the localized route $profileTypeLower = strtolower($first['profile_type_name']); $profilePath = route('profile.show_by_type_and_id', ['type' => __($profileTypeLower), 'id' => $first['profile_id']]); // Resolve org_role: prefer linked entry which carries the actual role $linkedEntry = $group->firstWhere('interaction_type', 'linked'); $orgRole = $linkedEntry['org_role'] ?? null; return [ 'profile_id' => $first['profile_id'], 'profile_type' => $first['profile_type'], 'profile_type_name' => $first['profile_type_name'], 'name' => $first['name'], 'full_name' => $first['full_name'], 'location' => $first['location'], 'profile_photo' => $first['profile_photo'], 'profile_path' => $profilePath, 'has_star' => $group->contains('interaction_type', 'star'), 'has_bookmark' => $group->contains('interaction_type', 'bookmark'), 'has_transaction' => $group->contains('interaction_type', 'transaction'), 'has_conversation' => $group->contains('interaction_type', 'conversation'), 'org_role' => $orgRole, 'last_interaction' => $group->max('last_interaction'), 'star_count' => $group->where('interaction_type', 'star')->sum('count'), 'bookmark_count' => $group->where('interaction_type', 'bookmark')->sum('count'), 'transaction_count' => $group->where('interaction_type', 'transaction')->sum('count'), 'message_count' => $group->where('interaction_type', 'conversation')->sum('count'), ]; })->values(); // Apply role filter (only meaningful for Org/Bank/Admin profiles) if (!empty($this->filterRole)) { if ($this->filterRole === 'any') { // Show only contacts that have any linked role $contacts = $contacts->filter(function ($contact) { return !empty($contact['org_role']); })->values(); } else { // Show only contacts with the specific role $contacts = $contacts->filter(function ($contact) { return ($contact['org_role'] ?? null) === $this->filterRole; })->values(); } } // Apply search filter if (!empty($this->search) && strlen(trim($this->search)) >= 2) { $search = strtolower(trim($this->search)); $contacts = $contacts->filter(function ($contact) use ($search) { $name = strtolower($contact['name'] ?? ''); $fullName = strtolower($contact['full_name'] ?? ''); $location = strtolower($contact['location'] ?? ''); return str_contains($name, $search) || str_contains($fullName, $search) || str_contains($location, $search); })->values(); } // Sort contacts $contacts = $this->sortContacts($contacts); // Paginate manually $currentPage = $this->paginators['page'] ?? 1; $total = $contacts->count(); $items = $contacts->forPage($currentPage, $this->perPage); return new \Illuminate\Pagination\LengthAwarePaginator( $items, $total, $this->perPage, $currentPage, ['path' => request()->url(), 'pageName' => 'page'] ); } /** * Get linked users (coordinators/managers) for Organization, Bank, or Admin profiles. * Each linked user entry carries an org_role resolved from Spatie Permission roles. */ private function getLinkedProfiles($activeProfile, string $activeProfileType): \Illuminate\Support\Collection { $profiles = collect(); $parentId = $activeProfile->id; $relationMethod = $activeProfileType === 'Bank' ? 'managers' : 'users'; $users = $activeProfile->$relationMethod() ->select('users.id', 'users.name', 'users.full_name', 'users.profile_photo_path') ->with('roles') ->get(); $userIds = $users->pluck('id'); $locations = $this->batchLoadLocations(User::class, $userIds); foreach ($users as $user) { $role = $this->resolveLinkedUserRole($user, $activeProfileType, $parentId); $profiles->push([ 'profile_key' => 'App\\Models\\User_' . $user->id, 'profile_id' => $user->id, 'profile_type' => User::class, 'profile_type_name' => 'User', 'name' => $user->name, 'full_name' => $user->full_name, 'location' => $locations[$user->id] ?? '', 'profile_photo' => $user->profile_photo_path, 'interaction_type' => 'linked', 'org_role' => $role, 'last_interaction' => null, 'count' => 0, ]); } return $profiles; } /** * Resolve the role of a linked user for the given parent profile type/id. * Mirrors the logic in Profiles\Manage::resolveLinkedUserRole(). */ private function resolveLinkedUserRole($user, string $parentType, int $parentId): string { $coordinatorMap = [ 'Organization' => 'organization-coordinator', 'Bank' => 'bank-coordinator', ]; $managerMap = [ 'Organization' => 'organization-manager', 'Bank' => 'bank-manager', 'Admin' => 'administrator', ]; $coordinatorRole = isset($coordinatorMap[$parentType]) ? "{$parentType}\\{$parentId}\\{$coordinatorMap[$parentType]}" : null; if ($coordinatorRole && $user->hasRole($coordinatorRole)) { return $coordinatorMap[$parentType]; } return $managerMap[$parentType] ?? 'organization-manager'; } /** * Get profiles the active profile has reacted to. */ private function getReactedProfiles($reacterId) { // Get all reactions by this reacter, grouped by reactant type $reactions = DB::table('love_reactions') ->join('love_reactants', 'love_reactions.reactant_id', '=', 'love_reactants.id') ->where('love_reactions.reacter_id', $reacterId) ->select( 'love_reactants.type as reactant_type', DB::raw('CAST(SUBSTRING_INDEX(love_reactants.type, "\\\\", -1) AS CHAR) as reactant_model') ) ->groupBy('love_reactants.type') ->get(); $profiles = collect(); foreach ($reactions as $reaction) { // Only process User, Organization, and Bank models if (!in_array($reaction->reactant_model, ['User', 'Organization', 'Bank'])) { continue; } $modelClass = "App\\Models\\{$reaction->reactant_model}"; // Get all profiles of this type that were reacted to, with reaction type breakdown $reactedToProfiles = DB::table('love_reactions') ->join('love_reactants', 'love_reactions.reactant_id', '=', 'love_reactants.id') ->join( DB::raw("(SELECT id, love_reactant_id, name, full_name, profile_photo_path FROM " . strtolower($reaction->reactant_model) . "s) as profiles"), 'love_reactants.id', '=', 'profiles.love_reactant_id' ) ->where('love_reactions.reacter_id', $reacterId) ->where('love_reactants.type', $reaction->reactant_type) ->select( 'profiles.id as profile_id', 'profiles.name', 'profiles.full_name', 'profiles.profile_photo_path', DB::raw("'{$modelClass}' as profile_type"), DB::raw("'{$reaction->reactant_model}' as profile_type_name"), 'love_reactions.reaction_type_id', DB::raw('MAX(love_reactions.created_at) as last_interaction'), DB::raw('COUNT(*) as count') ) ->groupBy('profiles.id', 'profiles.name', 'profiles.full_name', 'profiles.profile_photo_path', 'love_reactions.reaction_type_id') ->get(); // Batch load locations for all profiles of this type $profileIds = $reactedToProfiles->pluck('profile_id'); $locations = $this->batchLoadLocations($modelClass, $profileIds); foreach ($reactedToProfiles as $profile) { // Get location from batch-loaded data $location = $locations[$profile->profile_id] ?? ''; // Determine reaction type (1 = Star, 2 = Bookmark) $interactionType = $profile->reaction_type_id == 1 ? 'star' : ($profile->reaction_type_id == 2 ? 'bookmark' : 'reaction'); $profiles->push([ 'profile_key' => $modelClass . '_' . $profile->profile_id, 'profile_id' => $profile->profile_id, 'profile_type' => $profile->profile_type, 'profile_type_name' => $profile->profile_type_name, 'name' => $profile->name, 'full_name' => $profile->full_name, 'location' => $location, 'profile_photo' => $profile->profile_photo_path, 'interaction_type' => $interactionType, 'last_interaction' => $profile->last_interaction, 'count' => $profile->count, ]); } } return $profiles; } /** * Get profiles that have transacted with the active profile. */ private function getTransactionProfiles($activeProfile) { // Get all accounts belonging to the active profile $accountIds = DB::table('accounts') ->where('accountable_type', get_class($activeProfile)) ->where('accountable_id', $activeProfile->id) ->pluck('id'); if ($accountIds->isEmpty()) { return collect(); } // Get all transactions involving these accounts $transactions = DB::table('transactions') ->whereIn('from_account_id', $accountIds) ->orWhereIn('to_account_id', $accountIds) ->select( 'from_account_id', 'to_account_id', DB::raw('MAX(created_at) as last_interaction'), DB::raw('COUNT(*) as count') ) ->groupBy('from_account_id', 'to_account_id') ->get(); // Group counter accounts by type for batch loading $counterAccountsByType = collect(); foreach ($transactions as $transaction) { // Determine the counter account (the other party in the transaction) $counterAccountId = null; if ($accountIds->contains($transaction->from_account_id) && !$accountIds->contains($transaction->to_account_id)) { $counterAccountId = $transaction->to_account_id; } elseif ($accountIds->contains($transaction->to_account_id) && !$accountIds->contains($transaction->from_account_id)) { $counterAccountId = $transaction->from_account_id; } if ($counterAccountId) { $transaction->counter_account_id = $counterAccountId; } } // Get all counter account details in one query $counterAccountIds = $transactions->pluck('counter_account_id')->filter()->unique(); $accounts = DB::table('accounts') ->whereIn('id', $counterAccountIds) ->select('id', 'accountable_type', 'accountable_id') ->get() ->keyBy('id'); // Group profile IDs by type $profileIdsByType = []; foreach ($accounts as $account) { $profileTypeName = class_basename($account->accountable_type); if (!isset($profileIdsByType[$profileTypeName])) { $profileIdsByType[$profileTypeName] = []; } $profileIdsByType[$profileTypeName][] = $account->accountable_id; } // Batch load profile data and locations for each type $profileDataByType = []; $locationsByType = []; foreach ($profileIdsByType as $typeName => $ids) { $tableName = strtolower($typeName) . 's'; $modelClass = "App\\Models\\{$typeName}"; // Load profile data $profileDataByType[$typeName] = DB::table($tableName) ->whereIn('id', $ids) ->select('id', 'name', 'full_name', 'profile_photo_path') ->get() ->keyBy('id'); // Batch load locations $locationsByType[$typeName] = $this->batchLoadLocations($modelClass, $ids); } // Build final profiles collection $profiles = collect(); foreach ($transactions as $transaction) { if (!isset($transaction->counter_account_id)) { continue; // Skip self-transactions } $account = $accounts->get($transaction->counter_account_id); if (!$account) { continue; } $profileModel = $account->accountable_type; $profileId = $account->accountable_id; $profileTypeName = class_basename($profileModel); $profile = $profileDataByType[$profileTypeName][$profileId] ?? null; if (!$profile) { continue; } $location = $locationsByType[$profileTypeName][$profileId] ?? ''; $profileKey = $profileModel . '_' . $profileId; $profiles->push([ 'profile_key' => $profileKey, 'profile_id' => $profileId, 'profile_type' => $profileModel, 'profile_type_name' => $profileTypeName, 'name' => $profile->name, 'full_name' => $profile->full_name, 'location' => $location, 'profile_photo' => $profile->profile_photo_path, 'interaction_type' => 'transaction', 'last_interaction' => $transaction->last_interaction, 'count' => $transaction->count, ]); } return $profiles; } /** * Get profiles from private WireChat conversations. */ private function getConversationProfiles($activeProfile) { // Get all private conversations the active profile is participating in $participantType = get_class($activeProfile); $participantId = $activeProfile->id; // Get participant record for active profile $myParticipants = DB::table('wirechat_participants') ->join('wirechat_conversations', 'wirechat_participants.conversation_id', '=', 'wirechat_conversations.id') ->where('wirechat_participants.participantable_type', $participantType) ->where('wirechat_participants.participantable_id', $participantId) ->where('wirechat_conversations.type', ConversationType::PRIVATE->value) ->whereNull('wirechat_participants.deleted_at') ->select( 'wirechat_participants.conversation_id', 'wirechat_participants.last_active_at' ) ->get(); if ($myParticipants->isEmpty()) { return collect(); } $conversationIds = $myParticipants->pluck('conversation_id'); // Get all other participants in one query $otherParticipants = DB::table('wirechat_participants') ->whereIn('conversation_id', $conversationIds) ->where(function ($query) use ($participantType, $participantId) { $query->where('participantable_type', '!=', $participantType) ->orWhere('participantable_id', '!=', $participantId); }) ->whereNull('deleted_at') ->get() ->keyBy('conversation_id'); // Get message counts for all conversations in one query $messageCounts = DB::table('wirechat_messages') ->whereIn('conversation_id', $conversationIds) ->whereNull('deleted_at') ->select( 'conversation_id', DB::raw('COUNT(DISTINCT DATE(created_at)) as day_count') ) ->groupBy('conversation_id') ->get() ->keyBy('conversation_id'); // Get last messages for all conversations in one query $lastMessages = DB::table('wirechat_messages as wm1') ->whereIn('wm1.conversation_id', $conversationIds) ->whereNull('wm1.deleted_at') ->whereRaw('wm1.created_at = (SELECT MAX(wm2.created_at) FROM wirechat_messages wm2 WHERE wm2.conversation_id = wm1.conversation_id AND wm2.deleted_at IS NULL)') ->select('wm1.conversation_id', 'wm1.created_at') ->get() ->keyBy('conversation_id'); // Group profile IDs by type $profileIdsByType = []; foreach ($otherParticipants as $participant) { $profileTypeName = class_basename($participant->participantable_type); if (!isset($profileIdsByType[$profileTypeName])) { $profileIdsByType[$profileTypeName] = []; } $profileIdsByType[$profileTypeName][] = $participant->participantable_id; } // Batch load profile data and locations for each type $profileDataByType = []; $locationsByType = []; foreach ($profileIdsByType as $typeName => $ids) { $tableName = strtolower($typeName) . 's'; $modelClass = "App\\Models\\{$typeName}"; // Load profile data $profileDataByType[$typeName] = DB::table($tableName) ->whereIn('id', $ids) ->select('id', 'name', 'full_name', 'profile_photo_path') ->get() ->keyBy('id'); // Batch load locations $locationsByType[$typeName] = $this->batchLoadLocations($modelClass, $ids); } // Build final profiles collection $profiles = collect(); foreach ($myParticipants as $myParticipant) { $otherParticipant = $otherParticipants->get($myParticipant->conversation_id); if (!$otherParticipant) { continue; } $profileModel = $otherParticipant->participantable_type; $profileId = $otherParticipant->participantable_id; $profileTypeName = class_basename($profileModel); $profile = $profileDataByType[$profileTypeName][$profileId] ?? null; if (!$profile) { continue; } $messageCount = $messageCounts->get($myParticipant->conversation_id)->day_count ?? 0; $lastMessage = $lastMessages->get($myParticipant->conversation_id); $location = $locationsByType[$profileTypeName][$profileId] ?? ''; $profileKey = $profileModel . '_' . $profileId; $profiles->push([ 'profile_key' => $profileKey, 'profile_id' => $profileId, 'profile_type' => $profileModel, 'profile_type_name' => $profileTypeName, 'name' => $profile->name, 'full_name' => $profile->full_name, 'location' => $location, 'profile_photo' => $profile->profile_photo_path, 'interaction_type' => 'conversation', 'last_interaction' => $lastMessage ? $lastMessage->created_at : $myParticipant->last_active_at, 'count' => $messageCount, ]); } return $profiles; } /** * Batch load locations for multiple profiles of the same type. * This replaces the N+1 query problem in getProfileLocation(). * * @param string $modelClass The model class (e.g., 'App\Models\User') * @param array|\Illuminate\Support\Collection $profileIds Array of profile IDs * @return array Associative array of profile_id => location_name */ private function batchLoadLocations($modelClass, $profileIds) { if (empty($profileIds)) { return []; } // Ensure it's an array if ($profileIds instanceof \Illuminate\Support\Collection) { $profileIds = $profileIds->toArray(); } // Load all profiles with their location relationships $profiles = $modelClass::with([ 'locations.city.translations', 'locations.district.translations', 'locations.division.translations', 'locations.country.translations' ]) ->whereIn('id', $profileIds) ->get(); // Build location map $locationMap = []; foreach ($profiles as $profile) { if (method_exists($profile, 'getLocationFirst')) { $locationData = $profile->getLocationFirst(false); $locationMap[$profile->id] = $locationData['name'] ?? $locationData['name_short'] ?? ''; } else { $locationMap[$profile->id] = ''; } } return $locationMap; } /** * Get location for a profile (deprecated, use batchLoadLocations instead). * Kept for backwards compatibility. */ private function getProfileLocation($modelClass, $profileId) { $locations = $this->batchLoadLocations($modelClass, [$profileId]); return $locations[$profileId] ?? ''; } /** * Sort contacts based on sort field and direction. */ private function sortContacts($contacts) { $sortField = $this->sortField; $sortAsc = $this->sortAsc; return $contacts->sort(function ($a, $b) use ($sortField, $sortAsc) { $aVal = $a[$sortField] ?? ''; $bVal = $b[$sortField] ?? ''; if ($sortField === 'last_interaction') { $aTime = $aVal ? strtotime($aVal) : 0; $bTime = $bVal ? strtotime($bVal) : 0; $comparison = $bTime <=> $aTime; // Default: most recent first return $sortAsc ? -$comparison : $comparison; } // For count fields, use numeric comparison if (in_array($sortField, ['transaction_count', 'message_count'])) { $comparison = ($aVal ?? 0) <=> ($bVal ?? 0); return $sortAsc ? $comparison : -$comparison; } // For boolean fields if (in_array($sortField, ['has_star', 'has_bookmark'])) { $comparison = ($aVal ? 1 : 0) <=> ($bVal ? 1 : 0); return $sortAsc ? $comparison : -$comparison; } // String comparison for name, location, org_role, etc. $comparison = strcasecmp((string)($aVal ?? ''), (string)($bVal ?? '')); return $sortAsc ? $comparison : -$comparison; })->values(); } /** * Reset search and filters. */ public function resetSearch() { $this->resetPage(); $this->showSearchSection = false; $this->search = null; $this->searchInput = ''; $this->filterType = []; $this->filterTypeInput = []; $this->filterRole = ''; $this->filterRoleInput = ''; } /** * Scroll to top when page changes. */ public function updatedPage() { $this->dispatch('scroll-to-top'); } /** * Open the confirmation dialog for removing a coordinator. * Profile name is fetched server-side — never trusted from the client. */ public function confirmRemoveCoordinator(int $profileId) { $activeProfile = getActiveProfile(); \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile); $activeProfileType = getActiveProfileType(); $user = Auth::guard('web')->user(); // Only managers may remove coordinators $managerRoles = [ 'Organization' => "Organization\\{$activeProfile->id}\\organization-manager", 'Bank' => "Bank\\{$activeProfile->id}\\bank-manager", ]; if (!$user || !isset($managerRoles[$activeProfileType]) || !$user->hasRole($managerRoles[$activeProfileType])) { return; } // Verify the target user is actually linked to the active profile $relationMethod = $activeProfileType === 'Bank' ? 'managers' : 'users'; $isLinked = $activeProfile->$relationMethod()->where('users.id', $profileId)->exists(); if (!$isLinked) { return; } // Fetch name server-side — never trust client-supplied strings $coordinator = User::find($profileId); if (!$coordinator) { return; } $this->removingProfileId = $profileId; $this->removingProfileName = $coordinator->name; $this->confirmingRemoveCoordinator = true; } /** * Remove the coordinator after confirmation. */ public function removeCoordinator() { $activeProfile = getActiveProfile(); \App\Helpers\ProfileAuthorizationHelper::authorize($activeProfile); $activeProfileType = getActiveProfileType(); $user = Auth::guard('web')->user(); $managerRoles = [ 'Organization' => "Organization\\{$activeProfile->id}\\organization-manager", 'Bank' => "Bank\\{$activeProfile->id}\\bank-manager", ]; if (!$user || !isset($managerRoles[$activeProfileType]) || !$user->hasRole($managerRoles[$activeProfileType])) { $this->confirmingRemoveCoordinator = false; return; } $coordinator = User::find($this->removingProfileId); if (!$coordinator) { $this->confirmingRemoveCoordinator = false; return; } // Re-verify the target user is actually linked to the active profile // (guards against state manipulation between confirm and execute) $relationMethod = $activeProfileType === 'Bank' ? 'managers' : 'users'; $isLinked = $activeProfile->$relationMethod()->where('users.id', $coordinator->id)->exists(); if (!$isLinked) { $this->confirmingRemoveCoordinator = false; return; } // Detach from the pivot table $activeProfile->$relationMethod()->detach($coordinator->id); // Remove all coordinator/manager Spatie roles for this profile $roleBasePrefixes = [ 'Organization' => ['organization-coordinator', 'organization-manager'], 'Bank' => ['bank-coordinator', 'bank-manager'], ]; foreach ($roleBasePrefixes[$activeProfileType] as $base) { $roleName = "{$activeProfileType}\\{$activeProfile->id}\\{$base}"; if ($coordinator->hasRole($roleName)) { $coordinator->removeRole($roleName); } } $this->confirmingRemoveCoordinator = false; $this->removingProfileId = null; $this->removingProfileName = ''; } public function render() { $activeProfileType = getActiveProfileType(); $roleLabels = [ 'organization-coordinator' => __('Coordinator'), 'organization-manager' => __('Manager'), 'bank-coordinator' => __('Coordinator'), 'bank-manager' => __('Manager'), 'administrator' => __('Administrator'), ]; $availableRolesByType = [ 'Organization' => ['organization-coordinator', 'organization-manager'], 'Bank' => ['bank-coordinator', 'bank-manager'], 'Admin' => ['administrator'], ]; $availableRoles = []; if (isset($availableRolesByType[$activeProfileType])) { foreach ($availableRolesByType[$activeProfileType] as $roleKey) { $availableRoles[$roleKey] = $roleLabels[$roleKey] ?? $roleKey; } } // Determine if auth web user is a manager for the active profile $authIsManager = false; $activeProfile = getActiveProfile(); if ($activeProfile && in_array($activeProfileType, ['Organization', 'Bank'])) { $webUser = Auth::guard('web')->user(); $managerRoles = [ 'Organization' => "Organization\\{$activeProfile->id}\\organization-manager", 'Bank' => "Bank\\{$activeProfile->id}\\bank-manager", ]; $authIsManager = $webUser && $webUser->hasRole($managerRoles[$activeProfileType]); } return view('livewire.contacts.show', [ 'contacts' => $this->getContacts(), 'showRoleColumn' => in_array($activeProfileType, ['Organization', 'Bank', 'Admin']), 'availableRoles' => $availableRoles, 'roleLabels' => $roleLabels, 'authIsManager' => $authIsManager, ]); } }