From faef87738a8a16fdabb9888bcd57d30959f23089 Mon Sep 17 00:00:00 2001 From: Alex Popa Date: Tue, 29 Oct 2024 14:18:49 +0200 Subject: [PATCH 1/7] SuperAdmin add institutions --- .../Admin/Resources/InstitutionResource.php | 73 ++++++++ .../Pages/CreateInstitution.php | 166 ++++++++++++++++++ .../Pages/EditInstitution.php | 21 +++ .../Pages/ListInstitutions.php | 21 +++ app/Models/Institution.php | 91 ++++++++++ app/Models/Organization.php | 13 +- app/Providers/AppServiceProvider.php | 2 + composer.json | 1 + composer.lock | 113 +++++++++++- database/factories/InstitutionFactory.php | 57 ++++++ database/factories/OrganizationFactory.php | 17 +- .../2014_10_12_000000_create_users_table.php | 1 + ...10_28_192350_create_institutions_table.php | 49 ++++++ ...1_20_124722_create_organizations_table.php | 16 +- ..._084836_add_institution_in_users_table.php | 31 ++++ database/seeders/DatabaseSeeder.php | 9 +- lang/ro/institution.php | 14 ++ 17 files changed, 653 insertions(+), 42 deletions(-) create mode 100644 app/Filament/Admin/Resources/InstitutionResource.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Pages/ListInstitutions.php create mode 100644 app/Models/Institution.php create mode 100644 database/factories/InstitutionFactory.php create mode 100644 database/migrations/2023_10_28_192350_create_institutions_table.php create mode 100644 database/migrations/2024_10_29_084836_add_institution_in_users_table.php create mode 100644 lang/ro/institution.php diff --git a/app/Filament/Admin/Resources/InstitutionResource.php b/app/Filament/Admin/Resources/InstitutionResource.php new file mode 100644 index 00000000..89754408 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource.php @@ -0,0 +1,73 @@ +schema([ + // + ]); + } + + public static function table(Table $table): Table + { + return $table + ->modifyQueryUsing( + fn (Builder $query) => $query + ->withCount(['organizations', 'beneficiaries', 'users']) + ->with(['county', 'city']) + ) + ->columns([ + TextColumn::make('name') + ->label(__('institution.headings.institution_name')), + + TextColumn::make('county_and_city') + ->label(__('institution.headings.registered_office')), + + TextColumn::make('organizations_count') + ->label(__('institution.headings.centers')), + + TextColumn::make('beneficiaries_count') + ->label(__('institution.headings.cases')), + + TextColumn::make('users_count') + ->label(__('institution.headings.specialists')), + + TextColumn::make('status') + ->label(__('institution.headings.status')), + ]) + ->filters([ + // + ]) + ->actions([ + ViewAction::make(), + ]); + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListInstitutions::route('/'), + 'create' => Pages\CreateInstitution::route('/create'), + 'edit' => Pages\EditInstitution::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php new file mode 100644 index 00000000..933989a4 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php @@ -0,0 +1,166 @@ +schema([ + Section::make() + ->maxWidth('3xl') + ->columns() + ->schema([ + TextInput::make('name') + ->label(__('organization.field.name')), + + TextInput::make('short_name') + ->label(__('organization.field.short_name')), + + Select::make('type') + ->label(__('organization.field.type')) + ->options(OrganizationType::options()) + ->enum(OrganizationType::class), + + TextInput::make('cif') + ->label(__('organization.field.cif')) + ->rule(new ValidCIF), + + TextInput::make('main_activity') + ->label(__('organization.field.main_activity')), + + Location::make() + ->city() + ->required(), + + TextInput::make('address') + ->label(__('organization.field.address')) + ->maxLength(200) + ->required(), + + TextInput::make('phone') + ->label(__('organization.field.phone')) + ->tel(), + + TextInput::make('reprezentative_name') + ->label(__('organization.field.reprezentative_name')), + + TextInput::make('reprezentative_email') + ->label(__('organization.field.reprezentative_email')), + + TextInput::make('website') + ->label(__('organization.field.website')) + ->url(), + + SpatieMediaLibraryFileUpload::make('organization_status') + ->label(__('organization.field.organization_status')) + ->collection('organization_status') + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('social_service_provider_certificate') + ->label(__('organization.field.social_service_provider_certificate')) + ->collection('social_service_provider_certificate') + ->columnSpanFull(), + ]), + ]), + + Step::make('center_details') + ->schema([ + Section::make() + ->maxWidth('3xl') + ->schema([ + Repeater::make('organizations') + ->columns() + ->minItems(1) + ->relationship('organizations') + ->schema([ + TextInput::make('name') + ->label(__('organization.field.name')), + + TextInput::make('short_name') + ->label(__('organization.field.short_name')), + + TextInput::make('main_activity') + ->label(__('organization.field.main_activity')) + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('social_service_licensing_certificate') + ->label(__('organization.field.social_service_licensing_certificate')) + ->collection('social_service_licensing_certificate') + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('logo') + ->label(__('organization.field.logo')) + ->collection('logo') + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('organization_header') + ->label(__('organization.field.organization_header')) + ->collection('organization_header') + ->columnSpanFull(), + ]), + ]), + ]), + + Step::make('ngo_admin') + ->schema([ + Section::make() + ->maxWidth('3xl') + ->schema([ + Repeater::make('admins') + ->columns() + ->minItems(1) + ->relationship('admins') + ->schema([ + TextInput::make('first_name') + ->label(__('organization.field.name')), + + TextInput::make('last_name') + ->label(__('organization.field.last_name')), + + TextInput::make('email') + ->label(__('organization.field.email')), + + TextInput::make('phone') + ->label(__('organization.field.phone')), + + Hidden::make('ngo_admin') + ->default(1), + ]), + ]), + ]), + ]; + } + + public function afterCreate() + { + $record = $this->getRecord(); + $admins = $record->admins; + $organizations = $record->organizations; + + $organizations->each(fn (Organization $organization) => $organization->users()->attach($admins->pluck('id'))); + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php new file mode 100644 index 00000000..778c9b00 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php @@ -0,0 +1,21 @@ + OrganizationType::class, + ]; + + protected function getSlugSource(): string + { + return $this->name; + } + + public function organizations(): HasMany + { + return $this->hasMany(Organization::class); + } + + public function admins(): HasMany + { + return $this->hasMany(User::class); + } + + public function beneficiaries(): HasManyThrough + { + return $this->hasManyThrough(Beneficiary::class, Organization::class); + } + + public function users(): HasManyDeep + { + return $this->hasManyDeep( + User::class, + [Organization::class, 'model_has_organizations'], + ['institution_id', null, 'id'], + ['id', 'id', 'model_id'] + )->where('model_type', 'user'); + } + + public function county(): BelongsTo + { + return $this->belongsTo(County::class); + } + + public function city(): BelongsTo + { + return $this->belongsTo(City::class); + } + + public function getCountyAndCityAttribute(): string + { + return $this->city?->name . ' (' . $this->county?->name . ')'; + } +} diff --git a/app/Models/Organization.php b/app/Models/Organization.php index c3d542ac..c8850ebb 100644 --- a/app/Models/Organization.php +++ b/app/Models/Organization.php @@ -14,6 +14,7 @@ use Filament\Models\Contracts\HasName; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\MorphToMany; @@ -34,20 +35,18 @@ class Organization extends Model implements HasAvatar, HasMedia, HasName, HasCur 'name', 'slug', 'short_name', - 'type', - 'cif', 'main_activity', - 'address', - 'reprezentative_name', - 'reprezentative_email', - 'phone', - 'website', ]; protected $casts = [ 'type' => OrganizationType::class, ]; + public function institution(): BelongsTo + { + return $this->belongsTo(Institution::class); + } + public function users(): MorphToMany { return $this->morphedByMany(User::class, 'model', 'model_has_organizations'); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 036bda56..79de6040 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -18,6 +18,7 @@ use App\Models\Document; use App\Models\EvaluateDetails; use App\Models\FlowPresentation; +use App\Models\Institution; use App\Models\Intervention; use App\Models\Meeting; use App\Models\MultidisciplinaryEvaluation; @@ -73,6 +74,7 @@ public function boot(): void protected function enforceMorphMap(): void { Relation::enforceMorphMap([ + 'institution' => Institution::class, 'beneficiary' => Beneficiary::class, 'city' => City::class, 'community_profile' => CommunityProfile::class, diff --git a/composer.json b/composer.json index 6da3e77c..981a3365 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "sentry/sentry-laravel": "^4.7", "spatie/laravel-activitylog": "^4.8", "staudenmeir/belongs-to-through": "^2.5", + "staudenmeir/eloquent-has-many-deep": "^1.7", "stevegrunwell/time-constants": "^1.2", "tpetry/laravel-query-expressions": "^0.9" }, diff --git a/composer.lock b/composer.lock index e8d68a5b..689cb9d9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "950bb18e76dc62550f951ac52ef95ff5", + "content-hash": "fffb36dfb1c0790fca472cdc6e33c592", "packages": [ { "name": "alcea/cnp", @@ -6808,6 +6808,115 @@ ], "time": "2023-12-19T11:58:06+00:00" }, + { + "name": "staudenmeir/eloquent-has-many-deep", + "version": "v1.19.4", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/eloquent-has-many-deep.git", + "reference": "d9651c2c64d34a8fd4c680090d3521ed136f2ead" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep/zipball/d9651c2c64d34a8fd4c680090d3521ed136f2ead", + "reference": "d9651c2c64d34a8fd4c680090d3521ed136f2ead", + "shasum": "" + }, + "require": { + "illuminate/database": "^10.0", + "php": "^8.1", + "staudenmeir/eloquent-has-many-deep-contracts": "^1.1" + }, + "require-dev": { + "awobaz/compoships": "^2.2", + "barryvdh/laravel-ide-helper": "^2.13", + "illuminate/pagination": "^10.0", + "korridor/laravel-has-many-merged": "^1.0", + "mockery/mockery": "^1.6", + "orchestra/testbench": "^8.13", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.1", + "staudenmeir/eloquent-eager-limit": "^1.8", + "staudenmeir/eloquent-json-relations": "^1.8.2", + "staudenmeir/laravel-adjacency-list": "^1.13.7" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Staudenmeir\\EloquentHasManyDeep\\IdeHelperServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Staudenmeir\\EloquentHasManyDeep\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Laravel Eloquent HasManyThrough relationships with unlimited levels", + "support": { + "issues": "https://github.com/staudenmeir/eloquent-has-many-deep/issues", + "source": "https://github.com/staudenmeir/eloquent-has-many-deep/tree/v1.19.4" + }, + "funding": [ + { + "url": "https://paypal.me/JonasStaudenmeir", + "type": "custom" + } + ], + "time": "2024-05-25T09:46:08+00:00" + }, + { + "name": "staudenmeir/eloquent-has-many-deep-contracts", + "version": "v1.1", + "source": { + "type": "git", + "url": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts.git", + "reference": "c39317b839d6123be126b9980e4a3d38310f5939" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staudenmeir/eloquent-has-many-deep-contracts/zipball/c39317b839d6123be126b9980e4a3d38310f5939", + "reference": "c39317b839d6123be126b9980e4a3d38310f5939", + "shasum": "" + }, + "require": { + "illuminate/database": "^10.0", + "php": "^8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Staudenmeir\\EloquentHasManyDeepContracts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonas Staudenmeir", + "email": "mail@jonas-staudenmeir.de" + } + ], + "description": "Contracts for staudenmeir/eloquent-has-many-deep", + "support": { + "issues": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/issues", + "source": "https://github.com/staudenmeir/eloquent-has-many-deep-contracts/tree/v1.1" + }, + "time": "2023-01-18T12:43:26+00:00" + }, { "name": "stevegrunwell/time-constants", "version": "v1.2.0", @@ -14440,5 +14549,5 @@ "php": "^8.2" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } diff --git a/database/factories/InstitutionFactory.php b/database/factories/InstitutionFactory.php new file mode 100644 index 00000000..af941b56 --- /dev/null +++ b/database/factories/InstitutionFactory.php @@ -0,0 +1,57 @@ + + */ +class InstitutionFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $name = fake()->company(); + $city = City::query()->inRandomOrder()->first(); + + return [ + 'name' => $name, + 'short_name' => preg_replace('/\b(\w)|./u', '$1', $name), + 'type' => fake()->randomElement(OrganizationType::values()), + 'phone' => fake()->phoneNumber(), + 'website' => fake()->url(), + + 'city_id' => $city->id, + 'county_id' => $city->county_id, + 'address' => fake()->streetAddress(), + + 'reprezentative_name' => fake()->name(), + 'reprezentative_email' => fake()->safeEmail(), + ]; + } + + public function withOrganization() + { + return $this->afterCreating(function (Institution $institution) { + Organization::factory() + ->for($institution) + ->count(2) + ->withUsers() + ->withBeneficiaries() + ->withCommunityProfile() + ->withInterventions() + ->create(); + }); + } +} diff --git a/database/factories/OrganizationFactory.php b/database/factories/OrganizationFactory.php index fb58cc6c..3fc44053 100644 --- a/database/factories/OrganizationFactory.php +++ b/database/factories/OrganizationFactory.php @@ -4,9 +4,7 @@ namespace Database\Factories; -use App\Enums\OrganizationType; use App\Models\Beneficiary; -use App\Models\City; use App\Models\CommunityProfile; use App\Models\Intervention; use App\Models\Organization; @@ -28,21 +26,10 @@ class OrganizationFactory extends Factory public function definition(): array { $name = fake()->company(); - $city = City::query()->inRandomOrder()->first(); return [ 'name' => $name, 'short_name' => preg_replace('/\b(\w)|./u', '$1', $name), - 'type' => fake()->randomElement(OrganizationType::values()), - 'phone' => fake()->phoneNumber(), - 'website' => fake()->url(), - - 'city_id' => $city->id, - 'county_id' => $city->county_id, - 'address' => fake()->streetAddress(), - - 'reprezentative_name' => fake()->name(), - 'reprezentative_email' => fake()->safeEmail(), ]; } @@ -54,6 +41,8 @@ public function withUsers(int $count = 5): static ->count($count) ->sequence(fn (Sequence $sequence) => [ 'email' => \sprintf('user-%d-%d@example.com', $organization->id, $sequence->index + 1), + 'institution_id' => $sequence->index === 0 ? $organization->institution_id : null, + 'ngo_admin' => $sequence->index === 0, ]) ->create() ->pluck('id') @@ -71,7 +60,7 @@ public function withCommunityProfile(): static }); } - public function withBeneficiaries(int $count = 50): static + public function withBeneficiaries(int $count = 30): static { return $this->afterCreating(function (Organization $organization) use ($count) { Beneficiary::factory() diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index c0f0a69d..9264b54c 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -25,6 +25,7 @@ public function up(): void $table->json('case_permissions')->nullable(); $table->json('admin_permissions')->nullable(); $table->boolean('is_admin')->default(false); + $table->boolean('ngo_admin')->default(false); $table->timestamp('password_set_at')->nullable(); $table->string('password'); $table->rememberToken(); diff --git a/database/migrations/2023_10_28_192350_create_institutions_table.php b/database/migrations/2023_10_28_192350_create_institutions_table.php new file mode 100644 index 00000000..90953610 --- /dev/null +++ b/database/migrations/2023_10_28_192350_create_institutions_table.php @@ -0,0 +1,49 @@ +id(); + $table->ulid()->unique(); + + $table->string('name'); + $table->string('slug')->unique()->nullable(); + $table->string('short_name')->nullable(); + $table->string('type')->nullable(); + $table->string('cif')->nullable(); + $table->string('main_activity')->nullable(); + + $table->foreignIdFor(County::class)->nullable()->constrained()->cascadeOnDelete(); + $table->foreignIdFor(City::class)->nullable()->constrained()->cascadeOnDelete(); + $table->string('address')->nullable(); + + $table->string('reprezentative_name')->nullable(); + $table->string('reprezentative_email')->nullable(); + $table->string('phone')->nullable(); + $table->string('website')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('institutions'); + } +}; diff --git a/database/migrations/2023_11_20_124722_create_organizations_table.php b/database/migrations/2023_11_20_124722_create_organizations_table.php index c3b2ee68..3c7e4994 100644 --- a/database/migrations/2023_11_20_124722_create_organizations_table.php +++ b/database/migrations/2023_11_20_124722_create_organizations_table.php @@ -2,8 +2,7 @@ declare(strict_types=1); -use App\Models\City; -use App\Models\County; +use App\Models\Institution; use App\Models\Organization; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; @@ -16,22 +15,13 @@ public function up(): void Schema::create('organizations', function (Blueprint $table) { $table->id(); $table->ulid()->unique(); - $table->timestamps(); + $table->foreignIdFor(Institution::class)->constrained()->cascadeOnDelete(); $table->string('name'); $table->string('slug')->unique()->nullable(); $table->string('short_name')->nullable(); - $table->string('type')->nullable(); - $table->string('cif')->nullable(); $table->string('main_activity')->nullable(); - $table->foreignIdFor(County::class)->nullable()->constrained()->cascadeOnDelete(); - $table->foreignIdFor(City::class)->nullable()->constrained()->cascadeOnDelete(); - $table->string('address')->nullable(); - - $table->string('reprezentative_name')->nullable(); - $table->string('reprezentative_email')->nullable(); - $table->string('phone')->nullable(); - $table->string('website')->nullable(); + $table->timestamps(); }); Schema::create('model_has_organizations', function (Blueprint $table) { diff --git a/database/migrations/2024_10_29_084836_add_institution_in_users_table.php b/database/migrations/2024_10_29_084836_add_institution_in_users_table.php new file mode 100644 index 00000000..bcd0379c --- /dev/null +++ b/database/migrations/2024_10_29_084836_add_institution_in_users_table.php @@ -0,0 +1,31 @@ +foreignIdFor(Institution::class)->nullable()->constrained()->cascadeOnDelete(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 86ca0cda..66a71b6a 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -5,7 +5,7 @@ namespace Database\Seeders; use App\Models\Country; -use App\Models\Organization; +use App\Models\Institution; use App\Models\Service; use App\Models\User; use Illuminate\Database\Seeder; @@ -36,12 +36,9 @@ public function run(): void ->count(20) ->create(); - Organization::factory() + Institution::factory() ->count(2) - ->withUsers() - ->withBeneficiaries() - ->withCommunityProfile() - ->withInterventions() + ->withOrganization() ->create(); } } diff --git a/lang/ro/institution.php b/lang/ro/institution.php new file mode 100644 index 00000000..a6354684 --- /dev/null +++ b/lang/ro/institution.php @@ -0,0 +1,14 @@ + [ + 'institution_name' => 'Nume instituție', + 'registered_office' => 'Sediu social', + 'centers' => 'Centre', + 'cases' => 'Cazuri', + 'specialists' => 'Specialiști', + 'status' => 'Status', + ], +]; From 862d2a70c2207f3cf27515111990c701a8a4120a Mon Sep 17 00:00:00 2001 From: Alex Popa Date: Tue, 29 Oct 2024 17:27:57 +0200 Subject: [PATCH 2/7] Fix test --- database/factories/UserFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 0f06c6f9..7638a972 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -5,6 +5,7 @@ namespace Database\Factories; use App\Enums\UserStatus; +use App\Models\Institution; use App\Models\Organization; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -58,8 +59,11 @@ public function admin(): static public function withOrganization(): static { return $this->afterCreating(function (User $user) { + $institution = Institution::factory() + ->create(); $user->organizations()->attach( Organization::factory() + ->for($institution) ->create() ); }); From 750833e49fcfbb604ecf034f1df94c0242981851 Mon Sep 17 00:00:00 2001 From: Alex Popa Date: Wed, 30 Oct 2024 17:22:27 +0200 Subject: [PATCH 3/7] user institution --- app/Concerns/HasUserStatus.php | 10 + app/Enums/InstitutionStatus.php | 37 ++++ .../Admin/Resources/InstitutionResource.php | 16 +- .../Actions/ActivateInstitution.php | 24 +++ .../Actions/InactivateInstitution.php | 27 +++ .../Pages/CreateInstitution.php | 141 ++---------- .../Pages/EditInstitution.php | 21 -- .../Pages/EditInstitutionCenters.php | 64 ++++++ .../Pages/EditInstitutionDetails.php | 94 ++++++++ .../Pages/ListInstitutions.php | 14 +- .../Pages/ViewInstitution.php | 201 ++++++++++++++++++ .../Resources/UserInstitutionResource.php | 25 +++ .../Actions/ActivateUserAction.php | 39 ++++ .../Actions/DeactivateUserAction.php | 42 ++++ .../Actions/ResendInvitationAction.php | 61 ++++++ .../Pages/EditUserInstitution.php | 51 +++++ .../Pages/ViewUserInstitution.php | 85 ++++++++ app/Models/Institution.php | 23 ++ database/factories/InstitutionFactory.php | 3 + ...10_28_192350_create_institutions_table.php | 3 + lang/ro/enum.php | 8 +- lang/ro/institution.php | 43 ++++ lang/ro/user.php | 1 + 23 files changed, 889 insertions(+), 144 deletions(-) create mode 100644 app/Enums/InstitutionStatus.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Actions/ActivateInstitution.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Actions/InactivateInstitution.php delete mode 100644 app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionCenters.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php create mode 100644 app/Filament/Admin/Resources/UserInstitutionResource.php create mode 100644 app/Filament/Admin/Resources/UserInstitutionResource/Actions/ActivateUserAction.php create mode 100644 app/Filament/Admin/Resources/UserInstitutionResource/Actions/DeactivateUserAction.php create mode 100644 app/Filament/Admin/Resources/UserInstitutionResource/Actions/ResendInvitationAction.php create mode 100644 app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php create mode 100644 app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php diff --git a/app/Concerns/HasUserStatus.php b/app/Concerns/HasUserStatus.php index b327606e..3f07b4fe 100644 --- a/app/Concerns/HasUserStatus.php +++ b/app/Concerns/HasUserStatus.php @@ -18,6 +18,11 @@ public function isPending(): bool return UserStatus::isValue($this->status, UserStatus::PENDING); } + public function isInactive(): bool + { + return UserStatus::isValue($this->status, UserStatus::INACTIVE); + } + public function setPendingStatus(): void { $this->update(['status' => UserStatus::PENDING]); @@ -27,4 +32,9 @@ public function deactivate(): void { $this->update(['status' => UserStatus::INACTIVE]); } + + public function activate(): void + { + $this->update(['status' => UserStatus::ACTIVE]); + } } diff --git a/app/Enums/InstitutionStatus.php b/app/Enums/InstitutionStatus.php new file mode 100644 index 00000000..6377d446 --- /dev/null +++ b/app/Enums/InstitutionStatus.php @@ -0,0 +1,37 @@ + Color::Green, + self::INACTIVE => Color::Red, + self::PENDING => Color::Yellow, + }; + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource.php b/app/Filament/Admin/Resources/InstitutionResource.php index 89754408..9ee86258 100644 --- a/app/Filament/Admin/Resources/InstitutionResource.php +++ b/app/Filament/Admin/Resources/InstitutionResource.php @@ -5,6 +5,8 @@ namespace App\Filament\Admin\Resources; use App\Filament\Admin\Resources\InstitutionResource\Pages; +use App\Filament\Admin\Resources\UserInstitutionResource\Pages\EditUserInstitution; +use App\Filament\Admin\Resources\UserInstitutionResource\Pages\ViewUserInstitution; use App\Models\Institution; use Filament\Forms\Form; use Filament\Resources\Resource; @@ -58,8 +60,12 @@ public static function table(Table $table): Table // ]) ->actions([ - ViewAction::make(), - ]); + ViewAction::make() + ->label(__('general.action.view_details')), + ]) + ->emptyStateIcon('heroicon-o-clipboard-document-list') + ->emptyStateHeading(__('institution.headings.empty_state')) + ->emptyStateDescription(null); } public static function getPages(): array @@ -67,7 +73,11 @@ public static function getPages(): array return [ 'index' => Pages\ListInstitutions::route('/'), 'create' => Pages\CreateInstitution::route('/create'), - 'edit' => Pages\EditInstitution::route('/{record}/edit'), + 'view' => Pages\ViewInstitution::route('/{record}'), + 'edit_institution_details' => Pages\EditInstitutionDetails::route('/{record}/editInstitutionDetails'), + 'edit_institution_centers' => Pages\EditInstitutionCenters::route('/{record}/editCenters'), + 'user.view' => ViewUserInstitution::route('{parent}/user/{record}'), + 'user.edit' => EditUserInstitution::route('{parent}/user/{record}/edit'), ]; } } diff --git a/app/Filament/Admin/Resources/InstitutionResource/Actions/ActivateInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Actions/ActivateInstitution.php new file mode 100644 index 00000000..1ec7eeb8 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/Actions/ActivateInstitution.php @@ -0,0 +1,24 @@ +name('activate_institution'); + $this->label(__('institution.actions.activate')); + $this->icon('heroicon-s-arrow-path'); + $this->color('success'); + $this->outlined(); + $this->visible(fn (Institution $record) => $record->isInactivated()); + $this->action(fn (Institution $record) => $record->activate()); + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource/Actions/InactivateInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Actions/InactivateInstitution.php new file mode 100644 index 00000000..450538f9 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/Actions/InactivateInstitution.php @@ -0,0 +1,27 @@ +name('inactivate_institution'); + $this->label(__('institution.actions.inactivate')); + $this->icon('heroicon-o-user-minus'); + $this->color('danger'); + $this->outlined(); + $this->visible(fn (Institution $record) => $record->isActivated()); + $this->modalHeading(__('institution.headings.inactivate')); + $this->modalDescription(__('institution.labels.inactivate')); + $this->modalSubmitActionLabel(__('institution.actions.inactivate')); + $this->action(fn (Institution $record) => $record->inactivate()); + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php index 933989a4..2e349493 100644 --- a/app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/CreateInstitution.php @@ -4,17 +4,12 @@ namespace App\Filament\Admin\Resources\InstitutionResource\Pages; -use App\Enums\OrganizationType; use App\Filament\Admin\Resources\InstitutionResource; -use App\Forms\Components\Location; +use App\Filament\Admin\Resources\UserInstitutionResource\Pages\EditUserInstitution; use App\Forms\Components\Repeater; -use App\Forms\Components\Select; use App\Models\Organization; -use App\Rules\ValidCIF; use Filament\Forms\Components\Hidden; -use Filament\Forms\Components\Section; -use Filament\Forms\Components\SpatieMediaLibraryFileUpload; -use Filament\Forms\Components\TextInput; +use Filament\Forms\Components\Placeholder; use Filament\Forms\Components\Wizard\Step; use Filament\Resources\Pages\Concerns\HasWizard; use Filament\Resources\Pages\CreateRecord; @@ -28,128 +23,38 @@ class CreateInstitution extends CreateRecord public function getSteps(): array { return [ - Step::make('institution_details') + Step::make(__('institution.headings.institution_details')) + ->schema(EditInstitutionDetails::getSchema()), + + Step::make(__('institution.headings.center_details')) ->schema([ - Section::make() + Placeholder::make('center_details') + ->hiddenLabel() ->maxWidth('3xl') - ->columns() - ->schema([ - TextInput::make('name') - ->label(__('organization.field.name')), - - TextInput::make('short_name') - ->label(__('organization.field.short_name')), - - Select::make('type') - ->label(__('organization.field.type')) - ->options(OrganizationType::options()) - ->enum(OrganizationType::class), - - TextInput::make('cif') - ->label(__('organization.field.cif')) - ->rule(new ValidCIF), - - TextInput::make('main_activity') - ->label(__('organization.field.main_activity')), - - Location::make() - ->city() - ->required(), - - TextInput::make('address') - ->label(__('organization.field.address')) - ->maxLength(200) - ->required(), - - TextInput::make('phone') - ->label(__('organization.field.phone')) - ->tel(), - - TextInput::make('reprezentative_name') - ->label(__('organization.field.reprezentative_name')), - - TextInput::make('reprezentative_email') - ->label(__('organization.field.reprezentative_email')), - - TextInput::make('website') - ->label(__('organization.field.website')) - ->url(), - - SpatieMediaLibraryFileUpload::make('organization_status') - ->label(__('organization.field.organization_status')) - ->collection('organization_status') - ->columnSpanFull(), + ->content(__('institution.placeholders.center_details')), - SpatieMediaLibraryFileUpload::make('social_service_provider_certificate') - ->label(__('organization.field.social_service_provider_certificate')) - ->collection('social_service_provider_certificate') - ->columnSpanFull(), - ]), + ...EditInstitutionCenters::getSchema(), ]), - Step::make('center_details') + Step::make(__('institution.headings.ngo_admin')) ->schema([ - Section::make() + Placeholder::make('ngo_admins') + ->hiddenLabel() ->maxWidth('3xl') - ->schema([ - Repeater::make('organizations') - ->columns() - ->minItems(1) - ->relationship('organizations') - ->schema([ - TextInput::make('name') - ->label(__('organization.field.name')), - - TextInput::make('short_name') - ->label(__('organization.field.short_name')), - - TextInput::make('main_activity') - ->label(__('organization.field.main_activity')) - ->columnSpanFull(), - - SpatieMediaLibraryFileUpload::make('social_service_licensing_certificate') - ->label(__('organization.field.social_service_licensing_certificate')) - ->collection('social_service_licensing_certificate') - ->columnSpanFull(), - - SpatieMediaLibraryFileUpload::make('logo') - ->label(__('organization.field.logo')) - ->collection('logo') - ->columnSpanFull(), - - SpatieMediaLibraryFileUpload::make('organization_header') - ->label(__('organization.field.organization_header')) - ->collection('organization_header') - ->columnSpanFull(), - ]), - ]), - ]), + ->content(__('institution.placeholders.ngo_admins')), - Step::make('ngo_admin') - ->schema([ - Section::make() + Repeater::make('admins') ->maxWidth('3xl') + ->hiddenLabel() + ->columns() + ->minItems(1) + ->relationship('admins') + ->addActionLabel(__('institution.actions.add_admin')) ->schema([ - Repeater::make('admins') - ->columns() - ->minItems(1) - ->relationship('admins') - ->schema([ - TextInput::make('first_name') - ->label(__('organization.field.name')), - - TextInput::make('last_name') - ->label(__('organization.field.last_name')), - - TextInput::make('email') - ->label(__('organization.field.email')), - - TextInput::make('phone') - ->label(__('organization.field.phone')), + ...EditUserInstitution::getSchema(), - Hidden::make('ngo_admin') - ->default(1), - ]), + Hidden::make('ngo_admin') + ->default(1), ]), ]), ]; diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php deleted file mode 100644 index 778c9b00..00000000 --- a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitution.php +++ /dev/null @@ -1,21 +0,0 @@ -schema(self::getSchema()); + } + + public static function getSchema(): array + { + return [ + Repeater::make('organizations') + ->maxWidth('3xl') + ->hiddenLabel() + ->columns() + ->minItems(1) + ->relationship('organizations') + ->addActionLabel(__('institution.actions.add_organization')) + ->schema([ + TextInput::make('name') + ->label(__('institution.labels.center_name')), + + TextInput::make('short_name') + ->label(__('organization.field.short_name')), + + TextInput::make('main_activity') + ->label(__('organization.field.main_activity')) + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('social_service_licensing_certificate') + ->label(__('institution.labels.social_service_licensing_certificate')) + ->helperText(__('institution.helper_texts.social_service_licensing_certificate')) + ->collection('social_service_licensing_certificate') + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('logo') + ->label(__('institution.labels.logo_center')) + ->helperText(__('institution.helper_texts.logo')) + ->collection('logo') + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('organization_header') + ->label(__('institution.labels.organization_header')) + ->helperText(__('institution.helper_texts.organization_header')) + ->collection('organization_header') + ->columnSpanFull(), + ]), + ]; + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php new file mode 100644 index 00000000..e5fdac92 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php @@ -0,0 +1,94 @@ + $this->getRecord()]); + } + + public function form(Form $form): Form + { + return $form->schema(self::getSchema()); + } + + public static function getSchema(): array + { + return [ + Section::make() + ->maxWidth('3xl') + ->columns() + ->schema([ + TextInput::make('name') + ->label(__('organization.field.name')), + + TextInput::make('short_name') + ->label(__('organization.field.short_name')), + + Select::make('type') + ->label(__('organization.field.type')) + ->options(OrganizationType::options()) + ->enum(OrganizationType::class), + + TextInput::make('cif') + ->label(__('organization.field.cif')) + ->rule(new ValidCIF), + + TextInput::make('main_activity') + ->label(__('organization.field.main_activity')), + + Location::make() + ->city() + ->required(), + + TextInput::make('address') + ->label(__('organization.field.address')) + ->maxLength(200) + ->required(), + + TextInput::make('phone') + ->label(__('organization.field.phone')) + ->tel(), + + TextInput::make('reprezentative_name') + ->label(__('organization.field.reprezentative_name')), + + TextInput::make('reprezentative_email') + ->label(__('organization.field.reprezentative_email')), + + TextInput::make('website') + ->label(__('organization.field.website')) + ->url(), + + SpatieMediaLibraryFileUpload::make('organization_status') + ->label(__('institution.labels.organization_status')) + ->helperText(__('institution.helper_texts.organization_status')) + ->collection('organization_status') + ->columnSpanFull(), + + SpatieMediaLibraryFileUpload::make('social_service_provider_certificate') + ->label(__('institution.labels.social_service_provider_certificate')) + ->helperText(__('institution.helper_texts.social_service_provider_certificate')) + ->collection('social_service_provider_certificate') + ->columnSpanFull(), + ]), + ]; + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/ListInstitutions.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/ListInstitutions.php index d01f52b8..899e7b0b 100644 --- a/app/Filament/Admin/Resources/InstitutionResource/Pages/ListInstitutions.php +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/ListInstitutions.php @@ -7,15 +7,27 @@ use App\Filament\Admin\Resources\InstitutionResource; use Filament\Actions; use Filament\Resources\Pages\ListRecords; +use Illuminate\Contracts\Support\Htmlable; class ListInstitutions extends ListRecords { protected static string $resource = InstitutionResource::class; + public function getTitle(): string|Htmlable + { + return __('institution.headings.list_title'); + } + + public function getBreadcrumbs(): array + { + return []; + } + protected function getHeaderActions(): array { return [ - Actions\CreateAction::make(), + Actions\CreateAction::make() + ->label(__('institution.actions.create')), ]; } } diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php new file mode 100644 index 00000000..a94c020d --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php @@ -0,0 +1,201 @@ +schema([ + Tabs::make() + ->persistTabInQueryString() + ->tabs([ + Tab::make(__('institution.headings.institution_details')) + ->schema([ + Section::make(__('institution.headings.institution_details')) + ->headerActions([ + Action::make('edit') + ->label(__('general.action.edit')) + ->icon('heroicon-o-pencil') + ->link() + ->url(self::$resource::getUrl('edit_institution_details', ['record' => $this->getRecord()])), + ]) + ->maxWidth('3xl') + ->columns() + ->schema([ + TextEntry::make('name') + ->label(__('organization.field.name')), + + TextEntry::make('short_name') + ->label(__('organization.field.short_name')), + + TextEntry::make('type') + ->label(__('organization.field.type')), + + TextEntry::make('cif') + ->label(__('organization.field.cif')), + + TextEntry::make('main_activity') + ->label(__('organization.field.main_activity')), + + Location::make() + ->city(), + + TextEntry::make('address') + ->label(__('organization.field.address')), + + TextEntry::make('phone') + ->label(__('organization.field.phone')), + + TextEntry::make('reprezentative_name') + ->label(__('organization.field.reprezentative_name')), + + TextEntry::make('reprezentative_email') + ->label(__('organization.field.reprezentative_email')), + + TextEntry::make('website') + ->label(__('organization.field.website')), + + TextEntry::make('organization_status') + ->label(__('institution.labels.organization_status')) + ->columnSpanFull(), + + TextEntry::make('social_service_provider_certificate') + ->label(__('institution.labels.social_service_provider_certificate')) + ->columnSpanFull(), + ]), + ]), + + Tab::make(__('institution.headings.center_details')) + ->schema([ + Section::make() + ->schema([ + SectionHeader::make('center_details') + ->state(__('institution.headings.center_details')) + ->action( + Action::make('edit_centers') + ->label(__('general.action.edit')) + ->icon('heroicon-o-pencil') + ->link() + ->url(self::$resource::getUrl('edit_institution_centers', ['record' => $this->getRecord()])) + ), + + RepeatableEntry::make('organizations') + ->maxWidth('3xl') + ->hiddenLabel() + ->columns() + ->schema([ + TextEntry::make('name') + ->label(__('institution.labels.center_name')), + + TextEntry::make('short_name') + ->label(__('organization.field.short_name')), + + TextEntry::make('main_activity') + ->label(__('organization.field.main_activity')) + ->columnSpanFull(), + + TextEntry::make('social_service_licensing_certificate') + ->label(__('institution.labels.social_service_licensing_certificate')) + ->columnSpanFull(), + + TextEntry::make('logo') + ->label(__('institution.labels.logo_center')) + ->columnSpanFull(), + + TextEntry::make('organization_header') + ->label(__('institution.labels.organization_header')) + ->columnSpanFull(), + ]), + ]), + + ]), + + Tab::make(__('institution.headings.ngo_admin')) + ->schema([ + Section::make(__('institution.headings.admin_users')) + ->headerActions([ + Action::make('add_ngo_admin') + ->label(__('institution.actions.add_ngo_admin')) + ->form([ + TextInput::make('first_name') + ->label(__('institution.labels.first_name')), + + TextInput::make('last_name') + ->label(__('institution.labels.last_name')), + + TextInput::make('email') + ->label(__('institution.labels.email')), + + TextInput::make('phone') + ->label(__('institution.labels.phone')), + + Hidden::make('ngo_admin') + ->default(1), + ]), + ]) + ->schema([ + RepeatableEntry::make('admins') + ->hiddenLabel() + ->action(Action::make('view_user')) + ->columns(4) + ->schema([ + SectionHeader::make('admin_users') + ->action( + Action::make('view_user') + ->label(__('general.action.view_details')) + ->link() + ->url( + fn ($record) => self::$resource::getUrl('user.view', [ + 'parent' => $this->getRecord(), + 'record' => $record, + ]) + ), + ), + + TextEntry::make('first_name') + ->label(__('institution.labels.first_name')), + + TextEntry::make('last_name') + ->label(__('institution.labels.last_name')), + + TextEntry::make('email') + ->label(__('institution.labels.email')), + + TextEntry::make('phone') + ->label(__('institution.labels.phone')), + ]), + ]), + ]), + ]), + ]); + } +} diff --git a/app/Filament/Admin/Resources/UserInstitutionResource.php b/app/Filament/Admin/Resources/UserInstitutionResource.php new file mode 100644 index 00000000..ee3723e7 --- /dev/null +++ b/app/Filament/Admin/Resources/UserInstitutionResource.php @@ -0,0 +1,25 @@ + Pages\ListUserInstitutions::route('/'), + ]; + } +} diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Actions/ActivateUserAction.php b/app/Filament/Admin/Resources/UserInstitutionResource/Actions/ActivateUserAction.php new file mode 100644 index 00000000..3c31a9a7 --- /dev/null +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Actions/ActivateUserAction.php @@ -0,0 +1,39 @@ +visible(fn (User $record) => $record->isInactive()); + + $this->label(__('user.actions.activate')); + + $this->color('success'); + + $this->outlined(); + + $this->icon('heroicon-o-arrow-path'); + + $this->modalWidth('md'); + + $this->action(function (User $record) { + $record->activate(); + $this->success(); + }); + + } +} diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Actions/DeactivateUserAction.php b/app/Filament/Admin/Resources/UserInstitutionResource/Actions/DeactivateUserAction.php new file mode 100644 index 00000000..f0c620f8 --- /dev/null +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Actions/DeactivateUserAction.php @@ -0,0 +1,42 @@ +visible(fn (User $record) => $record->isActive()); + + $this->label(__('user.actions.deactivate')); + + $this->color('danger'); + + $this->outlined(); + + $this->icon('heroicon-o-user-minus'); + + $this->modalHeading(__('user.action_deactivate_confirm.title')); + + $this->modalWidth('md'); + + $this->action(function (User $record) { + $record->deactivate(); + $this->success(); + }); + + $this->successNotificationTitle(__('user.action_deactivate_confirm.success')); + } +} diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Actions/ResendInvitationAction.php b/app/Filament/Admin/Resources/UserInstitutionResource/Actions/ResendInvitationAction.php new file mode 100644 index 00000000..08a13480 --- /dev/null +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Actions/ResendInvitationAction.php @@ -0,0 +1,61 @@ +visible(fn (User $record) => $record->isPending()); + + $this->label(__('user.actions.resend_invitation')); + + $this->icon('heroicon-o-envelope-open'); + + $this->modalHeading(__('user.action_resend_invitation_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->sendWelcomeNotification(); + $this->success(); + }); + + $this->successNotificationTitle(__('user.action_resend_invitation_confirm.success')); + + $this->failureNotification( + fn (Notification $notification) => $notification + ->danger() + ->title(__('user.action_resend_invitation_confirm.failure_title')) + ->body(__('user.action_resend_invitation_confirm.failure_body')) + ); + } + + private function getRateLimiterKey(User $user): string + { + return 'resend-invitation:' . $user->id; + } +} diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php new file mode 100644 index 00000000..f7e28fdd --- /dev/null +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php @@ -0,0 +1,51 @@ +schema([ + Section::make() + ->maxWidth('3xl') + ->columns() + ->schema(self::getSchema()), + ]); + } + + public static function getSchema(): array + { + return [ + TextInput::make('first_name') + ->label(__('institution.labels.first_name')), + + TextInput::make('last_name') + ->label(__('institution.labels.last_name')), + + TextInput::make('email') + ->label(__('institution.labels.email')), + + TextInput::make('phone') + ->label(__('institution.labels.phone')), + ]; + } +} diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php new file mode 100644 index 00000000..875c5982 --- /dev/null +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php @@ -0,0 +1,85 @@ + user + return []; + } + + public function getTitle(): string|Htmlable + { + return $this->getRecord()->full_name; + } + + protected function getHeaderActions(): array + { + return [ + ActivateUserAction::make(), + + DeactivateUserAction::make(), + + ResendInvitationAction::make(), + ]; + } + + public function infolist(Infolist $infolist): Infolist + { + return $infolist->schema([ + Section::make() + ->maxWidth('3xl') + ->columns() + ->schema([ + TextEntry::make('status') + ->formatStateUsing(fn ($state) => $state === '-' ? $state : $state->label()), + TextEntry::make('updated_at'), + ]), + Section::make() + ->maxWidth('3xl') + ->columns() + ->schema([ + SectionHeader::make('edit_user') + ->action( + Action::make('edit') + ->label(__('general.action.edit')) + ->link() + ->url(self::getParentResource()::getUrl('user.edit', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + ])) + ), + + 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')), + ]), + ]); + } +} diff --git a/app/Models/Institution.php b/app/Models/Institution.php index 8907ae54..ab508e41 100644 --- a/app/Models/Institution.php +++ b/app/Models/Institution.php @@ -6,6 +6,7 @@ use App\Concerns\HasSlug; use App\Concerns\HasUlid; +use App\Enums\InstitutionStatus; use App\Enums\OrganizationType; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; @@ -38,10 +39,12 @@ class Institution extends Model implements HasMedia 'reprezentative_email', 'phone', 'website', + 'status', ]; protected $casts = [ 'type' => OrganizationType::class, + 'status' => InstitutionStatus::class, ]; protected function getSlugSource(): string @@ -88,4 +91,24 @@ public function getCountyAndCityAttribute(): string { return $this->city?->name . ' (' . $this->county?->name . ')'; } + + public function inactivate(): void + { + $this->update(['status' => InstitutionStatus::INACTIVE->value]); + } + + public function isInactivated(): bool + { + return InstitutionStatus::isValue($this->status, InstitutionStatus::INACTIVE); + } + + public function activate(): void + { + $this->update(['status' => InstitutionStatus::ACTIVE->value]); + } + + public function isActivated(): bool + { + return InstitutionStatus::isValue($this->status, InstitutionStatus::ACTIVE); + } } diff --git a/database/factories/InstitutionFactory.php b/database/factories/InstitutionFactory.php index af941b56..290ff945 100644 --- a/database/factories/InstitutionFactory.php +++ b/database/factories/InstitutionFactory.php @@ -4,6 +4,7 @@ namespace Database\Factories; +use App\Enums\InstitutionStatus; use App\Enums\OrganizationType; use App\Models\City; use App\Models\Institution; @@ -38,6 +39,8 @@ public function definition(): array 'reprezentative_name' => fake()->name(), 'reprezentative_email' => fake()->safeEmail(), + + 'status' => fake()->randomElement(InstitutionStatus::values()), ]; } diff --git a/database/migrations/2023_10_28_192350_create_institutions_table.php b/database/migrations/2023_10_28_192350_create_institutions_table.php index 90953610..06e66d46 100644 --- a/database/migrations/2023_10_28_192350_create_institutions_table.php +++ b/database/migrations/2023_10_28_192350_create_institutions_table.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use App\Enums\InstitutionStatus; use App\Models\City; use App\Models\County; use Illuminate\Database\Migrations\Migration; @@ -35,6 +36,8 @@ public function up(): void $table->string('phone')->nullable(); $table->string('website')->nullable(); + $table->string('status')->default(InstitutionStatus::PENDING->value); + $table->timestamps(); }); } diff --git a/lang/ro/enum.php b/lang/ro/enum.php index 7ac9881a..35b7b94c 100644 --- a/lang/ro/enum.php +++ b/lang/ro/enum.php @@ -293,7 +293,7 @@ 'maintenance_sources' => [ 'relationship_income' => 'Venitul existent în cadrul relației', 'alimony' => 'Pensie alimentară', - ], + ], 'activity_description' => [ 'created' => 'Creat', 'retrieved' => 'Vizualizat', @@ -326,4 +326,10 @@ 'no' => 'Nu', 'unknown' => 'Nu știe/ Nu răspunde', ], + + 'institution_status' => [ + 'active' => 'Activ', + 'inactive' => 'Suspendat', + 'pending' => 'În așteptare', + ], ]; diff --git a/lang/ro/institution.php b/lang/ro/institution.php index a6354684..2933de48 100644 --- a/lang/ro/institution.php +++ b/lang/ro/institution.php @@ -10,5 +10,48 @@ 'cases' => 'Cazuri', 'specialists' => 'Specialiști', 'status' => 'Status', + 'list_title' => 'Utilizatori instituționali', + 'empty_state' => 'Niciun utilizator instituțional identificat.', + 'institution_details' => 'Detalii organizație', + 'center_details' => 'Centre', + 'ngo_admin' => 'Administrator', + 'inactivate' => 'Dezactivează organizație', + 'admin_users' => 'Utilizatori de tip administrator', + ], + + 'labels' => [ + 'organization_status' => 'Statut organizație sau hotărâre de înființare', + 'social_service_provider_certificate' => 'Certificat furnizor de servicii sociale', + 'center_name' => 'Nume centru', + 'social_service_licensing_certificate' => 'Certificat de licențiere serviciu social', + 'logo_center' => 'Logo centru', + 'organization_header' => 'Antet centru', + 'first_name' => 'Nume', + 'last_name' => 'Prenume', + 'email' => 'Email', + 'phone' => 'Telefon', + 'inactivate' => 'Odată dezactivată o organizație, utilizatorii acesteia nu vor mai ave acces în platformă. Toate datele asociate organizației vor rămâne în baza de date. Pentru a oferi din nou acces utilizatorilor, organizația va trebui Reactivată din profilul acesteia.', + ], + + 'actions' => [ + 'create' => 'Adaugă o instituție', + 'add_organization' => 'Adaugă încă un centru', + 'add_admin' => 'Adaugă încă un administrator', + 'activate' => 'Reactivează organizație', + 'inactivate' => 'Deactivează organizație', + 'add_ngo_admin' => 'Adaugă administrator', + ], + + 'placeholders' => [ + 'center_details' => 'Dacă instituția are multiple centre acreditate pentru servicii diferite și necesită menținearea unor baze de date diferite de beneficiari, se pot crea tenants (profile) diferite pentru fiecare dintre acestea.', + 'ngo_admins' => 'Adăugați cel puțin un rol de administrator în sistem. Această persoană are drepturi depline asupra întregii aplicații Sunrise pentru toate centrele instituției (ale organizației). Un email de invitație va fi transmis administratorului odată cu finalizarea adăugării instituției.', + ], + + 'helper_texts' => [ + 'organization_status' => 'Încarcă statutul în format .pdf, .jpg sau .png', + 'social_service_provider_certificate' => 'Încarcă certificatul de furnizor de servicii sociale în format .pdf, .jpg sau .png', + 'social_service_licensing_certificate' => 'Încarcă certificatul de licențiere pentru serviciul social în format .pdf, .jpg sau .png', + 'logo' => 'Încarcă un logo pentru centru, care să fie folosit în interfață', + 'organization_header' => 'Încarcă un antet pentru centru, care să fie folosit pentru fișiele exportate', ], ]; diff --git a/lang/ro/user.php b/lang/ro/user.php index 97cedbc4..c15f5ff3 100644 --- a/lang/ro/user.php +++ b/lang/ro/user.php @@ -53,6 +53,7 @@ 'deactivate' => 'Deactivează cont', 'reset_password' => 'Resetează parola', 'resend_invitation' => 'Retrimite invitația', + 'activate' => 'Reactivează cont', ], 'action_resend_invitation_confirm' => [ From 7a58ddb7a2f0e07ef9d0193b0aebf0adaad506fe Mon Sep 17 00:00:00 2001 From: Alex Popa Date: Wed, 30 Oct 2024 23:37:17 +0200 Subject: [PATCH 4/7] super admin institutions --- .../AdminsRelationManager.php | 90 +++++++++++++++++++ .../OrganizationsRelationManager.php | 74 +++++++++++++++ .../Pages/ListUserInstitutions.php | 21 +++++ 3 files changed, 185 insertions(+) create mode 100644 app/Filament/Admin/Resources/InstitutionResource/RelationManagers/AdminsRelationManager.php create mode 100644 app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php create mode 100644 app/Filament/Admin/Resources/UserInstitutionResource/Pages/ListUserInstitutions.php diff --git a/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/AdminsRelationManager.php b/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/AdminsRelationManager.php new file mode 100644 index 00000000..6565ef96 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/AdminsRelationManager.php @@ -0,0 +1,90 @@ +schema([ + TextInput::make('first_name') + ->label(__('institution.labels.first_name')), + + TextInput::make('last_name') + ->label(__('institution.labels.last_name')), + + TextInput::make('email') + ->label(__('institution.labels.email')), + + TextInput::make('phone') + ->label(__('institution.labels.phone')), + + Hidden::make('ngo_admin') + ->default(1), + ]); + } + + public function table(Table $table): Table + { + return $table + ->heading(__('institution.headings.admin_users')) + ->columns([ + TextColumn::make('first_name') + ->label(__('institution.labels.first_name')), + + TextColumn::make('last_name') + ->label(__('institution.labels.last_name')), + + TextColumn::make('roles') + ->label(__('institution.labels.roles')), + + TextColumn::make('status') + ->label(__('institution.labels.account_status')), + + TextColumn::make('last_login_at') + ->label(__('institution.labels.last_login_at')), + ]) + ->headerActions([ + CreateAction::make() + ->label(__('institution.actions.add_ngo_admin')) + ->modalHeading(__('institution.actions.add_ngo_admin')) + ->createAnother(false), + ]) + ->actions([ + ViewAction::make() + ->label(__('general.action.view_details')) + ->url( + fn ($record) => InstitutionResource::getUrl('user.view', [ + 'parent' => $this->getOwnerRecord(), + 'record' => $record, + ]) + ), + ]); + } + + public static function getTitle(Model $ownerRecord, string $pageClass): string + { + return __('institution.headings.ngo_admin'); + } +} diff --git a/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php b/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php new file mode 100644 index 00000000..6ecb08d8 --- /dev/null +++ b/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php @@ -0,0 +1,74 @@ +schema([ + Section::make() + ->maxWidth('3xl') + ->schema([ + SectionHeader::make('center_details') + ->state(__('institution.headings.center_details')) + ->action( + Action::make('edit_centers') + ->label(__('general.action.edit')) + ->icon('heroicon-o-pencil') + ->link() + ->url(InstitutionResource::getUrl('edit_institution_centers', ['record' => $this->getOwnerRecord()])) + ), + + RepeatableEntry::make('organizations') + ->hiddenLabel() + ->columns() + ->schema([ + TextEntry::make('name') + ->label(__('institution.labels.center_name')), + + TextEntry::make('short_name') + ->label(__('organization.field.short_name')), + + TextEntry::make('main_activity') + ->label(__('organization.field.main_activity')) + ->columnSpanFull(), + + TextEntry::make('social_service_licensing_certificate') + ->label(__('institution.labels.social_service_licensing_certificate')) + ->columnSpanFull(), + + TextEntry::make('logo') + ->label(__('institution.labels.logo_center')) + ->columnSpanFull(), + + TextEntry::make('organization_header') + ->label(__('institution.labels.organization_header')) + ->columnSpanFull(), + ]), + ]), + + ])->state(['organizations' => $this->getOwnerRecord()->organizations->toArray()]); + } + + public static function getTitle(Model $ownerRecord, string $pageClass): string + { + return __('institution.headings.center_details'); + } +} diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ListUserInstitutions.php b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ListUserInstitutions.php new file mode 100644 index 00000000..67c4c425 --- /dev/null +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ListUserInstitutions.php @@ -0,0 +1,21 @@ + Date: Wed, 30 Oct 2024 23:37:34 +0200 Subject: [PATCH 5/7] super admin institutions --- .../Pages/EditInstitutionCenters.php | 22 ++ .../Pages/EditInstitutionDetails.php | 14 + .../Pages/ViewInstitution.php | 252 ++++++------------ .../Resources/UserInstitutionResource.php | 2 + .../Pages/EditUserInstitution.php | 24 +- .../Pages/ViewUserInstitution.php | 19 +- app/Models/User.php | 17 ++ lang/ro/institution.php | 3 + .../infolist-relation-manager.blade.php | 11 + 9 files changed, 194 insertions(+), 170 deletions(-) create mode 100644 resources/views/infolists/infolist-relation-manager.blade.php diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionCenters.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionCenters.php index bc3ad1ba..edba27a0 100644 --- a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionCenters.php +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionCenters.php @@ -10,11 +10,33 @@ use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Resources\Pages\EditRecord; +use Illuminate\Contracts\Support\Htmlable; class EditInstitutionCenters extends EditRecord { protected static string $resource = InstitutionResource::class; + protected function getRedirectUrl(): ?string + { + return self::$resource::getUrl('view', [ + 'record' => $this->getRecord(), + 'activeRelationManager' => 'organizations', + ]); + } + + public function getBreadcrumbs(): array + { + return [ + InstitutionResource::getUrl() => __('institution.headings.list_title'), + InstitutionResource::getUrl('view', ['record' => $this->getRecord()]) => $this->getRecord()->name, + ]; + } + + public function getTitle(): string|Htmlable + { + return $this->getRecord()->name; + } + public function form(Form $form): Form { return $form->schema(self::getSchema()); diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php index e5fdac92..5e6201cb 100644 --- a/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/EditInstitutionDetails.php @@ -14,6 +14,7 @@ use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Resources\Pages\EditRecord; +use Illuminate\Contracts\Support\Htmlable; class EditInstitutionDetails extends EditRecord { @@ -24,6 +25,19 @@ protected function getRedirectUrl(): ?string return self::$resource::getUrl('view', ['record' => $this->getRecord()]); } + public function getBreadcrumbs(): array + { + return [ + InstitutionResource::getUrl() => __('institution.headings.list_title'), + InstitutionResource::getUrl('view', ['record' => $this->getRecord()]) => $this->getRecord()->name, + ]; + } + + public function getTitle(): string|Htmlable + { + return $this->getRecord()->name; + } + public function form(Form $form): Form { return $form->schema(self::getSchema()); diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php index a94c020d..142d47ce 100644 --- a/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php @@ -5,28 +5,37 @@ namespace App\Filament\Admin\Resources\InstitutionResource\Pages; use App\Filament\Admin\Resources\InstitutionResource; +use App\Filament\Admin\Resources\InstitutionResource\Actions\ActivateInstitution; use App\Filament\Admin\Resources\InstitutionResource\Actions\InactivateInstitution; use App\Infolists\Components\Location; -use App\Infolists\Components\SectionHeader; -use Filament\Forms\Components\Hidden; -use Filament\Forms\Components\TextInput; use Filament\Infolists\Components\Actions\Action; -use Filament\Infolists\Components\RepeatableEntry; use Filament\Infolists\Components\Section; -use Filament\Infolists\Components\Tabs; -use Filament\Infolists\Components\Tabs\Tab; use Filament\Infolists\Components\TextEntry; use Filament\Infolists\Infolist; use Filament\Resources\Pages\ViewRecord; +use Illuminate\Contracts\Support\Htmlable; class ViewInstitution extends ViewRecord { protected static string $resource = InstitutionResource::class; + public function getBreadcrumbs(): array + { + return [ + InstitutionResource::getUrl() => __('institution.headings.list_title'), + InstitutionResource::getUrl('view', ['record' => $this->getRecord()]) => $this->getRecord()->name, + ]; + } + + public function getTitle(): string|Htmlable + { + return $this->getRecord()->name; + } + protected function getActions(): array { return [ - InstitutionResource\Actions\ActivateInstitution::make(), + ActivateInstitution::make(), InactivateInstitution::make(), ]; @@ -35,167 +44,76 @@ protected function getActions(): array public function infolist(Infolist $infolist): Infolist { return $infolist->schema([ - Tabs::make() - ->persistTabInQueryString() - ->tabs([ - Tab::make(__('institution.headings.institution_details')) - ->schema([ - Section::make(__('institution.headings.institution_details')) - ->headerActions([ - Action::make('edit') - ->label(__('general.action.edit')) - ->icon('heroicon-o-pencil') - ->link() - ->url(self::$resource::getUrl('edit_institution_details', ['record' => $this->getRecord()])), - ]) - ->maxWidth('3xl') - ->columns() - ->schema([ - TextEntry::make('name') - ->label(__('organization.field.name')), - - TextEntry::make('short_name') - ->label(__('organization.field.short_name')), - - TextEntry::make('type') - ->label(__('organization.field.type')), - - TextEntry::make('cif') - ->label(__('organization.field.cif')), - - TextEntry::make('main_activity') - ->label(__('organization.field.main_activity')), - - Location::make() - ->city(), - - TextEntry::make('address') - ->label(__('organization.field.address')), - - TextEntry::make('phone') - ->label(__('organization.field.phone')), - - TextEntry::make('reprezentative_name') - ->label(__('organization.field.reprezentative_name')), - - TextEntry::make('reprezentative_email') - ->label(__('organization.field.reprezentative_email')), - - TextEntry::make('website') - ->label(__('organization.field.website')), - - TextEntry::make('organization_status') - ->label(__('institution.labels.organization_status')) - ->columnSpanFull(), - - TextEntry::make('social_service_provider_certificate') - ->label(__('institution.labels.social_service_provider_certificate')) - ->columnSpanFull(), - ]), - ]), - - Tab::make(__('institution.headings.center_details')) - ->schema([ - Section::make() - ->schema([ - SectionHeader::make('center_details') - ->state(__('institution.headings.center_details')) - ->action( - Action::make('edit_centers') - ->label(__('general.action.edit')) - ->icon('heroicon-o-pencil') - ->link() - ->url(self::$resource::getUrl('edit_institution_centers', ['record' => $this->getRecord()])) - ), - - RepeatableEntry::make('organizations') - ->maxWidth('3xl') - ->hiddenLabel() - ->columns() - ->schema([ - TextEntry::make('name') - ->label(__('institution.labels.center_name')), - - TextEntry::make('short_name') - ->label(__('organization.field.short_name')), - - TextEntry::make('main_activity') - ->label(__('organization.field.main_activity')) - ->columnSpanFull(), - - TextEntry::make('social_service_licensing_certificate') - ->label(__('institution.labels.social_service_licensing_certificate')) - ->columnSpanFull(), - - TextEntry::make('logo') - ->label(__('institution.labels.logo_center')) - ->columnSpanFull(), - - TextEntry::make('organization_header') - ->label(__('institution.labels.organization_header')) - ->columnSpanFull(), - ]), - ]), - - ]), - - Tab::make(__('institution.headings.ngo_admin')) - ->schema([ - Section::make(__('institution.headings.admin_users')) - ->headerActions([ - Action::make('add_ngo_admin') - ->label(__('institution.actions.add_ngo_admin')) - ->form([ - TextInput::make('first_name') - ->label(__('institution.labels.first_name')), - - TextInput::make('last_name') - ->label(__('institution.labels.last_name')), - - TextInput::make('email') - ->label(__('institution.labels.email')), - - TextInput::make('phone') - ->label(__('institution.labels.phone')), - - Hidden::make('ngo_admin') - ->default(1), - ]), - ]) - ->schema([ - RepeatableEntry::make('admins') - ->hiddenLabel() - ->action(Action::make('view_user')) - ->columns(4) - ->schema([ - SectionHeader::make('admin_users') - ->action( - Action::make('view_user') - ->label(__('general.action.view_details')) - ->link() - ->url( - fn ($record) => self::$resource::getUrl('user.view', [ - 'parent' => $this->getRecord(), - 'record' => $record, - ]) - ), - ), - - TextEntry::make('first_name') - ->label(__('institution.labels.first_name')), - - TextEntry::make('last_name') - ->label(__('institution.labels.last_name')), - - TextEntry::make('email') - ->label(__('institution.labels.email')), - - TextEntry::make('phone') - ->label(__('institution.labels.phone')), - ]), - ]), - ]), + Section::make(__('institution.headings.institution_details')) + ->headerActions([ + Action::make('edit') + ->label(__('general.action.edit')) + ->icon('heroicon-o-pencil') + ->link() + ->url(self::$resource::getUrl('edit_institution_details', ['record' => $this->getRecord()])), + ]) + ->maxWidth('3xl') + ->columns() + ->schema([ + TextEntry::make('name') + ->label(__('organization.field.name')), + + TextEntry::make('short_name') + ->label(__('organization.field.short_name')), + + TextEntry::make('type') + ->label(__('organization.field.type')), + + TextEntry::make('cif') + ->label(__('organization.field.cif')), + + TextEntry::make('main_activity') + ->label(__('organization.field.main_activity')), + + Location::make() + ->city(), + + TextEntry::make('address') + ->label(__('organization.field.address')), + + TextEntry::make('phone') + ->label(__('organization.field.phone')), + + TextEntry::make('reprezentative_name') + ->label(__('organization.field.reprezentative_name')), + + TextEntry::make('reprezentative_email') + ->label(__('organization.field.reprezentative_email')), + + TextEntry::make('website') + ->label(__('organization.field.website')), + + TextEntry::make('organization_status') + ->label(__('institution.labels.organization_status')) + ->columnSpanFull(), + + TextEntry::make('social_service_provider_certificate') + ->label(__('institution.labels.social_service_provider_certificate')) + ->columnSpanFull(), ]), ]); } + + public function hasCombinedRelationManagerTabsWithContent(): bool + { + return true; + } + + public function getContentTabLabel(): ?string + { + return __('institution.headings.institution_details'); + } + + public function getRelationManagers(): array + { + return [ + 'organizations' => InstitutionResource\RelationManagers\OrganizationsRelationManager::make(), + 'admins' => InstitutionResource\RelationManagers\AdminsRelationManager::make(), + ]; + } } diff --git a/app/Filament/Admin/Resources/UserInstitutionResource.php b/app/Filament/Admin/Resources/UserInstitutionResource.php index ee3723e7..ef1ee5ba 100644 --- a/app/Filament/Admin/Resources/UserInstitutionResource.php +++ b/app/Filament/Admin/Resources/UserInstitutionResource.php @@ -14,6 +14,8 @@ class UserInstitutionResource extends Resource public static string $parentResource = InstitutionResource::class; + protected static bool $shouldRegisterNavigation = false; + protected static ?string $navigationIcon = 'heroicon-o-rectangle-stack'; public static function getPages(): array diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php index f7e28fdd..43fad57d 100644 --- a/app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/EditUserInstitution.php @@ -5,11 +5,13 @@ namespace App\Filament\Admin\Resources\UserInstitutionResource\Pages; use App\Concerns\HasParentResource; +use App\Filament\Admin\Resources\InstitutionResource; use App\Filament\Admin\Resources\UserInstitutionResource; use Filament\Forms\Components\Section; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Resources\Pages\EditRecord; +use Illuminate\Contracts\Support\Htmlable; class EditUserInstitution extends EditRecord { @@ -17,9 +19,29 @@ class EditUserInstitution extends EditRecord protected static string $resource = UserInstitutionResource::class; + protected function getRedirectUrl(): ?string + { + return self::getParentResource()::getUrl('view', [ + 'record' => $this->parent, + 'activeRelationManager' => 'admins', + ]); + } + public function getBreadcrumbs(): array { - return []; + return [ + InstitutionResource::getUrl() => __('institution.headings.list_title'), + InstitutionResource::getUrl('view', ['record' => $this->parent]) => $this->parent->name, + InstitutionResource::getUrl('user.view', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + ]) => $this->getRecord()->full_name, + ]; + } + + public function getTitle(): string|Htmlable + { + return $this->getRecord()->full_name; } public function form(Form $form): Form diff --git a/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php index 875c5982..dc50cced 100644 --- a/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php +++ b/app/Filament/Admin/Resources/UserInstitutionResource/Pages/ViewUserInstitution.php @@ -5,6 +5,7 @@ namespace App\Filament\Admin\Resources\UserInstitutionResource\Pages; use App\Concerns\HasParentResource; +use App\Filament\Admin\Resources\InstitutionResource; use App\Filament\Admin\Resources\UserInstitutionResource; use App\Filament\Admin\Resources\UserInstitutionResource\Actions\ActivateUserAction; use App\Filament\Admin\Resources\UserInstitutionResource\Actions\DeactivateUserAction; @@ -23,10 +24,24 @@ class ViewUserInstitution extends ViewRecord protected static string $resource = UserInstitutionResource::class; + protected function getRedirectUrl(): ?string + { + return self::getParentResource()::getUrl('view', [ + 'record' => $this->parent, + 'activeRelationManager' => 'admins', + ]); + } + public function getBreadcrumbs(): array { - // TODO: add breadcrumb institution -> user - return []; + return [ + InstitutionResource::getUrl() => __('institution.headings.list_title'), + InstitutionResource::getUrl('view', ['record' => $this->parent]) => $this->parent->name, + InstitutionResource::getUrl('user.view', [ + 'parent' => $this->parent, + 'record' => $this->getRecord(), + ]) => $this->getRecord()->full_name, + ]; } public function getTitle(): string|Htmlable diff --git a/app/Models/User.php b/app/Models/User.php index 93b54ade..935d9f66 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -66,6 +66,7 @@ class User extends Authenticatable implements FilamentUser, HasAvatar, HasName, 'password_set_at', 'latest_organization_id', 'is_admin', + 'ngo_admin', ]; /** @@ -102,6 +103,17 @@ protected static function booted() static::creating(function (User $model) { $model->setPendingStatus(); }); + + static::created(function (User $model) { + if ($model->institution) { + $model->organizations() + ->attach( + $model->institution + ->organizations + ?->pluck('id') + ); + } + }); } public function organizations(): MorphToMany @@ -114,6 +126,11 @@ public function latestOrganization(): BelongsTo return $this->belongsTo(Organization::class, 'latest_organization_id'); } + public function institution(): BelongsTo + { + return $this->belongsTo(Institution::class); + } + public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() diff --git a/lang/ro/institution.php b/lang/ro/institution.php index 2933de48..013c277b 100644 --- a/lang/ro/institution.php +++ b/lang/ro/institution.php @@ -31,6 +31,9 @@ 'email' => 'Email', 'phone' => 'Telefon', 'inactivate' => 'Odată dezactivată o organizație, utilizatorii acesteia nu vor mai ave acces în platformă. Toate datele asociate organizației vor rămâne în baza de date. Pentru a oferi din nou acces utilizatorilor, organizația va trebui Reactivată din profilul acesteia.', + 'roles' => 'Roluri', + 'account_status' => 'Cont', + 'last_login_at' => 'Ultima accesare', ], 'actions' => [ diff --git a/resources/views/infolists/infolist-relation-manager.blade.php b/resources/views/infolists/infolist-relation-manager.blade.php new file mode 100644 index 00000000..abc22a14 --- /dev/null +++ b/resources/views/infolists/infolist-relation-manager.blade.php @@ -0,0 +1,11 @@ +
+ + + {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::RESOURCE_RELATION_MANAGER_BEFORE, scopes: $this->getRenderHookScopes()) }} + + {{ $this->infolist }} + + {{ \Filament\Support\Facades\FilamentView::renderHook(\Filament\View\PanelsRenderHook::RESOURCE_RELATION_MANAGER_AFTER, scopes: $this->getRenderHookScopes()) }} + + +
From bf0e1133ceeb234fac439aefd044a38aa71ef241 Mon Sep 17 00:00:00 2001 From: Alex Popa Date: Wed, 30 Oct 2024 23:40:25 +0200 Subject: [PATCH 6/7] min --- app/Filament/Admin/Resources/OrganizationResource.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/Filament/Admin/Resources/OrganizationResource.php b/app/Filament/Admin/Resources/OrganizationResource.php index 0d200b21..0d370227 100644 --- a/app/Filament/Admin/Resources/OrganizationResource.php +++ b/app/Filament/Admin/Resources/OrganizationResource.php @@ -22,12 +22,15 @@ use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Table; +// TODO: remove this class OrganizationResource extends Resource { protected static ?string $model = Organization::class; protected static ?string $navigationIcon = 'heroicon-o-building-office-2'; + protected static bool $shouldRegisterNavigation = false; + public static function infolist(Infolist $infolist): Infolist { return $infolist From 362b9ed8329e9dc72968715cff110cb3d55119d7 Mon Sep 17 00:00:00 2001 From: Alex Popa Date: Thu, 31 Oct 2024 00:56:15 +0200 Subject: [PATCH 7/7] Ngo admin - view organization info --- .../Pages/ViewInstitution.php | 67 ++++++++++--------- .../OrganizationsRelationManager.php | 53 ++++++++------- .../Pages/ViewOrganizationTenant.php | 67 +++++++++++++++++++ lang/ro/organization.php | 4 ++ .../pages/view-organization-tenant.blade.php | 7 ++ 5 files changed, 143 insertions(+), 55 deletions(-) create mode 100644 app/Filament/Organizations/Pages/ViewOrganizationTenant.php create mode 100644 resources/views/filament/organizations/pages/view-organization-tenant.blade.php diff --git a/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php b/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php index 142d47ce..d14568b7 100644 --- a/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php +++ b/app/Filament/Admin/Resources/InstitutionResource/Pages/ViewInstitution.php @@ -54,49 +54,54 @@ public function infolist(Infolist $infolist): Infolist ]) ->maxWidth('3xl') ->columns() - ->schema([ - TextEntry::make('name') - ->label(__('organization.field.name')), + ->schema($this->getInfolistSchema()), + ]); + } - TextEntry::make('short_name') - ->label(__('organization.field.short_name')), + public static function getInfolistSchema(): array + { + return [ + TextEntry::make('name') + ->label(__('organization.field.name')), - TextEntry::make('type') - ->label(__('organization.field.type')), + TextEntry::make('short_name') + ->label(__('organization.field.short_name')), - TextEntry::make('cif') - ->label(__('organization.field.cif')), + TextEntry::make('type') + ->label(__('organization.field.type')), - TextEntry::make('main_activity') - ->label(__('organization.field.main_activity')), + TextEntry::make('cif') + ->label(__('organization.field.cif')), - Location::make() - ->city(), + TextEntry::make('main_activity') + ->label(__('organization.field.main_activity')), - TextEntry::make('address') - ->label(__('organization.field.address')), + Location::make() + ->city(), - TextEntry::make('phone') - ->label(__('organization.field.phone')), + TextEntry::make('address') + ->label(__('organization.field.address')), - TextEntry::make('reprezentative_name') - ->label(__('organization.field.reprezentative_name')), + TextEntry::make('phone') + ->label(__('organization.field.phone')), - TextEntry::make('reprezentative_email') - ->label(__('organization.field.reprezentative_email')), + TextEntry::make('reprezentative_name') + ->label(__('organization.field.reprezentative_name')), - TextEntry::make('website') - ->label(__('organization.field.website')), + TextEntry::make('reprezentative_email') + ->label(__('organization.field.reprezentative_email')), - TextEntry::make('organization_status') - ->label(__('institution.labels.organization_status')) - ->columnSpanFull(), + TextEntry::make('website') + ->label(__('organization.field.website')), - TextEntry::make('social_service_provider_certificate') - ->label(__('institution.labels.social_service_provider_certificate')) - ->columnSpanFull(), - ]), - ]); + TextEntry::make('organization_status') + ->label(__('institution.labels.organization_status')) + ->columnSpanFull(), + + TextEntry::make('social_service_provider_certificate') + ->label(__('institution.labels.social_service_provider_certificate')) + ->columnSpanFull(), + ]; } public function hasCombinedRelationManagerTabsWithContent(): bool diff --git a/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php b/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php index 6ecb08d8..4e5e3775 100644 --- a/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php +++ b/app/Filament/Admin/Resources/InstitutionResource/RelationManagers/OrganizationsRelationManager.php @@ -20,6 +20,11 @@ class OrganizationsRelationManager extends RelationManager protected static string $view = 'infolists.infolist-relation-manager'; + public static function getTitle(Model $ownerRecord, string $pageClass): string + { + return __('institution.headings.center_details'); + } + public function infolist(Infolist $infolist): Infolist { return $infolist->schema([ @@ -39,36 +44,36 @@ public function infolist(Infolist $infolist): Infolist RepeatableEntry::make('organizations') ->hiddenLabel() ->columns() - ->schema([ - TextEntry::make('name') - ->label(__('institution.labels.center_name')), + ->schema($this->getOrganizationInfolistSchema()), + ]), - TextEntry::make('short_name') - ->label(__('organization.field.short_name')), + ])->state(['organizations' => $this->getOwnerRecord()->organizations->toArray()]); + } - TextEntry::make('main_activity') - ->label(__('organization.field.main_activity')) - ->columnSpanFull(), + public static function getOrganizationInfolistSchema(): array + { + return [ + TextEntry::make('name') + ->label(__('institution.labels.center_name')), - TextEntry::make('social_service_licensing_certificate') - ->label(__('institution.labels.social_service_licensing_certificate')) - ->columnSpanFull(), + TextEntry::make('short_name') + ->label(__('organization.field.short_name')), - TextEntry::make('logo') - ->label(__('institution.labels.logo_center')) - ->columnSpanFull(), + TextEntry::make('main_activity') + ->label(__('organization.field.main_activity')) + ->columnSpanFull(), - TextEntry::make('organization_header') - ->label(__('institution.labels.organization_header')) - ->columnSpanFull(), - ]), - ]), + TextEntry::make('social_service_licensing_certificate') + ->label(__('institution.labels.social_service_licensing_certificate')) + ->columnSpanFull(), - ])->state(['organizations' => $this->getOwnerRecord()->organizations->toArray()]); - } + TextEntry::make('logo') + ->label(__('institution.labels.logo_center')) + ->columnSpanFull(), - public static function getTitle(Model $ownerRecord, string $pageClass): string - { - return __('institution.headings.center_details'); + TextEntry::make('organization_header') + ->label(__('institution.labels.organization_header')) + ->columnSpanFull(), + ]; } } diff --git a/app/Filament/Organizations/Pages/ViewOrganizationTenant.php b/app/Filament/Organizations/Pages/ViewOrganizationTenant.php new file mode 100644 index 00000000..76c02ee9 --- /dev/null +++ b/app/Filament/Organizations/Pages/ViewOrganizationTenant.php @@ -0,0 +1,67 @@ +schema([ + Section::make() + ->maxWidth('3xl') + ->schema([ + Notice::make('notice') + ->icon('heroicon-s-information-circle') + ->state(__('organization.helper_texts.view_tenant_info')) + ->color('primary'), + + Grid::make() + ->relationship('institution') + ->schema(ViewInstitution::getInfolistSchema()), + + Grid::make() + ->schema(OrganizationsRelationManager::getOrganizationInfolistSchema()), + ]), + + ]) + ->state(Filament::getTenant()->load('institution')->toArray()); + } +} diff --git a/lang/ro/organization.php b/lang/ro/organization.php index c6c94fd5..2a361b86 100644 --- a/lang/ro/organization.php +++ b/lang/ro/organization.php @@ -37,4 +37,8 @@ 'placeholder' => 'Național', ], ], + + 'helper_texts' => [ + 'view_tenant_info' => 'Pentru a modifica/ actualiza informațiile din această secțiune, vă rugăm contactați echipa de administrare Sunrise via email la admin@stopviolențeidomestice.ro ', + ], ]; diff --git a/resources/views/filament/organizations/pages/view-organization-tenant.blade.php b/resources/views/filament/organizations/pages/view-organization-tenant.blade.php new file mode 100644 index 00000000..840d8a6f --- /dev/null +++ b/resources/views/filament/organizations/pages/view-organization-tenant.blade.php @@ -0,0 +1,7 @@ + +
+ + {{ $this->infolist() }} + +
+