feat: Implement a comprehensive verification system with dedicated pages, models, and UI components for companies, media, and journalists.
This commit is contained in:
parent
7637bcc8b5
commit
b4d2ae52f0
42
app/Enums/DecisionAdmin.php
Normal file
42
app/Enums/DecisionAdmin.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\Enum\WithComment;
|
||||
use App\Traits\Enum\WithValue;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum DecisionAdmin: int implements HasColor, HasLabel
|
||||
{
|
||||
use WithComment, WithValue;
|
||||
|
||||
case APPROVED = 1;
|
||||
case NEED_REVISION = 2;
|
||||
case REJECTED = 3;
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::APPROVED => 'Disetujui',
|
||||
self::NEED_REVISION => 'Perlu Revisi',
|
||||
self::REJECTED => 'Ditolak',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::APPROVED => 'success',
|
||||
self::NEED_REVISION => 'info',
|
||||
self::REJECTED => 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
public static function options(): array
|
||||
{
|
||||
return collect(self::cases())
|
||||
->mapWithKeys(fn ($case) => [$case->value => $case->getLabel()])
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
45
app/Enums/VerificationStatus.php
Normal file
45
app/Enums/VerificationStatus.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use App\Traits\Enum\WithComment;
|
||||
use App\Traits\Enum\WithValue;
|
||||
use Filament\Support\Contracts\HasColor;
|
||||
use Filament\Support\Contracts\HasLabel;
|
||||
|
||||
enum VerificationStatus: int implements HasColor, HasLabel
|
||||
{
|
||||
use WithComment, WithValue;
|
||||
|
||||
case PENDING = 1;
|
||||
case APPROVED = 2;
|
||||
case NEED_REVISION = 3;
|
||||
case REJECTED = 4;
|
||||
|
||||
public function getLabel(): ?string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PENDING => 'Menunggu Verifikasi',
|
||||
self::APPROVED => 'Disetujui',
|
||||
self::NEED_REVISION => 'Perlu Revisi',
|
||||
self::REJECTED => 'Ditolak',
|
||||
};
|
||||
}
|
||||
|
||||
public function getColor(): string|array|null
|
||||
{
|
||||
return match ($this) {
|
||||
self::PENDING => 'warning',
|
||||
self::APPROVED => 'success',
|
||||
self::NEED_REVISION => 'info',
|
||||
self::REJECTED => 'danger',
|
||||
};
|
||||
}
|
||||
|
||||
public static function options(): array
|
||||
{
|
||||
return collect(self::cases())
|
||||
->mapWithKeys(fn ($case) => [$case->value => $case->getLabel()])
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
258
app/Filament/Pages/AdminVerification.php
Normal file
258
app/Filament/Pages/AdminVerification.php
Normal file
@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Enums\DecisionAdmin;
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\VerificationRequest;
|
||||
use App\Models\VerificationReview;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
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
|
||||
{
|
||||
use InteractsWithActions, InteractsWithForms;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-user-group';
|
||||
|
||||
protected static ?string $title = 'Verifikasi Rekanan';
|
||||
|
||||
protected static ?string $navigationLabel = 'Verifikasi Rekanan';
|
||||
|
||||
protected static string|UnitEnum|null $navigationGroup = 'Kelola';
|
||||
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
protected static ?string $slug = 'manage/admin-verification';
|
||||
|
||||
protected string $view = 'filament.pages.admin-verification';
|
||||
|
||||
public string $filterStatus = 'all';
|
||||
|
||||
public function getVerifications(): LengthAwarePaginator
|
||||
{
|
||||
$query = VerificationRequest::with([
|
||||
'verifiable',
|
||||
'submittedBy',
|
||||
'reviews.reviewer',
|
||||
])
|
||||
->latest('updated_at');
|
||||
|
||||
// Apply status filter
|
||||
if ($this->filterStatus !== 'all') {
|
||||
$query->where('status', $this->filterStatus);
|
||||
}
|
||||
|
||||
return $query->paginate(12);
|
||||
}
|
||||
|
||||
public function setFilter(string $status): void
|
||||
{
|
||||
$this->filterStatus = $status;
|
||||
}
|
||||
|
||||
public function getStatusCounts(): array
|
||||
{
|
||||
return [
|
||||
'all' => VerificationRequest::count(),
|
||||
'pending' => VerificationRequest::where('status', VerificationStatus::PENDING)->count(),
|
||||
'revision' => VerificationRequest::where('status', VerificationStatus::NEED_REVISION)->count(),
|
||||
'approved' => VerificationRequest::where('status', VerificationStatus::APPROVED)->count(),
|
||||
'rejected' => VerificationRequest::where('status', VerificationStatus::REJECTED)->count(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity type label
|
||||
*/
|
||||
public function getEntityTypeLabel(VerificationRequest $request): string
|
||||
{
|
||||
$type = class_basename($request->verifiable_type);
|
||||
|
||||
return match ($type) {
|
||||
'Company' => 'Perusahaan',
|
||||
'PartnerMedia' => 'Media',
|
||||
'Journalist' => 'Jurnalis',
|
||||
default => $type,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public function getDetailUrl(VerificationRequest $request): ?string
|
||||
{
|
||||
$entity = $request->verifiable;
|
||||
$type = class_basename($request->verifiable_type);
|
||||
|
||||
if (! $entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return match ($type) {
|
||||
'Company' => route('filament.dashboard.resources.manage.partners.view', ['record' => $entity->user_id]),
|
||||
'PartnerMedia' => route('filament.dashboard.resources.manage.partners.view', ['record' => $entity->company?->user_id]),
|
||||
'Journalist' => route('filament.dashboard.resources.manage.journalists.view', ['record' => $entity->id]),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Approve verification action
|
||||
*/
|
||||
public function approveAction(): Action
|
||||
{
|
||||
return Action::make('approve')
|
||||
->label('Setujui')
|
||||
->color('success')
|
||||
->icon('heroicon-o-check')
|
||||
->size('sm')
|
||||
->requiresConfirmation()
|
||||
->modalHeading('Setujui Verifikasi')
|
||||
->modalDescription('Apakah Anda yakin ingin menyetujui verifikasi ini?')
|
||||
->modalSubmitActionLabel('Ya, Setujui')
|
||||
->form([
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Revision verification action
|
||||
*/
|
||||
public function revisionAction(): Action
|
||||
{
|
||||
return Action::make('revision')
|
||||
->label('Minta Revisi')
|
||||
->color('warning')
|
||||
->icon('heroicon-o-pencil-square')
|
||||
->size('sm')
|
||||
->modalHeading('Minta Revisi')
|
||||
->modalDescription('User akan diminta untuk memperbaiki data sesuai catatan yang diberikan.')
|
||||
->form([
|
||||
Textarea::make('note')
|
||||
->label('Catatan Revisi')
|
||||
->required()
|
||||
->placeholder('Jelaskan bagian yang perlu diperbaiki...')
|
||||
->helperText('Catatan ini akan ditampilkan kepada user.'),
|
||||
])
|
||||
->action(function (array $arguments, array $data) {
|
||||
$this->processReview($arguments['id'], DecisionAdmin::NEED_REVISION, $data['note']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject verification action
|
||||
*/
|
||||
public function rejectAction(): Action
|
||||
{
|
||||
return Action::make('reject')
|
||||
->label('Tolak')
|
||||
->color('danger')
|
||||
->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.')
|
||||
->modalSubmitActionLabel('Ya, Tolak')
|
||||
->form([
|
||||
Textarea::make('note')
|
||||
->label('Alasan Penolakan')
|
||||
->required()
|
||||
->placeholder('Jelaskan alasan pengajuan ditolak...')
|
||||
->helperText('Alasan ini akan ditampilkan kepada user.'),
|
||||
])
|
||||
->action(function (array $arguments, array $data) {
|
||||
$this->processReview($arguments['id'], DecisionAdmin::REJECTED, $data['note']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process review decision
|
||||
*/
|
||||
protected function processReview(int $requestId, DecisionAdmin $decision, ?string $note): void
|
||||
{
|
||||
$verificationRequest = VerificationRequest::find($requestId);
|
||||
|
||||
if (! $verificationRequest) {
|
||||
Notification::make()
|
||||
->title('Error')
|
||||
->body('Data verifikasi tidak ditemukan.')
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 the related entity status if needed
|
||||
$entity = $verificationRequest->verifiable;
|
||||
if ($entity && method_exists($entity, 'update')) {
|
||||
$entity->update(['status' => $newStatus]);
|
||||
}
|
||||
|
||||
$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();
|
||||
}
|
||||
}
|
||||
434
app/Filament/Pages/CustomVerification.php
Normal file
434
app/Filament/Pages/CustomVerification.php
Normal file
@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use App\Enums\DecisionAdmin;
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\Company;
|
||||
use App\Models\Journalist;
|
||||
use App\Models\PartnerMedia;
|
||||
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\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Illuminate\Support\Collection;
|
||||
use UnitEnum;
|
||||
|
||||
class CustomVerification extends Page implements HasActions, HasForms
|
||||
{
|
||||
use HasPageShield, InteractsWithActions, InteractsWithForms;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-check-badge';
|
||||
|
||||
protected static ?string $title = 'Verifikasi';
|
||||
|
||||
protected static ?string $navigationLabel = 'Verifikasi';
|
||||
|
||||
protected static string|UnitEnum|null $navigationGroup = 'Kelola';
|
||||
|
||||
protected static ?string $slug = 'manage/verification';
|
||||
|
||||
protected static ?int $navigationSort = 12;
|
||||
|
||||
protected string $view = 'filament.pages.verification';
|
||||
|
||||
public ?Company $company = null;
|
||||
|
||||
public ?PartnerMedia $partnerMedia = null;
|
||||
|
||||
public ?Journalist $journalist = null;
|
||||
|
||||
public ?VerificationRequest $companyVerification = null;
|
||||
|
||||
public ?VerificationRequest $mediaVerification = null;
|
||||
|
||||
public ?VerificationRequest $journalistVerification = null;
|
||||
|
||||
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
|
||||
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()
|
||||
->with(['reviews.reviewer'])
|
||||
->latest()
|
||||
->first();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the current step based on status
|
||||
*/
|
||||
protected function resolveStep(?VerificationStatus $status, bool $hasData = false): int
|
||||
{
|
||||
if (! $hasData) {
|
||||
return 1; // No data yet
|
||||
}
|
||||
|
||||
return match ($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
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user can submit verification for entity
|
||||
*/
|
||||
protected function canSubmit($entity, ?VerificationRequest $verification): bool
|
||||
{
|
||||
if (! $entity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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' => route('filament.dashboard.pages.manage.company'),
|
||||
'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' => route('filament.dashboard.pages.manage.media'),
|
||||
'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' => route('filament.dashboard.resources.manage.journalists.index'),
|
||||
'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')
|
||||
->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.')
|
||||
->modalSubmitActionLabel('Ya, Ajukan')
|
||||
->visible(fn () => $this->canSubmit($this->company, $this->companyVerification))
|
||||
->action(function () {
|
||||
$this->submitVerification('company', $this->company, $this->companyVerification);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit media verification action
|
||||
*/
|
||||
public function submitMediaAction(): Action
|
||||
{
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Notification::make()
|
||||
->title('Error')
|
||||
->body('Data tidak ditemukan. Silakan lengkapi data terlebih dahulu.')
|
||||
->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',
|
||||
};
|
||||
|
||||
Notification::make()
|
||||
->title('Berhasil!')
|
||||
->body("Data {$entityName} berhasil diajukan untuk diverifikasi.")
|
||||
->success()
|
||||
->send();
|
||||
}
|
||||
|
||||
public function getViewData(): array
|
||||
{
|
||||
return [
|
||||
'companyProgress' => $this->companyProgress(),
|
||||
'mediaProgress' => $this->mediaProgress(),
|
||||
'journalistProgress' => $this->journalistProgress(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,13 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
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;
|
||||
@ -17,6 +19,13 @@ class Company extends Model implements HasMedia
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'status' => VerificationStatus::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
@ -31,4 +40,9 @@ public function journalists(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(Journalist::class, PartnerMedia::class);
|
||||
}
|
||||
|
||||
public function verificationRequest(): MorphOne
|
||||
{
|
||||
return $this->morphOne(VerificationRequest::class, 'verifiable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
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;
|
||||
@ -15,8 +17,20 @@ class Journalist extends Model implements HasMedia
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'status' => VerificationStatus::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function partnerMedia(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PartnerMedia::class);
|
||||
}
|
||||
|
||||
public function verificationRequest(): MorphOne
|
||||
{
|
||||
return $this->morphOne(VerificationRequest::class, 'verifiable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,18 +4,20 @@
|
||||
|
||||
use App\Enums\MediaClassification;
|
||||
use App\Enums\MediaType;
|
||||
use App\Enums\VerificationStatus;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
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;
|
||||
|
||||
class PartnerMedia extends Model implements HasMedia
|
||||
{
|
||||
use HasFactory, InteractsWithMedia,SoftDeletes;
|
||||
use HasFactory, InteractsWithMedia, SoftDeletes;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
@ -24,6 +26,7 @@ protected function casts(): array
|
||||
return [
|
||||
'type' => MediaType::class,
|
||||
'classification' => MediaClassification::class,
|
||||
'status' => VerificationStatus::class,
|
||||
];
|
||||
}
|
||||
|
||||
@ -65,4 +68,9 @@ public function cooperationMedias(): HasMany
|
||||
{
|
||||
return $this->hasMany(CooperationMedia::class);
|
||||
}
|
||||
|
||||
public function verificationRequest(): MorphOne
|
||||
{
|
||||
return $this->morphOne(VerificationRequest::class, 'verifiable');
|
||||
}
|
||||
}
|
||||
|
||||
@ -61,4 +61,14 @@ public function news(): HasMany
|
||||
{
|
||||
return $this->hasMany(News::class, 'author_id');
|
||||
}
|
||||
|
||||
public function verificationRequests(): HasMany
|
||||
{
|
||||
return $this->hasMany(VerificationRequest::class, 'submitted_by');
|
||||
}
|
||||
|
||||
public function verificationReviews(): HasMany
|
||||
{
|
||||
return $this->hasMany(VerificationReview::class, 'reviewer_id');
|
||||
}
|
||||
}
|
||||
|
||||
39
app/Models/VerificationRequest.php
Normal file
39
app/Models/VerificationRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
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
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'status' => VerificationStatus::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function verifiable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function submittedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'submitted_by');
|
||||
}
|
||||
|
||||
public function reviews(): HasMany
|
||||
{
|
||||
return $this->hasMany(VerificationReview::class);
|
||||
}
|
||||
}
|
||||
32
app/Models/VerificationReview.php
Normal file
32
app/Models/VerificationReview.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\DecisionAdmin;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class VerificationReview extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'decision' => DecisionAdmin::class,
|
||||
];
|
||||
}
|
||||
|
||||
public function verificationRequest(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(VerificationRequest::class);
|
||||
}
|
||||
|
||||
public function reviewer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reviewer_id');
|
||||
}
|
||||
}
|
||||
26
app/View/Components/Stepper.php
Normal file
26
app/View/Components/Stepper.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class Stepper extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public int $currentStep = 1,
|
||||
public array $steps = []
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.stepper');
|
||||
}
|
||||
}
|
||||
28
app/View/Components/VerificationCard.php
Normal file
28
app/View/Components/VerificationCard.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components;
|
||||
|
||||
use Closure;
|
||||
use Filament\Actions\Action;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\View\Component;
|
||||
|
||||
class VerificationCard extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*/
|
||||
public function __construct(
|
||||
public array $progress,
|
||||
public Action $submitAction,
|
||||
public string $type = 'company'
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*/
|
||||
public function render(): View|Closure|string
|
||||
{
|
||||
return view('components.verification-card');
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@ -27,8 +28,7 @@ public function up(): void
|
||||
$table->string('annual_tax_return', 100);
|
||||
$table->string('domicile_certificate', 100);
|
||||
$table->string('profile', 100);
|
||||
$table->date('validated_at')->nullable();
|
||||
$table->text('rejection_reason')->nullable();
|
||||
$table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment())->after('validated_at');
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
use App\Enums\MediaClassification;
|
||||
use App\Enums\MediaType;
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\Company;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@ -24,6 +25,7 @@ public function up(): void
|
||||
$table->enum('classification', [MediaClassification::values()])->comment(MediaClassification::comment());
|
||||
$table->string('journalism_organization')->nullable();
|
||||
$table->string('press_council_certificate')->nullable();
|
||||
$table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment())->after('profile');
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\PartnerMedia;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
@ -20,6 +21,7 @@ public function up(): void
|
||||
$table->string('phone_number', 20);
|
||||
$table->string('press_card', 100)->nullable();
|
||||
$table->string('ukw_certificate', 100)->nullable();
|
||||
$table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment())->after('profile');
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\VerificationStatus;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('verification_requests', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->morphs('verifiable');
|
||||
$table->foreignIdFor(User::class, 'submitted_by');
|
||||
$table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment());
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('verification_requests');
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\DecisionAdmin;
|
||||
use App\Models\User;
|
||||
use App\Models\VerificationRequest;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('verification_reviews', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(VerificationRequest::class);
|
||||
$table->foreignIdFor(User::class, 'reviewer_id');
|
||||
$table->enum('decision', DecisionAdmin::cases())->comment(DecisionAdmin::comment());
|
||||
$table->text('note')->nullable();
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
|
||||
$table->softDeletes();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('verification_reviews');
|
||||
}
|
||||
};
|
||||
@ -442,6 +442,7 @@ public function run(): void
|
||||
"permissions": [
|
||||
"View:CustomCompany",
|
||||
"View:CustomMedia",
|
||||
"View:CustomVerification",
|
||||
"ViewAny:Cooperation",
|
||||
"View:Cooperation",
|
||||
"Create:Cooperation",
|
||||
|
||||
50
resources/views/components/stepper.blade.php
Normal file
50
resources/views/components/stepper.blade.php
Normal file
@ -0,0 +1,50 @@
|
||||
<ul class="relative flex flex-col md:flex-row gap-2">
|
||||
@foreach ($steps as $index => $step)
|
||||
@php
|
||||
$stepNumber = $index + 1;
|
||||
$isCompleted = $stepNumber < $currentStep;
|
||||
$isActive = $stepNumber === $currentStep;
|
||||
@endphp
|
||||
|
||||
<li class="md:shrink md:basis-0 flex-1 group flex gap-x-2 md:block">
|
||||
<div
|
||||
class="min-w-7 min-h-7 flex flex-col items-center md:w-full md:inline-flex md:flex-wrap md:flex-row text-xs align-middle">
|
||||
|
||||
{{-- Circle --}}
|
||||
<span @class([
|
||||
'size-7 flex justify-center items-center shrink-0 rounded-full font-medium',
|
||||
'bg-primary-600 text-white' => $isActive || $isCompleted,
|
||||
'bg-gray-100 text-gray-800 dark:bg-neutral-700 dark:text-white' =>
|
||||
!$isActive && !$isCompleted,
|
||||
])>
|
||||
@if ($isCompleted)
|
||||
✓
|
||||
@else
|
||||
{{ $stepNumber }}
|
||||
@endif
|
||||
</span>
|
||||
|
||||
{{-- Line --}}
|
||||
<div
|
||||
class="mt-2 w-px h-full md:mt-0 md:ms-2 md:w-full md:h-px md:flex-1
|
||||
{{ $loop->last ? 'hidden' : 'bg-gray-200 dark:bg-neutral-700' }}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Content --}}
|
||||
<div class="grow md:grow-0 md:mt-3 pb-5">
|
||||
<span @class([
|
||||
'block text-sm font-medium',
|
||||
'text-primary-600' => $isActive || $isCompleted,
|
||||
'text-gray-800 dark:text-white' => !$isActive && !$isCompleted,
|
||||
])>
|
||||
{{ $step['title'] }}
|
||||
</span>
|
||||
|
||||
<p class="text-sm text-gray-800 dark:text-neutral-500">
|
||||
{{ $step['description'] }}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
234
resources/views/components/verification-card.blade.php
Normal file
234
resources/views/components/verification-card.blade.php
Normal file
@ -0,0 +1,234 @@
|
||||
@php
|
||||
$hasEntity = $progress['entity'] !== null;
|
||||
$verification = $progress['verification'];
|
||||
$status = $verification?->status;
|
||||
@endphp
|
||||
|
||||
<x-filament::section>
|
||||
<x-slot name="heading">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
{{-- Icon based on type --}}
|
||||
@if ($type === 'company')
|
||||
<div class="p-2 rounded-lg bg-info-50 dark:bg-info-500/10">
|
||||
<x-heroicon-o-building-office-2 class="w-6 h-6 text-info-500" />
|
||||
</div>
|
||||
@elseif($type === 'media')
|
||||
<div class="p-2 rounded-lg bg-info-50 dark:bg-info-500/10">
|
||||
<x-heroicon-o-newspaper class="w-6 h-6 text-info-500" />
|
||||
</div>
|
||||
@else
|
||||
<div class="p-2 rounded-lg bg-info-50 dark:bg-info-500/10">
|
||||
<x-heroicon-o-user-circle class="w-6 h-6 text-info-500" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ $progress['title'] }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ $progress['description'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Status Badge --}}
|
||||
@if ($verification)
|
||||
<x-filament::badge :color="$status->getColor()" size="lg">
|
||||
{{ $status->getLabel() }}
|
||||
</x-filament::badge>
|
||||
@else
|
||||
<x-filament::badge color="gray" size="lg">
|
||||
Belum Mengajukan
|
||||
</x-filament::badge>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- Stepper Progress --}}
|
||||
<div class="pt-4">
|
||||
<x-stepper :current-step="$progress['currentStep']" :steps="$progress['steps']" />
|
||||
</div>
|
||||
|
||||
{{-- Alert for Revision Notes --}}
|
||||
@if ($progress['needsRevision'] && $progress['latestNotes'])
|
||||
<div
|
||||
class="p-4 rounded-xl bg-warning-50 dark:bg-warning-500/10 border border-warning-200 dark:border-warning-500/20">
|
||||
<div class="flex gap-3">
|
||||
<div class="shrink-0">
|
||||
<x-heroicon-s-exclamation-triangle class="w-5 h-5 text-warning-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-warning-800 dark:text-warning-200">
|
||||
Catatan Revisi dari Admin
|
||||
</h4>
|
||||
<p class="mt-1 text-sm text-warning-700 dark:text-warning-300">
|
||||
{{ $progress['latestNotes'] }}
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
<x-filament::button tag="a" :href="$progress['editUrl']" size="sm" color="warning"
|
||||
icon="heroicon-m-pencil-square">
|
||||
Perbaiki Data
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Alert for Rejection --}}
|
||||
@if ($progress['isRejected'] && $progress['latestNotes'])
|
||||
<div
|
||||
class="p-4 rounded-xl bg-danger-50 dark:bg-danger-500/10 border border-danger-200 dark:border-danger-500/20">
|
||||
<div class="flex gap-3">
|
||||
<div class="shrink-0">
|
||||
<x-heroicon-s-x-circle class="w-5 h-5 text-danger-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-danger-800 dark:text-danger-200">
|
||||
Pengajuan Ditolak
|
||||
</h4>
|
||||
<p class="mt-1 text-sm text-danger-700 dark:text-danger-300">
|
||||
{{ $progress['latestNotes'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Success Message for Approved --}}
|
||||
@if ($progress['isApproved'])
|
||||
<div
|
||||
class="p-4 rounded-xl bg-success-50 dark:bg-success-500/10 border border-success-200 dark:border-success-500/20">
|
||||
<div class="flex gap-3">
|
||||
<div class="shrink-0">
|
||||
<x-heroicon-s-check-circle class="w-5 h-5 text-success-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-success-800 dark:text-success-200">
|
||||
Verifikasi Diterima
|
||||
</h4>
|
||||
<p class="mt-1 text-sm text-success-700 dark:text-success-300">
|
||||
Data {{ strtolower($progress['title']) }} Anda telah diverifikasi dan disetujui oleh admin.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Pending Message --}}
|
||||
@if ($progress['isPending'])
|
||||
<div class="p-4 rounded-xl bg-info-50 dark:bg-info-500/10 border border-info-200 dark:border-info-500/20">
|
||||
<div class="flex gap-3">
|
||||
<div class="shrink-0">
|
||||
<x-heroicon-s-clock class="w-5 h-5 text-info-500 animate-pulse" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-info-800 dark:text-info-200">
|
||||
Menunggu Verifikasi
|
||||
</h4>
|
||||
<p class="mt-1 text-sm text-info-700 dark:text-info-300">
|
||||
Data sedang dalam proses verifikasi oleh admin. Mohon tunggu.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- No Data Message --}}
|
||||
@if (!$hasEntity)
|
||||
<div class="p-4 rounded-xl bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700">
|
||||
<div class="flex gap-3">
|
||||
<div class="shrink-0">
|
||||
<x-heroicon-o-information-circle class="w-5 h-5 text-gray-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300">
|
||||
Data Belum Tersedia
|
||||
</h4>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
Silakan lengkapi data {{ strtolower($progress['title']) }} terlebih dahulu sebelum
|
||||
mengajukan verifikasi.
|
||||
</p>
|
||||
<div class="mt-3">
|
||||
<x-filament::button tag="a" :href="$progress['editUrl']" size="sm" color="gray"
|
||||
icon="heroicon-m-plus">
|
||||
Lengkapi Data
|
||||
</x-filament::button>
|
||||
</div>
|
||||
</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 --}}
|
||||
<x-slot name="footer">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
@if ($verification)
|
||||
Terakhir diajukan: {{ $verification->updated_at->format('d M Y, H:i') }}
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
@if ($hasEntity && !$progress['isPending'] && !$progress['isApproved'])
|
||||
<x-filament::button tag="a" :href="$progress['editUrl']" color="gray" size="sm"
|
||||
icon="heroicon-m-pencil">
|
||||
Edit Data
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
@if ($progress['canSubmit'])
|
||||
{{ $submitAction }}
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-slot>
|
||||
</x-filament::section>
|
||||
326
resources/views/filament/pages/admin-verification.blade.php
Normal file
326
resources/views/filament/pages/admin-verification.blade.php
Normal file
@ -0,0 +1,326 @@
|
||||
<x-filament-panels::page>
|
||||
{{-- Status Filter Tabs --}}
|
||||
@php
|
||||
$counts = $this->getStatusCounts();
|
||||
@endphp
|
||||
|
||||
<div class="mb-6">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button wire:click="setFilter('all')" @class([
|
||||
'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200',
|
||||
'bg-primary-500 text-white shadow-md shadow-primary-500/20' =>
|
||||
$filterStatus === 'all',
|
||||
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700' =>
|
||||
$filterStatus !== 'all',
|
||||
])>
|
||||
<span>Semua</span>
|
||||
<span @class([
|
||||
'px-2 py-0.5 text-xs rounded-full',
|
||||
'bg-white/20' => $filterStatus === 'all',
|
||||
'bg-gray-200 dark:bg-gray-700' => $filterStatus !== 'all',
|
||||
])>{{ $counts['all'] }}</span>
|
||||
</button>
|
||||
|
||||
<button wire:click="setFilter('{{ \App\Enums\VerificationStatus::PENDING->value }}')"
|
||||
@class([
|
||||
'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200',
|
||||
'bg-warning-500 text-white shadow-md shadow-warning-500/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::PENDING->value,
|
||||
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::PENDING->value,
|
||||
])>
|
||||
<x-heroicon-s-clock class="w-4 h-4" />
|
||||
<span>Menunggu</span>
|
||||
<span @class([
|
||||
'px-2 py-0.5 text-xs rounded-full',
|
||||
'bg-white/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::PENDING->value,
|
||||
'bg-warning-100 text-warning-700 dark:bg-warning-500/20 dark:text-warning-400' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::PENDING->value,
|
||||
])>{{ $counts['pending'] }}</span>
|
||||
</button>
|
||||
|
||||
<button wire:click="setFilter('{{ \App\Enums\VerificationStatus::NEED_REVISION->value }}')"
|
||||
@class([
|
||||
'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200',
|
||||
'bg-info-500 text-white shadow-md shadow-info-500/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::NEED_REVISION->value,
|
||||
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::NEED_REVISION->value,
|
||||
])>
|
||||
<x-heroicon-s-pencil-square class="w-4 h-4" />
|
||||
<span>Revisi</span>
|
||||
<span @class([
|
||||
'px-2 py-0.5 text-xs rounded-full',
|
||||
'bg-white/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::NEED_REVISION->value,
|
||||
'bg-info-100 text-info-700 dark:bg-info-500/20 dark:text-info-400' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::NEED_REVISION->value,
|
||||
])>{{ $counts['revision'] }}</span>
|
||||
</button>
|
||||
|
||||
<button wire:click="setFilter('{{ \App\Enums\VerificationStatus::APPROVED->value }}')"
|
||||
@class([
|
||||
'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200',
|
||||
'bg-success-500 text-white shadow-md shadow-success-500/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::APPROVED->value,
|
||||
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::APPROVED->value,
|
||||
])>
|
||||
<x-heroicon-s-check-circle class="w-4 h-4" />
|
||||
<span>Disetujui</span>
|
||||
<span @class([
|
||||
'px-2 py-0.5 text-xs rounded-full',
|
||||
'bg-white/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::APPROVED->value,
|
||||
'bg-success-100 text-success-700 dark:bg-success-500/20 dark:text-success-400' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::APPROVED->value,
|
||||
])>{{ $counts['approved'] }}</span>
|
||||
</button>
|
||||
|
||||
<button wire:click="setFilter('{{ \App\Enums\VerificationStatus::REJECTED->value }}')"
|
||||
@class([
|
||||
'inline-flex items-center gap-2 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200',
|
||||
'bg-danger-500 text-white shadow-md shadow-danger-500/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::REJECTED->value,
|
||||
'bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::REJECTED->value,
|
||||
])>
|
||||
<x-heroicon-s-x-circle class="w-4 h-4" />
|
||||
<span>Ditolak</span>
|
||||
<span @class([
|
||||
'px-2 py-0.5 text-xs rounded-full',
|
||||
'bg-white/20' =>
|
||||
$filterStatus == \App\Enums\VerificationStatus::REJECTED->value,
|
||||
'bg-danger-100 text-danger-700 dark:bg-danger-500/20 dark:text-danger-400' =>
|
||||
$filterStatus != \App\Enums\VerificationStatus::REJECTED->value,
|
||||
])>{{ $counts['rejected'] }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Verification Cards Grid --}}
|
||||
<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();
|
||||
@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 }}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{{-- Status Badge --}}
|
||||
<x-filament::badge :color="$verification->status->getColor()" size="lg">
|
||||
{{ $verification->status->getLabel() }}
|
||||
</x-filament::badge>
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<div class="space-y-4">
|
||||
{{-- Submitter Info --}}
|
||||
<div class="flex items-center gap-3 p-3 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div class="shrink-0">
|
||||
<div
|
||||
class="w-10 h-10 rounded-full bg-primary-100 dark:bg-primary-500/20 flex items-center justify-center">
|
||||
<x-heroicon-s-user class="w-5 h-5 text-primary-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<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' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Timeline Info --}}
|
||||
<div class="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase font-semibold">Diajukan</p>
|
||||
<p class="text-gray-700 dark:text-gray-300">
|
||||
{{ $verification->created_at->format('d M Y') }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ $verification->created_at->format('H:i') }}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 uppercase font-semibold">Update Terakhir
|
||||
</p>
|
||||
<p class="text-gray-700 dark:text-gray-300">
|
||||
{{ $verification->updated_at->format('d M Y') }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ $verification->updated_at->diffForHumans() }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Latest Review Note --}}
|
||||
@if ($latestReview && $latestReview->note)
|
||||
<div
|
||||
class="p-3 rounded-lg border
|
||||
{{ match ($latestReview->decision) {
|
||||
\App\Enums\DecisionAdmin::APPROVED
|
||||
=> 'bg-success-50 dark:bg-success-500/10 border-success-200 dark:border-success-500/20',
|
||||
\App\Enums\DecisionAdmin::NEED_REVISION
|
||||
=> 'bg-warning-50 dark:bg-warning-500/10 border-warning-200 dark:border-warning-500/20',
|
||||
\App\Enums\DecisionAdmin::REJECTED
|
||||
=> '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',
|
||||
} }}">
|
||||
Catatan Terakhir
|
||||
</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 line-clamp-2">
|
||||
{{ $latestReview->note }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mt-1">
|
||||
oleh {{ $latestReview->reviewer?->name ?? 'Admin' }} •
|
||||
{{ $latestReview->created_at->diffForHumans() }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Review History Accordion --}}
|
||||
@if ($verification->reviews->count() > 0)
|
||||
<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-3 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 ({{ $verification->reviews->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 max-h-60 overflow-y-auto">
|
||||
@foreach ($verification->reviews as $review)
|
||||
<div class="p-3">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<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>
|
||||
|
||||
<x-slot name="footer">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
{{-- View Detail Button --}}
|
||||
@if ($detailUrl)
|
||||
<x-filament::button color="gray" size="sm" icon="heroicon-m-eye" tag="a"
|
||||
:href="$detailUrl" target="_blank">
|
||||
Detail
|
||||
</x-filament::button>
|
||||
@endif
|
||||
|
||||
{{-- Action Buttons based on status --}}
|
||||
@if ($verification->status === \App\Enums\VerificationStatus::PENDING)
|
||||
{{ ($this->approveAction)(['id' => $verification->id]) }}
|
||||
{{ ($this->revisionAction)(['id' => $verification->id]) }}
|
||||
{{ ($this->rejectAction)(['id' => $verification->id]) }}
|
||||
@elseif($verification->status === \App\Enums\VerificationStatus::NEED_REVISION)
|
||||
<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)
|
||||
<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)
|
||||
<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" />
|
||||
Pengajuan ditolak
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
</x-filament::section>
|
||||
@empty
|
||||
<div class="col-span-full">
|
||||
<div
|
||||
class="flex flex-col items-center justify-center py-16 text-gray-400 bg-gray-50 dark:bg-gray-800/50 rounded-xl border-2 border-dashed border-gray-200 dark:border-gray-700">
|
||||
<x-heroicon-o-inbox class="w-16 h-16 mb-4 opacity-50" />
|
||||
<p class="text-lg font-medium">Tidak ada pengajuan verifikasi</p>
|
||||
<p class="text-sm text-gray-500 mt-1">
|
||||
@if ($filterStatus === 'all')
|
||||
Belum ada pengajuan verifikasi dari user.
|
||||
@else
|
||||
Tidak ada pengajuan dengan status ini.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{-- Pagination --}}
|
||||
@if ($this->getVerifications()->hasPages())
|
||||
<div class="mt-6">
|
||||
{{ $this->getVerifications()->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<x-filament-actions::modals />
|
||||
</x-filament-panels::page>
|
||||
14
resources/views/filament/pages/verification.blade.php
Normal file
14
resources/views/filament/pages/verification.blade.php
Normal file
@ -0,0 +1,14 @@
|
||||
<x-filament-panels::page>
|
||||
<div class="space-y-6">
|
||||
{{-- Company Verification Section --}}
|
||||
<x-verification-card :progress="$companyProgress" :submit-action="$this->submitCompanyAction" type="company" />
|
||||
|
||||
{{-- Media Verification Section --}}
|
||||
<x-verification-card :progress="$mediaProgress" :submit-action="$this->submitMediaAction" type="media" />
|
||||
|
||||
{{-- Journalist Verification Section --}}
|
||||
<x-verification-card :progress="$journalistProgress" :submit-action="$this->submitJournalistAction" type="journalist" />
|
||||
</div>
|
||||
|
||||
<x-filament-actions::modals />
|
||||
</x-filament-panels::page>
|
||||
Loading…
Reference in New Issue
Block a user