12-05-2026 Frontend dev
This commit is contained in:
parent
405df0a122
commit
5b8bdf4182
779 changed files with 480564 additions and 6241 deletions
201
resources/views/livewire/customer/press-releases/index.blade.php
Normal file
201
resources/views/livewire/customer/press-releases/index.blade.php
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
<?php
|
||||
|
||||
use App\Enums\PressReleaseStatus;
|
||||
use App\Models\PressRelease;
|
||||
use App\Services\PressRelease\BlacklistViolationException;
|
||||
use App\Services\PressRelease\PressReleaseService;
|
||||
use App\Services\Customer\CustomerCompanyContext;
|
||||
use Livewire\Attributes\Layout;
|
||||
use Livewire\Attributes\Title;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Volt\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
new #[Layout('components.layouts.app'), Title('Meine Pressemitteilungen')] class extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public string $statusFilter = 'all';
|
||||
|
||||
#[Url(as: 'company', except: 'all')]
|
||||
public string $companyFilter = 'all';
|
||||
|
||||
public string $sortBy = 'created_at';
|
||||
|
||||
public string $sortDir = 'desc';
|
||||
|
||||
public function sort(string $column): void
|
||||
{
|
||||
if ($this->sortBy === $column) {
|
||||
$this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
$this->sortBy = $column;
|
||||
$this->sortDir = 'asc';
|
||||
}
|
||||
$this->resetPage();
|
||||
}
|
||||
|
||||
public function updatedSearch(): void { $this->resetPage(); }
|
||||
|
||||
public function updatedStatusFilter(): void { $this->resetPage(); }
|
||||
|
||||
public function updatedCompanyFilter(): void { $this->resetPage(); }
|
||||
|
||||
public function submitForReview(int $id): void
|
||||
{
|
||||
$pr = $this->findMyPR($id);
|
||||
if (! $pr) { return; }
|
||||
|
||||
try {
|
||||
app(PressReleaseService::class)->submitForReview($pr);
|
||||
session()->flash('success', __('Pressemitteilung zur Prüfung eingereicht.'));
|
||||
} catch (BlacklistViolationException $e) {
|
||||
session()->flash('error', __('Pressemitteilung wurde automatisch abgelehnt: unzulässiges Wort ":word".', ['word' => $e->word]));
|
||||
} catch (\LogicException $e) {
|
||||
session()->flash('error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function with(): array
|
||||
{
|
||||
$userId = auth()->id();
|
||||
$context = app(CustomerCompanyContext::class);
|
||||
$selectedCompanyId = $context->selectedCompanyId(auth()->user());
|
||||
|
||||
$prs = PressRelease::withoutGlobalScopes()
|
||||
->where('user_id', $userId)
|
||||
->with('company:id,name')
|
||||
->when($selectedCompanyId !== null, fn ($q) => $q->where('company_id', $selectedCompanyId))
|
||||
->when($selectedCompanyId === null && $this->companyFilter === 'assigned', fn ($q) => $q->whereNotNull('company_id'))
|
||||
->when($selectedCompanyId === null && $this->companyFilter === 'unassigned', fn ($q) => $q->whereNull('company_id'))
|
||||
->when(filled($this->search), function ($q): void {
|
||||
$term = $this->search;
|
||||
$q->where('title', 'like', '%'.$term.'%');
|
||||
})
|
||||
->when($this->statusFilter !== 'all', fn ($q) => $q->where('status', $this->statusFilter))
|
||||
->orderBy(in_array($this->sortBy, ['title', 'status', 'created_at']) ? $this->sortBy : 'created_at', $this->sortDir)
|
||||
->paginate(100);
|
||||
|
||||
return [
|
||||
'pressReleases' => $prs,
|
||||
'statusOptions' => PressReleaseStatus::cases(),
|
||||
'selectedCompany' => $context->selectedCompany(auth()->user()),
|
||||
'hasGlobalCompanyContext' => $selectedCompanyId === null,
|
||||
];
|
||||
}
|
||||
|
||||
private function findMyPR(int $id): ?PressRelease
|
||||
{
|
||||
return PressRelease::withoutGlobalScopes()
|
||||
->where('id', $id)
|
||||
->where('user_id', auth()->id())
|
||||
->first();
|
||||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(session('success'))
|
||||
<div class="rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-800 dark:border-green-800 dark:bg-green-900/20 dark:text-green-300">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Meine Pressemitteilungen') }}</flux:heading>
|
||||
@if($selectedCompany)
|
||||
<flux:subheading>{{ __('Gefiltert auf :company', ['company' => $selectedCompany->name]) }}</flux:subheading>
|
||||
@endif
|
||||
</div>
|
||||
<flux:button variant="primary" icon="plus" href="{{ route('me.press-releases.create') }}" wire:navigate>
|
||||
{{ __('Neue Pressemitteilung') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-3 sm:flex-row">
|
||||
<flux:input wire:model.live.debounce.300ms="search" placeholder="{{ __('Titel suchen…') }}" icon="magnifying-glass" class="flex-1" />
|
||||
<flux:select wire:model.live="statusFilter" class="sm:w-44">
|
||||
<option value="all">{{ __('Alle Status') }}</option>
|
||||
@foreach($statusOptions as $s)
|
||||
<option value="{{ $s->value }}">{{ $s->label() }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
@if($hasGlobalCompanyContext)
|
||||
<flux:select wire:model.live="companyFilter" class="sm:w-48">
|
||||
<option value="all">{{ __('Alle Firmenzuordnungen') }}</option>
|
||||
<option value="assigned">{{ __('Mit Firma') }}</option>
|
||||
<option value="unassigned">{{ __('Ohne Firma') }}</option>
|
||||
</flux:select>
|
||||
@endif
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card class="p-0">
|
||||
<div class="p-4">
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column sortable :sorted="$sortBy==='title'" :direction="$sortDir" wire:click="sort('title')">{{ __('Titel') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Firma') }}</flux:table.column>
|
||||
<flux:table.column sortable :sorted="$sortBy==='status'" :direction="$sortDir" wire:click="sort('status')">{{ __('Status') }}</flux:table.column>
|
||||
<flux:table.column sortable :sorted="$sortBy==='created_at'" :direction="$sortDir" wire:click="sort('created_at')">{{ __('Erstellt') }}</flux:table.column>
|
||||
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
|
||||
</flux:table.columns>
|
||||
|
||||
@forelse($pressReleases as $pr)
|
||||
<flux:table.row wire:key="{{ $pr->id }}">
|
||||
<flux:table.cell>
|
||||
<p class="max-w-xs truncate font-medium">{{ $pr->title }}</p>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:text class="text-sm">{{ $pr->company?->name ?? '–' }}</flux:text>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:badge color="{{ match($pr->status->value) {
|
||||
'published' => 'green',
|
||||
'review' => 'yellow',
|
||||
'rejected' => 'red',
|
||||
'archived' => 'blue',
|
||||
default => 'zinc',
|
||||
} }}">{{ $pr->status->label() }}</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:text class="text-sm text-zinc-500">{{ $pr->created_at->format('d.m.Y') }}</flux:text>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<div class="flex items-center gap-1">
|
||||
<flux:button size="sm" variant="ghost" icon="eye" href="{{ route('me.press-releases.show', $pr->id) }}" wire:navigate />
|
||||
@if(in_array($pr->status->value, ['draft', 'rejected']))
|
||||
<flux:button size="sm" variant="ghost" icon="pencil" href="{{ route('me.press-releases.edit', $pr->id) }}" wire:navigate />
|
||||
<flux:button size="sm" variant="ghost" icon="paper-airplane" wire:click="submitForReview({{ $pr->id }})"
|
||||
wire:confirm="{{ __('Pressemitteilung zur Prüfung einreichen?') }}" />
|
||||
@endif
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="5">
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
|
||||
<flux:icon.newspaper class="size-10 text-zinc-300" />
|
||||
<flux:text weight="semibold" class="mt-3">{{ __('Keine Pressemitteilungen gefunden') }}</flux:text>
|
||||
<flux:text class="mt-1 max-w-md text-sm text-zinc-500">
|
||||
{{ __('Passen Sie die Filter an oder erstellen Sie eine neue Pressemitteilung.') }}
|
||||
</flux:text>
|
||||
<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>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
{{ $pressReleases->links() }}
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue