Optimierung 5.3 (Teil 1): Contact::displayName()

Anzeigename eines Kontakts (Vor-/Nachname mit Fallback) als Model-Methode.
Ersetzt die identische Inline-Logik an 6 eindeutigen Relation-$contact-Stellen
(Admin: Benutzer-/PM-/Firmen-Detail; Kunde: Press-Kit-/PM-Detail).

Bewusst noch offen: Select-/toBase-Varianten ($contactOption/$contactResult/
$lookupContact, stdClass-Risiko) sowie web/release-detail (User-Frontend).

Test: ContactDisplayNameTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-19 13:15:38 +00:00
parent 6bf6a091d3
commit 178adb00e2
8 changed files with 38 additions and 6 deletions

View file

@ -63,6 +63,17 @@ class Contact extends Model
return $this->belongsTo(Company::class);
}
/**
* Anzeigename eines Kontakts (Vor- + Nachname), mit Fallback, wenn beides
* leer ist. Single Source of Truth für die Listen-/Detail-Ansichten.
*/
public function displayName(): string
{
$name = trim(($this->first_name ?? '').' '.($this->last_name ?? ''));
return $name !== '' ? $name : __('Kontakt ohne Name');
}
public function pressReleases(): BelongsToMany
{
return $this->belongsToMany(PressRelease::class, 'press_release_contact');

View file

@ -434,7 +434,7 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
<div class="min-w-0">
<div class="flex flex-wrap items-center gap-2">
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
{{ $contact->displayName() }}
</span>
<span class="badge hub">{{ $contact->portal?->label() ?? __('Unbekannt') }}</span>
</div>

View file

@ -339,7 +339,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
@forelse ($contacts as $contact)
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
{{ $contact->displayName() }}
</div>
<div class="text-[12px] text-[color:var(--color-ink-3)]">
{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}

View file

@ -865,7 +865,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer')] class extends Compone
<div class="flex flex-col gap-2 rounded-md bg-zinc-50 p-2 dark:bg-zinc-900 sm:flex-row sm:items-center sm:justify-between">
<div class="min-w-0">
<flux:text class="text-sm" weight="medium">
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
{{ $contact->displayName() }}
</flux:text>
<flux:text class="text-xs text-zinc-500">
{{ $contact->responsibility ?? __('Keine Rolle hinterlegt') }}

View file

@ -203,7 +203,7 @@ new #[Layout('components.layouts.app'), Title('Benutzer anzeigen')] class extend
<div class="flex flex-col gap-2 rounded-[4px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-2 sm:flex-row sm:items-center sm:justify-between">
<div class="min-w-0">
<div class="text-[12.5px] font-medium text-[color:var(--color-ink)]">
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
{{ $contact->displayName() }}
</div>
<div class="text-[11px] text-[color:var(--color-ink-3)]">
{{ $contact->responsibility ?? __('Keine Rolle hinterlegt') }}

View file

@ -584,7 +584,7 @@ new #[Layout('components.layouts.app'), Title('Firma')] class extends Component
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div class="min-w-0">
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
{{ $contact->displayName() }}
</div>
<div class="text-[12px] text-[color:var(--color-ink-3)] mt-0.5">
{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}

View file

@ -611,7 +611,7 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
@forelse ($contacts as $contact)
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
{{ $contact->displayName() }}
</div>
<div class="text-[12px] text-[color:var(--color-ink-3)]">
{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}

View file

@ -0,0 +1,21 @@
<?php
use App\Models\Contact;
test('displayName joins first and last name', function () {
$contact = Contact::factory()->make(['first_name' => 'Erika', 'last_name' => 'Mustermann']);
expect($contact->displayName())->toBe('Erika Mustermann');
});
test('displayName trims a missing part', function () {
$contact = Contact::factory()->make(['first_name' => null, 'last_name' => 'Mustermann']);
expect($contact->displayName())->toBe('Mustermann');
});
test('displayName falls back when both names are empty', function () {
$contact = Contact::factory()->make(['first_name' => null, 'last_name' => null]);
expect($contact->displayName())->toBe(__('Kontakt ohne Name'));
});