diff --git a/app/Filament/Resources/SoloEndorsementResource.php b/app/Filament/Resources/SoloEndorsementResource.php new file mode 100644 index 0000000000..3a41b13965 --- /dev/null +++ b/app/Filament/Resources/SoloEndorsementResource.php @@ -0,0 +1,90 @@ +where('endorsable_type', Position::class)->whereNotNull('expires_at'); + } + + /** + * Overriding here as this is a specialisation of the Endorsement model + * with a filtered eloquent query + * and thus using the model policy might have unintended consequences. + */ + public static function canAccess(): bool + { + return auth()->user()->hasAnyPermission('endorsement.view.*'); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('account.id')->label('CID'), + Tables\Columns\TextColumn::make('account.name')->label('Account'), + Tables\Columns\TextColumn::make('endorsable.description')->label('Position'), + Tables\Columns\TextColumn::make('duration')->getStateUsing(fn ($record) => $record->expires_at->diffInDays($record->created_at).' days')->label('Duration'), + Tables\Columns\TextColumn::make('created_at')->label('Started At')->isoDateTimeFormat('lll'), + Tables\Columns\TextColumn::make('expires_at')->label('Expires At')->isoDateTimeFormat('lll'), + Tables\Columns\TextColumn::make('status')->label('Status')->badge() + ->getStateUsing(fn ($record) => $record->expires_at->isPast() ? 'Expired' : 'Active') + ->color( + fn (string $state): string => match ($state) { + 'Expired' => 'danger', + 'Active' => 'success', + default => 'primary', + } + ), + ]) + ->filters([ + Tables\Filters\TernaryFilter::make('expires_at') + ->label('Endorsement Expiry Status') + ->trueLabel('Active') + ->default(true) + ->falseLabel('Expired') + ->nullable() + ->placeholder('All endorsements') + ->queries( + true: fn (Builder $query) => $query->where('expires_at', '>', now()), + false: fn (Builder $query) => $query->where('expires_at', '<=', now()), + blank: fn (Builder $query) => $query + ), + + Tables\Filters\QueryBuilder::make() + ->constraints([ + Tables\Filters\QueryBuilder\Constraints\TextConstraint::make('account.id')->operators( + [ + Tables\Filters\QueryBuilder\Constraints\TextConstraint\Operators\EqualsOperator::class, + ] + ), + ]), + ], layout: FiltersLayout::AboveContent); + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListSoloEndorsements::route('/'), + ]; + } +} diff --git a/app/Filament/Resources/SoloEndorsementResource/Pages/ListSoloEndorsements.php b/app/Filament/Resources/SoloEndorsementResource/Pages/ListSoloEndorsements.php new file mode 100644 index 0000000000..654ac4648f --- /dev/null +++ b/app/Filament/Resources/SoloEndorsementResource/Pages/ListSoloEndorsements.php @@ -0,0 +1,11 @@ +adminUser); + } + + public function test_can_access_list_page_with_permission() + { + $this->adminUser->givePermissionTo('endorsement.view.*'); + + Livewire::test(ListSoloEndorsements::class) + ->assertSee('Solo Endorsements'); + } + + public function test_cannot_access_list_page_without_permission() + { + Livewire::test(ListSoloEndorsements::class) + ->assertForbidden(); + } + + public function test_only_displays_solo_endorsements_with_expiry() + { + $this->adminUser->givePermissionTo('endorsement.view.*'); + + $soloEndorsement = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => now()->addDays(1), + ]); + + $soloEndorsementWithoutExpiry = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => null, + ]); + + Livewire::test(ListSoloEndorsements::class) + ->assertSee($soloEndorsement->account->name) + ->assertDontSee($soloEndorsementWithoutExpiry->account->name); + } + + public function test_only_displays_active_solo_endorsements_by_default() + { + $this->adminUser->givePermissionTo('endorsement.view.*'); + + $soloEndorsement = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => now()->addDays(1), + ]); + + $expiredSoloEndorsement = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => now()->subDays(1), + ]); + + Livewire::test(ListSoloEndorsements::class) + ->assertSee($soloEndorsement->account->name) + ->assertDontSee($expiredSoloEndorsement->account->name); + } + + public function test_filter_can_be_changed_to_expired_endorsements() + { + $this->adminUser->givePermissionTo('endorsement.view.*'); + + $soloEndorsement = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => now()->addDays(1), + ]); + + $expiredSoloEndorsement = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => now()->subDays(1), + ]); + + Livewire::test(ListSoloEndorsements::class) + ->filterTable('expires_at', false) + ->assertSee($expiredSoloEndorsement->account->name) + ->assertDontSee($soloEndorsement->account->name); + } + + public function test_filter_can_be_changed_to_all_endorsements() + { + $this->adminUser->givePermissionTo('endorsement.view.*'); + + $soloEndorsement = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => now()->addDays(1), + ]); + + $expiredSoloEndorsement = Endorsement::factory()->create([ + 'endorsable_type' => Position::class, + 'endorsable_id' => Position::factory(), + 'expires_at' => now()->subDays(1), + ]); + + Livewire::test(ListSoloEndorsements::class) + ->filterTable('expires_at', null) + ->assertSee($soloEndorsement->account->name) + ->assertSee($expiredSoloEndorsement->account->name); + } +}