diff --git a/app/Concerns/HasUserStatus.php b/app/Concerns/HasUserStatus.php new file mode 100644 index 00000000..b327606e --- /dev/null +++ b/app/Concerns/HasUserStatus.php @@ -0,0 +1,30 @@ +status, UserStatus::ACTIVE); + } + + public function isPending(): bool + { + return UserStatus::isValue($this->status, UserStatus::PENDING); + } + + public function setPendingStatus(): void + { + $this->update(['status' => UserStatus::PENDING]); + } + + public function deactivate(): void + { + $this->update(['status' => UserStatus::INACTIVE]); + } +} diff --git a/app/Filament/Organizations/Resources/UserResource.php b/app/Filament/Organizations/Resources/UserResource.php index 71c01674..e66f057a 100644 --- a/app/Filament/Organizations/Resources/UserResource.php +++ b/app/Filament/Organizations/Resources/UserResource.php @@ -12,6 +12,7 @@ use Filament\Forms\Components\Checkbox; use Filament\Forms\Components\CheckboxList; use Filament\Forms\Components\Placeholder; +use Filament\Forms\Components\Section; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; @@ -69,7 +70,9 @@ public static function table(Table $table): Table ->searchable(), TextColumn::make('roles') ->sortable() - ->label(__('user.labels.roles')), + ->badge() + ->label(__('user.labels.roles')) + ->formatStateUsing(fn ($state) => $state->label()), TextColumn::make('status') ->sortable() ->label(__('user.labels.account_status')) @@ -115,38 +118,42 @@ public static function getPages(): array public static function getSchema(): array { return [ - TextInput::make('first_name') - ->label(__('user.labels.first_name')) - ->required(), - TextInput::make('last_name') - ->label(__('user.labels.last_name')) - ->required(), - TextInput::make('email') - ->label(__('user.labels.email')) - ->required(), - TextInput::make('phone_number') - ->label(__('user.labels.phone_number')) - ->tel() - ->required(), - Select::make('roles') - ->label(__('user.labels.select_roles')) - ->options(Role::options()) - ->multiple() - ->required(), - Checkbox::make('can_be_case_manager') - ->label(__('user.labels.can_be_case_manager')), - Placeholder::make('obs') - ->content(__('user.placeholders.obs')) - ->label('') - ->columnSpanFull(), - CheckboxList::make('case_permissions') - ->label(__('user.labels.case_permissions')) - ->options(CasePermission::options()) - ->columnSpanFull(), - CheckboxList::make('admin_permissions') - ->label(__('user.labels.admin_permissions')) - ->options(AdminPermission::options()) - ->columnSpanFull(), + Section::make() + ->columns() + ->schema([ + TextInput::make('first_name') + ->label(__('user.labels.first_name')) + ->required(), + TextInput::make('last_name') + ->label(__('user.labels.last_name')) + ->required(), + TextInput::make('email') + ->label(__('user.labels.email')) + ->required(), + TextInput::make('phone_number') + ->label(__('user.labels.phone_number')) + ->tel() + ->required(), + Select::make('roles') + ->label(__('user.labels.select_roles')) + ->options(Role::options()) + ->multiple() + ->required(), + Checkbox::make('can_be_case_manager') + ->label(__('user.labels.can_be_case_manager')), + Placeholder::make('obs') + ->content(__('user.placeholders.obs')) + ->label('') + ->columnSpanFull(), + CheckboxList::make('case_permissions') + ->label(__('user.labels.case_permissions')) + ->options(CasePermission::options()) + ->columnSpanFull(), + CheckboxList::make('admin_permissions') + ->label(__('user.labels.admin_permissions')) + ->options(AdminPermission::options()) + ->columnSpanFull(), + ]), ]; } } diff --git a/app/Filament/Organizations/Resources/UserResource/Actions/DeactivateUserAction.php b/app/Filament/Organizations/Resources/UserResource/Actions/DeactivateUserAction.php index dd6b63ed..75ada391 100644 --- a/app/Filament/Organizations/Resources/UserResource/Actions/DeactivateUserAction.php +++ b/app/Filament/Organizations/Resources/UserResource/Actions/DeactivateUserAction.php @@ -4,7 +4,6 @@ namespace App\Filament\Organizations\Resources\UserResource\Actions; -use App\Enums\UserStatus; use App\Models\User; use Filament\Actions\Action; @@ -19,13 +18,13 @@ protected function setUp(): void { parent::setUp(); - $this->visible(fn (User $record) => UserStatus::isValue($record->status, UserStatus::ACTIVE)); + $this->visible(fn (User $record) => $record->isActive()); $this->label(__('user.actions.deactivate')); $this->color('danger'); -// $this->icon('heroicon-s-ban'); + $this->icon('heroicon-o-user-minus'); $this->modalHeading(__('user.action_deactivate_confirm.title')); diff --git a/app/Filament/Organizations/Resources/UserResource/Actions/ResendInvitationAction.php b/app/Filament/Organizations/Resources/UserResource/Actions/ResendInvitationAction.php index a38881d8..2739d2b7 100644 --- a/app/Filament/Organizations/Resources/UserResource/Actions/ResendInvitationAction.php +++ b/app/Filament/Organizations/Resources/UserResource/Actions/ResendInvitationAction.php @@ -4,7 +4,6 @@ namespace App\Filament\Organizations\Resources\UserResource\Actions; -use App\Enums\UserStatus; use App\Models\User; use Filament\Actions\Action; use Filament\Notifications\Notification; @@ -21,11 +20,11 @@ protected function setUp(): void { parent::setUp(); - $this->visible(fn (User $record) => UserStatus::isValue($record->status, UserStatus::PENDING)); + $this->visible(fn (User $record) => $record->isPending()); $this->label(__('user.actions.resend_invitation')); -// $this->icon('heroicon-o-mail'); + $this->icon('heroicon-o-envelope-open'); $this->modalHeading(__('user.action_resend_invitation_confirm.title')); diff --git a/app/Filament/Organizations/Resources/UserResource/Actions/ResetPassword.php b/app/Filament/Organizations/Resources/UserResource/Actions/ResetPassword.php new file mode 100644 index 00000000..7eb55ce8 --- /dev/null +++ b/app/Filament/Organizations/Resources/UserResource/Actions/ResetPassword.php @@ -0,0 +1,57 @@ +visible(fn (User $record) => $record->isActive()); + + $this->label(__('user.actions.reset_password')); + + $this->icon('heroicon-o-lock-open'); + + $this->modalHeading(__('user.action_reset_password_confirm.title')); + + $this->modalWidth('md'); + + $this->action(function (User $record) { + $key = $this->getRateLimiterKey($record); + $maxAttempts = 1; + + if (RateLimiter::tooManyAttempts($key, $maxAttempts)) { + return $this->failure(); + } + + RateLimiter::increment($key, HOUR_IN_SECONDS); + + $record->resetPassword(); +// $record->sendWelcomeNotification(); +// $this->success(); + }); + + $this->successNotificationTitle(__('user.action_reset_password_confirm.success')); + + $this->failureNotification( + fn (Notification $notification) => $notification + ->danger() + ->title(__('user.action_reset_password_confirm.failure_title')) + ->body(__('user.action_reset_password_confirm.failure_body')) + ); + } + + private function getRateLimiterKey(User $user): string + { + return 'reset-password:' . $user->id; + } +} diff --git a/app/Filament/Organizations/Resources/UserResource/Pages/EditUser.php b/app/Filament/Organizations/Resources/UserResource/Pages/EditUser.php index 184b5868..267c8761 100644 --- a/app/Filament/Organizations/Resources/UserResource/Pages/EditUser.php +++ b/app/Filament/Organizations/Resources/UserResource/Pages/EditUser.php @@ -18,4 +18,17 @@ protected function getHeaderActions(): array Actions\DeleteAction::make(), ]; } + + public function getBreadcrumbs(): array + { + return [ + self::$resource::getUrl() => self::$resource::getBreadcrumb(), + self::$resource::getUrl('view', ['record' => $this->record->id]) => $this->record->getFilamentName(), + ]; + } + + protected function getRedirectUrl(): string + { + return self::$resource::getUrl('view', ['record' => $this->record->id]); + } } diff --git a/app/Filament/Organizations/Resources/UserResource/Pages/ViewUser.php b/app/Filament/Organizations/Resources/UserResource/Pages/ViewUser.php index 925dd661..4a81bcb1 100644 --- a/app/Filament/Organizations/Resources/UserResource/Pages/ViewUser.php +++ b/app/Filament/Organizations/Resources/UserResource/Pages/ViewUser.php @@ -4,32 +4,66 @@ namespace App\Filament\Organizations\Resources\UserResource\Pages; -use App\Enums\UserStatus; use App\Filament\Organizations\Resources\UserResource; -use App\Models\User; use Filament\Actions; -use Filament\Forms\Components\Group; -use Filament\Forms\Components\Placeholder; -use Filament\Forms\Form; +use Filament\Infolists\Components\Section; +use Filament\Infolists\Components\TextEntry; +use Filament\Infolists\Infolist; use Filament\Resources\Pages\ViewRecord; -use Illuminate\Contracts\Support\Htmlable; +use Illuminate\Support\Str; class ViewUser extends ViewRecord { protected static string $resource = UserResource::class; - public function form(Form $form): Form + public function infolist(Infolist $infolist): Infolist { - return $form->schema([ - ...[Group::make([ - Placeholder::make('status') - ->content(fn (User $record) => $record->status?->label()), - Placeholder::make('updated_at') - ->content(fn (User $record) => $record->updated_at), - ]) + return $infolist->schema([ + Section::make() ->columns() - ->columnSpanFull()], - ...UserResource::getSchema(), + ->schema([ + TextEntry::make('status') + ->formatStateUsing(fn ($state) => $state->label()), + TextEntry::make('updated_at'), + ]), + Section::make() + ->columns() + ->schema([ + TextEntry::make('first_name') + ->label(__('user.labels.first_name')), + TextEntry::make('last_name') + ->label(__('user.labels.last_name')), + TextEntry::make('email') + ->label(__('user.labels.email')), + TextEntry::make('phone_number') + ->label(__('user.labels.phone_number')), + TextEntry::make('roles') + ->label(__('user.labels.select_roles')) + ->badge(fn ($state) => $state != '-') + ->formatStateUsing(fn ($state) => $state != '-' ? $state->label() : $state), + TextEntry::make('can_be_case_manager') + ->label(__('user.labels.can_be_case_manager')) + ->default('0') + ->formatStateUsing(fn ($state) => $state != '-' ? __('enum.ternary.' . $state) : $state), + TextEntry::make('obs') + ->default( + Str::of(__('user.placeholders.obs')) + ->inlineMarkdown() + ->toHtmlString() + ) + ->hiddenLabel() + ->columnSpanFull(), + TextEntry::make('case_permissions') + ->label(__('user.labels.case_permissions')) + ->badge(fn ($state) => $state != '-') + ->formatStateUsing(fn ($state) => $state != '-' ? __('enum.case_permissions.' . $state) : $state) + ->columnSpanFull(), + TextEntry::make('admin_permissions') + ->label(__('user.labels.admin_permissions')) + ->badge(fn ($state) => $state != '-') + ->formatStateUsing(fn ($state) => $state != '-' ? __('enum.admin_permission.' . $state) : $state) + ->columnSpanFull(), + ]), ]); } @@ -40,10 +74,7 @@ protected function getHeaderActions(): array UserResource\Actions\DeactivateUserAction::make(), - Actions\Action::make('reset_password') - ->label(__('user.actions.reset_password')) - ->visible(fn (User $record) => UserStatus::isValue($record->status, UserStatus::ACTIVE)) - ->action(fn (User $record) => $record->resetPassword()), + // UserResource\Actions\ResetPassword::make('reset-password'), UserResource\Actions\ResendInvitationAction::make(), ]; @@ -51,10 +82,10 @@ protected function getHeaderActions(): array public function getBreadcrumb(): string { - return $this->record->getFilamentName(); + return $this->getTitle(); } - public function getHeading(): string|Htmlable + public function getTitle(): string { return $this->record->getFilamentName(); } diff --git a/app/Models/Beneficiary.php b/app/Models/Beneficiary.php index 2bf481fa..21544799 100644 --- a/app/Models/Beneficiary.php +++ b/app/Models/Beneficiary.php @@ -150,17 +150,14 @@ class Beneficiary extends Model 'act_location' => AsEnumCollection::class . ':' . ActLocation::class, ]; - protected static function boot() + protected static function booted() { - parent::boot(); static::created(function (Beneficiary $beneficiary) { if (auth()->user()?->can_be_case_manager) { - $team = new CaseTeam([ - 'beneficiary_id' => $beneficiary->id, + $beneficiary->team()->create([ 'user_id' => auth()->user()->id, 'roles' => [Role::MANGER], ]); - $team->save(); } }); } diff --git a/app/Models/User.php b/app/Models/User.php index 22f2b47a..93b54ade 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,6 +5,7 @@ namespace App\Models; use App\Concerns\HasUlid; +use App\Concerns\HasUserStatus; use App\Concerns\MustSetInitialPassword; use App\Enums\Role; use App\Enums\UserStatus; @@ -44,6 +45,7 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasName, use MustSetInitialPassword; use Notifiable; use TwoFactorAuthenticatable; + use HasUserStatus; /** * The attributes that are mass assignable. @@ -98,7 +100,7 @@ protected static function booted() }); static::creating(function (User $model) { - $model->status = UserStatus::PENDING; + $model->setPendingStatus(); }); } @@ -191,11 +193,6 @@ public function scopeWithLastLoginAt(Builder $query): Builder ->withCasts(['last_login_at' => 'datetime']); } - public function deactivate(): void - { - $this->update(['status' => UserStatus::INACTIVE]); - } - // TODO create notifications public function resetPassword(): void { diff --git a/app/Providers/Filament/OrganizationPanelProvider.php b/app/Providers/Filament/OrganizationPanelProvider.php index e943ba44..88e4ede8 100644 --- a/app/Providers/Filament/OrganizationPanelProvider.php +++ b/app/Providers/Filament/OrganizationPanelProvider.php @@ -65,6 +65,7 @@ public function panel(Panel $panel): Panel ->sidebarCollapsibleOnDesktop() ->collapsibleNavigationGroups(false) ->login(Pages\Auth\Login::class) + ->passwordReset() ->colors([ 'primary' => Color::Purple, ]) diff --git a/lang/ro/enum.php b/lang/ro/enum.php index 63c8ce42..4ac9332e 100644 --- a/lang/ro/enum.php +++ b/lang/ro/enum.php @@ -163,7 +163,7 @@ 'user_status' => [ 'active' => 'Activ', 'inactive' => 'Dezactivat', - 'pending' => 'In asteptare', + 'pending' => 'În așteptare', ], 'case_permissions' => [ @@ -177,7 +177,7 @@ 'can_change_nomenclature' => 'Are drepturi de modificare nomenclator', 'can_change_staff' => 'Are drepturi de modificare Echipă Specialiști (Staff)', 'can_change_organisation_profile' => 'Are drepturi de modificare Profilul Organizației în Rețeaua Sunrise', - ], + ], 'frequency' => [ 'daily' => 'Zilnică', diff --git a/lang/ro/user.php b/lang/ro/user.php index cb49cdfb..97cedbc4 100644 --- a/lang/ro/user.php +++ b/lang/ro/user.php @@ -46,8 +46,7 @@ ], 'placeholders' => [ - 'obs' => 'Acest tip de utilizator are acces doar la cazurile din echipa cărora face parte și nu deține drepturi - de administrare ale sistemului. Puteți oferi permisiuni suplimentare din lista de mai jos.', + 'obs' => 'Acest tip de utilizator __are acces doar la cazurile din echipa cărora face parte__ și nu deține drepturi de administrare ale sistemului. Puteți oferi permisiuni suplimentare din lista de mai jos.', ], 'actions' => [