From 422681df01675c8c3654ac727b5294a317287890 Mon Sep 17 00:00:00 2001 From: salam Date: Tue, 28 Apr 2026 16:50:23 +0200 Subject: [PATCH] statistic on separate page wip --- .../Pages/CreateStatisticFixtures.php | 11 + .../Pages/EditStatisticFixtures.php | 19 ++ .../Pages/ListStatisticFixtures.php | 26 +++ .../Schemas/StatisticFixturesForm.php | 18 ++ .../StatisticFixturesResource.php | 50 +++++ .../Tables/StatisticFixturesTable.php | 69 +++++++ .../Pages/ListStatistikoEvents.php | 9 - .../StatistikoEventsResource.php | 5 - .../Tables/StatistikoEventsTable.php | 1 - .../Schemas/StatistikoImportForm.php | 8 +- .../Tables/StatistikoImportsTable.php | 2 +- .../Pages/ListStatistikoPlayers.php | 10 - .../StatistikoPlayerResource.php | 7 +- .../Pages/CreateStatisticEvents.php | 11 - .../Pages/EditStatisticEvents.php | 19 -- .../Pages/ListStatisticEvents.php | 19 -- .../Schemas/StatisticEventsForm.php | 16 -- .../StatisticEventsResource.php | 47 ----- .../Tables/StatisticEventsTable.php | 189 ------------------ .../Clusters/Statistic/StatisticCluster.php | 16 -- .../Imports/Helper/ImportBenchmark.php | 57 ++++++ app/Filament/Imports/Helper/ImportEvents.php | 59 ++++++ .../Imports/Helper/ImportFixtures.php | 45 +++++ app/Filament/Imports/Helper/ImportPlayer.php | 52 +++++ .../Imports/StatistikoEventImporter.php | 104 ---------- app/Filament/Imports/StatistikoImporter.php | 151 +------------- .../Imports/StatistikoPlayerImporter.php | 130 ------------ app/Filament/Pages/Statistic.php | 127 ++++++++++++ .../Resources/Events/Tables/EventsTable.php | 1 - app/Filament/Widgets/StatisticChart.php | 22 ++ app/Models/StatisticFixtures.php | 45 +++++ app/Providers/Filament/AdminPanelProvider.php | 13 +- ...075531_create_statistic_fixtures_table.php | 34 ++++ .../views/filament/pages/statistic.blade.php | 2 + 34 files changed, 660 insertions(+), 734 deletions(-) create mode 100644 app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Pages/CreateStatisticFixtures.php create mode 100644 app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Pages/EditStatisticFixtures.php create mode 100644 app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Pages/ListStatisticFixtures.php create mode 100644 app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Schemas/StatisticFixturesForm.php create mode 100644 app/Filament/Clusters/StaticData/Resources/StatisticFixtures/StatisticFixturesResource.php create mode 100644 app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Tables/StatisticFixturesTable.php delete mode 100644 app/Filament/Clusters/Statistic/Resources/StatisticEvents/Pages/CreateStatisticEvents.php delete mode 100644 app/Filament/Clusters/Statistic/Resources/StatisticEvents/Pages/EditStatisticEvents.php delete mode 100644 app/Filament/Clusters/Statistic/Resources/StatisticEvents/Pages/ListStatisticEvents.php delete mode 100644 app/Filament/Clusters/Statistic/Resources/StatisticEvents/Schemas/StatisticEventsForm.php delete mode 100644 app/Filament/Clusters/Statistic/Resources/StatisticEvents/StatisticEventsResource.php delete mode 100644 app/Filament/Clusters/Statistic/Resources/StatisticEvents/Tables/StatisticEventsTable.php delete mode 100644 app/Filament/Clusters/Statistic/StatisticCluster.php create mode 100644 app/Filament/Imports/Helper/ImportBenchmark.php create mode 100644 app/Filament/Imports/Helper/ImportEvents.php create mode 100644 app/Filament/Imports/Helper/ImportFixtures.php create mode 100644 app/Filament/Imports/Helper/ImportPlayer.php delete mode 100644 app/Filament/Imports/StatistikoEventImporter.php delete mode 100644 app/Filament/Imports/StatistikoPlayerImporter.php create mode 100644 app/Filament/Pages/Statistic.php create mode 100644 app/Filament/Widgets/StatisticChart.php create mode 100644 app/Models/StatisticFixtures.php create mode 100644 database/migrations/2026_04_28_075531_create_statistic_fixtures_table.php create mode 100644 resources/views/filament/pages/statistic.blade.php diff --git a/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Pages/CreateStatisticFixtures.php b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Pages/CreateStatisticFixtures.php new file mode 100644 index 00000000..736cab98 --- /dev/null +++ b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Pages/CreateStatisticFixtures.php @@ -0,0 +1,11 @@ +icon('phosphor-trash') + ->requiresConfirmation() + ->color('danger') + ->action(function () { + StatistikoEvent::where('starttime', '<', now()->subHour())->delete(); + }), + ]; + } +} diff --git a/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Schemas/StatisticFixturesForm.php b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Schemas/StatisticFixturesForm.php new file mode 100644 index 00000000..b8f33890 --- /dev/null +++ b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Schemas/StatisticFixturesForm.php @@ -0,0 +1,18 @@ +components([ + TextInput::make('exp_home')->required(), + TextInput::make('exp_away')->required(), + ]); + } +} diff --git a/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/StatisticFixturesResource.php b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/StatisticFixturesResource.php new file mode 100644 index 00000000..125ab4d5 --- /dev/null +++ b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/StatisticFixturesResource.php @@ -0,0 +1,50 @@ + ListStatisticFixtures::route('/'), + ]; + } + + public static function getNavigationBadge(): ?string + { + return parent::getEloquentQuery()->count(); + } +} diff --git a/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Tables/StatisticFixturesTable.php b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Tables/StatisticFixturesTable.php new file mode 100644 index 00000000..d021905f --- /dev/null +++ b/app/Filament/Clusters/StaticData/Resources/StatisticFixtures/Tables/StatisticFixturesTable.php @@ -0,0 +1,69 @@ +columns([ + IconColumn::make('sport_id') + ->label('') + ->icon(fn (string $state): string => match ($state) { + '1' => 'icon-soccerball', + '2' => 'icon-icehockey', + '3' => 'icon-basketball', + '5' => 'icon-tennisball', + }), + TextColumn::make('league.name') + ->label('League') + ->searchable() + ->wrap() + ->hidden(fn () => auth()->user()->sport_id == 5) + ->description(fn ($record) => ucwords($record->league?->country->name)), + TextColumn::make('starttime')->dateTime('M j, Y')->label('Date'), + TextColumn::make('home.name')->color('primary')->searchable(), + TextColumn::make('away.name')->color('success')->searchable(), + // TextColumn::make('exp_home')->alignCenter()->numeric(), + // TextColumn::make('exp_away')->alignCenter()->numeric(), + TextColumn::make('updated_at')->alignCenter()->date(), + ]) + ->filters([ + SelectFilter::make('league_id') + ->options(function () { + $res = StatisticFixtures::where('sport_id', 1) + ->groupBy('league_id') + ->pluck('league_id')->toArray(); + + return CompetitionGroup::query()->whereIn('id', $res)->orderBy('name')->pluck('name', 'id'); + }) + ->preload() + ->searchable() + ->columnSpan(2) + ->label('League'), + ], layout: FiltersLayout::AboveContent) + ->deferFilters(false) + ->recordActions([ + EditAction::make()->iconButton(), + DeleteAction::make()->iconButton(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Pages/ListStatistikoEvents.php b/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Pages/ListStatistikoEvents.php index 2712db2a..f0228d0a 100644 --- a/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Pages/ListStatistikoEvents.php +++ b/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Pages/ListStatistikoEvents.php @@ -3,7 +3,6 @@ namespace App\Filament\Clusters\StaticData\Resources\StatistikoEvents\Pages; use App\Filament\Clusters\StaticData\Resources\StatistikoEvents\StatistikoEventsResource; -// use App\Filament\Imports\StatistikoEventImporter; use App\Models\StatistikoEvent; use Filament\Actions\Action; use Filament\Actions\CreateAction; @@ -27,14 +26,6 @@ class ListStatistikoEvents extends ListRecords CreateAction::make() ->label('New') ->icon('phosphor-plus'), - // ImportAction::make('importEvents') - // ->maxRows(200000) - // ->color('success') - // ->chunkSize(250) - // ->icon('phosphor-upload') - // ->label('Import') - // ->modalDescription('Max 20k rows per import, column separator: ";"') - // ->importer(StatistikoEventImporter::class), ]; } } diff --git a/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/StatistikoEventsResource.php b/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/StatistikoEventsResource.php index d8a5c354..3ffd6adf 100644 --- a/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/StatistikoEventsResource.php +++ b/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/StatistikoEventsResource.php @@ -47,9 +47,4 @@ class StatistikoEventsResource extends Resource { return parent::getEloquentQuery()->count(); } - - public static function getNavigationBadgeColor(): ?string - { - return 'success'; - } } diff --git a/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Tables/StatistikoEventsTable.php b/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Tables/StatistikoEventsTable.php index 5fb2e6ca..7d75c402 100644 --- a/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Tables/StatistikoEventsTable.php +++ b/app/Filament/Clusters/StaticData/Resources/StatistikoEvents/Tables/StatistikoEventsTable.php @@ -2,7 +2,6 @@ namespace App\Filament\Clusters\StaticData\Resources\StatistikoEvents\Tables; -use App\Models\Sport; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; diff --git a/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Schemas/StatistikoImportForm.php b/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Schemas/StatistikoImportForm.php index 82d4c2c0..a01ad695 100644 --- a/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Schemas/StatistikoImportForm.php +++ b/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Schemas/StatistikoImportForm.php @@ -20,10 +20,12 @@ class StatistikoImportForm ->options(Sport::where('is_active', 1)->pluck('name', 'id')), Select::make('data_type') ->required() + ->allowHtml() ->options([ - 'player' => 'Player', - 'events' => 'Events', - 'events_player' => 'Events & Player', + 'player' => 'Player', + 'events' => 'Events', + 'fixtures' => 'Fixtures', + 'events_player' => 'Events & Player', ]), FileUpload::make('file_name') diff --git a/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Tables/StatistikoImportsTable.php b/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Tables/StatistikoImportsTable.php index 0f593303..75f48f16 100644 --- a/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Tables/StatistikoImportsTable.php +++ b/app/Filament/Clusters/StaticData/Resources/StatistikoImports/Tables/StatistikoImportsTable.php @@ -34,11 +34,11 @@ class StatistikoImportsTable ->options([ 'player' => 'Player', 'events' => 'Events', + 'fixtures' => 'Fixtures', 'events_player' => 'Events & Player', ]), TextColumn::make('file_name_original') ->searchable() - ->color('primary') ->label('Name'), IconColumn::make('has_run') ->label('Import') diff --git a/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/Pages/ListStatistikoPlayers.php b/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/Pages/ListStatistikoPlayers.php index 7d46fe60..6526300c 100644 --- a/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/Pages/ListStatistikoPlayers.php +++ b/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/Pages/ListStatistikoPlayers.php @@ -3,11 +3,9 @@ namespace App\Filament\Clusters\StaticData\Resources\StatistikoPlayers\Pages; use App\Filament\Clusters\StaticData\Resources\StatistikoPlayers\StatistikoPlayerResource; -// use App\Filament\Imports\StatistikoPlayerImporter; use App\Models\StatistikoPlayer; use Filament\Actions\Action; use Filament\Actions\CreateAction; -// use Filament\Actions\ImportAction; use Filament\Resources\Pages\ListRecords; class ListStatistikoPlayers extends ListRecords @@ -27,14 +25,6 @@ class ListStatistikoPlayers extends ListRecords CreateAction::make() ->label('New') ->icon('phosphor-plus'), - // ImportAction::make('importPlayer') - // ->maxRows(200000) - // ->color('success') - // ->chunkSize(250) - // ->icon('phosphor-upload') - // ->label('Import') - // ->modalDescription('Max 20k rows per import, column separator ";"') - // ->importer(StatistikoPlayerImporter::class), ]; } } diff --git a/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/StatistikoPlayerResource.php b/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/StatistikoPlayerResource.php index 61148e29..4aa43324 100644 --- a/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/StatistikoPlayerResource.php +++ b/app/Filament/Clusters/StaticData/Resources/StatistikoPlayers/StatistikoPlayerResource.php @@ -24,7 +24,7 @@ class StatistikoPlayerResource extends Resource protected static ?string $cluster = StaticDataCluster::class; - protected static ?int $navigationSort = 2; + protected static ?int $navigationSort = 4; protected static ?string $recordTitleAttribute = 'giocatore'; @@ -49,9 +49,4 @@ class StatistikoPlayerResource extends Resource { return parent::getEloquentQuery()->count(); } - - public static function getNavigationBadgeColor(): ?string - { - return 'success'; - } } diff --git a/app/Filament/Clusters/Statistic/Resources/StatisticEvents/Pages/CreateStatisticEvents.php b/app/Filament/Clusters/Statistic/Resources/StatisticEvents/Pages/CreateStatisticEvents.php deleted file mode 100644 index 7771b26e..00000000 --- a/app/Filament/Clusters/Statistic/Resources/StatisticEvents/Pages/CreateStatisticEvents.php +++ /dev/null @@ -1,11 +0,0 @@ -components([ - // - ]); - } -} diff --git a/app/Filament/Clusters/Statistic/Resources/StatisticEvents/StatisticEventsResource.php b/app/Filament/Clusters/Statistic/Resources/StatisticEvents/StatisticEventsResource.php deleted file mode 100644 index c4f2dfc3..00000000 --- a/app/Filament/Clusters/Statistic/Resources/StatisticEvents/StatisticEventsResource.php +++ /dev/null @@ -1,47 +0,0 @@ - ListStatisticEvents::route('/'), - ]; - } -} diff --git a/app/Filament/Clusters/Statistic/Resources/StatisticEvents/Tables/StatisticEventsTable.php b/app/Filament/Clusters/Statistic/Resources/StatisticEvents/Tables/StatisticEventsTable.php deleted file mode 100644 index a4cee8b5..00000000 --- a/app/Filament/Clusters/Statistic/Resources/StatisticEvents/Tables/StatisticEventsTable.php +++ /dev/null @@ -1,189 +0,0 @@ -striped() - ->columns([ - IconColumn::make('sport_id') - ->label('') - ->icon(fn (string $state): string => match ($state) { - '1' => 'icon-soccerball', - '2' => 'icon-icehockey', - '3' => 'icon-basketball', - '5' => 'icon-tennisball', - }), - TextColumn::make('league.name')->searchable(), - TextColumn::make('starttime')->dateTime('M j, Y')->label('Date'), - TextColumn::make('home.name')->color('primary')->searchable(), - TextColumn::make('away.name')->color('success')->searchable(), - ]) - ->filters([ - Filter::make('events') - ->columns(5)->columnSpan(5) - ->schema([ - Select::make('sport_id') - ->default(1) - ->hiddenLabel() - ->options(Sport::whereIn('id', [1, 2, 3, 5])->pluck('name', 'id')), - Select::make(name: 'country_id') - ->hidden(fn (Get $get) => $get('sport_id') == 5) - ->placeholder('Country') - ->prefixIcon('phosphor-flag') - ->hiddenLabel() - ->searchable() - ->options(function (Get $get) { - $res = Event::where('has_markets', 1) - ->where('sport_id', $get('sport_id')) - ->groupBy('country_id') - ->pluck('country_id')->toArray(); - - return Country::query()->whereIn('id', $res)->orderBy('name')->pluck('name', 'id'); - }) - ->live() - ->reactive() - ->afterStateUpdated(function (callable $set) { - $set('competition_group_id', null); - }), - Select::make('competition_group_id') - ->placeholder(fn (Get $get) => $get('sport_id') == 5 ? 'Tournament' : 'League') - ->prefixIcon('phosphor-trophy') - ->hiddenLabel() - // ->multiple() - ->searchable() - ->options(function (Get $get) { - $country_id = $get('country_id') ?? 0; - $res = Event::with('country') - ->with('competition_group') - ->where('sport_id', $get('sport_id')) - ->when($country_id, function ($query, $country_id) { - $query->where('country_id', $country_id); - })->groupBy('competition_group_id')->get(); - - $arr = []; - foreach ($res as $val) { - $arr[(int) $val->competition_group_id] = $val->country?->name.' '.$val->competition_group?->name; - } - - asort($arr); - - return $arr; - }) - ->live(), - DatePicker::make('starttime_from') - ->prefix(fn () => new HtmlString('From')) - ->displayFormat('d M Y') - ->hiddenLabel() - ->label(''), - DatePicker::make('starttime_to') - ->prefix(fn () => new HtmlString('To:')) - ->displayFormat('d M Y') - ->hiddenLabel() - ->label(''), - ]) - ->query(function (Builder $query, array $data): Builder { - return $query->when( - $data['sport_id'], - function (Builder $query, $sport_id) { - $query->where('sport_id', (int) $sport_id); - } - )->when( - $data['competition_group_id'], - function (Builder $query, $competition_group_id) { - // dd($competition_group_id); - $query->where('league_id', (int) $competition_group_id); - } - )->when( - $data['starttime_from'], - function (Builder $query, $starttime_from) { - if (isset($starttime_from)) { - $from = Carbon::createFromDate($starttime_from); - - return $query->where('starttime', '>=', $starttime_from); - } - } - )->when( - $data['starttime_to'], - function (Builder $query, $starttime_to) { - if (isset($starttime_to)) { - $to = Carbon::createFromDate($starttime_to); - - return $query->where('starttime', '<=', $starttime_to); - } - } - ); - })->indicateUsing(function (array $data): array { - $indicators = []; - - if ($data['starttime_from'] ?? null) { - $starttime_from = $data['starttime_from']; - if ($starttime_from ?? null) { - $text = Carbon::parse($starttime_from)->format('d. M. Y'); - $indicators[] = Indicator::make('From: '.$text)->removeField('starttime_from')->color('info'); - } - } - if ($data['starttime_to'] ?? null) { - $starttime_to = $data['starttime_to']; - if ($starttime_to ?? null) { - $text = Carbon::parse($starttime_to)->format('d. M. Y'); - $indicators[] = Indicator::make('To: '.$text)->removeField('starttime_to')->color('success'); - } - } - - if ($data['country_id'] ?? null) { - $country_id = $data['country_id']; - if ($country_id ?? null) { - $text = Country::where('id', $country_id)->value('name'); - $indicators[] = Indicator::make('Country: '.$text)->removeField('country_id'); - } - } - - // if ($data['competition_group_id'] ?? null) { - // $competition_groups = CompetitionGroup::whereIn('id', $data['competition_group_id'])->pluck('name')->toArray(); - // if (count($competition_groups) > 1) { - // $indicators[] = Indicator::make(implode(', ', $competition_groups))->removeField('competition_group_id')->color('info'); - // } else { - // $indicators[] = Indicator::make($competition_groups[0])->removeField('competition_group_id')->color('info')->color('info'); - // } - // } - - return $indicators; - }), - - ], layout: FiltersLayout::AboveContent) - ->deferFilters(false) - ->hiddenFilterIndicators(false) - ->persistFiltersInSession() - ->recordActions([ - EditAction::make(), - ]) - ->toolbarActions([ - BulkActionGroup::make([ - DeleteBulkAction::make(), - ]), - ]); - } -} diff --git a/app/Filament/Clusters/Statistic/StatisticCluster.php b/app/Filament/Clusters/Statistic/StatisticCluster.php deleted file mode 100644 index d4a19b81..00000000 --- a/app/Filament/Clusters/Statistic/StatisticCluster.php +++ /dev/null @@ -1,16 +0,0 @@ -table($table)->count(); + self::$benchmarkStartTime = microtime(true); + self::$benchmarkStartMemory = memory_get_usage(); + DB::connection('mongodb')->enableQueryLog(); + } + + public static function end(string $table): void + { + $executionTime = microtime(true) - self::$benchmarkStartTime; + $memoryUsage = round((memory_get_usage() - self::$benchmarkStartMemory) / 1024 / 1024, 2); + + $logs = DB::connection('mongodb')->getQueryLog(); + $queriesCount = \count($logs); + + // Get row count after we've stopped tracking queries + $rowDiff = DB::connection('mongodb')->table($table)->count() - self::$startRowCount; + + $formattedTime = match (true) { + $executionTime >= 60 => sprintf('%dm %ds', floor($executionTime / 60), $executionTime % 60), + $executionTime >= 1 => round($executionTime, 2).'s', + default => round($executionTime * 1000).'ms', + }; + + $str = '⚡ TIME: '.$formattedTime; + $str .= '
MEM: '.$memoryUsage.'MB'; + $str .= '
SQL: '.number_format($queriesCount); + $str .= '
ROWS: '.number_format($rowDiff); + + $recipient = auth()->user(); + + Notification::make() + ->success() + ->title('Import compleated') + ->body(new HtmlString($str)) + ->sendToDatabase($recipient); + } +} diff --git a/app/Filament/Imports/Helper/ImportEvents.php b/app/Filament/Imports/Helper/ImportEvents.php new file mode 100644 index 00000000..8c477e84 --- /dev/null +++ b/app/Filament/Imports/Helper/ImportEvents.php @@ -0,0 +1,59 @@ +format('Y-m-d H:i:s'); + + SimpleExcelReader::create(storage_path('app/public/'.$filePath)) + ->getRows() + ->chunk(1000) + ->each(function (LazyCollection $chunk) use ($now, $sport_id) { + + foreach ($chunk as $row) { + + $row = array_values($row); + + StatistikoEvent::where('sport_id', $sport_id) + ->where('league_id', $row[0]) + ->where('home_id', $row[1]) + ->where('away_id', $row[2]) + ->where('starttime', Carbon::parse($row[3])->toIso8601String()) + ->update( + [ + 'season' => $row[4], + 'gls_h' => $row[5], + 'gls_a' => $row[6], + 'crn_h' => $row[7], + 'crn_a' => $row[8], + 'fls_h' => $row[9], + 'fls_a' => $row[10], + 'ofs_h' => $row[11], + 'ofs_a' => $row[12], + 'snt_h' => $row[13], + 'snt_a' => $row[14], + 'tsh_h' => $row[15], + 'tsh_a' => $row[16], + 'crd_h' => $row[17], + 'crd_a' => $row[18], + 'expgoalshome_value' => $row[19], + 'expgoalsaway_value' => $row[20], + 'exp_handicap' => $row[21], + 'exp_total' => $row[22], + 'created_at' => $now, + 'updated_at' => $now, + ], + ['upsert' => true], + ); + } + }); + } +} diff --git a/app/Filament/Imports/Helper/ImportFixtures.php b/app/Filament/Imports/Helper/ImportFixtures.php new file mode 100644 index 00000000..9c037df7 --- /dev/null +++ b/app/Filament/Imports/Helper/ImportFixtures.php @@ -0,0 +1,45 @@ +getRows() + ->chunk(200) + ->each(function (LazyCollection $chunk) use ($sport_id) { + + foreach ($chunk as $row) { + + $row = array_values($row); + + /* + 0 => 87 + 1 => 3614 + 2 => 1491 + 3 => DateTimeImmutable @1777334400 {#2722 ▶} + 4 => 0.90433699503783 + 5 => 1.035168989838 + */ + + StatisticFixtures::updateOrCreate( + [ + 'sport_id' => (int) $sport_id, + 'league_id' => (int) $row[0], + 'home_id' => (int) $row[1], + 'away_id' => (int) $row[2], + 'starttime' => Carbon::parse($row[3])->toDateString(), + ], + ['exp_home' => (float) $row[4], 'exp_away' => (float) $row[5]] + ); + } + }); + } +} diff --git a/app/Filament/Imports/Helper/ImportPlayer.php b/app/Filament/Imports/Helper/ImportPlayer.php new file mode 100644 index 00000000..7c73bb64 --- /dev/null +++ b/app/Filament/Imports/Helper/ImportPlayer.php @@ -0,0 +1,52 @@ +format('Y-m-d H:i:s'); + + SimpleExcelReader::create(storage_path('app/public/'.$filePath)) + ->getRows() + ->chunk(1000) + ->each(function (LazyCollection $chunk) use ($now) { + + foreach ($chunk as $row) { + + $row = array_values($row); + + StatistikoPlayer::where('player_id', $row[0]) + ->update( + [ + 'name' => $row[1], + 'birthdate' => Carbon::parse($row[2])->toDateString(), + 'height' => $row[3], + 'nationality' => $row[4], + 'position' => $row[5], + 'sport_radar_id' => $row[6], + 'id_squadra_tm' => $row[7], + 'full_name' => $row[8], + 'no_specialities' => $row[9], + 'rigorist' => $row[10], + 'national_team_id' => $row[11], + 'last_update' => Carbon::parse($row[12])->toDateString(), + 'league_code' => $row[13], + 'team_code' => $row[14], + 'team_name' => $row[15], + 'created_at' => $now, + 'updated_at' => $now, + ], + ['upsert' => true], + ); + + } + }); + } +} diff --git a/app/Filament/Imports/StatistikoEventImporter.php b/app/Filament/Imports/StatistikoEventImporter.php deleted file mode 100644 index fb059ef1..00000000 --- a/app/Filament/Imports/StatistikoEventImporter.php +++ /dev/null @@ -1,104 +0,0 @@ -where('sport_id', $this->data['sport_id']) - ->where('league_id', $this->data['league_id']) - ->where('home_id', $this->data['home_id']) - ->where('away_id', $this->data['away_id']) - ->where('starttime', $this->data['starttime']) - ->first(); - if ($res) { - return StatistikoEvent::find($res->id); - } else { - return new StatistikoEvent; - } - } - - public static function getColumns(): array - { - return [ - ImportColumn::make('sport_id')->numeric()->guess(['SPORT']), - ImportColumn::make('league_id')->numeric()->guess(['LEAGUE']), - ImportColumn::make('home_id')->numeric()->guess(['HOME']), - ImportColumn::make('away_id')->numeric()->guess(['AWAY']), - ImportColumn::make('starttime') - ->guess(['STARTTIME']) - ->castStateUsing(function ($state) { - return Carbon::parse($state); - }), - ImportColumn::make('season')->guess(['SEASON']), - ImportColumn::make('gls_h')->numeric(), - ImportColumn::make('gls_a')->numeric(), - ImportColumn::make('crn_h')->numeric(), - ImportColumn::make('fls_h')->numeric(), - ImportColumn::make('ofs_h')->numeric(), - ImportColumn::make('snt_h')->numeric(), - ImportColumn::make('tsh_h')->numeric(), - ImportColumn::make('crd_h')->numeric(), - ImportColumn::make('crn_a')->numeric(), - ImportColumn::make('fls_a')->numeric(), - ImportColumn::make('ofs_a')->numeric(), - ImportColumn::make('snt_a')->numeric(), - ImportColumn::make('tsh_a')->numeric(), - ImportColumn::make('crd_a')->numeric(), - ImportColumn::make('expgoalshome_value') - ->castStateUsing(function (string $state) { - if (blank($state)) { - return null; - } - - return (float) str_replace(',', '.', $state); - }), - ImportColumn::make('expgoalsaway_value') - ->castStateUsing(function (string $state) { - if (blank($state)) { - return null; - } - - return str_replace(',', '.', $state); - }), - ImportColumn::make('exp_handicap') - ->castStateUsing(function (string $state) { - if (blank($state)) { - return null; - } - - return str_replace(',', '.', $state); - }), - ImportColumn::make('exp_total') - ->castStateUsing(function (string $state) { - if (blank($state)) { - return null; - } - - return str_replace(',', '.', $state); - }), - ]; - } - - public static function getCompletedNotificationBody(Import $import): string - { - $body = 'Your events import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.'; - - if ($failedRowsCount = $import->getFailedRowsCount()) { - $body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.'; - } - - return $body; - } -} diff --git a/app/Filament/Imports/StatistikoImporter.php b/app/Filament/Imports/StatistikoImporter.php index ffd120ce..3cc88949 100644 --- a/app/Filament/Imports/StatistikoImporter.php +++ b/app/Filament/Imports/StatistikoImporter.php @@ -2,15 +2,11 @@ namespace App\Filament\Imports; -use App\Models\StatistikoEvent; +use App\Filament\Imports\Helper\ImportBenchmark; +use App\Filament\Imports\Helper\ImportEvents; +use App\Filament\Imports\Helper\ImportFixtures; +use App\Filament\Imports\Helper\ImportPlayer; use App\Models\StatistikoImport; -use App\Models\StatistikoPlayer; -use Filament\Notifications\Notification; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\HtmlString; -use Illuminate\Support\LazyCollection; -use Spatie\SimpleExcel\SimpleExcelReader; class StatistikoImporter { @@ -33,14 +29,17 @@ class StatistikoImporter $filePath = $record['file_name']; $sport_id = $record['sport_id']; - self::startBenchmark($table); + ImportBenchmark::start($table); switch ($record['data_type']) { case 'player': - self::importPlayer($filePath); + ImportPlayer::run($filePath); break; case 'events': - self::importEvents($filePath, $sport_id); + ImportEvents::run($filePath, $sport_id); + break; + case 'fixtures': + ImportFixtures::run($filePath, $sport_id); break; case 'events_player': break; @@ -48,134 +47,6 @@ class StatistikoImporter StatistikoImport::where('id', $record->id)->update(['has_run' => 1]); - self::endBenchmark($table); - } - - protected static function startBenchmark(string $table): void - { - self::$startRowCount = DB::connection('mongodb')->table($table)->count(); - self::$benchmarkStartTime = microtime(true); - self::$benchmarkStartMemory = memory_get_usage(); - DB::connection('mongodb')->enableQueryLog(); - } - - protected static function endBenchmark(string $table): void - { - $executionTime = microtime(true) - self::$benchmarkStartTime; - $memoryUsage = round((memory_get_usage() - self::$benchmarkStartMemory) / 1024 / 1024, 2); - - $logs = DB::connection('mongodb')->getQueryLog(); - $queriesCount = \count($logs); - - // Get row count after we've stopped tracking queries - $rowDiff = DB::connection('mongodb')->table($table)->count() - self::$startRowCount; - - $formattedTime = match (true) { - $executionTime >= 60 => sprintf('%dm %ds', floor($executionTime / 60), $executionTime % 60), - $executionTime >= 1 => round($executionTime, 2).'s', - default => round($executionTime * 1000).'ms', - }; - - $str = '⚡ TIME: '.$formattedTime; - $str .= '
MEM: '.$memoryUsage.'MB'; - $str .= '
SQL: '.number_format($queriesCount); - $str .= '
ROWS: '.number_format($rowDiff); - - $recipient = auth()->user(); - - Notification::make() - ->success() - ->title('Import compleated') - ->body(new HtmlString($str)) - ->sendToDatabase($recipient); - } - - private static function importPlayer(string $filePath): void - { - $now = now()->format('Y-m-d H:i:s'); - - SimpleExcelReader::create(storage_path('app/public/'.$filePath)) - ->getRows() - ->chunk(1000) - ->each(function (LazyCollection $chunk) use ($now) { - - foreach ($chunk as $row) { - - $row = array_values($row); - - StatistikoPlayer::where('player_id', $row[0]) - ->update( - [ - 'name' => $row[1], - 'birthdate' => Carbon::parse($row[2])->toDateString(), - 'height' => $row[3], - 'nationality' => $row[4], - 'position' => $row[5], - 'sport_radar_id' => $row[6], - 'id_squadra_tm' => $row[7], - 'full_name' => $row[8], - 'no_specialities' => $row[9], - 'rigorist' => $row[10], - 'national_team_id' => $row[11], - 'last_update' => Carbon::parse($row[12])->toDateString(), - 'league_code' => $row[13], - 'team_code' => $row[14], - 'team_name' => $row[15], - 'created_at' => $now, - 'updated_at' => $now, - ], - ['upsert' => true], - ); - - } - }); - } - - private static function importEvents(string $filePath, int $sport_id): void - { - $now = now()->format('Y-m-d H:i:s'); - - SimpleExcelReader::create(storage_path('app/public/'.$filePath)) - ->getRows() - ->chunk(1000) - ->each(function (LazyCollection $chunk) use ($now, $sport_id) { - - foreach ($chunk as $row) { - - $row = array_values($row); - - StatistikoEvent::where('sport_id', $sport_id) - ->where('league_id', $row[0]) - ->where('home_id', $row[1]) - ->where('away_id', $row[2]) - ->where('starttime', Carbon::parse($row[3])->toIso8601String()) - ->update( - [ - 'season' => $row[4], - 'gls_h' => $row[5], - 'gls_a' => $row[6], - 'crn_h' => $row[7], - 'crn_a' => $row[8], - 'fls_h' => $row[9], - 'fls_a' => $row[10], - 'ofs_h' => $row[11], - 'ofs_a' => $row[12], - 'snt_h' => $row[13], - 'snt_a' => $row[14], - 'tsh_h' => $row[15], - 'tsh_a' => $row[16], - 'crd_h' => $row[17], - 'crd_a' => $row[18], - 'expgoalshome_value' => $row[19], - 'expgoalsaway_value' => $row[20], - 'exp_handicap' => $row[21], - 'exp_total' => $row[22], - 'created_at' => $now, - 'updated_at' => $now, - ], - ['upsert' => true], - ); - } - }); + ImportBenchmark::end($table); } } diff --git a/app/Filament/Imports/StatistikoPlayerImporter.php b/app/Filament/Imports/StatistikoPlayerImporter.php deleted file mode 100644 index 559271d2..00000000 --- a/app/Filament/Imports/StatistikoPlayerImporter.php +++ /dev/null @@ -1,130 +0,0 @@ -where('id_giocatore', $this->data['id_giocatore']) - ->first(); - if ($res) { - return StatistikoPlayer::find($res->id); - } else { - return new StatistikoPlayer; - } - } - - public static function getColumns(): array - { - return [ - ImportColumn::make('id_giocatore') - ->label('ID GIOCATORE'), - ImportColumn::make('giocatore') - ->label('GIOCATORE') - ->castStateUsing(function (string $state): ?string { - if (blank($state)) { - return null; - } - - return (string) trim($state); - }), - ImportColumn::make('data_di_nascita') - ->label('DATA DI NASCITA') - ->castStateUsing(function ($state) { - if (blank($state)) { - return null; - } - - return Carbon::parse($state); - }), - ImportColumn::make('altezza') - ->label('ALTEZZA') - ->numeric(), - ImportColumn::make('nazionalita') - ->label('NAZIONALITA') - ->castStateUsing(function (string $state): ?string { - return (string) trim($state); - }), - ImportColumn::make('posizione') - ->label('POSIZIONE') - ->castStateUsing(function (string $state): ?string { - if (blank($state)) { - return null; - } - - return (string) trim($state); - }), - ImportColumn::make('sport_radar_id') - ->label('SPORT RADAR ID'), - ImportColumn::make('id_squadra_tm') - ->label('ID SQUADRA TM'), - ImportColumn::make('nome_completo') - ->label('NOME COMPLETO') - ->castStateUsing(function (?string $state): ?string { - if (blank($state)) { - return null; - } - - return (string) trim($state); - }), - ImportColumn::make('rigorista') - ->label('RIGORISTA') - ->castStateUsing(function (?string $state): ?string { - if (blank($state)) { - return null; - } - - return (string) trim($state); - }), - ImportColumn::make('national_team_id') - ->label('NATIONAL TEAM ID'), - ImportColumn::make('data_aggiornamento') - ->label('DATA AGGIORNAMENTO') - ->castStateUsing(function ($state) { - return Carbon::parse($state); - }), - ImportColumn::make('league_code') - ->label('LEAGUE CODE') - ->castStateUsing(function (string $state): ?string { - return (string) trim($state); - }), - ImportColumn::make('team_code') - ->label('TEAM CODE') - ->castStateUsing(function (string $state): ?string { - return (string) trim($state); - }), - ImportColumn::make('team_name') - ->label('TEAM NAME') - ->castStateUsing(function (string $state): ?string { - return (string) trim($state); - }), - ]; - } - - public static function getCompletedNotificationBody(Import $import): string - { - $body = 'Your player import has completed and '.Number::format($import->successful_rows).' '.str('row')->plural($import->successful_rows).' imported.'; - - if ($failedRowsCount = $import->getFailedRowsCount()) { - $body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to import.'; - } - - return $body; - } -} diff --git a/app/Filament/Pages/Statistic.php b/app/Filament/Pages/Statistic.php new file mode 100644 index 00000000..053f5382 --- /dev/null +++ b/app/Filament/Pages/Statistic.php @@ -0,0 +1,127 @@ +components([ + Section::make() + ->schema([ + Select::make('league_id') + ->prefixIcon('phosphor-trophy') + ->options(function () { + $res = StatisticFixtures::where('sport_id', 1) + ->groupBy('league_id') + ->pluck('league_id')->toArray(); + + return CompetitionGroup::query()->whereIn('id', $res)->orderBy('name')->pluck('name', 'id'); + }) + ->preload() + ->searchable() + ->live() + ->label('League'), + Select::make('team_id') + ->prefixIcon('icon-team') + ->options(function (Get $get) { + if ($get('league_id')) { + $res = StatisticFixtures::where('league_id', $get('league_id'))->get(); + $arr = []; + foreach ($res as $value) { + $arr[] = $value->home_id; + $arr[] = $value->away_id; + } + + $arr = array_unique($arr); + + return Element::query()->whereIn('id', $arr)->orderBy('name')->pluck('name', 'id'); + } + }) + ->preload() + ->searchable() + ->label('Team'), + DatePicker::make('startDate'), + DatePicker::make('endDate'), + ])->columns(4) + ->columnSpanFull(), + Section::make() + ->schema([ + Select::make('palyer_id') + ->label('Player') + ->prefixIcon('icon-player') + ->options(function () { + $res = StatisticFixtures::where('sport_id', 1) + ->groupBy('league_id') + ->pluck('league_id')->toArray(); + + return CompetitionGroup::query()->whereIn('id', $res)->orderBy('name')->pluck('name', 'id'); + }) + ->preload() + ->searchable() + ->live(), + Select::make('team_position') + ->prefixIcon('phosphor-map-pin') + ->default('home') + ->selectablePlaceholder(false) + ->options([ + 'home' => 'Home', + 'away' => 'Away', + 'all' => 'All', + ]), + Select::make('similar_past_matches') + ->prefixIcon('phosphor-align-center-horizontal') + ->default('none') + ->selectablePlaceholder(false) + ->options([ + 'none' => 'None', + 'league' => 'League', + 'all' => 'All Database', + ]), + Select::make('statistic_type') + ->prefixIcon('phosphor-cell-signal-full') + ->default('none') + ->selectablePlaceholder(false) + ->options([ + 'gls' => 'Gouls', + 'crn' => 'Corner', + 'fls' => 'Fouls commited', + 'ofs' => 'Offside', + 'crd' => 'Cards', + 'snt' => 'Shot on target', + 'tsh' => 'Total shots', + ]), + ])->columns(4) + ->columnSpanFull(), + Slider::make('accuracy') + ->default(0.5) + ->decimalPlaces(1) + ->tooltips() + ->fillTrack() + ->range(minValue: 0, maxValue: 1) + ->pips(), + ]); + } +} diff --git a/app/Filament/Resources/Events/Tables/EventsTable.php b/app/Filament/Resources/Events/Tables/EventsTable.php index 9ca847aa..0d871581 100644 --- a/app/Filament/Resources/Events/Tables/EventsTable.php +++ b/app/Filament/Resources/Events/Tables/EventsTable.php @@ -37,7 +37,6 @@ use Filament\Tables\Enums\FiltersLayout; use Filament\Tables\Filters\Filter; use Filament\Tables\Filters\Indicator; use Filament\Tables\Table; -use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; diff --git a/app/Filament/Widgets/StatisticChart.php b/app/Filament/Widgets/StatisticChart.php new file mode 100644 index 00000000..4f930fde --- /dev/null +++ b/app/Filament/Widgets/StatisticChart.php @@ -0,0 +1,22 @@ + 'date', + ]; + } + + public function sport() + { + return $this->belongsTo(Sport::class)->where('is_active', 1); + } + + public function league() + { + return $this->belongsTo(CompetitionGroup::class); + } + + public function home() + { + return $this->belongsTo(Element::class); + } + + public function away() + { + return $this->belongsTo(Element::class); + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 157688dc..d8742105 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -4,6 +4,8 @@ namespace App\Providers\Filament; // use Agencetwogether\HooksHelper\HooksHelperPlugin; use App\Filament\Exports\BetfairExporter; +use App\Filament\Pages\Statistic; +use App\Filament\Widgets\StatisticChart; use App\Http\Middleware\RecordUserActivity; use Awcodes\LightSwitch\Enums\Alignment; use Awcodes\LightSwitch\LightSwitchPlugin; @@ -21,8 +23,6 @@ use Filament\Http\Middleware\DispatchServingFilamentEvent; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Enums\Width; -use Filament\Widgets\AccountWidget; -use Filament\Widgets\FilamentInfoWidget; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Database\Eloquent\Builder; @@ -87,9 +87,6 @@ class AdminPanelProvider extends PanelProvider ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages') ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets') ->discoverClusters(in: app_path('Filament/Clusters'), for: 'App\\Filament\\Clusters') - ->pages([ - // Dashboard::class, - ]) ->maxContentWidth(Width::ScreenTwoExtraLarge) ->breadcrumbs(false) ->topNavigation() @@ -100,9 +97,11 @@ class AdminPanelProvider extends PanelProvider ->url(fn (): string => EditProfilePage::getUrl()) ->icon('phosphor-user-circle-fill'), ]) + ->pages([ + Statistic::class, + ]) ->widgets([ - AccountWidget::class, - FilamentInfoWidget::class, + StatisticChart::class, ]) ->middleware([ EncryptCookies::class, diff --git a/database/migrations/2026_04_28_075531_create_statistic_fixtures_table.php b/database/migrations/2026_04_28_075531_create_statistic_fixtures_table.php new file mode 100644 index 00000000..0af4a43d --- /dev/null +++ b/database/migrations/2026_04_28_075531_create_statistic_fixtures_table.php @@ -0,0 +1,34 @@ +id(); + $table->integer('sport_id')->index(); + $table->integer('league_id')->index(); + $table->integer('home_id')->index(); + $table->integer('away_id')->index(); + $table->dateTime('starttime')->index(); + $table->decimal('exp_home', 15, 14)->default(0); + $table->decimal('exp_away', 15, 14)->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('statistic_fixtures'); + } +}; diff --git a/resources/views/filament/pages/statistic.blade.php b/resources/views/filament/pages/statistic.blade.php new file mode 100644 index 00000000..166e4fa1 --- /dev/null +++ b/resources/views/filament/pages/statistic.blade.php @@ -0,0 +1,2 @@ + +