'DR', 4 => 'MR', 6 => 'RW', 9 => 'DC', 10 => 'DM', 11 => 'MC', 12 => 'AM', 15 => 'SW', 16 => 'DC', 17 => 'DM', 18 => 'MC', 19 => 'AM', 20 => 'SM', 21 => 'ST', 23 => 'DC', 24 => 'DM', 25 => 'MC', 26 => 'AM', 30 => 'DL', 32 => 'ML', 34 => 'LM']; public bool $copyDisabled = true; public bool $calculationDisabled = true; public bool $rangeXDisabled = true; public string $ss_team_name = ''; public array $lineupsMap = [ '3313' => [9, 16, 23, 11, 18, 25, 19, 6, 34, 21], '3331' => [9, 16, 23, 17, 11, 25, 12, 19, 26, 21], '3331-' => [9, 16, 23, 10, 18, 24, 12, 19, 26, 21], '3331+' => [9, 16, 23, 11, 18, 25, 12, 19, 26, 21], '3412-' => [9, 16, 23, 4, 10, 24, 32, 19, 20, 21], '3412+' => [9, 16, 23, 4, 11, 25, 32, 19, 20, 21], '3421-' => [9, 16, 23, 4, 10, 24, 32, 12, 26, 21], '3421+' => [9, 16, 23, 4, 11, 25, 32, 12, 26, 21], '343-' => [9, 16, 23, 4, 10, 24, 32, 6, 34, 21], '343+' => [9, 16, 23, 4, 11, 25, 32, 6, 34, 21], '3511' => [9, 16, 23, 4, 11, 17, 25, 32, 19, 21], '3511-' => [9, 16, 23, 4, 10, 18, 24, 32, 19, 21], '3511+' => [9, 16, 23, 4, 11, 18, 25, 32, 19, 21], '352' => [9, 16, 23, 4, 11, 17, 25, 32, 20, 21], '352-' => [9, 16, 23, 4, 10, 18, 24, 32, 20, 21], '352+' => [9, 16, 23, 4, 11, 18, 25, 32, 20, 21], '4132' => [2, 9, 23, 30, 17, 12, 19, 26, 20, 21], '4213-' => [2, 9, 23, 30, 10, 24, 19, 6, 34, 21], '4213+' => [2, 9, 23, 30, 11, 25, 19, 6, 34, 21], '4222' => [2, 9, 23, 30, 10, 24, 12, 26, 20, 21], '4231' => [2, 9, 23, 30, 10, 24, 12, 19, 26, 21], '424' => [2, 9, 23, 30, 10, 24, 6, 34, 20, 21], '4312' => [2, 9, 23, 30, 17, 11, 25, 19, 20, 21], '4312-' => [2, 9, 23, 30, 10, 18, 24, 19, 20, 21], '4312+' => [2, 9, 23, 30, 11, 18, 25, 19, 20, 21], '4321' => [2, 9, 23, 30, 17, 11, 25, 12, 26, 21], '4321-' => [2, 9, 23, 30, 10, 18, 24, 12, 26, 21], '4321+' => [2, 9, 23, 30, 11, 18, 25, 12, 26, 21], '433' => [2, 9, 23, 30, 17, 11, 25, 6, 34, 21], '433-' => [2, 9, 23, 30, 10, 18, 24, 6, 34, 21], '433+' => [2, 9, 23, 30, 11, 18, 25, 6, 34, 21], '4411-' => [2, 9, 23, 30, 4, 10, 24, 32, 19, 21], '4411+' => [2, 9, 23, 30, 4, 11, 25, 32, 19, 21], '442' => [2, 9, 23, 30, 4, 17, 18, 32, 20, 21], '442-' => [2, 9, 23, 30, 4, 10, 24, 32, 20, 21], '442+' => [2, 9, 23, 30, 4, 11, 25, 32, 20, 21], '451' => [2, 9, 23, 30, 4, 17, 11, 25, 32, 21], '451-' => [2, 9, 23, 30, 4, 10, 18, 24, 32, 21], '451+' => [2, 9, 23, 30, 4, 11, 18, 25, 32, 21], '523-' => [2, 9, 16, 23, 30, 10, 24, 6, 34, 21], '523+' => [2, 9, 16, 23, 30, 11, 25, 6, 34, 21], '5311' => [2, 9, 16, 23, 30, 17, 11, 25, 19, 21], '5311-' => [2, 9, 16, 23, 30, 10, 18, 24, 19, 21], '5311+' => [2, 9, 16, 23, 30, 11, 18, 25, 19, 21], '532' => [2, 9, 16, 23, 30, 17, 11, 25, 20, 21], '532-' => [2, 9, 16, 23, 30, 10, 18, 24, 20, 21], '532+' => [2, 9, 16, 23, 30, 11, 18, 25, 20, 21], '541-' => [2, 9, 16, 23, 30, 4, 10, 24, 32, 21], '541+' => [2, 9, 16, 23, 30, 4, 11, 25, 32, 21], ]; public array $lineup = []; public array $lineup_options = [ '3313' => '3313', '3331' => '3331', '3331-' => '3331-', '3331+' => '3331+', '3412-' => '3412-', '3412+' => '3412+', '3421-' => '3421-', '3421+' => '3421+', '343-' => '343-', '343+' => '343+', '3511' => '3511', '3511-' => '3511-', '3511+' => '3511+', '352' => '352', '352-' => '352-', '352+' => '352+', '4132' => '4132', '4213-' => '4213-', '4213+' => '4213+', '4222' => '4222', '4231' => '4231', '424' => '424', '4312' => '4312', '4312-' => '4312-', '4312+' => '4312+', '4321' => '4321', '4321-' => '4321-', '4321+' => '4321+', '433' => '433', '433-' => '433-', '433+' => '433+', '4411-' => '4411-', '4411+' => '4411+', '442' => '442', '442-' => '442-', '442+' => '442+', '451' => '451', '451-' => '451-', '451+' => '451+', '523-' => '523-', '523+' => '523+', '5311' => '5311', '5311-' => '5311-', '5311+' => '5311+', '532' => '532', '532-' => '532-', '532+' => '532+', '541-' => '541-', '541+' => '541+', ]; public string $lineup_selected = '3313'; public array $boxes = []; public array $beschriftung = []; public function mount(array $filters) { $this->filters = $filters; $this->ss_team_name = SofaScoreRosa::where('ss_team_id', $filters['team_id'])->value('ss_team_name'); $this->sourceRecords = PlayerCalculator::load($filters); $this->lineup = $this->lineupsMap[$this->lineup_selected]; for ($i = 1; $i <= 35; $i++) { $this->boxes[$i] = []; if (isset($this->activeBoxes[$i])) { $this->beschriftung[$i] = $this->activeBoxes[$i]; } else { $this->beschriftung[$i] = ''; } } // Log::debug('beschriftung', $this->beschriftung); } public function updateXvalue() { $this->calculationChecker(); } public function getHeadingTable(): HtmlString { return new HtmlString('
Gls
Fls
Ofs
Snt
Tsh
Crd
Foot
Evt
Ømin
'); } public function form(Schema $schema): Schema { return $schema ->components([ Select::make('lineup_selected') ->label('Lineup') ->live() ->prefixIcon('icon-soccerplayer') ->searchable() ->options($this->lineup_options) ->afterStateUpdated(function ($state, $livewire) { $livewire->updateLineup($state); }), ]); } public function table(Table $table): Table { return $table ->headerActions([ // FilamentView::registerRenderHook( // PanelsRenderHook::GLOBAL_SEARCH_AFTER, // fn (): View => view('filament.modals.position-abbreviation'), // ); Action::make('close') ->size(Size::Small) ->tooltip('Close application') ->hiddenLabel() ->icon('phosphor-x') ->close(), Action::make('lineups') ->size(Size::Small) ->tooltip('Lineup abbreviation explanation') ->hiddenLabel() ->icon('phosphor-question-mark') ->slideOver() ->slideOverPosition(SlideOverPosition::Start) ->modalWidth(Width::ExtraLarge) ->modalSubmitAction(false) ->modalCancelAction(false) ->modalContent(fn () => view('filament.modals.position-abbreviation')), Action::make('reset') ->hiddenLabel() ->size(Size::Small) ->tooltip('Remove all players from field') ->icon('phosphor-arrows-clockwise') ->action(fn () => $this->clearAll()), // Action::make('calculate') // ->hiddenLabel() // ->tooltip('Calculate expected values') // ->size(Size::Small) // ->icon('phosphor-calculator') // ->disabled($this->calculationDisabled) // ->action(fn () => $this->calculateAspg()), Action::make('copy') ->tooltip('Copy results for pasting into a spread sheet') ->hiddenLabel() ->size(Size::Small) ->icon('phosphor-copy') ->disabled($this->copyDisabled) ->action(fn () => $this->copyResult()), ]) /** * WICHTIG: Da Filament v5 zwingend eine Query braucht, nutzen wir ein * leeres Modell (z.B. ein beliebiges Basis-Modell deines Projekts) NUR als Dummy-Builder. * Alternativ kannst du ein anonymes Model nutzen. */ ->query(User::query()->whereRaw('1 = 0')) // Verhindert DB-Abfragen ->striped() ->defaultSort('name') ->columns([ ViewColumn::make('drag_card') ->columnSpanFull() ->label(fn () => $this->getHeadingTable()) ->view('filament.tables.columns.drag-card'), ]) ->filters([ SelectFilter::make('position_area') ->columnSpanFull() ->native(false) ->options([ // 'attack' => 'Attack', // 'midfield' => 'Midfield', // 'defence' => 'Denfence', // 'goalkeeper' => 'Goalkeeper', 'attack' => 'Attack', 'midfield' => 'Midfield', 'defence' => 'Defence', 'goalkeeper' => 'Goalkeeper', ])->modifyFormFieldUsing(fn (Select $component) => $component->allowHtml()) ->query(function ($query) { return $query; })->indicateUsing(function (array $data): array { // WICHTIG: Ein leeres Array sorgt dafür, dass kein Indicator-Badge erzeugt wird return []; }), ], layout: FiltersLayout::AboveContent) ->deferFilters(false) ->records(function () { $collection = collect($this->sourceRecords); $selectedPosition = $this->tableFilters['position_area']['value'] ?? null; if (! empty($selectedPosition)) { $collection = $collection->where('position_area', $selectedPosition); } return $collection; }); } #[On('element-dropped-in-box')] public function handleElementDropped(mixed $elementData, mixed $boxNumber): void { $boxNumber = (int) $boxNumber; $player = is_array($elementData) ? $elementData : json_decode($elementData, true); if (! $player || ! isset($player['id'])) { return; } foreach ($this->sourceRecords as $k => $record) { if ($record['id'] == (int) $player['id']) { if ($this->boxes[$boxNumber]) { $this->clearBox($boxNumber); } $this->boxes[$boxNumber] = $record; $this->sourceRecords[$k]['active'] = 1; $this->sourceRecords[$k]['position'] = $this->activeBoxes[$boxNumber]; } } $this->calculationChecker(); } public function calculationChecker() { $count = 0; foreach ($this->sourceRecords as $record) { if (count($record)) { $count++; } } if ($count >= 9) { $this->copyDisabled = false; $this->rangeXDisabled = false; $this->calculateAspg(); } else { $this->copyDisabled = true; $this->rangeXDisabled = true; $this->clearAll(); } } public function updateLineup(?string $state) { $this->clearAll(); $this->calculationChecker(); if (isset($this->lineupsMap[$state])) { $this->lineup = $this->lineupsMap[$state]; } else { $this->lineup = []; } } public function clearBox(int $boxNumber): void { if (isset($this->boxes[$boxNumber])) { $tmp = $this->boxes[$boxNumber]; $player_id = 0; foreach ($tmp as $key => $value) { if ($key == 'id') { $player_id = $value; } } foreach ($this->sourceRecords as $k => $record) { if ($record['id'] == (int) $player_id) { $this->boxes[$boxNumber] = []; $this->sourceRecords[$k]['position'] = ''; $this->sourceRecords[$k]['active'] = 0; } } $this->calculationChecker(); } } public function clearAll(): void { for ($i = 1; $i <= 35; $i++) { $this->boxes[$i] = []; } foreach ($this->sourceRecords as $k => $record) { $this->sourceRecords[$k]['position'] = ''; $this->sourceRecords[$k]['active'] = 0; $this->sourceRecords[$k]['gls'] = 0; $this->sourceRecords[$k]['fls'] = 0; $this->sourceRecords[$k]['ofs'] = 0; $this->sourceRecords[$k]['snt'] = 0; $this->sourceRecords[$k]['tsh'] = 0; $this->sourceRecords[$k]['crd'] = 0; } $this->calculationChecker(); } public function copyResult() { $copystring = ''; foreach ($this->results as $row) { dd($row); unset($row['origin']); unset($row['pcolor']); unset($row['ecolor']); unset($row['info']); $copystring .= implode("\t", $row)."\n"; } $this->js("navigator.clipboard.writeText(`$copystring`)"); } public function calculateAspg(): void { // Calcolare Aspettativa Team per GOALS // (il valore che appare quando viene selezionato un Evento): nell' esempio, // se scegliamo come Team il Remo (Home) il valore di riferimento sarà 1.2 $team_id = $this->filters['team_id']; $res = StatisticFixtures::where('id', $this->filters['match_id'])->first(); $asp_team = 0; if ((int) $res->home_id == (int) $team_id) { $asp_team = $res->exp_home; } elseif ((int) $res->away_id == (int) $team_id) { $asp_team = $res->exp_away; } // "atr" => 2.8868026211843 // "id" => 44691 // "name" => "Maicon" // "on_field" => 0 // "position" => "DC" // "sgf" => 1044 // "smg" => 1044 // "adg" => 90 // "as_goals" => 0.044 // "at_goals" => 67.511 // "as_cards" => 0.195 // "at_cards" => 67.54875 // "as_fouls" => 0.959 // "at_fouls" => 67.73975 // "as_offsides" => 0.036 // "at_offsides" => 67.509 // "as_total_shots" => 0.525 // "at_total_shots" => 67.63125 // "as_shots_on_target" => 0.263 // "at_shots_on_target" => 67.56575 $player = StatistikoDetail::where('ss_team_id', (int) $team_id)->where('position', '!=', 'GK')->where('is_active', 1)->get(); // Aspettativa Team di Riferimento // (ATR) = Aspettativa Team * Numero di Giocatori di Team in Rosters (esclusi i portieri) / 10. // Ad esempio per Remo (supponiamo 24 giocatori nel Roster esclusi i portieri): ATR = 1.2 * 24 / 10 = 2.88 $asp_team_riferimento = $asp_team * $player->count() / 10; $player_totals = SofaScorePlayerPerSeason::with('details')->where('season', 'total')->where('ss_team_id', $team_id)->get(); $results = []; foreach ($player_totals as $val) { if ($val->details && $val->details->is_active) { $ss_player_id = $val->ss_player_id; $position = $val->details->position; $tmp = []; $tmp['id'] = $ss_player_id; $tmp['name'] = $val->details->player_name; $tmp['asp_team'] = $asp_team; $tmp['asp_team_riferimento'] = $asp_team_riferimento; $position_field = $this->get_position($ss_player_id); if ($position_field) { $position = $position_field; $tmp['active'] = 1; } else { $tmp['active'] = 0; } $tmp['position'] = $position; // Aspettativa di Gioco (AdG) = Somma Goals fatti da Player nel DB / Somma Minuti Giocati da Player (SMG) nel DB * 90. // Se la SMG è < Y per quel Player prenderemo in considerazione solo l'Aspettativa Statistica (AS). // Il valore Y dovrebbe essere configurabile in una interfaccia. Il valore iniziale lo fissiamo a Y = 180 // Somma Goals fatti da Player nel DB $somma_goals = $val->goals; $tmp['somma_goals'] = $val->goals; // Somma Minuti Giocati da Player (SMG) nel DB $somma_minuti = $val->minutes_played_home + $val->minutes_played_away; $tmp['somma_minuti'] = $somma_minuti; // Aspettativa di Gioco // (AdG) $asp_gioco = $somma_goals / $somma_minuti * 90; $tmp['asp_gioco'] = $asp_gioco; // il valore corrispondente al ruolo del Player (esempio un giocatore in Details ha ruolo = RW // quindi Aspettativa Statistica (AS) per RW = 0.219) $bettypes = ['goals', 'cards', 'fouls', 'offsides', 'total_shots', 'shots_on_target']; foreach ($bettypes as $bettype) { $asp_statistica = (float) SofaScorePlayerPosition::where('ss_code', $position)->value('exp_'.$bettype); $tmp['asp_statistica_'.$bettype] = $asp_statistica; if ($somma_minuti < $this->y_value) { // Se la SMG è < Y per quel Player prenderemo in considerazione solo l'Aspettativa Statistica (AS). $tmp['asp_teorica_'.$bettype] = $asp_statistica; } else { // Aspettativa Teorica (AT) Goals = (X * AdG + AS) / (1 + X) // dove X è un valore che dovrebbe essere parametrizzabile da una interfaccia. // Il valore iniziale lo fissiamo a X = 3 // (AT) $tmp['asp_teorica_'.$bettype] = ($this->x_value * $asp_gioco + $asp_statistica) / (1 + $this->x_value); } } // Calcolare la Somma di tutte le AT (colonna G della Tabella) // Considerare la ATR calcolata allo Step 1 (colonna I della Tabella) // Aspettativa per Player: AP = AT * ATR / TOT AT $results[] = $tmp; } } $bettypes = ['goals', 'cards', 'fouls', 'offsides', 'total_shots', 'shots_on_target']; foreach ($bettypes as $bettype) { $total_bettype = array_sum(array_column($results, 'asp_teorica_'.$bettype)); foreach ($results as $key => $value) { // Aspettativa per Player: AP = AT * ATR / TOT AT $results[$key]['asp_total_'.$bettype] = $total_bettype; $results[$key]['asp_player_'.$bettype] = ((float) $value['asp_team'] * (float) $value['asp_teorica_'.$bettype]) / $total_bettype; } } // dd($results); // update frontend foreach ($this->sourceRecords as $k => $record) { // "id" => 787607 // "name" => "Keno" // "preferred_foot" => "Right" // "player_position" => "LW" // "position_area" => "attack" // "player_color" => "warning" // "timed_color" => "danger" // "events_played" => 7 // "everage_minutes" => 11 // "position" => "DC" // "active" => 1 // "gls" => 0 // "fls" => 0 // "ofs" => 0 // "snt" => 0 // "tsh" => 0 // "crd" => 0 foreach ($results as $key => $result) { // "id" => 44691 // "name" => "Maicon" // "asp_team" => "1.11030870045550" // "asp_team_riferimento" => 2.8868026211843 // "active" => 0 // "position" => "DC" // "somma_goals" => 1044 // "somma_minuti" => 1044 // "asp_gioco" => 90 // "asp_statistica_goals" => 0.044 // "asp_teorica_goals" => 67.511 // "asp_statistica_cards" => 0.195 // "asp_teorica_cards" => 67.54875 // "asp_statistica_fouls" => 0.959 // "asp_teorica_fouls" => 67.73975 // "asp_statistica_offsides" => 0.036 // "asp_teorica_offsides" => 67.509 // "asp_statistica_total_shots" => 0.525 // "asp_teorica_total_shots" => 67.63125 // "asp_statistica_shots_on_target" => 0.263 // "asp_teorica_shots_on_target" => 67.56575 // "asp_player_goals" => 0.041110675617421 // "asp_player_cards" => 0.041126169295623 // "asp_player_fouls" => 0.041104958293849 // "asp_player_offsides" => 0.041105354678332 // "asp_player_total_shots" => 0.041028401589513 // "asp_player_shots_on_target" => 0.041094150075013 if ($record['id'] == $result['id']) { // dd($result); $this->sourceRecords[$k]['active'] = $result['active']; $this->sourceRecords[$k]['gls'] = number_format($result['asp_player_goals'], 3); $this->sourceRecords[$k]['fls'] = number_format($result['asp_player_fouls'], 3); $this->sourceRecords[$k]['ofs'] = number_format($result['asp_player_offsides'], 3); $this->sourceRecords[$k]['snt'] = number_format($result['asp_player_total_shots'], 3); $this->sourceRecords[$k]['tsh'] = number_format($result['asp_player_shots_on_target'], 3); $this->sourceRecords[$k]['crd'] = number_format($result['asp_player_cards'], 3); } } } } public function get_position(int $ss_player_id) { // see if player is in field and if yes get new position foreach ($this->sourceRecords as $record) { if ($record['id'] == $ss_player_id) { return $record['position']; } } return false; } public function render() { return view('livewire.board-dispatcher'); } }