Files
WebAPP/resources/views/admin/files/create.blade.php
Rhino 5942bdf6c3 Datei-Upload: Mehrfach-Upload mit Drag & Drop und Dateiliste
- 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>
2026-03-03 10:25:37 +01:00

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>