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 @@
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"> @endif