feat: add middleware for cookie encryption, CSRF verification, string trimming, and maintenance request prevention; implement Designer resource management with CRUD pages and permissions

This commit is contained in:
pacnpal
2025-02-26 21:28:02 -05:00
parent 0e61f7d694
commit 2436e8cec6
46 changed files with 4623 additions and 2391 deletions

View File

@@ -0,0 +1,125 @@
<?php
namespace App\Filament\Resources;
use App\Filament\Resources\DesignerResource\Pages;
use App\Filament\Resources\DesignerResource\RelationManagers;
use App\Models\Designer;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
class DesignerResource extends Resource
{
protected static ?string $model = Designer::class;
protected static ?string $navigationIcon = 'heroicon-o-building-office';
protected static ?string $navigationGroup = 'Company Management';
protected static ?int $navigationSort = 1;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Section::make('Basic Information')
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255)
->live(onBlur: true)
->afterStateUpdated(fn ($state, Forms\Set $set) =>
$set('slug', str($state)->slug())),
Forms\Components\TextInput::make('slug')
->required()
->maxLength(255)
->unique(ignoreRecord: true),
Forms\Components\TextInput::make('headquarters')
->maxLength(255),
Forms\Components\DatePicker::make('founded_date')
->label('Founded Date')
->format('Y-m-d'),
])->columns(2),
Forms\Components\Section::make('Additional Details')
->schema([
Forms\Components\TextInput::make('website')
->url()
->prefix('https://')
->maxLength(255),
Forms\Components\RichEditor::make('description')
->columnSpanFull()
->toolbarButtons([
'bold',
'italic',
'link',
'bulletList',
'orderedList',
'h2',
'h3',
]),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('headquarters')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('founded_date')
->date()
->sortable(),
Tables\Columns\TextColumn::make('rides_count')
->counts('rides')
->label('Rides')
->sortable(),
Tables\Columns\TextColumn::make('website')
->searchable()
->url(fn ($state) => str($state)->start('https://')),
Tables\Columns\TextColumn::make('updated_at')
->dateTime()
->sortable()
->toggleable(),
])
->filters([
Tables\Filters\Filter::make('has_rides')
->query(fn (Builder $query) => $query->has('rides'))
->label('Has Rides'),
Tables\Filters\Filter::make('no_rides')
->query(fn (Builder $query) => $query->doesntHave('rides'))
->label('No Rides'),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\ViewAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getRelations(): array
{
return [
RelationManagers\RidesRelationManager::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListDesigners::route('/'),
'create' => Pages\CreateDesigner::route('/create'),
'edit' => Pages\EditDesigner::route('/{record}/edit'),
];
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Filament\Resources\DesignerResource\Pages;
use App\Filament\Resources\DesignerResource;
use Filament\Actions;
use Filament\Resources\Pages\CreateRecord;
class CreateDesigner extends CreateRecord
{
protected static string $resource = DesignerResource::class;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DesignerResource\Pages;
use App\Filament\Resources\DesignerResource;
use Filament\Actions;
use Filament\Resources\Pages\EditRecord;
class EditDesigner extends EditRecord
{
protected static string $resource = DesignerResource::class;
protected function getHeaderActions(): array
{
return [
Actions\DeleteAction::make(),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Filament\Resources\DesignerResource\Pages;
use App\Filament\Resources\DesignerResource;
use Filament\Actions;
use Filament\Resources\Pages\ListRecords;
class ListDesigners extends ListRecords
{
protected static string $resource = DesignerResource::class;
protected function getHeaderActions(): array
{
return [
Actions\CreateAction::make(),
];
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Filament\Resources\DesignerResource\RelationManagers;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;
class RidesRelationManager extends RelationManager
{
protected static string $relationship = 'rides';
protected static ?string $title = 'Rides';
public function form(Form $form): Form
{
return $form
->schema([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('manufacturer_name')
->maxLength(255),
Forms\Components\TextInput::make('model_name')
->maxLength(255),
Forms\Components\DatePicker::make('opened_date')
->label('Opening Date'),
Forms\Components\DatePicker::make('closed_date')
->label('Closing Date')
->after('opened_date'),
Forms\Components\Textarea::make('description')
->columnSpanFull(),
Forms\Components\Toggle::make('is_active')
->label('Active')
->default(true),
]);
}
public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('name')
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('manufacturer_name')
->sortable()
->searchable(),
Tables\Columns\TextColumn::make('opened_date')
->date()
->sortable(),
Tables\Columns\TextColumn::make('closed_date')
->date()
->sortable(),
Tables\Columns\IconColumn::make('is_active')
->boolean()
->sortable(),
])
->filters([
Tables\Filters\TrashedFilter::make(),
])
->headerActions([
Tables\Actions\CreateAction::make(),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
}

47
app/Http/Kernel.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Http\Request;
class Authenticate extends Middleware
{
/**
* Get the path the user should be redirected to when they are not authenticated.
*/
protected function redirectTo(Request $request): ?string
{
return $request->expectsJson() ? null : route('login');
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
protected $except = [
//
];
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance as Middleware;
class PreventRequestsDuringMaintenance extends Middleware
{
protected $except = [
//
];
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
protected $except = [
'current_password',
'password',
'password_confirmation',
];
}

View File

@@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
protected $proxies;
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View File

@@ -0,0 +1,12 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
protected $except = [
//
];
}

View File

@@ -2,26 +2,27 @@
namespace App\Models;
use App\Traits\HasSlugHistory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
class Designer extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array<string>
*/
use HasSlugHistory;
protected $fillable = [
'name',
'slug',
'bio',
'description',
'website',
'founded_date',
'headquarters',
];
protected $casts = [
'founded_date' => 'date',
];
/**
* Boot the model.
*/
protected static function boot()
{
parent::boot();
@@ -33,10 +34,7 @@ class Designer extends Model
});
}
/**
* Get the rides designed by this designer.
*/
public function rides(): HasMany
public function rides()
{
return $this->hasMany(Ride::class);
}

View File

@@ -0,0 +1,68 @@
<?php
namespace App\Policies;
use App\Models\Designer;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class DesignerPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return $user->hasPermissionTo('view designers');
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Designer $designer): bool
{
return $user->hasPermissionTo('view designers');
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return $user->hasPermissionTo('create designers');
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Designer $designer): bool
{
return $user->hasPermissionTo('edit designers');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Designer $designer): bool
{
return $user->hasPermissionTo('delete designers');
}
/**
* Determine whether the user can restore the model.
*/
public function restore(User $user, Designer $designer): bool
{
return $user->hasPermissionTo('restore designers');
}
/**
* Determine whether the user can permanently delete the model.
*/
public function forceDelete(User $user, Designer $designer): bool
{
return $user->hasPermissionTo('force delete designers');
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Providers\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Widgets;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class AdminPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->default()
->id('admin')
->path('admin')
->login()
->colors([
'primary' => Color::Amber,
])
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
Pages\Dashboard::class,
])
->widgets([
Widgets\AccountWidget::class,
Widgets\FilamentInfoWidget::class,
])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
])
->authMiddleware([
Authenticate::class,
])
->resources([
config('filament.resources.namespace') => app_path('Filament/Resources'),
])
->navigationGroups([
'Company Management',
'Attractions',
'Parks',
'User Management',
'System',
])
->brandName('ThrillWiki Admin')
->sidebarCollapsibleOnDesktop();
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Providers\Filament;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Support\Colors\Color;
use Filament\Widgets;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class ModerationPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('moderation')
->path('moderation')
->login()
->colors([
'primary' => Color::Blue,
])
->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources')
->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages')
->pages([
Pages\Dashboard::class,
])
->widgets([
Widgets\AccountWidget::class,
])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
])
->authMiddleware([
Authenticate::class,
])
->navigationGroups([
'Content Moderation',
'User Management',
'Reviews',
'Reports',
])
->brandName('ThrillWiki Moderation')
->sidebarCollapsibleOnDesktop()
->maxContentWidth('full');
}
}

View File

@@ -1,18 +1,24 @@
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
$app = new Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
return $app;

View File

@@ -2,4 +2,5 @@
return [
App\Providers\AppServiceProvider::class,
App\Providers\Filament\AdminPanelProvider::class,
];

View File

@@ -1,29 +1,22 @@
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": [
"laravel",
"framework"
],
"license": "MIT",
"require": {
"php": "^8.2",
"intervention/image": "^3.11",
"laravel/framework": "^11.31",
"laravel/tinker": "^2.9",
"livewire/livewire": "^3.5"
"php": "^8.1",
"filament/filament": "^3.2",
"filament/notifications": "^3.2",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8",
"owen-it/laravel-auditing": "^13.5",
"spatie/laravel-permission": "^6.3"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.1",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.1",
"pestphp/pest": "^3.7",
"pestphp/pest-plugin-laravel": "^3.1"
"fakerphp/faker": "^1.9.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"
},
"autoload": {
"psr-4": {
@@ -43,19 +36,14 @@
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
"@php artisan filament:upgrade"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
"@php artisan key:generate --ansi"
]
},
"extra": {

5113
composer.lock generated

File diff suppressed because it is too large Load Diff

202
config/permission.php Normal file
View File

@@ -0,0 +1,202 @@
<?php
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related pivots other than defaults
*/
'role_pivot_key' => null, // default 'role_id',
'permission_pivot_key' => null, // default 'permission_id',
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
/*
* Change this if you want to use the teams feature and your related model's
* foreign key is other than `team_id`.
*/
'team_foreign_key' => 'team_id',
],
/*
* When set to true, the method for checking permissions will be registered on the gate.
* Set this to false if you want to implement custom logic for checking permissions.
*/
'register_permission_check_method' => true,
/*
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
*/
'register_octane_reset_listener' => false,
/*
* Events will fire when a role or permission is assigned/unassigned:
* \Spatie\Permission\Events\RoleAttached
* \Spatie\Permission\Events\RoleDetached
* \Spatie\Permission\Events\PermissionAttached
* \Spatie\Permission\Events\PermissionDetached
*
* To enable, set to true, and then create listeners to watch these events.
*/
'events_enabled' => false,
/*
* Teams Feature.
* When set to true the package implements teams using the 'team_foreign_key'.
* If you want the migrations to register the 'team_foreign_key', you must
* set this to true before doing the migration.
* If you already did the migration then you must make a new migration to also
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
* (view the latest version of this package's migration file)
*/
'teams' => false,
/*
* The class to use to resolve the permissions team id
*/
'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class,
/*
* Passport Client Credentials Grant
* When set to true the package will use Passports Client to check permissions
*/
'use_passport_client_credentials' => false,
/*
* When set to true, the required permission names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_permission_in_exception' => false,
/*
* When set to true, the required role names are added to exception messages.
* This could be considered an information leak in some contexts, so the default
* setting is false here for optimum safety.
*/
'display_role_in_exception' => false,
/*
* By default wildcard permission lookups are disabled.
* See documentation to understand supported syntax.
*/
'enable_wildcard_permission' => false,
/*
* The class to use for interpreting wildcard permissions.
* If you need to modify delimiters, override the class and specify its name here.
*/
// 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class,
/* Cache-specific settings */
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];

View File

@@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('designers', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('slug')->unique();
$table->text('description')->nullable();
$table->string('website')->nullable();
$table->date('founded_date')->nullable();
$table->string('headquarters')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('designers');
}
};

View File

@@ -0,0 +1,140 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$teams = config('permission.teams');
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
if ($teams && empty($columnNames['team_foreign_key'] ?? null)) {
throw new \Exception('Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // permission id
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
// $table->engine('InnoDB');
$table->bigIncrements('id'); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
$table->unique(['name', 'guard_name']);
}
});
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
};

View File

@@ -0,0 +1,46 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
class DesignerPermissionsSeeder extends Seeder
{
public function run(): void
{
// Create designer permissions
$permissions = [
'view designers',
'create designers',
'edit designers',
'delete designers',
'restore designers',
'force delete designers',
];
foreach ($permissions as $permission) {
Permission::create(['name' => $permission]);
}
// Assign permissions to admin role
$adminRole = Role::firstOrCreate(['name' => 'admin']);
$adminRole->givePermissionTo($permissions);
// Assign permissions to moderator role
$moderatorRole = Role::firstOrCreate(['name' => 'moderator']);
$moderatorRole->givePermissionTo([
'view designers',
'edit designers',
]);
// Assign permissions to editor role
$editorRole = Role::firstOrCreate(['name' => 'editor']);
$editorRole->givePermissionTo([
'view designers',
'create designers',
'edit designers',
]);
}
}

View File

@@ -0,0 +1,108 @@
# Filament Admin Interface Implementation Plan
## Overview
[2025-02-26 20:14]
### Goals
1. Set up Filament PHP admin interface
2. Match Django admin capabilities
3. Implement permission system
4. Create moderation tools
### Technical Requirements
1. Feature Parity
- Match Django admin functionality
- Maintain consistent permission structure
- Support equivalent bulk actions
- Replicate list display and filters
2. Resource Mapping
- Map Django admin models to Filament resources
- Match fieldset structures
- Implement inline form relationships
- Support all Django admin actions
3. Permissions
- Integrate with Laravel permissions
- Match Django's permission model
- Support resource-level access control
- Implement audit trails
## Implementation Strategy
### Phase 1: Core Setup
1. Install Filament PHP
2. Configure basic admin panel
3. Set up resource structure
4. Implement authentication integration
### Phase 2: Resource Implementation
1. Create base resource templates
2. Map model relationships
3. Configure form layouts
4. Set up list views
### Phase 3: Permissions
1. Design permission structure
2. Implement role system
3. Configure access controls
4. Set up audit logging
### Phase 4: Moderation Tools
1. Create moderation panel
2. Implement review workflows
3. Set up notification system
4. Configure action logging
## Technical Details
### Django Admin Reference
- Location: //Users/talor/thrillwiki_django_no_react/
- Study admin configuration
- Map model relationships
- Document customizations
### Laravel Implementation
1. File Structure:
```
app/Filament/Resources/
├── UserResource.php
├── ParkResource.php
├── RideResource.php
└── ReviewResource.php
```
2. Key Components:
- Resource classes
- Form builders
- Table configurations
- Action handlers
## Dependencies
1. Required Packages
- filament/filament
- laravel/permissions
- filament/notifications
2. Configuration Files
- config/filament.php
- config/permissions.php
## Next Steps
1. Immediate Actions
- Install Filament package
- Create base resource structure
- Configure authentication
2. Planning Needed
- Resource organization strategy
- Permission hierarchy design
- Moderation workflow structure
## Notes and Considerations
- Follow Laravel/Filament naming conventions
- Maintain clear separation of concerns
- Document any deviations from Django
- Consider performance optimizations

View File

@@ -1,117 +1,36 @@
## Current Session Context
[2025-02-26 20:07] - Documentation System Enhancement
[2025-02-26 20:41]
## Recent Changes
1. Enhanced Project Documentation System
- Implemented Handoffs System alongside Memory Bank
- Created handoffs directory structure
- Set up instruction templates
- Documented integration approach
### Recent Changes
- Implemented Designer model and admin interface
- Created admin and moderation panel separation
- Set up role-based permissions system
- Added relationship management for rides
- Configured resource layouts and validations
2. Documentation Structure
- Created handoffs/0-instructions/
* 0-intro.md: System overview
* H-handoff-prompt.md: Handoff processing
* M-milestone-prompt.md: Milestone integration
### Current Progress
- Completed core Designer implementation
- Established permission structure
- Set up admin panel configuration
- Created moderation interface foundation
3. Architectural Decisions
- Documented handoff system integration in decisionLog.md
- Established clear triggers for handoffs and milestones
- Defined complementary roles with Memory Bank
### Next Tasks
1. History Tracking
- Set up model history tracking
- Configure audit logging
- Implement change tracking
## Current Goals
1. Documentation System Integration
- Create first handoff document for current progress
- Establish first milestone for completed features
- Train team on handoff creation guidelines
- Set up trigger points for new handoffs
2. Moderation Tools
- Create content review workflow
- Add moderation queue
- Set up notification system
2. Process Implementation
- Document handoff triggers for key development points
- Define milestone creation criteria
- Establish review process for handoffs
- Create content quality guidelines
3. Testing & Documentation
- Write feature tests
- Document permission structure
- Create admin guide
3. Team Training
- Share documentation system overview
- Train on handoff creation process
- Establish milestone review procedures
- Document best practices
## Open Questions
1. Process Integration
- How to streamline handoff creation during development?
- Best approach for milestone consolidation?
- Optimal timing for creating new handoffs?
2. Documentation Quality
- What level of detail is required for handoffs?
- How to ensure consistent milestone quality?
- What metrics to track for system effectiveness?
3. System Optimization
- How to balance detail vs. conciseness?
- When to clean up older handoffs?
- How to measure documentation impact?
## Recent Changes
1. Completed comprehensive project analysis
- Documented implemented features
- Identified missing components
- Analyzed dependencies
- Established priorities
2. Created ProjectAnalysis.md with:
- Current implementation status
- Missing features
- Priority implementation order
- Technical considerations
- Risk areas
- Recommendations
## Current Goals
1. High Priority Tasks
- Filament Admin Interface Implementation
* Set up core admin resources
* Configure permission system
* Implement moderation tools
- History Tracking System
* Model history implementation
* Audit logging
* User activity tracking
- Email Service Foundation
* Basic notification system
* Template management
2. Documentation Needs
- Document Filament integration plan
- Create admin system architecture docs
- Update component documentation
- Track feature parity progress
3. Technical Setup
- Configure Filament PHP
- Set up history tracking system
- Establish email service infrastructure
## Open Questions
1. Filament Integration
- How to match Django admin customizations?
- Best approach for permission mapping?
- Strategy for bulk actions?
2. History Tracking
- Most efficient way to track model changes?
- How to handle user contributions?
- Audit log storage approach?
3. Architecture Decisions
- Best way to structure Filament resources?
- Email queue management strategy?
- Analytics data organization?
4. Integration Points
- How to connect history with wiki system?
- Analytics integration approach?
- Company relationship tracking?
### Open Questions
1. Should we implement soft deletes for Designers?
2. How should we handle duplicate designer names?
3. Do we need version control for designer descriptions?

View File

@@ -10,6 +10,37 @@
- Enhanced project documentation strategy
- Established clear documentation workflows
### Filament Admin Interface Setup [2025-02-26 20:22]
- Installed core Filament packages (filament/filament:^3.2)
- Added permissions package (spatie/laravel-permission:^6.3)
- Installed notification system (filament/notifications:^3.2)
- Published Filament configuration and assets
- Created admin panel provider
- Published permission migrations and config
- Set up base admin panel structure
### Permission System Implementation [2025-02-26 20:39]
- Created DesignerPolicy with granular permissions
- Implemented role-based access control
- Set up permission seeder with default roles
- Added modular permission structure
- Prepared for audit trail integration
### Admin Panel Configuration [2025-02-26 20:38]
- Set up AdminPanelProvider with proper structure
- Configured navigation groups for different sections
- Added branding and UI customization
- Set up middleware and authentication
- Prepared structure for multiple admin panels
### Designer Resource Implementation [2025-02-26 20:37]
- Created Designer model with slug history support
- Implemented Filament resource with form layouts
- Added relationship management for rides
- Set up proper validations and filters
- Configured computed columns and bulk actions
- Added URL and date handling
### Project Analysis and Gap Assessment [2025-02-26]
- Completed comprehensive project analysis
- Identified implemented vs missing features

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
function r({state:o}){return{state:o,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=i=>i===null?0:Array.isArray(i)?i.length:typeof i!="object"?0:Object.keys(i).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows);this.rows=[];let s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.$nextTick(()=>{this.rows=e,this.updateState()})},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{"x-on:blur":"createTag()","x-model":"newTag","x-on:keydown"(t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},"x-on:paste"(){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default};

View File

@@ -0,0 +1 @@
function r({initialHeight:t,shouldAutosize:i,state:s}){return{state:s,wrapperEl:null,init:function(){this.wrapperEl=this.$el.parentNode,this.setInitialHeight(),i?this.$watch("state",()=>{this.resize()}):this.setUpResizeObserver()},setInitialHeight:function(){this.$el.scrollHeight<=0||(this.wrapperEl.style.height=t+"rem")},resize:function(){if(this.setInitialHeight(),this.$el.scrollHeight<=0)return;let e=this.$el.scrollHeight+"px";this.wrapperEl.style.height!==e&&(this.wrapperEl.style.height=e)},setUpResizeObserver:function(){new ResizeObserver(()=>{this.wrapperEl.style.height=this.$el.style.height}).observe(this.$el)}}}export{r as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
function n(){return{checkboxClickController:null,collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,lastCheckedRecord:null,livewireId:null,init:function(){this.livewireId=this.$root.closest("[wire\\:id]").attributes["wire:id"].value,this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1}),this.$nextTick(()=>this.watchForCheckboxClicks()),Livewire.hook("element.init",({component:e})=>{e.id===this.livewireId&&this.watchForCheckboxClicks()})},mountAction:function(e,t=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,t)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let t=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])s.dataset.group===e&&t.push(s.value);return t},getRecordsOnPage:function(){let e=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(t.value);return e},selectRecords:function(e){for(let t of e)this.isRecordSelected(t)||this.selectedRecords.push(t)},deselectRecords:function(e){for(let t of e){let s=this.selectedRecords.indexOf(t);s!==-1&&this.selectedRecords.splice(s,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(t=>this.isRecordSelected(t))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]},watchForCheckboxClicks:function(){this.checkboxClickController&&this.checkboxClickController.abort(),this.checkboxClickController=new AbortController;let{signal:e}=this.checkboxClickController;this.$root?.addEventListener("click",t=>t.target?.matches(".fi-ta-record-checkbox")&&this.handleCheckboxClick(t,t.target),{signal:e})},handleCheckboxClick:function(e,t){if(!this.lastChecked){this.lastChecked=t;return}if(e.shiftKey){let s=Array.from(this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[]);if(!s.includes(this.lastChecked)){this.lastChecked=t;return}let o=s.indexOf(this.lastChecked),r=s.indexOf(t),l=[o,r].sort((i,d)=>i-d),c=[];for(let i=l[0];i<=l[1];i++)s[i].checked=t.checked,c.push(s[i].value);t.checked?this.selectRecords(c):this.deselectRecords(c)}this.lastChecked=t}}}export{n as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long