12-05-2026 Frontend dev
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run

This commit is contained in:
Kevin Adametz 2026-05-12 18:32:33 +02:00
parent 405df0a122
commit 5b8bdf4182
779 changed files with 480564 additions and 6241 deletions

View file

@ -0,0 +1,275 @@
<?php
use App\Enums\Portal;
use App\Models\Company;
use App\Models\Contact;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends Component
{
public int|string|null $companyId = null;
public string $companySearch = '';
public string $portal = '';
public ?string $salutationKey = null;
public ?string $title = null;
public ?string $firstName = null;
public ?string $lastName = null;
public ?string $responsibility = null;
public ?string $phone = null;
public ?string $fax = null;
public ?string $email = null;
public bool $isCompanyPrefilled = false;
public function mount(?int $companyId = null): void
{
$prefilledCompanyId = $companyId ?: request()->integer('company');
if ($prefilledCompanyId > 0) {
$this->companyId = $prefilledCompanyId;
$company = Company::withoutGlobalScopes()->find($prefilledCompanyId);
$this->portal = $company?->portal?->value ?? Portal::Both->value;
$this->isCompanyPrefilled = true;
} else {
$this->portal = Portal::Both->value;
}
}
public function updatedCompanySearch(): void
{
$this->resetErrorBag('companyId');
}
public function updatedCompanyId(): void
{
if (! $this->companyId) {
return;
}
$company = Company::withoutGlobalScopes()->find((int) $this->companyId);
if ($company) {
$this->portal = $company->portal?->value ?? Portal::Both->value;
}
}
public function save(): void
{
$validated = $this->validate([
'companyId' => ['required', 'integer', Rule::exists('companies', 'id')],
'portal' => ['required', Rule::in(array_map(static fn (Portal $portal): string => $portal->value, Portal::cases()))],
'salutationKey' => ['nullable', 'string', 'max:20'],
'title' => ['nullable', 'string', 'max:80'],
'firstName' => ['nullable', 'string', 'max:80'],
'lastName' => ['nullable', 'string', 'max:80'],
'responsibility' => ['nullable', 'string', 'max:255'],
'phone' => ['nullable', 'string', 'max:40'],
'fax' => ['nullable', 'string', 'max:40'],
'email' => ['nullable', 'email', 'max:255'],
]);
$this->companySearch = '';
$contact = Contact::query()->create([
'company_id' => (int) $validated['companyId'],
'portal' => $validated['portal'],
'salutation_key' => $validated['salutationKey'] ?: null,
'title' => $validated['title'] ?: null,
'first_name' => $validated['firstName'] ?: null,
'last_name' => $validated['lastName'] ?: null,
'responsibility' => $validated['responsibility'] ?: null,
'phone' => $validated['phone'] ?: null,
'fax' => $validated['fax'] ?: null,
'email' => $validated['email'] ?: null,
]);
session()->flash('success', __('Kontakt wurde angelegt.'));
$this->redirect(route('admin.contacts.edit', $contact->id), navigate: true);
}
public function with(): array
{
$term = trim($this->companySearch);
$companies = Company::withoutGlobalScopes()
->when(filled($term), function ($q) use ($term): void {
$q->where(function ($q) use ($term): void {
if ($this->supportsFullTextSearch($term)) {
$q->whereFullText(['name', 'email', 'slug'], $term);
return;
}
$q->where('name', 'like', '%'.$term.'%')
->orWhere('slug', 'like', '%'.$term.'%');
});
})
->when(blank($term) && $this->companyId, function ($q): void {
// Aktuell gewählte Firma immer einschließen, damit das Combobox-Label korrekt angezeigt wird
$q->whereIn('id', [(int) $this->companyId]);
})
->when(blank($term) && ! $this->companyId, function ($q): void {
// Ohne Suchbegriff und ohne Auswahl: keine Ergebnisse
$q->whereRaw('0 = 1');
})
->orderBy('name')
->limit(50)
->get(['id', 'name']);
return [
'companies' => $companies,
'salutations' => config('salutations.items', []),
'portalOptions' => Portal::cases(),
];
}
private function supportsFullTextSearch(string $term): bool
{
return mb_strlen($term) >= 3
&& in_array(DB::connection()->getDriverName(), ['mysql', 'pgsql'], true);
}
}; ?>
<form wire:submit="save" class="space-y-6">
<flux:card>
<flux:heading size="lg">{{ __('Kontakt anlegen') }}</flux:heading>
<flux:subheading>{{ __('Kontakt einer Firma zuordnen und Stammdaten erfassen.') }}</flux:subheading>
@if($isCompanyPrefilled && $companyId)
<flux:text class="mt-2 text-sm text-zinc-500">
{{ __('Firma wurde vorausgewählt. Du kannst sie bei Bedarf trotzdem ändern.') }}
</flux:text>
@endif
</flux:card>
<flux:card>
<div class="grid gap-4 sm:grid-cols-2">
<flux:field>
<flux:label>{{ __('Firma') }}</flux:label>
<flux:select
wire:model.live="companyId"
variant="combobox"
:filter="false"
clearable
placeholder="{{ __('Firma auswählen...') }}"
>
<x-slot name="input">
<flux:select.input
wire:model.live.debounce.300ms="companySearch"
placeholder="{{ __('Name eingeben…') }}"
/>
</x-slot>
@foreach($companies as $company)
<flux:select.option :value="$company->id" wire:key="{{ $company->id }}">
{{ $company->name }}
</flux:select.option>
@endforeach
<x-slot name="empty">
<flux:select.option.empty>
@if(blank(trim($companySearch)))
{{ __('Mindestens 1 Zeichen eingeben…') }}
@else
{{ __('Keine Firma gefunden.') }}
@endif
</flux:select.option.empty>
</x-slot>
</flux:select>
<flux:error name="companyId" />
</flux:field>
<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:error name="portal" />
</flux:field>
</div>
</flux:card>
<flux:card>
<flux:heading size="lg" class="mb-4">{{ __('Kontaktdaten') }}</flux:heading>
<div class="grid gap-4 sm:grid-cols-2">
<flux:field>
<flux:label>{{ __('Anrede') }}</flux:label>
<flux:select wire:model="salutationKey">
<option value="">{{ __('Bitte wählen') }}</option>
@foreach($salutations as $key => $labels)
<option value="{{ $key }}">{{ $labels['de'] ?? $key }}</option>
@endforeach
</flux:select>
</flux:field>
<flux:field>
<flux:label>{{ __('Titel') }}</flux:label>
<flux:input wire:model="title" />
</flux:field>
<flux:field>
<flux:label>{{ __('Vorname') }}</flux:label>
<flux:input wire:model="firstName" />
<flux:error name="firstName" />
</flux:field>
<flux:field>
<flux:label>{{ __('Nachname') }}</flux:label>
<flux:input wire:model="lastName" />
<flux:error name="lastName" />
</flux:field>
<flux:field class="sm:col-span-2">
<flux:label>{{ __('Verantwortlichkeit') }}</flux:label>
<flux:input wire:model="responsibility" />
<flux:error name="responsibility" />
</flux:field>
<flux:field>
<flux:label>{{ __('E-Mail') }}</flux:label>
<flux:input wire:model="email" type="email" />
<flux:error name="email" />
</flux:field>
<flux:field>
<flux:label>{{ __('Telefon') }}</flux:label>
<flux:input wire:model="phone" />
<flux:error name="phone" />
</flux:field>
<flux:field class="sm:col-span-2">
<flux:label>{{ __('Fax') }}</flux:label>
<flux:input wire:model="fax" />
<flux:error name="fax" />
</flux:field>
</div>
</flux:card>
<flux:card>
<div class="flex justify-end gap-3">
<flux:button variant="ghost" href="{{ route('admin.contacts.index') }}" wire:navigate>
{{ __('Abbrechen') }}
</flux:button>
<flux:button type="submit" variant="primary">
{{ __('Kontakt anlegen') }}
</flux:button>
</div>
</flux:card>
</form>