Refactor verification process and UI updates
- Updated JournalistResource to conditionally display actions based on user roles and verification status. - Modified ManageJournalists page to hide header actions if verification request status is not NEED_REVISION. - Changed CategoryResource navigation label from 'Kategori Berita' to 'Kategori'. - Adjusted ManageCategories page to reflect the new category label and modal headings. - Refactored Company model to change verificationRequest relationship from MorphOne to HasOne. - Removed verificationRequest method from Journalist and PartnerMedia models. - Updated VerificationRequest model to change verifiable relationship from MorphTo to BelongsTo. - Introduced VerificationService to handle verification logic, including data completeness checks and submission processes. - Created a new migration to adjust the verification_requests table structure. - Enhanced admin verification view to display detailed verification progress and review history. - Updated company and media forms to conditionally show the save button based on verification request status.
This commit is contained in:
parent
bd122f77b9
commit
69c86e5d3c
@ -4,10 +4,9 @@
|
||||
|
||||
use App\Enums\DecisionAdmin;
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists;
|
||||
use App\Filament\Resources\Manage\Partners\Pages\ViewPartner;
|
||||
use App\Models\VerificationRequest;
|
||||
use App\Models\VerificationReview;
|
||||
use App\Services\VerificationService;
|
||||
use BackedEnum;
|
||||
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
|
||||
use Filament\Actions\Action;
|
||||
@ -19,7 +18,6 @@
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use UnitEnum;
|
||||
|
||||
class AdminVerification extends Page implements HasActions, HasForms
|
||||
@ -40,6 +38,13 @@ class AdminVerification extends Page implements HasActions, HasForms
|
||||
|
||||
public string $filterStatus = 'all';
|
||||
|
||||
protected VerificationService $verificationService;
|
||||
|
||||
public function boot(): void
|
||||
{
|
||||
$this->verificationService = app(VerificationService::class);
|
||||
}
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
{
|
||||
return VerificationRequest::query()->pending()->count();
|
||||
@ -48,7 +53,9 @@ public static function getNavigationBadge(): ?string
|
||||
public function getVerifications(): LengthAwarePaginator
|
||||
{
|
||||
$query = VerificationRequest::with([
|
||||
'verifiable',
|
||||
'company.user',
|
||||
'company.partnerMedia',
|
||||
'company.partnerMedia.journalists',
|
||||
'submittedBy',
|
||||
'reviews.reviewer',
|
||||
])
|
||||
@ -79,52 +86,64 @@ public function getStatusCounts(): array
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity type label
|
||||
* Get company name from verification request
|
||||
*/
|
||||
public function getEntityTypeLabel(VerificationRequest $request): string
|
||||
public function getCompanyName(VerificationRequest $request): string
|
||||
{
|
||||
$type = class_basename($request->verifiable_type);
|
||||
|
||||
return match ($type) {
|
||||
'Company' => 'Perusahaan',
|
||||
'PartnerMedia' => 'Media',
|
||||
'Journalist' => 'Jurnalis',
|
||||
default => $type,
|
||||
};
|
||||
return $this->verificationService->getCompanyName($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity name
|
||||
*/
|
||||
public function getEntityName(VerificationRequest $request): string
|
||||
{
|
||||
$entity = $request->verifiable;
|
||||
|
||||
if (! $entity) {
|
||||
return 'Data tidak ditemukan';
|
||||
}
|
||||
|
||||
return $entity->name ?? 'Tanpa Nama';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detail URL based on entity type
|
||||
* Get detail URL for the company
|
||||
*/
|
||||
public function getDetailUrl(VerificationRequest $request): ?string
|
||||
{
|
||||
$entity = $request->verifiable;
|
||||
$type = class_basename($request->verifiable_type);
|
||||
|
||||
if (! $entity) {
|
||||
if (! $request->company || ! $request->company->user_id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
'Company' => ViewPartner::getUrl(['record' => $entity->user_id]),
|
||||
'PartnerMedia' => ViewPartner::getUrl(['record' => $entity->company?->user_id]),
|
||||
'Journalist' => ManageJournalists::getUrl(),
|
||||
default => null,
|
||||
};
|
||||
return ViewPartner::getUrl(['record' => $request->company->user_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get verification summary data
|
||||
*/
|
||||
public function getVerificationSummary(VerificationRequest $request): array
|
||||
{
|
||||
return $this->verificationService->getVerificationSummary($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get card data for verification request
|
||||
*/
|
||||
public function getCardData(VerificationRequest $verification): array
|
||||
{
|
||||
$latestReview = $verification->reviews->first();
|
||||
$summary = $this->getVerificationSummary($verification);
|
||||
$status = $verification->status;
|
||||
|
||||
return [
|
||||
'companyName' => $this->getCompanyName($verification),
|
||||
'detailUrl' => $this->getDetailUrl($verification),
|
||||
'status' => $status,
|
||||
'statusLabel' => $status->getLabel(),
|
||||
'statusColor' => $status->getColor(),
|
||||
'summary' => $summary,
|
||||
'latestReview' => $latestReview,
|
||||
'latestReviewNote' => $latestReview?->note,
|
||||
'latestReviewDecision' => $latestReview?->decision,
|
||||
'latestReviewDecisionLabel' => $latestReview?->decision?->getLabel(),
|
||||
'latestReviewDecisionColor' => $latestReview?->decision?->getColor(),
|
||||
'latestReviewReviewer' => $latestReview?->reviewer?->name ?? 'Admin',
|
||||
'latestReviewCreatedAt' => $latestReview?->created_at,
|
||||
'submittedBy' => $verification->submittedBy?->name ?? 'Unknown',
|
||||
'reviewsCount' => $verification->reviews->count(),
|
||||
'canApprove' => $status === VerificationStatus::PENDING,
|
||||
'isPending' => $status === VerificationStatus::PENDING,
|
||||
'isRevision' => $status === VerificationStatus::NEED_REVISION,
|
||||
'isApproved' => $status === VerificationStatus::APPROVED,
|
||||
'isRejected' => $status === VerificationStatus::REJECTED,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,14 +157,9 @@ public function approveAction(): Action
|
||||
->icon('heroicon-o-check')
|
||||
->size('sm')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Setujui Verifikasi')
|
||||
->modalDescription('Apakah Anda yakin ingin menyetujui verifikasi ini?')
|
||||
->modalHeading('Setujui Verifikasi Lengkap')
|
||||
->modalDescription('Apakah Anda yakin ingin menyetujui verifikasi ini? Status akan diterapkan pada Perusahaan, Media, dan semua Jurnalis sekaligus.')
|
||||
->modalSubmitActionLabel('Ya, Setujui')
|
||||
->schema([
|
||||
Textarea::make('note')
|
||||
->label('Catatan (Opsional)')
|
||||
->placeholder('Tambahkan catatan jika diperlukan...'),
|
||||
])
|
||||
->action(function (array $arguments, array $data) {
|
||||
$this->processReview($arguments['id'], DecisionAdmin::APPROVED, $data['note'] ?? null);
|
||||
});
|
||||
@ -163,11 +177,11 @@ public function revisionAction(): Action
|
||||
->size('sm')
|
||||
->modalHeading('Minta Revisi')
|
||||
->modalDescription('User akan diminta untuk memperbaiki data sesuai catatan yang diberikan.')
|
||||
->form([
|
||||
->schema([
|
||||
Textarea::make('note')
|
||||
->label('Catatan Revisi')
|
||||
->required()
|
||||
->placeholder('Jelaskan bagian yang perlu diperbaiki...')
|
||||
->placeholder('Jelaskan bagian yang perlu diperbaiki (Perusahaan, Media, atau Jurnalis)...')
|
||||
->helperText('Catatan ini akan ditampilkan kepada user.'),
|
||||
])
|
||||
->action(function (array $arguments, array $data) {
|
||||
@ -186,10 +200,10 @@ public function rejectAction(): Action
|
||||
->icon('heroicon-o-x-mark')
|
||||
->size('sm')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Tolak Verifikasi')
|
||||
->modalDescription('Pengajuan akan ditolak secara permanen. User tidak dapat mengajukan ulang untuk data ini.')
|
||||
->modalHeading('Tolak Verifikasi Lengkap')
|
||||
->modalDescription('Pengajuan akan ditolak secara permanen. Status REJECTED akan diterapkan pada Perusahaan, Media, dan semua Jurnalis. User tidak dapat mengajukan ulang untuk data ini.')
|
||||
->modalSubmitActionLabel('Ya, Tolak')
|
||||
->form([
|
||||
->schema([
|
||||
Textarea::make('note')
|
||||
->label('Alasan Penolakan')
|
||||
->required()
|
||||
@ -206,7 +220,7 @@ public function rejectAction(): Action
|
||||
*/
|
||||
protected function processReview(int $requestId, DecisionAdmin $decision, ?string $note): void
|
||||
{
|
||||
$verificationRequest = VerificationRequest::find($requestId);
|
||||
$verificationRequest = VerificationRequest::with(['company.partnerMedia.journalists'])->find($requestId);
|
||||
|
||||
if (! $verificationRequest) {
|
||||
Notification::make()
|
||||
@ -218,47 +232,32 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin
|
||||
return;
|
||||
}
|
||||
|
||||
// Create review record
|
||||
VerificationReview::create([
|
||||
'verification_request_id' => $verificationRequest->id,
|
||||
'reviewer_id' => Auth::id(),
|
||||
'decision' => $decision,
|
||||
'note' => $note,
|
||||
]);
|
||||
try {
|
||||
$this->verificationService->processReview($verificationRequest, $decision, $note);
|
||||
|
||||
// Update verification request status
|
||||
$newStatus = match ($decision) {
|
||||
DecisionAdmin::APPROVED => VerificationStatus::APPROVED,
|
||||
DecisionAdmin::NEED_REVISION => VerificationStatus::NEED_REVISION,
|
||||
DecisionAdmin::REJECTED => VerificationStatus::REJECTED,
|
||||
};
|
||||
$actionLabel = match ($decision) {
|
||||
DecisionAdmin::APPROVED => 'disetujui',
|
||||
DecisionAdmin::NEED_REVISION => 'diminta revisi',
|
||||
DecisionAdmin::REJECTED => 'ditolak',
|
||||
};
|
||||
|
||||
$verificationRequest->update([
|
||||
'status' => $newStatus,
|
||||
]);
|
||||
$color = match ($decision) {
|
||||
DecisionAdmin::APPROVED => 'success',
|
||||
DecisionAdmin::NEED_REVISION => 'info',
|
||||
DecisionAdmin::REJECTED => 'danger',
|
||||
};
|
||||
|
||||
// Update the related entity status if needed
|
||||
$entity = $verificationRequest->verifiable;
|
||||
if ($entity && method_exists($entity, 'update')) {
|
||||
$entity->update(['status' => $newStatus]);
|
||||
Notification::make()
|
||||
->title('Berhasil')
|
||||
->body("Verifikasi lengkap telah {$actionLabel}. Status telah diterapkan pada Perusahaan, Media, dan semua Jurnalis.")
|
||||
->color($color)
|
||||
->send();
|
||||
} catch (\Exception $e) {
|
||||
Notification::make()
|
||||
->title('Error')
|
||||
->body('Terjadi kesalahan saat memproses verifikasi: '.$e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
|
||||
$actionLabel = match ($decision) {
|
||||
DecisionAdmin::APPROVED => 'disetujui',
|
||||
DecisionAdmin::NEED_REVISION => 'diminta revisi',
|
||||
DecisionAdmin::REJECTED => 'ditolak',
|
||||
};
|
||||
|
||||
$color = match ($decision) {
|
||||
DecisionAdmin::APPROVED => 'success',
|
||||
DecisionAdmin::NEED_REVISION => 'info',
|
||||
DecisionAdmin::REJECTED => 'danger',
|
||||
};
|
||||
|
||||
Notification::make()
|
||||
->title('Berhasil')
|
||||
->body("Verifikasi telah {$actionLabel}.")
|
||||
->color($color)
|
||||
->send();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Models\Company;
|
||||
use App\Models\VerificationRequest;
|
||||
use Asmit\FilamentUpload\Forms\Components\AdvancedFileUpload;
|
||||
use BackedEnum;
|
||||
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
|
||||
@ -37,6 +38,8 @@ class CustomCompany extends Page
|
||||
|
||||
public ?Company $company = null;
|
||||
|
||||
public ?VerificationRequest $verificationRequest = null;
|
||||
|
||||
public array $data = [];
|
||||
|
||||
public function mount(): void
|
||||
@ -47,6 +50,10 @@ public function mount(): void
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verificationRequest = $company->verificationRequest()
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
$this->company = $company;
|
||||
|
||||
$this->form->fill($company->toArray());
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
use App\Enums\MediaType;
|
||||
use App\Models\Company;
|
||||
use App\Models\PartnerMedia;
|
||||
use App\Models\VerificationRequest;
|
||||
use Asmit\FilamentUpload\Forms\Components\AdvancedFileUpload;
|
||||
use BackedEnum;
|
||||
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
|
||||
@ -45,6 +46,8 @@ class CustomMedia extends Page
|
||||
|
||||
public ?PartnerMedia $media = null;
|
||||
|
||||
public ?VerificationRequest $verificationRequest = null;
|
||||
|
||||
public bool $restricted = false;
|
||||
|
||||
public array $data = [];
|
||||
@ -65,6 +68,10 @@ public function mount(): void
|
||||
return;
|
||||
}
|
||||
|
||||
$this->verificationRequest = $this->company->verificationRequest()
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
$this->media = $media;
|
||||
|
||||
$this->form->fill($media->toArray());
|
||||
|
||||
@ -2,13 +2,10 @@
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Enums\DecisionAdmin;
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists;
|
||||
use App\Models\Company;
|
||||
use App\Models\Journalist;
|
||||
use App\Models\PartnerMedia;
|
||||
use App\Models\VerificationRequest;
|
||||
use App\Services\VerificationService;
|
||||
use BackedEnum;
|
||||
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
|
||||
use Filament\Actions\Action;
|
||||
@ -18,7 +15,6 @@
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Support\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class CustomVerification extends Page implements HasActions, HasForms
|
||||
@ -39,39 +35,22 @@ class CustomVerification extends Page implements HasActions, HasForms
|
||||
|
||||
public ?Company $company = null;
|
||||
|
||||
public ?PartnerMedia $partnerMedia = null;
|
||||
public ?VerificationRequest $verificationRequest = null;
|
||||
|
||||
public ?Journalist $journalist = null;
|
||||
protected VerificationService $verificationService;
|
||||
|
||||
public ?VerificationRequest $companyVerification = null;
|
||||
|
||||
public ?VerificationRequest $mediaVerification = null;
|
||||
|
||||
public ?VerificationRequest $journalistVerification = null;
|
||||
public function boot(): void
|
||||
{
|
||||
$this->verificationService = app(VerificationService::class);
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->company = auth()->user()->company;
|
||||
$this->partnerMedia = auth()->user()->company?->partnerMedia;
|
||||
$this->journalist = auth()->user()->company?->partnerMedia?->journalists?->first();
|
||||
|
||||
// Load verification requests with reviews
|
||||
// Load unified verification request
|
||||
if ($this->company) {
|
||||
$this->companyVerification = $this->company->verificationRequest()
|
||||
->with(['reviews.reviewer'])
|
||||
->latest()
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($this->partnerMedia) {
|
||||
$this->mediaVerification = $this->partnerMedia->verificationRequest()
|
||||
->with(['reviews.reviewer'])
|
||||
->latest()
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($this->journalist) {
|
||||
$this->journalistVerification = $this->journalist->verificationRequest()
|
||||
$this->verificationRequest = $this->company->verificationRequest()
|
||||
->with(['reviews.reviewer'])
|
||||
->latest()
|
||||
->first();
|
||||
@ -79,345 +58,79 @@ public function mount(): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the current step based on status
|
||||
* Get unified verification progress
|
||||
*/
|
||||
protected function resolveStep(?VerificationStatus $status, bool $hasData = false): int
|
||||
public function getVerificationProgress(): array
|
||||
{
|
||||
if (! $hasData) {
|
||||
return 1; // No data yet
|
||||
}
|
||||
$progress = $this->verificationService->getVerificationProgress(
|
||||
$this->company,
|
||||
$this->verificationRequest
|
||||
);
|
||||
|
||||
return match ($status) {
|
||||
VerificationStatus::PENDING,
|
||||
VerificationStatus::NEED_REVISION => 2, // In review or needs revision
|
||||
// Add additional data for view
|
||||
$progress['missingDataButtons'] = $this->verificationService->getMissingDataButtons($this->company);
|
||||
$progress['alert'] = $this->verificationService->getAlertData($this->verificationRequest);
|
||||
$progress['lastSubmittedAt'] = $this->verificationRequest?->updated_at?->format('d M Y, H:i');
|
||||
|
||||
VerificationStatus::APPROVED,
|
||||
VerificationStatus::REJECTED => 3, // Final decision
|
||||
|
||||
default => 1, // Draft / not submitted yet
|
||||
};
|
||||
return $progress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can submit verification for entity
|
||||
* Submit unified verification action
|
||||
*/
|
||||
protected function canSubmit($entity, ?VerificationRequest $verification): bool
|
||||
public function submitVerificationAction(): Action
|
||||
{
|
||||
if (! $entity) {
|
||||
return false;
|
||||
}
|
||||
$progress = $this->getVerificationProgress();
|
||||
|
||||
// No verification yet - can submit
|
||||
if (! $verification) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Can resubmit only if status is NEED_REVISION
|
||||
return $verification->status === VerificationStatus::NEED_REVISION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entity is approved
|
||||
*/
|
||||
protected function isApproved(?VerificationRequest $verification): bool
|
||||
{
|
||||
return $verification?->status === VerificationStatus::APPROVED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entity is rejected
|
||||
*/
|
||||
protected function isRejected(?VerificationRequest $verification): bool
|
||||
{
|
||||
return $verification?->status === VerificationStatus::REJECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entity is pending review
|
||||
*/
|
||||
protected function isPending(?VerificationRequest $verification): bool
|
||||
{
|
||||
return $verification?->status === VerificationStatus::PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if entity needs revision
|
||||
*/
|
||||
protected function needsRevision(?VerificationRequest $verification): bool
|
||||
{
|
||||
return $verification?->status === VerificationStatus::NEED_REVISION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest review notes for an entity
|
||||
*/
|
||||
protected function getLatestReviewNotes(?VerificationRequest $verification): ?string
|
||||
{
|
||||
if (! $verification) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$latestReview = $verification->reviews()
|
||||
->whereIn('decision', [DecisionAdmin::NEED_REVISION, DecisionAdmin::REJECTED])
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
return $latestReview?->note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all reviews history for an entity
|
||||
*/
|
||||
protected function getReviewHistory(?VerificationRequest $verification): Collection
|
||||
{
|
||||
if (! $verification) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $verification->reviews()
|
||||
->with('reviewer')
|
||||
->latest()
|
||||
->get();
|
||||
}
|
||||
|
||||
public function companyProgress(): array
|
||||
{
|
||||
$hasData = $this->company !== null;
|
||||
$currentStep = $this->resolveStep($this->companyVerification?->status, $hasData);
|
||||
|
||||
return [
|
||||
'title' => 'Perusahaan',
|
||||
'description' => 'Progress verifikasi perusahaan',
|
||||
'icon' => 'heroicon-o-building-office-2',
|
||||
'currentStep' => $currentStep,
|
||||
'entity' => $this->company,
|
||||
'verification' => $this->companyVerification,
|
||||
'canSubmit' => $this->canSubmit($this->company, $this->companyVerification),
|
||||
'isApproved' => $this->isApproved($this->companyVerification),
|
||||
'isRejected' => $this->isRejected($this->companyVerification),
|
||||
'isPending' => $this->isPending($this->companyVerification),
|
||||
'needsRevision' => $this->needsRevision($this->companyVerification),
|
||||
'latestNotes' => $this->getLatestReviewNotes($this->companyVerification),
|
||||
'reviewHistory' => $this->getReviewHistory($this->companyVerification),
|
||||
'editUrl' => CustomCompany::getUrl(),
|
||||
'steps' => [
|
||||
[
|
||||
'title' => 'Pengajuan',
|
||||
'description' => $hasData ? 'Data perusahaan sudah diisi' : 'Lengkapi data perusahaan',
|
||||
],
|
||||
[
|
||||
'title' => 'Verifikasi',
|
||||
'description' => match ($this->companyVerification?->status) {
|
||||
VerificationStatus::NEED_REVISION => 'Perlu perbaikan data',
|
||||
VerificationStatus::PENDING => 'Sedang diverifikasi admin',
|
||||
default => 'Menunggu pengajuan',
|
||||
},
|
||||
],
|
||||
[
|
||||
'title' => 'Selesai',
|
||||
'description' => match ($this->companyVerification?->status) {
|
||||
VerificationStatus::APPROVED => 'Perusahaan disetujui',
|
||||
VerificationStatus::REJECTED => 'Perusahaan ditolak',
|
||||
default => 'Menunggu keputusan',
|
||||
},
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function mediaProgress(): array
|
||||
{
|
||||
$hasData = $this->partnerMedia !== null;
|
||||
$currentStep = $this->resolveStep($this->mediaVerification?->status, $hasData);
|
||||
|
||||
return [
|
||||
'title' => 'Media',
|
||||
'description' => 'Progress verifikasi media',
|
||||
'currentStep' => $currentStep,
|
||||
'entity' => $this->partnerMedia,
|
||||
'verification' => $this->mediaVerification,
|
||||
'canSubmit' => $this->canSubmit($this->partnerMedia, $this->mediaVerification),
|
||||
'isApproved' => $this->isApproved($this->mediaVerification),
|
||||
'isRejected' => $this->isRejected($this->mediaVerification),
|
||||
'isPending' => $this->isPending($this->mediaVerification),
|
||||
'needsRevision' => $this->needsRevision($this->mediaVerification),
|
||||
'latestNotes' => $this->getLatestReviewNotes($this->mediaVerification),
|
||||
'reviewHistory' => $this->getReviewHistory($this->mediaVerification),
|
||||
'editUrl' => CustomMedia::getUrl(),
|
||||
'steps' => [
|
||||
[
|
||||
'title' => 'Pengajuan',
|
||||
'description' => $hasData ? 'Data media sudah diisi' : 'Lengkapi data media',
|
||||
],
|
||||
[
|
||||
'title' => 'Verifikasi',
|
||||
'description' => match ($this->mediaVerification?->status) {
|
||||
VerificationStatus::NEED_REVISION => 'Perlu perbaikan data',
|
||||
VerificationStatus::PENDING => 'Sedang diverifikasi admin',
|
||||
default => 'Menunggu pengajuan',
|
||||
},
|
||||
],
|
||||
[
|
||||
'title' => 'Selesai',
|
||||
'description' => match ($this->mediaVerification?->status) {
|
||||
VerificationStatus::APPROVED => 'Media disetujui',
|
||||
VerificationStatus::REJECTED => 'Media ditolak',
|
||||
default => 'Menunggu keputusan',
|
||||
},
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function journalistProgress(): array
|
||||
{
|
||||
$hasData = $this->journalist !== null;
|
||||
$currentStep = $this->resolveStep($this->journalistVerification?->status, $hasData);
|
||||
|
||||
return [
|
||||
'title' => 'Jurnalis',
|
||||
'description' => 'Progress verifikasi jurnalis',
|
||||
'currentStep' => $currentStep,
|
||||
'entity' => $this->journalist,
|
||||
'verification' => $this->journalistVerification,
|
||||
'canSubmit' => $this->canSubmit($this->journalist, $this->journalistVerification),
|
||||
'isApproved' => $this->isApproved($this->journalistVerification),
|
||||
'isRejected' => $this->isRejected($this->journalistVerification),
|
||||
'isPending' => $this->isPending($this->journalistVerification),
|
||||
'needsRevision' => $this->needsRevision($this->journalistVerification),
|
||||
'latestNotes' => $this->getLatestReviewNotes($this->journalistVerification),
|
||||
'reviewHistory' => $this->getReviewHistory($this->journalistVerification),
|
||||
'editUrl' => ManageJournalists::getUrl(),
|
||||
'steps' => [
|
||||
[
|
||||
'title' => 'Pengajuan',
|
||||
'description' => $hasData ? 'Data jurnalis sudah diisi' : 'Lengkapi data jurnalis',
|
||||
],
|
||||
[
|
||||
'title' => 'Verifikasi',
|
||||
'description' => match ($this->journalistVerification?->status) {
|
||||
VerificationStatus::NEED_REVISION => 'Perlu perbaikan data',
|
||||
VerificationStatus::PENDING => 'Sedang diverifikasi admin',
|
||||
default => 'Menunggu pengajuan',
|
||||
},
|
||||
],
|
||||
[
|
||||
'title' => 'Selesai',
|
||||
'description' => match ($this->journalistVerification?->status) {
|
||||
VerificationStatus::APPROVED => 'Jurnalis disetujui',
|
||||
VerificationStatus::REJECTED => 'Jurnalis ditolak',
|
||||
default => 'Menunggu keputusan',
|
||||
},
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit company verification action
|
||||
*/
|
||||
public function submitCompanyAction(): Action
|
||||
{
|
||||
return Action::make('submitCompany')
|
||||
->label(fn () => $this->needsRevision($this->companyVerification) ? 'Kirim Ulang' : 'Ajukan Verifikasi')
|
||||
return Action::make('submitVerification')
|
||||
->label(fn () => $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION
|
||||
? 'Kirim Ulang'
|
||||
: 'Ajukan Verifikasi')
|
||||
->icon('heroicon-o-paper-airplane')
|
||||
->color('primary')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Ajukan Verifikasi Perusahaan')
|
||||
->modalDescription('Apakah Anda yakin ingin mengajukan data perusahaan untuk diverifikasi? Pastikan semua data sudah benar.')
|
||||
->modalHeading('Ajukan Verifikasi Lengkap')
|
||||
->modalDescription(function () use ($progress) {
|
||||
if (! $progress['isDataComplete']) {
|
||||
return 'Data belum lengkap. Silakan lengkapi terlebih dahulu: '.implode(', ', $progress['missingData']);
|
||||
}
|
||||
|
||||
return 'Apakah Anda yakin ingin mengajukan verifikasi untuk Perusahaan, Media, dan Jurnalis? Pastikan semua data sudah benar dan lengkap.';
|
||||
})
|
||||
->modalSubmitActionLabel('Ya, Ajukan')
|
||||
->visible(fn () => $this->canSubmit($this->company, $this->companyVerification))
|
||||
->visible(fn () => $progress['canSubmit'])
|
||||
->disabled(fn () => ! $progress['isDataComplete'])
|
||||
->action(function () {
|
||||
$this->submitVerification('company', $this->company, $this->companyVerification);
|
||||
$this->submitVerification();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit media verification action
|
||||
* Handle unified verification submission
|
||||
*/
|
||||
public function submitMediaAction(): Action
|
||||
protected function submitVerification(): void
|
||||
{
|
||||
return Action::make('submitMedia')
|
||||
->label(fn () => $this->needsRevision($this->mediaVerification) ? 'Kirim Ulang' : 'Ajukan Verifikasi')
|
||||
->icon('heroicon-o-paper-airplane')
|
||||
->color('primary')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Ajukan Verifikasi Media')
|
||||
->modalDescription('Apakah Anda yakin ingin mengajukan data media untuk diverifikasi? Pastikan semua data sudah benar.')
|
||||
->modalSubmitActionLabel('Ya, Ajukan')
|
||||
->visible(fn () => $this->canSubmit($this->partnerMedia, $this->mediaVerification))
|
||||
->action(function () {
|
||||
$this->submitVerification('media', $this->partnerMedia, $this->mediaVerification);
|
||||
});
|
||||
}
|
||||
$progress = $this->getVerificationProgress();
|
||||
|
||||
/**
|
||||
* Submit journalist verification action
|
||||
*/
|
||||
public function submitJournalistAction(): Action
|
||||
{
|
||||
return Action::make('submitJournalist')
|
||||
->label(fn () => $this->needsRevision($this->journalistVerification) ? 'Kirim Ulang' : 'Ajukan Verifikasi')
|
||||
->icon('heroicon-o-paper-airplane')
|
||||
->color('primary')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Ajukan Verifikasi Jurnalis')
|
||||
->modalDescription('Apakah Anda yakin ingin mengajukan data jurnalis untuk diverifikasi? Pastikan semua data sudah benar.')
|
||||
->modalSubmitActionLabel('Ya, Ajukan')
|
||||
->visible(fn () => $this->canSubmit($this->journalist, $this->journalistVerification))
|
||||
->action(function () {
|
||||
$this->submitVerification('journalist', $this->journalist, $this->journalistVerification);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle verification submission
|
||||
*/
|
||||
protected function submitVerification(string $type, $entity, ?VerificationRequest $existingRequest): void
|
||||
{
|
||||
if (! $entity) {
|
||||
// Validate data completeness
|
||||
if (! $progress['isDataComplete']) {
|
||||
Notification::make()
|
||||
->title('Error')
|
||||
->body('Data tidak ditemukan. Silakan lengkapi data terlebih dahulu.')
|
||||
->title('Data Belum Lengkap')
|
||||
->body('Silakan lengkapi data terlebih dahulu: '.implode(', ', $progress['missingData']))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($existingRequest && $existingRequest->status === VerificationStatus::NEED_REVISION) {
|
||||
// Resubmit - update existing request
|
||||
$existingRequest->update([
|
||||
'status' => VerificationStatus::PENDING,
|
||||
]);
|
||||
|
||||
$verification = $existingRequest;
|
||||
} else {
|
||||
// New submission
|
||||
$verification = VerificationRequest::create([
|
||||
'verifiable_type' => get_class($entity),
|
||||
'verifiable_id' => $entity->id,
|
||||
'submitted_by' => auth()->id(),
|
||||
'status' => VerificationStatus::PENDING,
|
||||
]);
|
||||
}
|
||||
|
||||
// Update the local property
|
||||
match ($type) {
|
||||
'company' => $this->companyVerification = $verification->fresh(['reviews.reviewer']),
|
||||
'media' => $this->mediaVerification = $verification->fresh(['reviews.reviewer']),
|
||||
'journalist' => $this->journalistVerification = $verification->fresh(['reviews.reviewer']),
|
||||
};
|
||||
|
||||
$entityName = match ($type) {
|
||||
'company' => 'Perusahaan',
|
||||
'media' => 'Media',
|
||||
'journalist' => 'Jurnalis',
|
||||
};
|
||||
$this->verificationRequest = $this->verificationService->submitVerification(
|
||||
$this->company,
|
||||
$this->verificationRequest
|
||||
);
|
||||
|
||||
Notification::make()
|
||||
->title('Berhasil!')
|
||||
->body("Data {$entityName} berhasil diajukan untuk diverifikasi.")
|
||||
->body('Verifikasi lengkap (Perusahaan, Media, dan Jurnalis) berhasil diajukan untuk diverifikasi.')
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
@ -425,9 +138,7 @@ protected function submitVerification(string $type, $entity, ?VerificationReques
|
||||
public function getViewData(): array
|
||||
{
|
||||
return [
|
||||
'companyProgress' => $this->companyProgress(),
|
||||
'mediaProgress' => $this->mediaProgress(),
|
||||
'journalistProgress' => $this->journalistProgress(),
|
||||
'verificationProgress' => $this->getVerificationProgress(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,9 +238,53 @@ public static function table(Table $table): Table
|
||||
}
|
||||
|
||||
return $record;
|
||||
})
|
||||
->visible(function () {
|
||||
$user = auth()->user();
|
||||
|
||||
if (! $user->hasRole('Perusahaan')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $user->company?->partnerMedia) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$verificationRequest = $user->company
|
||||
->verificationRequest()
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($verificationRequest && $verificationRequest->status !== \App\Enums\VerificationStatus::NEED_REVISION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
DeleteAction::make(),
|
||||
DeleteAction::make()
|
||||
->visible(function () {
|
||||
$user = auth()->user();
|
||||
|
||||
if (! $user->hasRole('Perusahaan')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! $user->company?->partnerMedia) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$verificationRequest = $user->company
|
||||
->verificationRequest()
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($verificationRequest && $verificationRequest->status !== \App\Enums\VerificationStatus::NEED_REVISION) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
ForceDeleteAction::make(),
|
||||
|
||||
|
||||
@ -21,6 +21,14 @@ protected function getHeaderActions(): array
|
||||
return [];
|
||||
}
|
||||
|
||||
$verificationRequest = auth()->user()->company?->verificationRequest()
|
||||
->latest()
|
||||
->first();
|
||||
|
||||
if ($verificationRequest && $verificationRequest->status !== \App\Enums\VerificationStatus::NEED_REVISION) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label('Tambah')
|
||||
|
||||
@ -34,7 +34,7 @@ class CategoryResource extends Resource
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::ListBullet;
|
||||
|
||||
protected static ?string $navigationLabel = 'Kategori Berita';
|
||||
protected static ?string $navigationLabel = 'Kategori';
|
||||
|
||||
protected static ?string $recordTitleAttribute = 'name';
|
||||
|
||||
@ -92,7 +92,7 @@ public static function table(Table $table): Table
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkActionGroup::make([
|
||||
...DefaultBulkActions::make('kategori berita'),
|
||||
...DefaultBulkActions::make('kategori'),
|
||||
]),
|
||||
])
|
||||
->emptyStateIcon(Heroicon::ListBullet)
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
class ManageCategories extends ManageRecords
|
||||
{
|
||||
protected static ?string $title = 'Kategori Berita';
|
||||
protected static ?string $title = 'Kategori';
|
||||
|
||||
protected static string $resource = CategoryResource::class;
|
||||
|
||||
@ -18,11 +18,11 @@ protected function getHeaderActions(): array
|
||||
return [
|
||||
CreateAction::make()
|
||||
->label('Tambah')
|
||||
->modalHeading('Tambah Kategori Berita')
|
||||
->modalHeading('Tambah Kategori')
|
||||
->modalSubmitActionLabel('Simpan')
|
||||
->modalCancelActionLabel('Batal')
|
||||
->extraModalFooterActions(fn (CreateAction $action): array => [
|
||||
$action->makeModalSubmitAction('createAnother', arguments: ['another' => true])
|
||||
$action->makeModalSubmitAction('createAknother', arguments: ['another' => true])
|
||||
->label('Simpan dan Tambah Lagi'),
|
||||
])
|
||||
->modalWidth(Width::Large),
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
@ -41,8 +40,8 @@ public function journalists(): HasManyThrough
|
||||
return $this->hasManyThrough(Journalist::class, PartnerMedia::class);
|
||||
}
|
||||
|
||||
public function verificationRequest(): MorphOne
|
||||
public function verificationRequest(): HasOne
|
||||
{
|
||||
return $this->morphOne(VerificationRequest::class, 'verifiable');
|
||||
return $this->hasOne(VerificationRequest::class);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
@ -28,9 +27,4 @@ public function partnerMedia(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PartnerMedia::class);
|
||||
}
|
||||
|
||||
public function verificationRequest(): MorphOne
|
||||
{
|
||||
return $this->morphOne(VerificationRequest::class, 'verifiable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Spatie\MediaLibrary\HasMedia;
|
||||
use Spatie\MediaLibrary\InteractsWithMedia;
|
||||
@ -68,9 +67,4 @@ public function cooperationMedias(): HasMany
|
||||
{
|
||||
return $this->hasMany(CooperationMedia::class);
|
||||
}
|
||||
|
||||
public function verificationRequest(): MorphOne
|
||||
{
|
||||
return $this->morphOne(VerificationRequest::class, 'verifiable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class VerificationRequest extends Model
|
||||
@ -30,9 +29,9 @@ protected function pending(Builder $query): void
|
||||
$query->where('status', VerificationStatus::PENDING);
|
||||
}
|
||||
|
||||
public function verifiable(): MorphTo
|
||||
public function company(): BelongsTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function submittedBy(): BelongsTo
|
||||
|
||||
391
app/Services/VerificationService.php
Normal file
391
app/Services/VerificationService.php
Normal file
@ -0,0 +1,391 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\DecisionAdmin;
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\Company;
|
||||
use App\Models\VerificationRequest;
|
||||
use App\Models\VerificationReview;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class VerificationService
|
||||
{
|
||||
/**
|
||||
* Check if all required data is complete for verification
|
||||
*/
|
||||
public function isDataComplete(?Company $company): bool
|
||||
{
|
||||
return $company !== null
|
||||
&& $company->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()
|
||||
->whereIn('decision', [DecisionAdmin::NEED_REVISION, DecisionAdmin::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 (\Exception $e) {
|
||||
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 lengkap Anda telah diterima dan disetujui oleh admin. Seluruh data (Perusahaan, Media, dan Jurnalis) telah terverifikasi.',
|
||||
'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' => \App\Filament\Pages\CustomCompany::getUrl(),
|
||||
'icon' => 'building-office-2',
|
||||
'color' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
if ($company && ! $company->partnerMedia) {
|
||||
$buttons[] = [
|
||||
'label' => 'Lengkapi Data Media',
|
||||
'url' => \App\Filament\Pages\CustomMedia::getUrl(),
|
||||
'icon' => 'newspaper',
|
||||
'color' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
if ($company && $company->partnerMedia && ($company->partnerMedia->journalists === null || $company->partnerMedia->journalists->count() === 0)) {
|
||||
$buttons[] = [
|
||||
'label' => 'Lengkapi Data Jurnalis',
|
||||
'url' => \App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists::getUrl(),
|
||||
'icon' => 'user-circle',
|
||||
'color' => 'warning',
|
||||
];
|
||||
}
|
||||
|
||||
return $buttons;
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\Company;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@ -15,7 +16,7 @@ public function up(): void
|
||||
{
|
||||
Schema::create('verification_requests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('verifiable');
|
||||
$table->foreignIdFor(Company::class);
|
||||
$table->foreignIdFor(User::class, 'submitted_by');
|
||||
$table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment());
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<x-filament-panels::page>
|
||||
{{-- Status Filter Tabs --}}
|
||||
@php
|
||||
$counts = $this->getStatusCounts();
|
||||
@endphp
|
||||
@ -103,39 +102,25 @@
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
@forelse ($this->getVerifications() as $verification)
|
||||
@php
|
||||
$entityType = $this->getEntityTypeLabel($verification);
|
||||
$entityName = $this->getEntityName($verification);
|
||||
$detailUrl = $this->getDetailUrl($verification);
|
||||
$latestReview = $verification->reviews->first();
|
||||
$card = $this->getCardData($verification);
|
||||
@endphp
|
||||
|
||||
<x-filament::section class="h-full">
|
||||
<x-slot name="heading">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
{{-- Entity Type Badge --}}
|
||||
@php
|
||||
$typeColor = match ($entityType) {
|
||||
'Perusahaan' => 'primary',
|
||||
'Media' => 'info',
|
||||
'Jurnalis' => 'success',
|
||||
default => 'gray',
|
||||
};
|
||||
@endphp
|
||||
<x-filament::badge :color="$typeColor" size="sm">
|
||||
{{ $entityType }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
<h3 class="font-bold text-lg text-gray-900 dark:text-white truncate"
|
||||
title="{{ $entityName }}">
|
||||
{{ $entityName }}
|
||||
title="{{ $card['companyName'] }}">
|
||||
{{ $card['companyName'] }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||
Perusahaan, Media & Jurnalis
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Status Badge --}}
|
||||
<x-filament::badge :color="$verification->status->getColor()" size="lg">
|
||||
{{ $verification->status->getLabel() }}
|
||||
<x-filament::badge :color="$card['statusColor']" size="lg">
|
||||
{{ $card['statusLabel'] }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
</x-slot>
|
||||
@ -153,11 +138,57 @@ class="w-10 h-10 rounded-full bg-primary-100 dark:bg-primary-500/20 flex items-c
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase font-semibold">Diajukan oleh
|
||||
</p>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||
{{ $verification->submittedBy?->name ?? 'Unknown' }}
|
||||
{{ $card['submittedBy'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Data Summary --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
{{-- Company Summary --}}
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<x-heroicon-o-building-office-2 class="w-4 h-4 text-primary-500" />
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Perusahaan
|
||||
</p>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||
{{ $card['summary']['company']['name'] }}
|
||||
</p>
|
||||
<x-filament::badge :color="$card['summary']['company']['exists'] ? 'success' : 'warning'" size="xs" class="mt-1">
|
||||
{{ $card['summary']['company']['exists'] ? '✓ Lengkap' : '⚠ Belum diisi' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
|
||||
{{-- Media Summary --}}
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<x-heroicon-o-newspaper class="w-4 h-4 text-info-500" />
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Media</p>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||
{{ $card['summary']['media']['name'] }}
|
||||
</p>
|
||||
<x-filament::badge :color="$card['summary']['media']['exists'] ? 'success' : 'warning'" size="xs" class="mt-1">
|
||||
{{ $card['summary']['media']['exists'] ? '✓ Lengkap' : '⚠ Belum diisi' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
|
||||
{{-- Journalists Summary --}}
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<x-heroicon-o-identification class="w-4 h-4 text-info-500" />
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Jurnalis</p>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ $card['summary']['journalists']['count'] }} jurnalis
|
||||
</p>
|
||||
<x-filament::badge :color="$card['summary']['journalists']['exists'] ? 'success' : 'warning'" size="xs" class="mt-1">
|
||||
{{ $card['summary']['journalists']['exists'] ? '✓ Lengkap' : '⚠ Belum diisi' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Timeline Info --}}
|
||||
<div class="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
@ -182,38 +213,42 @@ class="w-10 h-10 rounded-full bg-primary-100 dark:bg-primary-500/20 flex items-c
|
||||
</div>
|
||||
|
||||
{{-- Latest Review Note --}}
|
||||
@if ($latestReview && $latestReview->note)
|
||||
<div
|
||||
class="p-3 rounded-lg border
|
||||
{{ match ($latestReview->decision) {
|
||||
\App\Enums\DecisionAdmin::APPROVED
|
||||
@if ($card['latestReviewNote'])
|
||||
@php
|
||||
$reviewClasses = match ($card['latestReviewDecisionColor']) {
|
||||
'success'
|
||||
=> 'bg-success-50 dark:bg-success-500/10 border-success-200 dark:border-success-500/20',
|
||||
\App\Enums\DecisionAdmin::NEED_REVISION
|
||||
'warning'
|
||||
=> 'bg-warning-50 dark:bg-warning-500/10 border-warning-200 dark:border-warning-500/20',
|
||||
\App\Enums\DecisionAdmin::REJECTED
|
||||
'danger'
|
||||
=> 'bg-danger-50 dark:bg-danger-500/10 border-danger-200 dark:border-danger-500/20',
|
||||
} }}">
|
||||
<p
|
||||
class="text-xs font-semibold uppercase mb-1
|
||||
{{ match ($latestReview->decision) {
|
||||
\App\Enums\DecisionAdmin::APPROVED => 'text-success-700 dark:text-success-400',
|
||||
\App\Enums\DecisionAdmin::NEED_REVISION => 'text-warning-700 dark:text-warning-400',
|
||||
\App\Enums\DecisionAdmin::REJECTED => 'text-danger-700 dark:text-danger-400',
|
||||
} }}">
|
||||
'info' => 'bg-info-50 dark:bg-info-500/10 border-info-200 dark:border-info-500/20',
|
||||
default => 'bg-gray-50 dark:bg-gray-500/10 border-gray-200 dark:border-gray-500/20',
|
||||
};
|
||||
$reviewTitleClasses = match ($card['latestReviewDecisionColor']) {
|
||||
'success' => 'text-success-700 dark:text-success-400',
|
||||
'warning' => 'text-warning-700 dark:text-warning-400',
|
||||
'danger' => 'text-danger-700 dark:text-danger-400',
|
||||
'info' => 'text-info-700 dark:text-info-400',
|
||||
default => 'text-gray-700 dark:text-gray-400',
|
||||
};
|
||||
@endphp
|
||||
<div class="p-3 rounded-lg border {{ $reviewClasses }}">
|
||||
<p class="text-xs font-semibold uppercase mb-1 {{ $reviewTitleClasses }}">
|
||||
Catatan Terakhir
|
||||
</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 line-clamp-2">
|
||||
{{ $latestReview->note }}
|
||||
{{ $card['latestReviewNote'] }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
oleh {{ $latestReview->reviewer?->name ?? 'Admin' }} •
|
||||
{{ $latestReview->created_at->diffForHumans() }}
|
||||
oleh {{ $card['latestReviewReviewer'] }} •
|
||||
{{ $card['latestReviewCreatedAt']->diffForHumans() }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Review History Accordion --}}
|
||||
@if ($verification->reviews->count() > 0)
|
||||
@if ($card['reviewsCount'] > 0)
|
||||
<div x-data="{ open: false }"
|
||||
class="border border-gray-200 dark:border-gray-700 rounded-xl overflow-hidden">
|
||||
<button @click="open = !open"
|
||||
@ -221,7 +256,7 @@ class="w-full flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800
|
||||
<div
|
||||
class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<x-heroicon-o-clock class="w-4 h-4" />
|
||||
Riwayat Review ({{ $verification->reviews->count() }})
|
||||
Riwayat Review ({{ $card['reviewsCount'] }})
|
||||
</div>
|
||||
<x-heroicon-o-chevron-down
|
||||
class="w-4 h-4 text-gray-500 transition-transform duration-200"
|
||||
@ -263,31 +298,31 @@ class="w-4 h-4 text-gray-500 transition-transform duration-200"
|
||||
<x-slot name="footer">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
{{-- View Detail Button --}}
|
||||
@if ($detailUrl)
|
||||
@if ($card['detailUrl'])
|
||||
<x-filament::button color="gray" size="sm" icon="heroicon-m-eye" tag="a"
|
||||
:href="$detailUrl" target="_blank">
|
||||
:href="$card['detailUrl']">
|
||||
Detail
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
{{-- Action Buttons based on status --}}
|
||||
@if ($verification->status === \App\Enums\VerificationStatus::PENDING)
|
||||
@if ($card['isPending'])
|
||||
{{ ($this->approveAction)(['id' => $verification->id]) }}
|
||||
{{ ($this->revisionAction)(['id' => $verification->id]) }}
|
||||
{{ ($this->rejectAction)(['id' => $verification->id]) }}
|
||||
@elseif($verification->status === \App\Enums\VerificationStatus::NEED_REVISION)
|
||||
@elseif($card['isRevision'])
|
||||
<span
|
||||
class="flex items-center gap-1 text-xs text-warning-600 dark:text-warning-400 font-medium italic">
|
||||
<x-heroicon-s-clock class="w-3 h-3 animate-pulse" />
|
||||
Menunggu revisi dari user...
|
||||
</span>
|
||||
@elseif($verification->status === \App\Enums\VerificationStatus::APPROVED)
|
||||
@elseif($card['isApproved'])
|
||||
<span
|
||||
class="flex items-center gap-1 text-xs text-success-600 dark:text-success-400 font-medium">
|
||||
<x-heroicon-s-check-circle class="w-3 h-3" />
|
||||
Verifikasi selesai
|
||||
</span>
|
||||
@elseif($verification->status === \App\Enums\VerificationStatus::REJECTED)
|
||||
@elseif($card['isRejected'])
|
||||
<span
|
||||
class="flex items-center gap-1 text-xs text-danger-600 dark:text-danger-400 font-medium">
|
||||
<x-heroicon-s-x-circle class="w-3 h-3" />
|
||||
|
||||
@ -2,8 +2,12 @@
|
||||
<form wire:submit="save">
|
||||
{{ $this->form }}
|
||||
|
||||
<div style="margin-top:20px;">
|
||||
<x-filament::button type="submit">Simpan</x-filament::button>
|
||||
</div>
|
||||
@if (
|
||||
!$verificationRequest ||
|
||||
($verificationRequest && $verificationRequest->status === App\Enums\VerificationStatus::NEED_REVISION))
|
||||
<div class="mt-6">
|
||||
<x-filament::button type="submit">Simpan</x-filament::button>
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
</x-filament-panels::page>
|
||||
|
||||
@ -32,11 +32,13 @@ class="dark:text-gray-400">
|
||||
<form wire:submit="save">
|
||||
{{ $this->form }}
|
||||
|
||||
<div class="mt-6">
|
||||
<x-filament::button type="submit">
|
||||
Simpan
|
||||
</x-filament::button>
|
||||
</div>
|
||||
@if (
|
||||
!$verificationRequest ||
|
||||
($verificationRequest && $verificationRequest->status === App\Enums\VerificationStatus::NEED_REVISION))
|
||||
<div class="mt-6">
|
||||
<x-filament::button type="submit">Simpan</x-filament::button>
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
@endif
|
||||
</x-filament-panels::page>
|
||||
|
||||
@ -1,13 +1,206 @@
|
||||
<x-filament-panels::page>
|
||||
@php
|
||||
$progress = $this->getVerificationProgress();
|
||||
$alert = $progress['alert'];
|
||||
$hasJournalists = $progress['journalists'] && $progress['journalists']->count() > 0;
|
||||
@endphp
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- Company Verification Section --}}
|
||||
<x-verification-card :progress="$companyProgress" :submit-action="$this->submitCompanyAction" type="company" />
|
||||
{{-- Unified Verification Card --}}
|
||||
<x-filament::section>
|
||||
<x-slot name="heading">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-primary-50 dark:bg-primary-500/10">
|
||||
<x-heroicon-o-check-badge class="w-6 h-6 text-primary-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Verifikasi Data
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Lengkapi data perusahaan, media, dan jurnalis Anda untuk mengajukan verifikasi kepada
|
||||
Administrator.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Media Verification Section --}}
|
||||
<x-verification-card :progress="$mediaProgress" :submit-action="$this->submitMediaAction" type="media" />
|
||||
{{-- Status Badge --}}
|
||||
<x-filament::badge :color="$progress['status']['color']" size="lg">
|
||||
{{ $progress['status']['label'] }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
{{-- Journalist Verification Section --}}
|
||||
<x-verification-card :progress="$journalistProgress" :submit-action="$this->submitJournalistAction" type="journalist" />
|
||||
<div class="space-y-6">
|
||||
{{-- Stepper Progress --}}
|
||||
<div class="pt-4">
|
||||
<x-stepper :current-step="$progress['currentStep']" :steps="$progress['steps']" />
|
||||
</div>
|
||||
|
||||
{{-- Data Summary --}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{{-- Company Summary --}}
|
||||
<div
|
||||
class="p-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<x-heroicon-o-building-office-2 class="w-5 h-5 text-primary-500" />
|
||||
<h4 class="font-medium text-gray-900 dark:text-white">Perusahaan</h4>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{{ $progress['company']?->name }}</p>
|
||||
<x-filament::badge :color="$progress['company'] ? 'success' : 'warning'" size="sm" class="mt-2">
|
||||
{{ $progress['company'] ? '✓ Lengkap' : 'Belum Lengkap' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
|
||||
{{-- Media Summary --}}
|
||||
<div
|
||||
class="p-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<x-heroicon-o-newspaper class="w-5 h-5 text-info-500" />
|
||||
<h4 class="font-medium text-gray-900 dark:text-white">Media</h4>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{{ $progress['partnerMedia']?->name }}
|
||||
</p>
|
||||
<x-filament::badge :color="$progress['partnerMedia'] ? 'success' : 'warning'" size="sm" class="mt-2">
|
||||
{{ $progress['partnerMedia'] ? '✓ Lengkap' : 'Belum Lengkap' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
|
||||
{{-- Journalists Summary --}}
|
||||
<div
|
||||
class="p-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<x-heroicon-o-identification class="w-5 h-5 text-info-500" />
|
||||
<h4 class="font-medium text-gray-900 dark:text-white">Jurnalis</h4>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $progress['journalists']?->count() }} jurnalis terdaftar
|
||||
</p>
|
||||
<x-filament::badge :color="$hasJournalists ? 'success' : 'warning'" size="sm" class="mt-2">
|
||||
{{ $hasJournalists ? '✓ Lengkap' : 'Belum Lengkap' }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Alert Messages --}}
|
||||
@if ($alert)
|
||||
@php
|
||||
$alertClasses = match ($alert['color']) {
|
||||
'success'
|
||||
=> 'bg-success-50 dark:bg-success-500/10 border-success-200 dark:border-success-500/20',
|
||||
'warning'
|
||||
=> 'bg-warning-50 dark:bg-warning-500/10 border-warning-200 dark:border-warning-500/20',
|
||||
'danger'
|
||||
=> 'bg-danger-50 dark:bg-danger-500/10 border-danger-200 dark:border-danger-500/20',
|
||||
'info' => 'bg-info-50 dark:bg-info-500/10 border-info-200 dark:border-info-500/20',
|
||||
default => 'bg-gray-50 dark:bg-gray-500/10 border-gray-200 dark:border-gray-500/20',
|
||||
};
|
||||
$alertIconClasses = match ($alert['color']) {
|
||||
'success' => 'text-success-500',
|
||||
'warning' => 'text-warning-500',
|
||||
'danger' => 'text-danger-500',
|
||||
'info' => 'text-info-500',
|
||||
default => 'text-gray-500',
|
||||
};
|
||||
$alertTitleClasses = match ($alert['color']) {
|
||||
'success' => 'text-success-800 dark:text-success-200',
|
||||
'warning' => 'text-warning-800 dark:text-warning-200',
|
||||
'danger' => 'text-danger-800 dark:text-danger-200',
|
||||
'info' => 'text-info-800 dark:text-info-200',
|
||||
default => 'text-gray-800 dark:text-gray-200',
|
||||
};
|
||||
$alertMessageClasses = match ($alert['color']) {
|
||||
'success' => 'text-success-700 dark:text-success-300',
|
||||
'warning' => 'text-warning-700 dark:text-warning-300',
|
||||
'danger' => 'text-danger-700 dark:text-danger-300',
|
||||
'info' => 'text-info-700 dark:text-info-300',
|
||||
default => 'text-gray-700 dark:text-gray-300',
|
||||
};
|
||||
$iconName = 'heroicon-s-' . $alert['icon'];
|
||||
$iconClasses =
|
||||
$alertIconClasses . (isset($alert['animate']) && $alert['animate'] ? ' animate-pulse' : '');
|
||||
@endphp
|
||||
<div class="p-4 rounded-xl border {{ $alertClasses }}">
|
||||
<div class="flex gap-3">
|
||||
<div class="shrink-0">
|
||||
<x-dynamic-component :component="$iconName" class="w-5 h-5 {{ $iconClasses }}" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium {{ $alertTitleClasses }}">
|
||||
{{ $alert['title'] }}
|
||||
</h4>
|
||||
<p class="mt-1 text-sm {{ $alertMessageClasses }}">
|
||||
{{ $alert['message'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Review History Accordion --}}
|
||||
@if ($progress['reviewHistory']->isNotEmpty())
|
||||
<div x-data="{ open: false }"
|
||||
class="border border-gray-200 dark:border-gray-700 rounded-xl overflow-hidden">
|
||||
<button @click="open = !open"
|
||||
class="w-full flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||
<div class="flex items-center gap-2 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
<x-heroicon-o-clock class="w-4 h-4" />
|
||||
Riwayat Review ({{ $progress['reviewHistory']->count() }})
|
||||
</div>
|
||||
<x-heroicon-o-chevron-down class="w-4 h-4 text-gray-500 transition-transform duration-200"
|
||||
x-bind:class="{ 'rotate-180': open }" />
|
||||
</button>
|
||||
|
||||
<div x-show="open" x-collapse class="border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
@foreach ($progress['reviewHistory'] as $review)
|
||||
<div class="p-4">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<x-filament::badge :color="$review->decision->getColor()" size="sm">
|
||||
{{ $review->decision->getLabel() }}
|
||||
</x-filament::badge>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||
oleh {{ $review->reviewer?->name ?? 'Admin' }}
|
||||
</span>
|
||||
</div>
|
||||
@if ($review->note)
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
{{ $review->note }}
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
<div class="shrink-0 text-xs text-gray-400">
|
||||
{{ $review->created_at->format('d M Y, H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Footer with Actions --}}
|
||||
@if ($progress['canSubmit'])
|
||||
<x-slot name="footer">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
@if ($progress['lastSubmittedAt'])
|
||||
Terakhir diajukan: {{ $progress['lastSubmittedAt'] }}
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
{{ $this->submitVerificationAction }}
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
@endif
|
||||
</x-filament::section>
|
||||
</div>
|
||||
|
||||
<x-filament-actions::modals />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user