From 705dfe6537f1d476e1565c0f034f01af25c4b1a5 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 29 Jan 2026 21:12:04 +0700 Subject: [PATCH 01/20] feat: Add custom cheerful saved notifications to general, SEO, and social media settings pages. --- app/Filament/Pages/Settings/ManageGeneralSettings.php | 7 +++++++ app/Filament/Pages/Settings/ManageSeoSettings.php | 7 +++++++ app/Filament/Pages/Settings/ManageSocialMediaSettings.php | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/app/Filament/Pages/Settings/ManageGeneralSettings.php b/app/Filament/Pages/Settings/ManageGeneralSettings.php index eb0c2ff..1c9f85f 100644 --- a/app/Filament/Pages/Settings/ManageGeneralSettings.php +++ b/app/Filament/Pages/Settings/ManageGeneralSettings.php @@ -2,6 +2,7 @@ namespace App\Filament\Pages\Settings; +use App\Filament\Support\CheerfulNotification; use App\Settings\GeneralSettings; use BackedEnum; use BezhanSalleh\FilamentShield\Traits\HasPageShield; @@ -9,6 +10,7 @@ use Filament\Forms\Components\RichEditor; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; +use Filament\Notifications\Notification; use Filament\Pages\SettingsPage; use Filament\Schemas\Components\Section; use Filament\Schemas\Schema; @@ -98,4 +100,9 @@ public function form(Schema $schema): Schema ]) ->columns(3); } + + public function getSavedNotification(): ?Notification + { + return CheerfulNotification::update()->send(); + } } diff --git a/app/Filament/Pages/Settings/ManageSeoSettings.php b/app/Filament/Pages/Settings/ManageSeoSettings.php index aa7aec8..f0acd9c 100644 --- a/app/Filament/Pages/Settings/ManageSeoSettings.php +++ b/app/Filament/Pages/Settings/ManageSeoSettings.php @@ -2,11 +2,13 @@ namespace App\Filament\Pages\Settings; +use App\Filament\Support\CheerfulNotification; use App\Settings\SeoSettings; use BackedEnum; use BezhanSalleh\FilamentShield\Traits\HasPageShield; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; +use Filament\Notifications\Notification; use Filament\Pages\SettingsPage; use Filament\Schemas\Components\Section; use Filament\Schemas\Schema; @@ -58,4 +60,9 @@ public function form(Schema $schema): Schema ->columnSpanFull(), ]); } + + public function getSavedNotification(): ?Notification + { + return CheerfulNotification::update()->send(); + } } diff --git a/app/Filament/Pages/Settings/ManageSocialMediaSettings.php b/app/Filament/Pages/Settings/ManageSocialMediaSettings.php index 066a41b..1893429 100644 --- a/app/Filament/Pages/Settings/ManageSocialMediaSettings.php +++ b/app/Filament/Pages/Settings/ManageSocialMediaSettings.php @@ -2,10 +2,12 @@ namespace App\Filament\Pages\Settings; +use App\Filament\Support\CheerfulNotification; use App\Settings\SocialMediaSettings; use BackedEnum; use BezhanSalleh\FilamentShield\Traits\HasPageShield; use Filament\Forms\Components\TextInput; +use Filament\Notifications\Notification; use Filament\Pages\SettingsPage; use Filament\Schemas\Components\Grid; use Filament\Schemas\Components\Section; @@ -67,4 +69,9 @@ public function form(Schema $schema): Schema ->columnSpanFull(), ]); } + + public function getSavedNotification(): ?Notification + { + return CheerfulNotification::update()->send(); + } } From dceeabdf7e1eb755f66e403d838a4df9f3b779d7 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 29 Jan 2026 21:20:35 +0700 Subject: [PATCH 02/20] refactor: Adjust password field to set a default value only on creation and be visible only during creation. --- app/Filament/Resources/Master/Users/UserResource.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Filament/Resources/Master/Users/UserResource.php b/app/Filament/Resources/Master/Users/UserResource.php index a3f8387..8d56e1e 100644 --- a/app/Filament/Resources/Master/Users/UserResource.php +++ b/app/Filament/Resources/Master/Users/UserResource.php @@ -88,7 +88,8 @@ public static function form(Schema $schema): Schema ->searchable(), Hidden::make('password') - ->default(fn (?User $record): ?string => $record ? null : config('auth.password_default')), + ->default(fn (): string => config('auth.password_default')) + ->visibleOn('create'), ]) ->columns(1); } From ac36fe3051de5e927346ecfbf6a234f39ba3769f Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 29 Jan 2026 21:28:38 +0700 Subject: [PATCH 03/20] feat(auth): enforce panel access check and notify inactive users --- .agent/workflows/commit.md | 26 ++++++++++++++++++++++++++ app/Filament/Pages/Auth/Login.php | 9 +++++++++ 2 files changed, 35 insertions(+) create mode 100644 .agent/workflows/commit.md diff --git a/.agent/workflows/commit.md b/.agent/workflows/commit.md new file mode 100644 index 0000000..3386851 --- /dev/null +++ b/.agent/workflows/commit.md @@ -0,0 +1,26 @@ +--- +description: Automatic commit message generator and fast AI-powered commit for all current changes +--- + +// turbo-all + +This workflow automatically stages all changes, generates a descriptive commit message, and commits them in one go. + +### Steps: + +1. **Stage All Changes**: Automatically stage all modified and new files. + ```bash + git add . + ``` +2. **Analyze Changes**: Get the diff of staged changes to understand the context. + ```bash + git diff --cached + ``` +3. **Generate & Commit**: Generate a professional message following [Conventional Commits](https://www.conventionalcommits.org/) and execute the commit. + ```bash + git commit -m "" + ``` +4. **Push**: Optionally push the changes. + ```bash + git push + ``` \ No newline at end of file diff --git a/app/Filament/Pages/Auth/Login.php b/app/Filament/Pages/Auth/Login.php index 403980b..c6341b0 100644 --- a/app/Filament/Pages/Auth/Login.php +++ b/app/Filament/Pages/Auth/Login.php @@ -91,6 +91,15 @@ public function authenticate(): ?LoginResponse $this->throwFailureValidationException(); } + if ($user instanceof FilamentUser && (! $user->canAccessPanel(Filament::getCurrentOrDefaultPanel()))) { + CheerfulNotification::danger( + 'Akun Tidak Aktif 🚫', + 'Maaf, akun Anda saat ini tidak aktif. Silakan hubungi Administrator. πŸ“ž' + )->send(); + + throw ValidationException::withMessages([]); + } + if ( filled($this->userUndertakingMultiFactorAuthentication) && (decrypt($this->userUndertakingMultiFactorAuthentication) === $user->getAuthIdentifier()) From 94f1a06b440c5b2bdb8869962844b6d7fdbece1d Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 29 Jan 2026 21:38:08 +0700 Subject: [PATCH 04/20] feat: Filter location and sub-location selections in issue management forms to only display active records. --- .agent/workflows/commit.md | 4 ---- .../IssueManagements/Schemas/IssueManagementForm.php | 4 ++-- app/Models/Location.php | 8 ++++++++ app/Models/SubLocation.php | 8 ++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.agent/workflows/commit.md b/.agent/workflows/commit.md index 3386851..f5d2a41 100644 --- a/.agent/workflows/commit.md +++ b/.agent/workflows/commit.md @@ -19,8 +19,4 @@ ### Steps: 3. **Generate & Commit**: Generate a professional message following [Conventional Commits](https://www.conventionalcommits.org/) and execute the commit. ```bash git commit -m "" - ``` -4. **Push**: Optionally push the changes. - ```bash - git push ``` \ No newline at end of file diff --git a/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php b/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php index 844b619..efcd924 100644 --- a/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php +++ b/app/Filament/Resources/Monitoring/IssueManagements/Schemas/IssueManagementForm.php @@ -35,7 +35,7 @@ public static function configure(Schema $schema): Schema ->schema([ Select::make('location_id') ->label('Lokus') - ->relationship('location', 'name') + ->relationship('location', 'name', fn (Builder $query): Builder => $query->active()) ->searchable() ->preload() ->live() @@ -46,7 +46,7 @@ public static function configure(Schema $schema): Schema Select::make('sub_location_id') ->label('Sub Lokus') - ->relationship('subLocation', 'name', fn (Builder $query, Get $get): Builder => $query->where('location_id', $get('location_id'))) + ->relationship('subLocation', 'name', fn (Builder $query, Get $get): Builder => $query->active()->where('location_id', $get('location_id'))) ->searchable() ->preload() ->required(), diff --git a/app/Models/Location.php b/app/Models/Location.php index d6d328c..7803ed2 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -3,6 +3,8 @@ namespace App\Models; use App\Enums\IsActive; +use Illuminate\Database\Eloquent\Attributes\Scope; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -23,6 +25,12 @@ protected function casts(): array ]; } + #[Scope] + protected function active(Builder $query): void + { + $query->where('is_active', IsActive::ACTIVE); + } + public function issueManagements(): HasMany { return $this->hasMany(IssueManagement::class); diff --git a/app/Models/SubLocation.php b/app/Models/SubLocation.php index dd0b117..49a3b85 100644 --- a/app/Models/SubLocation.php +++ b/app/Models/SubLocation.php @@ -3,6 +3,8 @@ namespace App\Models; use App\Enums\IsActive; +use Illuminate\Database\Eloquent\Attributes\Scope; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -24,6 +26,12 @@ protected function casts(): array ]; } + #[Scope] + protected function active(Builder $query): void + { + $query->where('is_active', IsActive::ACTIVE); + } + public function issueManagements(): HasMany { return $this->hasMany(IssueManagement::class); From 4529992dd2ecb0071f705a1b405489f944c2b84c Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 29 Jan 2026 21:53:26 +0700 Subject: [PATCH 05/20] feat: Add Classification and Location permissions to the Admin Monitoring role and reorder existing permissions. --- database/seeders/ShieldSeeder.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index e57d6d4..0b40b9c 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -522,6 +522,18 @@ public function run(): void "name": "Admin Monitoring", "guard_name": "web", "permissions": [ + "ViewAny:Classification", + "View:Classification", + "Create:Classification", + "Update:Classification", + "Delete:Classification", + "Restore:Classification", + "ForceDelete:Classification", + "ForceDeleteAny:Classification", + "RestoreAny:Classification", + "Replicate:Classification", + "Reorder:Classification", + "View:Dashboard", "ViewAny:IssueManagement", @@ -535,6 +547,18 @@ public function run(): void "RestoreAny:IssueManagement", "Replicate:IssueManagement", "Reorder:IssueManagement", + + "ViewAny:Location", + "View:Location", + "Create:Location", + "Update:Location", + "Delete:Location", + "Restore:Location", + "ForceDelete:Location", + "ForceDeleteAny:Location", + "RestoreAny:Location", + "Replicate:Location", + "Reorder:Location", "ViewAny:MediaMonitoring", "View:MediaMonitoring", From 902c4ad8cf9234030cbda20d63ebbb1edb742f3a Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 29 Jan 2026 21:55:03 +0700 Subject: [PATCH 06/20] fix: remove excessive Cooperation resource permissions from a role in the Shield seeder. --- database/seeders/ShieldSeeder.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index 0b40b9c..edef701 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -683,15 +683,6 @@ public function run(): void "permissions": [ "ViewAny:Cooperation", "View:Cooperation", - "Create:Cooperation", - "Update:Cooperation", - "Delete:Cooperation", - "Restore:Cooperation", - "ForceDelete:Cooperation", - "ForceDeleteAny:Cooperation", - "RestoreAny:Cooperation", - "Replicate:Cooperation", - "Reorder:Cooperation", "View:Dashboard" ] From 5781595fc644ef7c39fc4cb800a3053e8637df13 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 08:58:09 +0700 Subject: [PATCH 07/20] feat: make BroadcastNotification queueable by implementing ShouldQueue and using SerializesModels. --- app/Notifications/BroadcastNotification.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Notifications/BroadcastNotification.php b/app/Notifications/BroadcastNotification.php index 777aaa0..b52de30 100644 --- a/app/Notifications/BroadcastNotification.php +++ b/app/Notifications/BroadcastNotification.php @@ -4,11 +4,14 @@ use Filament\Notifications\Notification as FilamentNotification; use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Notification; -class BroadcastNotification extends Notification +use Illuminate\Queue\SerializesModels; + +class BroadcastNotification extends Notification implements ShouldQueue { - use Queueable; + use Queueable, SerializesModels; public function __construct( protected array $payload From 78246123c5eae4ccf9f6ea54b257774bfa03562d Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 09:59:53 +0700 Subject: [PATCH 08/20] fix: Corrected typo in Journalist permissions within the Shield seeder. --- app/Notifications/BroadcastNotification.php | 1 - database/seeders/ShieldSeeder.php | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/Notifications/BroadcastNotification.php b/app/Notifications/BroadcastNotification.php index b52de30..b5c8e3a 100644 --- a/app/Notifications/BroadcastNotification.php +++ b/app/Notifications/BroadcastNotification.php @@ -6,7 +6,6 @@ use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Notification; - use Illuminate\Queue\SerializesModels; class BroadcastNotification extends Notification implements ShouldQueue diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index edef701..3852cc6 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -731,11 +731,11 @@ public function run(): void "Replicate:DataChangeRequest", "Reorder:DataChangeRequest", - "ViewAny:Journalits", - "View:Journalits", - "Create:Journalits", - "Update:Journalits", - "Delete:Journalits", + "ViewAny:Journalist", + "View:Journalist", + "Create:Journalist", + "Update:Journalist", + "Delete:Journalist", "View:Media", From 9005f9cbecfcfb73c8375115c047dbe58e7e540c Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 13:26:43 +0700 Subject: [PATCH 09/20] feat: Implement company-based verification status for media and journalist data change requests, and refine admin verification UI/UX. --- app/Filament/Pages/AdminVerification.php | 10 +- app/Filament/Pages/Company.php | 3 +- app/Filament/Pages/Media.php | 5 +- .../Actions/CreateJournalistAction.php | 10 +- .../Actions/DeleteJournalistAction.php | 10 +- .../Actions/EditJournalistAction.php | 111 +++++++++--------- .../Manage/Journalists/JournalistResource.php | 11 -- .../Manage/Partners/PartnerResource.php | 16 ++- app/Models/Company.php | 8 -- app/Models/Journalist.php | 7 -- app/Models/PartnerMedia.php | 26 ---- ...25_11_20_041121_create_companies_table.php | 2 - ...1_20_041125_create_partner_media_table.php | 2 - ..._11_20_041138_create_journalists_table.php | 2 - 14 files changed, 86 insertions(+), 137 deletions(-) diff --git a/app/Filament/Pages/AdminVerification.php b/app/Filament/Pages/AdminVerification.php index e69c153..f2d7786 100644 --- a/app/Filament/Pages/AdminVerification.php +++ b/app/Filament/Pages/AdminVerification.php @@ -19,6 +19,7 @@ use Filament\Forms\Concerns\InteractsWithForms; use Filament\Forms\Contracts\HasForms; use Filament\Pages\Page; +use Filament\Support\Enums\Width; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Support\Facades\Log; use UnitEnum; @@ -186,13 +187,14 @@ public function revisionAction(): Action Textarea::make('note') ->label('Catatan Revisi') ->required() - ->placeholder('Jelaskan bagian yang perlu diperbaiki (Perusahaan, Media, atau Jurnalis)...') + ->placeholder('Jelaskan bagian yang perlu diperbaiki...') ->autocomplete(false) ->autofocus(), ]) ->action(function (array $arguments, array $data): void { $this->processReview($arguments['id'], DecisionAdmin::NEED_REVISION, $data['note']); - }); + }) + ->modalWidth(Width::Large); } /** @@ -292,8 +294,8 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin ]); CheerfulNotification::danger( - 'Belum ketemu nih πŸ™Œ', - 'Data verifikasi belum kami temukan atau terjadi kesalahan sistem. Coba sebentar lagi, ya.', + 'Terjadi Kesalahan ⚠️', + 'Proses verifikasi gagal karena kesalahan sistem. Tim kami sudah mencatat masalah ini. Silakan coba lagi nanti.', ) ->send(); } diff --git a/app/Filament/Pages/Company.php b/app/Filament/Pages/Company.php index 9765557..d17434d 100644 --- a/app/Filament/Pages/Company.php +++ b/app/Filament/Pages/Company.php @@ -3,7 +3,6 @@ namespace App\Filament\Pages; use App\Enums\DataChangeStatus; -use App\Enums\VerificationStatus; use App\Filament\Resources\Manage\DataChanges\DataChangesResource; use App\Filament\Support\CheerfulNotification; use App\Models\Company as CompanyModel; @@ -271,7 +270,7 @@ public function save(): void public function saveAction(): Action { - $needsReview = $this->company?->status === VerificationStatus::APPROVED; + $needsReview = $this->company?->verificationRequest?->status !== null; return Action::make('save') ->label('Simpan') diff --git a/app/Filament/Pages/Media.php b/app/Filament/Pages/Media.php index 223f706..c1e2f12 100644 --- a/app/Filament/Pages/Media.php +++ b/app/Filament/Pages/Media.php @@ -6,7 +6,6 @@ use App\Enums\MediaClassification; use App\Enums\MediaType; use App\Enums\RoleEnum; -use App\Enums\VerificationStatus; use App\Filament\Resources\Manage\DataChanges\DataChangesResource; use App\Filament\Support\CheerfulNotification; use App\Models\Company; @@ -225,7 +224,7 @@ public function save(): void public function saveAction(): Action { - $needsReview = $this->media?->status === VerificationStatus::APPROVED; + $needsReview = $this->media?->company?->verificationRequest?->status !== null; return Action::make('save') ->label('Simpan') @@ -251,7 +250,7 @@ public function saveAction(): Action public function performSave(array $data = [], bool $needsReview = false): void { - if ($needsReview && $this->media !== null) { + if ($needsReview) { $existingRequest = DataChangeRequest::where('entity_type', PartnerMedia::class) ->where('entity_id', $this->media->id) ->where('status', DataChangeStatus::PENDING) diff --git a/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php b/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php index 171c22b..61fdb46 100644 --- a/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php +++ b/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php @@ -26,13 +26,15 @@ protected function setUp(): void ->modalSubmitActionLabel('Simpan') ->modalCancelActionLabel('Batal') ->successNotification(null) - ->extraModalFooterActions(fn (CreateAction $action): array => [ + ->extraModalFooterActions(fn(CreateAction $action): array => [ $action->makeModalSubmitAction('createAnother', arguments: ['another' => true]) ->label('Simpan dan Tambah Lagi'), ]) ->modalWidth(Width::Large) ->using(function (array $data, string $model): Model { - $data['partner_media_id'] = auth()->user()->company?->partnerMedia?->id; + $user = auth()->user(); + + $data['partner_media_id'] = $user->company?->partnerMedia?->id; $journalist = $model::create($data); @@ -68,7 +70,7 @@ protected function setUp(): void } // If company is already approved, addition of new journalist needs permohonan/review - if (auth()->user()->company?->status === VerificationStatus::APPROVED) { + if ($user->company?->verificationRequest !== null) { $fieldLabels = Journalist::dataChangeEntryLabels(); $newFields = []; @@ -101,7 +103,7 @@ protected function setUp(): void ->each(function ($admin) use ($dataChangeRequest): void { $admin->notify(new BroadcastNotification([ 'title' => 'Ada Penambahan Jurnalis Baru ✨', - 'body' => 'Halooo Admin! πŸ‘‹ '.auth()->user()->name.' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! πŸš€', + 'body' => 'Halooo Admin! πŸ‘‹ ' . $user->name . ' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! πŸš€', 'action' => [ Action::make('view') ->label('Lihat') diff --git a/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php b/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php index a7b7e2c..d66206e 100644 --- a/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php +++ b/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php @@ -23,9 +23,9 @@ protected function setUp(): void $this->successNotification(null); - $this->modalHeading(fn () => auth()->user()->company?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis'); + $this->modalHeading(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis'); - $this->schema(fn () => auth()->user()->company?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) ? [ + $this->schema(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? [ Textarea::make('deletion_reason') ->label('Alasan Penghapusan') ->placeholder('Jelaskan alasan mengapa jurnalis ini dihapus...') @@ -34,10 +34,8 @@ protected function setUp(): void $this->action(function (Journalist $record, array $data): void { $user = auth()->user(); - $needsReview = $user->company?->status === VerificationStatus::APPROVED && - $user->hasRole(RoleEnum::PERUSAHAAN->value); - if ($needsReview) { + if ($user->company?->verificationRequest !== null) { $existingRequest = DataChangeRequest::where('entity_type', Journalist::class) ->where('entity_id', $record->id) ->where('status', DataChangeStatus::PENDING) @@ -71,7 +69,7 @@ protected function setUp(): void ->each(function ($admin) use ($user, $record, $dataChangeRequest): void { $admin->notify(new BroadcastNotification([ 'title' => 'Ada Pengajuan Penghapusan Jurnalis ✨', - 'body' => 'Halooo Admin! πŸ‘‹ '.$user->name.' baru saja mengajukan penghapusan jurnalis '.$record->name.'. Yuk, cek detailnya! πŸš€', + 'body' => 'Halooo Admin! πŸ‘‹ ' . $user->name . ' baru saja mengajukan penghapusan jurnalis ' . $record->name . '. Yuk, cek detailnya! πŸš€', 'action' => [ Action::make('view') ->label('Lihat') diff --git a/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php b/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php index 3a3c7c4..0b6eecb 100644 --- a/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php +++ b/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php @@ -50,10 +50,9 @@ protected function setUp(): void return $data; }) ->using(function (Journalist $record, array $data): Journalist { - $needsReview = auth()->user()->company?->status === VerificationStatus::APPROVED && - auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value); + $user = auth()->user(); - if ($needsReview) { + if ($user->company?->verificationRequest !== null) { $existingRequest = DataChangeRequest::where('entity_type', Journalist::class) ->where('entity_id', $record->id) ->where('status', DataChangeStatus::PENDING) @@ -67,69 +66,67 @@ protected function setUp(): void return $record; } - } - $fieldLabels = Journalist::dataChangeEntryLabels(); - $editableFields = ['name', 'email', 'phone_number', 'press_card', 'ukw_certificate']; + $fieldLabels = Journalist::dataChangeEntryLabels(); + $editableFields = ['name', 'email', 'phone_number', 'press_card', 'ukw_certificate']; - $changedFields = []; - $oldDataArr = []; + $changedFields = []; + $oldDataArr = []; - $casts = $record->getCasts(); - foreach ($editableFields as $field) { - $val = $data[$field] ?? null; - $oldVal = $record->{$field}; + $casts = $record->getCasts(); + foreach ($editableFields as $field) { + $val = $data[$field] ?? null; + $oldVal = $record->{$field}; - // If value is a raw scalar but should be an enum, resolve it for display - if (isset($casts[$field]) && ! ($val instanceof \BackedEnum) && enum_exists($casts[$field])) { - $enumClass = $casts[$field]; - $val = $enumClass::tryFrom($val) ?? $val; + // If value is a raw scalar but should be an enum, resolve it for display + if (isset($casts[$field]) && ! ($val instanceof \BackedEnum) && enum_exists($casts[$field])) { + $enumClass = $casts[$field]; + $val = $enumClass::tryFrom($val) ?? $val; + } + + $displayVal = ($val instanceof HasLabel) ? $val->getLabel() : ($val instanceof \BackedEnum ? $val->value : $val); + $displayOldVal = ($oldVal instanceof HasLabel) ? $oldVal->getLabel() : ($oldVal instanceof \BackedEnum ? $oldVal->value : $oldVal); + + if ($displayOldVal !== $displayVal) { + $label = $fieldLabels[$field] ?? $field; + $changedFields[$label] = $displayVal; + $oldDataArr[$label] = $displayOldVal; + } } - $displayVal = ($val instanceof HasLabel) ? $val->getLabel() : ($val instanceof \BackedEnum ? $val->value : $val); - $displayOldVal = ($oldVal instanceof HasLabel) ? $oldVal->getLabel() : ($oldVal instanceof \BackedEnum ? $oldVal->value : $oldVal); + $documentFields = ['press_card_docs', 'ukw_certificate_docs']; + $mediaItems = $record->getMedia('journalists'); - if ($displayOldVal !== $displayVal) { - $label = $fieldLabels[$field] ?? $field; - $changedFields[$label] = $displayVal; - $oldDataArr[$label] = $displayOldVal; + foreach ($documentFields as $field) { + $newDocs = array_values((array) ($data[$field] ?? [])); + sort($newDocs); + + $docType = str($field)->replace('_docs', '')->slug('-'); + $currentMedia = $mediaItems + ->where('custom_properties.doc_type', $docType) + ->sortByDesc('created_at') + ->first(); + + $oldDocs = $currentMedia ? [$currentMedia->getPathRelativeToRoot()] : []; + $oldDocs = array_values($oldDocs); + sort($oldDocs); + + if (json_encode($newDocs) !== json_encode($oldDocs)) { + $label = $fieldLabels[$field] ?? $field; + $changedFields[$label] = $newDocs; + $oldDataArr[$label] = $oldDocs; + } } - } - $documentFields = ['press_card_docs', 'ukw_certificate_docs']; - $mediaItems = $record->getMedia('journalists'); + if (empty($changedFields)) { + CheerfulNotification::info( + 'Belum ada yang berubah ✨', + 'Ubah data terlebih dulu lalu simpan kembali πŸ’ͺ' + )->send(); - foreach ($documentFields as $field) { - $newDocs = array_values((array) ($data[$field] ?? [])); - sort($newDocs); - - $docType = str($field)->replace('_docs', '')->slug('-'); - $currentMedia = $mediaItems - ->where('custom_properties.doc_type', $docType) - ->sortByDesc('created_at') - ->first(); - - $oldDocs = $currentMedia ? [$currentMedia->getPathRelativeToRoot()] : []; - $oldDocs = array_values($oldDocs); - sort($oldDocs); - - if (json_encode($newDocs) !== json_encode($oldDocs)) { - $label = $fieldLabels[$field] ?? $field; - $changedFields[$label] = $newDocs; - $oldDataArr[$label] = $oldDocs; + return $record; } - } - if (empty($changedFields)) { - CheerfulNotification::info( - 'Belum ada yang berubah ✨', - 'Ubah data terlebih dulu lalu simpan kembali πŸ’ͺ' - )->send(); - - return $record; - } - - if ($needsReview) { $dataChangeRequest = DataChangeRequest::create([ 'user_id' => auth()->id(), 'entity_type' => Journalist::class, @@ -149,7 +146,7 @@ protected function setUp(): void ->each(function ($admin) use ($dataChangeRequest): void { $admin->notify(new BroadcastNotification([ 'title' => 'Ada Pengajuan Perubahan Jurnalis ✨', - 'body' => 'Halooo Admin! πŸ‘‹ '.auth()->user()->name.' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! πŸš€', + 'body' => 'Halooo Admin! πŸ‘‹ ' . $user->name . ' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! πŸš€', 'action' => [ Action::make('view') ->label('Lihat') @@ -163,7 +160,7 @@ protected function setUp(): void $this->performSave($record, $data); - CheerfulNotification::update()->send(); // Manual success notification + CheerfulNotification::update()->send(); return $record; }) @@ -198,7 +195,7 @@ private function performSave(Journalist $record, array $data): void ]; $existingMedia = $record->getMedia('journalists'); - $existingPaths = $existingMedia->map(fn ($m) => $m->getPathRelativeToRoot())->toArray(); + $existingPaths = $existingMedia->map(fn($m) => $m->getPathRelativeToRoot())->toArray(); foreach ($documents as $field) { if (empty($data[$field])) { diff --git a/app/Filament/Resources/Manage/Journalists/JournalistResource.php b/app/Filament/Resources/Manage/Journalists/JournalistResource.php index 04bd5a5..bb91067 100644 --- a/app/Filament/Resources/Manage/Journalists/JournalistResource.php +++ b/app/Filament/Resources/Manage/Journalists/JournalistResource.php @@ -49,17 +49,6 @@ class JournalistResource extends Resource protected static ?string $slug = 'manage/journalists'; - public static function getNavigationBadge(): ?string - { - return static::getModel()::pending() - ->when(auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value), function (Builder $query): void { - $query->whereHas('partnerMedia.company', function (Builder $q): void { - $q->where('user_id', auth()->id()); - }); - }) - ->count() ?: null; - } - public static function form(Schema $schema): Schema { $documents = [ diff --git a/app/Filament/Resources/Manage/Partners/PartnerResource.php b/app/Filament/Resources/Manage/Partners/PartnerResource.php index 93d3a16..b3d646d 100644 --- a/app/Filament/Resources/Manage/Partners/PartnerResource.php +++ b/app/Filament/Resources/Manage/Partners/PartnerResource.php @@ -112,7 +112,7 @@ public static function infolist(Schema $schema): Schema ->first(); if (! $media) { - return 'Tidak ada dokumen'; + return ''; } $url = $media->getUrl(); @@ -298,9 +298,19 @@ public static function table(Table $table): Table ->sortable() ->badge(), - TextColumn::make('company.status') + TextColumn::make('status') ->label('Status') - ->badge(), + ->badge() + ->getStateUsing(function ($record) { + return $record->company->verificationRequest + ? $record->company->verificationRequest->status->getLabel() + : 'Belum Mengajukan'; + }) + ->color(function ($record) { + return $record->company->verificationRequest + ? $record->company->verificationRequest->status->getColor() + : 'gray'; + }), TextColumn::make('created_at') ->label('Tgl Daftar') diff --git a/app/Models/Company.php b/app/Models/Company.php index 4aa4882..e95a2da 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Enums\VerificationStatus; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -19,13 +18,6 @@ class Company extends Model implements HasMedia protected $guarded = ['id']; - protected function casts(): array - { - return [ - 'status' => VerificationStatus::class, - ]; - } - public function journalists(): HasManyThrough { return $this->hasManyThrough(Journalist::class, PartnerMedia::class); diff --git a/app/Models/Journalist.php b/app/Models/Journalist.php index 0635777..cbbc11d 100644 --- a/app/Models/Journalist.php +++ b/app/Models/Journalist.php @@ -17,13 +17,6 @@ class Journalist extends Model implements HasMedia protected $guarded = ['id']; - protected function casts(): array - { - return [ - 'status' => VerificationStatus::class, - ]; - } - public function partnerMedia(): BelongsTo { return $this->belongsTo(PartnerMedia::class); diff --git a/app/Models/PartnerMedia.php b/app/Models/PartnerMedia.php index 7e7ccbe..bb50622 100644 --- a/app/Models/PartnerMedia.php +++ b/app/Models/PartnerMedia.php @@ -5,7 +5,6 @@ use App\Enums\ApprovalStatus; use App\Enums\MediaClassification; use App\Enums\MediaType; -use App\Enums\VerificationStatus; use Illuminate\Database\Eloquent\Attributes\Scope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -29,34 +28,9 @@ protected function casts(): array return [ 'type' => MediaType::class, 'classification' => MediaClassification::class, - 'status' => VerificationStatus::class, ]; } - #[Scope] - protected function pending(Builder $query): void - { - $query->where('status', VerificationStatus::PENDING); - } - - #[Scope] - protected function approved(Builder $query): void - { - $query->where('status', VerificationStatus::APPROVED); - } - - #[Scope] - protected function needRevision(Builder $query): void - { - $query->where('status', VerificationStatus::NEED_REVISION); - } - - #[Scope] - protected function rejected(Builder $query): void - { - $query->where('status', VerificationStatus::REJECTED); - } - #[Scope] protected function verified(Builder $query): void { diff --git a/database/migrations/2025_11_20_041121_create_companies_table.php b/database/migrations/2025_11_20_041121_create_companies_table.php index df06d13..751df5c 100644 --- a/database/migrations/2025_11_20_041121_create_companies_table.php +++ b/database/migrations/2025_11_20_041121_create_companies_table.php @@ -1,6 +1,5 @@ string('annual_tax_return', 100); $table->string('domicile_certificate', 100); $table->string('profile', 100); - $table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment()); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->softDeletes(); diff --git a/database/migrations/2025_11_20_041125_create_partner_media_table.php b/database/migrations/2025_11_20_041125_create_partner_media_table.php index 6b2c2de..8b35d19 100644 --- a/database/migrations/2025_11_20_041125_create_partner_media_table.php +++ b/database/migrations/2025_11_20_041125_create_partner_media_table.php @@ -2,7 +2,6 @@ use App\Enums\MediaClassification; use App\Enums\MediaType; -use App\Enums\VerificationStatus; use App\Models\Company; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; @@ -25,7 +24,6 @@ public function up(): void $table->enum('classification', [MediaClassification::values()])->comment(MediaClassification::comment()); $table->string('journalism_organization')->nullable(); $table->string('press_council_certificate')->nullable(); - $table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment()); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->softDeletes(); diff --git a/database/migrations/2025_11_20_041138_create_journalists_table.php b/database/migrations/2025_11_20_041138_create_journalists_table.php index 540a288..f1802d3 100644 --- a/database/migrations/2025_11_20_041138_create_journalists_table.php +++ b/database/migrations/2025_11_20_041138_create_journalists_table.php @@ -1,6 +1,5 @@ string('phone_number', 20); $table->string('press_card', 100)->nullable(); $table->string('ukw_certificate', 100)->nullable(); - $table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment()); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->softDeletes(); From f06d8d100c8a1fa77df18da3f6dee5271f7ec1e4 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:23:52 +0700 Subject: [PATCH 10/20] refactor: move verification logic from `VerificationService` to the `Verification` Filament page. --- app/Filament/Pages/AdminVerification.php | 58 ++- app/Filament/Pages/Company.php | 11 +- app/Filament/Pages/Media.php | 12 +- app/Filament/Pages/Verification.php | 304 ++++++++++++- .../DataChangeRequest/RejectAction.php | 2 +- .../DataChanges/DataChangesResource.php | 12 +- .../Actions/CreateJournalistAction.php | 7 +- .../Actions/DeleteJournalistAction.php | 6 +- .../Actions/EditJournalistAction.php | 7 +- .../Manage/Journalists/JournalistResource.php | 22 +- .../Journalists/Pages/ManageJournalists.php | 4 +- app/Services/VerificationService.php | 401 ------------------ .../views/filament/pages/company.blade.php | 8 +- .../views/filament/pages/media.blade.php | 8 +- 14 files changed, 398 insertions(+), 464 deletions(-) delete mode 100644 app/Services/VerificationService.php diff --git a/app/Filament/Pages/AdminVerification.php b/app/Filament/Pages/AdminVerification.php index f2d7786..bb2a685 100644 --- a/app/Filament/Pages/AdminVerification.php +++ b/app/Filament/Pages/AdminVerification.php @@ -8,8 +8,8 @@ use App\Filament\Support\CheerfulNotification; use App\Models\User; use App\Models\VerificationRequest; +use App\Models\VerificationReview; use App\Notifications\BroadcastNotification; -use App\Services\VerificationService; use BackedEnum; use BezhanSalleh\FilamentShield\Traits\HasPageShield; use Filament\Actions\Action; @@ -21,6 +21,7 @@ use Filament\Pages\Page; use Filament\Support\Enums\Width; use Illuminate\Contracts\Pagination\LengthAwarePaginator; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use UnitEnum; @@ -42,15 +43,8 @@ class AdminVerification extends Page implements HasActions, HasForms protected static ?string $title = 'Verifikasi Rekanan'; - protected VerificationService $verificationService; - protected string $view = 'filament.pages.admin-verification'; - public function boot(): void - { - $this->verificationService = app(VerificationService::class); - } - public static function getNavigationBadge(): ?string { return VerificationRequest::query()->pending()->count(); @@ -96,7 +90,7 @@ public function getStatusCounts(): array */ public function getCompanyName(VerificationRequest $request): string { - return $this->verificationService->getCompanyName($request); + return $request->company?->name ?? 'Data tidak ditemukan'; } /** @@ -116,7 +110,27 @@ public function getDetailUrl(VerificationRequest $request): ?string */ public function getVerificationSummary(VerificationRequest $request): array { - return $this->verificationService->getVerificationSummary($request); + $company = $request->company; + $media = $company?->partnerMedia; + $journalists = $media?->journalists ?? collect(); + + return [ + 'company' => [ + 'name' => $company?->name ?? 'Belum diisi', + 'email' => $company?->email ?? '-', + 'exists' => $company !== null, + ], + 'media' => [ + 'name' => $media?->name ?? 'Belum diisi', + 'type' => $media?->type?->getLabel() ?? '-', + 'exists' => $media !== null, + ], + 'journalists' => [ + 'count' => $journalists->count(), + 'names' => $journalists->pluck('name')->take(3)->toArray(), + 'exists' => $journalists->count() > 0, + ], + ]; } /** @@ -242,7 +256,28 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin } try { - $this->verificationService->processReview($verificationRequest, $decision, $note); + DB::beginTransaction(); + + // Create review record + VerificationReview::create([ + 'verification_request_id' => $verificationRequest->id, + 'reviewer_id' => auth()->id(), + 'decision' => $decision, + 'note' => $note, + ]); + + // Update verification request status + $newStatus = match ($decision) { + DecisionAdmin::APPROVED => VerificationStatus::APPROVED, + DecisionAdmin::NEED_REVISION => VerificationStatus::NEED_REVISION, + DecisionAdmin::REJECTED => VerificationStatus::REJECTED, + }; + + $verificationRequest->update([ + 'status' => $newStatus, + ]); + + DB::commit(); $actionLabel = match ($decision) { DecisionAdmin::APPROVED => 'disetujui', @@ -286,6 +321,7 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin ])); } } catch (\Throwable $e) { + DB::rollBack(); Log::error('Verification processing failed', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), diff --git a/app/Filament/Pages/Company.php b/app/Filament/Pages/Company.php index d17434d..1a513b1 100644 --- a/app/Filament/Pages/Company.php +++ b/app/Filament/Pages/Company.php @@ -3,6 +3,7 @@ namespace App\Filament\Pages; use App\Enums\DataChangeStatus; +use App\Enums\VerificationStatus; use App\Filament\Resources\Manage\DataChanges\DataChangesResource; use App\Filament\Support\CheerfulNotification; use App\Models\Company as CompanyModel; @@ -37,6 +38,8 @@ class Company extends Page implements HasForms public array $originalDocuments = []; + public bool $isVerificationStage = false; + protected static string|UnitEnum|null $navigationGroup = 'Kelola'; protected static string|BackedEnum|null $navigationIcon = Heroicon::BuildingOffice2; @@ -63,6 +66,11 @@ public function mount(): void return; } + $this->isVerificationStage = in_array($company->verificationRequest?->status, [ + VerificationStatus::PENDING, + VerificationStatus::REJECTED, + ]) || $company->verificationRequest()->pending()->exists(); + $this->verificationRequest = $company->verificationRequest() ->latest() ->first(); @@ -283,7 +291,8 @@ public function saveAction(): Action ->label('Alasan') ->placeholder('Jelaskan alasan perubahan data ini...') ->autocomplete(false) - ->autofocus(), + ->autofocus() + ->required(), ] : [] ) diff --git a/app/Filament/Pages/Media.php b/app/Filament/Pages/Media.php index c1e2f12..c88c45e 100644 --- a/app/Filament/Pages/Media.php +++ b/app/Filament/Pages/Media.php @@ -6,6 +6,7 @@ use App\Enums\MediaClassification; use App\Enums\MediaType; use App\Enums\RoleEnum; +use App\Enums\VerificationStatus; use App\Filament\Resources\Manage\DataChanges\DataChangesResource; use App\Filament\Support\CheerfulNotification; use App\Models\Company; @@ -58,6 +59,8 @@ class Media extends Page implements HasForms public bool $restricted = false; + public bool $isVerificationStage = false; + protected static ?string $slug = 'manage/media'; protected static ?string $title = 'Media'; @@ -84,6 +87,11 @@ public function mount(): void $this->company = $company; + $this->isVerificationStage = in_array($company?->verificationRequest?->status, [ + VerificationStatus::PENDING, + VerificationStatus::REJECTED, + ]) || $company->verificationRequest()->pending()->exists(); + $verificationRequest = $this->company->verificationRequest() ->latest() ->first(); @@ -235,10 +243,10 @@ public function saveAction(): Action ? [ Textarea::make('change_reason') ->label('Alasan') - ->required() ->placeholder('Jelaskan alasan perubahan data ini...') ->autocomplete(false) - ->autofocus(), + ->autofocus() + ->required(), ] : [] ) diff --git a/app/Filament/Pages/Verification.php b/app/Filament/Pages/Verification.php index 5a2fc9c..d7719b0 100644 --- a/app/Filament/Pages/Verification.php +++ b/app/Filament/Pages/Verification.php @@ -3,12 +3,13 @@ namespace App\Filament\Pages; use App\Enums\VerificationStatus; +use App\Filament\Pages\Company as PagesCompany; +use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists; use App\Filament\Resources\Manage\Partners\PartnerResource; use App\Models\Company; use App\Models\User; use App\Models\VerificationRequest; use App\Notifications\BroadcastNotification; -use App\Services\VerificationService; use BackedEnum; use BezhanSalleh\FilamentShield\Traits\HasPageShield; use Filament\Actions\Action; @@ -19,6 +20,7 @@ use Filament\Notifications\Notification; use Filament\Pages\Page; use Filament\Support\Icons\Heroicon; +use Illuminate\Support\Collection; use UnitEnum; class Verification extends Page implements HasActions, HasForms @@ -41,15 +43,8 @@ class Verification extends Page implements HasActions, HasForms public ?VerificationRequest $verificationRequest = null; - protected VerificationService $verificationService; - protected string $view = 'filament.pages.verification'; - public function boot(): void - { - $this->verificationService = app(VerificationService::class); - } - public function mount(): void { $user = auth()->user(); @@ -69,14 +64,35 @@ public function mount(): void */ public function getVerificationProgress(): array { - $progress = $this->verificationService->getVerificationProgress( - $this->company, - $this->verificationRequest - ); + $isComplete = $this->isDataComplete(); + $missingData = $this->getMissingData(); + $status = $this->verificationRequest?->status; + + $progress = [ + 'currentStep' => $this->resolveStep(), + 'isDataComplete' => $isComplete, + 'missingData' => $missingData, + 'canSubmit' => $this->canSubmit(), + 'verification' => $this->verificationRequest, + 'status' => [ + 'label' => $status?->getLabel() ?? 'Belum Mengajukan', + 'color' => $status?->getColor() ?? 'gray', + ], + 'isApproved' => $status === VerificationStatus::APPROVED, + 'isRejected' => $status === VerificationStatus::REJECTED, + 'isPending' => $status === VerificationStatus::PENDING, + 'needsRevision' => $status === VerificationStatus::NEED_REVISION, + 'latestNotes' => $this->getLatestReviewNotes(), + 'reviewHistory' => $this->getReviewHistory(), + 'company' => $this->company, + 'partnerMedia' => $this->company?->partnerMedia, + 'journalists' => $this->company?->partnerMedia?->journalists ?? collect(), + 'steps' => $this->getSteps($isComplete), + ]; // Add additional data for view - $progress['missingDataButtons'] = $this->verificationService->getMissingDataButtons($this->company); - $progress['alert'] = $this->verificationService->getAlertData($this->verificationRequest); + $progress['missingDataButtons'] = $this->getMissingDataButtons(); + $progress['alert'] = $this->getAlertData(); $progress['lastSubmittedAt'] = $this->verificationRequest?->updated_at?->translatedFormat('l, d M Y H:i'); return $progress; @@ -120,10 +136,7 @@ public function submitVerificationAction(): Action $isRevision = $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION; - $this->verificationRequest = $this->verificationService->submitVerification( - $this->company, - $this->verificationRequest - ); + $this->verificationRequest = $this->submitVerification(); Notification::make() ->title('Berhasil Diajukan! πŸš€') @@ -151,6 +164,261 @@ public function submitVerificationAction(): Action }); } + /** + * Check if all required data is complete for verification + */ + protected function isDataComplete(): bool + { + return $this->company !== null + && $this->company->partnerMedia !== null + && $this->company->partnerMedia?->journalists !== null + && $this->company->partnerMedia->journalists->count() > 0; + } + + /** + * Get missing data items for verification + */ + protected function getMissingData(): array + { + $missing = []; + + if (! $this->company) { + $missing[] = 'Data Perusahaan'; + } + + if ($this->company && ! $this->company->partnerMedia) { + $missing[] = 'Data Media'; + } + + if ($this->company && $this->company->partnerMedia && ($this->company->partnerMedia?->journalists === null || $this->company->partnerMedia?->journalists?->count() === 0)) { + $missing[] = 'Data Jurnalis (minimal 1)'; + } + + return $missing; + } + + /** + * Check if user can submit verification + */ + protected function canSubmit(): bool + { + if (! $this->isDataComplete()) { + return false; + } + + // No verification yet - can submit + if (! $this->verificationRequest) { + return true; + } + + // Can resubmit only if status is NEED_REVISION + return $this->verificationRequest->status === VerificationStatus::NEED_REVISION; + } + + /** + * Determine the current step based on status + */ + protected function resolveStep(): int + { + if (! $this->isDataComplete()) { + return 1; // Data belum lengkap + } + + if (! $this->verificationRequest) { + return 1; // Belum submit + } + + return match ($this->verificationRequest->status) { + VerificationStatus::PENDING, + VerificationStatus::NEED_REVISION => 2, // In review or needs revision + + VerificationStatus::APPROVED, + VerificationStatus::REJECTED => 3, // Final decision + + default => 1, // Draft / not submitted yet + }; + } + + /** + * Get the latest review notes + */ + protected function getLatestReviewNotes(): ?string + { + if (! $this->verificationRequest) { + return null; + } + + $latestReview = $this->verificationRequest->reviews() + ->where(fn ($q) => $q->needRevision()->orWhere(fn ($q) => $q->rejected())) + ->latest() + ->first(); + + return $latestReview?->note; + } + + /** + * Get all reviews history + */ + protected function getReviewHistory(): Collection + { + if (! $this->verificationRequest) { + return collect(); + } + + return $this->verificationRequest->reviews() + ->with('reviewer') + ->latest() + ->get(); + } + + /** + * Submit unified verification + */ + protected function submitVerification(): VerificationRequest + { + if ($this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION) { + // Resubmit - update existing request + $this->verificationRequest->update([ + 'status' => VerificationStatus::PENDING, + ]); + + return $this->verificationRequest->fresh(['reviews.reviewer']); + } + + // New submission + return VerificationRequest::create([ + 'company_id' => $this->company->id, + 'submitted_by' => auth()->id(), + 'status' => VerificationStatus::PENDING, + ])->load(['reviews.reviewer']); + } + + /** + * Get alert data for verification status + */ + protected function getAlertData(): ?array + { + if (! $this->verificationRequest) { + return null; + } + + $status = $this->verificationRequest->status; + $latestNotes = $this->getLatestReviewNotes(); + + if ($status === VerificationStatus::NEED_REVISION && $latestNotes) { + return [ + 'type' => 'revision', + 'icon' => 'exclamation-triangle', + 'title' => 'Catatan Revisi dari Admin', + 'message' => $latestNotes, + 'color' => 'warning', + ]; + } + + if ($status === VerificationStatus::REJECTED && $latestNotes) { + return [ + 'type' => 'rejected', + 'icon' => 'x-circle', + 'title' => 'Pengajuan Ditolak', + 'message' => $latestNotes, + 'color' => 'danger', + ]; + } + + if ($status === VerificationStatus::APPROVED) { + return [ + 'type' => 'approved', + 'icon' => 'check-circle', + 'title' => 'Verifikasi Diterima', + 'message' => 'Verifikasi Anda telah diterima dan disetujui oleh admin. Sekarang Anda sudah resmi terverifikasi dan bekerja sama dengan kami!', + 'color' => 'success', + ]; + } + + if ($status === VerificationStatus::PENDING) { + return [ + 'type' => 'pending', + 'icon' => 'clock', + 'title' => 'Menunggu Verifikasi', + 'message' => 'Data Anda sedang dalam proses verifikasi oleh admin. Mohon tunggu.', + 'color' => 'info', + 'animate' => true, + ]; + } + + return null; + } + + /** + * Get missing data buttons configuration + */ + protected function getMissingDataButtons(): array + { + $buttons = []; + + if (! $this->company) { + $buttons[] = [ + 'label' => 'Lengkapi Data Perusahaan', + 'url' => PagesCompany::getUrl(), + 'icon' => 'building-office-2', + 'color' => 'warning', + ]; + } + + if ($this->company && ! $this->company->partnerMedia) { + $buttons[] = [ + 'label' => 'Lengkapi Data Media', + 'url' => Media::getUrl(), + 'icon' => 'newspaper', + 'color' => 'warning', + ]; + } + + if ($this->company && $this->company->partnerMedia && ($this->company->partnerMedia->journalists === null || $this->company->partnerMedia->journalists->count() === 0)) { + $buttons[] = [ + 'label' => 'Lengkapi Data Jurnalis', + 'url' => ManageJournalists::getUrl(), + 'icon' => 'user-circle', + 'color' => 'warning', + ]; + } + + return $buttons; + } + + /** + * Get steps array for stepper + */ + protected function getSteps(bool $isComplete): array + { + $status = $this->verificationRequest?->status; + + return [ + [ + 'title' => 'Pengajuan', + 'description' => $isComplete + ? 'Data sudah lengkap, silakan ajukan verifikasi' + : 'Silakan lengkapi data', + ], + [ + 'title' => 'Verifikasi', + 'description' => match ($status) { + VerificationStatus::NEED_REVISION => 'Perlu perbaikan data', + VerificationStatus::PENDING => 'Sedang diverifikasi admin', + default => 'Menunggu pengajuan', + }, + ], + [ + 'title' => 'Selesai', + 'description' => match ($status) { + VerificationStatus::APPROVED => 'Verifikasi disetujui', + VerificationStatus::REJECTED => 'Verifikasi ditolak', + default => 'Menunggu keputusan', + }, + ], + ]; + } + public function getViewData(): array { return [ diff --git a/app/Filament/Resources/Manage/DataChanges/Actions/DataChangeRequest/RejectAction.php b/app/Filament/Resources/Manage/DataChanges/Actions/DataChangeRequest/RejectAction.php index 3ba5ba9..614f413 100644 --- a/app/Filament/Resources/Manage/DataChanges/Actions/DataChangeRequest/RejectAction.php +++ b/app/Filament/Resources/Manage/DataChanges/Actions/DataChangeRequest/RejectAction.php @@ -56,7 +56,7 @@ protected function setUp(): void // Notify User $record->user?->notify(new BroadcastNotification([ 'title' => 'Permohonan Perubahan Ditolak πŸ›‘', - 'body' => "Halo! Mohon maaf, permohonan perubahan data Anda ({$record->change_reason}) ditolak oleh admin. Alasan: {$data['rejection_reason']} πŸ˜”", + 'body' => "Halo! Mohon maaf, permohonan perubahan data Anda untuk {$record->change_reason} ditolak oleh admin. Alasan: {$data['rejection_reason']} πŸ˜”", ])); }) ->visible(fn (DataChangeRequest $record): bool => $record->status === DataChangeStatus::PENDING && ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) diff --git a/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php b/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php index 2b4789b..a562333 100644 --- a/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php +++ b/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php @@ -44,13 +44,13 @@ class DataChangesResource extends Resource protected static ?string $slug = 'manage/data-changes'; - public static function getNavigationBadge(): string + public static function getNavigationBadge(): ?string { - return static::getModel()::pending() - ->when(auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value), function (Builder $query): void { - $query->where('user_id', auth()->id()); - }) - ->count(); + if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN)) { + return null; + } + + return static::getModel()::pending()->count(); } public static function infolist(Schema $schema): Schema diff --git a/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php b/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php index 61fdb46..235c0cf 100644 --- a/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php +++ b/app/Filament/Resources/Manage/Journalists/Actions/CreateJournalistAction.php @@ -2,7 +2,6 @@ namespace App\Filament\Resources\Manage\Journalists\Actions; -use App\Enums\VerificationStatus; use App\Filament\Actions\Cheerful\CreateAction; use App\Filament\Resources\Manage\DataChanges\DataChangesResource; use App\Filament\Support\CheerfulNotification; @@ -26,7 +25,7 @@ protected function setUp(): void ->modalSubmitActionLabel('Simpan') ->modalCancelActionLabel('Batal') ->successNotification(null) - ->extraModalFooterActions(fn(CreateAction $action): array => [ + ->extraModalFooterActions(fn (CreateAction $action): array => [ $action->makeModalSubmitAction('createAnother', arguments: ['another' => true]) ->label('Simpan dan Tambah Lagi'), ]) @@ -100,10 +99,10 @@ protected function setUp(): void User::superAdmin() ->get() - ->each(function ($admin) use ($dataChangeRequest): void { + ->each(function ($admin) use ($dataChangeRequest, $user): void { $admin->notify(new BroadcastNotification([ 'title' => 'Ada Penambahan Jurnalis Baru ✨', - 'body' => 'Halooo Admin! πŸ‘‹ ' . $user->name . ' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! πŸš€', + 'body' => 'Halooo Admin! πŸ‘‹ '.$user->name.' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! πŸš€', 'action' => [ Action::make('view') ->label('Lihat') diff --git a/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php b/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php index d66206e..d74b0dd 100644 --- a/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php +++ b/app/Filament/Resources/Manage/Journalists/Actions/DeleteJournalistAction.php @@ -23,9 +23,9 @@ protected function setUp(): void $this->successNotification(null); - $this->modalHeading(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis'); + $this->modalHeading(fn () => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis'); - $this->schema(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? [ + $this->schema(fn () => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? [ Textarea::make('deletion_reason') ->label('Alasan Penghapusan') ->placeholder('Jelaskan alasan mengapa jurnalis ini dihapus...') @@ -69,7 +69,7 @@ protected function setUp(): void ->each(function ($admin) use ($user, $record, $dataChangeRequest): void { $admin->notify(new BroadcastNotification([ 'title' => 'Ada Pengajuan Penghapusan Jurnalis ✨', - 'body' => 'Halooo Admin! πŸ‘‹ ' . $user->name . ' baru saja mengajukan penghapusan jurnalis ' . $record->name . '. Yuk, cek detailnya! πŸš€', + 'body' => 'Halooo Admin! πŸ‘‹ '.$user->name.' baru saja mengajukan penghapusan jurnalis '.$record->name.'. Yuk, cek detailnya! πŸš€', 'action' => [ Action::make('view') ->label('Lihat') diff --git a/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php b/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php index 0b6eecb..0c80a14 100644 --- a/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php +++ b/app/Filament/Resources/Manage/Journalists/Actions/EditJournalistAction.php @@ -4,7 +4,6 @@ use App\Enums\DataChangeStatus; use App\Enums\RoleEnum; -use App\Enums\VerificationStatus; use App\Filament\Resources\Manage\DataChanges\DataChangesResource; use App\Filament\Support\CheerfulNotification; use App\Models\DataChangeRequest; @@ -143,10 +142,10 @@ protected function setUp(): void User::superAdmin() ->get() - ->each(function ($admin) use ($dataChangeRequest): void { + ->each(function ($admin) use ($dataChangeRequest, $user): void { $admin->notify(new BroadcastNotification([ 'title' => 'Ada Pengajuan Perubahan Jurnalis ✨', - 'body' => 'Halooo Admin! πŸ‘‹ ' . $user->name . ' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! πŸš€', + 'body' => 'Halooo Admin! πŸ‘‹ '.$user->name.' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! πŸš€', 'action' => [ Action::make('view') ->label('Lihat') @@ -195,7 +194,7 @@ private function performSave(Journalist $record, array $data): void ]; $existingMedia = $record->getMedia('journalists'); - $existingPaths = $existingMedia->map(fn($m) => $m->getPathRelativeToRoot())->toArray(); + $existingPaths = $existingMedia->map(fn ($m) => $m->getPathRelativeToRoot())->toArray(); foreach ($documents as $field) { if (empty($data[$field])) { diff --git a/app/Filament/Resources/Manage/Journalists/JournalistResource.php b/app/Filament/Resources/Manage/Journalists/JournalistResource.php index bb91067..e5ff292 100644 --- a/app/Filament/Resources/Manage/Journalists/JournalistResource.php +++ b/app/Filament/Resources/Manage/Journalists/JournalistResource.php @@ -134,7 +134,7 @@ public static function form(Schema $schema): Schema ->rows(3) ->required(), ]) - ->visible(fn () => auth()->user()->company?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)), + ->visible(fn () => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)), ]) ->columns(1); } @@ -177,14 +177,24 @@ public static function table(Table $table): Table ->native(false) ->visible(fn () => auth()->user()?->hasRole('Developer')), ]) + ->checkIfRecordIsSelectableUsing( + fn () => ! ( + in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || + auth()->user()->company?->verificationRequest()->pending()->exists() + ) + ) ->recordActions([ - EditJournalistAction::make('edit'), + EditJournalistAction::make('edit') + ->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())), - DeleteJournalistAction::make('delete'), + DeleteJournalistAction::make('delete') + ->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())), - ForceDeleteAction::make(), + ForceDeleteAction::make() + ->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())), - RestoreAction::make(), + RestoreAction::make() + ->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())), ]) ->toolbarActions([ BulkActionGroup::make([ @@ -193,7 +203,7 @@ public static function table(Table $table): Table ForceDeleteBulkAction::make(), RestoreBulkAction::make(), - ]), + ])->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())), ]) ->emptyStateIcon(Heroicon::Identification) ->emptyStateDescription('Setelah Anda membubat data pertama, maka akan muncul disini.') diff --git a/app/Filament/Resources/Manage/Journalists/Pages/ManageJournalists.php b/app/Filament/Resources/Manage/Journalists/Pages/ManageJournalists.php index a1eb103..1419499 100644 --- a/app/Filament/Resources/Manage/Journalists/Pages/ManageJournalists.php +++ b/app/Filament/Resources/Manage/Journalists/Pages/ManageJournalists.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Manage\Journalists\Pages; use App\Enums\RoleEnum; +use App\Enums\VerificationStatus; use App\Filament\Resources\Manage\Journalists\Actions\CreateJournalistAction; use App\Filament\Resources\Manage\Journalists\JournalistResource; use Filament\Resources\Pages\ManageRecords; @@ -20,7 +21,8 @@ protected function getHeaderActions(): array } return [ - CreateJournalistAction::make(), + CreateJournalistAction::make() + ->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())), ]; } } diff --git a/app/Services/VerificationService.php b/app/Services/VerificationService.php deleted file mode 100644 index cdf1af5..0000000 --- a/app/Services/VerificationService.php +++ /dev/null @@ -1,401 +0,0 @@ -partnerMedia !== null - && $company->partnerMedia?->journalists !== null - && $company->partnerMedia->journalists->count() > 0; - } - - /** - * Get missing data items for verification - */ - public function getMissingData(?Company $company): array - { - $missing = []; - - if (! $company) { - $missing[] = 'Data Perusahaan'; - } - - if ($company && ! $company->partnerMedia) { - $missing[] = 'Data Media'; - } - - if ($company && $company->partnerMedia && ($company->partnerMedia?->journalists === null || $company->partnerMedia?->journalists?->count() === 0)) { - $missing[] = 'Data Jurnalis (minimal 1)'; - } - - return $missing; - } - - /** - * Check if user can submit verification - */ - public function canSubmit(?Company $company, ?VerificationRequest $verificationRequest): bool - { - if (! $this->isDataComplete($company)) { - return false; - } - - // No verification yet - can submit - if (! $verificationRequest) { - return true; - } - - // Can resubmit only if status is NEED_REVISION - return $verificationRequest->status === VerificationStatus::NEED_REVISION; - } - - /** - * Determine the current step based on status - */ - public function resolveStep(?Company $company, ?VerificationRequest $verificationRequest): int - { - if (! $this->isDataComplete($company)) { - return 1; // Data belum lengkap - } - - if (! $verificationRequest) { - return 1; // Belum submit - } - - return match ($verificationRequest->status) { - VerificationStatus::PENDING, - VerificationStatus::NEED_REVISION => 2, // In review or needs revision - - VerificationStatus::APPROVED, - VerificationStatus::REJECTED => 3, // Final decision - - default => 1, // Draft / not submitted yet - }; - } - - /** - * Get the latest review notes - */ - public function getLatestReviewNotes(?VerificationRequest $verificationRequest): ?string - { - if (! $verificationRequest) { - return null; - } - - $latestReview = $verificationRequest->reviews() - ->where(fn ($q) => $q->needRevision()->orWhere(fn ($q) => $q->rejected())) - ->latest() - ->first(); - - return $latestReview?->note; - } - - /** - * Get all reviews history - */ - public function getReviewHistory(?VerificationRequest $verificationRequest): Collection - { - if (! $verificationRequest) { - return collect(); - } - - return $verificationRequest->reviews() - ->with('reviewer') - ->latest() - ->get(); - } - - /** - * Get unified verification progress data - */ - public function getVerificationProgress(?Company $company, ?VerificationRequest $verificationRequest): array - { - $currentStep = $this->resolveStep($company, $verificationRequest); - $isComplete = $this->isDataComplete($company); - $missingData = $this->getMissingData($company); - - $status = $verificationRequest?->status; - - return [ - 'currentStep' => $currentStep, - 'isDataComplete' => $isComplete, - 'missingData' => $missingData, - 'canSubmit' => $this->canSubmit($company, $verificationRequest), - 'verification' => $verificationRequest, - 'status' => [ - 'label' => $status?->getLabel() ?? 'Belum Mengajukan', - 'color' => $status?->getColor() ?? 'gray', - ], - 'isApproved' => $status === VerificationStatus::APPROVED, - 'isRejected' => $status === VerificationStatus::REJECTED, - 'isPending' => $status === VerificationStatus::PENDING, - 'needsRevision' => $status === VerificationStatus::NEED_REVISION, - 'latestNotes' => $this->getLatestReviewNotes($verificationRequest), - 'reviewHistory' => $this->getReviewHistory($verificationRequest), - 'company' => $company, - 'partnerMedia' => $company?->partnerMedia, - 'journalists' => $company?->partnerMedia?->journalists ?? collect(), - 'steps' => $this->getSteps($verificationRequest, $isComplete, $missingData), - ]; - } - - /** - * Get steps array for stepper - */ - protected function getSteps(?VerificationRequest $verificationRequest, bool $isComplete, array $missingData): array - { - $status = $verificationRequest?->status; - - return [ - [ - 'title' => 'Pengajuan', - 'description' => $isComplete - ? 'Data sudah lengkap, silakan ajukan verifikasi' - : 'Silakan lengkapi data', - ], - [ - 'title' => 'Verifikasi', - 'description' => match ($status) { - VerificationStatus::NEED_REVISION => 'Perlu perbaikan data', - VerificationStatus::PENDING => 'Sedang diverifikasi admin', - default => 'Menunggu pengajuan', - }, - ], - [ - 'title' => 'Selesai', - 'description' => match ($status) { - VerificationStatus::APPROVED => 'Verifikasi disetujui', - VerificationStatus::REJECTED => 'Verifikasi ditolak', - default => 'Menunggu keputusan', - }, - ], - ]; - } - - /** - * Submit unified verification - */ - public function submitVerification(Company $company, ?VerificationRequest $existingRequest): VerificationRequest - { - if ($existingRequest && $existingRequest->status === VerificationStatus::NEED_REVISION) { - // Resubmit - update existing request - $existingRequest->update([ - 'status' => VerificationStatus::PENDING, - ]); - - return $existingRequest->fresh(['reviews.reviewer']); - } - - // New submission - return VerificationRequest::create([ - 'company_id' => $company->id, - 'submitted_by' => auth()->id(), - 'status' => VerificationStatus::PENDING, - ])->load(['reviews.reviewer']); - } - - /** - * Process review decision - unified for Company, Media, and Journalists - */ - public function processReview(VerificationRequest $verificationRequest, DecisionAdmin $decision, ?string $note): void - { - $verificationRequest->load(['company.partnerMedia.journalists']); - - DB::beginTransaction(); - - try { - // Create review record - VerificationReview::create([ - 'verification_request_id' => $verificationRequest->id, - 'reviewer_id' => auth()->id(), - 'decision' => $decision, - 'note' => $note, - ]); - - // Update verification request status - $newStatus = match ($decision) { - DecisionAdmin::APPROVED => VerificationStatus::APPROVED, - DecisionAdmin::NEED_REVISION => VerificationStatus::NEED_REVISION, - DecisionAdmin::REJECTED => VerificationStatus::REJECTED, - }; - - $verificationRequest->update([ - 'status' => $newStatus, - ]); - - // Update Company status - $company = $verificationRequest->company; - if ($company) { - $company->update(['status' => $newStatus]); - - // Update Media status - $media = $company->partnerMedia; - if ($media) { - $media->update(['status' => $newStatus]); - - // Update all Journalists status - $media->journalists()->update(['status' => $newStatus]); - } - } - - DB::commit(); - } catch (\Throwable $e) { - Log::error('Verification review processing failed', [ - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - 'request_id' => $verificationRequest->id, - 'decision' => $decision->value, - ]); - - DB::rollBack(); - throw $e; - } - } - - /** - * Get company name from verification request - */ - public function getCompanyName(VerificationRequest $request): string - { - return $request->company?->name ?? 'Data tidak ditemukan'; - } - - /** - * Get verification summary data for admin - */ - public function getVerificationSummary(VerificationRequest $request): array - { - $company = $request->company; - $media = $company?->partnerMedia; - $journalists = $media?->journalists ?? collect(); - - return [ - 'company' => [ - 'name' => $company?->name ?? 'Belum diisi', - 'email' => $company?->email ?? '-', - 'exists' => $company !== null, - ], - 'media' => [ - 'name' => $media?->name ?? 'Belum diisi', - 'type' => $media?->type?->getLabel() ?? '-', - 'exists' => $media !== null, - ], - 'journalists' => [ - 'count' => $journalists->count(), - 'names' => $journalists->pluck('name')->take(3)->toArray(), - 'exists' => $journalists->count() > 0, - ], - ]; - } - - /** - * Get alert data for verification status - */ - public function getAlertData(?VerificationRequest $verificationRequest): ?array - { - if (! $verificationRequest) { - return null; - } - - $status = $verificationRequest->status; - $latestNotes = $this->getLatestReviewNotes($verificationRequest); - - if ($status === VerificationStatus::NEED_REVISION && $latestNotes) { - return [ - 'type' => 'revision', - 'icon' => 'exclamation-triangle', - 'title' => 'Catatan Revisi dari Admin', - 'message' => $latestNotes, - 'color' => 'warning', - ]; - } - - if ($status === VerificationStatus::REJECTED && $latestNotes) { - return [ - 'type' => 'rejected', - 'icon' => 'x-circle', - 'title' => 'Pengajuan Ditolak', - 'message' => $latestNotes, - 'color' => 'danger', - ]; - } - - if ($status === VerificationStatus::APPROVED) { - return [ - 'type' => 'approved', - 'icon' => 'check-circle', - 'title' => 'Verifikasi Diterima', - 'message' => 'Verifikasi Anda telah diterima dan disetujui oleh admin. Sekarang Anda sudah resmi terverifikasi dan bekerja sama dengan kami!', - 'color' => 'success', - ]; - } - - if ($status === VerificationStatus::PENDING) { - return [ - 'type' => 'pending', - 'icon' => 'clock', - 'title' => 'Menunggu Verifikasi', - 'message' => 'Data Anda sedang dalam proses verifikasi oleh admin. Mohon tunggu.', - 'color' => 'info', - 'animate' => true, - ]; - } - - return null; - } - - /** - * Get missing data buttons configuration - */ - public function getMissingDataButtons(?Company $company): array - { - $buttons = []; - - if (! $company) { - $buttons[] = [ - 'label' => 'Lengkapi Data Perusahaan', - 'url' => PagesCompany::getUrl(), - 'icon' => 'building-office-2', - 'color' => 'warning', - ]; - } - - if ($company && ! $company->partnerMedia) { - $buttons[] = [ - 'label' => 'Lengkapi Data Media', - 'url' => Media::getUrl(), - 'icon' => 'newspaper', - 'color' => 'warning', - ]; - } - - if ($company && $company->partnerMedia && ($company->partnerMedia->journalists === null || $company->partnerMedia->journalists->count() === 0)) { - $buttons[] = [ - 'label' => 'Lengkapi Data Jurnalis', - 'url' => ManageJournalists::getUrl(), - 'icon' => 'user-circle', - 'color' => 'warning', - ]; - } - - return $buttons; - } -} diff --git a/resources/views/filament/pages/company.blade.php b/resources/views/filament/pages/company.blade.php index 01e2f4e..5be30ce 100644 --- a/resources/views/filament/pages/company.blade.php +++ b/resources/views/filament/pages/company.blade.php @@ -2,8 +2,10 @@
{{ $this->form }} -
- {{ $this->saveAction() }} -
+ @if (!$this->isVerificationStage) +
+ {{ $this->saveAction() }} +
+ @endif
diff --git a/resources/views/filament/pages/media.blade.php b/resources/views/filament/pages/media.blade.php index e4dac6b..c159df5 100644 --- a/resources/views/filament/pages/media.blade.php +++ b/resources/views/filament/pages/media.blade.php @@ -32,9 +32,11 @@ class="dark:text-gray-400">
{{ $this->form }} -
- {{ $this->saveAction() }} -
+ @if (!$this->isVerificationStage) +
+ {{ $this->saveAction() }} +
+ @endif
@endif From 912a20421c1c83eb030cdd2aca21e1cbee003409 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:27:30 +0700 Subject: [PATCH 11/20] feat: Add `VerificationStatus` enum import and a commented-out verification check to `canAccessPanel`. --- app/Models/User.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Models/User.php b/app/Models/User.php index edbbdf5..587ba2d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ use App\Enums\IsActive; use App\Enums\RoleEnum; +use App\Enums\VerificationStatus; use Filament\Models\Contracts\FilamentUser; use Filament\Panel; use Illuminate\Contracts\Auth\MustVerifyEmail; @@ -37,6 +38,10 @@ protected function casts(): array public function canAccessPanel(Panel $panel): bool { + // if ($this->company?->verificationRequest?->status === VerificationStatus::REJECTED) { + // return false; + // } + return $this->is_active === IsActive::ACTIVE; } From 0d4662086a36a1a7fd55159393d481efd70a0707 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:28:42 +0700 Subject: [PATCH 12/20] feat: Restrict TrashedFilter visibility to only the Developer role in various resource tables. --- .../Manage/Cooperations/Tables/CooperationsTable.php | 4 +++- .../Monitoring/ContentRecaps/Tables/ContentRecapsTable.php | 4 +++- .../MediaMonitorings/Tables/MediaMonitoringsTable.php | 4 +++- .../Publication/Announcements/AnnouncementResource.php | 4 +++- app/Filament/Resources/Publication/News/Tables/NewsTable.php | 4 +++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php index 3ad1031..a30a641 100644 --- a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php +++ b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php @@ -141,7 +141,9 @@ public static function configure(Table $table): Table ...TimestampColumns::make(), ]) ->filters([ - TrashedFilter::make()->native(false), + TrashedFilter::make() + ->native(false) + ->visible(fn () => auth()->user()?->hasRole('Developer')), SelectFilter::make('status') ->label('Status') diff --git a/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php b/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php index c1058d3..7b4fc4a 100644 --- a/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php +++ b/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php @@ -72,7 +72,9 @@ public static function configure(Table $table): Table ...TimestampColumns::make(), ]) ->filters([ - TrashedFilter::make()->native(false), + TrashedFilter::make() + ->native(false) + ->visible(fn () => auth()->user()?->hasRole('Developer')), SelectFilter::make('social_media') ->label('Media Sosial') diff --git a/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php b/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php index 3d3e348..715ad47 100644 --- a/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php +++ b/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php @@ -96,7 +96,9 @@ public static function configure(Table $table): Table ...TimestampColumns::make(), ]) ->filters([ - TrashedFilter::make()->native(false), + TrashedFilter::make() + ->native(false) + ->visible(fn () => auth()->user()?->hasRole('Developer')), SelectFilter::make('themes') ->label('Tema') diff --git a/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php index d757ab1..7a3bc24 100644 --- a/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php +++ b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php @@ -126,7 +126,9 @@ public static function table(Table $table): Table ...TimestampColumns::make(), ]) ->filters([ - TrashedFilter::make()->native(false), + TrashedFilter::make() + ->native(false) + ->visible(fn () => auth()->user()?->hasRole('Developer')), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Publication/News/Tables/NewsTable.php b/app/Filament/Resources/Publication/News/Tables/NewsTable.php index f9b9a82..9e982b5 100644 --- a/app/Filament/Resources/Publication/News/Tables/NewsTable.php +++ b/app/Filament/Resources/Publication/News/Tables/NewsTable.php @@ -81,7 +81,9 @@ public static function configure(Table $table): Table ...TimestampColumns::make(), ]) ->filters([ - TrashedFilter::make()->native(false), + TrashedFilter::make() + ->native(false) + ->visible(fn () => auth()->user()?->hasRole('Developer')), ]) ->recordActions([ EditAction::make(), From 0c2784d82a15f035c6492e66511561956c7094f7 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:29:49 +0700 Subject: [PATCH 13/20] Remove default status assignment from Company factory. --- database/factories/CompanyFactory.php | 1 - 1 file changed, 1 deletion(-) diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 2d742cc..ba5bd17 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -36,7 +36,6 @@ public function definition(): array 'domicile_certificate' => $this->faker->numerify('###/###/DOMISILI/####'), 'profile' => $this->faker->numerify('PROFILE-####-########'), 'profile' => $this->faker->numerify('PROFILE-####-########'), - 'status' => VerificationStatus::PENDING, ]; } From ca912e5d6ae96438ad3037866177e73dc5f4eb8e Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:39:10 +0700 Subject: [PATCH 14/20] feat: display partner media names as badges in the announcement table using custom state retrieval. --- .../Announcements/AnnouncementResource.php | 10 ++++++++-- database/seeders/ShieldSeeder.php | 12 ------------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php index 7a3bc24..02ad31a 100644 --- a/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php +++ b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php @@ -116,12 +116,18 @@ public static function table(Table $table): Table ->badge() ->sortable(), - TextColumn::make('partnerMedia.name') + TextColumn::make('partnerMedia') ->label('Penerima') + ->getStateUsing( + fn ($record) => $record->partnerMedia->pluck('name')->toArray() + ) ->listWithLineBreaks() ->limitList(3) ->expandableLimitedList() - ->placeholder(fn (Announcement $record): string => $record->type === AnnouncementType::PUBLIC ? 'Publik' : 'Tidak ada penerima'), + ->badge() + ->placeholder( + fn (Announcement $record) => $record->type === AnnouncementType::PUBLIC ? 'Publik' : 'Tidak ada penerima' + ), ...TimestampColumns::make(), ]) diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index 3852cc6..b40ca44 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -691,18 +691,6 @@ public function run(): void "name": "Perusahaan", "guard_name": "web", "permissions": [ - "ViewAny:Announcement", - "View:Announcement", - "Create:Announcement", - "Update:Announcement", - "Delete:Announcement", - "Restore:Announcement", - "ForceDelete:Announcement", - "ForceDeleteAny:Announcement", - "RestoreAny:Announcement", - "Replicate:Announcement", - "Reorder:Announcement", - "ViewAny:Cooperation", "View:Cooperation", "Create:Cooperation", From 01fe159d52457af736a83bc7905756aa216f30d4 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:46:40 +0700 Subject: [PATCH 15/20] fix: Adjust 'Media Terverifikasi' count to reflect company's approved verification status in the OverviewStats widget. --- app/Filament/Widgets/OverviewStats.php | 36 ++++++++++++++------------ 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/app/Filament/Widgets/OverviewStats.php b/app/Filament/Widgets/OverviewStats.php index d12f36e..c83d515 100644 --- a/app/Filament/Widgets/OverviewStats.php +++ b/app/Filament/Widgets/OverviewStats.php @@ -27,57 +27,59 @@ protected function getStats(): array $startDate = $this->filters['startDate'] ?? null; $endDate = $this->filters['endDate'] ?? null; - $totalJournalists = Journalist::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) + $totalJournalists = Journalist::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) ->count(); - $totalMedia = PartnerMedia::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) + $totalMedia = PartnerMedia::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) ->count(); $average = $totalMedia > 0 ? round($totalJournalists / $totalMedia, 1) : 0; return [ - Stat::make('Total Perusahaan', Company::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) + Stat::make('Total Perusahaan', Company::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Perusahaan terdaftar') ->descriptionIcon(Heroicon::BuildingOffice2) ->color('primary'), - Stat::make('Media Terverifikasi', PartnerMedia::approved() - ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) + Stat::make('Media Terverifikasi', PartnerMedia::whereHas('company.verificationRequest', function ($q) { + $q->approved(); + }) + ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Media aktif') ->descriptionIcon(Heroicon::CheckBadge) ->color('success'), - Stat::make('Total Jurnalis', Journalist::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) + Stat::make('Total Jurnalis', Journalist::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Jurnalis terdaftar') ->descriptionIcon(Heroicon::Identification) ->color('info'), Stat::make('Antrean Verifikasi', VerificationRequest::pending() - ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) + ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Butuh tindakan') ->descriptionIcon(Heroicon::PencilSquare) ->color('warning'), Stat::make('Antrean Perubahan', DataChangeRequest::pending() - ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) + ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Perubahan data') ->descriptionIcon(Heroicon::DocumentText) ->color('warning'), - Stat::make('Total Pengunjung', Visitor::when($startDate, fn ($q) => $q->whereDate('date', '>=', $startDate)) - ->when($endDate, fn ($q) => $q->whereDate('date', '<=', $endDate)) + Stat::make('Total Pengunjung', Visitor::when($startDate, fn($q) => $q->whereDate('date', '>=', $startDate)) + ->when($endDate, fn($q) => $q->whereDate('date', '<=', $endDate)) ->count()) ->description('Total traffic') ->descriptionIcon(Heroicon::ChartBar) From ebd748fe8339366508c77af823fafce38495bbbf Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:50:22 +0700 Subject: [PATCH 16/20] feat: Enhance table filter UI by removing default date type selection, setting filter layout above content, and standardizing date/select input styles. --- .../Cooperations/Tables/CooperationsTable.php | 26 ++++++++++----- .../Tables/ContentRecapsTable.php | 1 - .../Tables/IssueManagementTable.php | 1 - .../Tables/MediaMonitoringsTable.php | 1 - app/Filament/Widgets/OverviewStats.php | 32 +++++++++---------- 5 files changed, 34 insertions(+), 27 deletions(-) diff --git a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php index a30a641..10bd11a 100644 --- a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php +++ b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php @@ -26,6 +26,7 @@ use Filament\Schemas\Components\Utilities\Get; use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\TextColumn; +use Filament\Tables\Enums\FiltersLayout; use Filament\Tables\Filters\Filter; use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Filters\TrashedFilter; @@ -178,12 +179,14 @@ public static function configure(Table $table): Table 'year' => 'Tahun', 'range' => 'Rentang Tanggal', ]) - ->default('date') - ->live(), + ->live() + ->native(false), DatePicker::make('specific_date') ->label('Tanggal') - ->visible(fn (Get $get) => $get('date_type') === 'date'), + ->visible(fn (Get $get) => $get('date_type') === 'date') + ->native(false) + ->displayFormat('l, d F Y'), Select::make('month') ->label('Bulan') @@ -201,7 +204,8 @@ public static function configure(Table $table): Table 11 => 'November', 12 => 'Desember', ]) - ->visible(fn (Get $get) => $get('date_type') === 'month'), + ->visible(fn (Get $get) => $get('date_type') === 'month') + ->native(false), Select::make('year') ->label('Tahun') @@ -210,15 +214,20 @@ public static function configure(Table $table): Table return array_combine($years, $years); }) - ->visible(fn (Get $get) => in_array($get('date_type'), ['month', 'year'])), + ->visible(fn (Get $get) => in_array($get('date_type'), ['month', 'year'])) + ->native(false), DatePicker::make('start_date') ->label('Mulai Tanggal') - ->visible(fn (Get $get) => $get('date_type') === 'range'), + ->visible(fn (Get $get) => $get('date_type') === 'range') + ->native(false) + ->displayFormat('l, d F Y'), DatePicker::make('end_date') ->label('Sampai Tanggal') - ->visible(fn (Get $get) => $get('date_type') === 'range'), + ->visible(fn (Get $get) => $get('date_type') === 'range') + ->native(false) + ->displayFormat('l, d F Y'), ]) ->query(function (Builder $query, array $data): Builder { $type = $data['date_type'] ?? null; @@ -240,7 +249,8 @@ public static function configure(Table $table): Table return $query; }), - ]) + ], FiltersLayout::AboveContentCollapsible) + ->filtersFormColumns(3) ->recordActions([ ViewAction::make(), diff --git a/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php b/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php index 7b4fc4a..c402d8e 100644 --- a/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php +++ b/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php @@ -108,7 +108,6 @@ public static function configure(Table $table): Table 'year' => 'Tahun', 'range' => 'Rentang Tanggal', ]) - ->default('date') ->live() ->native(false), diff --git a/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php b/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php index d517086..3404e92 100644 --- a/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php +++ b/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php @@ -123,7 +123,6 @@ public static function configure(Table $table): Table 'year' => 'Tahun', 'range' => 'Rentang Tanggal', ]) - ->default('date') ->live() ->native(false), diff --git a/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php b/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php index 715ad47..e41d007 100644 --- a/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php +++ b/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php @@ -135,7 +135,6 @@ public static function configure(Table $table): Table 'year' => 'Tahun', 'range' => 'Rentang Tanggal', ]) - ->default('date') ->live() ->native(false), diff --git a/app/Filament/Widgets/OverviewStats.php b/app/Filament/Widgets/OverviewStats.php index c83d515..d88e8f1 100644 --- a/app/Filament/Widgets/OverviewStats.php +++ b/app/Filament/Widgets/OverviewStats.php @@ -27,19 +27,19 @@ protected function getStats(): array $startDate = $this->filters['startDate'] ?? null; $endDate = $this->filters['endDate'] ?? null; - $totalJournalists = Journalist::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + $totalJournalists = Journalist::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) ->count(); - $totalMedia = PartnerMedia::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + $totalMedia = PartnerMedia::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) ->count(); $average = $totalMedia > 0 ? round($totalJournalists / $totalMedia, 1) : 0; return [ - Stat::make('Total Perusahaan', Company::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + Stat::make('Total Perusahaan', Company::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Perusahaan terdaftar') ->descriptionIcon(Heroicon::BuildingOffice2) @@ -48,38 +48,38 @@ protected function getStats(): array Stat::make('Media Terverifikasi', PartnerMedia::whereHas('company.verificationRequest', function ($q) { $q->approved(); }) - ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Media aktif') ->descriptionIcon(Heroicon::CheckBadge) ->color('success'), - Stat::make('Total Jurnalis', Journalist::when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + Stat::make('Total Jurnalis', Journalist::when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Jurnalis terdaftar') ->descriptionIcon(Heroicon::Identification) ->color('info'), Stat::make('Antrean Verifikasi', VerificationRequest::pending() - ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Butuh tindakan') ->descriptionIcon(Heroicon::PencilSquare) ->color('warning'), Stat::make('Antrean Perubahan', DataChangeRequest::pending() - ->when($startDate, fn($q) => $q->whereDate('created_at', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('created_at', '<=', $endDate)) + ->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate)) ->count()) ->description('Perubahan data') ->descriptionIcon(Heroicon::DocumentText) ->color('warning'), - Stat::make('Total Pengunjung', Visitor::when($startDate, fn($q) => $q->whereDate('date', '>=', $startDate)) - ->when($endDate, fn($q) => $q->whereDate('date', '<=', $endDate)) + Stat::make('Total Pengunjung', Visitor::when($startDate, fn ($q) => $q->whereDate('date', '>=', $startDate)) + ->when($endDate, fn ($q) => $q->whereDate('date', '<=', $endDate)) ->count()) ->description('Total traffic') ->descriptionIcon(Heroicon::ChartBar) From 9a63d339913acb4ccc9617575e706e3c1eba433e Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 14:56:42 +0700 Subject: [PATCH 17/20] refactor: Standardize role checks by using `RoleEnum` for the 'Developer' role across multiple Filament resources and tables. --- .../Resources/Manage/Cooperations/Tables/CooperationsTable.php | 2 +- .../Resources/Manage/DataChanges/DataChangesResource.php | 2 +- .../Resources/Manage/Journalists/JournalistResource.php | 2 +- app/Filament/Resources/Manage/Partners/PartnerResource.php | 3 ++- app/Filament/Resources/Master/Categories/CategoryResource.php | 3 ++- .../Master/Classifications/ClassificationResource.php | 3 ++- .../Resources/Master/Departments/DepartmentResource.php | 3 ++- app/Filament/Resources/Master/Locations/LocationResource.php | 3 ++- .../Master/SubClassifications/SubClassificationResource.php | 3 ++- .../Resources/Master/SubLocations/SubLocationResource.php | 3 ++- app/Filament/Resources/Master/Themes/ThemeResource.php | 3 ++- app/Filament/Resources/Master/Users/UserResource.php | 2 +- .../Monitoring/ContentRecaps/Tables/ContentRecapsTable.php | 3 ++- .../IssueManagements/Tables/IssueManagementTable.php | 3 ++- .../MediaMonitorings/Tables/MediaMonitoringsTable.php | 3 ++- .../Publication/Announcements/AnnouncementResource.php | 3 ++- app/Filament/Resources/Publication/News/Tables/NewsTable.php | 3 ++- 17 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php index 10bd11a..32f8777 100644 --- a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php +++ b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php @@ -144,7 +144,7 @@ public static function configure(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn (): bool => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), SelectFilter::make('status') ->label('Status') diff --git a/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php b/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php index a562333..0fbb64b 100644 --- a/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php +++ b/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php @@ -249,7 +249,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ ViewAction::make(), diff --git a/app/Filament/Resources/Manage/Journalists/JournalistResource.php b/app/Filament/Resources/Manage/Journalists/JournalistResource.php index e5ff292..8adc390 100644 --- a/app/Filament/Resources/Manage/Journalists/JournalistResource.php +++ b/app/Filament/Resources/Manage/Journalists/JournalistResource.php @@ -175,7 +175,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->checkIfRecordIsSelectableUsing( fn () => ! ( diff --git a/app/Filament/Resources/Manage/Partners/PartnerResource.php b/app/Filament/Resources/Manage/Partners/PartnerResource.php index b3d646d..0993308 100644 --- a/app/Filament/Resources/Manage/Partners/PartnerResource.php +++ b/app/Filament/Resources/Manage/Partners/PartnerResource.php @@ -2,6 +2,7 @@ namespace App\Filament\Resources\Manage\Partners; +use App\Enums\RoleEnum; use App\Filament\Actions\DefaultBulkActions; use App\Filament\Resources\Manage\Partners\Pages\ManagePartners; use App\Filament\Resources\Manage\Partners\Pages\ViewPartner; @@ -323,7 +324,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ ViewAction::make(), diff --git a/app/Filament/Resources/Master/Categories/CategoryResource.php b/app/Filament/Resources/Master/Categories/CategoryResource.php index 2a216e3..44aed5f 100644 --- a/app/Filament/Resources/Master/Categories/CategoryResource.php +++ b/app/Filament/Resources/Master/Categories/CategoryResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Master\Categories; use App\Enums\IsActive; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -83,7 +84,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Master/Classifications/ClassificationResource.php b/app/Filament/Resources/Master/Classifications/ClassificationResource.php index 6820dd8..101773c 100644 --- a/app/Filament/Resources/Master/Classifications/ClassificationResource.php +++ b/app/Filament/Resources/Master/Classifications/ClassificationResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Master\Classifications; use App\Enums\IsActive; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -83,7 +84,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Master/Departments/DepartmentResource.php b/app/Filament/Resources/Master/Departments/DepartmentResource.php index f2a85dd..1031b32 100644 --- a/app/Filament/Resources/Master/Departments/DepartmentResource.php +++ b/app/Filament/Resources/Master/Departments/DepartmentResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Master\Departments; use App\Enums\IsActive; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -93,7 +94,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Master/Locations/LocationResource.php b/app/Filament/Resources/Master/Locations/LocationResource.php index bbd8445..e5b22d3 100644 --- a/app/Filament/Resources/Master/Locations/LocationResource.php +++ b/app/Filament/Resources/Master/Locations/LocationResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Master\Locations; use App\Enums\IsActive; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -83,7 +84,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php b/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php index f5d4b22..3b365a1 100644 --- a/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php +++ b/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Master\SubClassifications; use App\Enums\IsActive; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -102,7 +103,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Master/SubLocations/SubLocationResource.php b/app/Filament/Resources/Master/SubLocations/SubLocationResource.php index 85e5cd5..3991466 100644 --- a/app/Filament/Resources/Master/SubLocations/SubLocationResource.php +++ b/app/Filament/Resources/Master/SubLocations/SubLocationResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Master\SubLocations; use App\Enums\IsActive; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -98,7 +99,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Master/Themes/ThemeResource.php b/app/Filament/Resources/Master/Themes/ThemeResource.php index 765a97e..0c1c038 100644 --- a/app/Filament/Resources/Master/Themes/ThemeResource.php +++ b/app/Filament/Resources/Master/Themes/ThemeResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Master\Themes; use App\Enums\IsActive; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -83,7 +84,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Master/Users/UserResource.php b/app/Filament/Resources/Master/Users/UserResource.php index 8d56e1e..1a867bf 100644 --- a/app/Filament/Resources/Master/Users/UserResource.php +++ b/app/Filament/Resources/Master/Users/UserResource.php @@ -147,7 +147,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php b/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php index c402d8e..5c1d63c 100644 --- a/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php +++ b/app/Filament/Resources/Monitoring/ContentRecaps/Tables/ContentRecapsTable.php @@ -4,6 +4,7 @@ use App\Enums\Channel; use App\Enums\ContentType; +use App\Enums\RoleEnum; use App\Enums\SocialMedia; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; @@ -74,7 +75,7 @@ public static function configure(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), SelectFilter::make('social_media') ->label('Media Sosial') diff --git a/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php b/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php index 3404e92..464f6cf 100644 --- a/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php +++ b/app/Filament/Resources/Monitoring/IssueManagements/Tables/IssueManagementTable.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Monitoring\IssueManagements\Tables; use App\Enums\IssueSentiment; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -73,7 +74,7 @@ public static function configure(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), SelectFilter::make('issue') ->label('Filter Isu') diff --git a/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php b/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php index e41d007..ec18f8d 100644 --- a/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php +++ b/app/Filament/Resources/Monitoring/MediaMonitorings/Tables/MediaMonitoringsTable.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Monitoring\MediaMonitorings\Tables; use App\Enums\Channel; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -98,7 +99,7 @@ public static function configure(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), SelectFilter::make('themes') ->label('Tema') diff --git a/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php index 02ad31a..32a1996 100644 --- a/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php +++ b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Publication\Announcements; use App\Enums\AnnouncementType; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -134,7 +135,7 @@ public static function table(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make() diff --git a/app/Filament/Resources/Publication/News/Tables/NewsTable.php b/app/Filament/Resources/Publication/News/Tables/NewsTable.php index 9e982b5..7fd2654 100644 --- a/app/Filament/Resources/Publication/News/Tables/NewsTable.php +++ b/app/Filament/Resources/Publication/News/Tables/NewsTable.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Publication\News\Tables; use App\Enums\NewsStatus; +use App\Enums\RoleEnum; use App\Filament\Actions\Cheerful\DeleteAction; use App\Filament\Actions\Cheerful\EditAction; use App\Filament\Actions\Cheerful\ForceDeleteAction; @@ -83,7 +84,7 @@ public static function configure(Table $table): Table ->filters([ TrashedFilter::make() ->native(false) - ->visible(fn () => auth()->user()?->hasRole('Developer')), + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), ]) ->recordActions([ EditAction::make(), From c4347f980ecc4134acc654100f5e313ffa5ef27f Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 15:21:10 +0700 Subject: [PATCH 18/20] feat: Conditionally render cooperation table filters based on user role and set the status filter to non-native. --- .../Cooperations/Tables/CooperationsTable.php | 214 +++++++++--------- database/seeders/ShieldSeeder.php | 18 -- 2 files changed, 109 insertions(+), 123 deletions(-) diff --git a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php index 32f8777..39bfb54 100644 --- a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php +++ b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php @@ -141,115 +141,120 @@ public static function configure(Table $table): Table ...TimestampColumns::make(), ]) - ->filters([ - TrashedFilter::make() - ->native(false) - ->visible(fn (): bool => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), + ->filters( + auth()->user()->hasRole(RoleEnum::DEVELOPER->value) || auth()->user()->hasRole(RoleEnum::ADMINISTRATOR->value) + ? [ + TrashedFilter::make() + ->native(false) + ->visible(fn (): bool => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), - SelectFilter::make('status') - ->label('Status') - ->options(CooperationStatus::class), + SelectFilter::make('status') + ->label('Status') + ->options(CooperationStatus::class) + ->native(false), - SelectFilter::make('partnerMedia') - ->label('Media') - ->relationship('partnerMedia', 'name') - ->searchable() - ->preload(), + SelectFilter::make('partnerMedia') + ->label('Media') + ->relationship('partnerMedia', 'name') + ->searchable() + ->preload(), - Filter::make('title') - ->schema([ - TextInput::make('title') - ->label('Nama Kegiatan') - ->placeholder('Cari nama kegiatan...'), - ]) - ->query(function (Builder $query, array $data): Builder { - return $query->when( - $data['title'], - fn (Builder $query, $title): Builder => $query->where('title', 'like', "%{$title}%"), - ); - }), - - Filter::make('date_filter') - ->schema([ - Select::make('date_type') - ->label('Jenis Filter Tanggal') - ->options([ - 'date' => 'Tanggal Penyerahan Awal', - 'month' => 'Bulan & Tahun', - 'year' => 'Tahun', - 'range' => 'Rentang Tanggal', + Filter::make('title') + ->schema([ + TextInput::make('title') + ->label('Nama Kegiatan') + ->placeholder('Cari nama kegiatan...'), ]) - ->live() - ->native(false), + ->query(function (Builder $query, array $data): Builder { + return $query->when( + $data['title'], + fn (Builder $query, $title): Builder => $query->where('title', 'like', "%{$title}%"), + ); + }), - DatePicker::make('specific_date') - ->label('Tanggal') - ->visible(fn (Get $get) => $get('date_type') === 'date') - ->native(false) - ->displayFormat('l, d F Y'), + Filter::make('date_filter') + ->schema([ + Select::make('date_type') + ->label('Jenis Filter Tanggal') + ->options([ + 'date' => 'Tanggal Penyerahan Awal', + 'month' => 'Bulan & Tahun', + 'year' => 'Tahun', + 'range' => 'Rentang Tanggal', + ]) + ->live() + ->native(false), - Select::make('month') - ->label('Bulan') - ->options([ - 1 => 'Januari', - 2 => 'Februari', - 3 => 'Maret', - 4 => 'April', - 5 => 'Mei', - 6 => 'Juni', - 7 => 'Juli', - 8 => 'Agustus', - 9 => 'September', - 10 => 'Oktober', - 11 => 'November', - 12 => 'Desember', + DatePicker::make('specific_date') + ->label('Tanggal') + ->visible(fn (Get $get) => $get('date_type') === 'date') + ->native(false) + ->displayFormat('l, d F Y'), + + Select::make('month') + ->label('Bulan') + ->options([ + 1 => 'Januari', + 2 => 'Februari', + 3 => 'Maret', + 4 => 'April', + 5 => 'Mei', + 6 => 'Juni', + 7 => 'Juli', + 8 => 'Agustus', + 9 => 'September', + 10 => 'Oktober', + 11 => 'November', + 12 => 'Desember', + ]) + ->visible(fn (Get $get) => $get('date_type') === 'month') + ->native(false), + + Select::make('year') + ->label('Tahun') + ->options(function () { + $years = range(date('Y'), 2020); + + return array_combine($years, $years); + }) + ->visible(fn (Get $get) => in_array($get('date_type'), ['month', 'year'])) + ->native(false), + + DatePicker::make('start_date') + ->label('Mulai Tanggal') + ->visible(fn (Get $get) => $get('date_type') === 'range') + ->native(false) + ->displayFormat('l, d F Y'), + + DatePicker::make('end_date') + ->label('Sampai Tanggal') + ->visible(fn (Get $get) => $get('date_type') === 'range') + ->native(false) + ->displayFormat('l, d F Y'), ]) - ->visible(fn (Get $get) => $get('date_type') === 'month') - ->native(false), + ->query(function (Builder $query, array $data): Builder { + $type = $data['date_type'] ?? null; + $column = 'initial_submission_date'; - Select::make('year') - ->label('Tahun') - ->options(function () { - $years = range(date('Y'), 2020); + if ($type === 'date' && ! empty($data['specific_date'])) { + return $query->whereDate($column, $data['specific_date']); + } + if ($type === 'month' && ! empty($data['month']) && ! empty($data['year'])) { + return $query->whereMonth($column, $data['month']) + ->whereYear($column, $data['year']); + } + if ($type === 'year' && ! empty($data['year'])) { + return $query->whereYear($column, $data['year']); + } + if ($type === 'range' && ! empty($data['start_date']) && ! empty($data['end_date'])) { + return $query->whereBetween($column, [$data['start_date'], $data['end_date']]); + } - return array_combine($years, $years); - }) - ->visible(fn (Get $get) => in_array($get('date_type'), ['month', 'year'])) - ->native(false), - - DatePicker::make('start_date') - ->label('Mulai Tanggal') - ->visible(fn (Get $get) => $get('date_type') === 'range') - ->native(false) - ->displayFormat('l, d F Y'), - - DatePicker::make('end_date') - ->label('Sampai Tanggal') - ->visible(fn (Get $get) => $get('date_type') === 'range') - ->native(false) - ->displayFormat('l, d F Y'), - ]) - ->query(function (Builder $query, array $data): Builder { - $type = $data['date_type'] ?? null; - $column = 'initial_submission_date'; - - if ($type === 'date' && ! empty($data['specific_date'])) { - return $query->whereDate($column, $data['specific_date']); - } - if ($type === 'month' && ! empty($data['month']) && ! empty($data['year'])) { - return $query->whereMonth($column, $data['month']) - ->whereYear($column, $data['year']); - } - if ($type === 'year' && ! empty($data['year'])) { - return $query->whereYear($column, $data['year']); - } - if ($type === 'range' && ! empty($data['start_date']) && ! empty($data['end_date'])) { - return $query->whereBetween($column, [$data['start_date'], $data['end_date']]); - } - - return $query; - }), - ], FiltersLayout::AboveContentCollapsible) + return $query; + }), + ] : [], + FiltersLayout::AboveContentCollapsible + ) ->filtersFormColumns(3) ->recordActions([ ViewAction::make(), @@ -266,18 +271,17 @@ public static function configure(Table $table): Table CompleteCooperationAction::make('completeCooperation'), - EditAction::make() - ->visible(fn (): bool => ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)), + EditAction::make(), - DeleteAction::make() - ->visible(fn (): bool => ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)), + DeleteAction::make(), ]) ->headerActions([ ExportAction::make() ->exports([ CooperationExcelExport::make(), ]) - ->color('success'), + ->color('success') + ->visible(fn (): bool => auth()->user()->hasRole(RoleEnum::DEVELOPER->value) || auth()->user()->hasRole(RoleEnum::ADMINISTRATOR->value)), ]) ->toolbarActions([ diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index b40ca44..94b0c08 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -693,15 +693,6 @@ public function run(): void "permissions": [ "ViewAny:Cooperation", "View:Cooperation", - "Create:Cooperation", - "Update:Cooperation", - "Delete:Cooperation", - "Restore:Cooperation", - "ForceDelete:Cooperation", - "ForceDeleteAny:Cooperation", - "RestoreAny:Cooperation", - "Replicate:Cooperation", - "Reorder:Cooperation", "View:Company", @@ -709,15 +700,6 @@ public function run(): void "ViewAny:DataChangeRequest", "View:DataChangeRequest", - "Create:DataChangeRequest", - "Update:DataChangeRequest", - "Delete:DataChangeRequest", - "Restore:DataChangeRequest", - "ForceDelete:DataChangeRequest", - "ForceDeleteAny:DataChangeRequest", - "RestoreAny:DataChangeRequest", - "Replicate:DataChangeRequest", - "Reorder:DataChangeRequest", "ViewAny:Journalist", "View:Journalist", From 693d1f001c9d30f7b585bcf87286effeee49db36 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 30 Jan 2026 15:32:31 +0700 Subject: [PATCH 19/20] feat: Add empty state icons and descriptions to Filament relation managers, update `CooperationMedia` payment relationship to `HasOne`, and adjust media manager visibility. --- .../RelationManagers/CooperationMediaRelationManager.php | 6 ++++-- .../RelationManagers/CooperationProposalRelationManager.php | 3 +++ .../Cooperations/RelationManagers/ReportRelationManager.php | 5 ++++- .../RelationManagers/TaskAssignmentRelationManager.php | 5 ++++- app/Models/CooperationMedia.php | 6 +++--- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php index c558615..c008eb8 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php @@ -12,6 +12,7 @@ use Filament\Resources\RelationManagers\RelationManager; use Filament\Schemas\Components\Grid; use Filament\Schemas\Schema; +use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; @@ -24,7 +25,7 @@ class CooperationMediaRelationManager extends RelationManager public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool { - return auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value); + return ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value); } public static function getBadge(Model $ownerRecord, string $pageClass): ?string @@ -57,7 +58,8 @@ public function table(Table $table): Table ViewAction::make() ->visible(fn (CooperationMedia $record): bool => $record->status === ApprovalStatus::ACCEPTED && $record->payment()->exists()), ]) - ->recordAction(null); + ->emptyStateIcon(Heroicon::Newspaper) + ->emptyStateDescription('Tidak ada media yang diikutsertakan.'); } public function infolist(Schema $infolist): Schema diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php index 756344d..6b4225d 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php @@ -15,6 +15,7 @@ use Filament\Resources\RelationManagers\RelationManager; use Filament\Schemas\Components\Grid; use Filament\Schemas\Schema; +use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; @@ -107,6 +108,8 @@ public function table(Table $table): Table ), ]), ]) + ->emptyStateIcon(Heroicon::DocumentText) + ->emptyStateDescription('Tidak ada proposal yang diajukan.') ->modifyQueryUsing(function ($query) { $user = auth()->user(); diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php index 80c4b1f..1941ada 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php @@ -21,6 +21,7 @@ use Filament\Resources\RelationManagers\RelationManager; use Filament\Schemas\Schema; use Filament\Support\Enums\Width; +use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; @@ -188,6 +189,8 @@ public function table(Table $table): Table ->count() < $taskAssignment->report_amount; }), ]) - ->toolbarActions([]); + ->toolbarActions([]) + ->emptyStateIcon(Heroicon::InboxArrowDown) + ->emptyStateDescription('Tidak ada laporan yang diajukan.'); } } diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php index ae955ca..6c6a645 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php @@ -11,6 +11,7 @@ use Filament\Actions\DeleteBulkAction; use Filament\Notifications\Notification; use Filament\Resources\RelationManagers\RelationManager; +use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Model; @@ -102,6 +103,8 @@ public function table(Table $table): Table ->success() ), ]), - ]); + ]) + ->emptyStateIcon(Heroicon::ArchiveBoxXMark) + ->emptyStateDescription('Tidak ada penugasan yang ditugaskan.'); } } diff --git a/app/Models/CooperationMedia.php b/app/Models/CooperationMedia.php index 890be45..abf35fb 100644 --- a/app/Models/CooperationMedia.php +++ b/app/Models/CooperationMedia.php @@ -7,7 +7,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\Pivot; use Spatie\MediaLibrary\HasMedia; @@ -57,9 +57,9 @@ public function partnerMedia(): BelongsTo return $this->belongsTo(PartnerMedia::class); } - public function payments(): HasMany + public function payment(): HasOne { - return $this->hasMany(CooperationPayment::class, 'cooperation_media_id'); + return $this->hasOne(CooperationPayment::class, 'cooperation_media_id'); } public function rejectionReasons(): MorphMany From 2d7f4ee0e80699cfe9ce4d65db4b13f8fd9a8d28 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Mon, 2 Feb 2026 11:56:49 +0700 Subject: [PATCH 20/20] feat: Enhance cooperation and reporting workflows with refined relationships, new payment management, and updated actions. --- .agent/workflows/commit.md | 22 ------ app/Enums/CooperationStatus.php | 2 +- .../Cooperation/CompleteCooperationAction.php | 2 +- .../Cooperation/ProceedToPaymentAction.php | 5 +- .../Cooperation/SendProposalAction.php | 4 +- .../Cooperations/Actions/Media/PayAction.php | 15 ++++ .../Actions/Report/AcceptAction.php | 31 +++----- .../Actions/Report/RejectAction.php | 31 +++----- .../Cooperations/CooperationResource.php | 7 ++ .../CooperationMediaRelationManager.php | 9 ++- .../CooperationPaymentRelationManager.php | 74 +++++++++++++++++++ .../CooperationProposalRelationManager.php | 3 + .../ReportRelationManager.php | 52 +++++++++++-- .../Cooperations/Tables/CooperationsTable.php | 7 ++ app/Models/Cooperation.php | 28 +++++-- app/Models/MediaTaskAssignment.php | 6 ++ app/Models/PartnerMedia.php | 2 + app/Models/Report.php | 4 +- app/Models/TaskAssignment.php | 15 +++- app/Models/User.php | 6 +- ...12_12_111212_create_cooperations_table.php | 3 +- ..._111307_create_cooperation_media_table.php | 2 +- ...245_create_cooperation_proposals_table.php | 4 +- ...823_create_media_task_assignment_table.php | 2 +- ...2025_12_12_135847_create_reports_table.php | 6 +- 25 files changed, 246 insertions(+), 96 deletions(-) delete mode 100644 .agent/workflows/commit.md create mode 100644 app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationPaymentRelationManager.php diff --git a/.agent/workflows/commit.md b/.agent/workflows/commit.md deleted file mode 100644 index f5d2a41..0000000 --- a/.agent/workflows/commit.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -description: Automatic commit message generator and fast AI-powered commit for all current changes ---- - -// turbo-all - -This workflow automatically stages all changes, generates a descriptive commit message, and commits them in one go. - -### Steps: - -1. **Stage All Changes**: Automatically stage all modified and new files. - ```bash - git add . - ``` -2. **Analyze Changes**: Get the diff of staged changes to understand the context. - ```bash - git diff --cached - ``` -3. **Generate & Commit**: Generate a professional message following [Conventional Commits](https://www.conventionalcommits.org/) and execute the commit. - ```bash - git commit -m "" - ``` \ No newline at end of file diff --git a/app/Enums/CooperationStatus.php b/app/Enums/CooperationStatus.php index 3e7e379..93420c2 100644 --- a/app/Enums/CooperationStatus.php +++ b/app/Enums/CooperationStatus.php @@ -34,7 +34,7 @@ public function getColor(): string|array|null self::PENDING => 'warning', self::ASSIGNMENT => 'info', self::VERIFICATION => 'primary', - self::PAYMENT => 'secondary', + self::PAYMENT => 'slate', self::COMPLETED => 'success', }; } diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CompleteCooperationAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CompleteCooperationAction.php index 38a1b14..e30c388 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CompleteCooperationAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CompleteCooperationAction.php @@ -54,7 +54,7 @@ protected function setUp(): void $record->update([ 'payment_amount' => $totalPayment, - 'payment_date' => now(), + 'completed_at' => now(), 'status' => CooperationStatus::COMPLETED, ]); diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/ProceedToPaymentAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/ProceedToPaymentAction.php index e8fe503..7b3a410 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/ProceedToPaymentAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/ProceedToPaymentAction.php @@ -23,7 +23,10 @@ protected function setUp(): void ->modalHeading('Lanjutkan ke Pembayaran?') ->modalDescription('Apakah Anda yakin ingin melanjutkan kerja sama ini ke tahap pembayaran? Pastikan semua laporan telah diverifikasi.') ->action(function (Cooperation $record): void { - $record->update(['status' => CooperationStatus::PAYMENT]); + $record->update([ + 'status' => CooperationStatus::PAYMENT, + 'payment_date' => now()->toDateString(), + ]); CheerfulNotification::success( 'Tahap Pembayaran Dimulai! πŸ’Έβœ¨', diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php index 55cf37a..5333b35 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php @@ -34,7 +34,9 @@ protected function setUp(): void ->autocomplete(false) ->autofocus() ->nullable() - ->url(), + ->url() + ->required() + ->maxLength(200), Textarea::make('description') ->label('Deskripsi') diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Media/PayAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Media/PayAction.php index 58d1771..be96e3d 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Media/PayAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Media/PayAction.php @@ -4,8 +4,10 @@ use App\Enums\ApprovalStatus; use App\Enums\CooperationStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; use App\Filament\Support\CheerfulNotification; use App\Models\CooperationMedia; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Forms\Components\SpatieMediaLibraryFileUpload; use Filament\Forms\Components\Textarea; @@ -64,6 +66,19 @@ protected function setUp(): void 'Pembayaran Berhasil! πŸ’Έβœ¨', "Pembayaran untuk {$record->partnerMedia->name} telah berhasil dicatat." )->send(); + + $partnerUser = $record->partnerMedia->company?->user; + if ($partnerUser) { + $partnerUser->notify(new BroadcastNotification([ + 'title' => 'Pembayaran Telah Dikirim! πŸ’Έβœ¨', + 'body' => "Halo! Pembayaran untuk kerja sama \"{$record->cooperation->title}\" telah berhasil dikirim oleh Admin. Silakan cek detailnya ya! 😊", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->cooperation_id, 'relation' => 0])), + ], + ])); + } }) ->modalWidth(Width::Large); } diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php index 60b2a63..8ed1555 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php @@ -32,28 +32,21 @@ protected function setUp(): void ->success() ->send(); - // Notify all partners in this cooperation (since reports might be per media even if shared in DB) - $partners = $record->taskAssignment?->cooperation?->partnerMedia() - ->wherePivot('status', ApprovalStatus::ACCEPTED) - ->get(); - - foreach ($partners as $media) { - $partnerUser = $media->company?->user; - if ($partnerUser) { - $partnerUser->notify(new BroadcastNotification([ - 'title' => 'Mantap! Laporan Diterima 🎊✨', - 'body' => "Hore! Laporan \"{$record->title}\" pada kerja sama \"{$record->taskAssignment->cooperation->title}\" telah disetujui oleh Admin. Satu langkah lebih dekat! πŸš€", - 'action' => [ - Action::make('view') - ->label('Lihat') - ->url(CooperationResource::getUrl('view', ['record' => $record->taskAssignment->cooperation_id, 'relation' => 3])), - ], - ])); - } + $partnerUser = $record->mediaTaskAssignment?->partnerMedia?->company?->user; + if ($partnerUser) { + $partnerUser->notify(new BroadcastNotification([ + 'title' => 'Mantap! Laporan Diterima 🎊✨', + 'body' => "Hore! Laporan \"{$record->title}\" pada kerja sama \"{$record->mediaTaskAssignment->taskAssignment->cooperation->title}\" telah disetujui oleh Admin. Satu langkah lebih dekat! πŸš€", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->mediaTaskAssignment->taskAssignment->cooperation_id, 'relation' => 3])), + ], + ])); } }) ->visible( - fn (Report $record): bool => $record->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION && + fn (Report $record): bool => $record->mediaTaskAssignment?->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION && ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) && $record->status === ApprovalStatus::PENDING ); diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php index 90a7c76..30f5844 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php @@ -42,28 +42,21 @@ protected function setUp(): void ->warning() ->send(); - // Notify all partners in this cooperation (since we can't tell which specific one) - $partners = $record->taskAssignment?->cooperation?->partnerMedia() - ->wherePivot('status', ApprovalStatus::ACCEPTED) - ->get(); - - foreach ($partners as $media) { - $partnerUser = $media->company?->user; - if ($partnerUser) { - $partnerUser->notify(new BroadcastNotification([ - 'title' => 'Yah, Laporan Perlu Diperbaiki nih πŸ“', - 'body' => "Halo! Laporan \"{$record->title}\" pada kerja sama \"{$record->taskAssignment->cooperation->title}\" ditolak oleh Admin. πŸ’ͺ\n\nCatatan Admin: {$data['reason']}", - 'action' => [ - Action::make('view') - ->label('Lihat') - ->url(CooperationResource::getUrl('view', ['record' => $record->taskAssignment->cooperation_id])), - ], - ])); - } + $partnerUser = $record->mediaTaskAssignment?->partnerMedia?->company?->user; + if ($partnerUser) { + $partnerUser->notify(new BroadcastNotification([ + 'title' => 'Yah, Laporan Ditolak πŸ™', + 'body' => "Halo! Laporan \"{$record->title}\" pada kerja sama \"{$record->mediaTaskAssignment->taskAssignment->cooperation->title}\" ditolak oleh Admin. πŸ’ͺ\n\nCatatan Admin: {$data['reason']}", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->mediaTaskAssignment->taskAssignment->cooperation_id])), + ], + ])); } }) ->visible( - fn (Report $record): bool => $record->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION && + fn (Report $record): bool => $record->mediaTaskAssignment?->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION && ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) && $record->status === ApprovalStatus::PENDING ) diff --git a/app/Filament/Resources/Manage/Cooperations/CooperationResource.php b/app/Filament/Resources/Manage/Cooperations/CooperationResource.php index 11f8ad2..2d3cf17 100644 --- a/app/Filament/Resources/Manage/Cooperations/CooperationResource.php +++ b/app/Filament/Resources/Manage/Cooperations/CooperationResource.php @@ -8,6 +8,7 @@ use App\Filament\Resources\Manage\Cooperations\Pages\ListCooperations; use App\Filament\Resources\Manage\Cooperations\Pages\ViewCooperation; use App\Filament\Resources\Manage\Cooperations\RelationManagers\CooperationMediaRelationManager; +use App\Filament\Resources\Manage\Cooperations\RelationManagers\CooperationPaymentRelationManager; use App\Filament\Resources\Manage\Cooperations\RelationManagers\CooperationProposalRelationManager; use App\Filament\Resources\Manage\Cooperations\RelationManagers\ReportRelationManager; use App\Filament\Resources\Manage\Cooperations\RelationManagers\TaskAssignmentRelationManager; @@ -120,6 +121,11 @@ public static function infolist(Schema $schema): Schema ->label('Tanggal Pembayaran') ->date('l, d F Y') ->placeholder('Belum dibayar'), + + TextEntry::make('completed_at') + ->label('Tanggal Selesai') + ->date('l, d F Y') + ->placeholder('Masih berjalan'), ]), Grid::make(3) @@ -153,6 +159,7 @@ public static function getRelations(): array CooperationProposalRelationManager::class, TaskAssignmentRelationManager::class, ReportRelationManager::class, + CooperationPaymentRelationManager::class, ]; } diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php index c008eb8..e75fd0c 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationMediaRelationManager.php @@ -19,6 +19,8 @@ class CooperationMediaRelationManager extends RelationManager { + protected static ?string $recordTitleAttribute = 'partnerMedia.name'; + protected static string $relationship = 'cooperationMedia'; protected static ?string $title = 'Media'; @@ -59,7 +61,8 @@ public function table(Table $table): Table ->visible(fn (CooperationMedia $record): bool => $record->status === ApprovalStatus::ACCEPTED && $record->payment()->exists()), ]) ->emptyStateIcon(Heroicon::Newspaper) - ->emptyStateDescription('Tidak ada media yang diikutsertakan.'); + ->emptyStateDescription('Tidak ada media yang diikutsertakan.') + ->defaultSort('created_at', 'desc'); } public function infolist(Schema $infolist): Schema @@ -70,11 +73,11 @@ public function infolist(Schema $infolist): Schema ->schema([ TextEntry::make('payment.amount') ->label('Nominal') - ->money('IDR'), + ->money('IDR', decimalPlaces: 0), TextEntry::make('payment.payment_date') ->label('Tanggal Bayar') - ->dateTime(), + ->dateTime('l, d F Y'), ]), TextEntry::make('payment.description') diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationPaymentRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationPaymentRelationManager.php new file mode 100644 index 0000000..b1a125e --- /dev/null +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationPaymentRelationManager.php @@ -0,0 +1,74 @@ +user()->hasRole(RoleEnum::PERUSAHAAN->value)) { + return false; + } + + return $ownerRecord->cooperationMedia() + ->whereHas('partnerMedia', function ($query) { + $query->whereHas('company', function ($query) { + $query->where('user_id', auth()->id()); + }); + }) + ->where('status', ApprovalStatus::ACCEPTED) + ->exists(); + } + + public function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('amount') + ->label('Nominal') + ->money('IDR', decimalPlaces: 0), + + TextColumn::make('payment_date') + ->label('Tanggal Bayar') + ->date('l, d F Y'), + + TextColumn::make('description') + ->label('Keterangan') + ->limit(50), + + ImageColumn::make('payment_proof') + ->label('Bukti Pembayaran') + ->getStateUsing(fn (CooperationPayment $record): ?string => optional($record->cooperationMedia->getMedia('cooperations')->where('custom_properties.doc_type', 'payment-proof')->sortByDesc('created_at')->first())->getPathRelativeToRoot()) + ->disk(config('filesystems.default')), + ]) + ->filters([]) + ->headerActions([]) + ->recordActions([]) + ->emptyStateIcon(Heroicon::CreditCard) + ->emptyStateDescription('Belum ada data pembayaran.') + ->defaultSort('created_at', 'desc') + ->modifyQueryUsing(function ($query) { + $user = auth()->user(); + + return $query->whereHas('cooperationMedia.partnerMedia.company', function ($q) use ($user) { + $q->where('user_id', $user->id); + }); + }); + } +} diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php index 6b4225d..ee18b45 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php @@ -23,6 +23,8 @@ class CooperationProposalRelationManager extends RelationManager { + protected static ?string $recordTitleAttribute = 'e_catalog'; + protected static string $relationship = 'proposal'; protected static ?string $title = 'Proposal'; @@ -110,6 +112,7 @@ public function table(Table $table): Table ]) ->emptyStateIcon(Heroicon::DocumentText) ->emptyStateDescription('Tidak ada proposal yang diajukan.') + ->defaultSort('created_at', 'desc') ->modifyQueryUsing(function ($query) { $user = auth()->user(); diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php index 1941ada..fa82fc2 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php @@ -32,6 +32,11 @@ class ReportRelationManager extends RelationManager protected static ?string $title = 'Laporan'; + public function isReadOnly(): bool + { + return false; + } + public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool { if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) { @@ -48,13 +53,16 @@ public static function canViewForRecord(Model $ownerRecord, string $pageClass): return true; } - public function isReadOnly(): bool - { - return false; - } - public static function getBadge(Model $ownerRecord, string $pageClass): ?string { + if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) { + return $ownerRecord->reports() + ->whereHas('mediaTaskAssignment.partnerMedia.company', function ($q) { + $q->where('user_id', auth()->id()); + }) + ->count(); + } + return $ownerRecord->reports()->count(); } @@ -106,6 +114,11 @@ public function table(Table $table): Table { return $table ->columns([ + TextColumn::make('mediaTaskAssignment.partnerMedia.name') + ->label('Media') + ->searchable() + ->visible(! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)), + TextColumn::make('title') ->label('Judul') ->searchable(), @@ -140,7 +153,17 @@ public function table(Table $table): Table ->modalWidth(Width::Large) ->modalHeading('Buat Laporan') ->mutateDataUsing(function (array $data): array { - $data['task_assignment_id'] = $this->getOwnerRecord()->taskAssignment?->id; + $taskAssignment = $this->getOwnerRecord()->taskAssignment; + + if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) { + $mediaTaskAssignment = $taskAssignment?->mediaTaskAssignments() + ->whereHas('partnerMedia.company', function ($query) { + $query->where('user_id', auth()->id()); + }) + ->first(); + + $data['media_task_assignment_id'] = $mediaTaskAssignment?->id; + } return $data; }) @@ -185,12 +208,25 @@ public function table(Table $table): Table && $taskAssignment->start_date->toDateString() <= now()->toDateString() && $taskAssignment->end_date->toDateString() >= now()->toDateString() && $taskAssignment->reports() - ->where('status', '!=', ApprovalStatus::REJECTED) + ->whereHas('mediaTaskAssignment.partnerMedia.company', fn ($q) => $q->where('user_id', auth()->id())) + ->where('reports.status', '!=', ApprovalStatus::REJECTED) ->count() < $taskAssignment->report_amount; }), ]) ->toolbarActions([]) ->emptyStateIcon(Heroicon::InboxArrowDown) - ->emptyStateDescription('Tidak ada laporan yang diajukan.'); + ->emptyStateDescription('Tidak ada laporan yang diajukan.') + ->defaultSort('created_at', 'desc') + ->modifyQueryUsing(function ($query) { + $user = auth()->user(); + + if (! $user->hasRole(RoleEnum::PERUSAHAAN->value)) { + return $query; + } + + $query->whereHas('mediaTaskAssignment.partnerMedia.company', function ($q) use ($user) { + $q->where('user_id', $user->id); + }); + }); } } diff --git a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php index 39bfb54..ba25bc0 100644 --- a/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php +++ b/app/Filament/Resources/Manage/Cooperations/Tables/CooperationsTable.php @@ -132,6 +132,13 @@ public static function configure(Table $table): Table ->placeholder('Belum dibayar') ->toggleable(isToggledHiddenByDefault: true), + TextColumn::make('completed_at') + ->label('Tanggal Selesai') + ->date('l, d F Y') + ->sortable() + ->placeholder('Masih berjalan') + ->toggleable(isToggledHiddenByDefault: true), + TextColumn::make('description') ->label('Deskripsi') ->searchable() diff --git a/app/Models/Cooperation.php b/app/Models/Cooperation.php index 1444479..0231284 100644 --- a/app/Models/Cooperation.php +++ b/app/Models/Cooperation.php @@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; @@ -28,6 +29,7 @@ protected function casts(): array 'final_submission_date' => 'date', 'payment_amount' => 'integer', 'payment_date' => 'date', + 'completed_at' => 'date', 'status' => CooperationStatus::class, ]; } @@ -62,30 +64,46 @@ protected function completed(Builder $query): void $query->where('status', CooperationStatus::COMPLETED); } - public function cooperationMedia(): HasOne + public function cooperationMedia(): HasMany { - return $this->hasOne(CooperationMedia::class); + return $this->hasMany(CooperationMedia::class); } public function partnerMedia(): BelongsToMany { return $this->belongsToMany(PartnerMedia::class, 'cooperation_media') + ->using(CooperationMedia::class) ->withPivot(['status']) ->withTimestamps(); } - public function proposal(): HasOne + public function proposal(): HasMany { - return $this->hasOne(CooperationProposal::class); + return $this->hasMany(CooperationProposal::class); } public function reports(): HasManyThrough { - return $this->hasManyThrough(Report::class, TaskAssignment::class); + return $this->hasManyThrough( + Report::class, + MediaTaskAssignment::class, + 'task_assignment_id', + 'media_task_assignment_id' + ); } public function taskAssignment(): HasOne { return $this->hasOne(TaskAssignment::class); } + + public function payments(): HasManyThrough + { + return $this->hasManyThrough( + CooperationPayment::class, + CooperationMedia::class, + 'cooperation_id', + 'cooperation_media_id' + ); + } } diff --git a/app/Models/MediaTaskAssignment.php b/app/Models/MediaTaskAssignment.php index e63db0f..a9aefb9 100644 --- a/app/Models/MediaTaskAssignment.php +++ b/app/Models/MediaTaskAssignment.php @@ -5,6 +5,7 @@ use App\Enums\ApprovalStatus; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\Pivot; use Swindon\FilamentHashids\Traits\HasHashid; @@ -38,4 +39,9 @@ public function rejectionReasons(): MorphMany { return $this->morphMany(RejectionReason::class, 'rejectable'); } + + public function reports(): HasMany + { + return $this->hasMany(Report::class); + } } diff --git a/app/Models/PartnerMedia.php b/app/Models/PartnerMedia.php index bb50622..0e07e91 100644 --- a/app/Models/PartnerMedia.php +++ b/app/Models/PartnerMedia.php @@ -85,6 +85,7 @@ public function cooperationProposals(): HasMany public function cooperations(): BelongsToMany { return $this->belongsToMany(Cooperation::class, 'cooperation_media') + ->using(CooperationMedia::class) ->withPivot(['status']) ->withTimestamps(); } @@ -102,6 +103,7 @@ public function mediaTaskAssignments(): HasMany public function taskAssignments(): BelongsToMany { return $this->belongsToMany(TaskAssignment::class, 'media_task_assignment') + ->using(MediaTaskAssignment::class) ->withPivot(['status']) ->withTimestamps(); } diff --git a/app/Models/Report.php b/app/Models/Report.php index a2350b7..a44d1b7 100644 --- a/app/Models/Report.php +++ b/app/Models/Report.php @@ -26,9 +26,9 @@ protected function casts(): array ]; } - public function taskAssignment(): BelongsTo + public function mediaTaskAssignment(): BelongsTo { - return $this->belongsTo(TaskAssignment::class); + return $this->belongsTo(MediaTaskAssignment::class); } public function rejectionReasons(): MorphMany diff --git a/app/Models/TaskAssignment.php b/app/Models/TaskAssignment.php index d6100ee..acfd977 100644 --- a/app/Models/TaskAssignment.php +++ b/app/Models/TaskAssignment.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\MorphMany; use Swindon\FilamentHashids\Traits\HasHashid; @@ -29,9 +30,16 @@ public function cooperation(): BelongsTo return $this->belongsTo(Cooperation::class); } - public function reports(): HasMany + public function reports(): HasManyThrough { - return $this->hasMany(Report::class); + return $this->hasManyThrough( + Report::class, + MediaTaskAssignment::class, + 'task_assignment_id', // Foreign key di tabel media_task_assignment + 'media_task_assignment_id', // Foreign key di tabel reports + 'id', // Local key di tabel task_assignments + 'id' // Local key di tabel media_task_assignment + ); } public function rejectionReasons(): MorphMany @@ -44,9 +52,10 @@ public function mediaTaskAssignments(): HasMany return $this->hasMany(MediaTaskAssignment::class); } - public function partnerMedias(): BelongsToMany + public function partnerMedia(): BelongsToMany { return $this->belongsToMany(PartnerMedia::class, 'media_task_assignment') + ->using(MediaTaskAssignment::class) ->withPivot(['status']) ->withTimestamps(); } diff --git a/app/Models/User.php b/app/Models/User.php index 587ba2d..2274019 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -38,9 +38,9 @@ protected function casts(): array public function canAccessPanel(Panel $panel): bool { - // if ($this->company?->verificationRequest?->status === VerificationStatus::REJECTED) { - // return false; - // } + if ($this->company?->verificationRequest?->status === VerificationStatus::REJECTED) { + return false; + } return $this->is_active === IsActive::ACTIVE; } diff --git a/database/migrations/2025_12_12_111212_create_cooperations_table.php b/database/migrations/2025_12_12_111212_create_cooperations_table.php index 7cb214a..5069550 100644 --- a/database/migrations/2025_12_12_111212_create_cooperations_table.php +++ b/database/migrations/2025_12_12_111212_create_cooperations_table.php @@ -20,7 +20,8 @@ public function up(): void $table->text('description'); $table->unsignedInteger('payment_amount')->nullable(); $table->date('payment_date')->nullable(); - $table->enum('status', [CooperationStatus::values()])->default(CooperationStatus::PENDING)->comment(CooperationStatus::comment()); + $table->date('completed_at')->nullable(); + $table->enum('status', CooperationStatus::values())->default(CooperationStatus::PENDING)->comment(CooperationStatus::comment()); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->softDeletes(); diff --git a/database/migrations/2025_12_12_111307_create_cooperation_media_table.php b/database/migrations/2025_12_12_111307_create_cooperation_media_table.php index 86ec253..d8f8cea 100644 --- a/database/migrations/2025_12_12_111307_create_cooperation_media_table.php +++ b/database/migrations/2025_12_12_111307_create_cooperation_media_table.php @@ -18,7 +18,7 @@ public function up(): void $table->id(); $table->foreignIdFor(Cooperation::class)->constrained()->cascadeOnDelete(); $table->foreignIdFor(PartnerMedia::class)->constrained()->cascadeOnDelete(); - $table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); + $table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); }); diff --git a/database/migrations/2025_12_12_132245_create_cooperation_proposals_table.php b/database/migrations/2025_12_12_132245_create_cooperation_proposals_table.php index ca3019d..443004c 100644 --- a/database/migrations/2025_12_12_132245_create_cooperation_proposals_table.php +++ b/database/migrations/2025_12_12_132245_create_cooperation_proposals_table.php @@ -19,8 +19,8 @@ public function up(): void $table->foreignIdFor(Cooperation::class)->constrained()->cascadeOnDelete(); $table->foreignIdFor(PartnerMedia::class)->constrained()->cascadeOnDelete(); $table->text('description')->nullable(); - $table->string('e_catalog', 50)->nullable(); - $table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); + $table->string('e_catalog', 200)->nullable(); + $table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); $table->timestamp('submitted_at')->useCurrent(); $table->timestamp('responded_at')->nullable(); $table->timestamp('created_at')->useCurrent(); diff --git a/database/migrations/2025_12_12_133823_create_media_task_assignment_table.php b/database/migrations/2025_12_12_133823_create_media_task_assignment_table.php index b067bc6..2de977b 100644 --- a/database/migrations/2025_12_12_133823_create_media_task_assignment_table.php +++ b/database/migrations/2025_12_12_133823_create_media_task_assignment_table.php @@ -18,7 +18,7 @@ public function up(): void $table->id(); $table->foreignIdFor(TaskAssignment::class)->constrained()->cascadeOnDelete(); $table->foreignIdFor(PartnerMedia::class)->constrained()->cascadeOnDelete(); - $table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); + $table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); }); diff --git a/database/migrations/2025_12_12_135847_create_reports_table.php b/database/migrations/2025_12_12_135847_create_reports_table.php index c527c73..dcb3952 100644 --- a/database/migrations/2025_12_12_135847_create_reports_table.php +++ b/database/migrations/2025_12_12_135847_create_reports_table.php @@ -1,7 +1,7 @@ id(); - $table->foreignIdFor(TaskAssignment::class)->constrained()->cascadeOnDelete(); + $table->foreignIdFor(MediaTaskAssignment::class)->constrained()->cascadeOnDelete(); $table->string('title', 200); $table->date('publication_date'); $table->string('link', 50); $table->text('description'); - $table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); + $table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment()); $table->timestamp('created_at')->useCurrent(); $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); $table->softDeletes();