feat: Implement company-based verification status for media and journalist data change requests, and refine admin verification UI/UX.

This commit is contained in:
Yoga Pangestu 2026-01-30 13:26:43 +07:00
parent 78246123c5
commit 9005f9cbec
14 changed files with 86 additions and 137 deletions

View File

@ -19,6 +19,7 @@
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Pages\Page;
use Filament\Support\Enums\Width;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Log;
use UnitEnum;
@ -186,13 +187,14 @@ public function revisionAction(): Action
Textarea::make('note')
->label('Catatan Revisi')
->required()
->placeholder('Jelaskan bagian yang perlu diperbaiki (Perusahaan, Media, atau Jurnalis)...')
->placeholder('Jelaskan bagian yang perlu diperbaiki...')
->autocomplete(false)
->autofocus(),
])
->action(function (array $arguments, array $data): void {
$this->processReview($arguments['id'], DecisionAdmin::NEED_REVISION, $data['note']);
});
})
->modalWidth(Width::Large);
}
/**
@ -292,8 +294,8 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin
]);
CheerfulNotification::danger(
'Belum ketemu nih 🙌',
'Data verifikasi belum kami temukan atau terjadi kesalahan sistem. Coba sebentar lagi, ya.',
'Terjadi Kesalahan ⚠️',
'Proses verifikasi gagal karena kesalahan sistem. Tim kami sudah mencatat masalah ini. Silakan coba lagi nanti.',
)
->send();
}

View File

@ -3,7 +3,6 @@
namespace App\Filament\Pages;
use App\Enums\DataChangeStatus;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
use App\Models\Company as CompanyModel;
@ -271,7 +270,7 @@ public function save(): void
public function saveAction(): Action
{
$needsReview = $this->company?->status === VerificationStatus::APPROVED;
$needsReview = $this->company?->verificationRequest?->status !== null;
return Action::make('save')
->label('Simpan')

View File

@ -6,7 +6,6 @@
use App\Enums\MediaClassification;
use App\Enums\MediaType;
use App\Enums\RoleEnum;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
use App\Models\Company;
@ -225,7 +224,7 @@ public function save(): void
public function saveAction(): Action
{
$needsReview = $this->media?->status === VerificationStatus::APPROVED;
$needsReview = $this->media?->company?->verificationRequest?->status !== null;
return Action::make('save')
->label('Simpan')
@ -251,7 +250,7 @@ public function saveAction(): Action
public function performSave(array $data = [], bool $needsReview = false): void
{
if ($needsReview && $this->media !== null) {
if ($needsReview) {
$existingRequest = DataChangeRequest::where('entity_type', PartnerMedia::class)
->where('entity_id', $this->media->id)
->where('status', DataChangeStatus::PENDING)

View File

@ -26,13 +26,15 @@ protected function setUp(): void
->modalSubmitActionLabel('Simpan')
->modalCancelActionLabel('Batal')
->successNotification(null)
->extraModalFooterActions(fn (CreateAction $action): array => [
->extraModalFooterActions(fn(CreateAction $action): array => [
$action->makeModalSubmitAction('createAnother', arguments: ['another' => true])
->label('Simpan dan Tambah Lagi'),
])
->modalWidth(Width::Large)
->using(function (array $data, string $model): Model {
$data['partner_media_id'] = auth()->user()->company?->partnerMedia?->id;
$user = auth()->user();
$data['partner_media_id'] = $user->company?->partnerMedia?->id;
$journalist = $model::create($data);
@ -68,7 +70,7 @@ protected function setUp(): void
}
// If company is already approved, addition of new journalist needs permohonan/review
if (auth()->user()->company?->status === VerificationStatus::APPROVED) {
if ($user->company?->verificationRequest !== null) {
$fieldLabels = Journalist::dataChangeEntryLabels();
$newFields = [];
@ -101,7 +103,7 @@ protected function setUp(): void
->each(function ($admin) use ($dataChangeRequest): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Penambahan Jurnalis Baru ✨',
'body' => 'Halooo Admin! 👋 '.auth()->user()->name.' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! 🚀',
'body' => 'Halooo Admin! 👋 ' . $user->name . ' baru saja menambahkan jurnalis baru. Yuk, cek detailnya! 🚀',
'action' => [
Action::make('view')
->label('Lihat')

View File

@ -23,9 +23,9 @@ protected function setUp(): void
$this->successNotification(null);
$this->modalHeading(fn () => auth()->user()->company?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis');
$this->modalHeading(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? 'Ajukan Penghapusan Jurnalis' : 'Hapus Jurnalis');
$this->schema(fn () => auth()->user()->company?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) ? [
$this->schema(fn() => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED ? [
Textarea::make('deletion_reason')
->label('Alasan Penghapusan')
->placeholder('Jelaskan alasan mengapa jurnalis ini dihapus...')
@ -34,10 +34,8 @@ protected function setUp(): void
$this->action(function (Journalist $record, array $data): void {
$user = auth()->user();
$needsReview = $user->company?->status === VerificationStatus::APPROVED &&
$user->hasRole(RoleEnum::PERUSAHAAN->value);
if ($needsReview) {
if ($user->company?->verificationRequest !== null) {
$existingRequest = DataChangeRequest::where('entity_type', Journalist::class)
->where('entity_id', $record->id)
->where('status', DataChangeStatus::PENDING)
@ -71,7 +69,7 @@ protected function setUp(): void
->each(function ($admin) use ($user, $record, $dataChangeRequest): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Pengajuan Penghapusan Jurnalis ✨',
'body' => 'Halooo Admin! 👋 '.$user->name.' baru saja mengajukan penghapusan jurnalis '.$record->name.'. Yuk, cek detailnya! 🚀',
'body' => 'Halooo Admin! 👋 ' . $user->name . ' baru saja mengajukan penghapusan jurnalis ' . $record->name . '. Yuk, cek detailnya! 🚀',
'action' => [
Action::make('view')
->label('Lihat')

View File

@ -50,10 +50,9 @@ protected function setUp(): void
return $data;
})
->using(function (Journalist $record, array $data): Journalist {
$needsReview = auth()->user()->company?->status === VerificationStatus::APPROVED &&
auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value);
$user = auth()->user();
if ($needsReview) {
if ($user->company?->verificationRequest !== null) {
$existingRequest = DataChangeRequest::where('entity_type', Journalist::class)
->where('entity_id', $record->id)
->where('status', DataChangeStatus::PENDING)
@ -67,69 +66,67 @@ protected function setUp(): void
return $record;
}
}
$fieldLabels = Journalist::dataChangeEntryLabels();
$editableFields = ['name', 'email', 'phone_number', 'press_card', 'ukw_certificate'];
$fieldLabels = Journalist::dataChangeEntryLabels();
$editableFields = ['name', 'email', 'phone_number', 'press_card', 'ukw_certificate'];
$changedFields = [];
$oldDataArr = [];
$changedFields = [];
$oldDataArr = [];
$casts = $record->getCasts();
foreach ($editableFields as $field) {
$val = $data[$field] ?? null;
$oldVal = $record->{$field};
$casts = $record->getCasts();
foreach ($editableFields as $field) {
$val = $data[$field] ?? null;
$oldVal = $record->{$field};
// If value is a raw scalar but should be an enum, resolve it for display
if (isset($casts[$field]) && ! ($val instanceof \BackedEnum) && enum_exists($casts[$field])) {
$enumClass = $casts[$field];
$val = $enumClass::tryFrom($val) ?? $val;
// If value is a raw scalar but should be an enum, resolve it for display
if (isset($casts[$field]) && ! ($val instanceof \BackedEnum) && enum_exists($casts[$field])) {
$enumClass = $casts[$field];
$val = $enumClass::tryFrom($val) ?? $val;
}
$displayVal = ($val instanceof HasLabel) ? $val->getLabel() : ($val instanceof \BackedEnum ? $val->value : $val);
$displayOldVal = ($oldVal instanceof HasLabel) ? $oldVal->getLabel() : ($oldVal instanceof \BackedEnum ? $oldVal->value : $oldVal);
if ($displayOldVal !== $displayVal) {
$label = $fieldLabels[$field] ?? $field;
$changedFields[$label] = $displayVal;
$oldDataArr[$label] = $displayOldVal;
}
}
$displayVal = ($val instanceof HasLabel) ? $val->getLabel() : ($val instanceof \BackedEnum ? $val->value : $val);
$displayOldVal = ($oldVal instanceof HasLabel) ? $oldVal->getLabel() : ($oldVal instanceof \BackedEnum ? $oldVal->value : $oldVal);
$documentFields = ['press_card_docs', 'ukw_certificate_docs'];
$mediaItems = $record->getMedia('journalists');
if ($displayOldVal !== $displayVal) {
$label = $fieldLabels[$field] ?? $field;
$changedFields[$label] = $displayVal;
$oldDataArr[$label] = $displayOldVal;
foreach ($documentFields as $field) {
$newDocs = array_values((array) ($data[$field] ?? []));
sort($newDocs);
$docType = str($field)->replace('_docs', '')->slug('-');
$currentMedia = $mediaItems
->where('custom_properties.doc_type', $docType)
->sortByDesc('created_at')
->first();
$oldDocs = $currentMedia ? [$currentMedia->getPathRelativeToRoot()] : [];
$oldDocs = array_values($oldDocs);
sort($oldDocs);
if (json_encode($newDocs) !== json_encode($oldDocs)) {
$label = $fieldLabels[$field] ?? $field;
$changedFields[$label] = $newDocs;
$oldDataArr[$label] = $oldDocs;
}
}
}
$documentFields = ['press_card_docs', 'ukw_certificate_docs'];
$mediaItems = $record->getMedia('journalists');
if (empty($changedFields)) {
CheerfulNotification::info(
'Belum ada yang berubah ✨',
'Ubah data terlebih dulu lalu simpan kembali 💪'
)->send();
foreach ($documentFields as $field) {
$newDocs = array_values((array) ($data[$field] ?? []));
sort($newDocs);
$docType = str($field)->replace('_docs', '')->slug('-');
$currentMedia = $mediaItems
->where('custom_properties.doc_type', $docType)
->sortByDesc('created_at')
->first();
$oldDocs = $currentMedia ? [$currentMedia->getPathRelativeToRoot()] : [];
$oldDocs = array_values($oldDocs);
sort($oldDocs);
if (json_encode($newDocs) !== json_encode($oldDocs)) {
$label = $fieldLabels[$field] ?? $field;
$changedFields[$label] = $newDocs;
$oldDataArr[$label] = $oldDocs;
return $record;
}
}
if (empty($changedFields)) {
CheerfulNotification::info(
'Belum ada yang berubah ✨',
'Ubah data terlebih dulu lalu simpan kembali 💪'
)->send();
return $record;
}
if ($needsReview) {
$dataChangeRequest = DataChangeRequest::create([
'user_id' => auth()->id(),
'entity_type' => Journalist::class,
@ -149,7 +146,7 @@ protected function setUp(): void
->each(function ($admin) use ($dataChangeRequest): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Pengajuan Perubahan Jurnalis ✨',
'body' => 'Halooo Admin! 👋 '.auth()->user()->name.' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! 🚀',
'body' => 'Halooo Admin! 👋 ' . $user->name . ' baru saja mengajukan perubahan data jurnalis. Yuk, cek detailnya! 🚀',
'action' => [
Action::make('view')
->label('Lihat')
@ -163,7 +160,7 @@ protected function setUp(): void
$this->performSave($record, $data);
CheerfulNotification::update()->send(); // Manual success notification
CheerfulNotification::update()->send();
return $record;
})
@ -198,7 +195,7 @@ private function performSave(Journalist $record, array $data): void
];
$existingMedia = $record->getMedia('journalists');
$existingPaths = $existingMedia->map(fn ($m) => $m->getPathRelativeToRoot())->toArray();
$existingPaths = $existingMedia->map(fn($m) => $m->getPathRelativeToRoot())->toArray();
foreach ($documents as $field) {
if (empty($data[$field])) {

View File

@ -49,17 +49,6 @@ class JournalistResource extends Resource
protected static ?string $slug = 'manage/journalists';
public static function getNavigationBadge(): ?string
{
return static::getModel()::pending()
->when(auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value), function (Builder $query): void {
$query->whereHas('partnerMedia.company', function (Builder $q): void {
$q->where('user_id', auth()->id());
});
})
->count() ?: null;
}
public static function form(Schema $schema): Schema
{
$documents = [

View File

@ -112,7 +112,7 @@ public static function infolist(Schema $schema): Schema
->first();
if (! $media) {
return 'Tidak ada dokumen';
return '';
}
$url = $media->getUrl();
@ -298,9 +298,19 @@ public static function table(Table $table): Table
->sortable()
->badge(),
TextColumn::make('company.status')
TextColumn::make('status')
->label('Status')
->badge(),
->badge()
->getStateUsing(function ($record) {
return $record->company->verificationRequest
? $record->company->verificationRequest->status->getLabel()
: 'Belum Mengajukan';
})
->color(function ($record) {
return $record->company->verificationRequest
? $record->company->verificationRequest->status->getColor()
: 'gray';
}),
TextColumn::make('created_at')
->label('Tgl Daftar')

View File

@ -2,7 +2,6 @@
namespace App\Models;
use App\Enums\VerificationStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -19,13 +18,6 @@ class Company extends Model implements HasMedia
protected $guarded = ['id'];
protected function casts(): array
{
return [
'status' => VerificationStatus::class,
];
}
public function journalists(): HasManyThrough
{
return $this->hasManyThrough(Journalist::class, PartnerMedia::class);

View File

@ -17,13 +17,6 @@ class Journalist extends Model implements HasMedia
protected $guarded = ['id'];
protected function casts(): array
{
return [
'status' => VerificationStatus::class,
];
}
public function partnerMedia(): BelongsTo
{
return $this->belongsTo(PartnerMedia::class);

View File

@ -5,7 +5,6 @@
use App\Enums\ApprovalStatus;
use App\Enums\MediaClassification;
use App\Enums\MediaType;
use App\Enums\VerificationStatus;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@ -29,34 +28,9 @@ protected function casts(): array
return [
'type' => MediaType::class,
'classification' => MediaClassification::class,
'status' => VerificationStatus::class,
];
}
#[Scope]
protected function pending(Builder $query): void
{
$query->where('status', VerificationStatus::PENDING);
}
#[Scope]
protected function approved(Builder $query): void
{
$query->where('status', VerificationStatus::APPROVED);
}
#[Scope]
protected function needRevision(Builder $query): void
{
$query->where('status', VerificationStatus::NEED_REVISION);
}
#[Scope]
protected function rejected(Builder $query): void
{
$query->where('status', VerificationStatus::REJECTED);
}
#[Scope]
protected function verified(Builder $query): void
{

View File

@ -1,6 +1,5 @@
<?php
use App\Enums\VerificationStatus;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
@ -28,7 +27,6 @@ public function up(): void
$table->string('annual_tax_return', 100);
$table->string('domicile_certificate', 100);
$table->string('profile', 100);
$table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment());
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();

View File

@ -2,7 +2,6 @@
use App\Enums\MediaClassification;
use App\Enums\MediaType;
use App\Enums\VerificationStatus;
use App\Models\Company;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
@ -25,7 +24,6 @@ public function up(): void
$table->enum('classification', [MediaClassification::values()])->comment(MediaClassification::comment());
$table->string('journalism_organization')->nullable();
$table->string('press_council_certificate')->nullable();
$table->enum('status', VerificationStatus::cases())->default(VerificationStatus::PENDING)->comment(VerificationStatus::comment());
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();

View File

@ -1,6 +1,5 @@
<?php
use App\Enums\VerificationStatus;
use App\Models\PartnerMedia;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
@ -21,7 +20,6 @@ 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());
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();