Spielerpositionen, Statistiken, Fahrgemeinschaften, Spielfeld-Visualisierung

- PlayerPosition Enum (7 Handball-Positionen) mit Label/ShortLabel
- Spielerstatistik pro Spiel (Tore, Würfe, TW-Paraden, Bemerkung)
- Position-Dropdown in Spieler-Editor und Event-Stats-Formular
- Statistik-Seite: TW zuerst, Trennlinie, Feldspieler, Position-Badges
- Spielfeld-SVG mit Ampel-Performance (grün/gelb/rot)
- Anklickbare Spieler im Spielfeld öffnen Detail-Modal
- Fahrgemeinschaften (Anbieten, Zuordnen, Zurückziehen)
- Übersetzungen in allen 6 Sprachen (de, en, pl, ru, ar, tr)
- .gitignore für Laravel hinzugefügt
- Demo-Daten mit Positionen und Statistiken

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Rhino
2026-03-02 11:47:34 +01:00
parent 2e24a40d68
commit ad60e7a9f9
46 changed files with 2041 additions and 86 deletions

View File

@@ -6,12 +6,16 @@ use App\Enums\CateringStatus;
use App\Enums\EventStatus;
use App\Enums\EventType;
use App\Enums\ParticipantStatus;
use App\Enums\PlayerPosition;
use App\Enums\UserRole;
use App\Models\ActivityLog;
use App\Models\Comment;
use App\Models\Event;
use App\Models\EventCarpool;
use App\Models\EventCarpoolPassenger;
use App\Models\EventCatering;
use App\Models\EventParticipant;
use App\Models\EventPlayerStat;
use App\Models\EventTimekeeper;
use App\Models\Faq;
use App\Models\Location;
@@ -43,6 +47,8 @@ class DemoDataSeeder extends Seeder
$this->seedCatering($events, $parentUsers);
$this->seedTimekeepers($events, $parentUsers);
$this->seedComments($events, $admin, $coach, $parentUsers);
$this->seedPlayerStats($events, $players);
$this->seedCarpools($events, $parentUsers, $players);
$this->seedFaqs($admin);
$this->seedActivityLogs($admin, $coach, $team, $events);
$this->seedSoftDeletedRecords($team);
@@ -200,9 +206,27 @@ class DemoDataSeeder extends Seeder
private function seedPlayers(array $data, Team $team): array
{
// Positionsverteilung: Index → Position
$positions = [
0 => PlayerPosition::Torwart,
1 => PlayerPosition::LinksAussen,
2 => PlayerPosition::RueckraumMitte,
3 => PlayerPosition::RueckraumLinks,
4 => PlayerPosition::RechtsAussen,
5 => PlayerPosition::RueckraumRechts,
6 => PlayerPosition::Kreislaeufer,
7 => PlayerPosition::LinksAussen,
8 => PlayerPosition::RechtsAussen,
9 => PlayerPosition::RueckraumLinks,
10 => PlayerPosition::RueckraumMitte,
11 => PlayerPosition::RueckraumRechts,
12 => PlayerPosition::Kreislaeufer,
13 => PlayerPosition::Torwart, // Ersatz-TW
];
$players = [];
$jerseyNr = 1;
foreach ($data as [$childFirst, $lastName, $parents]) {
foreach ($data as $idx => [$childFirst, $lastName, $parents]) {
$player = Player::firstOrCreate(
['first_name' => $childFirst, 'last_name' => $lastName, 'team_id' => $team->id],
[
@@ -210,8 +234,13 @@ class DemoDataSeeder extends Seeder
'jersey_number' => $jerseyNr,
'is_active' => true,
'photo_permission' => true,
'position' => $positions[$idx] ?? null,
]
);
// Position auch bei bestehenden Spielern setzen
if ($player->position === null && isset($positions[$idx])) {
$player->update(['position' => $positions[$idx]]);
}
$players["{$childFirst} {$lastName}"] = $player;
$jerseyNr++;
}
@@ -601,6 +630,122 @@ class DemoDataSeeder extends Seeder
}
}
// ─── Spielerstatistiken ─────────────────────────────────
// Heimspiel (Index 2, Ergebnis 15:12) — Stats für zugesagte Spieler
private function seedPlayerStats(array $events, array $players): void
{
$homeGame = $events[2]; // Heimspiel vs. TSV Beispielburg (15:12)
$playerList = array_values($players);
// Nur die ersten 18 Spieler haben beim Heimspiel zugesagt (siehe seedParticipants)
$statsData = [
// [index, is_goalkeeper, gk_saves, gk_shots, goals, shots, note, position]
[0, true, 8, 20, 0, 0, 'Starke Leistung im Tor', 'torwart'],
[1, false, null, null, 3, 6, 'Drei Tempogegenstöße', 'links_aussen'],
[2, false, null, null, 2, 5, 'Guter Aufbau', 'rueckraum_mitte'],
[3, false, null, null, 2, 4, null, 'rueckraum_links'],
[4, false, null, null, 1, 3, 'Schnelle Beine', 'rechts_aussen'],
[5, false, null, null, 2, 5, null, 'rueckraum_rechts'],
[6, false, null, null, 1, 2, 'Erste Tore der Saison!', 'kreislaeufer'],
[7, false, null, null, 1, 4, null, 'links_aussen'],
[8, false, null, null, 1, 3, null, 'rechts_aussen'],
[9, false, null, null, 1, 2, null, 'rueckraum_links'],
[10, false, null, null, 1, 3, 'Stark in der Abwehr', 'rueckraum_mitte'],
[11, false, null, null, 0, 2, null, 'rueckraum_rechts'],
[12, false, null, null, 0, 1, null, 'kreislaeufer'],
[13, false, null, null, 0, 1, null, 'torwart'],
[14, false, null, null, 0, 2, 'Erste Spielminuten', null],
[15, false, null, null, 0, 0, null, null],
[16, false, null, null, 0, 1, null, null],
[17, false, null, null, 0, 0, null, null],
];
foreach ($statsData as [$idx, $isGk, $gkSaves, $gkShots, $goals, $shots, $note, $position]) {
if (!isset($playerList[$idx])) {
continue;
}
EventPlayerStat::updateOrCreate(
['event_id' => $homeGame->id, 'player_id' => $playerList[$idx]->id],
[
'is_goalkeeper' => $isGk,
'goalkeeper_saves' => $gkSaves,
'goalkeeper_shots' => $gkShots,
'goals' => $goals,
'shots' => $shots,
'note' => $note,
'position' => $position,
]
);
}
}
// ─── Fahrgemeinschaften ──────────────────────────────────
// Auswärtsspiel (Index 3) + Turnier (Index 4)
private function seedCarpools(array $events, array $parentUsers, array $players): void
{
$p = array_values($parentUsers);
$pl = array_values($players);
// Auswärtsspiel (Index 3): 2 Fahrgemeinschaften
$carpool1 = EventCarpool::where('event_id', $events[3]->id)->where('user_id', $p[0]->id)->first();
if (!$carpool1) {
$carpool1 = new EventCarpool(['event_id' => $events[3]->id, 'seats' => 3, 'note' => 'Treffpunkt Parkplatz, 9:30 Uhr']);
$carpool1->user_id = $p[0]->id;
$carpool1->save();
}
// 2 Kinder zuordnen
foreach ([0, 1] as $childIdx) {
if (isset($pl[$childIdx])) {
$pass = EventCarpoolPassenger::where('carpool_id', $carpool1->id)->where('player_id', $pl[$childIdx]->id)->first();
if (!$pass) {
$pass = new EventCarpoolPassenger(['carpool_id' => $carpool1->id, 'player_id' => $pl[$childIdx]->id]);
$pass->added_by = $p[0]->id;
$pass->save();
}
}
}
$carpool2 = EventCarpool::where('event_id', $events[3]->id)->where('user_id', $p[2]->id)->first();
if (!$carpool2) {
$carpool2 = new EventCarpool(['event_id' => $events[3]->id, 'seats' => 2, 'note' => 'Abfahrt 9:15 Uhr ab Vereinsheim']);
$carpool2->user_id = $p[2]->id;
$carpool2->save();
}
// Voll belegt — 2 Kinder
foreach ([2, 3] as $childIdx) {
if (isset($pl[$childIdx])) {
$pass = EventCarpoolPassenger::where('carpool_id', $carpool2->id)->where('player_id', $pl[$childIdx]->id)->first();
if (!$pass) {
$pass = new EventCarpoolPassenger(['carpool_id' => $carpool2->id, 'player_id' => $pl[$childIdx]->id]);
$pass->added_by = $p[2]->id;
$pass->save();
}
}
}
// Turnier (Index 4): 1 Fahrgemeinschaft
$carpool3 = EventCarpool::where('event_id', $events[4]->id)->where('user_id', $p[5]->id)->first();
if (!$carpool3) {
$carpool3 = new EventCarpool(['event_id' => $events[4]->id, 'seats' => 4, 'note' => 'Großer Van, Abfahrt 8:00 Uhr']);
$carpool3->user_id = $p[5]->id;
$carpool3->save();
}
// 1 Kind zugeordnet
if (isset($pl[5])) {
$pass = EventCarpoolPassenger::where('carpool_id', $carpool3->id)->where('player_id', $pl[5]->id)->first();
if (!$pass) {
$pass = new EventCarpoolPassenger(['carpool_id' => $carpool3->id, 'player_id' => $pl[5]->id]);
$pass->added_by = $p[5]->id;
$pass->save();
}
}
}
// ─── FAQs ──────────────────────────────────────────────
private function seedFaqs(User $admin): void