web: Jahresarchiv je Branche, Kategoriebeschreibungen, Portalname im Titel (Roadmap 2.3)
- /de/category/{slug}/archiv/{jahr} (CategoryArchivePageData,
CategoryArchiveController, View kategorie-archiv): 30 Meldungen je
Seite, neueste zuerst, Jahresleiste mit Zahlen; ohne Jahr das juengste
Jahr mit Meldungen. Canonical mit ?page=N auf Folgeseiten, hreflang
nur auf Seite 1, CollectionPage + BreadcrumbList. Gecacht bis Seite 3.
Leere Jahre, unbekannte Kategorien, leere Folgeseiten: 404.
- Branchenseite: Jahresleiste unter dem Feed (archiveYears, eine
gruppierte Query); Sitemap listet alle Jahresarchive.
- categories:seed-descriptions {--dry-run} {--force}: fuellt leere DE/EN
Beschreibungen aus database/seeders/data/category-descriptions.php
(14 Kategorien, sachlich, ohne Werbeaussagen), leert den Seiten-Cache.
- Branchenseite: Titel trug config('app.name') ("– pressekonto") statt
des Portalnamens — jetzt presseecho/businessportal24.
- Protokoll Teil 5 (2.3 mit Befund: 0 von 14 Beschreibungen, Sonstiges =
28 % des Bestands), Roadmap 2.3 abgehakt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b60a28dc9c
commit
4616bb7aad
15 changed files with 793 additions and 13 deletions
186
resources/views/web/kategorie-archiv.blade.php
Normal file
186
resources/views/web/kategorie-archiv.blade.php
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
@extends('web.layouts.web-master')
|
||||
|
||||
@php
|
||||
use App\Support\StructuredData;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
\Carbon\Carbon::setLocale(app()->getLocale());
|
||||
|
||||
$catTranslation = $category->translations->firstWhere('locale', app()->getLocale()) ?? $category->translations->first();
|
||||
$catName = $catTranslation?->name ?? __('web.category.fallback_name');
|
||||
$catSlug = $catTranslation?->slug;
|
||||
|
||||
$parentTranslation = $category->parent?->translations->firstWhere('locale', app()->getLocale()) ?? $category->parent?->translations->first();
|
||||
$parentName = $parentTranslation?->name;
|
||||
|
||||
$fmt = fn ($value) => number_format((int) $value, 0, ',', '.');
|
||||
$currentPage = $releases->currentPage();
|
||||
$lastPage = $releases->lastPage();
|
||||
|
||||
$portalBase = StructuredData::portalBase($domainUrl ?? null);
|
||||
$brandLabel = config('app.theme') === 'presseecho' ? 'presseecho' : 'businessportal24';
|
||||
$categoryUrl = $portalBase.'/'.app()->getLocale().'/category/'.$catSlug;
|
||||
$archiveUrl = $categoryUrl.'/archiv/'.$year;
|
||||
// Canonical mit Seitenzahl: der Layout-Default (Pfad ohne Query) würde
|
||||
// jede Folgeseite auf Seite 1 kanonisieren.
|
||||
$canonicalUrl = $currentPage > 1 ? $archiveUrl.'?page='.$currentPage : $archiveUrl;
|
||||
|
||||
$pageSuffix = $currentPage > 1 ? ' – '.__('web.category_archive.page', ['page' => $currentPage]) : '';
|
||||
$metaTitle = __('web.category_archive.meta_title', ['category' => $catName, 'year' => $year, 'brand' => $brandLabel]).$pageSuffix;
|
||||
$metaDescription = __('web.category_archive.meta_description', ['category' => $catName, 'year' => $year, 'count' => $fmt($years[$year] ?? 0), 'brand' => $brandLabel]);
|
||||
|
||||
$alternateSlugs = $categoryAlternateSlugs ?? [];
|
||||
$catSlugDe = $alternateSlugs['de'] ?? null;
|
||||
$catSlugEn = $alternateSlugs['en'] ?? null;
|
||||
|
||||
$breadcrumbs = collect([
|
||||
['name' => $brandLabel, 'item' => $portalBase.'/'.app()->getLocale()],
|
||||
$parentName && $parentTranslation?->slug ? ['name' => $parentName, 'item' => $portalBase.'/'.app()->getLocale().'/category/'.$parentTranslation->slug] : null,
|
||||
['name' => $catName, 'item' => $categoryUrl],
|
||||
['name' => __('web.category_archive.breadcrumb', ['year' => $year]), 'item' => $archiveUrl],
|
||||
])->filter()->values();
|
||||
|
||||
$jsonLd = [
|
||||
'@context' => 'https://schema.org',
|
||||
'@graph' => [
|
||||
[
|
||||
'@type' => 'CollectionPage',
|
||||
'name' => __('web.category_archive.title', ['category' => $catName, 'year' => $year]),
|
||||
'url' => $canonicalUrl,
|
||||
'inLanguage' => app()->getLocale(),
|
||||
'isPartOf' => ['@type' => 'WebSite', 'url' => $portalBase.'/'],
|
||||
],
|
||||
[
|
||||
'@type' => 'BreadcrumbList',
|
||||
'itemListElement' => $breadcrumbs->map(fn (array $crumb, int $index) => [
|
||||
'@type' => 'ListItem',
|
||||
'position' => $index + 1,
|
||||
'name' => $crumb['name'],
|
||||
'item' => $crumb['item'],
|
||||
])->all(),
|
||||
],
|
||||
],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@section('title', $metaTitle)
|
||||
@section('meta_description', Str::limit($metaDescription, 155))
|
||||
@section('canonical', $canonicalUrl)
|
||||
|
||||
@if ($catSlugDe && $catSlugEn && $currentPage === 1)
|
||||
@section('hreflang')
|
||||
<link rel="alternate" hreflang="de" href="{{ $portalBase }}/de/category/{{ $catSlugDe }}/archiv/{{ $year }}">
|
||||
<link rel="alternate" hreflang="en" href="{{ $portalBase }}/en/category/{{ $catSlugEn }}/archiv/{{ $year }}">
|
||||
<link rel="alternate" hreflang="x-default" href="{{ $portalBase }}/de/category/{{ $catSlugDe }}/archiv/{{ $year }}">
|
||||
@endsection
|
||||
@endif
|
||||
|
||||
@section('structured_data')
|
||||
<x-web.structured-data :data="$jsonLd" />
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<x-web.site-header />
|
||||
|
||||
<main class="bg-bg text-ink">
|
||||
{{-- Breadcrumb --}}
|
||||
<div class="border-b border-bg-rule">
|
||||
<div class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 py-2.5 flex items-center gap-2 text-[11.5px] text-ink-3 flex-wrap">
|
||||
<a href="{{ route('web.home') }}" class="hover:text-ink transition-colors">{{ __('web.nav.home') }}</a>
|
||||
<span aria-hidden="true">›</span>
|
||||
@if ($parentName && $parentTranslation?->slug)
|
||||
<a href="{{ route('kategorie', ['slug' => $parentTranslation->slug]) }}" class="hover:text-ink transition-colors">{{ $parentName }}</a>
|
||||
<span aria-hidden="true">›</span>
|
||||
@endif
|
||||
<a href="{{ route('kategorie', ['slug' => $catSlug]) }}" class="hover:text-ink transition-colors">{{ $catName }}</a>
|
||||
<span aria-hidden="true">›</span>
|
||||
<span class="text-ink">{{ __('web.category_archive.breadcrumb', ['year' => $year]) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 pt-8 lg:pt-9">
|
||||
<div class="flex items-center justify-between gap-4 mb-3">
|
||||
<div class="eyebrow">{{ __('web.category_archive.eyebrow') }}</div>
|
||||
<a href="{{ route('kategorie', ['slug' => $catSlug]) }}" class="inline-flex items-center gap-1.5 text-[12px] font-semibold text-ink-3 hover:text-ink transition-colors whitespace-nowrap">
|
||||
← {{ __('web.category_archive.back_to_category', ['category' => $catName]) }}
|
||||
</a>
|
||||
</div>
|
||||
<h1 class="font-serif text-[32px] sm:text-[40px] font-semibold m-0 leading-[1.1] tracking-[-0.5px] text-ink">
|
||||
{{ __('web.category_archive.title', ['category' => $catName, 'year' => $year]) }}
|
||||
</h1>
|
||||
<p class="text-[14px] leading-[1.6] text-ink-2 mt-3 mb-0 max-w-[720px]">
|
||||
{{ __('web.category_archive.intro', ['count' => $fmt($years[$year] ?? 0), 'category' => $catName, 'year' => $year, 'total' => $fmt($totalReleases), 'first' => array_key_last($years)]) }}
|
||||
</p>
|
||||
|
||||
{{-- Jahresleiste --}}
|
||||
<nav class="mt-6" aria-label="{{ __('web.category_archive.nav_label') }}">
|
||||
<ul class="m-0 p-0 list-none flex flex-wrap gap-1.5">
|
||||
@foreach ($years as $archiveYear => $archiveCount)
|
||||
<li>
|
||||
@if ($archiveYear === $year)
|
||||
<span class="inline-flex items-baseline gap-1.5 px-2.5 py-1.5 border border-brand bg-brand text-white text-[12.5px] font-mono" aria-current="page">
|
||||
{{ $archiveYear }} <span class="text-[10.5px] text-white/80">{{ $fmt($archiveCount) }}</span>
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ route('kategorie.archiv', ['slug' => $catSlug, 'year' => $archiveYear]) }}" class="inline-flex items-baseline gap-1.5 px-2.5 py-1.5 border border-bg-rule-strong bg-white text-[12.5px] font-mono text-ink hover:bg-bg-elev transition-colors">
|
||||
{{ $archiveYear }} <span class="text-[10.5px] text-ink-3">{{ $fmt($archiveCount) }}</span>
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="max-w-layout mx-auto px-4 sm:px-6 lg:px-8 mt-10 mb-16">
|
||||
<header class="flex items-baseline justify-between mb-4 flex-wrap gap-3">
|
||||
<h2 class="font-serif text-[22px] font-semibold m-0 tracking-[-0.2px] text-ink">
|
||||
{{ __('web.category_archive.list_title', ['year' => $year]) }}
|
||||
@if ($lastPage > 1)
|
||||
<span class="text-[13px] text-ink-3 font-normal ml-1.5">· {{ __('web.category_archive.page_of', ['page' => $currentPage, 'last' => $lastPage]) }}</span>
|
||||
@endif
|
||||
</h2>
|
||||
<span class="text-[11.5px] text-ink-3">{{ __('web.category_archive.sorted') }}</span>
|
||||
</header>
|
||||
<hr class="rule-strong">
|
||||
|
||||
<ol class="m-0 p-0 list-none">
|
||||
@foreach ($releases as $release)
|
||||
@php
|
||||
$rowCategory = $release->localizedCategoryName();
|
||||
$isRecommended = $release->content_score !== null && $release->content_score >= 80;
|
||||
@endphp
|
||||
<li>
|
||||
<a href="{{ route('release.detail', ['slug' => $release->slug]) }}" class="grid items-start gap-x-4 py-3.5 border-b border-bg-rule hover:bg-bg-elev transition-colors group pl-1.5 pr-2 sm:pl-2 grid-cols-[minmax(0,1fr)] md:grid-cols-[6.5rem_minmax(0,1fr)]">
|
||||
<time datetime="{{ $release->published_at?->toIso8601String() }}" class="font-mono text-[11.5px] leading-snug text-ink-3 pt-0.5 tabular-nums">
|
||||
{{ $release->published_at?->translatedFormat('d.m.Y') }}
|
||||
</time>
|
||||
<div class="min-w-0">
|
||||
<div class="font-serif text-[16px] sm:text-[17px] leading-[1.3] text-ink group-hover:text-brand transition-colors">
|
||||
{{ $release->title }}
|
||||
</div>
|
||||
<div class="mt-1 text-[11.5px] text-ink-3 flex flex-wrap items-center gap-x-2">
|
||||
@if ($release->company?->name)
|
||||
<span class="truncate max-w-[24rem]">{{ $release->company->name }}</span>
|
||||
@endif
|
||||
@if ($rowCategory && $rowCategory !== $catName)
|
||||
<span aria-hidden="true">·</span>
|
||||
<span>{{ $rowCategory }}</span>
|
||||
@endif
|
||||
@if ($isRecommended)
|
||||
<span aria-hidden="true">·</span>
|
||||
<span class="text-brand font-semibold">{{ __('web.detail.recommended') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
|
||||
<x-web.pagination :paginator="$releases" />
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<x-web.site-footer />
|
||||
@endsection
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
];
|
||||
@endphp
|
||||
|
||||
@section('title', __('web.category.meta_title', ['category' => $catName, 'brand' => config('app.name')]))
|
||||
@section('title', __('web.category.meta_title', ['category' => $catName, 'brand' => $brandLabel]))
|
||||
@section('meta_description', Str::limit(strip_tags($catDescription), 155))
|
||||
|
||||
@if ($catSlugDe && $catSlugEn)
|
||||
|
|
@ -264,6 +264,25 @@
|
|||
<span class="text-[11.5px] text-ink-3">{{ __('web.category.week_stat', ['count' => $fmt($categoryStats['week'] ?? 0)]) }}</span>
|
||||
<a href="{{ route('suche') }}" class="px-3.5 py-2.5 text-[13px] font-semibold border border-bg-rule-strong bg-white text-ink hover:bg-bg-elev transition-colors">{{ __('web.category.search_archive') }} →</a>
|
||||
</div>
|
||||
|
||||
{{-- Jahresarchiv (2.3): der Bestand als stabile, verlinkte Seiten. --}}
|
||||
@if (! empty($archiveYears ?? []) && $catSlug)
|
||||
<nav class="mt-9 pt-6 border-t border-bg-rule" aria-label="{{ __('web.category_archive.nav_label') }}">
|
||||
<div class="flex items-baseline justify-between flex-wrap gap-2 mb-3">
|
||||
<h3 class="font-serif text-[18px] font-semibold m-0 text-ink">{{ __('web.category_archive.section_title', ['category' => $catName]) }}</h3>
|
||||
<span class="text-[11.5px] text-ink-3">{{ __('web.category_archive.section_total', ['count' => $fmt(array_sum($archiveYears))]) }}</span>
|
||||
</div>
|
||||
<ul class="m-0 p-0 list-none flex flex-wrap gap-1.5">
|
||||
@foreach ($archiveYears as $archiveYear => $archiveCount)
|
||||
<li>
|
||||
<a href="{{ route('kategorie.archiv', ['slug' => $catSlug, 'year' => $archiveYear]) }}" class="inline-flex items-baseline gap-1.5 px-2.5 py-1.5 border border-bg-rule-strong bg-white text-[12.5px] font-mono text-ink hover:bg-bg-elev transition-colors">
|
||||
{{ $archiveYear }} <span class="text-[10.5px] text-ink-3">{{ $fmt($archiveCount) }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</nav>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<aside class="flex flex-col gap-9">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue