kunde: PM-Liste zeigt Firmen-Kontext unter der Headline und Bild-Thumbnails

Der aktive Firmen-Kontext (Firmen-Umschalter) steht jetzt prominent
unter der Überschrift — Firmenname oder „Alle Firmen" — statt als
kleiner Hinweistext. Neue Spalte „Bild" zwischen Status und Titel mit
dem Titelbild (thumb-Variante, is_preview zuerst), dem gewählten
Platzhalter bei bildlosen PMs oder einem generischen Icon-Fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-07-21 14:31:44 +00:00
parent 17e22d2d46
commit 45dc4ce66c
4 changed files with 100 additions and 8 deletions

View file

@ -178,6 +178,18 @@ class PressRelease extends Model
return $image?->variantUrl('card') ?? $image?->url();
}
/**
* Bild-URL für Listen-Thumbnails: thumb-Variante des Titelbildes,
* Fallback card-Variante bzw. Original. Null bei bildlosen PMs
* (dann greift chosenPlaceholderUrl() bzw. der generische Fallback).
*/
public function thumbImageUrl(): ?string
{
$image = $this->images?->first();
return $image?->variantUrl('thumb') ?? $image?->variantUrl('card') ?? $image?->url();
}
/**
* Tag-freier, whitespace-normalisierter Anriss des PM-Textes.
*/
@ -607,8 +619,14 @@ class PressRelease extends Model
$models = static::query()->withoutGlobalScopes()
->whereKey($pageIds)
// Kategorie fürs Listen-Rendering (localizedCategoryName, 13.07.).
->with(['company:id,name', 'category.translations'])
// Kategorie fürs Listen-Rendering (localizedCategoryName, 13.07.);
// Bilder fürs Listen-Thumbnail — Titelbild zuerst (is_preview,
// gleiche Reihenfolge wie in der PM-Vorschau).
->with([
'company:id,name',
'category.translations',
'images' => fn ($query) => $query->orderByDesc('is_preview')->orderBy('sort_order'),
])
// Für das Einreichungs-Modal je Zeile: ohne eigenes Bild entfällt
// die Bildrechte-Bestätigung.
->withCount('images')

View file

@ -205,6 +205,17 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
{{ __('Meine Pressemitteilungen') }}
</h1>
{{-- Aktiver Firmen-Kontext direkt unter der Headline: macht sichtbar,
auf welche Firma (Firmen-Umschalter oben) die Liste eingeschränkt ist. --}}
<div class="mt-2 flex items-center gap-1.5 text-[13.5px] font-semibold text-[color:var(--color-ink-2)]">
<flux:icon.building-office variant="micro" class="size-4 text-[color:var(--color-ink-3)]" />
@if ($selectedCompany)
<span>{{ $selectedCompany->name }}</span>
@else
<span>{{ __('Alle Firmen') }}</span>
@endif
</div>
{{-- Counter-Strip + Kontext-Hinweis --}}
@if ($hasAnyPR)
<div class="counter-strip mt-3">
@ -236,11 +247,7 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
</div>
@endif
@if ($selectedCompany)
<p class="text-[12.5px] leading-[1.55] mt-2 m-0 text-[color:var(--color-ink-3)]">
{{ __('Gefiltert auf :company', ['company' => $selectedCompany->name]) }}
</p>
@elseif (!$hasAnyPR)
@if (!$hasAnyPR)
<p class="text-[13.5px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
{{ __('Übersicht aller PMs Ihres Kundenkontos, mit Filter und Schnellaktionen.') }}
</p>
@ -426,6 +433,7 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
<flux:table>
<flux:table.columns>
<flux:table.column class="w-[140px]">{{ __('Status') }}</flux:table.column>
<flux:table.column class="w-[88px]">{{ __('Bild') }}</flux:table.column>
<flux:table.column sortable :sorted="$sortBy === 'title'" :direction="$sortDir"
wire:click="sort('title')">
{{ __('Titel') }}
@ -506,6 +514,24 @@ new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class
@endif
</flux:table.cell>
<flux:table.cell>
{{-- Vorschaubild: Titelbild der PM, sonst der gewählte
Platzhalter, sonst generischer Bild-Fallback. --}}
@php $thumbUrl = $pr->thumbImageUrl() ?? $pr->chosenPlaceholderUrl(); @endphp
<a href="{{ route('me.press-releases.show', $pr->id) }}" wire:navigate class="block w-16">
@if ($thumbUrl)
<img src="{{ $thumbUrl }}" alt=""
class="h-12 w-16 rounded-[3px] object-cover border border-[color:var(--color-bg-rule)]"
loading="lazy">
@else
<span
class="flex h-12 w-16 items-center justify-center rounded-[3px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] text-[color:var(--color-ink-4)]">
<flux:icon.photo variant="micro" class="size-4" />
</span>
@endif
</a>
</flux:table.cell>
<flux:table.cell>
<div class="flex items-start gap-1.5">
<a href="{{ route('me.press-releases.show', $pr->id) }}" wire:navigate

View file

@ -98,7 +98,8 @@ test('customer press release list is filtered by selected company context', func
->withSession([CustomerCompanyContext::SessionKey => $company->id]);
LivewireVolt::test('customer.press-releases.index')
->assertSee('Gefiltert auf Alpha GmbH')
->assertSee('Alpha GmbH')
->assertDontSee('Alle Firmen')
->assertSee('Alpha Produktmeldung')
->assertDontSee('Beta Produktmeldung');
});
@ -124,6 +125,7 @@ test('customer press release list can filter unassigned legacy press releases',
LivewireVolt::test('customer.press-releases.index')
->set('companyFilter', 'unassigned')
->assertSee('Alle Firmen')
->assertSee('Legacy ohne Firma')
->assertDontSee('Alpha Firmenmeldung');
});

View file

@ -205,3 +205,49 @@ test('admin list filtert nach KI-Klassifikation', function () {
->assertSee('Gelber Grenzfall')
->assertDontSee('Grüne Mitteilung');
});
test('customer list zeigt das Titelbild als Thumbnail in der Bild-Spalte', function () {
/** @var TestCase $this */
$customer = makeCustomerForIndexPhase8b();
$this->actingAs($customer);
$pr = PressRelease::factory()->create([
'user_id' => $customer->id,
'title' => 'PM mit Titelbild',
]);
// Zweitbild zuerst angelegt — das Titelbild (is_preview) muss trotzdem gewinnen.
$pr->images()->create([
'disk' => 'public',
'path' => 'press/second.jpg',
'variants' => ['thumb' => 'press/second-thumb.jpg'],
'is_preview' => false,
'sort_order' => 2,
]);
$pr->images()->create([
'disk' => 'public',
'path' => 'press/cover.jpg',
'variants' => ['thumb' => 'press/cover-thumb.jpg'],
'is_preview' => true,
'sort_order' => 1,
]);
LivewireVolt::test('customer.press-releases.index')
->assertSee('Bild')
->assertSee('storage/press/cover-thumb.jpg', false)
->assertDontSee('storage/press/second-thumb.jpg', false);
});
test('customer list zeigt den gewählten Platzhalter als Thumbnail bei bildlosen PMs', function () {
/** @var TestCase $this */
$customer = makeCustomerForIndexPhase8b();
$this->actingAs($customer);
PressRelease::factory()->create([
'user_id' => $customer->id,
'title' => 'PM ohne Bild',
'placeholder_variant' => '05-lines-green',
]);
LivewireVolt::test('customer.press-releases.index')
->assertSee('press-release-placeholders/05-lines-green.svg', false);
});