refactor: replace Verification page with a new Index page and associated services for improved structure, maintainability, and functionality in the verification process
This commit is contained in:
parent
20dcd0e981
commit
d220f941c1
@ -1,432 +0,0 @@
|
||||
<?php
|
||||
|
||||
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\Filament\Support\CheerfulNotification;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Models\VerificationRequest;
|
||||
use App\Notifications\BroadcastNotification;
|
||||
use BackedEnum;
|
||||
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Illuminate\Support\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class Verification extends Page implements HasActions, HasForms
|
||||
{
|
||||
use HasPageShield, InteractsWithActions, InteractsWithForms;
|
||||
|
||||
public ?Company $company = null;
|
||||
|
||||
protected static string|UnitEnum|null $navigationGroup = 'Kelola';
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedCheckBadge;
|
||||
|
||||
protected static ?string $navigationLabel = 'Verifikasi';
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
protected static ?string $slug = 'manage/verification';
|
||||
|
||||
protected static ?string $title = 'Verifikasi';
|
||||
|
||||
public ?VerificationRequest $verificationRequest = null;
|
||||
|
||||
protected string $view = 'filament.pages.verification';
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$user = auth()->user();
|
||||
$this->company = $user->company;
|
||||
|
||||
// Load unified verification request
|
||||
if ($this->company) {
|
||||
$this->verificationRequest = $this->company->verificationRequest()
|
||||
->with(['reviews.reviewer'])
|
||||
->latest()
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unified verification progress
|
||||
*/
|
||||
public function getVerificationProgress(): array
|
||||
{
|
||||
$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->getMissingDataButtons();
|
||||
$progress['alert'] = $this->getAlertData();
|
||||
$progress['lastSubmittedAt'] = $this->verificationRequest?->updated_at?->translatedFormat('l, d M Y H:i');
|
||||
|
||||
return $progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit unified verification action
|
||||
*/
|
||||
public function submitVerificationAction(): Action
|
||||
{
|
||||
$progress = $this->getVerificationProgress();
|
||||
|
||||
return Action::make('submitVerification')
|
||||
->label(fn (): string => $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION
|
||||
? 'Kirim Ulang'
|
||||
: 'Ajukan Verifikasi')
|
||||
->icon('heroicon-o-paper-airplane')
|
||||
->color('primary')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(fn () => CheerfulNotification::getByKey('verification.submit.title'))
|
||||
->modalDescription(function () use ($progress): string {
|
||||
if (! $progress['isDataComplete']) {
|
||||
return CheerfulNotification::getByKey('verification.submit.description.incomplete', ['missing' => implode(', ', $progress['missingData'])]);
|
||||
}
|
||||
|
||||
if ($this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION) {
|
||||
return CheerfulNotification::getByKey('verification.submit.description.revision');
|
||||
}
|
||||
|
||||
return CheerfulNotification::getByKey('verification.submit.description.default');
|
||||
})
|
||||
->modalSubmitActionLabel('Ya, Ajukan')
|
||||
->visible(fn (): bool => $progress['canSubmit'])
|
||||
->disabled(fn (): bool => ! $progress['isDataComplete'])
|
||||
->action(function () use ($progress): void {
|
||||
if (! $progress['isDataComplete']) {
|
||||
CheerfulNotification::danger(
|
||||
CheerfulNotification::getByKey('verification.incomplete.title'),
|
||||
CheerfulNotification::getByKey('verification.incomplete.body', ['missing' => implode(', ', $progress['missingData'])])
|
||||
)->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$isRevision = $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION;
|
||||
|
||||
$this->verificationRequest = $this->submitVerification();
|
||||
|
||||
CheerfulNotification::success(
|
||||
CheerfulNotification::getByKey('verification.success.title'),
|
||||
CheerfulNotification::getByKey('verification.success.body')
|
||||
)->send();
|
||||
|
||||
User::superAdmin()
|
||||
->get()
|
||||
->each(function ($admin) use ($isRevision): void {
|
||||
$user = auth()->user();
|
||||
|
||||
$admin->notify(new BroadcastNotification([
|
||||
'title' => $isRevision
|
||||
? CheerfulNotification::getByKey('verification.admin_notify.title.revision')
|
||||
: CheerfulNotification::getByKey('verification.admin_notify.title.new'),
|
||||
'body' => $isRevision
|
||||
? CheerfulNotification::getByKey('verification.admin_notify.body.revision', ['name' => $user->name, 'company' => $user->company->name])
|
||||
: CheerfulNotification::getByKey('verification.admin_notify.body.new', ['name' => $user->name, 'company' => $user->company->name]),
|
||||
'action' => [
|
||||
Action::make('view')
|
||||
->label('Lihat')
|
||||
->url(PartnerResource::getUrl('view', ['record' => $user])),
|
||||
],
|
||||
]));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => CheerfulNotification::getByKey('verification.alert.approved.body'),
|
||||
'color' => 'success',
|
||||
];
|
||||
}
|
||||
|
||||
if ($status === VerificationStatus::PENDING) {
|
||||
return [
|
||||
'type' => 'pending',
|
||||
'icon' => 'clock',
|
||||
'title' => CheerfulNotification::getByKey('verification.alert.pending.title'),
|
||||
'message' => CheerfulNotification::getByKey('verification.alert.pending.body'),
|
||||
'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 [
|
||||
'verificationProgress' => $this->getVerificationProgress(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Verification\Actions;
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Filament\Support\CheerfulNotification;
|
||||
use Filament\Actions\Action;
|
||||
|
||||
class SubmitVerificationAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'submitVerification';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(function (): string {
|
||||
$progress = $this->getLivewire()->getVerificationProgress();
|
||||
$verification = $progress['verification'];
|
||||
|
||||
return $verification && $verification->status === VerificationStatus::NEED_REVISION
|
||||
? 'Kirim Ulang'
|
||||
: 'Ajukan Verifikasi';
|
||||
})
|
||||
->icon('heroicon-o-paper-airplane')
|
||||
->color('primary')
|
||||
->requiresConfirmation()
|
||||
->modalHeading(fn () => CheerfulNotification::getByKey('verification.submit.title'))
|
||||
->modalDescription(function (): string {
|
||||
$progress = $this->getLivewire()->getVerificationProgress();
|
||||
|
||||
if (! $progress['isDataComplete']) {
|
||||
return CheerfulNotification::getByKey(
|
||||
'verification.submit.description.incomplete',
|
||||
['missing' => implode(', ', $progress['missingData'])]
|
||||
);
|
||||
}
|
||||
|
||||
$verification = $progress['verification'];
|
||||
|
||||
if ($verification && $verification->status === VerificationStatus::NEED_REVISION) {
|
||||
return CheerfulNotification::getByKey('verification.submit.description.revision');
|
||||
}
|
||||
|
||||
return CheerfulNotification::getByKey('verification.submit.description.default');
|
||||
})
|
||||
->modalSubmitActionLabel('Ya, Ajukan')
|
||||
->visible(function (): bool {
|
||||
return $this->getLivewire()->getVerificationProgress()['canSubmit'];
|
||||
})
|
||||
->disabled(function (): bool {
|
||||
return ! $this->getLivewire()->getVerificationProgress()['isDataComplete'];
|
||||
})
|
||||
->action(function (array $data = []): void {
|
||||
$this->getLivewire()->handleVerificationSubmit($data);
|
||||
});
|
||||
}
|
||||
}
|
||||
216
app/Filament/Pages/Verification/Index.php
Normal file
216
app/Filament/Pages/Verification/Index.php
Normal file
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Verification;
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Filament\Pages\Verification\Actions\SubmitVerificationAction;
|
||||
use App\Filament\Pages\Verification\Services\VerificationAdminNotifier;
|
||||
use App\Filament\Pages\Verification\Services\VerificationProgressService;
|
||||
use App\Filament\Pages\Verification\Services\VerificationSubmissionService;
|
||||
use App\Filament\Support\CheerfulNotification;
|
||||
use App\Models\Company;
|
||||
use App\Models\VerificationRequest;
|
||||
use BackedEnum;
|
||||
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use UnitEnum;
|
||||
|
||||
class Index extends Page implements HasActions, HasForms
|
||||
{
|
||||
use HasPageShield, InteractsWithActions, InteractsWithForms;
|
||||
|
||||
public ?Company $company = null;
|
||||
|
||||
protected static string|UnitEnum|null $navigationGroup = 'Kelola';
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedCheckBadge;
|
||||
|
||||
protected static ?string $navigationLabel = 'Verifikasi';
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
protected static ?string $slug = 'manage/verification';
|
||||
|
||||
protected static ?string $title = 'Verifikasi';
|
||||
|
||||
public ?VerificationRequest $verificationRequest = null;
|
||||
|
||||
protected string $view = 'filament.pages.verification';
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()->can('View:Verification');
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
$this->company = $user->company;
|
||||
|
||||
// Load unified verification request
|
||||
if ($this->company) {
|
||||
$this->verificationRequest = $this->company->verificationRequest()
|
||||
->with(['reviews.reviewer'])
|
||||
->latest()
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unified verification progress
|
||||
*/
|
||||
public function getVerificationProgress(): array
|
||||
{
|
||||
return app(VerificationProgressService::class)->getProgress(
|
||||
$this->company,
|
||||
$this->verificationRequest,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit unified verification action
|
||||
*/
|
||||
public function submitVerificationAction(): Action
|
||||
{
|
||||
return SubmitVerificationAction::make();
|
||||
}
|
||||
|
||||
public function handleVerificationSubmit(array $data = []): void
|
||||
{
|
||||
$user = Auth::user();
|
||||
$progress = app(VerificationProgressService::class)->getProgress(
|
||||
$this->company,
|
||||
$this->verificationRequest,
|
||||
);
|
||||
|
||||
if (! $progress['isDataComplete']) {
|
||||
CheerfulNotification::danger(
|
||||
CheerfulNotification::getByKey('verification.incomplete.title'),
|
||||
CheerfulNotification::getByKey(
|
||||
'verification.incomplete.body',
|
||||
['missing' => implode(', ', $progress['missingData'])]
|
||||
)
|
||||
)->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$isRevision = $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION;
|
||||
|
||||
$this->verificationRequest = app(VerificationSubmissionService::class)->submit(
|
||||
$this->company,
|
||||
$this->verificationRequest,
|
||||
$user,
|
||||
);
|
||||
|
||||
CheerfulNotification::success(
|
||||
CheerfulNotification::getByKey('verification.success.title'),
|
||||
CheerfulNotification::getByKey('verification.success.body')
|
||||
)->send();
|
||||
|
||||
app(VerificationAdminNotifier::class)->notifySuperAdmins($user, $isRevision);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if all required data is complete for verification
|
||||
*/
|
||||
protected function isDataComplete(): bool
|
||||
{
|
||||
return app(VerificationProgressService::class)->isDataComplete($this->company);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get missing data items for verification
|
||||
*/
|
||||
protected function getMissingData(): array
|
||||
{
|
||||
return app(VerificationProgressService::class)->getMissingData($this->company);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can submit verification
|
||||
*/
|
||||
protected function canSubmit(): bool
|
||||
{
|
||||
return app(VerificationProgressService::class)->canSubmit($this->company, $this->verificationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the current step based on status
|
||||
*/
|
||||
protected function resolveStep(): int
|
||||
{
|
||||
return app(VerificationProgressService::class)->resolveStep($this->company, $this->verificationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest review notes
|
||||
*/
|
||||
protected function getLatestReviewNotes(): ?string
|
||||
{
|
||||
return app(VerificationProgressService::class)->getLatestReviewNotes($this->verificationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reviews history
|
||||
*/
|
||||
protected function getReviewHistory(): Collection
|
||||
{
|
||||
return app(VerificationProgressService::class)->getReviewHistory($this->verificationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit unified verification
|
||||
*/
|
||||
protected function submitVerification(): VerificationRequest
|
||||
{
|
||||
return app(VerificationSubmissionService::class)->submit(
|
||||
$this->company,
|
||||
$this->verificationRequest,
|
||||
Auth::user(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get alert data for verification status
|
||||
*/
|
||||
protected function getAlertData(): ?array
|
||||
{
|
||||
return app(VerificationProgressService::class)->getAlertData($this->verificationRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get missing data buttons configuration
|
||||
*/
|
||||
protected function getMissingDataButtons(): array
|
||||
{
|
||||
return app(VerificationProgressService::class)->getMissingDataButtons($this->company);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get steps array for stepper
|
||||
*/
|
||||
protected function getSteps(bool $isComplete): array
|
||||
{
|
||||
return app(VerificationProgressService::class)->getSteps(
|
||||
$this->company,
|
||||
$this->verificationRequest,
|
||||
$isComplete
|
||||
);
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
{
|
||||
return [
|
||||
'verificationProgress' => $this->getVerificationProgress(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Verification\Services;
|
||||
|
||||
use App\Filament\Resources\Manage\Partners\PartnerResource;
|
||||
use App\Filament\Support\CheerfulNotification;
|
||||
use App\Models\User;
|
||||
use App\Notifications\BroadcastNotification;
|
||||
use Filament\Actions\Action;
|
||||
|
||||
class VerificationAdminNotifier
|
||||
{
|
||||
public function notifySuperAdmins(User $actor, bool $isRevision): void
|
||||
{
|
||||
$companyName = $actor->company?->name;
|
||||
|
||||
User::superAdmin()->get()->each(function (User $admin) use ($actor, $companyName, $isRevision): void {
|
||||
$admin->notify(new BroadcastNotification([
|
||||
'title' => $isRevision
|
||||
? CheerfulNotification::getByKey('verification.admin_notify.title.revision')
|
||||
: CheerfulNotification::getByKey('verification.admin_notify.title.new'),
|
||||
'body' => $isRevision
|
||||
? CheerfulNotification::getByKey('verification.admin_notify.body.revision', ['name' => $actor->name, 'company' => $companyName])
|
||||
: CheerfulNotification::getByKey('verification.admin_notify.body.new', ['name' => $actor->name, 'company' => $companyName]),
|
||||
'action' => [
|
||||
Action::make('view')
|
||||
->label('Lihat')
|
||||
->url(PartnerResource::getUrl('view', ['record' => $actor])),
|
||||
],
|
||||
]));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Verification\Services;
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Filament\Pages\Company\Index as CompanyPage;
|
||||
use App\Filament\Pages\Media\Index as MediaPage;
|
||||
use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists;
|
||||
use App\Filament\Support\CheerfulNotification;
|
||||
use App\Models\Company;
|
||||
use App\Models\VerificationRequest;
|
||||
use Illuminate\Support\Collection as SupportCollection;
|
||||
|
||||
class VerificationProgressService
|
||||
{
|
||||
public function getProgress(?Company $company, ?VerificationRequest $verificationRequest): array
|
||||
{
|
||||
$isComplete = $this->isDataComplete($company);
|
||||
$missingData = $this->getMissingData($company);
|
||||
$status = $verificationRequest?->status;
|
||||
|
||||
$progress = [
|
||||
'currentStep' => $this->resolveStep($company, $verificationRequest),
|
||||
'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($company, $verificationRequest, $isComplete),
|
||||
];
|
||||
|
||||
$progress['missingDataButtons'] = $this->getMissingDataButtons($company);
|
||||
$progress['alert'] = $this->getAlertData($verificationRequest);
|
||||
$progress['lastSubmittedAt'] = $verificationRequest?->updated_at?->translatedFormat('l, d M Y H:i');
|
||||
|
||||
return $progress;
|
||||
}
|
||||
|
||||
public function isDataComplete(?Company $company): bool
|
||||
{
|
||||
return $company !== null
|
||||
&& $company->partnerMedia !== null
|
||||
&& $company->partnerMedia?->journalists !== null
|
||||
&& $company->partnerMedia->journalists->count() > 0;
|
||||
}
|
||||
|
||||
public function getMissingData(?Company $company): array
|
||||
{
|
||||
$missing = [];
|
||||
|
||||
if (! $company) {
|
||||
$missing[] = 'Data Perusahaan';
|
||||
|
||||
return $missing;
|
||||
}
|
||||
|
||||
if (! $company->partnerMedia) {
|
||||
$missing[] = 'Data Media';
|
||||
}
|
||||
|
||||
if ($company->partnerMedia && ($company->partnerMedia?->journalists === null || $company->partnerMedia?->journalists?->count() === 0)) {
|
||||
$missing[] = 'Data Jurnalis (minimal 1)';
|
||||
}
|
||||
|
||||
return $missing;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public function getReviewHistory(?VerificationRequest $verificationRequest): SupportCollection
|
||||
{
|
||||
if (! $verificationRequest) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $verificationRequest->reviews()
|
||||
->with('reviewer')
|
||||
->latest()
|
||||
->get();
|
||||
}
|
||||
|
||||
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' => CheerfulNotification::getByKey('verification.alert.approved.body'),
|
||||
'color' => 'success',
|
||||
];
|
||||
}
|
||||
|
||||
if ($status === VerificationStatus::PENDING) {
|
||||
return [
|
||||
'type' => 'pending',
|
||||
'icon' => 'clock',
|
||||
'title' => CheerfulNotification::getByKey('verification.alert.pending.title'),
|
||||
'message' => CheerfulNotification::getByKey('verification.alert.pending.body'),
|
||||
'color' => 'info',
|
||||
'animate' => true,
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getMissingDataButtons(?Company $company): array
|
||||
{
|
||||
$buttons = [];
|
||||
|
||||
if (! $company) {
|
||||
$buttons[] = [
|
||||
'label' => 'Lengkapi Data Perusahaan',
|
||||
'url' => CompanyPage::getUrl(),
|
||||
'icon' => 'building-office-2',
|
||||
'color' => 'warning',
|
||||
];
|
||||
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
if (! $company->partnerMedia) {
|
||||
$buttons[] = [
|
||||
'label' => 'Lengkapi Data Media',
|
||||
'url' => MediaPage::getUrl(),
|
||||
'icon' => 'newspaper',
|
||||
'color' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
if ($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;
|
||||
}
|
||||
|
||||
public function getSteps(?Company $company, ?VerificationRequest $verificationRequest, bool $isComplete): 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',
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages\Verification\Services;
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use App\Models\VerificationRequest;
|
||||
|
||||
class VerificationSubmissionService
|
||||
{
|
||||
public function submit(Company $company, ?VerificationRequest $verificationRequest, User $actor): VerificationRequest
|
||||
{
|
||||
if ($verificationRequest && $verificationRequest->status === VerificationStatus::NEED_REVISION) {
|
||||
// Resubmit - update existing request
|
||||
$verificationRequest->update([
|
||||
'status' => VerificationStatus::PENDING,
|
||||
]);
|
||||
|
||||
return $verificationRequest->fresh(['reviews.reviewer']);
|
||||
}
|
||||
|
||||
// New submission
|
||||
return VerificationRequest::create([
|
||||
'company_id' => $company->id,
|
||||
'submitted_by' => $actor->id,
|
||||
'status' => VerificationStatus::PENDING,
|
||||
])->load(['reviews.reviewer']);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user