762 lines
38 KiB
PHP
762 lines
38 KiB
PHP
<?php
|
||
|
||
use App\Models\Company;
|
||
use App\Models\Contact;
|
||
use App\Models\PressRelease;
|
||
use App\Services\Customer\CustomerCompanyContext;
|
||
use App\Services\Image\ImageService;
|
||
use Illuminate\Validation\Rule;
|
||
use Illuminate\Validation\ValidationException;
|
||
use Livewire\Attributes\Layout;
|
||
use Livewire\Attributes\Locked;
|
||
use Livewire\Attributes\Title;
|
||
use Livewire\Volt\Component;
|
||
use Livewire\WithFileUploads;
|
||
|
||
new #[Layout('components.layouts.app'), Title('Firma')] class extends Component {
|
||
use WithFileUploads;
|
||
|
||
#[Locked]
|
||
public int $id;
|
||
|
||
public bool $showCompanyForm = false;
|
||
|
||
public string $companyName = '';
|
||
|
||
public string $companyAddress = '';
|
||
|
||
public string $companyEmail = '';
|
||
|
||
public string $companyPhone = '';
|
||
|
||
public string $companyWebsite = '';
|
||
|
||
public string $companyCountryCode = 'DE';
|
||
|
||
public bool $companyDisableFooterCode = false;
|
||
|
||
public $companyLogo = null;
|
||
|
||
public bool $removeCompanyLogo = false;
|
||
|
||
public bool $showContactForm = false;
|
||
|
||
public ?int $editingContactId = null;
|
||
|
||
public string $contactFirstName = '';
|
||
|
||
public string $contactLastName = '';
|
||
|
||
public string $contactResponsibility = '';
|
||
|
||
public string $contactEmail = '';
|
||
|
||
public string $contactPhone = '';
|
||
|
||
public function mount(int $id): void
|
||
{
|
||
$this->id = $id;
|
||
|
||
$context = app(CustomerCompanyContext::class);
|
||
$company = $context->findFor(auth()->user(), $id);
|
||
|
||
abort_unless($company !== null, 404);
|
||
|
||
$context->select(auth()->user(), $id);
|
||
}
|
||
|
||
public function startEditCompany(): void
|
||
{
|
||
$company = $this->company();
|
||
$this->authorize('update', $company);
|
||
|
||
$this->companyName = (string) $company->name;
|
||
$this->companyAddress = (string) ($company->address ?? '');
|
||
$this->companyEmail = (string) ($company->email ?? '');
|
||
$this->companyPhone = (string) ($company->phone ?? '');
|
||
$this->companyWebsite = (string) ($company->website ?? '');
|
||
$this->companyCountryCode = (string) ($company->country_code ?? 'DE');
|
||
$this->companyDisableFooterCode = (bool) $company->disable_footer_code;
|
||
$this->companyLogo = null;
|
||
$this->removeCompanyLogo = false;
|
||
$this->showCompanyForm = true;
|
||
}
|
||
|
||
public function cancelCompanyForm(): void
|
||
{
|
||
$this->resetCompanyForm();
|
||
}
|
||
|
||
public function saveCompany(ImageService $imageService): void
|
||
{
|
||
$company = $this->company();
|
||
$this->authorize('update', $company);
|
||
|
||
$validated = $this->validate([
|
||
'companyName' => ['required', 'string', 'max:255'],
|
||
'companyAddress' => ['nullable', 'string', 'max:1000'],
|
||
'companyEmail' => ['nullable', 'email', 'max:190'],
|
||
'companyPhone' => ['nullable', 'string', 'max:40'],
|
||
'companyWebsite' => ['nullable', 'url', 'max:190'],
|
||
'companyCountryCode' => ['nullable', 'string', 'size:2', Rule::in(array_keys((array) config('countries.items', [])))],
|
||
'companyLogo' => ['nullable', 'image', 'max:' . (int) (ImageService::MAX_LOGO_BYTES / 1024)],
|
||
]);
|
||
|
||
$company->fill([
|
||
'name' => $validated['companyName'],
|
||
'address' => $validated['companyAddress'] ?: null,
|
||
'email' => $validated['companyEmail'] ?: null,
|
||
'phone' => $validated['companyPhone'] ?: null,
|
||
'website' => $validated['companyWebsite'] ?: null,
|
||
'country_code' => $validated['companyCountryCode'] ?: null,
|
||
'disable_footer_code' => $this->companyDisableFooterCode,
|
||
]);
|
||
|
||
if ($this->removeCompanyLogo) {
|
||
$imageService->deleteCompanyLogo($company->logo_path, $company->logo_variants);
|
||
$company->logo_path = null;
|
||
$company->logo_variants = null;
|
||
}
|
||
|
||
if ($this->companyLogo) {
|
||
$imageService->deleteCompanyLogo($company->logo_path, $company->logo_variants);
|
||
|
||
$stored = $imageService->storeCompanyLogo($this->companyLogo, $company->portal?->value ?? 'presseecho', $company->id);
|
||
|
||
$company->logo_path = $stored['path'];
|
||
$company->logo_variants = $stored['variants'];
|
||
}
|
||
|
||
$company->save();
|
||
$this->resetCompanyForm();
|
||
|
||
session()->flash('company-status', __('Stammdaten wurden gespeichert.'));
|
||
}
|
||
|
||
public function startCreateContact(): void
|
||
{
|
||
$this->authorize('update', $this->company());
|
||
|
||
$this->resetContactForm();
|
||
$this->showContactForm = true;
|
||
}
|
||
|
||
public function editContact(int $contactId): void
|
||
{
|
||
$this->authorize('update', $this->company());
|
||
|
||
$contact = $this->contact($contactId);
|
||
|
||
$this->editingContactId = $contact->id;
|
||
$this->contactFirstName = (string) ($contact->first_name ?? '');
|
||
$this->contactLastName = (string) ($contact->last_name ?? '');
|
||
$this->contactResponsibility = (string) ($contact->responsibility ?? '');
|
||
$this->contactEmail = (string) ($contact->email ?? '');
|
||
$this->contactPhone = (string) ($contact->phone ?? '');
|
||
$this->showContactForm = true;
|
||
}
|
||
|
||
public function cancelContactForm(): void
|
||
{
|
||
$this->resetContactForm();
|
||
}
|
||
|
||
public function saveContact(): void
|
||
{
|
||
$company = $this->company();
|
||
$this->authorize('update', $company);
|
||
|
||
$validated = $this->validate([
|
||
'contactFirstName' => ['nullable', 'string', 'max:80'],
|
||
'contactLastName' => ['nullable', 'string', 'max:80'],
|
||
'contactResponsibility' => ['nullable', 'string', 'max:255'],
|
||
'contactEmail' => ['required', 'email', 'max:255'],
|
||
'contactPhone' => ['nullable', 'string', 'max:40'],
|
||
]);
|
||
|
||
if (blank($validated['contactFirstName']) && blank($validated['contactLastName'])) {
|
||
throw ValidationException::withMessages([
|
||
'contactLastName' => __('Bitte geben Sie mindestens einen Namen an.'),
|
||
]);
|
||
}
|
||
|
||
$payload = [
|
||
'company_id' => $company->id,
|
||
'portal' => $company->portal?->value,
|
||
'first_name' => $validated['contactFirstName'] ?: null,
|
||
'last_name' => $validated['contactLastName'] ?: null,
|
||
'responsibility' => $validated['contactResponsibility'] ?: null,
|
||
'email' => $validated['contactEmail'],
|
||
'phone' => $validated['contactPhone'] ?: null,
|
||
];
|
||
|
||
if ($this->editingContactId) {
|
||
$this->contact($this->editingContactId)->update($payload);
|
||
session()->flash('contact-status', __('Pressekontakt wurde aktualisiert.'));
|
||
} else {
|
||
Contact::query()->create($payload);
|
||
session()->flash('contact-status', __('Pressekontakt wurde angelegt.'));
|
||
}
|
||
|
||
$this->resetContactForm();
|
||
}
|
||
|
||
public function deleteContact(int $contactId): void
|
||
{
|
||
$this->authorize('update', $this->company());
|
||
|
||
$contact = $this->contact($contactId);
|
||
$contact->delete();
|
||
|
||
if ($this->editingContactId === $contactId) {
|
||
$this->resetContactForm();
|
||
}
|
||
|
||
session()->flash('contact-status', __('Pressekontakt wurde gelöscht.'));
|
||
}
|
||
|
||
public function with(): array
|
||
{
|
||
$user = auth()->user();
|
||
$context = app(CustomerCompanyContext::class);
|
||
$company = $context
|
||
->accessibleCompanyQuery($user)
|
||
->withCount(['contacts', 'pressReleases'])
|
||
->findOrFail($this->id);
|
||
|
||
return [
|
||
'company' => $company,
|
||
'roleLabel' => $context->roleLabelFor($company, $user),
|
||
'canManageCompany' => $user->can('update', $company),
|
||
'canManageContacts' => $user->can('update', $company),
|
||
'countries' => (array) config('countries.items', []),
|
||
'contacts' => Contact::withoutGlobalScopes()
|
||
->where('company_id', $company->id)
|
||
->withCount('pressReleases')
|
||
->orderBy('last_name')
|
||
->orderBy('first_name')
|
||
->limit(10)
|
||
->get(['id', 'company_id', 'first_name', 'last_name', 'responsibility', 'email', 'phone']),
|
||
'pressReleases' => PressRelease::withoutGlobalScopes()
|
||
->where('user_id', $user->id)
|
||
->where('company_id', $company->id)
|
||
->latest()
|
||
->limit(10)
|
||
->get(['id', 'title', 'status', 'created_at', 'published_at']),
|
||
];
|
||
}
|
||
|
||
private function company(): Company
|
||
{
|
||
$company = app(CustomerCompanyContext::class)->findFor(auth()->user(), $this->id);
|
||
|
||
abort_unless($company !== null, 404);
|
||
|
||
return $company;
|
||
}
|
||
|
||
private function contact(int $contactId): Contact
|
||
{
|
||
return Contact::withoutGlobalScopes()->where('company_id', $this->id)->findOrFail($contactId);
|
||
}
|
||
|
||
private function resetCompanyForm(): void
|
||
{
|
||
$this->showCompanyForm = false;
|
||
$this->companyName = '';
|
||
$this->companyAddress = '';
|
||
$this->companyEmail = '';
|
||
$this->companyPhone = '';
|
||
$this->companyWebsite = '';
|
||
$this->companyCountryCode = 'DE';
|
||
$this->companyDisableFooterCode = false;
|
||
$this->companyLogo = null;
|
||
$this->removeCompanyLogo = false;
|
||
$this->resetValidation();
|
||
}
|
||
|
||
private function resetContactForm(): void
|
||
{
|
||
$this->showContactForm = false;
|
||
$this->editingContactId = null;
|
||
$this->contactFirstName = '';
|
||
$this->contactLastName = '';
|
||
$this->contactResponsibility = '';
|
||
$this->contactEmail = '';
|
||
$this->contactPhone = '';
|
||
$this->resetValidation();
|
||
}
|
||
}; ?>
|
||
|
||
<div class="space-y-8">
|
||
{{-- ============== PAGE HEADER ============== --}}
|
||
<header class="page-header">
|
||
<div class="min-w-0">
|
||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||
<span class="badge hub dot">{{ __('User Backend') }}</span>
|
||
<span class="eyebrow muted">{{ __('Mein Bereich · Firma') }}</span>
|
||
@if ($company->is_active)
|
||
<span class="badge ok">{{ __('Aktiv') }}</span>
|
||
@else
|
||
<span class="badge err">{{ __('Inaktiv') }}</span>
|
||
@endif
|
||
<span class="badge hub">{{ $company->portal?->label() ?? __('Portal unbekannt') }}</span>
|
||
<span class="badge hub">{{ $roleLabel }}</span>
|
||
@if ($company->disable_footer_code)
|
||
<span class="badge warn">{{ __('Footer-Code deaktiviert') }}</span>
|
||
@endif
|
||
</div>
|
||
<div class="flex items-start gap-4">
|
||
@if ($company->logoUrl())
|
||
<img src="{{ $company->logoUrl() }}" alt="{{ $company->name }}" width="64" height="64"
|
||
class="h-16 max-h-16 w-16 max-w-16 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)] flex-shrink-0" />
|
||
@else
|
||
<div class="flex h-16 w-16 items-center justify-center rounded-[6px]
|
||
border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] flex-shrink-0">
|
||
<flux:icon.building-office class="size-7 text-[color:var(--color-ink-3)]" />
|
||
</div>
|
||
@endif
|
||
<div class="min-w-0">
|
||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)] break-words">
|
||
{{ $company->name }}
|
||
</h1>
|
||
<div class="text-[12px] text-[color:var(--color-ink-3)] mt-1">{{ $company->slug }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex flex-wrap items-center gap-2 flex-shrink-0">
|
||
<flux:button icon="arrow-left" variant="filled" href="{{ route('me.press-kits.index') }}" wire:navigate>
|
||
{{ __('Zurück') }}
|
||
</flux:button>
|
||
@if ($canManageCompany)
|
||
<flux:button icon="pencil" variant="filled" wire:click="startEditCompany">
|
||
{{ __('Stammdaten bearbeiten') }}
|
||
</flux:button>
|
||
@endif
|
||
<flux:button icon="plus" variant="primary" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||
{{ __('Neue Pressemitteilung') }}
|
||
</flux:button>
|
||
</div>
|
||
</header>
|
||
|
||
{{-- ============== QUICK-NAV ============== --}}
|
||
<nav class="flex flex-wrap items-center gap-2 border-b border-[color:var(--color-bg-rule)] pb-3">
|
||
<a href="#stammdaten" class="text-[12px] font-semibold text-[color:var(--color-ink-3)] hover:text-[color:var(--color-ink)] px-3 py-1.5 rounded-[4px] hover:bg-[color:var(--color-bg-elev)] transition-colors">{{ __('Stammdaten') }}</a>
|
||
<a href="#pressekontakte" class="text-[12px] font-semibold text-[color:var(--color-ink-3)] hover:text-[color:var(--color-ink)] px-3 py-1.5 rounded-[4px] hover:bg-[color:var(--color-bg-elev)] transition-colors">{{ __('Pressekontakte') }}</a>
|
||
<a href="#pressemitteilungen" class="text-[12px] font-semibold text-[color:var(--color-ink-3)] hover:text-[color:var(--color-ink)] px-3 py-1.5 rounded-[4px] hover:bg-[color:var(--color-bg-elev)] transition-colors">{{ __('Pressemitteilungen') }}</a>
|
||
<a href="#abrechnung" class="text-[12px] font-semibold text-[color:var(--color-ink-3)] hover:text-[color:var(--color-ink)] px-3 py-1.5 rounded-[4px] hover:bg-[color:var(--color-bg-elev)] transition-colors">{{ __('Abrechnung') }}</a>
|
||
<a href="#statistik" class="text-[12px] font-semibold text-[color:var(--color-ink-3)] hover:text-[color:var(--color-ink)] px-3 py-1.5 rounded-[4px] hover:bg-[color:var(--color-bg-elev)] transition-colors">{{ __('Statistik') }}</a>
|
||
</nav>
|
||
|
||
{{-- ============== KPI-Reihe ============== --}}
|
||
<section class="grid grid-cols-2 gap-4 lg:grid-cols-4">
|
||
<x-portal.stat-card variant="primary" :label="__('Pressemitteilungen')" :value="number_format($company->press_releases_count)">
|
||
<x-slot:meta>{{ __('zugeordnet') }}</x-slot:meta>
|
||
<x-slot:trend>{{ __('Content-Output') }}</x-slot:trend>
|
||
</x-portal.stat-card>
|
||
<x-portal.stat-card variant="ok" :label="__('Pressekontakte')" :value="number_format($company->contacts_count)">
|
||
<x-slot:meta>{{ __('Ansprechpartner') }}</x-slot:meta>
|
||
<x-slot:trend>{{ __('für PMs verfügbar') }}</x-slot:trend>
|
||
</x-portal.stat-card>
|
||
<x-portal.stat-card variant="muted" :label="__('Portal')" :value="$company->portal?->label() ?? '–'">
|
||
<x-slot:meta>{{ __('Sichtbarkeit') }}</x-slot:meta>
|
||
<x-slot:trend>{{ __('für diese Firma') }}</x-slot:trend>
|
||
</x-portal.stat-card>
|
||
<x-portal.stat-card variant="muted" :label="__('Deine Rolle')" :value="$roleLabel">
|
||
<x-slot:meta>{{ __('Berechtigungen') }}</x-slot:meta>
|
||
<x-slot:trend>{{ $canManageCompany ? __('Voller Schreibzugriff') : __('Lesezugriff') }}</x-slot:trend>
|
||
</x-portal.stat-card>
|
||
</section>
|
||
|
||
<div class="grid gap-6 xl:grid-cols-2">
|
||
{{-- ============== STAMMDATEN ============== --}}
|
||
<article class="panel" id="stammdaten">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Stammdaten') }}</span>
|
||
@if ($canManageCompany)
|
||
<flux:button size="sm" variant="filled" icon="pencil" wire:click="startEditCompany">
|
||
{{ __('Bearbeiten') }}
|
||
</flux:button>
|
||
@endif
|
||
</div>
|
||
|
||
@if (session('company-status'))
|
||
<div class="mx-5 mt-4 px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
|
||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
|
||
{{ session('company-status') }}
|
||
</div>
|
||
@endif
|
||
|
||
@if ($showCompanyForm)
|
||
<div class="mx-5 my-4 rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-5">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-4">
|
||
{{ __('Stammdaten bearbeiten') }}
|
||
</div>
|
||
|
||
<div class="grid gap-4 sm:grid-cols-2">
|
||
<flux:field>
|
||
<flux:input wire:model="companyName" :label="__('Firmenname')" required />
|
||
<flux:error name="companyName" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:input wire:model="companyEmail" :label="__('E-Mail')" type="email" />
|
||
<flux:error name="companyEmail" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:input wire:model="companyPhone" :label="__('Telefon')" />
|
||
<flux:error name="companyPhone" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:input wire:model="companyWebsite" :label="__('Website')" placeholder="https://..." />
|
||
<flux:error name="companyWebsite" />
|
||
</flux:field>
|
||
<flux:field class="sm:col-span-2">
|
||
<flux:textarea wire:model="companyAddress" :label="__('Adresse')" rows="3" />
|
||
<flux:error name="companyAddress" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:select wire:model="companyCountryCode" :label="__('Land')">
|
||
@foreach ($countries as $code => $name)
|
||
<option value="{{ $code }}">{{ $name }}</option>
|
||
@endforeach
|
||
</flux:select>
|
||
<flux:error name="companyCountryCode" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:switch wire:model="companyDisableFooterCode" :label="__('Footer-Code deaktivieren')" />
|
||
</flux:field>
|
||
</div>
|
||
|
||
<div class="mt-5 pt-4 border-t border-[color:var(--color-bg-rule)] space-y-3">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold">
|
||
{{ __('Firmenlogo') }}
|
||
</div>
|
||
@php($logoUrl = $company->logoUrl())
|
||
@if ($logoUrl && ! $removeCompanyLogo)
|
||
<div class="flex items-center gap-3">
|
||
<img src="{{ $logoUrl }}" alt="{{ $company->name }}" width="64" height="64"
|
||
class="h-16 max-h-16 w-16 max-w-16 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-white" />
|
||
<flux:button type="button" size="sm" variant="filled" wire:click="$set('removeCompanyLogo', true)">
|
||
{{ __('Logo entfernen') }}
|
||
</flux:button>
|
||
</div>
|
||
@endif
|
||
<flux:field>
|
||
<flux:file-upload wire:model="companyLogo"
|
||
:label="__('Neues Logo hochladen')"
|
||
accept="image/jpeg,image/png,image/webp,image/gif"
|
||
:description="__('JPG/PNG/WebP/GIF, max. 4 MB. Varianten werden automatisch generiert.')">
|
||
<flux:file-upload.dropzone
|
||
:heading="__('Logo hierher ziehen oder klicken')"
|
||
:text="__('JPG, PNG, WebP oder GIF · max. 4 MB')"
|
||
with-progress
|
||
inline
|
||
/>
|
||
</flux:file-upload>
|
||
<flux:error name="companyLogo" />
|
||
|
||
@if ($companyLogo)
|
||
<flux:file-item class="mt-2"
|
||
:heading="$companyLogo->getClientOriginalName()"
|
||
:image="$companyLogo->temporaryUrl()"
|
||
:size="$companyLogo->getSize()"
|
||
>
|
||
<x-slot name="actions">
|
||
<flux:file-item.remove wire:click="$set('companyLogo', null)" :aria-label="__('Logo entfernen')" />
|
||
</x-slot>
|
||
</flux:file-item>
|
||
@endif
|
||
</flux:field>
|
||
</div>
|
||
|
||
<div class="mt-4 pt-4 border-t border-[color:var(--color-bg-rule)] flex justify-end gap-2">
|
||
<flux:button type="button" variant="filled" wire:click="cancelCompanyForm">
|
||
{{ __('Abbrechen') }}
|
||
</flux:button>
|
||
<flux:button type="button" variant="primary" wire:click="saveCompany">
|
||
{{ __('Speichern') }}
|
||
</flux:button>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
<dl class="p-5 grid gap-3 sm:grid-cols-2 text-[12.5px]">
|
||
<div>
|
||
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('E-Mail') }}</dt>
|
||
<dd class="text-[color:var(--color-ink)] break-all">{{ $company->email ?: '–' }}</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Telefon') }}</dt>
|
||
<dd class="text-[color:var(--color-ink)]">{{ $company->phone ?: '–' }}</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Website') }}</dt>
|
||
<dd class="break-all">
|
||
@if ($company->website)
|
||
<a href="{{ $company->website }}" target="_blank" rel="noopener"
|
||
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">{{ $company->website }}</a>
|
||
@else
|
||
<span class="text-[color:var(--color-ink)]">–</span>
|
||
@endif
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Land') }}</dt>
|
||
<dd class="text-[color:var(--color-ink)]">{{ $company->country_code ?: '–' }}</dd>
|
||
</div>
|
||
<div class="sm:col-span-2">
|
||
<dt class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Adresse') }}</dt>
|
||
<dd class="text-[color:var(--color-ink)] whitespace-pre-line">{{ $company->address ?: '–' }}</dd>
|
||
</div>
|
||
</dl>
|
||
</article>
|
||
|
||
{{-- ============== PRESSEKONTAKTE ============== --}}
|
||
<article class="panel" id="pressekontakte">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Pressekontakte') }}</span>
|
||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||
{{ trans_choice(':count Kontakt|:count Kontakte', $company->contacts_count, ['count' => $company->contacts_count]) }}
|
||
</span>
|
||
</div>
|
||
|
||
@if ($canManageContacts)
|
||
<div class="px-5 pt-4 flex justify-end">
|
||
<flux:button size="sm" variant="primary" icon="plus" wire:click="startCreateContact">
|
||
{{ __('Kontakt hinzufügen') }}
|
||
</flux:button>
|
||
</div>
|
||
@endif
|
||
|
||
@if (session('contact-status'))
|
||
<div class="mx-5 mt-4 px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
|
||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
|
||
{{ session('contact-status') }}
|
||
</div>
|
||
@endif
|
||
|
||
@if ($showContactForm)
|
||
<div class="mx-5 my-4 rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-5">
|
||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-4">
|
||
{{ $editingContactId ? __('Pressekontakt bearbeiten') : __('Neuen Pressekontakt anlegen') }}
|
||
</div>
|
||
|
||
<div class="grid gap-4 sm:grid-cols-2">
|
||
<flux:field>
|
||
<flux:input wire:model="contactFirstName" :label="__('Vorname')" />
|
||
<flux:error name="contactFirstName" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:input wire:model="contactLastName" :label="__('Nachname')" />
|
||
<flux:error name="contactLastName" />
|
||
</flux:field>
|
||
<flux:field class="sm:col-span-2">
|
||
<flux:input wire:model="contactResponsibility" :label="__('Position / Rolle')" />
|
||
<flux:error name="contactResponsibility" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:input wire:model="contactEmail" :label="__('E-Mail')" type="email" required />
|
||
<flux:error name="contactEmail" />
|
||
</flux:field>
|
||
<flux:field>
|
||
<flux:input wire:model="contactPhone" :label="__('Telefon')" />
|
||
<flux:error name="contactPhone" />
|
||
</flux:field>
|
||
</div>
|
||
|
||
<div class="mt-4 pt-4 border-t border-[color:var(--color-bg-rule)] flex justify-end gap-2">
|
||
<flux:button type="button" variant="filled" wire:click="cancelContactForm">
|
||
{{ __('Abbrechen') }}
|
||
</flux:button>
|
||
<flux:button type="button" variant="primary" wire:click="saveContact">
|
||
{{ __('Speichern') }}
|
||
</flux:button>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
<div class="p-5 space-y-2">
|
||
@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="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') }}
|
||
</div>
|
||
<div class="text-[12px] text-[color:var(--color-ink-3)] mt-0.5">
|
||
{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}
|
||
</div>
|
||
<div class="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11.5px] text-[color:var(--color-ink-3)]">
|
||
@if ($contact->email)
|
||
<a href="mailto:{{ $contact->email }}"
|
||
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">{{ $contact->email }}</a>
|
||
@endif
|
||
@if ($contact->phone)
|
||
<span>{{ $contact->phone }}</span>
|
||
@endif
|
||
@if ($contact->press_releases_count > 0)
|
||
<span>{{ trans_choice('in :count Pressemitteilung hinterlegt|in :count Pressemitteilungen hinterlegt', $contact->press_releases_count, ['count' => $contact->press_releases_count]) }}</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
@if ($canManageContacts)
|
||
<div class="flex gap-1 flex-shrink-0">
|
||
<flux:button size="sm" variant="filled" icon="pencil" wire:click="editContact({{ $contact->id }})">
|
||
{{ __('Bearbeiten') }}
|
||
</flux:button>
|
||
<flux:button size="sm" variant="filled" icon="trash"
|
||
wire:click="deleteContact({{ $contact->id }})"
|
||
wire:confirm="{{ __('Diesen Pressekontakt löschen?') }}">
|
||
{{ __('Löschen') }}
|
||
</flux:button>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
@empty
|
||
<div class="rounded-[5px] border border-dashed border-[color:var(--color-bg-rule)] p-4">
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">{{ __('Keine Pressekontakte hinterlegt') }}</div>
|
||
<p class="mt-1 text-[12px] text-[color:var(--color-ink-3)] m-0">
|
||
{{ __('Pressekontakte helfen, Pressemitteilungen eindeutig einer Ansprechperson zuzuordnen.') }}
|
||
</p>
|
||
@if ($canManageContacts)
|
||
<flux:button class="mt-3" size="sm" variant="primary" icon="plus" wire:click="startCreateContact">
|
||
{{ __('Kontakt hinzufügen') }}
|
||
</flux:button>
|
||
@endif
|
||
</div>
|
||
@endforelse
|
||
</div>
|
||
</article>
|
||
</div>
|
||
|
||
{{-- ============== PRESSEMITTEILUNGEN ============== --}}
|
||
<article class="panel overflow-hidden" id="pressemitteilungen">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Pressemitteilungen dieser Firma') }}</span>
|
||
<flux:button size="sm" variant="filled" href="{{ route('me.press-releases.index') }}" wire:navigate>
|
||
{{ __('Alle anzeigen') }}
|
||
</flux:button>
|
||
</div>
|
||
<flux:table>
|
||
<flux:table.columns>
|
||
<flux:table.column>{{ __('Titel') }}</flux:table.column>
|
||
<flux:table.column>{{ __('Status') }}</flux:table.column>
|
||
<flux:table.column>{{ __('Datum') }}</flux:table.column>
|
||
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
|
||
</flux:table.columns>
|
||
|
||
@forelse ($pressReleases as $pressRelease)
|
||
<flux:table.row wire:key="company-pr-{{ $pressRelease->id }}">
|
||
<flux:table.cell>
|
||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">{{ $pressRelease->title }}</span>
|
||
</flux:table.cell>
|
||
<flux:table.cell>
|
||
@php($statusValue = $pressRelease->status->value)
|
||
<span @class([
|
||
'badge dot',
|
||
'ok' => $statusValue === 'published',
|
||
'warn' => $statusValue === 'review',
|
||
'err' => $statusValue === 'rejected',
|
||
'hub' => ! in_array($statusValue, ['published', 'review', 'rejected'], true),
|
||
])>
|
||
{{ $pressRelease->status->label() }}
|
||
</span>
|
||
</flux:table.cell>
|
||
<flux:table.cell>
|
||
<span class="text-[12px] text-[color:var(--color-ink-3)]">
|
||
{{ $pressRelease->published_at?->format('d.m.Y') ?? ($pressRelease->created_at?->format('d.m.Y') ?? '–') }}
|
||
</span>
|
||
</flux:table.cell>
|
||
<flux:table.cell>
|
||
<flux:button size="sm" variant="filled" icon="eye"
|
||
href="{{ route('me.press-releases.show', $pressRelease->id) }}" wire:navigate>
|
||
{{ __('Öffnen') }}
|
||
</flux:button>
|
||
</flux:table.cell>
|
||
</flux:table.row>
|
||
@empty
|
||
<flux:table.row>
|
||
<flux:table.cell colspan="4">
|
||
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
|
||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
|
||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||
<flux:icon.newspaper class="size-6" />
|
||
</div>
|
||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||
{{ __('Keine Pressemitteilungen für diese Firma') }}
|
||
</div>
|
||
<p class="text-[12px] text-[color:var(--color-ink-3)] max-w-md m-0">
|
||
{{ __('Erstellen Sie die erste Pressemitteilung direkt mit dieser Firma als Kontext.') }}
|
||
</p>
|
||
<flux:button class="mt-4" size="sm" variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||
{{ __('Neue Pressemitteilung') }}
|
||
</flux:button>
|
||
</div>
|
||
</flux:table.cell>
|
||
</flux:table.row>
|
||
@endforelse
|
||
</flux:table>
|
||
</article>
|
||
|
||
<div class="grid gap-6 xl:grid-cols-2">
|
||
{{-- ============== ABRECHNUNG ============== --}}
|
||
<article class="panel" id="abrechnung">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Abrechnung') }}</span>
|
||
<span class="badge warn">{{ __('In Vorbereitung') }}</span>
|
||
</div>
|
||
<div class="p-5 space-y-4">
|
||
<p class="text-[12.5px] text-[color:var(--color-ink-2)] m-0">
|
||
{{ __('Firmenspezifische Zahlungsarten und Add-ons werden hier später zusammengeführt.') }}
|
||
</p>
|
||
<div class="rounded-[5px] border border-dashed border-[color:var(--color-bg-rule)] p-4">
|
||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">
|
||
{{ __('Noch keine firmenspezifische Abrechnung') }}
|
||
</div>
|
||
<p class="mt-1 text-[12px] text-[color:var(--color-ink-3)] m-0">
|
||
{{ __('Rechnungen finden Sie aktuell gesammelt im Finanzbereich. Firmenscharfe Zahlungsarten folgen mit dem Preismodell.') }}
|
||
</p>
|
||
<flux:button class="mt-3" size="sm" variant="filled" href="{{ route('me.invoices.index') }}" wire:navigate>
|
||
{{ __('Rechnungen öffnen') }}
|
||
</flux:button>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
|
||
{{-- ============== STATISTIK ============== --}}
|
||
<article class="panel" id="statistik">
|
||
<div class="panel-head">
|
||
<span class="section-eyebrow">{{ __('Statistik') }}</span>
|
||
<span class="badge warn">{{ __('Später') }}</span>
|
||
</div>
|
||
<div class="p-5 space-y-4">
|
||
<p class="text-[12.5px] text-[color:var(--color-ink-2)] m-0">
|
||
{{ __('Erste Kennzahlen zur Firma; detaillierte Auswertungen folgen später.') }}
|
||
</p>
|
||
<div class="grid grid-cols-2 gap-3">
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[10.5px] uppercase tracking-[0.6px] font-semibold text-[color:var(--color-ink-3)]">
|
||
{{ __('Pressemitteilungen') }}
|
||
</div>
|
||
<div class="text-[18px] font-bold text-[color:var(--color-ink)] mt-1">
|
||
{{ $company->press_releases_count }}
|
||
</div>
|
||
</div>
|
||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||
<div class="text-[10.5px] uppercase tracking-[0.6px] font-semibold text-[color:var(--color-ink-3)]">
|
||
{{ __('Pressekontakte') }}
|
||
</div>
|
||
<div class="text-[18px] font-bold text-[color:var(--color-ink)] mt-1">
|
||
{{ $company->contacts_count }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
</div>
|