Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Livewire\Component;
|
||||
|
||||
class SwitchProfileUnreadIndicator extends Component
|
||||
{
|
||||
use \App\Traits\ListensToProfileChats;
|
||||
|
||||
public $hasAnyUnread = false;
|
||||
|
||||
public function getListeners(): array
|
||||
{
|
||||
// Chat events of every profile of the logged-in user, the active one included: the keys
|
||||
// must stay the same after a profile switch (see ListensToProfileChats). The unread status
|
||||
// itself still only counts the non-active profiles (loadUnreadStatus).
|
||||
return array_merge(['refresh' => 'refreshComponent'], $this->profileChatListeners('refreshComponent'));
|
||||
}
|
||||
|
||||
public function refreshComponent(): void
|
||||
{
|
||||
$this->loadUnreadStatus();
|
||||
$this->dispatch('switch-profile-unread-updated', hasUnread: $this->hasAnyUnread);
|
||||
$this->dispatch('refreshSwitchProfileUnread');
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadUnreadStatus();
|
||||
|
||||
// Dispatch browser event to notify JavaScript
|
||||
$this->dispatch('switch-profile-unread-updated', hasUnread: $this->hasAnyUnread);
|
||||
}
|
||||
|
||||
private function loadUnreadStatus()
|
||||
{
|
||||
$webUser = Auth::guard('web')->user();
|
||||
if (!$webUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get user's profiles using same logic as SwitchProfile
|
||||
$userWithRelations = \App\Models\User::with([
|
||||
'organizations',
|
||||
'banksManaged',
|
||||
'admins'
|
||||
])->find($webUser->id);
|
||||
|
||||
if (!$userWithRelations) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter out removed (soft-deleted) items
|
||||
$organizations = $userWithRelations->organizations->filter(
|
||||
fn ($organizations) => $organizations->deleted_at === null || $organizations->deleted_at > now()
|
||||
);
|
||||
$banks = $userWithRelations->banksManaged->filter(
|
||||
fn ($bank) => $bank->deleted_at === null || $bank->deleted_at > now()
|
||||
);
|
||||
$admins = $userWithRelations->admins->filter(
|
||||
fn ($admin) => $admin->deleted_at === null || $admin->deleted_at > now()
|
||||
);
|
||||
|
||||
// Merge profiles collections
|
||||
$profiles = $organizations
|
||||
->concat($banks)
|
||||
->concat($admins);
|
||||
|
||||
// Collect all profile IDs — exclude the web user's own User profile,
|
||||
// as UnreadIndicator handles that dot for the active profile
|
||||
$profileIds = [
|
||||
'user' => [],
|
||||
'admin' => [],
|
||||
'bank' => [],
|
||||
'organization' => [],
|
||||
];
|
||||
|
||||
foreach ($profiles as $profile) {
|
||||
if ($profile instanceof \App\Models\Organization) {
|
||||
$profileIds['organization'][] = $profile->id;
|
||||
} elseif ($profile instanceof \App\Models\Bank) {
|
||||
$profileIds['bank'][] = $profile->id;
|
||||
} elseif ($profile instanceof \App\Models\Admin) {
|
||||
$profileIds['admin'][] = $profile->id;
|
||||
}
|
||||
}
|
||||
|
||||
// Get active profile info to exclude it from unread check
|
||||
$activeProfileType = session('activeProfileType');
|
||||
$activeProfileId = session('activeProfileId');
|
||||
|
||||
// Check for unread messages
|
||||
$profilesWithUnread = $this->checkUnreadMessages($profileIds);
|
||||
|
||||
// Check if ANY non-active org/bank/admin profile has unread messages
|
||||
$activeTypeKey = null;
|
||||
if ($activeProfileType) {
|
||||
$activeTypeKey = match($activeProfileType) {
|
||||
'App\\Models\\Admin', 'App\Models\Admin' => 'admin',
|
||||
'App\\Models\\Bank', 'App\Models\Bank' => 'bank',
|
||||
'App\\Models\\Organization', 'App\Models\Organization' => 'organization',
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
$this->hasAnyUnread = false;
|
||||
foreach (['admin', 'bank', 'organization'] as $type) {
|
||||
if (!empty($profilesWithUnread[$type])) {
|
||||
foreach ($profilesWithUnread[$type] as $profileId => $hasUnread) {
|
||||
if ($activeTypeKey === $type && $activeProfileId == $profileId) {
|
||||
continue;
|
||||
}
|
||||
if ($hasUnread) {
|
||||
$this->hasAnyUnread = true;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function checkUnreadMessages(array $profileIds): array
|
||||
{
|
||||
$result = [
|
||||
'user' => false,
|
||||
'admin' => [],
|
||||
'bank' => [],
|
||||
'organization' => [],
|
||||
];
|
||||
|
||||
// Collect all profile type/id pairs
|
||||
$profilePairs = [];
|
||||
foreach ($profileIds as $type => $ids) {
|
||||
foreach ($ids as $id) {
|
||||
$modelClass = $this->getModelClass($type);
|
||||
$profilePairs[] = ['type' => $modelClass, 'id' => $id];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($profilePairs)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Use parameter binding with IN clause for better escaping
|
||||
$typeIds = collect($profilePairs)->groupBy('type')->map(function ($items) {
|
||||
return $items->pluck('id')->toArray();
|
||||
})->toArray();
|
||||
|
||||
// Check each type separately and combine results
|
||||
foreach ($typeIds as $modelClass => $ids) {
|
||||
$unreadForType = DB::table('wirechat_participants as p')
|
||||
->select('p.participantable_type', 'p.participantable_id')
|
||||
->join('wirechat_conversations as c', 'p.conversation_id', '=', 'c.id')
|
||||
->where('p.participantable_type', '=', $modelClass)
|
||||
->whereIn('p.participantable_id', $ids)
|
||||
->whereNull('p.deleted_at')
|
||||
->whereExists(function ($query) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('wirechat_messages as m')
|
||||
->whereColumn('m.conversation_id', 'p.conversation_id')
|
||||
->whereNull('m.deleted_at')
|
||||
->where(function ($q) {
|
||||
$q->whereNull('p.conversation_read_at')
|
||||
->orWhereColumn('m.created_at', '>', 'p.conversation_read_at');
|
||||
})
|
||||
->where(function ($q) {
|
||||
$q->where('m.sendable_id', '!=', DB::raw('p.participantable_id'))
|
||||
->orWhere('m.sendable_type', '!=', DB::raw('p.participantable_type'));
|
||||
});
|
||||
})
|
||||
->get();
|
||||
|
||||
foreach ($unreadForType as $row) {
|
||||
$type = $this->getTypeFromModelClass($row->participantable_type);
|
||||
$id = $row->participantable_id;
|
||||
|
||||
if ($type === 'user') {
|
||||
$result['user'] = true;
|
||||
} else {
|
||||
$result[$type][$id] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getModelClass(string $type): string
|
||||
{
|
||||
return match($type) {
|
||||
'user' => 'App\Models\User',
|
||||
'admin' => 'App\Models\Admin',
|
||||
'bank' => 'App\Models\Bank',
|
||||
'organization' => 'App\Models\Organization',
|
||||
};
|
||||
}
|
||||
|
||||
private function getTypeFromModelClass(string $class): string
|
||||
{
|
||||
return match($class) {
|
||||
'App\\Models\\User', 'App\Models\User' => 'user',
|
||||
'App\\Models\\Admin', 'App\Models\Admin' => 'admin',
|
||||
'App\\Models\\Bank', 'App\Models\Bank' => 'bank',
|
||||
'App\\Models\\Organization', 'App\Models\Organization' => 'organization',
|
||||
default => 'unknown'
|
||||
};
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.switch-profile-unread-indicator');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user