From 356dabb0c1f42d1d1e3a0f45cd5fc34ce36aab1a Mon Sep 17 00:00:00 2001 From: salam Date: Thu, 5 Mar 2026 11:56:18 +0100 Subject: [PATCH] download countries, teams/player and tournaments for txodds --- .../Tables/CompetitionGroupsTable.php | 22 +++++++++++ .../Elements/Tables/ElementsTable.php | 18 +++++++++ .../Resources/Bettypes/Pages/ListBettypes.php | 8 ---- .../Bettypes/Tables/BettypesTable.php | 28 ++++++++++++-- .../Resources/Countries/CountryResource.php | 20 ++++++++++ .../Tables/TxoddsMarketsTable.php | 1 - .../Exports/CompetitionGroupExporter.php | 37 +++++++++++++++++++ app/Filament/Exports/CountryExporter.php | 33 +++++++++++++++++ app/Filament/Exports/ElementExporter.php | 36 ++++++++++++++++++ app/Providers/Filament/AdminPanelProvider.php | 2 +- composer.lock | 6 +-- ...2026_03_05_102039_create_imports_table.php | 35 ++++++++++++++++++ ...2026_03_05_102040_create_exports_table.php | 35 ++++++++++++++++++ ...102041_create_failed_import_rows_table.php | 30 +++++++++++++++ 14 files changed, 294 insertions(+), 17 deletions(-) create mode 100644 app/Filament/Exports/CompetitionGroupExporter.php create mode 100644 app/Filament/Exports/CountryExporter.php create mode 100644 app/Filament/Exports/ElementExporter.php create mode 100644 database/migrations/2026_03_05_102039_create_imports_table.php create mode 100644 database/migrations/2026_03_05_102040_create_exports_table.php create mode 100644 database/migrations/2026_03_05_102041_create_failed_import_rows_table.php diff --git a/app/Filament/Clusters/DynamicData/Resources/CompetitionGroups/Tables/CompetitionGroupsTable.php b/app/Filament/Clusters/DynamicData/Resources/CompetitionGroups/Tables/CompetitionGroupsTable.php index 6b297247..fb546022 100644 --- a/app/Filament/Clusters/DynamicData/Resources/CompetitionGroups/Tables/CompetitionGroupsTable.php +++ b/app/Filament/Clusters/DynamicData/Resources/CompetitionGroups/Tables/CompetitionGroupsTable.php @@ -2,6 +2,7 @@ namespace App\Filament\Clusters\DynamicData\Resources\CompetitionGroups\Tables; +use App\Filament\Exports\CompetitionGroupExporter; use App\Models\CompetitionGroupProvider; use App\Models\Country; use App\Models\ElementGroup; @@ -13,13 +14,17 @@ use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteAction; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; +use Filament\Actions\ExportAction; +use Filament\Actions\Exports\Enums\ExportFormat; use Filament\Notifications\Notification; +use Filament\Schemas\Components\Utilities\Get; use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\SelectColumn; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Enums\FiltersLayout; use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Builder; class CompetitionGroupsTable { @@ -53,6 +58,8 @@ class CompetitionGroupsTable ])->filters([ SelectFilter::make('sport_id') ->label('Sport') + ->default(1) + ->selectablePlaceholder(false) ->options(fn () => Sport::where('is_active', 1)->pluck('name', 'id')->toArray()), SelectFilter::make('country_id') ->searchable() @@ -80,6 +87,21 @@ class CompetitionGroupsTable ])->iconButton() ->icon('phosphor-dots-three-vertical'), ]) + ->headerActions([ + ExportAction::make() + ->modalHeading('Download Groups') + ->label('Export') + ->icon('phosphor-download') + ->modalDescription('This will download the TxOdds tournaments with "sport_id" and "country_id" for the sport selected.') + ->modalIcon('phosphor-microsoft-excel-logo') + ->exporter(CompetitionGroupExporter::class) + ->columnMapping(false) + ->modifyQueryUsing(fn (Builder $query, Get $get) => $query->where('spoirt_id', $get('sport_id'))) + ->formats([ + ExportFormat::Xlsx, + ExportFormat::Csv, + ]), + ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make(), diff --git a/app/Filament/Clusters/DynamicData/Resources/Elements/Tables/ElementsTable.php b/app/Filament/Clusters/DynamicData/Resources/Elements/Tables/ElementsTable.php index 138fd381..e621cc4a 100644 --- a/app/Filament/Clusters/DynamicData/Resources/Elements/Tables/ElementsTable.php +++ b/app/Filament/Clusters/DynamicData/Resources/Elements/Tables/ElementsTable.php @@ -2,6 +2,7 @@ namespace App\Filament\Clusters\DynamicData\Resources\Elements\Tables; +use App\Filament\Exports\ElementExporter; use App\Models\CompetitionGroup; use App\Models\Country; use App\Models\ElementGroup; @@ -13,6 +14,8 @@ use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteAction; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; +use Filament\Actions\ExportAction; +use Filament\Actions\Exports\Enums\ExportFormat; use Filament\Forms\Components\Select; use Filament\Notifications\Notification; use Filament\Schemas\Components\Utilities\Get; @@ -103,6 +106,21 @@ class ElementsTable ])->iconButton() ->icon('phosphor-dots-three-vertical'), ]) + ->headerActions([ + ExportAction::make() + ->modalHeading('Download Teams/Player') + ->label('Export') + ->icon('phosphor-download') + ->modalDescription('This will download the TxOdds Teams/Player with "sport_id" and "country_id" for the sport selected.') + ->modalIcon('phosphor-microsoft-excel-logo') + ->exporter(ElementExporter::class) + ->columnMapping(false) + ->modifyQueryUsing(fn (Builder $query, Get $get) => $query->where('spoirt_id', $get('sport_id'))) + ->formats([ + ExportFormat::Xlsx, + ExportFormat::Csv, + ]), + ]) ->toolbarActions([ BulkActionGroup::make([ DeleteBulkAction::make(), diff --git a/app/Filament/Clusters/StaticData/Resources/Bettypes/Pages/ListBettypes.php b/app/Filament/Clusters/StaticData/Resources/Bettypes/Pages/ListBettypes.php index bb42bc59..c9119c38 100644 --- a/app/Filament/Clusters/StaticData/Resources/Bettypes/Pages/ListBettypes.php +++ b/app/Filament/Clusters/StaticData/Resources/Bettypes/Pages/ListBettypes.php @@ -3,17 +3,9 @@ namespace App\Filament\Clusters\StaticData\Resources\Bettypes\Pages; use App\Filament\Clusters\StaticData\Resources\Bettypes\BettypeResource; -use Filament\Actions\CreateAction; use Filament\Resources\Pages\ListRecords; class ListBettypes extends ListRecords { protected static string $resource = BettypeResource::class; - - protected function getHeaderActions(): array - { - return [ - CreateAction::make(), - ]; - } } diff --git a/app/Filament/Clusters/StaticData/Resources/Bettypes/Tables/BettypesTable.php b/app/Filament/Clusters/StaticData/Resources/Bettypes/Tables/BettypesTable.php index e3593dfa..c25494cc 100644 --- a/app/Filament/Clusters/StaticData/Resources/Bettypes/Tables/BettypesTable.php +++ b/app/Filament/Clusters/StaticData/Resources/Bettypes/Tables/BettypesTable.php @@ -4,11 +4,14 @@ namespace App\Filament\Clusters\StaticData\Resources\Bettypes\Tables; use App\Models\Sport; use Filament\Actions\EditAction; +use Filament\Forms\Components\Select; use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Enums\FiltersLayout; +use Filament\Tables\Filters\Filter; use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Builder; class BettypesTable { @@ -38,11 +41,28 @@ class BettypesTable IconColumn::make('q1')->boolean()->trueIcon('phosphor-check-circle-fill')->falseIcon('phosphor-circle')->falseColor('gray'), ]) ->filters([ - SelectFilter::make('sport_id') - ->label('Sport') - ->options(fn () => Sport::where('is_active', 1)->pluck('name', 'id') - ->toArray()), + Filter::make('sport_id') + ->schema([ + Select::make(name: 'sport_id') + ->placeholder('Filter Sport') + ->hiddenLabel() + ->preload() + ->options(fn () => Sport::where('is_active', 1)->pluck('name', 'id')) + ->live(), + ]) + ->query(function (Builder $query, array $data): Builder { + return $query + ->when( + $data['sport_id'], + fn (Builder $query, $sport_id) => $query->where('sport_id', $sport_id) + ); + }), + // SelectFilter::make('sport_id') + // ->label('') + // ->options(fn () => Sport::where('is_active', 1)->pluck('name', 'id') + // ->toArray()), ], layout: FiltersLayout::AboveContent) + ->deferFilters(false) ->recordActions([ EditAction::make()->hiddenLabel(), ]); diff --git a/app/Filament/Clusters/StaticData/Resources/Countries/CountryResource.php b/app/Filament/Clusters/StaticData/Resources/Countries/CountryResource.php index 30b95a84..c8421235 100644 --- a/app/Filament/Clusters/StaticData/Resources/Countries/CountryResource.php +++ b/app/Filament/Clusters/StaticData/Resources/Countries/CountryResource.php @@ -4,8 +4,11 @@ namespace App\Filament\Clusters\StaticData\Resources\Countries; use App\Filament\Clusters\StaticData\Resources\Countries\Pages\ManageCountries; use App\Filament\Clusters\StaticData\StaticDataCluster; +use App\Filament\Exports\CountryExporter; use App\Models\Country; use Filament\Actions\EditAction; +use Filament\Actions\ExportAction; +use Filament\Actions\Exports\Enums\ExportFormat; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; use Filament\Resources\Resource; @@ -13,6 +16,7 @@ use Filament\Schemas\Schema; use Filament\Support\Enums\IconSize; use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Enums\FiltersLayout; use Filament\Tables\Filters\Filter; use Filament\Tables\Filters\TernaryFilter; use Filament\Tables\Table; @@ -77,6 +81,22 @@ class CountryResource extends Resource Filter::make('icon') ->label('Missing Icons') ->query(fn (Builder $query): Builder => $query->whereNull('icon')), + ], layout: FiltersLayout::AboveContent) + ->deferFilters(false) + ->headerActions([ + ExportAction::make() + ->modalHeading('Download Countries') + ->label('Export') + ->icon('phosphor-download') + ->modalDescription('This will download the Countries (id, name) from TxOdds.') + ->modalIcon('phosphor-microsoft-excel-logo') + ->exporter(CountryExporter::class) + ->columnMapping(false) + ->modifyQueryUsing(fn (Builder $query) => $query->where('is_country', true)) + ->formats([ + ExportFormat::Xlsx, + ExportFormat::Csv, + ]), ]) ->recordActions([ EditAction::make()->hiddenLabel(), diff --git a/app/Filament/Clusters/StaticData/Resources/TxoddsMarkets/Tables/TxoddsMarketsTable.php b/app/Filament/Clusters/StaticData/Resources/TxoddsMarkets/Tables/TxoddsMarketsTable.php index 36b9db69..b82c6da7 100644 --- a/app/Filament/Clusters/StaticData/Resources/TxoddsMarkets/Tables/TxoddsMarketsTable.php +++ b/app/Filament/Clusters/StaticData/Resources/TxoddsMarkets/Tables/TxoddsMarketsTable.php @@ -29,7 +29,6 @@ class TxoddsMarketsTable Select::make(name: 'sport_market') ->placeholder('Filter Sport') ->hiddenLabel() - ->label('') ->preload() ->options(function () { return [ diff --git a/app/Filament/Exports/CompetitionGroupExporter.php b/app/Filament/Exports/CompetitionGroupExporter.php new file mode 100644 index 00000000..c76e2d78 --- /dev/null +++ b/app/Filament/Exports/CompetitionGroupExporter.php @@ -0,0 +1,37 @@ +label('ID'), + ExportColumn::make('name'), + ExportColumn::make('level'), + ExportColumn::make('sport_id'), + ExportColumn::make('country_id'), + ]; + } + + public static function getCompletedNotificationBody(Export $export): string + { + $body = 'Your competition group export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.'; + + if ($failedRowsCount = $export->getFailedRowsCount()) { + $body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.'; + } + + return $body; + } +} diff --git a/app/Filament/Exports/CountryExporter.php b/app/Filament/Exports/CountryExporter.php new file mode 100644 index 00000000..8b86f343 --- /dev/null +++ b/app/Filament/Exports/CountryExporter.php @@ -0,0 +1,33 @@ +successful_rows).' '.str('row')->plural($export->successful_rows).' exported.'; + + if ($failedRowsCount = $export->getFailedRowsCount()) { + $body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.'; + } + + return $body; + } +} diff --git a/app/Filament/Exports/ElementExporter.php b/app/Filament/Exports/ElementExporter.php new file mode 100644 index 00000000..6d7aa40d --- /dev/null +++ b/app/Filament/Exports/ElementExporter.php @@ -0,0 +1,36 @@ +label('ID'), + ExportColumn::make('name'), + ExportColumn::make('sport_id'), + ExportColumn::make('country_id'), + ]; + } + + public static function getCompletedNotificationBody(Export $export): string + { + $body = 'Your element export has completed and '.Number::format($export->successful_rows).' '.str('row')->plural($export->successful_rows).' exported.'; + + if ($failedRowsCount = $export->getFailedRowsCount()) { + $body .= ' '.Number::format($failedRowsCount).' '.str('row')->plural($failedRowsCount).' failed to export.'; + } + + return $body; + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 8ecf5d54..4770df4d 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -48,8 +48,8 @@ class AdminPanelProvider extends PanelProvider ->emailVerification() ->emailChangeVerification() ->databaseNotifications() - ->sidebarCollapsibleOnDesktop() // ->databaseNotificationsPolling('15s') default is 30 + ->sidebarCollapsibleOnDesktop() ->profile(isSimple: false) ->default() ->globalSearch(false) diff --git a/composer.lock b/composer.lock index 3fbce6b4..9b4e9b37 100644 --- a/composer.lock +++ b/composer.lock @@ -13404,12 +13404,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.3" }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" + "platform-dev": [], + "plugin-api-version": "2.6.0" } diff --git a/database/migrations/2026_03_05_102039_create_imports_table.php b/database/migrations/2026_03_05_102039_create_imports_table.php new file mode 100644 index 00000000..1d9c09d8 --- /dev/null +++ b/database/migrations/2026_03_05_102039_create_imports_table.php @@ -0,0 +1,35 @@ +id(); + $table->timestamp('completed_at')->nullable(); + $table->string('file_name'); + $table->string('file_path'); + $table->string('importer'); + $table->unsignedInteger('processed_rows')->default(0); + $table->unsignedInteger('total_rows'); + $table->unsignedInteger('successful_rows')->default(0); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('imports'); + } +}; diff --git a/database/migrations/2026_03_05_102040_create_exports_table.php b/database/migrations/2026_03_05_102040_create_exports_table.php new file mode 100644 index 00000000..6a87ac32 --- /dev/null +++ b/database/migrations/2026_03_05_102040_create_exports_table.php @@ -0,0 +1,35 @@ +id(); + $table->timestamp('completed_at')->nullable(); + $table->string('file_disk'); + $table->string('file_name')->nullable(); + $table->string('exporter'); + $table->unsignedInteger('processed_rows')->default(0); + $table->unsignedInteger('total_rows'); + $table->unsignedInteger('successful_rows')->default(0); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('exports'); + } +}; diff --git a/database/migrations/2026_03_05_102041_create_failed_import_rows_table.php b/database/migrations/2026_03_05_102041_create_failed_import_rows_table.php new file mode 100644 index 00000000..2f77805e --- /dev/null +++ b/database/migrations/2026_03_05_102041_create_failed_import_rows_table.php @@ -0,0 +1,30 @@ +id(); + $table->json('data'); + $table->foreignId('import_id')->constrained()->cascadeOnDelete(); + $table->text('validation_error')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('failed_import_rows'); + } +};