WS-8: doppelte Logik konsolidieren (Tags, Kontakt-Query, Billing)

- Themen-Tag-Verwaltung (addTag/removeTag/tagsArray) war in create- und
  edit-Editor byte-identisch dupliziert -> Trait
  App\Livewire\Concerns\InteractsWithPressReleaseTags.
- Kontaktauswahl je Firma (withoutGlobalScopes + where + Sortierung) aus
  create/edit/dashboard in Contact::scopeForCompany() zentralisiert.
- Rechnungsadress-Pflichtfelder als BillingAddress::REQUIRED_FIELDS; isComplete()
  und neues completenessPercent() teilen die Liste, Dashboard nutzt es statt
  eigener Feldliste.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-18 13:33:59 +00:00
parent 3da335388e
commit 1c5eedec7b
6 changed files with 115 additions and 134 deletions

View file

@ -0,0 +1,68 @@
<?php
namespace App\Livewire\Concerns;
/**
* Themen-Tag-Verwaltung für die PM-Editoren (Erstellen/Bearbeiten).
*
* Tags werden als kommaseparierte Liste in der string-Property `$keywords`
* gehalten (max. 5, eindeutig). Die nutzende Livewire-Komponente muss eine
* `$keywords`-Property sowie die Computed-Properties `tags` und
* `presubmitChecks` bereitstellen — diese werden nach jeder Änderung
* invalidiert.
*/
trait InteractsWithPressReleaseTags
{
public function addTag(string $tag): void
{
$tag = trim($tag);
if ($tag === '') {
return;
}
$existing = $this->tagsArray();
if (count($existing) >= 5) {
return;
}
if (in_array($tag, $existing, true)) {
return;
}
$existing[] = $tag;
$this->keywords = implode(', ', $existing);
unset($this->tags, $this->presubmitChecks);
}
public function removeTag(string $tag): void
{
$existing = array_values(array_filter(
$this->tagsArray(),
fn (string $existingTag): bool => $existingTag !== $tag,
));
$this->keywords = implode(', ', $existing);
unset($this->tags, $this->presubmitChecks);
}
/**
* @return list<string>
*/
private function tagsArray(): array
{
if (trim($this->keywords) === '') {
return [];
}
return collect(explode(',', $this->keywords))
->map(fn (string $tag): string => trim($tag))
->filter()
->unique()
->values()
->all();
}
}