PM-Vorschau: Firmen-Kachel im Stil der Firmenübersicht

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-06-12 16:20:23 +00:00
parent 284d029b29
commit 970b4909fa
2 changed files with 131 additions and 18 deletions

View file

@ -1,6 +1,6 @@
**Version:** Juni 2026 (Rev. 2) **Datum:** 12.06.2026 **Status:** Abgestimmt bereit zur Integration ins Konzept-/Decision-Log **Scope:** Definition von Boost und Veröffentlichungsnachweis (Launch), des Magic-Link-Zugangs- und Änderungsprozesses sowie der Phase-2-Funktionen (Vorab-Prüfung/Redigieren, Prüfzähler, höheres Prüfkontingent, kostenpflichtige Änderungspfade). **Änderungen Rev. 2:** KI bei E/F entfernt (Admin-Panel statt KI-Check); A nutzt Algorithmus-Diff statt KI; Bremsen-/Limit-Spalte für alle Pfade ergänzt. **Änderungen Rev. 3:** A & B als Launch bestätigt; Boost-Preisstaffel (12/20/35) und PDF-Preis (3 Credits) festgelegt; G Depublizieren = 25 Credits. **Version:** Juni 2026 (Rev. 2) **Datum:** 12.06.2026 **Status:** Abgestimmt bereit zur Integration ins Konzept-/Decision-Log **Scope:** Definition von Boost und Veröffentlichungsnachweis (Launch), des Magic-Link-Zugangs- und Änderungsprozesses sowie der Phase-2-Funktionen (Vorab-Prüfung/Redigieren, Prüfzähler, höheres Prüfkontingent, kostenpflichtige Änderungspfade). **Änderungen Rev. 2:** KI bei E/F entfernt (Admin-Panel statt KI-Check); A nutzt Algorithmus-Diff statt KI; Bremsen-/Limit-Spalte für alle Pfade ergänzt. **Änderungen Rev. 3:** A & B als Launch bestätigt; Boost-Preisstaffel (12/20/35) und PDF-Preis (3 Credits) festgelegt; G Depublizieren = 25 Credits. **Änderungen Rev. 4:** Extra-PM ergänzt tier-gestaffelt (19/15/12/10/8 Credits); Cross-Portal-Veröffentlichung als kein Thema vermerkt (Portale getrennt).
--- ---
@ -17,7 +17,27 @@ Dieses Update definiert die credit-nahen Funktionen und den Änderungsprozess f
## 2. Launch-Funktionen ## 2. Launch-Funktionen
### 2.1 Boost (Platzierung) ### 2.1 Extra-Pressemitteilung (Kontingent-Nachkauf)
Die Brücke zwischen Tarif-Kontingent und Credit-Wallet: Ist das monatliche PM-Kontingent voll, kann eine einzelne weitere PM über die Wallet nachgekauft werden ohne Zwang zum nächsthöheren Tarif. Das ist die faire Alternative zum erzwungenen Upgrade und der genau gewünschte Fall „ich brauche _einmalig_ eine PM mehr".
**Preis ist tier-gestaffelt** (Treuevorteil: höheres Abo = günstigere Extra-PM):
|Situation|Extra-PM|Inkl. PM-Preis|Logik|
|---|---|---|---|
|Kein Abo (Einzel)|19 Credits|19,00 €|voller Satz = Einzel-PM-Preis|
|Starter|15 Credits|9,67 €|günstiger als Einzel, mild über inkludiert|
|Business|12 Credits|4,90 €||
|Pro|10 Credits|3,96 €||
|Agency|8 Credits|3,32 €||
**Mechanik:** Der Extra-PM-Preis wird **zur Kaufzeit aus dem aktiven Abo abgeleitet**, nicht statisch gespeichert. Reicht das Wallet-Guthaben nicht, greift der kontextuelle Mini-Checkout („Kostet 15 Credits, du hast 8" → Paket nachladen). Der Volumenrabatt steckt bereits in den Credit-Paketen **keine** separaten Extra-PM-Bündel nötig (eine Wallet, ein tier-abhängiger Preis).
**Design-Entscheidung (bewusst):** Der prozentuale Aufschlag ggü. dem inkludierten PM-Preis ist bei Starter am mildesten (+55 %), bei den oberen Tiers höher (~140150 %). Das ist gewollt: Die gelegentliche 4. PM eines Starter-Kunden wird _nicht_ bestraft, um künstlichen Upgrade-Druck zu erzeugen passt zur „fair bleiben, kein Zwang"-Linie. Wer dauerhaft viel veröffentlicht, wandert ohnehin in höhere Tiers.
**Tageslimit gilt auch hier:** Nachgekaufte Extra-PMs unterliegen demselben Tageslimit wie das Kontingent (kein „Spam freikaufen"; höherer Tagesdurchsatz = Enterprise-Fall).
### 2.2 Boost (Platzierung)
- **Was:** bezahlte Hervorhebung einer PM Platzierung auf Startseite und Branchen-/Kategorieseite - **Was:** bezahlte Hervorhebung einer PM Platzierung auf Startseite und Branchen-/Kategorieseite
- **Gate:** nur **grüne** PMs sind boostbar; gelb/rot nicht - **Gate:** nur **grüne** PMs sind boostbar; gelb/rot nicht
@ -35,7 +55,7 @@ Dieses Update definiert die credit-nahen Funktionen und den Änderungsprozess f
Moderate Staffel: Pro-Tag-Preis sinkt mit der Dauer, der Einstieg (7 Tage / 12) bleibt unter dem PM-Preis von 19 ein Boost wirkt nie teurer als das Veröffentlichen selbst. Passt zur „Nische besetzen statt abschöpfen"-Linie (Volumen statt Einzelmarge). Moderate Staffel: Pro-Tag-Preis sinkt mit der Dauer, der Einstieg (7 Tage / 12) bleibt unter dem PM-Preis von 19 ein Boost wirkt nie teurer als das Veröffentlichen selbst. Passt zur „Nische besetzen statt abschöpfen"-Linie (Volumen statt Einzelmarge).
### 2.2 Veröffentlichungsnachweis / PDF ### 2.3 Veröffentlichungsnachweis / PDF
- **Was:** generiertes PDF „PM XY wurde am … auf … veröffentlicht" inkl. URL, Datum, Vorschau - **Was:** generiertes PDF „PM XY wurde am … auf … veröffentlicht" inkl. URL, Datum, Vorschau
- **Zweck:** Reporting an Vorgesetzte/Kunden klassische PR-Mitnahme - **Zweck:** Reporting an Vorgesetzte/Kunden klassische PR-Mitnahme
@ -173,8 +193,9 @@ Depublizieren bewusst am teuersten und mit Bedenkzeit, weil irreversibelste Akti
--- ---
## 6. Offene Punkte
- **Kosten-Anker C/D** final bestätigen, sobald das Credit-/Prüf-System gebaut wird (aktuell ≈ 8 / ≈ 4 Credits).
**In Rev. 3 abgeschlossen:** A/B-Launch ✓ · Boost-Staffel 12/20/35 ✓ · PDF 3 Credits ✓ · G Depublizieren 25 Credits ✓ **Bewusst nicht im Scope Cross-Portal-Veröffentlichung:** Die beiden Portale (presseecho.de, businessportal24.com) sind optisch, inhaltlich und systemisch vollständig getrennt; im Relaunch laufen sie lediglich auf einem gemeinsamen Backend zusammen. Eine PM auf beiden Portalen gleichzeitig zu veröffentlichen ist **kein Feature** wer in beiden präsent sein will, bucht zwei getrennte Einträge (zwei Slots / zwei Extra-PMs). Das vermeidet Duplicate-Content über die eigenen Domains und passt zur getrennten Zielgruppen-Logik. Kein Phase-2-Punkt, sondern bewusst ausgeschlossen.
Noch mal ein wichtiger Hintergrund, der noch dokumentiert werden muss. Das sollte auch zukünftig gegebenenfalls geprüft werden.
**In Rev. 3 abgeschlossen:** A/B-Launch ✓ · Boost-Staffel 12/20/35 ✓ · PDF 3 Credits ✓ · G Depublizieren 25 Credits ✓ **In Rev. 4 abgeschlossen:** Extra-PM tier-gestaffelt (19/15/12/10/8) ✓ · Cross-Portal ausgeschlossen ✓

View file

@ -99,8 +99,13 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
$cover = app(PressReleaseCoverImage::class); $cover = app(PressReleaseCoverImage::class);
$user = auth()->user(); $user = auth()->user();
$company = $pr->company?->loadCount(['pressReleases', 'contacts']);
return [ return [
'pr' => $pr, 'pr' => $pr,
'companyLogoUrl' => $company?->logoUrl(),
'companyInitials' => $this->companyInitials($company?->name),
'companyMetaLine' => $this->companyMetaLine($company),
'categoryName' => $categoryName, 'categoryName' => $categoryName,
'coverUrl' => $cover->coverUrl($pr, 'cover'), 'coverUrl' => $cover->coverUrl($pr, 'cover'),
'coverIsPlaceholder' => $cover->coverIsPlaceholder($pr), 'coverIsPlaceholder' => $cover->coverIsPlaceholder($pr),
@ -122,12 +127,59 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
]; ];
} }
/**
* Initialen für die Logo-Kachel (analog zur Firmenübersicht).
*/
private function companyInitials(?string $name): string
{
$name = trim((string) $name);
if ($name === '') {
return '';
}
$letters = collect(preg_split('/\s+/u', $name) ?: [])
->map(fn (string $word): string => mb_substr($word, 0, 1))
->filter()
->take(2)
->implode('');
return mb_strtoupper($letters ?: mb_substr($name, 0, 2));
}
/**
* Kompakte Meta-Zeile: Ort (letzte Adresszeile) · Firmentyp.
*/
private function companyMetaLine(?\App\Models\Company $company): string
{
if (! $company) {
return '';
}
$parts = [];
$lastAddressLine = collect(preg_split('/\r?\n/', trim((string) $company->address)) ?: [])
->map(fn ($line) => trim((string) $line))
->filter()
->last();
if (is_string($lastAddressLine) && $lastAddressLine !== '') {
$parts[] = $lastAddressLine;
}
if ($company->type?->label()) {
$parts[] = $company->type->label();
}
return implode(' · ', $parts);
}
private function getMyPR(): PressRelease private function getMyPR(): PressRelease
{ {
return PressRelease::withoutGlobalScopes() return PressRelease::withoutGlobalScopes()
->where('user_id', auth()->id()) ->where('user_id', auth()->id())
->with([ ->with([
'company:id,name,email,phone', 'company:id,name,email,phone,address,portal,logo_path,legacy_portal,is_active,type',
'category.translations', 'category.translations',
'contacts' => fn ($query) => $query 'contacts' => fn ($query) => $query
->withoutGlobalScopes() ->withoutGlobalScopes()
@ -314,16 +366,30 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
<span class="section-eyebrow">{{ __('Firma & Pressekontakt') }}</span> <span class="section-eyebrow">{{ __('Firma & Pressekontakt') }}</span>
</div> </div>
<div class="p-5 space-y-4"> <div class="p-5 space-y-4">
{{-- Firma der Pressemitteilung --}} {{-- Firma der Pressemitteilung gleiche Kachel wie in der Firmenübersicht --}}
@if ($pr->company) @if ($pr->company)
<div class="flex items-start gap-3 rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3"> <div class="firm-card">
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-[5px] <div class="flex items-start justify-between gap-3">
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]"> <div class="logo {{ $companyLogoUrl ? '' : 'lg-'.['brew', 'mv', 'soft', 'warm'][$pr->company->id % 4] }}">
<flux:icon.building-office class="size-5" /> @if ($companyLogoUrl)
<img src="{{ $companyLogoUrl }}" alt="{{ $pr->company->name }}" loading="lazy" />
@else
{{ $companyInitials }}
@endif
</div> </div>
<div class="min-w-0 flex-1"> @if ($pr->company->is_active)
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">{{ $pr->company->name }}</div> <span class="badge ok dot">{{ __('Aktiv') }}</span>
<div class="mt-0.5 flex flex-wrap gap-x-3 gap-y-1 text-[11.5px] text-[color:var(--color-ink-3)]"> @else
<span class="badge err dot">{{ __('Inaktiv') }}</span>
@endif
</div>
<div class="min-w-0">
<h3 class="name">{{ $pr->company->name }}</h3>
@if (filled($companyMetaLine))
<div class="meta-line">{{ $companyMetaLine }}</div>
@endif
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11.5px] text-[color:var(--color-ink-3)]">
@if ($pr->company->email) @if ($pr->company->email)
<span>{{ $pr->company->email }}</span> <span>{{ $pr->company->email }}</span>
@endif @endif
@ -332,9 +398,35 @@ new #[Layout('components.layouts.app'), Title('Pressemitteilung')] class extends
@endif @endif
</div> </div>
</div> </div>
<flux:button size="xs" variant="filled" href="{{ route('me.press-kits.show', $pr->company->id) }}" wire:navigate>
<div class="flex items-center gap-2 flex-wrap">
@if ($pr->company->portal === \App\Enums\Portal::Both)
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
<span class="portal-pill bp"><span class="pdot"></span>businessportal24</span>
@elseif ($pr->company->portal === \App\Enums\Portal::Presseecho)
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
@elseif ($pr->company->portal === \App\Enums\Portal::Businessportal24)
<span class="portal-pill bp"><span class="pdot"></span>businessportal24</span>
@endif
</div>
<div class="kpis">
<div class="kpi">
<span class="k">{{ number_format($pr->company->press_releases_count ?? 0, 0, ',', '.') }}</span>
<span class="l">{{ __('PMs') }}</span>
</div>
<div class="kpi">
<span class="k">{{ number_format($pr->company->contacts_count ?? 0, 0, ',', '.') }}</span>
<span class="l">{{ __('Kontakte') }}</span>
</div>
</div>
<div class="flex items-center gap-2 pt-1">
<a href="{{ route('me.press-kits.show', $pr->company->id) }}" wire:navigate class="card-action primary" style="flex:1;">
<flux:icon.arrow-right class="size-3" />
{{ __('Firma öffnen') }} {{ __('Firma öffnen') }}
</flux:button> </a>
</div>
</div> </div>
@endif @endif