- Upload-Formular unterstützt jetzt mehrere Dateien gleichzeitig - Drag & Drop oder Klick zum Auswählen (mehrfach möglich) - Dateiliste mit Dateiname, Größe, individueller Kategorie-Auswahl und Entfernen-Button pro Datei - Standard-Kategorie kann oben gewählt werden, individuelle Kategorie pro Datei ist optional überschreibbar - Controller verarbeitet Array von Dateien (je max. 10 MB) - Übersetzungen in allen 6 Sprachen ergänzt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
7.1 KiB
PHP
122 lines
7.1 KiB
PHP
<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-2xl">
|
|
<form method="POST" action="{{ route('admin.files.store') }}" enctype="multipart/form-data"
|
|
x-data="{
|
|
files: [],
|
|
dragging: false,
|
|
defaultCategory: '',
|
|
addFiles(fileList) {
|
|
for (const f of fileList) {
|
|
if (f.size > 10485760) continue;
|
|
this.files.push({ file: f, name: f.name, size: f.size, category: this.defaultCategory });
|
|
}
|
|
},
|
|
removeFile(index) {
|
|
this.files.splice(index, 1);
|
|
},
|
|
humanSize(bytes) {
|
|
if (bytes < 1024) return bytes + ' B';
|
|
if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
|
|
return (bytes / 1048576).toFixed(1) + ' MB';
|
|
}
|
|
}">
|
|
@csrf
|
|
|
|
{{-- Standard-Kategorie --}}
|
|
<div class="mb-4">
|
|
<label for="default_category" class="block text-sm font-medium text-gray-700 mb-1">{{ __('admin.file_category') }} *</label>
|
|
<select id="default_category" x-model="defaultCategory" 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 }}">{{ $cat->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
</div>
|
|
|
|
{{-- Drag & Drop Zone --}}
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-1">{{ __('admin.select_files') }}</label>
|
|
<div
|
|
@dragover.prevent="dragging = true"
|
|
@dragleave.prevent="dragging = false"
|
|
@drop.prevent="dragging = false; addFiles($event.dataTransfer.files)"
|
|
: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="!files.length">{{ __('admin.drag_or_click_files') }}</p>
|
|
<p class="mt-1 text-xs text-gray-500" x-show="!files.length">{{ __('admin.allowed_file_types') }} · {{ __('admin.max_file_size') }}</p>
|
|
<p class="mt-2 text-sm font-medium text-blue-600" x-show="files.length" x-text="files.length + ' {{ __('admin.files_selected') }}'"></p>
|
|
</div>
|
|
<input type="file" x-ref="fileInput" class="hidden" multiple
|
|
accept=".pdf,.docx,.xlsx,.jpg,.jpeg,.png,.gif,.webp"
|
|
@change="addFiles($refs.fileInput.files); $refs.fileInput.value = ''">
|
|
@error('files')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
|
|
@error('files.*')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
|
|
@error('categories')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
|
|
@error('categories.*')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
|
|
</div>
|
|
|
|
{{-- Datei-Liste --}}
|
|
<template x-if="files.length > 0">
|
|
<div class="mb-4 space-y-2">
|
|
<template x-for="(f, index) in files" :key="index">
|
|
<div class="flex items-center gap-3 bg-gray-50 rounded-md px-3 py-2">
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-medium text-gray-800 truncate" x-text="f.name"></p>
|
|
<p class="text-xs text-gray-500" x-text="humanSize(f.size)"></p>
|
|
</div>
|
|
<select x-model="f.category" class="px-2 py-1 border border-gray-300 rounded-md text-sm shrink-0">
|
|
<option value="">{{ __('admin.select_category') }}</option>
|
|
@foreach ($categories as $cat)
|
|
<option value="{{ $cat->id }}">{{ $cat->name }}</option>
|
|
@endforeach
|
|
</select>
|
|
<button type="button" @click="removeFile(index)" class="text-red-500 hover:text-red-700 shrink-0">
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
{{-- Hidden Inputs für tatsächlichen Upload --}}
|
|
<input type="file" name="files[]" x-ref="submitFiles" class="hidden" multiple>
|
|
|
|
<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"
|
|
:disabled="!files.length"
|
|
:class="!files.length ? 'opacity-50 cursor-not-allowed' : ''"
|
|
@click.prevent="
|
|
const dt = new DataTransfer();
|
|
const catValues = [];
|
|
let valid = true;
|
|
files.forEach(f => {
|
|
const cat = f.category || defaultCategory;
|
|
if (!cat) { valid = false; }
|
|
dt.items.add(f.file);
|
|
catValues.push(cat);
|
|
});
|
|
if (!valid) { alert('{{ __('admin.select_category_for_all') }}'); return; }
|
|
$refs.submitFiles.files = dt.files;
|
|
catValues.forEach((c, i) => {
|
|
const input = document.createElement('input');
|
|
input.type = 'hidden';
|
|
input.name = 'categories[' + i + ']';
|
|
input.value = c;
|
|
$el.closest('form').appendChild(input);
|
|
});
|
|
$el.closest('form').submit();
|
|
">
|
|
{{ __('admin.upload_files') }}
|
|
</button>
|
|
<a href="{{ route('admin.files.index') }}" class="text-sm text-gray-600 hover:underline">{{ __('ui.cancel') }}</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</x-layouts.admin>
|