*/ public function build(Portal $portal, string $language = 'de'): array { $portalValues = [$portal->value, Portal::Both->value]; // Aktualitäts-Stichtag an den jüngsten Datensatz koppeln (historisches Archiv). $referenceNow = EditorialClock::reference($portalValues, $language); $recentSince = $referenceNow->copy()->subDays(7); $previousSince = $referenceNow->copy()->subDays(14); $publishedQuery = static fn (): Builder => PressRelease::query() ->forPortalValues($portalValues) ->forLanguage($language) ->visibleInArchive(); $with = [ 'company', 'category.translations' => fn ($query) => $query->where('locale', $language), 'images' => fn ($query) => $query ->orderByDesc('is_preview') ->orderBy('sort_order') ->limit(1), ]; $oldestPublishedAt = $publishedQuery() ->oldest('published_at') ->value('published_at'); $leadRelease = $publishedQuery() ->with($with) ->orderByDesc('published_at') ->first(); $sideReleases = $publishedQuery() ->with($with) ->when($leadRelease, fn (Builder $query) => $query->where('id', '!=', $leadRelease->id)) ->orderByDesc('published_at') ->limit(4) ->get(); $mostReadReleases = $publishedQuery() ->orderByDesc('hits') ->orderByDesc('published_at') ->limit(4) ->get(['id', 'slug', 'title', 'hits', 'portal', 'language']); $activeNewsrooms = Company::query() ->whereIn('portal', $portalValues) ->where('is_active', true) ->whereHas('pressReleases', function (Builder $query) use ($portalValues, $recentSince, $language): void { $query ->forPortalValues($portalValues) ->forLanguage($language) ->published() ->where('published_at', '>=', $recentSince); }) ->withCount([ 'pressReleases as recent_releases_count' => function (Builder $query) use ($portalValues, $recentSince, $language): void { $query ->forPortalValues($portalValues) ->forLanguage($language) ->published() ->where('published_at', '>=', $recentSince); }, 'pressReleases as today_releases_count' => function (Builder $query) use ($portalValues, $referenceNow, $language): void { $query ->forPortalValues($portalValues) ->forLanguage($language) ->published() ->whereDate('published_at', $referenceNow->toDateString()); }, ]) ->orderByDesc('today_releases_count') ->orderByDesc('recent_releases_count') ->limit(6) ->get() ->map(fn (Company $company): array => [ 'name' => $company->name, 'slug' => $company->slug, 'initial' => mb_strtoupper(mb_substr((string) $company->name, 0, 1)), 'count' => (int) $company->recent_releases_count, 'today' => (int) $company->today_releases_count > 0, ]); $industryIndex = Category::query() ->with(['translations' => fn ($query) => $query->where('locale', $language)]) ->withCount([ 'pressReleases as recent_count' => function (Builder $query) use ($portalValues, $recentSince, $language): void { $query ->forPortalValues($portalValues) ->forLanguage($language) ->published() ->where('published_at', '>=', $recentSince); }, 'pressReleases as previous_count' => function (Builder $query) use ($portalValues, $recentSince, $previousSince, $language): void { $query ->forPortalValues($portalValues) ->forLanguage($language) ->published() ->where('published_at', '>=', $previousSince) ->where('published_at', '<', $recentSince); }, ]) ->whereIn('portal', $portalValues) ->where('is_active', true) ->whereNull('parent_id') ->orderByDesc('recent_count') ->limit(7) ->get() ->map(function (Category $category): ?array { $translation = $category->translations->first(); if (! $translation) { return null; } return [ 'name' => $translation->name, 'href' => $translation->slug ? route('kategorie', ['slug' => $translation->slug]) : route('web.home'), 'count' => (int) ($category->recent_count ?? 0), 'delta' => (int) ($category->recent_count ?? 0) - (int) ($category->previous_count ?? 0), ]; }) ->filter() ->values(); // "Heute im Fokus · Branche": aktivste Top-Rubrik im Stichtag-Fenster mit echten Meldungen. $spotlightCategory = Category::query() ->with(['translations' => fn ($query) => $query->where('locale', $language)]) ->withCount([ 'pressReleases as window_count' => function (Builder $query) use ($portalValues, $recentSince, $referenceNow, $language): void { $query ->forPortalValues($portalValues) ->forLanguage($language) ->published() ->where('published_at', '>=', $recentSince) ->where('published_at', '<=', $referenceNow); }, ]) ->whereIn('portal', $portalValues) ->where('is_active', true) ->whereNull('parent_id') ->orderByDesc('window_count') ->first(); $spotlight = null; if ($spotlightCategory && $spotlightTranslation = $spotlightCategory->translations->first()) { $spotlightBase = static fn (): Builder => PressRelease::query() ->forPortalValues($portalValues) ->forLanguage($language) ->visibleInArchive() ->where('category_id', $spotlightCategory->id); $spotlightReleases = $spotlightBase() ->with(['company']) ->orderByDesc('published_at') ->limit(3) ->get() ->map(static function (PressRelease $release) use ($spotlightTranslation): array { $words = str_word_count(strip_tags((string) $release->text)); $publishedAt = $release->published_at; return [ 'time' => $publishedAt?->format('H:i') ?? '', 'date' => $publishedAt?->translatedFormat('j. M') ?? '', 'category' => $spotlightTranslation->name, 'title' => $release->title, 'company' => $release->company?->name ?? '', 'city' => null, 'minutes' => max(1, (int) ceil($words / 200)), 'href' => $release->slug ? route('release.detail', ['slug' => $release->slug]) : '#', ]; }); if ($spotlightReleases->isNotEmpty()) { $windowCount = (int) ($spotlightCategory->window_count ?? 0); $archiveCount = $spotlightBase()->count(); $activeCompanies = (clone $spotlightBase()) ->where('published_at', '>=', $recentSince) ->where('published_at', '<=', $referenceNow) ->distinct() ->count('company_id'); $spotlight = [ 'industry' => $spotlightTranslation->name, 'stats' => [ ['label' => 'Meldungen (7 Tage)', 'value' => number_format($windowCount, 0, ',', '.'), 'sub' => 'Stand '.$referenceNow->translatedFormat('j. M Y')], ['label' => 'Aktive Unternehmen', 'value' => number_format($activeCompanies, 0, ',', '.'), 'sub' => 'in dieser Branche'], ['label' => 'Im Archiv', 'value' => number_format($archiveCount, 0, ',', '.'), 'sub' => 'Meldungen gesamt'], ], 'releases' => $spotlightReleases->all(), ]; } } // Ad-Hoc-Ticker aus den jüngsten echten Pressemitteilungen. $tickerItems = $publishedQuery() ->orderByDesc('published_at') ->limit(8) ->get(['id', 'title', 'published_at']) ->map(static fn (PressRelease $release): array => [ 'time' => $release->published_at?->format('H:i') ?? '', 'text' => Str::limit((string) $release->title, 70), ]) ->all(); return [ 'leadRelease' => $leadRelease, 'sideReleases' => $sideReleases, 'mostReadReleases' => $mostReadReleases, 'activeNewsrooms' => $activeNewsrooms, 'industryIndex' => $industryIndex, 'spotlight' => $spotlight, 'tickerItems' => $tickerItems, 'homeStats' => [ 'publishedCount' => $publishedQuery()->count(), 'publishedToday' => $publishedQuery()->whereDate('published_at', $referenceNow->toDateString())->count(), 'archiveSince' => $oldestPublishedAt ? (int) Carbon::parse($oldestPublishedAt)->format('Y') : null, ], ]; } }