diff --git a/app/Filament/Actions/Models/Auth/User/GiveProhibitionAction.php b/app/Filament/Actions/Models/Auth/User/GiveProhibitionAction.php index dda88e91e..f4815b9c7 100644 --- a/app/Filament/Actions/Models/Auth/User/GiveProhibitionAction.php +++ b/app/Filament/Actions/Models/Auth/User/GiveProhibitionAction.php @@ -11,8 +11,11 @@ use App\Filament\Components\Fields\TextInput; use App\Filament\Resources\Auth\Prohibition as ProhibitionResource; use App\Models\Auth\Prohibition; use App\Models\Auth\User; +use Filament\Actions\Action; use Filament\Forms\Components\DateTimePicker; use Filament\Schemas\Schema; +use Filament\Support\Colors\Color; +use Filament\Support\Icons\Heroicon; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Date; @@ -49,7 +52,9 @@ class GiveProhibitionAction extends BaseAction $reason = Arr::get($data, 'reason'); - $expiresAt = Date::createFromFormat('Y-m-d H:i:s', Arr::get($data, 'expires_at')); + $expiresAt = ($expiresAtField = Arr::get($data, 'expires_at')) + ? Date::createFromFormat('Y-m-d H:i:s', $expiresAtField) + : null; $user->prohibit($prohibition, $expiresAt, $reason, Auth::user()); @@ -72,15 +77,85 @@ class GiveProhibitionAction extends BaseAction ->required() ->options($prohibitions), - DateTimePicker::make('expires_at') - ->label(__('filament.actions.user.give_prohibition.expires_at.name')) - ->helperText(__('filament.actions.user.give_prohibition.expires_at.help')) - ->nullable(), + static::getExpiresAtField(), - TextInput::make('reason') - ->label(__('filament.actions.user.give_prohibition.reason.name')) - ->helperText(__('filament.actions.user.give_prohibition.reason.help')) - ->required(), + static::getReasonField(), ]); } + + public static function getExpiresAtField(): DateTimePicker + { + return DateTimePicker::make('expires_at') + ->label(__('filament.actions.user.give_prohibition.expires_at.name')) + ->helperText(__('filament.actions.user.give_prohibition.expires_at.help')) + ->default(now()) + ->native(false) + ->hintActions(static::getHintDateActions()) + ->suffixAction( + Action::make('clear') + ->icon(Heroicon::Trash) + ->color(Color::Red) + ->action(fn (DateTimePicker $component): DateTimePicker => $component->state(null)) + ) + ->minDate(now()) + ->nullable(); + } + + public static function getReasonField(): TextInput + { + return TextInput::make('reason') + ->label(__('filament.actions.user.give_prohibition.reason.name')) + ->helperText(__('filament.actions.user.give_prohibition.reason.help')) + ->required(); + } + + /** + * Get hint actions for date picker. + * + * @return Action[] + */ + public static function getHintDateActions(): array + { + return [ + Action::make('1 hour') + ->label('+1 hour') + ->action( + fn (DateTimePicker $component): DateTimePicker => $component->state( + Date::parse($component->getState() ?? now()) + ->addHour() + ->format($component->getFormat()) + ) + ), + + Action::make('1 day') + ->label('+1 day') + ->action( + fn (DateTimePicker $component): DateTimePicker => $component->state( + Date::parse($component->getState() ?? now()) + ->addDay() + ->format($component->getFormat()) + ) + ), + + Action::make('1 week') + ->label('+1 week') + ->action( + fn (DateTimePicker $component): DateTimePicker => $component->state( + Date::parse($component->getState() ?? now()) + ->addWeek() + ->format($component->getFormat()) + ) + ), + + Action::make('1 month') + ->label('+1 month') + ->action( + fn (DateTimePicker $component): DateTimePicker => $component->state( + Date::parse($component->getState() ?? now()) + ->addMonth() + ->format($component->getFormat()) + ) + ), + ]; + } } diff --git a/app/Filament/Actions/Models/Auth/User/GiveSanctionAction.php b/app/Filament/Actions/Models/Auth/User/GiveSanctionAction.php index 1ddf9f2be..c22fee0f0 100644 --- a/app/Filament/Actions/Models/Auth/User/GiveSanctionAction.php +++ b/app/Filament/Actions/Models/Auth/User/GiveSanctionAction.php @@ -7,11 +7,9 @@ namespace App\Filament\Actions\Models\Auth\User; use App\Events\Auth\Sanction\ModelSanctioned; use App\Filament\Actions\BaseAction; use App\Filament\Components\Fields\Select; -use App\Filament\Components\Fields\TextInput; use App\Filament\Resources\Auth\Sanction as SanctionResource; use App\Models\Auth\Sanction; use App\Models\Auth\User; -use Filament\Forms\Components\DateTimePicker; use Filament\Schemas\Schema; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Auth; @@ -48,7 +46,9 @@ class GiveSanctionAction extends BaseAction $reason = Arr::get($data, 'reason'); - $expiresAt = Date::createFromFormat('Y-m-d H:i:s', Arr::get($data, 'expires_at')); + $expiresAt = ($expiresAtField = Arr::get($data, 'expires_at')) + ? Date::createFromFormat('Y-m-d H:i:s', $expiresAtField) + : null; $user->applySanction($sanction, $expiresAt, $reason, Auth::user()); @@ -71,15 +71,11 @@ class GiveSanctionAction extends BaseAction ->required() ->options($sanctions), - DateTimePicker::make('expires_at') - ->label(__('filament.actions.user.give_sanction.expires_at.name')) - ->helperText(__('filament.actions.user.give_sanction.expires_at.help')) - ->nullable(), + GiveProhibitionAction::getExpiresAtField() + ->helperText(__('filament.actions.user.give_sanction.expires_at.help')), - TextInput::make('reason') - ->label(__('filament.actions.user.give_sanction.reason.name')) - ->helperText(__('filament.actions.user.give_sanction.reason.help')) - ->required(), + GiveProhibitionAction::getReasonField() + ->helperText(__('filament.actions.user.give_sanction.reason.help')), ]); } } diff --git a/app/Filament/Resources/Auth/User/RelationManagers/ProhibitionUserRelationManager.php b/app/Filament/Resources/Auth/User/RelationManagers/ProhibitionUserRelationManager.php index ba3b7bdd0..87fd45dd0 100644 --- a/app/Filament/Resources/Auth/User/RelationManagers/ProhibitionUserRelationManager.php +++ b/app/Filament/Resources/Auth/User/RelationManagers/ProhibitionUserRelationManager.php @@ -4,13 +4,14 @@ declare(strict_types=1); namespace App\Filament\Resources\Auth\User\RelationManagers; +use App\Filament\Actions\Models\Auth\User\GiveProhibitionAction; use App\Filament\Components\Columns\TextColumn; -use App\Filament\Components\Fields\TextInput; use App\Filament\RelationManagers\Auth\ProhibitionRelationManager; use App\Models\Auth\Prohibition; use App\Models\Auth\User; -use Filament\Forms\Components\DateTimePicker; +use Filament\Tables\Filters\Filter; use Filament\Tables\Table; +use Illuminate\Support\Facades\Config; class ProhibitionUserRelationManager extends ProhibitionRelationManager { @@ -22,14 +23,9 @@ class ProhibitionUserRelationManager extends ProhibitionRelationManager public function getPivotComponents(): array { return [ - DateTimePicker::make('expires_at') - ->label(__('filament.actions.user.give_prohibition.expires_at.name')) - ->helperText(__('filament.actions.user.give_prohibition.expires_at.help')) - ->nullable(), + GiveProhibitionAction::getExpiresAtField(), - TextInput::make('reason') - ->label(__('filament.actions.user.give_prohibition.reason.name')) - ->helperText(__('filament.actions.user.give_prohibition.reason.help')) + GiveProhibitionAction::getReasonField() ->disabled(), ]; } @@ -51,11 +47,25 @@ class ProhibitionUserRelationManager extends ProhibitionRelationManager return parent::table( $table ->inverseRelationship(Prohibition::RELATION_USERS) - ); + ) + ->defaultSort(Config::string('prohibition.table_names.model_prohibitions').'.created_at', 'desc'); } public static function getBulkActions(?array $actionsIncludedInGroup = []): array { return []; } + + public static function getFilters(): array + { + return [ + ...parent::getFilters(), + + Filter::make('expired') + ->modifyQueryUsing(fn ($query) => $query->expired()), + + Filter::make('not expired') + ->modifyQueryUsing(fn ($query) => $query->notExpired()), + ]; + } } diff --git a/app/Filament/Resources/Auth/User/RelationManagers/SanctionUserRelationManager.php b/app/Filament/Resources/Auth/User/RelationManagers/SanctionUserRelationManager.php index ddcf969ee..a1a90538b 100644 --- a/app/Filament/Resources/Auth/User/RelationManagers/SanctionUserRelationManager.php +++ b/app/Filament/Resources/Auth/User/RelationManagers/SanctionUserRelationManager.php @@ -4,13 +4,14 @@ declare(strict_types=1); namespace App\Filament\Resources\Auth\User\RelationManagers; +use App\Filament\Actions\Models\Auth\User\GiveProhibitionAction; use App\Filament\Components\Columns\TextColumn; -use App\Filament\Components\Fields\TextInput; use App\Filament\RelationManagers\Auth\SanctionRelationManager; use App\Models\Auth\Sanction; use App\Models\Auth\User; -use Filament\Forms\Components\DateTimePicker; +use Filament\Tables\Filters\Filter; use Filament\Tables\Table; +use Illuminate\Support\Facades\Config; class SanctionUserRelationManager extends SanctionRelationManager { @@ -19,24 +20,13 @@ class SanctionUserRelationManager extends SanctionRelationManager */ protected static string $relationship = User::RELATION_SANCTIONS; - public function table(Table $table): Table - { - return parent::table( - $table - ->inverseRelationship(Sanction::RELATION_USERS) - ); - } - public function getPivotComponents(): array { return [ - DateTimePicker::make('expires_at') - ->label(__('filament.actions.user.give_sanction.expires_at.name')) - ->helperText(__('filament.actions.user.give_sanction.expires_at.help')) - ->nullable(), + GiveProhibitionAction::getExpiresAtField() + ->helperText(__('filament.actions.user.give_sanction.expires_at.help')), - TextInput::make('reason') - ->label(__('filament.actions.user.give_sanction.reason.name')) + GiveProhibitionAction::getReasonField() ->helperText(__('filament.actions.user.give_sanction.reason.help')) ->disabled(), ]; @@ -56,6 +46,15 @@ class SanctionUserRelationManager extends SanctionRelationManager ]; } + public function table(Table $table): Table + { + return parent::table( + $table + ->inverseRelationship(Sanction::RELATION_USERS) + ) + ->defaultSort(Config::string('prohibition.table_names.model_sanctions').'.created_at', 'desc'); + } + /** * @return array */ @@ -70,4 +69,17 @@ class SanctionUserRelationManager extends SanctionRelationManager { return []; } + + public static function getFilters(): array + { + return [ + ...parent::getFilters(), + + Filter::make('expired') + ->modifyQueryUsing(fn ($query) => $query->expired()), + + Filter::make('not expired') + ->modifyQueryUsing(fn ($query) => $query->notExpired()), + ]; + } } diff --git a/app/Models/Auth/Prohibition.php b/app/Models/Auth/Prohibition.php index 29a44ef4f..e94cf9b7b 100644 --- a/app/Models/Auth/Prohibition.php +++ b/app/Models/Auth/Prohibition.php @@ -5,6 +5,9 @@ declare(strict_types=1); namespace App\Models\Auth; use App\Contracts\Models\Nameable; +use Illuminate\Database\Eloquent\Attributes\Scope; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\Config; use Kyrch\Prohibition\Models\Prohibition as BaseProhibition; class Prohibition extends BaseProhibition implements Nameable @@ -21,4 +24,21 @@ class Prohibition extends BaseProhibition implements Nameable { return $this->name; } + + #[Scope] + protected function expired(Builder $query): Builder + { + $tableName = Config::string('prohibition.table_names.model_prohibitions'); + + return $query->where("$tableName.expires_at", '<', now()); + } + + #[Scope] + protected function notExpired(Builder $query): Builder + { + $tableName = Config::string('prohibition.table_names.model_prohibitions'); + + return $query->where("$tableName.expires_at", '>', now()) + ->orWhereNull("$tableName.expires_at"); + } } diff --git a/app/Models/Auth/Sanction.php b/app/Models/Auth/Sanction.php index 784e309f2..849f967d4 100644 --- a/app/Models/Auth/Sanction.php +++ b/app/Models/Auth/Sanction.php @@ -5,6 +5,9 @@ declare(strict_types=1); namespace App\Models\Auth; use App\Contracts\Models\Nameable; +use Illuminate\Database\Eloquent\Attributes\Scope; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Support\Facades\Config; use Kyrch\Prohibition\Models\Sanction as BaseSanction; class Sanction extends BaseSanction implements Nameable @@ -21,4 +24,21 @@ class Sanction extends BaseSanction implements Nameable { return $this->name; } + + #[Scope] + protected function expired(Builder $query): Builder + { + $tableName = Config::string('prohibition.table_names.model_sanctions'); + + return $query->where("$tableName.expires_at", '<', now()); + } + + #[Scope] + protected function notExpired(Builder $query): Builder + { + $tableName = Config::string('prohibition.table_names.model_sanctions'); + + return $query->where("$tableName.expires_at", '>', now()) + ->orWhereNull("$tableName.expires_at"); + } } diff --git a/app/Notifications/Auth/ProhibitionOrSanctionNotification.php b/app/Notifications/Auth/ProhibitionOrSanctionNotification.php index c29d2c3f8..798b259c6 100644 --- a/app/Notifications/Auth/ProhibitionOrSanctionNotification.php +++ b/app/Notifications/Auth/ProhibitionOrSanctionNotification.php @@ -57,7 +57,7 @@ class ProhibitionOrSanctionNotification extends Notification return "This {$type} is permanent."; } - $human = Date::instance($this->expiresAt)->diffForHumans(syntax: Carbon::DIFF_ABSOLUTE, parts: 4); + $human = Date::instance($this->expiresAt)->diffForHumans(syntax: Carbon::DIFF_ABSOLUTE, parts: 3); return "This {$type} will expire in {$human}."; } diff --git a/app/Policies/Auth/UserPolicy.php b/app/Policies/Auth/UserPolicy.php index abee32de5..818909328 100644 --- a/app/Policies/Auth/UserPolicy.php +++ b/app/Policies/Auth/UserPolicy.php @@ -86,6 +86,62 @@ class UserPolicy extends BasePolicy : Response::deny(); } + public function attachAnySanction(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + + public function attachSanction(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + + public function detachAnySanction(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + + public function detachSanction(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + + public function attachAnyProhibition(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + + public function attachProhibition(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + + public function detachAnyProhibition(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + + public function detachProhibition(User $user): Response + { + return $user->hasRole(RoleEnum::ADMIN->value) + ? Response::allow() + : Response::deny(); + } + public function addPlaylist(User $user): Response { return $user->hasRole(RoleEnum::ADMIN->value)