flash('error', __('Profile not found')); return; } // Validate authorization - ensure authenticated user owns/manages this profile ProfileAuthorizationHelper::authorize($profile); // Validate export type if (!in_array($type, ['xlsx', 'ods', 'csv', 'json'])) { session()->flash('error', __('Invalid export format')); return; } // Get all account IDs for this profile $accountIds = $profile->accounts()->pluck('id')->toArray(); if (empty($accountIds)) { session()->flash('error', __('No accounts found for this profile')); return; } // Get all transactions from all accounts $transactions = Transaction::with([ 'accountTo.accountable:id,name,full_name,profile_photo_path', 'accountFrom.accountable:id,name,full_name,profile_photo_path', 'transactionType:id,name' ]) ->where(function ($query) use ($accountIds) { $query->whereIn('to_account_id', $accountIds) ->orWhereIn('from_account_id', $accountIds); }) ->orderBy('created_at', 'desc') ->get(); // Transform data for export $data = $transactions->map(function ($transaction) use ($accountIds) { // Determine if this is debit or credit for this profile $isDebit = in_array($transaction->from_account_id, $accountIds); // Get the account and counter account $account = $isDebit ? $transaction->accountFrom : $transaction->accountTo; $counterAccount = $isDebit ? $transaction->accountTo : $transaction->accountFrom; // Get relation (the other party) $relation = $counterAccount->accountable; return [ 'trans_id' => $transaction->id, 'datetime' => $transaction->created_at->format('Y-m-d H:i:s'), 'amount' => $transaction->amount, 'c/d' => $isDebit ? 'Debit' : 'Credit', 'account_id' => $account->id, 'account_name' => $account->name, 'account_counter_id' => $counterAccount->id, 'account_counter_name' => $counterAccount->name, 'relation' => $relation->name ?? '', 'relation_full_name' => $relation->full_name ?? $relation->name ?? '', 'type' => $transaction->transactionType->name ?? '', 'description' => $transaction->description ?? '', ]; }); $this->dispatch('saved'); // Handle JSON export differently if ($type === 'json') { $fileName = 'profile-transactions.json'; // Transform data for JSON export $jsonData = $data->map(function ($item) { // Rename amount to amount_minutes and add amount_hours $amountMinutes = $item['amount']; unset($item['amount']); $item['amount_minutes'] = $amountMinutes; $item['amount_hours'] = round($amountMinutes / 60, 4); return $item; })->toArray(); $json = json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); return response()->streamDownload(function () use ($json) { echo $json; }, $fileName, [ 'Content-Type' => 'application/json', ]); } return (new ProfileTransactionsExport($data))->download('profile-transactions.' . $type); } /** * Export profile data */ public function exportProfileData($type) { // Get active profile $profileType = session('activeProfileType'); $profileId = session('activeProfileId'); $profile = $profileType::find($profileId); if (!$profile) { session()->flash('error', __('Profile not found')); return; } // Validate authorization - ensure authenticated user owns/manages this profile ProfileAuthorizationHelper::authorize($profile); // Validate export type if (!in_array($type, ['xlsx', 'ods', 'csv', 'json'])) { session()->flash('error', __('Invalid export format')); return; } // Get location string if available $location = ''; if ($profile->location) { $locationParts = []; if ($profile->location->district) { $locationParts[] = $profile->location->district->name; } if ($profile->location->city) { $locationParts[] = $profile->location->city->name; } if ($profile->location->division) { $locationParts[] = $profile->location->division->name; } if ($profile->location->country) { $locationParts[] = $profile->location->country->name; } $location = implode(', ', $locationParts); } // Get location first name $locationFirstName = ''; $locationFirst = $profile->getLocationFirst(); if ($locationFirst) { $locationFirstName = $locationFirst['name'] ?? ''; } // Get social media accounts formatted as comma-separated string $socials = []; if ($profile->socials) { foreach ($profile->socials as $social) { $isBlueSky = $social->id == 3; $isFullUrl = str_starts_with($social->pivot->user_on_social, 'https://'); if ($isBlueSky) { $socials[] = '@' . $social->pivot->user_on_social; } elseif ($isFullUrl) { $socials[] = $social->pivot->user_on_social; } elseif ($social->pivot->server_of_social) { $socials[] = '@' . $social->pivot->user_on_social . '@' . $social->pivot->server_of_social; } else { $socials[] = '@' . $social->pivot->user_on_social; } } } $socialsString = implode("\n", $socials); // Transform profile data to array $data = collect([[ 'name' => $profile->name, 'full_name' => $profile->full_name ?? '', 'email' => $profile->email ?? '', 'about' => strip_tags($profile->about ?? ''), 'about_short' => $profile->about_short ?? '', 'motivation' => strip_tags($profile->motivation ?? ''), 'website' => $profile->website ?? '', 'phone' => $profile->phone ?? '', 'phone_public' => $profile->phone_public ?? false, 'location' => $location, 'location_first' => $locationFirstName, 'social_media' => $socialsString, 'profile_photo_path' => $profile->profile_photo_path ?? '', 'lang_preference' => $profile->lang_preference ?? '', 'created_at' => $profile->created_at ? (is_object($profile->created_at) ? $profile->created_at->format('Y-m-d H:i:s') : $profile->created_at) : '', 'updated_at' => $profile->updated_at ? (is_object($profile->updated_at) ? $profile->updated_at->format('Y-m-d H:i:s') : $profile->updated_at) : '', 'last_login_at' => $profile->last_login_at ? (is_object($profile->last_login_at) ? $profile->last_login_at->format('Y-m-d H:i:s') : $profile->last_login_at) : '', ]]); $profileTypeName = strtolower(class_basename($profileType)); $this->dispatch('saved'); // Handle JSON export differently if ($type === 'json') { $fileName = 'profile-data.json'; // Transform data for JSON export $jsonData = $data->map(function ($item) { // Remove location key unset($item['location']); // Replace newlines with commas in social_media if (isset($item['social_media'])) { $item['social_media'] = str_replace("\n", ', ', $item['social_media']); } // Reorder keys: put phone_visible_for_platform_users right after phone $ordered = []; foreach ($item as $key => $value) { $ordered[$key] = $value; if ($key === 'phone') { // Rename and insert phone_public right after phone $ordered['phone_visible_for_platform_users'] = $item['phone_public']; } } // Remove the old phone_public key unset($ordered['phone_public']); return $ordered; })->toArray(); $json = json_encode($jsonData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); return response()->streamDownload(function () use ($json) { echo $json; }, $fileName, [ 'Content-Type' => 'application/json', ]); } return (new ProfileDataExport($data, $profileTypeName))->download('profile-data.' . $type); } /** * Export all messages from conversations the profile participated in */ public function exportMessages($type) { set_time_limit(0); // Get active profile $profileType = session('activeProfileType'); $profileId = session('activeProfileId'); $profile = $profileType::find($profileId); if (!$profile) { session()->flash('error', __('Profile not found')); return; } // Validate authorization - ensure authenticated user owns/manages this profile ProfileAuthorizationHelper::authorize($profile); // Validate export type if (!in_array($type, ['xlsx', 'ods', 'csv', 'json'])) { session()->flash('error', __('Invalid export format')); return; } // Get all conversation IDs where the profile is a participant $conversationIds = Participant::where('participantable_type', $profileType) ->where('participantable_id', $profileId) ->pluck('conversation_id'); if ($conversationIds->isEmpty()) { session()->flash('error', __('No conversations found for this profile')); return; } // Get all messages from those conversations with sender information $messages = Message::with([ 'conversation:id,type', 'sendable:id,name,full_name' // Load sender information ]) ->whereIn('conversation_id', $conversationIds) ->orderBy('conversation_id', 'asc') ->orderBy('created_at', 'asc') ->get(); // Transform data for export $data = $messages->map(function ($message) { $conversationType = ''; if ($message->conversation && $message->conversation->type) { $conversationType = is_object($message->conversation->type) ? $message->conversation->type->value : $message->conversation->type; } // Get sender information $senderName = ''; $senderType = ''; if ($message->sendable) { $senderName = $message->sendable->full_name ?? $message->sendable->name ?? ''; $senderType = class_basename($message->sendable_type); } return [ 'conversation_id' => $message->conversation_id, 'conversation_type' => $conversationType, 'id' => $message->id, 'created_at' => $message->created_at ? (is_object($message->created_at) ? $message->created_at->format('Y-m-d H:i:s') : $message->created_at) : '', 'sender_name' => $senderName, 'sender_type' => $senderType, 'body' => $message->body ?? '', 'reply_id' => $message->reply_id ?? '', ]; }); $this->dispatch('saved'); // Handle JSON export differently if ($type === 'json') { $fileName = 'profile-messages.json'; $json = json_encode($data->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); return response()->streamDownload(function () use ($json) { echo $json; }, $fileName, [ 'Content-Type' => 'application/json', ]); } return (new ProfileMessagesExport($data))->download('profile-messages.' . $type); } /** * Export all tags (skills) associated with the profile * Tags are exported in the profile's language preference or fallback locale */ public function exportTags($type) { // Get active profile $profileType = session('activeProfileType'); $profileId = session('activeProfileId'); $profile = $profileType::find($profileId); if (!$profile) { session()->flash('error', __('Profile not found')); return; } // Validate authorization - ensure authenticated user owns/manages this profile ProfileAuthorizationHelper::authorize($profile); // Validate export type if (!in_array($type, ['xlsx', 'ods', 'csv', 'json'])) { session()->flash('error', __('Invalid export format')); return; } // Determine which locale to use for tag translation $locale = $profile->lang_preference ?? App::getLocale(); $fallbackLocale = App::getFallbackLocale(); // Get all tag IDs from the profile $tagIds = $profile->tags->pluck('tag_id'); if ($tagIds->isEmpty()) { session()->flash('error', __('No tags found for this profile')); return; } // Translate tags to the profile's language preference (or fallback) $translatedTags = collect((new Tag())->translateTagIdsWithContexts($tagIds)); // Transform data for export $data = $translatedTags->map(function ($tag) { return [ 'tag_id' => $tag['tag_id'] ?? '', 'tag' => $tag['tag'] ?? '', 'category' => $tag['category'] ?? '', 'category_path' => $tag['category_path'] ?? '', 'locale' => $tag['locale'] ?? '', ]; }); $this->dispatch('saved'); // Handle JSON export differently if ($type === 'json') { $fileName = 'profile-tags.json'; $json = json_encode($data->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); return response()->streamDownload(function () use ($json) { echo $json; }, $fileName, [ 'Content-Type' => 'application/json', ]); } return (new ProfileTagsExport($data))->download('profile-tags.' . $type); } /** * Export all contacts associated with the profile */ public function exportContacts($type) { set_time_limit(0); // Get active profile $profileType = session('activeProfileType'); $profileId = session('activeProfileId'); $profile = $profileType::find($profileId); if (!$profile) { session()->flash('error', __('Profile not found')); return; } // Validate authorization - ensure authenticated user owns/manages this profile ProfileAuthorizationHelper::authorize($profile); // Validate export type if (!in_array($type, ['xlsx', 'ods', 'csv', 'json'])) { session()->flash('error', __('Invalid export format')); return; } // Initialize contacts collection $contactsData = collect(); // Get the reacter_id and reactant_id for the active profile $reacterId = $profile->love_reacter_id; $reactantId = $profile->love_reactant_id; // 1. Get profiles the active profile has reacted to (stars, bookmarks) if ($reacterId) { $reactedProfiles = $this->getReactedProfilesForExport($reacterId); $contactsData = $contactsData->merge($reactedProfiles); } // 2. Get profiles that have transacted with the active profile $transactionProfiles = $this->getTransactionProfilesForExport($profile); $contactsData = $contactsData->merge($transactionProfiles); // 3. Get profiles from private WireChat conversations $conversationProfiles = $this->getConversationProfilesForExport($profile); $contactsData = $contactsData->merge($conversationProfiles); // Group by profile and merge interaction data $contacts = $contactsData->groupBy('profile_key')->map(function ($group) { $first = $group->first(); 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'], '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'), '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(); // Sort by last interaction (most recent first) $contacts = $contacts->sortByDesc('last_interaction')->values(); $this->dispatch('saved'); // Handle JSON export differently if ($type === 'json') { $fileName = 'profile-contacts.json'; $json = json_encode($contacts->toArray(), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); return response()->streamDownload(function () use ($json) { echo $json; }, $fileName, [ 'Content-Type' => 'application/json', ]); } return (new ProfileContactsExport($contacts))->download('profile-contacts.' . $type); } /** * Get profiles the active profile has reacted to. */ private function getReactedProfilesForExport($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->batchLoadLocationsForExport($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 getTransactionProfilesForExport($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->batchLoadLocationsForExport($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 getConversationProfilesForExport($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->batchLoadLocationsForExport($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. */ private function batchLoadLocationsForExport($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; } public function render() { return view('livewire.profile.export-profile-data'); } }