Files
timebank-cc-public/app/Http/Livewire/WelcomePage/CallCardCarousel.php
T

161 lines
6.7 KiB
PHP

<?php
namespace App\Http\Livewire\WelcomePage;
use App\Http\Livewire\Calls\CallCarouselScorer;
use App\Http\Livewire\Calls\ProfileCalls;
use App\Models\Call;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Livewire\Attributes\Locked;
use Livewire\Component;
class CallCardCarousel extends Component
{
#[Locked]
public array $calls = [];
public bool $random = false;
public int $maxCards = 0;
public bool $showScore = false;
public bool $showReactions = true;
public int $guestPhotoBlur = 0;
public int $guestPhotoContrast = 100;
public int $guestPhotoSaturate = 100;
public int $guestPhotoBrightness = 100;
public function mount(bool $random = false, int $maxCards = 0): void
{
$cfg = timebank_config('calls.welcome_carousel', []);
$this->maxCards = (int) ($cfg['max_cards'] ?? ($maxCards ?: 12));
$this->random = $random;
$this->showScore = (bool) ($cfg['show_score'] ?? false)
|| (getActiveProfileType() === 'Admin' && (bool) ($cfg['show_score_for_admins'] ?? true));
$this->guestPhotoBlur = (int) timebank_config('calls.guest_photo_blur_px', 0);
$this->guestPhotoContrast = (int) timebank_config('calls.guest_photo_contrast', 100);
$this->guestPhotoSaturate = (int) timebank_config('calls.guest_photo_saturate', 100);
$this->guestPhotoBrightness = (int) timebank_config('calls.guest_photo_brightness', 100);
$locale = App::getLocale();
// Base query — is_public always hardcoded, safety filters always hardcoded
$query = Call::with([
'tag.contexts.category.translations',
'tag.contexts.category.ancestors.translations',
'translations',
'location.city.translations',
'location.country.translations',
'callable.locations.city.translations',
'callable.locations.division.translations',
'callable.locations.country.translations',
'callable.loveReactant.reactionCounters',
'loveReactant.reactionCounters',
])
->where('is_public', true)
->where('is_paused', false)
->where('is_suppressed', false)
->whereNull('deleted_at')
->where(fn ($q) => $q->whereNull('till')->orWhere('till', '>=', now()));
$poolSize = $this->maxCards * max(1, (int) ($cfg['pool_multiplier'] ?? 5));
// Scorer with null profile location — location proximity boosts won't fire
$scorer = new CallCarouselScorer($cfg, null, null, null);
$calls = $query->limit($poolSize)->get();
$this->calls = $calls->map(function (Call $call) use ($locale, $scorer) {
$translation = $call->translations->firstWhere('locale', $locale)
?? $call->translations->first();
$tag = $call->tag;
$tagContext = $tag?->contexts->first();
$tagCategory = $tagContext?->category;
$tagColor = $tagCategory?->relatedColor ?? 'gray';
$tagName = $tag?->translation?->name ?? $tag?->name;
$locationStr = null;
if ($call->location) {
$loc = $call->location;
$parts = [];
if ($loc->city) {
$cityName = optional($loc->city->translations->first())->name;
if ($cityName) $parts[] = $cityName;
}
if ($loc->country) {
if ($loc->country->code === 'XX') {
$parts[] = __('Location not specified');
} elseif ($loc->country->code) {
$parts[] = strtoupper($loc->country->code);
}
}
$locationStr = $parts ? implode(', ', $parts) : null;
}
$tagCategories = [];
if ($tagCategory) {
$ancestors = Cache::remember(
'category_ancestors_' . $tagCategory->id,
now()->addHours(6),
fn () => $tagCategory->ancestorsAndSelf()->get()->reverse()
);
foreach ($ancestors as $cat) {
$catName = $cat->translations->firstWhere('locale', $locale)?->name
?? $cat->translations->first()?->name
?? '';
if ($catName) {
$tagCategories[] = [
'id' => $cat->id,
'name' => $catName,
'color' => $cat->relatedColor ?? 'gray',
];
}
}
}
$score = $scorer->score($call);
if ($this->random) {
$score *= (random_int(85, 115) / 100);
}
return [
'id' => $call->id,
'model' => Call::class,
'title' => $tagName ?? '',
'excerpt' => $translation?->content ?? '',
'photo' => $call->callable?->profile_photo_url ?? '',
'location' => $locationStr,
'tag_color' => $tagColor,
'tag_categories' => $tagCategories,
'callable_name' => $call->callable?->name ?? '',
'callable_location' => ProfileCalls::buildCallableLocation($call->callable),
'till' => $call->till,
'expiry_badge_text' => ProfileCalls::buildExpiryBadgeText($call->till),
'like_count' => $call->loveReactant?->reactionCounters
->firstWhere('reaction_type_id', 3)?->count ?? 0,
'score' => $score,
'_callable_key' => ($call->callable_type ?? '') . ':' . ($call->callable_id ?? ''),
];
})
->sortByDesc('score')
->pipe(function ($sorted) use ($cfg) {
$maxPerProfile = (int) ($cfg['max_cards_per_profile'] ?? 2);
$counts = [];
return $sorted->filter(function ($item) use ($maxPerProfile, &$counts) {
$key = $item['_callable_key'];
$counts[$key] = ($counts[$key] ?? 0) + 1;
return $counts[$key] <= $maxPerProfile;
});
})
->take($this->maxCards)
->map(fn ($item) => array_diff_key($item, ['_callable_key' => null]))
->values()
->toArray();
}
public function render()
{
return view('livewire.main-page.call-card-carousel');
}
}