Merge branch 'dev' into 'main'

refactor: Adjust password field to set a default value only on creation and be...

See merge request pratama/purwakarta/diskominfo/simedkom!3
This commit is contained in:
Yoga Pangestu 2026-02-02 11:58:48 +07:00
commit f781741a3d
70 changed files with 978 additions and 832 deletions

View File

@ -34,7 +34,7 @@ public function getColor(): string|array|null
self::PENDING => 'warning',
self::ASSIGNMENT => 'info',
self::VERIFICATION => 'primary',
self::PAYMENT => 'secondary',
self::PAYMENT => 'slate',
self::COMPLETED => 'success',
};
}

View File

@ -8,8 +8,8 @@
use App\Filament\Support\CheerfulNotification;
use App\Models\User;
use App\Models\VerificationRequest;
use App\Models\VerificationReview;
use App\Notifications\BroadcastNotification;
use App\Services\VerificationService;
use BackedEnum;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Filament\Actions\Action;
@ -19,7 +19,9 @@
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\DB;
use Illuminate\Support\Facades\Log;
use UnitEnum;
@ -41,15 +43,8 @@ class AdminVerification extends Page implements HasActions, HasForms
protected static ?string $title = 'Verifikasi Rekanan';
protected VerificationService $verificationService;
protected string $view = 'filament.pages.admin-verification';
public function boot(): void
{
$this->verificationService = app(VerificationService::class);
}
public static function getNavigationBadge(): ?string
{
return VerificationRequest::query()->pending()->count();
@ -95,7 +90,7 @@ public function getStatusCounts(): array
*/
public function getCompanyName(VerificationRequest $request): string
{
return $this->verificationService->getCompanyName($request);
return $request->company?->name ?? 'Data tidak ditemukan';
}
/**
@ -115,7 +110,27 @@ public function getDetailUrl(VerificationRequest $request): ?string
*/
public function getVerificationSummary(VerificationRequest $request): array
{
return $this->verificationService->getVerificationSummary($request);
$company = $request->company;
$media = $company?->partnerMedia;
$journalists = $media?->journalists ?? collect();
return [
'company' => [
'name' => $company?->name ?? 'Belum diisi',
'email' => $company?->email ?? '-',
'exists' => $company !== null,
],
'media' => [
'name' => $media?->name ?? 'Belum diisi',
'type' => $media?->type?->getLabel() ?? '-',
'exists' => $media !== null,
],
'journalists' => [
'count' => $journalists->count(),
'names' => $journalists->pluck('name')->take(3)->toArray(),
'exists' => $journalists->count() > 0,
],
];
}
/**
@ -186,13 +201,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);
}
/**
@ -240,7 +256,28 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin
}
try {
$this->verificationService->processReview($verificationRequest, $decision, $note);
DB::beginTransaction();
// Create review record
VerificationReview::create([
'verification_request_id' => $verificationRequest->id,
'reviewer_id' => auth()->id(),
'decision' => $decision,
'note' => $note,
]);
// Update verification request status
$newStatus = match ($decision) {
DecisionAdmin::APPROVED => VerificationStatus::APPROVED,
DecisionAdmin::NEED_REVISION => VerificationStatus::NEED_REVISION,
DecisionAdmin::REJECTED => VerificationStatus::REJECTED,
};
$verificationRequest->update([
'status' => $newStatus,
]);
DB::commit();
$actionLabel = match ($decision) {
DecisionAdmin::APPROVED => 'disetujui',
@ -284,6 +321,7 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin
]));
}
} catch (\Throwable $e) {
DB::rollBack();
Log::error('Verification processing failed', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
@ -292,8 +330,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

@ -91,6 +91,15 @@ public function authenticate(): ?LoginResponse
$this->throwFailureValidationException();
}
if ($user instanceof FilamentUser && (! $user->canAccessPanel(Filament::getCurrentOrDefaultPanel()))) {
CheerfulNotification::danger(
'Akun Tidak Aktif 🚫',
'Maaf, akun Anda saat ini tidak aktif. Silakan hubungi Administrator. 📞'
)->send();
throw ValidationException::withMessages([]);
}
if (
filled($this->userUndertakingMultiFactorAuthentication) &&
(decrypt($this->userUndertakingMultiFactorAuthentication) === $user->getAuthIdentifier())

View File

@ -38,6 +38,8 @@ class Company extends Page implements HasForms
public array $originalDocuments = [];
public bool $isVerificationStage = false;
protected static string|UnitEnum|null $navigationGroup = 'Kelola';
protected static string|BackedEnum|null $navigationIcon = Heroicon::BuildingOffice2;
@ -64,6 +66,11 @@ public function mount(): void
return;
}
$this->isVerificationStage = in_array($company->verificationRequest?->status, [
VerificationStatus::PENDING,
VerificationStatus::REJECTED,
]) || $company->verificationRequest()->pending()->exists();
$this->verificationRequest = $company->verificationRequest()
->latest()
->first();
@ -271,7 +278,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')
@ -284,7 +291,8 @@ public function saveAction(): Action
->label('Alasan')
->placeholder('Jelaskan alasan perubahan data ini...')
->autocomplete(false)
->autofocus(),
->autofocus()
->required(),
]
: []
)

View File

@ -59,6 +59,8 @@ class Media extends Page implements HasForms
public bool $restricted = false;
public bool $isVerificationStage = false;
protected static ?string $slug = 'manage/media';
protected static ?string $title = 'Media';
@ -85,6 +87,11 @@ public function mount(): void
$this->company = $company;
$this->isVerificationStage = in_array($company?->verificationRequest?->status, [
VerificationStatus::PENDING,
VerificationStatus::REJECTED,
]) || $company->verificationRequest()->pending()->exists();
$verificationRequest = $this->company->verificationRequest()
->latest()
->first();
@ -225,7 +232,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')
@ -236,10 +243,10 @@ public function saveAction(): Action
? [
Textarea::make('change_reason')
->label('Alasan')
->required()
->placeholder('Jelaskan alasan perubahan data ini...')
->autocomplete(false)
->autofocus(),
->autofocus()
->required(),
]
: []
)
@ -251,7 +258,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

@ -2,6 +2,7 @@
namespace App\Filament\Pages\Settings;
use App\Filament\Support\CheerfulNotification;
use App\Settings\GeneralSettings;
use BackedEnum;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
@ -9,6 +10,7 @@
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\SettingsPage;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
@ -98,4 +100,9 @@ public function form(Schema $schema): Schema
])
->columns(3);
}
public function getSavedNotification(): ?Notification
{
return CheerfulNotification::update()->send();
}
}

View File

@ -2,11 +2,13 @@
namespace App\Filament\Pages\Settings;
use App\Filament\Support\CheerfulNotification;
use App\Settings\SeoSettings;
use BackedEnum;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\SettingsPage;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
@ -58,4 +60,9 @@ public function form(Schema $schema): Schema
->columnSpanFull(),
]);
}
public function getSavedNotification(): ?Notification
{
return CheerfulNotification::update()->send();
}
}

View File

@ -2,10 +2,12 @@
namespace App\Filament\Pages\Settings;
use App\Filament\Support\CheerfulNotification;
use App\Settings\SocialMediaSettings;
use BackedEnum;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
use Filament\Pages\SettingsPage;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
@ -67,4 +69,9 @@ public function form(Schema $schema): Schema
->columnSpanFull(),
]);
}
public function getSavedNotification(): ?Notification
{
return CheerfulNotification::update()->send();
}
}

View File

@ -3,12 +3,13 @@
namespace App\Filament\Pages;
use App\Enums\VerificationStatus;
use App\Filament\Pages\Company as PagesCompany;
use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists;
use App\Filament\Resources\Manage\Partners\PartnerResource;
use App\Models\Company;
use App\Models\User;
use App\Models\VerificationRequest;
use App\Notifications\BroadcastNotification;
use App\Services\VerificationService;
use BackedEnum;
use BezhanSalleh\FilamentShield\Traits\HasPageShield;
use Filament\Actions\Action;
@ -19,6 +20,7 @@
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Filament\Support\Icons\Heroicon;
use Illuminate\Support\Collection;
use UnitEnum;
class Verification extends Page implements HasActions, HasForms
@ -41,15 +43,8 @@ class Verification extends Page implements HasActions, HasForms
public ?VerificationRequest $verificationRequest = null;
protected VerificationService $verificationService;
protected string $view = 'filament.pages.verification';
public function boot(): void
{
$this->verificationService = app(VerificationService::class);
}
public function mount(): void
{
$user = auth()->user();
@ -69,14 +64,35 @@ public function mount(): void
*/
public function getVerificationProgress(): array
{
$progress = $this->verificationService->getVerificationProgress(
$this->company,
$this->verificationRequest
);
$isComplete = $this->isDataComplete();
$missingData = $this->getMissingData();
$status = $this->verificationRequest?->status;
$progress = [
'currentStep' => $this->resolveStep(),
'isDataComplete' => $isComplete,
'missingData' => $missingData,
'canSubmit' => $this->canSubmit(),
'verification' => $this->verificationRequest,
'status' => [
'label' => $status?->getLabel() ?? 'Belum Mengajukan',
'color' => $status?->getColor() ?? 'gray',
],
'isApproved' => $status === VerificationStatus::APPROVED,
'isRejected' => $status === VerificationStatus::REJECTED,
'isPending' => $status === VerificationStatus::PENDING,
'needsRevision' => $status === VerificationStatus::NEED_REVISION,
'latestNotes' => $this->getLatestReviewNotes(),
'reviewHistory' => $this->getReviewHistory(),
'company' => $this->company,
'partnerMedia' => $this->company?->partnerMedia,
'journalists' => $this->company?->partnerMedia?->journalists ?? collect(),
'steps' => $this->getSteps($isComplete),
];
// Add additional data for view
$progress['missingDataButtons'] = $this->verificationService->getMissingDataButtons($this->company);
$progress['alert'] = $this->verificationService->getAlertData($this->verificationRequest);
$progress['missingDataButtons'] = $this->getMissingDataButtons();
$progress['alert'] = $this->getAlertData();
$progress['lastSubmittedAt'] = $this->verificationRequest?->updated_at?->translatedFormat('l, d M Y H:i');
return $progress;
@ -120,10 +136,7 @@ public function submitVerificationAction(): Action
$isRevision = $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION;
$this->verificationRequest = $this->verificationService->submitVerification(
$this->company,
$this->verificationRequest
);
$this->verificationRequest = $this->submitVerification();
Notification::make()
->title('Berhasil Diajukan! 🚀')
@ -151,6 +164,261 @@ public function submitVerificationAction(): Action
});
}
/**
* Check if all required data is complete for verification
*/
protected function isDataComplete(): bool
{
return $this->company !== null
&& $this->company->partnerMedia !== null
&& $this->company->partnerMedia?->journalists !== null
&& $this->company->partnerMedia->journalists->count() > 0;
}
/**
* Get missing data items for verification
*/
protected function getMissingData(): array
{
$missing = [];
if (! $this->company) {
$missing[] = 'Data Perusahaan';
}
if ($this->company && ! $this->company->partnerMedia) {
$missing[] = 'Data Media';
}
if ($this->company && $this->company->partnerMedia && ($this->company->partnerMedia?->journalists === null || $this->company->partnerMedia?->journalists?->count() === 0)) {
$missing[] = 'Data Jurnalis (minimal 1)';
}
return $missing;
}
/**
* Check if user can submit verification
*/
protected function canSubmit(): bool
{
if (! $this->isDataComplete()) {
return false;
}
// No verification yet - can submit
if (! $this->verificationRequest) {
return true;
}
// Can resubmit only if status is NEED_REVISION
return $this->verificationRequest->status === VerificationStatus::NEED_REVISION;
}
/**
* Determine the current step based on status
*/
protected function resolveStep(): int
{
if (! $this->isDataComplete()) {
return 1; // Data belum lengkap
}
if (! $this->verificationRequest) {
return 1; // Belum submit
}
return match ($this->verificationRequest->status) {
VerificationStatus::PENDING,
VerificationStatus::NEED_REVISION => 2, // In review or needs revision
VerificationStatus::APPROVED,
VerificationStatus::REJECTED => 3, // Final decision
default => 1, // Draft / not submitted yet
};
}
/**
* Get the latest review notes
*/
protected function getLatestReviewNotes(): ?string
{
if (! $this->verificationRequest) {
return null;
}
$latestReview = $this->verificationRequest->reviews()
->where(fn ($q) => $q->needRevision()->orWhere(fn ($q) => $q->rejected()))
->latest()
->first();
return $latestReview?->note;
}
/**
* Get all reviews history
*/
protected function getReviewHistory(): Collection
{
if (! $this->verificationRequest) {
return collect();
}
return $this->verificationRequest->reviews()
->with('reviewer')
->latest()
->get();
}
/**
* Submit unified verification
*/
protected function submitVerification(): VerificationRequest
{
if ($this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION) {
// Resubmit - update existing request
$this->verificationRequest->update([
'status' => VerificationStatus::PENDING,
]);
return $this->verificationRequest->fresh(['reviews.reviewer']);
}
// New submission
return VerificationRequest::create([
'company_id' => $this->company->id,
'submitted_by' => auth()->id(),
'status' => VerificationStatus::PENDING,
])->load(['reviews.reviewer']);
}
/**
* Get alert data for verification status
*/
protected function getAlertData(): ?array
{
if (! $this->verificationRequest) {
return null;
}
$status = $this->verificationRequest->status;
$latestNotes = $this->getLatestReviewNotes();
if ($status === VerificationStatus::NEED_REVISION && $latestNotes) {
return [
'type' => 'revision',
'icon' => 'exclamation-triangle',
'title' => 'Catatan Revisi dari Admin',
'message' => $latestNotes,
'color' => 'warning',
];
}
if ($status === VerificationStatus::REJECTED && $latestNotes) {
return [
'type' => 'rejected',
'icon' => 'x-circle',
'title' => 'Pengajuan Ditolak',
'message' => $latestNotes,
'color' => 'danger',
];
}
if ($status === VerificationStatus::APPROVED) {
return [
'type' => 'approved',
'icon' => 'check-circle',
'title' => 'Verifikasi Diterima',
'message' => 'Verifikasi Anda telah diterima dan disetujui oleh admin. Sekarang Anda sudah resmi terverifikasi dan bekerja sama dengan kami!',
'color' => 'success',
];
}
if ($status === VerificationStatus::PENDING) {
return [
'type' => 'pending',
'icon' => 'clock',
'title' => 'Menunggu Verifikasi',
'message' => 'Data Anda sedang dalam proses verifikasi oleh admin. Mohon tunggu.',
'color' => 'info',
'animate' => true,
];
}
return null;
}
/**
* Get missing data buttons configuration
*/
protected function getMissingDataButtons(): array
{
$buttons = [];
if (! $this->company) {
$buttons[] = [
'label' => 'Lengkapi Data Perusahaan',
'url' => PagesCompany::getUrl(),
'icon' => 'building-office-2',
'color' => 'warning',
];
}
if ($this->company && ! $this->company->partnerMedia) {
$buttons[] = [
'label' => 'Lengkapi Data Media',
'url' => Media::getUrl(),
'icon' => 'newspaper',
'color' => 'warning',
];
}
if ($this->company && $this->company->partnerMedia && ($this->company->partnerMedia->journalists === null || $this->company->partnerMedia->journalists->count() === 0)) {
$buttons[] = [
'label' => 'Lengkapi Data Jurnalis',
'url' => ManageJournalists::getUrl(),
'icon' => 'user-circle',
'color' => 'warning',
];
}
return $buttons;
}
/**
* Get steps array for stepper
*/
protected function getSteps(bool $isComplete): array
{
$status = $this->verificationRequest?->status;
return [
[
'title' => 'Pengajuan',
'description' => $isComplete
? 'Data sudah lengkap, silakan ajukan verifikasi'
: 'Silakan lengkapi data',
],
[
'title' => 'Verifikasi',
'description' => match ($status) {
VerificationStatus::NEED_REVISION => 'Perlu perbaikan data',
VerificationStatus::PENDING => 'Sedang diverifikasi admin',
default => 'Menunggu pengajuan',
},
],
[
'title' => 'Selesai',
'description' => match ($status) {
VerificationStatus::APPROVED => 'Verifikasi disetujui',
VerificationStatus::REJECTED => 'Verifikasi ditolak',
default => 'Menunggu keputusan',
},
],
];
}
public function getViewData(): array
{
return [

View File

@ -54,7 +54,7 @@ protected function setUp(): void
$record->update([
'payment_amount' => $totalPayment,
'payment_date' => now(),
'completed_at' => now(),
'status' => CooperationStatus::COMPLETED,
]);

View File

@ -23,7 +23,10 @@ protected function setUp(): void
->modalHeading('Lanjutkan ke Pembayaran?')
->modalDescription('Apakah Anda yakin ingin melanjutkan kerja sama ini ke tahap pembayaran? Pastikan semua laporan telah diverifikasi.')
->action(function (Cooperation $record): void {
$record->update(['status' => CooperationStatus::PAYMENT]);
$record->update([
'status' => CooperationStatus::PAYMENT,
'payment_date' => now()->toDateString(),
]);
CheerfulNotification::success(
'Tahap Pembayaran Dimulai! 💸✨',

View File

@ -34,7 +34,9 @@ protected function setUp(): void
->autocomplete(false)
->autofocus()
->nullable()
->url(),
->url()
->required()
->maxLength(200),
Textarea::make('description')
->label('Deskripsi')

View File

@ -4,8 +4,10 @@
use App\Enums\ApprovalStatus;
use App\Enums\CooperationStatus;
use App\Filament\Resources\Manage\Cooperations\CooperationResource;
use App\Filament\Support\CheerfulNotification;
use App\Models\CooperationMedia;
use App\Notifications\BroadcastNotification;
use Filament\Actions\Action;
use Filament\Forms\Components\SpatieMediaLibraryFileUpload;
use Filament\Forms\Components\Textarea;
@ -64,6 +66,19 @@ protected function setUp(): void
'Pembayaran Berhasil! 💸✨',
"Pembayaran untuk {$record->partnerMedia->name} telah berhasil dicatat."
)->send();
$partnerUser = $record->partnerMedia->company?->user;
if ($partnerUser) {
$partnerUser->notify(new BroadcastNotification([
'title' => 'Pembayaran Telah Dikirim! 💸✨',
'body' => "Halo! Pembayaran untuk kerja sama \"{$record->cooperation->title}\" telah berhasil dikirim oleh Admin. Silakan cek detailnya ya! 😊",
'action' => [
Action::make('view')
->label('Lihat')
->url(CooperationResource::getUrl('view', ['record' => $record->cooperation_id, 'relation' => 0])),
],
]));
}
})
->modalWidth(Width::Large);
}

View File

@ -32,28 +32,21 @@ protected function setUp(): void
->success()
->send();
// Notify all partners in this cooperation (since reports might be per media even if shared in DB)
$partners = $record->taskAssignment?->cooperation?->partnerMedia()
->wherePivot('status', ApprovalStatus::ACCEPTED)
->get();
foreach ($partners as $media) {
$partnerUser = $media->company?->user;
if ($partnerUser) {
$partnerUser->notify(new BroadcastNotification([
'title' => 'Mantap! Laporan Diterima 🎊✨',
'body' => "Hore! Laporan \"{$record->title}\" pada kerja sama \"{$record->taskAssignment->cooperation->title}\" telah disetujui oleh Admin. Satu langkah lebih dekat! 🚀",
'action' => [
Action::make('view')
->label('Lihat')
->url(CooperationResource::getUrl('view', ['record' => $record->taskAssignment->cooperation_id, 'relation' => 3])),
],
]));
}
$partnerUser = $record->mediaTaskAssignment?->partnerMedia?->company?->user;
if ($partnerUser) {
$partnerUser->notify(new BroadcastNotification([
'title' => 'Mantap! Laporan Diterima 🎊✨',
'body' => "Hore! Laporan \"{$record->title}\" pada kerja sama \"{$record->mediaTaskAssignment->taskAssignment->cooperation->title}\" telah disetujui oleh Admin. Satu langkah lebih dekat! 🚀",
'action' => [
Action::make('view')
->label('Lihat')
->url(CooperationResource::getUrl('view', ['record' => $record->mediaTaskAssignment->taskAssignment->cooperation_id, 'relation' => 3])),
],
]));
}
})
->visible(
fn (Report $record): bool => $record->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION &&
fn (Report $record): bool => $record->mediaTaskAssignment?->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION &&
! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) &&
$record->status === ApprovalStatus::PENDING
);

View File

@ -42,28 +42,21 @@ protected function setUp(): void
->warning()
->send();
// Notify all partners in this cooperation (since we can't tell which specific one)
$partners = $record->taskAssignment?->cooperation?->partnerMedia()
->wherePivot('status', ApprovalStatus::ACCEPTED)
->get();
foreach ($partners as $media) {
$partnerUser = $media->company?->user;
if ($partnerUser) {
$partnerUser->notify(new BroadcastNotification([
'title' => 'Yah, Laporan Perlu Diperbaiki nih 📝',
'body' => "Halo! Laporan \"{$record->title}\" pada kerja sama \"{$record->taskAssignment->cooperation->title}\" ditolak oleh Admin. 💪\n\nCatatan Admin: {$data['reason']}",
'action' => [
Action::make('view')
->label('Lihat')
->url(CooperationResource::getUrl('view', ['record' => $record->taskAssignment->cooperation_id])),
],
]));
}
$partnerUser = $record->mediaTaskAssignment?->partnerMedia?->company?->user;
if ($partnerUser) {
$partnerUser->notify(new BroadcastNotification([
'title' => 'Yah, Laporan Ditolak 🙁',
'body' => "Halo! Laporan \"{$record->title}\" pada kerja sama \"{$record->mediaTaskAssignment->taskAssignment->cooperation->title}\" ditolak oleh Admin. 💪\n\nCatatan Admin: {$data['reason']}",
'action' => [
Action::make('view')
->label('Lihat')
->url(CooperationResource::getUrl('view', ['record' => $record->mediaTaskAssignment->taskAssignment->cooperation_id])),
],
]));
}
})
->visible(
fn (Report $record): bool => $record->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION &&
fn (Report $record): bool => $record->mediaTaskAssignment?->taskAssignment?->cooperation?->status === CooperationStatus::VERIFICATION &&
! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value) &&
$record->status === ApprovalStatus::PENDING
)

View File

@ -8,6 +8,7 @@
use App\Filament\Resources\Manage\Cooperations\Pages\ListCooperations;
use App\Filament\Resources\Manage\Cooperations\Pages\ViewCooperation;
use App\Filament\Resources\Manage\Cooperations\RelationManagers\CooperationMediaRelationManager;
use App\Filament\Resources\Manage\Cooperations\RelationManagers\CooperationPaymentRelationManager;
use App\Filament\Resources\Manage\Cooperations\RelationManagers\CooperationProposalRelationManager;
use App\Filament\Resources\Manage\Cooperations\RelationManagers\ReportRelationManager;
use App\Filament\Resources\Manage\Cooperations\RelationManagers\TaskAssignmentRelationManager;
@ -120,6 +121,11 @@ public static function infolist(Schema $schema): Schema
->label('Tanggal Pembayaran')
->date('l, d F Y')
->placeholder('Belum dibayar'),
TextEntry::make('completed_at')
->label('Tanggal Selesai')
->date('l, d F Y')
->placeholder('Masih berjalan'),
]),
Grid::make(3)
@ -153,6 +159,7 @@ public static function getRelations(): array
CooperationProposalRelationManager::class,
TaskAssignmentRelationManager::class,
ReportRelationManager::class,
CooperationPaymentRelationManager::class,
];
}

View File

@ -12,19 +12,22 @@
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class CooperationMediaRelationManager extends RelationManager
{
protected static ?string $recordTitleAttribute = 'partnerMedia.name';
protected static string $relationship = 'cooperationMedia';
protected static ?string $title = 'Media';
public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
return auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value);
return ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value);
}
public static function getBadge(Model $ownerRecord, string $pageClass): ?string
@ -57,7 +60,9 @@ public function table(Table $table): Table
ViewAction::make()
->visible(fn (CooperationMedia $record): bool => $record->status === ApprovalStatus::ACCEPTED && $record->payment()->exists()),
])
->recordAction(null);
->emptyStateIcon(Heroicon::Newspaper)
->emptyStateDescription('Tidak ada media yang diikutsertakan.')
->defaultSort('created_at', 'desc');
}
public function infolist(Schema $infolist): Schema
@ -68,11 +73,11 @@ public function infolist(Schema $infolist): Schema
->schema([
TextEntry::make('payment.amount')
->label('Nominal')
->money('IDR'),
->money('IDR', decimalPlaces: 0),
TextEntry::make('payment.payment_date')
->label('Tanggal Bayar')
->dateTime(),
->dateTime('l, d F Y'),
]),
TextEntry::make('payment.description')

View File

@ -0,0 +1,74 @@
<?php
namespace App\Filament\Resources\Manage\Cooperations\RelationManagers;
use App\Enums\ApprovalStatus;
use App\Enums\RoleEnum;
use App\Models\CooperationPayment;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\ImageColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
class CooperationPaymentRelationManager extends RelationManager
{
protected static ?string $recordTitleAttribute = 'description';
protected static string $relationship = 'payments';
protected static ?string $title = 'Pembayaran';
public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
if (! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) {
return false;
}
return $ownerRecord->cooperationMedia()
->whereHas('partnerMedia', function ($query) {
$query->whereHas('company', function ($query) {
$query->where('user_id', auth()->id());
});
})
->where('status', ApprovalStatus::ACCEPTED)
->exists();
}
public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('amount')
->label('Nominal')
->money('IDR', decimalPlaces: 0),
TextColumn::make('payment_date')
->label('Tanggal Bayar')
->date('l, d F Y'),
TextColumn::make('description')
->label('Keterangan')
->limit(50),
ImageColumn::make('payment_proof')
->label('Bukti Pembayaran')
->getStateUsing(fn (CooperationPayment $record): ?string => optional($record->cooperationMedia->getMedia('cooperations')->where('custom_properties.doc_type', 'payment-proof')->sortByDesc('created_at')->first())->getPathRelativeToRoot())
->disk(config('filesystems.default')),
])
->filters([])
->headerActions([])
->recordActions([])
->emptyStateIcon(Heroicon::CreditCard)
->emptyStateDescription('Belum ada data pembayaran.')
->defaultSort('created_at', 'desc')
->modifyQueryUsing(function ($query) {
$user = auth()->user();
return $query->whereHas('cooperationMedia.partnerMedia.company', function ($q) use ($user) {
$q->where('user_id', $user->id);
});
});
}
}

View File

@ -15,6 +15,7 @@
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
@ -22,6 +23,8 @@
class CooperationProposalRelationManager extends RelationManager
{
protected static ?string $recordTitleAttribute = 'e_catalog';
protected static string $relationship = 'proposal';
protected static ?string $title = 'Proposal';
@ -107,6 +110,9 @@ public function table(Table $table): Table
),
]),
])
->emptyStateIcon(Heroicon::DocumentText)
->emptyStateDescription('Tidak ada proposal yang diajukan.')
->defaultSort('created_at', 'desc')
->modifyQueryUsing(function ($query) {
$user = auth()->user();

View File

@ -21,6 +21,7 @@
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Support\Enums\Width;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
@ -31,6 +32,11 @@ class ReportRelationManager extends RelationManager
protected static ?string $title = 'Laporan';
public function isReadOnly(): bool
{
return false;
}
public static function canViewForRecord(Model $ownerRecord, string $pageClass): bool
{
if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) {
@ -47,13 +53,16 @@ public static function canViewForRecord(Model $ownerRecord, string $pageClass):
return true;
}
public function isReadOnly(): bool
{
return false;
}
public static function getBadge(Model $ownerRecord, string $pageClass): ?string
{
if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) {
return $ownerRecord->reports()
->whereHas('mediaTaskAssignment.partnerMedia.company', function ($q) {
$q->where('user_id', auth()->id());
})
->count();
}
return $ownerRecord->reports()->count();
}
@ -105,6 +114,11 @@ public function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('mediaTaskAssignment.partnerMedia.name')
->label('Media')
->searchable()
->visible(! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)),
TextColumn::make('title')
->label('Judul')
->searchable(),
@ -139,7 +153,17 @@ public function table(Table $table): Table
->modalWidth(Width::Large)
->modalHeading('Buat Laporan')
->mutateDataUsing(function (array $data): array {
$data['task_assignment_id'] = $this->getOwnerRecord()->taskAssignment?->id;
$taskAssignment = $this->getOwnerRecord()->taskAssignment;
if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)) {
$mediaTaskAssignment = $taskAssignment?->mediaTaskAssignments()
->whereHas('partnerMedia.company', function ($query) {
$query->where('user_id', auth()->id());
})
->first();
$data['media_task_assignment_id'] = $mediaTaskAssignment?->id;
}
return $data;
})
@ -184,10 +208,25 @@ public function table(Table $table): Table
&& $taskAssignment->start_date->toDateString() <= now()->toDateString()
&& $taskAssignment->end_date->toDateString() >= now()->toDateString()
&& $taskAssignment->reports()
->where('status', '!=', ApprovalStatus::REJECTED)
->whereHas('mediaTaskAssignment.partnerMedia.company', fn ($q) => $q->where('user_id', auth()->id()))
->where('reports.status', '!=', ApprovalStatus::REJECTED)
->count() < $taskAssignment->report_amount;
}),
])
->toolbarActions([]);
->toolbarActions([])
->emptyStateIcon(Heroicon::InboxArrowDown)
->emptyStateDescription('Tidak ada laporan yang diajukan.')
->defaultSort('created_at', 'desc')
->modifyQueryUsing(function ($query) {
$user = auth()->user();
if (! $user->hasRole(RoleEnum::PERUSAHAAN->value)) {
return $query;
}
$query->whereHas('mediaTaskAssignment.partnerMedia.company', function ($q) use ($user) {
$q->where('user_id', $user->id);
});
});
}
}

View File

@ -11,6 +11,7 @@
use Filament\Actions\DeleteBulkAction;
use Filament\Notifications\Notification;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
@ -102,6 +103,8 @@ public function table(Table $table): Table
->success()
),
]),
]);
])
->emptyStateIcon(Heroicon::ArchiveBoxXMark)
->emptyStateDescription('Tidak ada penugasan yang ditugaskan.');
}
}

View File

@ -26,6 +26,7 @@
use Filament\Schemas\Components\Utilities\Get;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Enums\FiltersLayout;
use Filament\Tables\Filters\Filter;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Filters\TrashedFilter;
@ -131,6 +132,13 @@ public static function configure(Table $table): Table
->placeholder('Belum dibayar')
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('completed_at')
->label('Tanggal Selesai')
->date('l, d F Y')
->sortable()
->placeholder('Masih berjalan')
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('description')
->label('Deskripsi')
->searchable()
@ -140,105 +148,121 @@ public static function configure(Table $table): Table
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()->native(false),
->filters(
auth()->user()->hasRole(RoleEnum::DEVELOPER->value) || auth()->user()->hasRole(RoleEnum::ADMINISTRATOR->value)
? [
TrashedFilter::make()
->native(false)
->visible(fn (): bool => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
SelectFilter::make('status')
->label('Status')
->options(CooperationStatus::class),
SelectFilter::make('status')
->label('Status')
->options(CooperationStatus::class)
->native(false),
SelectFilter::make('partnerMedia')
->label('Media')
->relationship('partnerMedia', 'name')
->searchable()
->preload(),
SelectFilter::make('partnerMedia')
->label('Media')
->relationship('partnerMedia', 'name')
->searchable()
->preload(),
Filter::make('title')
->schema([
TextInput::make('title')
->label('Nama Kegiatan')
->placeholder('Cari nama kegiatan...'),
])
->query(function (Builder $query, array $data): Builder {
return $query->when(
$data['title'],
fn (Builder $query, $title): Builder => $query->where('title', 'like', "%{$title}%"),
);
}),
Filter::make('date_filter')
->schema([
Select::make('date_type')
->label('Jenis Filter Tanggal')
->options([
'date' => 'Tanggal Penyerahan Awal',
'month' => 'Bulan & Tahun',
'year' => 'Tahun',
'range' => 'Rentang Tanggal',
Filter::make('title')
->schema([
TextInput::make('title')
->label('Nama Kegiatan')
->placeholder('Cari nama kegiatan...'),
])
->default('date')
->live(),
->query(function (Builder $query, array $data): Builder {
return $query->when(
$data['title'],
fn (Builder $query, $title): Builder => $query->where('title', 'like', "%{$title}%"),
);
}),
DatePicker::make('specific_date')
->label('Tanggal')
->visible(fn (Get $get) => $get('date_type') === 'date'),
Filter::make('date_filter')
->schema([
Select::make('date_type')
->label('Jenis Filter Tanggal')
->options([
'date' => 'Tanggal Penyerahan Awal',
'month' => 'Bulan & Tahun',
'year' => 'Tahun',
'range' => 'Rentang Tanggal',
])
->live()
->native(false),
Select::make('month')
->label('Bulan')
->options([
1 => 'Januari',
2 => 'Februari',
3 => 'Maret',
4 => 'April',
5 => 'Mei',
6 => 'Juni',
7 => 'Juli',
8 => 'Agustus',
9 => 'September',
10 => 'Oktober',
11 => 'November',
12 => 'Desember',
DatePicker::make('specific_date')
->label('Tanggal')
->visible(fn (Get $get) => $get('date_type') === 'date')
->native(false)
->displayFormat('l, d F Y'),
Select::make('month')
->label('Bulan')
->options([
1 => 'Januari',
2 => 'Februari',
3 => 'Maret',
4 => 'April',
5 => 'Mei',
6 => 'Juni',
7 => 'Juli',
8 => 'Agustus',
9 => 'September',
10 => 'Oktober',
11 => 'November',
12 => 'Desember',
])
->visible(fn (Get $get) => $get('date_type') === 'month')
->native(false),
Select::make('year')
->label('Tahun')
->options(function () {
$years = range(date('Y'), 2020);
return array_combine($years, $years);
})
->visible(fn (Get $get) => in_array($get('date_type'), ['month', 'year']))
->native(false),
DatePicker::make('start_date')
->label('Mulai Tanggal')
->visible(fn (Get $get) => $get('date_type') === 'range')
->native(false)
->displayFormat('l, d F Y'),
DatePicker::make('end_date')
->label('Sampai Tanggal')
->visible(fn (Get $get) => $get('date_type') === 'range')
->native(false)
->displayFormat('l, d F Y'),
])
->visible(fn (Get $get) => $get('date_type') === 'month'),
->query(function (Builder $query, array $data): Builder {
$type = $data['date_type'] ?? null;
$column = 'initial_submission_date';
Select::make('year')
->label('Tahun')
->options(function () {
$years = range(date('Y'), 2020);
if ($type === 'date' && ! empty($data['specific_date'])) {
return $query->whereDate($column, $data['specific_date']);
}
if ($type === 'month' && ! empty($data['month']) && ! empty($data['year'])) {
return $query->whereMonth($column, $data['month'])
->whereYear($column, $data['year']);
}
if ($type === 'year' && ! empty($data['year'])) {
return $query->whereYear($column, $data['year']);
}
if ($type === 'range' && ! empty($data['start_date']) && ! empty($data['end_date'])) {
return $query->whereBetween($column, [$data['start_date'], $data['end_date']]);
}
return array_combine($years, $years);
})
->visible(fn (Get $get) => in_array($get('date_type'), ['month', 'year'])),
DatePicker::make('start_date')
->label('Mulai Tanggal')
->visible(fn (Get $get) => $get('date_type') === 'range'),
DatePicker::make('end_date')
->label('Sampai Tanggal')
->visible(fn (Get $get) => $get('date_type') === 'range'),
])
->query(function (Builder $query, array $data): Builder {
$type = $data['date_type'] ?? null;
$column = 'initial_submission_date';
if ($type === 'date' && ! empty($data['specific_date'])) {
return $query->whereDate($column, $data['specific_date']);
}
if ($type === 'month' && ! empty($data['month']) && ! empty($data['year'])) {
return $query->whereMonth($column, $data['month'])
->whereYear($column, $data['year']);
}
if ($type === 'year' && ! empty($data['year'])) {
return $query->whereYear($column, $data['year']);
}
if ($type === 'range' && ! empty($data['start_date']) && ! empty($data['end_date'])) {
return $query->whereBetween($column, [$data['start_date'], $data['end_date']]);
}
return $query;
}),
])
return $query;
}),
] : [],
FiltersLayout::AboveContentCollapsible
)
->filtersFormColumns(3)
->recordActions([
ViewAction::make(),
@ -254,18 +278,17 @@ public static function configure(Table $table): Table
CompleteCooperationAction::make('completeCooperation'),
EditAction::make()
->visible(fn (): bool => ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)),
EditAction::make(),
DeleteAction::make()
->visible(fn (): bool => ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)),
DeleteAction::make(),
])
->headerActions([
ExportAction::make()
->exports([
CooperationExcelExport::make(),
])
->color('success'),
->color('success')
->visible(fn (): bool => auth()->user()->hasRole(RoleEnum::DEVELOPER->value) || auth()->user()->hasRole(RoleEnum::ADMINISTRATOR->value)),
])
->toolbarActions([

View File

@ -56,7 +56,7 @@ protected function setUp(): void
// Notify User
$record->user?->notify(new BroadcastNotification([
'title' => 'Permohonan Perubahan Ditolak 🛑',
'body' => "Halo! Mohon maaf, permohonan perubahan data Anda ({$record->change_reason}) ditolak oleh admin. Alasan: {$data['rejection_reason']} 😔",
'body' => "Halo! Mohon maaf, permohonan perubahan data Anda untuk {$record->change_reason} ditolak oleh admin. Alasan: {$data['rejection_reason']} 😔",
]));
})
->visible(fn (DataChangeRequest $record): bool => $record->status === DataChangeStatus::PENDING && ! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value))

View File

@ -44,13 +44,13 @@ class DataChangesResource extends Resource
protected static ?string $slug = 'manage/data-changes';
public static function getNavigationBadge(): string
public static function getNavigationBadge(): ?string
{
return static::getModel()::pending()
->when(auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value), function (Builder $query): void {
$query->where('user_id', auth()->id());
})
->count();
if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN)) {
return null;
}
return static::getModel()::pending()->count();
}
public static function infolist(Schema $schema): Schema
@ -249,7 +249,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
ViewAction::make(),

View File

@ -2,7 +2,6 @@
namespace App\Filament\Resources\Manage\Journalists\Actions;
use App\Enums\VerificationStatus;
use App\Filament\Actions\Cheerful\CreateAction;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
@ -32,7 +31,9 @@ protected function setUp(): void
])
->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 +69,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 = [];
@ -98,10 +99,10 @@ protected function setUp(): void
User::superAdmin()
->get()
->each(function ($admin) use ($dataChangeRequest): void {
->each(function ($admin) use ($dataChangeRequest, $user): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Penambahan Jurnalis Baru ✨',
'body' => 'Halooo Admin! 👋 '.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)

View File

@ -4,7 +4,6 @@
use App\Enums\DataChangeStatus;
use App\Enums\RoleEnum;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\DataChanges\DataChangesResource;
use App\Filament\Support\CheerfulNotification;
use App\Models\DataChangeRequest;
@ -50,10 +49,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 +65,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,
@ -146,10 +142,10 @@ protected function setUp(): void
User::superAdmin()
->get()
->each(function ($admin) use ($dataChangeRequest): void {
->each(function ($admin) use ($dataChangeRequest, $user): void {
$admin->notify(new BroadcastNotification([
'title' => 'Ada Pengajuan Perubahan Jurnalis ✨',
'body' => 'Halooo Admin! 👋 '.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 +159,7 @@ protected function setUp(): void
$this->performSave($record, $data);
CheerfulNotification::update()->send(); // Manual success notification
CheerfulNotification::update()->send();
return $record;
})

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 = [
@ -145,7 +134,7 @@ public static function form(Schema $schema): Schema
->rows(3)
->required(),
])
->visible(fn () => auth()->user()->company?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)),
->visible(fn () => auth()->user()->company?->verificationRequest?->status === VerificationStatus::APPROVED && auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)),
])
->columns(1);
}
@ -186,16 +175,26 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->checkIfRecordIsSelectableUsing(
fn () => ! (
in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) ||
auth()->user()->company?->verificationRequest()->pending()->exists()
)
)
->recordActions([
EditJournalistAction::make('edit'),
EditJournalistAction::make('edit')
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
DeleteJournalistAction::make('delete'),
DeleteJournalistAction::make('delete')
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
ForceDeleteAction::make(),
ForceDeleteAction::make()
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
RestoreAction::make(),
RestoreAction::make()
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
])
->toolbarActions([
BulkActionGroup::make([
@ -204,7 +203,7 @@ public static function table(Table $table): Table
ForceDeleteBulkAction::make(),
RestoreBulkAction::make(),
]),
])->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
])
->emptyStateIcon(Heroicon::Identification)
->emptyStateDescription('Setelah Anda membubat data pertama, maka akan muncul disini.')

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Manage\Journalists\Pages;
use App\Enums\RoleEnum;
use App\Enums\VerificationStatus;
use App\Filament\Resources\Manage\Journalists\Actions\CreateJournalistAction;
use App\Filament\Resources\Manage\Journalists\JournalistResource;
use Filament\Resources\Pages\ManageRecords;
@ -20,7 +21,8 @@ protected function getHeaderActions(): array
}
return [
CreateJournalistAction::make(),
CreateJournalistAction::make()
->visible(fn () => ! (in_array(auth()->user()->company?->verificationRequest?->status, [VerificationStatus::PENDING, VerificationStatus::REJECTED]) || auth()->user()->company?->verificationRequest()->pending()->exists())),
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Filament\Resources\Manage\Partners;
use App\Enums\RoleEnum;
use App\Filament\Actions\DefaultBulkActions;
use App\Filament\Resources\Manage\Partners\Pages\ManagePartners;
use App\Filament\Resources\Manage\Partners\Pages\ViewPartner;
@ -112,7 +113,7 @@ public static function infolist(Schema $schema): Schema
->first();
if (! $media) {
return 'Tidak ada dokumen';
return '';
}
$url = $media->getUrl();
@ -298,9 +299,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')
@ -313,7 +324,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
ViewAction::make(),

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Master\Categories;
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -83,7 +84,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Master\Classifications;
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -83,7 +84,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Master\Departments;
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -93,7 +94,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Master\Locations;
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -83,7 +84,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Master\SubClassifications;
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -102,7 +103,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Master\SubLocations;
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -98,7 +99,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Master\Themes;
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -83,7 +84,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -88,7 +88,8 @@ public static function form(Schema $schema): Schema
->searchable(),
Hidden::make('password')
->default(fn (?User $record): ?string => $record ? null : config('auth.password_default')),
->default(fn (): string => config('auth.password_default'))
->visibleOn('create'),
])
->columns(1);
}
@ -146,7 +147,7 @@ public static function table(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -4,6 +4,7 @@
use App\Enums\Channel;
use App\Enums\ContentType;
use App\Enums\RoleEnum;
use App\Enums\SocialMedia;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
@ -72,7 +73,9 @@ public static function configure(Table $table): Table
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()->native(false),
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
SelectFilter::make('social_media')
->label('Media Sosial')
@ -106,7 +109,6 @@ public static function configure(Table $table): Table
'year' => 'Tahun',
'range' => 'Rentang Tanggal',
])
->default('date')
->live()
->native(false),

View File

@ -35,7 +35,7 @@ public static function configure(Schema $schema): Schema
->schema([
Select::make('location_id')
->label('Lokus')
->relationship('location', 'name')
->relationship('location', 'name', fn (Builder $query): Builder => $query->active())
->searchable()
->preload()
->live()
@ -46,7 +46,7 @@ public static function configure(Schema $schema): Schema
Select::make('sub_location_id')
->label('Sub Lokus')
->relationship('subLocation', 'name', fn (Builder $query, Get $get): Builder => $query->where('location_id', $get('location_id')))
->relationship('subLocation', 'name', fn (Builder $query, Get $get): Builder => $query->active()->where('location_id', $get('location_id')))
->searchable()
->preload()
->required(),

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Monitoring\IssueManagements\Tables;
use App\Enums\IssueSentiment;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -73,7 +74,7 @@ public static function configure(Table $table): Table
->filters([
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole('Developer')),
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
SelectFilter::make('issue')
->label('Filter Isu')
@ -123,7 +124,6 @@ public static function configure(Table $table): Table
'year' => 'Tahun',
'range' => 'Rentang Tanggal',
])
->default('date')
->live()
->native(false),

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Monitoring\MediaMonitorings\Tables;
use App\Enums\Channel;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -96,7 +97,9 @@ public static function configure(Table $table): Table
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()->native(false),
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
SelectFilter::make('themes')
->label('Tema')
@ -133,7 +136,6 @@ public static function configure(Table $table): Table
'year' => 'Tahun',
'range' => 'Rentang Tanggal',
])
->default('date')
->live()
->native(false),

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Publication\Announcements;
use App\Enums\AnnouncementType;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -116,17 +117,25 @@ public static function table(Table $table): Table
->badge()
->sortable(),
TextColumn::make('partnerMedia.name')
TextColumn::make('partnerMedia')
->label('Penerima')
->getStateUsing(
fn ($record) => $record->partnerMedia->pluck('name')->toArray()
)
->listWithLineBreaks()
->limitList(3)
->expandableLimitedList()
->placeholder(fn (Announcement $record): string => $record->type === AnnouncementType::PUBLIC ? 'Publik' : 'Tidak ada penerima'),
->badge()
->placeholder(
fn (Announcement $record) => $record->type === AnnouncementType::PUBLIC ? 'Publik' : 'Tidak ada penerima'
),
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()->native(false),
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make()

View File

@ -3,6 +3,7 @@
namespace App\Filament\Resources\Publication\News\Tables;
use App\Enums\NewsStatus;
use App\Enums\RoleEnum;
use App\Filament\Actions\Cheerful\DeleteAction;
use App\Filament\Actions\Cheerful\EditAction;
use App\Filament\Actions\Cheerful\ForceDeleteAction;
@ -81,7 +82,9 @@ public static function configure(Table $table): Table
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()->native(false),
TrashedFilter::make()
->native(false)
->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)),
])
->recordActions([
EditAction::make(),

View File

@ -45,7 +45,9 @@ protected function getStats(): array
->descriptionIcon(Heroicon::BuildingOffice2)
->color('primary'),
Stat::make('Media Terverifikasi', PartnerMedia::approved()
Stat::make('Media Terverifikasi', PartnerMedia::whereHas('company.verificationRequest', function ($q) {
$q->approved();
})
->when($startDate, fn ($q) => $q->whereDate('created_at', '>=', $startDate))
->when($endDate, fn ($q) => $q->whereDate('created_at', '<=', $endDate))
->count())

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

@ -8,6 +8,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -28,6 +29,7 @@ protected function casts(): array
'final_submission_date' => 'date',
'payment_amount' => 'integer',
'payment_date' => 'date',
'completed_at' => 'date',
'status' => CooperationStatus::class,
];
}
@ -62,30 +64,46 @@ protected function completed(Builder $query): void
$query->where('status', CooperationStatus::COMPLETED);
}
public function cooperationMedia(): HasOne
public function cooperationMedia(): HasMany
{
return $this->hasOne(CooperationMedia::class);
return $this->hasMany(CooperationMedia::class);
}
public function partnerMedia(): BelongsToMany
{
return $this->belongsToMany(PartnerMedia::class, 'cooperation_media')
->using(CooperationMedia::class)
->withPivot(['status'])
->withTimestamps();
}
public function proposal(): HasOne
public function proposal(): HasMany
{
return $this->hasOne(CooperationProposal::class);
return $this->hasMany(CooperationProposal::class);
}
public function reports(): HasManyThrough
{
return $this->hasManyThrough(Report::class, TaskAssignment::class);
return $this->hasManyThrough(
Report::class,
MediaTaskAssignment::class,
'task_assignment_id',
'media_task_assignment_id'
);
}
public function taskAssignment(): HasOne
{
return $this->hasOne(TaskAssignment::class);
}
public function payments(): HasManyThrough
{
return $this->hasManyThrough(
CooperationPayment::class,
CooperationMedia::class,
'cooperation_id',
'cooperation_media_id'
);
}
}

View File

@ -7,7 +7,7 @@
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Spatie\MediaLibrary\HasMedia;
@ -57,9 +57,9 @@ public function partnerMedia(): BelongsTo
return $this->belongsTo(PartnerMedia::class);
}
public function payments(): HasMany
public function payment(): HasOne
{
return $this->hasMany(CooperationPayment::class, 'cooperation_media_id');
return $this->hasOne(CooperationPayment::class, 'cooperation_media_id');
}
public function rejectionReasons(): MorphMany

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

@ -3,6 +3,8 @@
namespace App\Models;
use App\Enums\IsActive;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
@ -23,6 +25,12 @@ protected function casts(): array
];
}
#[Scope]
protected function active(Builder $query): void
{
$query->where('is_active', IsActive::ACTIVE);
}
public function issueManagements(): HasMany
{
return $this->hasMany(IssueManagement::class);

View File

@ -5,6 +5,7 @@
use App\Enums\ApprovalStatus;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Swindon\FilamentHashids\Traits\HasHashid;
@ -38,4 +39,9 @@ public function rejectionReasons(): MorphMany
{
return $this->morphMany(RejectionReason::class, 'rejectable');
}
public function reports(): HasMany
{
return $this->hasMany(Report::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
{
@ -111,6 +85,7 @@ public function cooperationProposals(): HasMany
public function cooperations(): BelongsToMany
{
return $this->belongsToMany(Cooperation::class, 'cooperation_media')
->using(CooperationMedia::class)
->withPivot(['status'])
->withTimestamps();
}
@ -128,6 +103,7 @@ public function mediaTaskAssignments(): HasMany
public function taskAssignments(): BelongsToMany
{
return $this->belongsToMany(TaskAssignment::class, 'media_task_assignment')
->using(MediaTaskAssignment::class)
->withPivot(['status'])
->withTimestamps();
}

View File

@ -26,9 +26,9 @@ protected function casts(): array
];
}
public function taskAssignment(): BelongsTo
public function mediaTaskAssignment(): BelongsTo
{
return $this->belongsTo(TaskAssignment::class);
return $this->belongsTo(MediaTaskAssignment::class);
}
public function rejectionReasons(): MorphMany

View File

@ -3,6 +3,8 @@
namespace App\Models;
use App\Enums\IsActive;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@ -24,6 +26,12 @@ protected function casts(): array
];
}
#[Scope]
protected function active(Builder $query): void
{
$query->where('is_active', IsActive::ACTIVE);
}
public function issueManagements(): HasMany
{
return $this->hasMany(IssueManagement::class);

View File

@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Swindon\FilamentHashids\Traits\HasHashid;
@ -29,9 +30,16 @@ public function cooperation(): BelongsTo
return $this->belongsTo(Cooperation::class);
}
public function reports(): HasMany
public function reports(): HasManyThrough
{
return $this->hasMany(Report::class);
return $this->hasManyThrough(
Report::class,
MediaTaskAssignment::class,
'task_assignment_id', // Foreign key di tabel media_task_assignment
'media_task_assignment_id', // Foreign key di tabel reports
'id', // Local key di tabel task_assignments
'id' // Local key di tabel media_task_assignment
);
}
public function rejectionReasons(): MorphMany
@ -44,9 +52,10 @@ public function mediaTaskAssignments(): HasMany
return $this->hasMany(MediaTaskAssignment::class);
}
public function partnerMedias(): BelongsToMany
public function partnerMedia(): BelongsToMany
{
return $this->belongsToMany(PartnerMedia::class, 'media_task_assignment')
->using(MediaTaskAssignment::class)
->withPivot(['status'])
->withTimestamps();
}

View File

@ -4,6 +4,7 @@
use App\Enums\IsActive;
use App\Enums\RoleEnum;
use App\Enums\VerificationStatus;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Contracts\Auth\MustVerifyEmail;
@ -37,6 +38,10 @@ protected function casts(): array
public function canAccessPanel(Panel $panel): bool
{
if ($this->company?->verificationRequest?->status === VerificationStatus::REJECTED) {
return false;
}
return $this->is_active === IsActive::ACTIVE;
}

View File

@ -4,11 +4,13 @@
use Filament\Notifications\Notification as FilamentNotification;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Notification;
use Illuminate\Queue\SerializesModels;
class BroadcastNotification extends Notification
class BroadcastNotification extends Notification implements ShouldQueue
{
use Queueable;
use Queueable, SerializesModels;
public function __construct(
protected array $payload

View File

@ -1,401 +0,0 @@
<?php
namespace App\Services;
use App\Enums\DecisionAdmin;
use App\Enums\VerificationStatus;
use App\Filament\Pages\Company as PagesCompany;
use App\Filament\Pages\Media;
use App\Filament\Resources\Manage\Journalists\Pages\ManageJournalists;
use App\Models\Company;
use App\Models\VerificationRequest;
use App\Models\VerificationReview;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
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()
->where(fn ($q) => $q->needRevision()->orWhere(fn ($q) => $q->rejected()))
->latest()
->first();
return $latestReview?->note;
}
/**
* Get all reviews history
*/
public function getReviewHistory(?VerificationRequest $verificationRequest): Collection
{
if (! $verificationRequest) {
return collect();
}
return $verificationRequest->reviews()
->with('reviewer')
->latest()
->get();
}
/**
* Get unified verification progress data
*/
public function getVerificationProgress(?Company $company, ?VerificationRequest $verificationRequest): array
{
$currentStep = $this->resolveStep($company, $verificationRequest);
$isComplete = $this->isDataComplete($company);
$missingData = $this->getMissingData($company);
$status = $verificationRequest?->status;
return [
'currentStep' => $currentStep,
'isDataComplete' => $isComplete,
'missingData' => $missingData,
'canSubmit' => $this->canSubmit($company, $verificationRequest),
'verification' => $verificationRequest,
'status' => [
'label' => $status?->getLabel() ?? 'Belum Mengajukan',
'color' => $status?->getColor() ?? 'gray',
],
'isApproved' => $status === VerificationStatus::APPROVED,
'isRejected' => $status === VerificationStatus::REJECTED,
'isPending' => $status === VerificationStatus::PENDING,
'needsRevision' => $status === VerificationStatus::NEED_REVISION,
'latestNotes' => $this->getLatestReviewNotes($verificationRequest),
'reviewHistory' => $this->getReviewHistory($verificationRequest),
'company' => $company,
'partnerMedia' => $company?->partnerMedia,
'journalists' => $company?->partnerMedia?->journalists ?? collect(),
'steps' => $this->getSteps($verificationRequest, $isComplete, $missingData),
];
}
/**
* Get steps array for stepper
*/
protected function getSteps(?VerificationRequest $verificationRequest, bool $isComplete, array $missingData): array
{
$status = $verificationRequest?->status;
return [
[
'title' => 'Pengajuan',
'description' => $isComplete
? 'Data sudah lengkap, silakan ajukan verifikasi'
: 'Silakan lengkapi data',
],
[
'title' => 'Verifikasi',
'description' => match ($status) {
VerificationStatus::NEED_REVISION => 'Perlu perbaikan data',
VerificationStatus::PENDING => 'Sedang diverifikasi admin',
default => 'Menunggu pengajuan',
},
],
[
'title' => 'Selesai',
'description' => match ($status) {
VerificationStatus::APPROVED => 'Verifikasi disetujui',
VerificationStatus::REJECTED => 'Verifikasi ditolak',
default => 'Menunggu keputusan',
},
],
];
}
/**
* Submit unified verification
*/
public function submitVerification(Company $company, ?VerificationRequest $existingRequest): VerificationRequest
{
if ($existingRequest && $existingRequest->status === VerificationStatus::NEED_REVISION) {
// Resubmit - update existing request
$existingRequest->update([
'status' => VerificationStatus::PENDING,
]);
return $existingRequest->fresh(['reviews.reviewer']);
}
// New submission
return VerificationRequest::create([
'company_id' => $company->id,
'submitted_by' => auth()->id(),
'status' => VerificationStatus::PENDING,
])->load(['reviews.reviewer']);
}
/**
* Process review decision - unified for Company, Media, and Journalists
*/
public function processReview(VerificationRequest $verificationRequest, DecisionAdmin $decision, ?string $note): void
{
$verificationRequest->load(['company.partnerMedia.journalists']);
DB::beginTransaction();
try {
// Create review record
VerificationReview::create([
'verification_request_id' => $verificationRequest->id,
'reviewer_id' => auth()->id(),
'decision' => $decision,
'note' => $note,
]);
// Update verification request status
$newStatus = match ($decision) {
DecisionAdmin::APPROVED => VerificationStatus::APPROVED,
DecisionAdmin::NEED_REVISION => VerificationStatus::NEED_REVISION,
DecisionAdmin::REJECTED => VerificationStatus::REJECTED,
};
$verificationRequest->update([
'status' => $newStatus,
]);
// Update Company status
$company = $verificationRequest->company;
if ($company) {
$company->update(['status' => $newStatus]);
// Update Media status
$media = $company->partnerMedia;
if ($media) {
$media->update(['status' => $newStatus]);
// Update all Journalists status
$media->journalists()->update(['status' => $newStatus]);
}
}
DB::commit();
} catch (\Throwable $e) {
Log::error('Verification review processing failed', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'request_id' => $verificationRequest->id,
'decision' => $decision->value,
]);
DB::rollBack();
throw $e;
}
}
/**
* Get company name from verification request
*/
public function getCompanyName(VerificationRequest $request): string
{
return $request->company?->name ?? 'Data tidak ditemukan';
}
/**
* Get verification summary data for admin
*/
public function getVerificationSummary(VerificationRequest $request): array
{
$company = $request->company;
$media = $company?->partnerMedia;
$journalists = $media?->journalists ?? collect();
return [
'company' => [
'name' => $company?->name ?? 'Belum diisi',
'email' => $company?->email ?? '-',
'exists' => $company !== null,
],
'media' => [
'name' => $media?->name ?? 'Belum diisi',
'type' => $media?->type?->getLabel() ?? '-',
'exists' => $media !== null,
],
'journalists' => [
'count' => $journalists->count(),
'names' => $journalists->pluck('name')->take(3)->toArray(),
'exists' => $journalists->count() > 0,
],
];
}
/**
* Get alert data for verification status
*/
public function getAlertData(?VerificationRequest $verificationRequest): ?array
{
if (! $verificationRequest) {
return null;
}
$status = $verificationRequest->status;
$latestNotes = $this->getLatestReviewNotes($verificationRequest);
if ($status === VerificationStatus::NEED_REVISION && $latestNotes) {
return [
'type' => 'revision',
'icon' => 'exclamation-triangle',
'title' => 'Catatan Revisi dari Admin',
'message' => $latestNotes,
'color' => 'warning',
];
}
if ($status === VerificationStatus::REJECTED && $latestNotes) {
return [
'type' => 'rejected',
'icon' => 'x-circle',
'title' => 'Pengajuan Ditolak',
'message' => $latestNotes,
'color' => 'danger',
];
}
if ($status === VerificationStatus::APPROVED) {
return [
'type' => 'approved',
'icon' => 'check-circle',
'title' => 'Verifikasi Diterima',
'message' => 'Verifikasi Anda telah diterima dan disetujui oleh admin. Sekarang Anda sudah resmi terverifikasi dan bekerja sama dengan kami!',
'color' => 'success',
];
}
if ($status === VerificationStatus::PENDING) {
return [
'type' => 'pending',
'icon' => 'clock',
'title' => 'Menunggu Verifikasi',
'message' => 'Data Anda sedang dalam proses verifikasi oleh admin. Mohon tunggu.',
'color' => 'info',
'animate' => true,
];
}
return null;
}
/**
* Get missing data buttons configuration
*/
public function getMissingDataButtons(?Company $company): array
{
$buttons = [];
if (! $company) {
$buttons[] = [
'label' => 'Lengkapi Data Perusahaan',
'url' => PagesCompany::getUrl(),
'icon' => 'building-office-2',
'color' => 'warning',
];
}
if ($company && ! $company->partnerMedia) {
$buttons[] = [
'label' => 'Lengkapi Data Media',
'url' => Media::getUrl(),
'icon' => 'newspaper',
'color' => 'warning',
];
}
if ($company && $company->partnerMedia && ($company->partnerMedia->journalists === null || $company->partnerMedia->journalists->count() === 0)) {
$buttons[] = [
'label' => 'Lengkapi Data Jurnalis',
'url' => ManageJournalists::getUrl(),
'icon' => 'user-circle',
'color' => 'warning',
];
}
return $buttons;
}
}

View File

@ -36,7 +36,6 @@ public function definition(): array
'domicile_certificate' => $this->faker->numerify('###/###/DOMISILI/####'),
'profile' => $this->faker->numerify('PROFILE-####-########'),
'profile' => $this->faker->numerify('PROFILE-####-########'),
'status' => VerificationStatus::PENDING,
];
}

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();

View File

@ -20,7 +20,8 @@ public function up(): void
$table->text('description');
$table->unsignedInteger('payment_amount')->nullable();
$table->date('payment_date')->nullable();
$table->enum('status', [CooperationStatus::values()])->default(CooperationStatus::PENDING)->comment(CooperationStatus::comment());
$table->date('completed_at')->nullable();
$table->enum('status', CooperationStatus::values())->default(CooperationStatus::PENDING)->comment(CooperationStatus::comment());
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();

View File

@ -18,7 +18,7 @@ public function up(): void
$table->id();
$table->foreignIdFor(Cooperation::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(PartnerMedia::class)->constrained()->cascadeOnDelete();
$table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
});

View File

@ -19,8 +19,8 @@ public function up(): void
$table->foreignIdFor(Cooperation::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(PartnerMedia::class)->constrained()->cascadeOnDelete();
$table->text('description')->nullable();
$table->string('e_catalog', 50)->nullable();
$table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->string('e_catalog', 200)->nullable();
$table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->timestamp('submitted_at')->useCurrent();
$table->timestamp('responded_at')->nullable();
$table->timestamp('created_at')->useCurrent();

View File

@ -18,7 +18,7 @@ public function up(): void
$table->id();
$table->foreignIdFor(TaskAssignment::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(PartnerMedia::class)->constrained()->cascadeOnDelete();
$table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
});

View File

@ -1,7 +1,7 @@
<?php
use App\Enums\ApprovalStatus;
use App\Models\TaskAssignment;
use App\Models\MediaTaskAssignment;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
@ -15,12 +15,12 @@ public function up(): void
{
Schema::create('reports', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(TaskAssignment::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(MediaTaskAssignment::class)->constrained()->cascadeOnDelete();
$table->string('title', 200);
$table->date('publication_date');
$table->string('link', 50);
$table->text('description');
$table->enum('status', [ApprovalStatus::values()])->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->enum('status', ApprovalStatus::values())->default(ApprovalStatus::PENDING)->comment(ApprovalStatus::comment());
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();

View File

@ -522,6 +522,18 @@ public function run(): void
"name": "Admin Monitoring",
"guard_name": "web",
"permissions": [
"ViewAny:Classification",
"View:Classification",
"Create:Classification",
"Update:Classification",
"Delete:Classification",
"Restore:Classification",
"ForceDelete:Classification",
"ForceDeleteAny:Classification",
"RestoreAny:Classification",
"Replicate:Classification",
"Reorder:Classification",
"View:Dashboard",
"ViewAny:IssueManagement",
@ -535,6 +547,18 @@ public function run(): void
"RestoreAny:IssueManagement",
"Replicate:IssueManagement",
"Reorder:IssueManagement",
"ViewAny:Location",
"View:Location",
"Create:Location",
"Update:Location",
"Delete:Location",
"Restore:Location",
"ForceDelete:Location",
"ForceDeleteAny:Location",
"RestoreAny:Location",
"Replicate:Location",
"Reorder:Location",
"ViewAny:MediaMonitoring",
"View:MediaMonitoring",
@ -659,15 +683,6 @@ public function run(): void
"permissions": [
"ViewAny:Cooperation",
"View:Cooperation",
"Create:Cooperation",
"Update:Cooperation",
"Delete:Cooperation",
"Restore:Cooperation",
"ForceDelete:Cooperation",
"ForceDeleteAny:Cooperation",
"RestoreAny:Cooperation",
"Replicate:Cooperation",
"Reorder:Cooperation",
"View:Dashboard"
]
@ -676,29 +691,8 @@ public function run(): void
"name": "Perusahaan",
"guard_name": "web",
"permissions": [
"ViewAny:Announcement",
"View:Announcement",
"Create:Announcement",
"Update:Announcement",
"Delete:Announcement",
"Restore:Announcement",
"ForceDelete:Announcement",
"ForceDeleteAny:Announcement",
"RestoreAny:Announcement",
"Replicate:Announcement",
"Reorder:Announcement",
"ViewAny:Cooperation",
"View:Cooperation",
"Create:Cooperation",
"Update:Cooperation",
"Delete:Cooperation",
"Restore:Cooperation",
"ForceDelete:Cooperation",
"ForceDeleteAny:Cooperation",
"RestoreAny:Cooperation",
"Replicate:Cooperation",
"Reorder:Cooperation",
"View:Company",
@ -706,21 +700,12 @@ public function run(): void
"ViewAny:DataChangeRequest",
"View:DataChangeRequest",
"Create:DataChangeRequest",
"Update:DataChangeRequest",
"Delete:DataChangeRequest",
"Restore:DataChangeRequest",
"ForceDelete:DataChangeRequest",
"ForceDeleteAny:DataChangeRequest",
"RestoreAny:DataChangeRequest",
"Replicate:DataChangeRequest",
"Reorder:DataChangeRequest",
"ViewAny:Journalits",
"View:Journalits",
"Create:Journalits",
"Update:Journalits",
"Delete:Journalits",
"ViewAny:Journalist",
"View:Journalist",
"Create:Journalist",
"Update:Journalist",
"Delete:Journalist",
"View:Media",

View File

@ -2,8 +2,10 @@
<form wire:submit.prevent="save">
{{ $this->form }}
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@if (!$this->isVerificationStage)
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@endif
</form>
</x-filament-panels::page>

View File

@ -32,9 +32,11 @@ class="dark:text-gray-400">
<form wire:submit.prevent="save">
{{ $this->form }}
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@if (!$this->isVerificationStage)
<div class="mt-6">
{{ $this->saveAction() }}
</div>
@endif
</form>
@endif
</x-filament-panels::page>