PM-Vorschau: Firmen-Kachel im Stil der Firmenübersicht
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
284d029b29
commit
970b4909fa
2 changed files with 131 additions and 18 deletions
|
|
@ -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 (~140–150 %). 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 ✓
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue