454 lines
19 KiB
PHP
454 lines
19 KiB
PHP
<?php
|
|
|
|
use App\Enums\CompanyType;
|
|
use App\Enums\Portal;
|
|
use App\Models\Company;
|
|
use App\Services\Image\ImageService;
|
|
use Illuminate\Support\Str;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Title;
|
|
use Livewire\Attributes\Validate;
|
|
use Livewire\Volt\Component;
|
|
use Livewire\WithFileUploads;
|
|
|
|
new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends Component
|
|
{
|
|
use WithFileUploads;
|
|
|
|
public int $companyId;
|
|
|
|
public string $portal = 'both';
|
|
|
|
public string $type = 'company';
|
|
|
|
#[Validate('required|min:3|max:255')]
|
|
public string $company_name = '';
|
|
|
|
#[Validate('nullable|max:500')]
|
|
public string $description = '';
|
|
|
|
#[Validate('required|email')]
|
|
public string $email = '';
|
|
|
|
#[Validate('nullable|max:50')]
|
|
public string $phone = '';
|
|
|
|
#[Validate('nullable|url')]
|
|
public string $website = '';
|
|
|
|
#[Validate('nullable|max:255')]
|
|
public string $street = '';
|
|
|
|
#[Validate('nullable|max:20')]
|
|
public string $zip = '';
|
|
|
|
#[Validate('nullable|max:255')]
|
|
public string $city = '';
|
|
|
|
#[Validate('nullable|max:255')]
|
|
public string $state = '';
|
|
|
|
#[Validate('required|max:2')]
|
|
public string $country = 'DE';
|
|
|
|
#[Validate('nullable|image|max:4096')]
|
|
public $logo;
|
|
|
|
public bool $remove_logo = false;
|
|
|
|
public ?string $current_logo_url = null;
|
|
|
|
#[Validate('nullable|max:255')]
|
|
public ?string $tax_id = null;
|
|
|
|
#[Validate('nullable|max:255')]
|
|
public ?string $registration_number = null;
|
|
|
|
public bool $is_verified = false;
|
|
|
|
public bool $is_active = true;
|
|
|
|
public function mount(int $id): void
|
|
{
|
|
$this->companyId = $id;
|
|
|
|
$company = Company::query()->find($id);
|
|
if (! $company) {
|
|
session()->flash('error', __('Die angeforderte Firma wurde nicht gefunden.'));
|
|
$this->redirect(route('admin.companies.index'), navigate: true);
|
|
|
|
return;
|
|
}
|
|
|
|
$this->portal = $company->portal?->value ?? Portal::Both->value;
|
|
$this->type = $company->type?->value ?? CompanyType::Company->value;
|
|
$this->company_name = $company->name;
|
|
$this->description = '';
|
|
$this->email = $company->email ?? '';
|
|
$this->phone = $company->phone ?? '';
|
|
$this->website = $company->website ?? '';
|
|
$this->street = $company->address ?? '';
|
|
$this->zip = '';
|
|
$this->city = '';
|
|
$this->state = '';
|
|
$this->country = $company->country_code ?? 'DE';
|
|
$this->is_verified = false;
|
|
$this->is_active = (bool) $company->is_active;
|
|
$this->current_logo_url = $company->logoUrl();
|
|
}
|
|
|
|
public function update(ImageService $imageService): void
|
|
{
|
|
$this->validate();
|
|
|
|
$company = Company::query()->find($this->companyId);
|
|
if (! $company) {
|
|
session()->flash('error', __('Die angeforderte Firma wurde nicht gefunden.'));
|
|
$this->redirect(route('admin.companies.index'), navigate: true);
|
|
|
|
return;
|
|
}
|
|
|
|
$slug = $company->generateUniqueSlug($this->company_name, ['portal' => $this->portal]);
|
|
$logoPath = $company->logo_path;
|
|
$logoVariants = $company->logo_variants;
|
|
|
|
if ($this->remove_logo) {
|
|
$imageService->deleteCompanyLogo($company->logo_path, $company->logo_variants);
|
|
$logoPath = null;
|
|
$logoVariants = null;
|
|
}
|
|
|
|
if ($this->logo) {
|
|
$imageService->deleteCompanyLogo($logoPath, $logoVariants);
|
|
|
|
$stored = $imageService->storeCompanyLogo(
|
|
$this->logo,
|
|
$this->portal === Portal::Both->value ? Portal::Presseecho->value : $this->portal,
|
|
$company->id,
|
|
);
|
|
|
|
$logoPath = $stored['path'];
|
|
$logoVariants = $stored['variants'];
|
|
}
|
|
|
|
$company->update([
|
|
'portal' => $this->portal,
|
|
'type' => $this->type,
|
|
'name' => $this->company_name,
|
|
'slug' => $slug,
|
|
'address' => $this->composeAddress(),
|
|
'country_code' => strtoupper($this->country),
|
|
'phone' => $this->phone ?: null,
|
|
'email' => $this->email ?: null,
|
|
'website' => $this->website ?: null,
|
|
'logo_path' => $logoPath,
|
|
'logo_variants' => $logoVariants,
|
|
'is_active' => $this->is_active,
|
|
]);
|
|
|
|
session()->flash('success', 'Firma erfolgreich aktualisiert.');
|
|
$this->redirect(route('admin.companies.index'), navigate: true);
|
|
}
|
|
|
|
public function with(): array
|
|
{
|
|
return [
|
|
'countries' => collect([
|
|
['code' => 'DE', 'name' => 'Deutschland'],
|
|
['code' => 'AT', 'name' => 'Österreich'],
|
|
['code' => 'CH', 'name' => 'Schweiz'],
|
|
['code' => 'FR', 'name' => 'Frankreich'],
|
|
['code' => 'GB', 'name' => 'Großbritannien'],
|
|
['code' => 'US', 'name' => 'USA'],
|
|
]),
|
|
'portalOptions' => Portal::cases(),
|
|
'typeOptions' => CompanyType::cases(),
|
|
];
|
|
}
|
|
|
|
public function deleteCompany(): void
|
|
{
|
|
$company = Company::query()->find($this->companyId);
|
|
if (! $company) {
|
|
session()->flash('error', __('Die angeforderte Firma wurde nicht gefunden.'));
|
|
$this->redirect(route('admin.companies.index'), navigate: true);
|
|
|
|
return;
|
|
}
|
|
|
|
$company->delete();
|
|
|
|
session()->flash('success', __('Firma wurde erfolgreich gelöscht.'));
|
|
$this->redirect(route('admin.companies.index'), navigate: true);
|
|
}
|
|
|
|
protected function composeAddress(): ?string
|
|
{
|
|
$lineOne = trim($this->street);
|
|
$lineTwo = trim(trim($this->zip).' '.trim($this->city));
|
|
$lineThree = trim($this->state);
|
|
|
|
$parts = array_filter([$lineOne, $lineTwo, $lineThree], fn ($value) => $value !== '');
|
|
|
|
return $parts !== [] ? implode(', ', $parts) : null;
|
|
}
|
|
}; ?>
|
|
|
|
<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">{{ __('Admin Backend') }}</span>
|
|
<span class="eyebrow muted">{{ __('Stammdaten · Firma bearbeiten') }}</span>
|
|
<span class="badge hub">ID {{ $companyId }}</span>
|
|
</div>
|
|
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
|
{{ __('Firma bearbeiten') }}
|
|
</h1>
|
|
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
|
{{ __('Stammdaten, Adresse, Logo und Rechtsangaben der Firma aktualisieren.') }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 flex-shrink-0">
|
|
<flux:button variant="filled" icon="arrow-left" href="{{ route('admin.companies.show', $companyId) }}" wire:navigate>
|
|
{{ __('Zurück') }}
|
|
</flux:button>
|
|
</div>
|
|
</header>
|
|
|
|
<form wire:submit="update" class="space-y-6">
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Basisinformationen') }}</span>
|
|
</div>
|
|
<div class="p-5 space-y-4">
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<flux:field>
|
|
<flux:label>{{ __('Portal') }}</flux:label>
|
|
<flux:select wire:model="portal">
|
|
@foreach ($portalOptions as $portalOption)
|
|
<option value="{{ $portalOption->value }}">{{ $portalOption->label() }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>{{ __('Typ') }}</flux:label>
|
|
<flux:select wire:model="type">
|
|
@foreach ($typeOptions as $typeOption)
|
|
<option value="{{ $typeOption->value }}">{{ $typeOption->label() }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
</flux:field>
|
|
</div>
|
|
|
|
<flux:field>
|
|
<flux:label>{{ __('Firmenname') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
|
<flux:input wire:model="company_name" placeholder="{{ __('Vollständiger Firmenname...') }}" />
|
|
<flux:error name="company_name" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>{{ __('Beschreibung') }}</flux:label>
|
|
<flux:textarea wire:model="description" rows="4" placeholder="{{ __('Kurze Beschreibung der Firma (optional)...') }}" />
|
|
<flux:error name="description" />
|
|
</flux:field>
|
|
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<flux:field>
|
|
<flux:label>{{ __('E-Mail') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
|
<flux:input wire:model="email" type="email" placeholder="{{ __('kontakt@firma.de') }}" icon="envelope" />
|
|
<flux:error name="email" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>{{ __('Telefon') }}</flux:label>
|
|
<flux:input wire:model="phone" type="tel" placeholder="{{ __('+49 123 456789') }}" icon="phone" />
|
|
<flux:error name="phone" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
<flux:field>
|
|
<flux:label>{{ __('Website') }}</flux:label>
|
|
<flux:input wire:model="website" type="url" placeholder="{{ __('https://www.firma.de') }}" icon="globe-alt" />
|
|
<flux:error name="website" />
|
|
</flux:field>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Adresse') }}</span>
|
|
</div>
|
|
<div class="p-5 space-y-4">
|
|
<flux:field>
|
|
<flux:label>{{ __('Straße & Hausnummer') }}</flux:label>
|
|
<flux:input wire:model="street" placeholder="{{ __('Musterstraße 123') }}" />
|
|
<flux:error name="street" />
|
|
</flux:field>
|
|
|
|
<div class="grid gap-4 sm:grid-cols-3">
|
|
<flux:field>
|
|
<flux:label>{{ __('PLZ') }}</flux:label>
|
|
<flux:input wire:model="zip" placeholder="{{ __('12345') }}" />
|
|
<flux:error name="zip" />
|
|
</flux:field>
|
|
|
|
<flux:field class="sm:col-span-2">
|
|
<flux:label>{{ __('Stadt') }}</flux:label>
|
|
<flux:input wire:model="city" placeholder="{{ __('Berlin') }}" />
|
|
<flux:error name="city" />
|
|
</flux:field>
|
|
</div>
|
|
|
|
<div class="grid gap-4 sm:grid-cols-2">
|
|
<flux:field>
|
|
<flux:label>{{ __('Bundesland / Region') }}</flux:label>
|
|
<flux:input wire:model="state" placeholder="{{ __('Bayern') }}" />
|
|
<flux:error name="state" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>{{ __('Land') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
|
<flux:select wire:model="country">
|
|
@foreach ($countries as $country)
|
|
<option value="{{ $country['code'] }}">{{ $country['name'] }}</option>
|
|
@endforeach
|
|
</flux:select>
|
|
<flux:error name="country" />
|
|
</flux:field>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Rechtliche Daten') }}</span>
|
|
</div>
|
|
<div class="p-5 grid gap-4 sm:grid-cols-2">
|
|
<flux:field>
|
|
<flux:label>{{ __('Steuernummer / USt-IdNr.') }}</flux:label>
|
|
<flux:input wire:model="tax_id" placeholder="{{ __('DE123456789') }}" />
|
|
<flux:error name="tax_id" />
|
|
</flux:field>
|
|
|
|
<flux:field>
|
|
<flux:label>{{ __('Handelsregisternummer') }}</flux:label>
|
|
<flux:input wire:model="registration_number" placeholder="{{ __('HRB 12345') }}" />
|
|
<flux:error name="registration_number" />
|
|
</flux:field>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Logo & Status') }}</span>
|
|
</div>
|
|
<div class="p-5 space-y-4">
|
|
<flux:field>
|
|
<flux:label>{{ __('Firmenlogo') }}</flux:label>
|
|
<flux:file-upload wire:model="logo" accept="image/jpeg,image/png,image/webp,image/gif">
|
|
<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:description>{{ __('Maximal 4 MB. Varianten (sq/wide) werden automatisch generiert.') }}</flux:description>
|
|
<flux:error name="logo" />
|
|
|
|
@if ($logo)
|
|
<div class="mt-4">
|
|
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-2">
|
|
{{ __('Neues Logo (Vorschau):') }}
|
|
</div>
|
|
<img src="{{ $logo->temporaryUrl() }}" width="128" height="128"
|
|
class="h-32 max-h-32 w-32 max-w-32 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)]">
|
|
</div>
|
|
@elseif ($current_logo_url && ! $remove_logo)
|
|
<div class="mt-4 flex items-center gap-4">
|
|
<div>
|
|
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-2">
|
|
{{ __('Aktuelles Logo:') }}
|
|
</div>
|
|
<img src="{{ $current_logo_url }}" width="128" height="128"
|
|
class="h-32 max-h-32 w-32 max-w-32 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)]">
|
|
</div>
|
|
<flux:button type="button" size="sm" variant="filled" wire:click="$set('remove_logo', true)">
|
|
{{ __('Logo entfernen') }}
|
|
</flux:button>
|
|
</div>
|
|
@elseif ($remove_logo)
|
|
<div class="mt-4 px-4 py-3 rounded-[5px] border text-[12.5px] flex items-start gap-3
|
|
bg-[color:var(--color-warn-soft)] border-[color:var(--color-warn)]/30 text-[color:var(--color-accent-deep)]">
|
|
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0 mt-0.5" />
|
|
<div class="flex-1">
|
|
{{ __('Logo wird beim Speichern entfernt.') }}
|
|
</div>
|
|
<flux:button type="button" size="sm" variant="filled" wire:click="$set('remove_logo', false)">
|
|
{{ __('Rückgängig') }}
|
|
</flux:button>
|
|
</div>
|
|
@endif
|
|
</flux:field>
|
|
|
|
<div class="flex gap-6 pt-2 border-t border-[color:var(--color-bg-rule)]">
|
|
<flux:checkbox wire:model="is_verified" label="{{ __('Firma ist verifiziert') }}" />
|
|
<flux:checkbox wire:model="is_active" label="{{ __('Firma ist aktiv') }}" />
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="panel">
|
|
<div class="panel-head">
|
|
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
|
</div>
|
|
<div class="p-5 flex justify-between items-center gap-3 flex-wrap">
|
|
<flux:modal.trigger name="confirm-company-deletion">
|
|
<flux:button
|
|
variant="danger"
|
|
icon="trash"
|
|
type="button"
|
|
x-data=""
|
|
x-on:click.prevent="$dispatch('open-modal', 'confirm-company-deletion')"
|
|
>
|
|
{{ __('Löschen') }}
|
|
</flux:button>
|
|
</flux:modal.trigger>
|
|
<div class="flex gap-3">
|
|
<flux:button variant="filled" href="{{ route('admin.companies.index') }}" wire:navigate>
|
|
{{ __('Abbrechen') }}
|
|
</flux:button>
|
|
<flux:button type="submit" variant="primary">
|
|
{{ __('Änderungen speichern') }}
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</form>
|
|
|
|
<flux:modal name="confirm-company-deletion" class="max-w-lg">
|
|
<div class="space-y-6">
|
|
<div>
|
|
<flux:heading size="lg">{{ __('Firma wirklich löschen?') }}</flux:heading>
|
|
<flux:subheading>
|
|
{{ __('Diese Aktion kann nicht direkt rückgängig gemacht werden. Die Firma wird archiviert (Soft Delete) und aus den Standardlisten entfernt.') }}
|
|
</flux:subheading>
|
|
</div>
|
|
|
|
<div class="flex justify-end space-x-2 rtl:space-x-reverse">
|
|
<flux:modal.close>
|
|
<flux:button variant="filled">{{ __('Abbrechen') }}</flux:button>
|
|
</flux:modal.close>
|
|
|
|
<flux:button variant="danger" wire:click="deleteCompany">
|
|
{{ __('Löschung bestätigen') }}
|
|
</flux:button>
|
|
</div>
|
|
</div>
|
|
</flux:modal>
|
|
</div>
|