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:
255
app/Http/Controllers/Admin/ListGeneratorController.php
Normal file
255
app/Http/Controllers/Admin/ListGeneratorController.php
Normal file
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ActivityLog;
|
||||
use App\Models\File;
|
||||
use App\Models\FileCategory;
|
||||
use App\Models\Player;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class ListGeneratorController extends Controller
|
||||
{
|
||||
public function create(): View
|
||||
{
|
||||
$teams = Team::where('is_active', true)->orderBy('name')->get();
|
||||
|
||||
return view('admin.list-generator.create', compact('teams'));
|
||||
}
|
||||
|
||||
public function store(Request $request): View
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'title' => 'required|string|max:255',
|
||||
'subtitle' => 'nullable|string|max:255',
|
||||
'notes' => 'nullable|string|max:2000',
|
||||
'team_id' => 'nullable|exists:teams,id',
|
||||
'source' => 'required|in:players,parents,freetext',
|
||||
'freetext_rows' => 'nullable|required_if:source,freetext|string|max:50000',
|
||||
'columns' => 'nullable|array',
|
||||
'custom_columns' => 'nullable|array',
|
||||
'custom_columns.*' => 'string|max:100',
|
||||
]);
|
||||
|
||||
$columns = $this->buildColumns($validated);
|
||||
$rows = $this->buildRows($validated, $columns);
|
||||
|
||||
// Auto-detect orientation and font size for single-page PDF
|
||||
$colCount = count($columns);
|
||||
$rowCount = count($rows);
|
||||
$orientation = $colCount > 4 ? 'landscape' : 'portrait';
|
||||
|
||||
// Font size calculation for single-page fit
|
||||
$fontSize = 10;
|
||||
if ($rowCount > 35) {
|
||||
$fontSize = 7;
|
||||
} elseif ($rowCount > 25) {
|
||||
$fontSize = 8;
|
||||
} elseif ($rowCount > 15) {
|
||||
$fontSize = 9;
|
||||
}
|
||||
|
||||
$viewData = [
|
||||
'title' => $validated['title'],
|
||||
'subtitle' => $validated['subtitle'] ?? null,
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
'columns' => $columns,
|
||||
'rows' => $rows,
|
||||
'generatedAt' => now(),
|
||||
'orientation' => $orientation,
|
||||
'fontSize' => $fontSize,
|
||||
];
|
||||
|
||||
// Generate PDF
|
||||
$pdf = Pdf::loadView('admin.list-generator.document', $viewData)
|
||||
->setPaper('a4', $orientation);
|
||||
|
||||
$pdfContent = $pdf->output();
|
||||
|
||||
// Save to file library
|
||||
$category = FileCategory::where('slug', 'allgemein')->firstOrFail();
|
||||
$storedName = Str::uuid() . '.pdf';
|
||||
Storage::disk('local')->put('files/' . $storedName, $pdfContent);
|
||||
|
||||
$file = new File([
|
||||
'file_category_id' => $category->id,
|
||||
'original_name' => Str::slug($validated['title']) . '.pdf',
|
||||
'mime_type' => 'application/pdf',
|
||||
'size' => strlen($pdfContent),
|
||||
]);
|
||||
$file->stored_name = $storedName;
|
||||
$file->disk = 'private';
|
||||
$file->uploaded_by = auth()->id();
|
||||
$file->save();
|
||||
|
||||
ActivityLog::log('created', __('admin.log_list_generated', ['title' => $validated['title']]), 'File', $file->id);
|
||||
|
||||
return view('admin.list-generator.result', [
|
||||
'title' => $validated['title'],
|
||||
'subtitle' => $validated['subtitle'] ?? null,
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
'columns' => $columns,
|
||||
'rows' => $rows,
|
||||
'file' => $file,
|
||||
]);
|
||||
}
|
||||
|
||||
private function buildColumns(array $data): array
|
||||
{
|
||||
$columns = ['name' => __('ui.name')];
|
||||
$selected = $data['columns'] ?? [];
|
||||
|
||||
$playerColumns = [
|
||||
'team' => __('admin.nav_teams'),
|
||||
'jersey_number' => __('admin.jersey_number'),
|
||||
'birth_year' => __('admin.birth_year'),
|
||||
'parents' => __('admin.parents'),
|
||||
'photo_permission' => __('admin.photo_permission'),
|
||||
];
|
||||
|
||||
$parentColumns = [
|
||||
'team' => __('admin.nav_teams'),
|
||||
'email' => __('ui.email'),
|
||||
'phone' => __('admin.phone'),
|
||||
'children' => __('admin.children'),
|
||||
];
|
||||
|
||||
$available = match ($data['source']) {
|
||||
'players' => $playerColumns,
|
||||
'parents' => $parentColumns,
|
||||
default => [],
|
||||
};
|
||||
|
||||
foreach ($selected as $col) {
|
||||
if (isset($available[$col])) {
|
||||
$columns[$col] = $available[$col];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (($data['custom_columns'] ?? []) as $i => $header) {
|
||||
$header = trim($header);
|
||||
if ($header !== '') {
|
||||
$columns['custom_' . $i] = $header;
|
||||
}
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
private function buildRows(array $data, array $columns): array
|
||||
{
|
||||
if ($data['source'] === 'freetext') {
|
||||
return $this->buildFreetextRows($data);
|
||||
}
|
||||
|
||||
if ($data['source'] === 'players') {
|
||||
return $this->buildPlayerRows($data, $columns);
|
||||
}
|
||||
|
||||
return $this->buildParentRows($data, $columns);
|
||||
}
|
||||
|
||||
private function buildPlayerRows(array $data, array $columns): array
|
||||
{
|
||||
$query = Player::with(['team', 'parents'])->where('is_active', true);
|
||||
|
||||
if (!empty($data['team_id'])) {
|
||||
$query->where('team_id', $data['team_id']);
|
||||
}
|
||||
|
||||
$query->orderBy('last_name')->orderBy('first_name');
|
||||
$players = $query->get();
|
||||
|
||||
$rows = [];
|
||||
foreach ($players as $player) {
|
||||
$row = ['name' => $player->full_name];
|
||||
|
||||
if (isset($columns['team'])) {
|
||||
$row['team'] = $player->team->name ?? '–';
|
||||
}
|
||||
if (isset($columns['jersey_number'])) {
|
||||
$row['jersey_number'] = $player->jersey_number ?? '–';
|
||||
}
|
||||
if (isset($columns['birth_year'])) {
|
||||
$row['birth_year'] = $player->birth_year ?? '–';
|
||||
}
|
||||
if (isset($columns['parents'])) {
|
||||
$row['parents'] = $player->parents->map(fn ($p) => $p->name)->implode(', ') ?: '–';
|
||||
}
|
||||
if (isset($columns['photo_permission'])) {
|
||||
$row['photo_permission'] = $player->photo_permission ? __('ui.yes') : __('ui.no');
|
||||
}
|
||||
|
||||
foreach ($columns as $key => $header) {
|
||||
if (str_starts_with($key, 'custom_')) {
|
||||
$row[$key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function buildParentRows(array $data, array $columns): array
|
||||
{
|
||||
$query = User::with('children.team')->where('is_active', true);
|
||||
|
||||
if (!empty($data['team_id'])) {
|
||||
$query->whereHas('children', fn ($q) => $q->where('team_id', $data['team_id']));
|
||||
}
|
||||
|
||||
$query->orderBy('name');
|
||||
$users = $query->get();
|
||||
|
||||
$rows = [];
|
||||
foreach ($users as $user) {
|
||||
$row = ['name' => $user->name];
|
||||
|
||||
if (isset($columns['team'])) {
|
||||
$teamNames = $user->children->pluck('team.name')->filter()->unique()->implode(', ');
|
||||
$row['team'] = $teamNames ?: '–';
|
||||
}
|
||||
if (isset($columns['email'])) {
|
||||
$row['email'] = $user->email;
|
||||
}
|
||||
if (isset($columns['phone'])) {
|
||||
$row['phone'] = $user->phone ?? '–';
|
||||
}
|
||||
if (isset($columns['children'])) {
|
||||
$row['children'] = $user->children->map(fn ($c) => $c->first_name)->implode(', ') ?: '–';
|
||||
}
|
||||
|
||||
foreach ($columns as $key => $header) {
|
||||
if (str_starts_with($key, 'custom_')) {
|
||||
$row[$key] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
private function buildFreetextRows(array $data): array
|
||||
{
|
||||
$lines = array_filter(
|
||||
array_map('trim', explode("\n", $data['freetext_rows'] ?? '')),
|
||||
fn ($line) => $line !== ''
|
||||
);
|
||||
|
||||
// Maximum 200 Zeilen — DoS-Schutz (V10)
|
||||
$lines = array_slice($lines, 0, 200);
|
||||
|
||||
return array_map(fn ($line) => ['name' => $line], array_values($lines));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user