Stand: SMTP-Test, Admin-Mail-Tab, Notifiable-Fix, Lazy-Quill

- Fix: Notifiable-Trait zum User-Model hinzugefuegt (behebt notify()-500er)
- Installer: SMTP-Verbindungstest mit EsmtpTransport + Ueberspringen-Link
- Admin: Neuer E-Mail-Tab mit SMTP-Konfiguration + Verbindungstest
- Admin: Lazy Quill-Initialisierung (nur sichtbare Locale wird geladen)
- Uebersetzungen: 17 neue Mail-Keys in allen 6 Sprachen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 07:30:37 +01:00
commit 2e24a40d68
9633 changed files with 1300799 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
<x-layouts.admin :title="__('admin.upload_file')">
<h1 class="text-2xl font-bold mb-6">{{ __('admin.upload_file') }}</h1>
<div class="bg-white rounded-lg shadow p-6 max-w-lg">
<form method="POST" action="{{ route('admin.files.store') }}" enctype="multipart/form-data">
@csrf
<div class="mb-4">
<label for="file_category_id" class="block text-sm font-medium text-gray-700 mb-1">{{ __('admin.file_category') }} *</label>
<select name="file_category_id" id="file_category_id" required class="w-full px-3 py-2 border border-gray-300 rounded-md">
<option value="">{{ __('admin.select_category') }}</option>
@foreach ($categories as $cat)
<option value="{{ $cat->id }}" {{ old('file_category_id') == $cat->id ? 'selected' : '' }}>{{ $cat->name }}</option>
@endforeach
</select>
@error('file_category_id')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
</div>
<div class="mb-4">
<label for="file" class="block text-sm font-medium text-gray-700 mb-1">{{ __('admin.upload_file') }} *</label>
<div x-data="{ fileName: '', dragging: false }" class="relative">
<div
@dragover.prevent="dragging = true"
@dragleave.prevent="dragging = false"
@drop.prevent="dragging = false; $refs.fileInput.files = $event.dataTransfer.files; fileName = $refs.fileInput.files[0]?.name || ''"
:class="dragging ? 'border-blue-400 bg-blue-50' : 'border-gray-300'"
class="border-2 border-dashed rounded-lg p-8 text-center cursor-pointer hover:border-blue-400 transition-colors"
@click="$refs.fileInput.click()">
<svg class="mx-auto h-10 w-10 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
<p class="mt-2 text-sm text-gray-600" x-show="!fileName">{{ __('admin.allowed_file_types') }}</p>
<p class="mt-1 text-xs text-gray-500" x-show="!fileName">{{ __('admin.max_file_size') }}</p>
<p class="mt-2 text-sm font-medium text-blue-600" x-show="fileName" x-text="fileName"></p>
</div>
<input type="file" name="file" id="file" x-ref="fileInput" class="hidden"
accept=".pdf,.docx,.xlsx,.jpg,.jpeg,.png,.gif,.webp"
@change="fileName = $refs.fileInput.files[0]?.name || ''">
</div>
@error('file')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
</div>
<div class="flex items-center gap-3">
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 text-sm font-medium">{{ __('admin.upload_file') }}</button>
<a href="{{ route('admin.files.index') }}" class="text-sm text-gray-600 hover:underline">{{ __('ui.cancel') }}</a>
</div>
</form>
</div>
</x-layouts.admin>

View File

@@ -0,0 +1,89 @@
<x-layouts.admin :title="__('admin.files_title')">
<div class="flex justify-between items-center mb-6">
<h1 class="text-2xl font-bold">{{ __('admin.files_title') }}</h1>
<a href="{{ route('admin.files.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 text-sm font-medium">
{{ __('admin.upload_file') }}
</a>
</div>
{{-- Kategorie-Tabs --}}
<div class="flex flex-wrap gap-2 mb-6">
<a href="{{ route('admin.files.index') }}"
class="px-3 py-1.5 rounded-md text-sm font-medium {{ !$activeCategory ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }}">
{{ __('ui.all') }} ({{ $categories->sum('files_count') }})
</a>
@foreach ($categories as $category)
<a href="{{ route('admin.files.index', ['category' => $category->slug]) }}"
class="px-3 py-1.5 rounded-md text-sm font-medium {{ $activeCategory === $category->slug ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200' }} {{ !$category->is_active ? 'opacity-50' : '' }}">
{{ $category->name }} ({{ $category->files_count }})
</a>
@endforeach
</div>
{{-- Datei-Liste --}}
@if ($files->isEmpty())
<div class="bg-white rounded-lg shadow p-8 text-center">
<svg class="mx-auto h-12 w-12 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m6.75 12H9.75m3 0h3m-1.5-3H12m-3 3V18m0-3h.008v.008H9v-.008zm0 3h.008v.008H9V18z" />
</svg>
<p class="mt-3 text-sm text-gray-500">{{ $activeCategory ? __('admin.no_files_yet') : __('admin.no_files_at_all') }}</p>
</div>
@else
<div class="grid gap-3" x-data>
@foreach ($files as $file)
<div class="bg-white rounded-lg shadow px-4 sm:px-5 py-4 flex items-center gap-3 sm:gap-4 min-w-0">
{{-- Clickable file area --}}
<div @click="$dispatch('open-file-preview', @js($file->previewData()))"
class="flex items-center gap-3 min-w-0 flex-1 cursor-pointer hover:opacity-80 transition-opacity">
{{-- Thumbnail oder Icon --}}
@if ($file->isImage())
<img src="{{ route('files.preview', $file) }}" alt="" class="flex-shrink-0 w-10 h-10 rounded-lg object-cover bg-gray-100" loading="lazy">
@else
<div class="flex-shrink-0 w-10 h-10 rounded-lg flex items-center justify-center
{{ match($file->iconType()) {
'pdf' => 'bg-red-100 text-red-600',
'word' => 'bg-blue-100 text-blue-600',
'excel' => 'bg-green-100 text-green-600',
default => 'bg-gray-100 text-gray-600',
} }}">
@if ($file->iconType() === 'pdf')
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zm-1 2l5 5h-5V4z"/></svg>
@elseif ($file->iconType() === 'word')
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zm-1 2l5 5h-5V4zM9 13h6v1H9v-1zm0 2h6v1H9v-1zm0 2h4v1H9v-1z"/></svg>
@elseif ($file->iconType() === 'excel')
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zm-1 2l5 5h-5V4zM9 13h2v2H9v-2zm0 3h2v2H9v-2zm3-3h2v2h-2v-2zm0 3h2v2h-2v-2z"/></svg>
@else
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zm-1 2l5 5h-5V4z"/></svg>
@endif
</div>
@endif
<div class="min-w-0">
<p class="text-sm font-medium text-gray-900 truncate">{{ $file->original_name }}</p>
<div class="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs text-gray-500 mt-0.5">
<span class="inline-flex items-center px-1.5 py-0.5 rounded bg-gray-100 text-gray-600 font-medium">{{ $file->category->name }}</span>
<span>{{ $file->humanSize() }}</span>
<span class="hidden sm:inline">{{ $file->uploader->name ?? '' }}</span>
<span class="hidden sm:inline">{{ $file->created_at->translatedFormat(__('ui.date_format_short')) }}</span>
</div>
</div>
</div>
{{-- Admin Actions --}}
<div class="flex items-center gap-2 flex-shrink-0">
<a href="{{ route('files.download', $file) }}" class="text-xs text-blue-600 hover:text-blue-800 font-medium">{{ __('ui.download') }}</a>
<form method="POST" action="{{ route('admin.files.destroy', $file) }}" class="inline" onsubmit="return confirm(@js(__('admin.confirm_delete_file')))">
@csrf
@method('DELETE')
<button type="submit" class="text-xs text-red-600 hover:text-red-800">{{ __('ui.delete') }}</button>
</form>
</div>
</div>
@endforeach
</div>
<div class="mt-4">{{ $files->links() }}</div>
@endif
<x-file-preview-modal />
</x-layouts.admin>