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)); } }