From ab318a030be3369f62008725784a77209a6baaf5 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Tue, 13 Jan 2026 15:53:07 +0700 Subject: [PATCH] feat(notification): enhance user notifications for cooperation and verification processes - Added detailed notifications for users and admins upon acceptance, rejection, and submission of proposals, verifications, and reports. - Improved user experience with personalized messages and actionable links in notifications. - Refactored notification logic across various actions to ensure consistent messaging and clarity. - Introduced new notification titles and bodies to better reflect the status of actions taken. --- app/Filament/Pages/AdminVerification.php | 36 ++++++++- app/Filament/Pages/Verification.php | 80 +++++++++++-------- .../Actions/Cooperation/AcceptAction.php | 40 ++++++++-- .../CreateTaskAssignmentAction.php | 24 +++++- .../Actions/Cooperation/RejectAction.php | 27 +++++-- .../Cooperation/SendProposalAction.php | 50 +++++++++--- .../Actions/Proposal/AcceptAction.php | 21 ++++- .../Actions/Proposal/RejectAction.php | 21 ++++- .../Actions/Report/AcceptAction.php | 26 +++++- .../Actions/Report/RejectAction.php | 28 ++++++- .../Cooperations/Pages/CreateCooperation.php | 18 +++++ .../Cooperations/Pages/ViewCooperation.php | 71 ++-------------- .../CooperationProposalRelationManager.php | 11 +-- .../ReportRelationManager.php | 48 ++++++++++- .../TaskAssignmentRelationManager.php | 7 -- app/Models/User.php | 11 ++- app/Notifications/BroadcastNotification.php | 11 ++- 17 files changed, 380 insertions(+), 150 deletions(-) diff --git a/app/Filament/Pages/AdminVerification.php b/app/Filament/Pages/AdminVerification.php index 2376792..76323df 100644 --- a/app/Filament/Pages/AdminVerification.php +++ b/app/Filament/Pages/AdminVerification.php @@ -5,7 +5,9 @@ use App\Enums\DecisionAdmin; use App\Enums\VerificationStatus; use App\Filament\Resources\Manage\Partners\Pages\ViewPartner; +use App\Models\User; use App\Models\VerificationRequest; +use App\Notifications\BroadcastNotification; use App\Services\VerificationService; use BackedEnum; use BezhanSalleh\FilamentShield\Traits\HasPageShield; @@ -254,10 +256,40 @@ protected function processReview(int $requestId, DecisionAdmin $decision, ?strin }; Notification::make() - ->title('Berhasil') - ->body("Verifikasi lengkap telah {$actionLabel}. Status telah diterapkan pada Perusahaan, Media, dan semua Jurnalis.") + ->title('Berhasil Diproses! ✨') + ->body("Mantap! Verifikasi telah {$actionLabel} dan notifikasi sudah dikirim ke user. Kerja bagus, Admin! 💪") ->color($color) ->send(); + + // Notify User + $partnerUser = $verificationRequest->company?->user; + if ($partnerUser) { + $statusTitle = match ($decision) { + DecisionAdmin::APPROVED => 'Horee! Verifikasi Disetujui 🎉', + DecisionAdmin::NEED_REVISION => 'Yah, Ada Yang Perlu Direvisi nih 📝', + DecisionAdmin::REJECTED => 'Maaf, Verifikasi Ditolak 😔', + }; + + $body = match ($decision) { + DecisionAdmin::APPROVED => 'Yeay! Pengajuan verifikasi perusahaan Anda telah disetujui. Sekarang Anda sudah resmi terverifikasi! 🎊', + DecisionAdmin::NEED_REVISION => 'Ayo sedikit lagi! Admin meminta beberapa perbaikan pada pengajuan Anda agar bisa segera disetujui. 💪', + DecisionAdmin::REJECTED => 'Yah, maaf banget... pengajuan verifikasi perusahaan Anda belum bisa kami terima saat ini. 💔', + }; + + if ($note) { + $body .= "\n\nCatatan Admin: {$note}"; + } + + $partnerUser->notify(new BroadcastNotification([ + 'title' => $statusTitle, + 'body' => $body, + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(Verification::getUrl()), + ], + ])); + } } catch (\Exception $e) { Notification::make() ->title('Error') diff --git a/app/Filament/Pages/Verification.php b/app/Filament/Pages/Verification.php index b8f81e9..b89752f 100644 --- a/app/Filament/Pages/Verification.php +++ b/app/Filament/Pages/Verification.php @@ -3,8 +3,11 @@ namespace App\Filament\Pages; use App\Enums\VerificationStatus; +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; @@ -16,6 +19,7 @@ use Filament\Notifications\Notification; use Filament\Pages\Page; use Filament\Support\Icons\Heroicon; +use Illuminate\Support\Facades\Auth; use UnitEnum; class Verification extends Page implements HasActions, HasForms @@ -49,7 +53,8 @@ public function boot(): void public function mount(): void { - $this->company = auth()->user()->company; + $user = Auth::user(); + $this->company = $user->company; // Load unified verification request if ($this->company) { @@ -103,41 +108,50 @@ public function submitVerificationAction(): Action ->modalSubmitActionLabel('Ya, Ajukan') ->visible(fn (): bool => $progress['canSubmit']) ->disabled(fn (): bool => ! $progress['isDataComplete']) - ->action(function (): void { - $this->submitVerification(); + ->action(function () use ($progress): void { + if (! $progress['isDataComplete']) { + Notification::make() + ->title('Data Belum Lengkap') + ->body('Silakan lengkapi data terlebih dahulu: '.implode(', ', $progress['missingData'])) + ->danger() + ->send(); + + return; + } + + $isRevision = $this->verificationRequest && $this->verificationRequest->status === VerificationStatus::NEED_REVISION; + + $this->verificationRequest = $this->verificationService->submitVerification( + $this->company, + $this->verificationRequest + ); + + Notification::make() + ->title('Berhasil Diajukan! 🚀') + ->body('Yeay! Pengajuan verifikasi Anda sudah terkirim ke Admin. Mohon ditunggu ya, semoga hasilnya memuaskan! ✨') + ->success() + ->send(); + + User::superAdmin() + ->get() + ->each(function ($admin) use ($isRevision): void { + $user = Auth::user(); + + $admin->notify(new BroadcastNotification([ + 'title' => $isRevision ? 'Pembaruan Data Verifikasi ✨' : 'Ada Pengajuan Verifikasi Baru! 🚀', + 'body' => $isRevision + ? "Cihuy! {$user->name} baru saja memperbarui data verifikasi untuk perusahaan {$user->company->name}. Yuk, cek perubahannya!" + : "Halo Admin! {$user->name} baru saja mengajukan verifikasi data untuk perusahaan {$user->company->name}. Segera diproses ya! 😊", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(PartnerResource::getUrl('view', ['record' => $user])), + ], + ])); + }); }); } - /** - * Handle unified verification submission - */ - protected function submitVerification(): void - { - $progress = $this->getVerificationProgress(); - - // Validate data completeness - if (! $progress['isDataComplete']) { - Notification::make() - ->title('Data Belum Lengkap') - ->body('Silakan lengkapi data terlebih dahulu: '.implode(', ', $progress['missingData'])) - ->danger() - ->send(); - - return; - } - - $this->verificationRequest = $this->verificationService->submitVerification( - $this->company, - $this->verificationRequest - ); - - Notification::make() - ->title('Berhasil!') - ->body('Verifikasi lengkap (Perusahaan, Media, dan Jurnalis) berhasil diajukan untuk diverifikasi.') - ->success() - ->send(); - } - public function getViewData(): array { return [ diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/AcceptAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/AcceptAction.php index ab60222..c37c138 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/AcceptAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/AcceptAction.php @@ -3,9 +3,14 @@ namespace App\Filament\Resources\Manage\Cooperations\Actions\Cooperation; use App\Enums\ApprovalStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; +use App\Models\Cooperation; +use App\Models\User; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Notifications\Notification; use Filament\Support\Icons\Heroicon; +use Illuminate\Database\Eloquent\Builder; class AcceptAction extends Action { @@ -16,10 +21,10 @@ protected function setUp(): void $this->label('Terima') ->icon(Heroicon::OutlinedCheck) ->color('success') - ->action(function (\App\Models\Cooperation $record): void { + ->action(function (Cooperation $record): void { $cooperationMedia = $record->cooperationMedia() - ->whereHas('partnerMedia', function (\Illuminate\Database\Eloquent\Builder $query): void { - $query->whereHas('company', function (\Illuminate\Database\Eloquent\Builder $subQuery): void { + ->whereHas('partnerMedia', function (Builder $query): void { + $query->whereHas('company', function (Builder $subQuery): void { $subQuery->where('user_id', auth()->id()); }); }) @@ -27,20 +32,39 @@ protected function setUp(): void if ($cooperationMedia) { $cooperationMedia->update(['status' => ApprovalStatus::ACCEPTED]); + Notification::make() - ->title('Kerja sama diterima') + ->title('Berhasil Diterima! ✅✨') + ->body('Mantap! Anda telah menerima penawaran kerja sama ini. Silakan lanjut ke tahap pengajuan proposal ya! 💪') ->success() ->send(); + + // Notify Admins + User::superAdmin() + ->get() + ->each(function ($admin) use ($record, $cooperationMedia): void { + $admin->notify(new BroadcastNotification([ + 'title' => 'Kabar Baik! Tawaran Diterima ✨🤝', + 'body' => "Media {$cooperationMedia->partnerMedia->name} baru saja menerima penawaran kerja sama \"{$record->title}\". Siap-siap cek proposal mereka ya! 😊", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->id])), + ], + ])); + }); } }) - ->visible(function (\App\Models\Cooperation $record): bool { - if (! auth()->user()->hasRole('Perusahaan')) { + ->visible(function (Cooperation $record): bool { + $user = auth()->user(); + + if (! $user || ! $user->hasRole('Perusahaan')) { return false; } $cooperationMedia = $record->cooperationMedia() - ->whereHas('partnerMedia', function (\Illuminate\Database\Eloquent\Builder $query): void { - $query->whereHas('company', function (\Illuminate\Database\Eloquent\Builder $subQuery): void { + ->whereHas('partnerMedia', function (Builder $query): void { + $query->whereHas('company', function (Builder $subQuery): void { $subQuery->where('user_id', auth()->id()); }); }) diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CreateTaskAssignmentAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CreateTaskAssignmentAction.php index 1700e4e..b6ecfe0 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CreateTaskAssignmentAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/CreateTaskAssignmentAction.php @@ -4,7 +4,9 @@ use App\Enums\ApprovalStatus; use App\Enums\CooperationStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; use App\Models\Cooperation; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Textarea; @@ -50,6 +52,7 @@ protected function setUp(): void ->minValue(1) ->required() ->autocomplete(false), + Textarea::make('task_description') ->label('Deskripsi Tugas') ->required() @@ -75,17 +78,34 @@ protected function setUp(): void 'partner_media_id' => $partnerMedia->id, 'status' => ApprovalStatus::PENDING, ]); + + // Notify Partner + $partnerUser = $partnerMedia->company?->user; + if ($partnerUser) { + $partnerUser->notify(new BroadcastNotification([ + 'title' => 'Tugas Baru Telah Menanti! 📝✨', + 'body' => "Halo! Admin sudah menetapkan tugas untuk kerja sama \"{$record->title}\". Yuk, cek detail tugasnya dan mulai buat laporannya! Semangat! 💪😊", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->id, 'relation' => 2])), + ], + ])); + } } Notification::make() - ->title('Penugasan berhasil dibuat') + ->title('Penugasan Berhasil Dibuat! 🚀✨') + ->body('Mantap! Penugasan telah dibuat dan semua media terkait sudah diberi tahu. Mari kita pantau progresnya! 💪') ->success() ->send(); }) ->modalHeading('Buat Penugasan') ->modalSubmitActionLabel('Simpan') ->visible(function (Cooperation $record): bool { - return ! auth()->user()->hasRole('Perusahaan') + $user = auth()->user(); + + return $user && ! $user->hasRole('Perusahaan') && ! $record->taskAssignments()->exists() && $record->status === CooperationStatus::ASSIGNMENT; }) diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/RejectAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/RejectAction.php index d9eff5f..008154e 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/RejectAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/RejectAction.php @@ -3,6 +3,9 @@ namespace App\Filament\Resources\Manage\Cooperations\Actions\Cooperation; use App\Enums\ApprovalStatus; +use App\Models\Cooperation; +use App\Models\User; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Notifications\Notification; use Filament\Support\Icons\Heroicon; @@ -16,7 +19,7 @@ protected function setUp(): void $this->label('Tolak') ->icon(Heroicon::XMark) ->color('danger') - ->action(function (\App\Models\Cooperation $record): void { + ->action(function (Cooperation $record): void { $cooperationMedia = $record->cooperationMedia() ->whereHas('partnerMedia', function ($query) { $query->whereHas('company', function ($subQuery) { @@ -27,14 +30,28 @@ protected function setUp(): void if ($cooperationMedia) { $cooperationMedia->update(['status' => ApprovalStatus::REJECTED]); + Notification::make() - ->title('Kerja sama ditolak') - ->success() + ->title('Berhasil Ditolak 🙏') + ->body('Terima kasih atas konfirmasinya. Pesan penolakan telah diteruskan ke Admin. 😊') + ->info() ->send(); + + // Notify Admins + User::superAdmin() + ->get() + ->each(function ($admin) use ($record, $cooperationMedia): void { + $admin->notify(new BroadcastNotification([ + 'title' => 'Yah, Tawaran Ditolak 😔🤝', + 'body' => "Media {$cooperationMedia->partnerMedia->name} baru saja menolak penawaran kerja sama \"{$record->title}\". Mungkin di lain waktu ya! 🙏", + ])); + }); } }) - ->visible(function (\App\Models\Cooperation $record): bool { - if (! auth()->user()->hasRole('Perusahaan')) { + ->visible(function (Cooperation $record): bool { + $user = auth()->user(); + + if (! $user->hasRole('Perusahaan')) { return false; } diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php index 7fa4808..c560042 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Cooperation/SendProposalAction.php @@ -3,6 +3,10 @@ namespace App\Filament\Resources\Manage\Cooperations\Actions\Cooperation; use App\Enums\ApprovalStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; +use App\Models\Cooperation; +use App\Models\User; +use App\Notifications\BroadcastNotification; use Asmit\FilamentUpload\Forms\Components\AdvancedFileUpload; use Filament\Actions\Action; use Filament\Forms\Components\Textarea; @@ -10,6 +14,7 @@ use Filament\Notifications\Notification; use Filament\Support\Enums\Width; use Filament\Support\Icons\Heroicon; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Storage; class SendProposalAction extends Action @@ -45,16 +50,22 @@ protected function setUp(): void ->directory(fn (): string => 'cooperations/proposal-attachment/'.now()->toDateString()) ->required(), ]) - ->action(function (\App\Models\Cooperation $record, array $data): void { + ->action(function (Cooperation $record, array $data): void { $cooperationMedia = $record->cooperationMedia() - ->whereHas('partnerMedia', function (\Illuminate\Database\Eloquent\Builder $query): void { - $query->whereHas('company', function (\Illuminate\Database\Eloquent\Builder $subQuery): void { + ->whereHas('partnerMedia', function (Builder $query): void { + $query->whereHas('company', function (Builder $subQuery): void { $subQuery->where('user_id', auth()->id()); }); }) ->first(); if ($cooperationMedia) { + $existingProposal = $record->proposals() + ->where('partner_media_id', $cooperationMedia->partner_media_id) + ->first(); + + $isRevision = (bool) $existingProposal; + $proposal = $record->proposals()->create([ 'partner_media_id' => $cooperationMedia->partner_media_id, 'description' => $data['description'], @@ -79,21 +90,42 @@ protected function setUp(): void } Notification::make() - ->title('Proposal kerja sama berhasil dikirim') + ->title('Berhasil Diajukan! 🚀') + ->body('Yeay! Proposal kerja sama Anda sudah terkirim ke Admin. Mohon ditunggu ya, semoga hasilnya memuaskan! ✨') ->success() ->send(); + + // Notify Admin + User::superAdmin() + ->get() + ->each(function ($admin) use ($isRevision, $record, $cooperationMedia): void { + $user = auth()->user(); + $admin->notify(new BroadcastNotification([ + 'title' => $isRevision ? 'Pembaruan Proposal Kerja Sama ✨' : 'Ada Proposal Kerja Sama Baru! 🚀', + 'body' => $isRevision + ? "Cihuy! {$user->name} baru saja memperbarui proposal kerja sama untuk media {$cooperationMedia->partnerMedia->name} pada kampanye {$record->title}. Yuk, cek perubahannya!" + : "Halo Admin! {$user->name} baru saja mengajukan proposal kerja sama baru untuk media {$cooperationMedia->partnerMedia->name} pada kampanye {$record->title}. Segera diproses ya! 😊", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->id, 'relation' => 1])), + ], + ])); + }); } }) ->modalHeading('Proposal Kerja Sama') ->modalSubmitActionLabel('Kirim Proposal') - ->visible(function (\App\Models\Cooperation $record): bool { - if (! auth()->user()->hasRole('Perusahaan')) { + ->visible(function (Cooperation $record): bool { + $user = auth()->user(); + + if (! $user->hasRole('Perusahaan')) { return false; } $cooperationMedia = $record->cooperationMedia() - ->whereHas('partnerMedia', function (\Illuminate\Database\Eloquent\Builder $query): void { - $query->whereHas('company', function (\Illuminate\Database\Eloquent\Builder $subQuery): void { + ->whereHas('partnerMedia', function (Builder $query): void { + $query->whereHas('company', function (Builder $subQuery): void { $subQuery->where('user_id', auth()->id()); }); }) @@ -104,7 +136,7 @@ protected function setUp(): void && $record->initial_submission_date->toDateString() === now()->toDateString() && ! $record->proposals() ->where('partner_media_id', $cooperationMedia->partner_media_id) - ->where(fn (\Illuminate\Database\Eloquent\Builder $q): \Illuminate\Database\Eloquent\Builder => $q->accepted()->orWhere(fn (\Illuminate\Database\Eloquent\Builder $q): \Illuminate\Database\Eloquent\Builder => $q->pending())) + ->where(fn (Builder $q): Builder => $q->accepted()->orWhere(fn (Builder $q): Builder => $q->pending())) ->exists(); }) ->modalWidth(Width::Large); diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/AcceptAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/AcceptAction.php index 9f741f1..6878eca 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/AcceptAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/AcceptAction.php @@ -3,7 +3,10 @@ namespace App\Filament\Resources\Manage\Cooperations\Actions\Proposal; use App\Enums\ApprovalStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; use App\Models\CooperationProposal; +use App\Models\User; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Notifications\Notification; use Filament\Support\Icons\Heroicon; @@ -24,10 +27,24 @@ protected function setUp(): void ]); Notification::make() - ->title('Proposal Diterima') - ->body("Proposal dari {$record->partnerMedia->name} telah diterima.") + ->title('Berhasil Diproses! ✨') + ->body("Mantap! Proposal dari {$record->partnerMedia->name} telah diterima. Notifikasi sudah dikirim! 💪") ->success() ->send(); + + // Notify User (Partner) + $partnerUser = $record->partnerMedia?->company?->user; + if ($partnerUser) { + $partnerUser->notify(new BroadcastNotification([ + 'title' => 'Horee! Proposal Disetujui 🎉', + 'body' => 'Yeay! Proposal kerja sama Anda telah disetujui oleh Admin. Mari kita lanjut ke tahap berikutnya! 🎊', + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->cooperation_id])), + ], + ])); + } }) ->visible(fn (CooperationProposal $record): bool => ! auth()->user()->hasRole('Perusahaan') && $record->status === ApprovalStatus::PENDING); } diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/RejectAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/RejectAction.php index f30ca73..0bfbc00 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/RejectAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Proposal/RejectAction.php @@ -3,7 +3,10 @@ namespace App\Filament\Resources\Manage\Cooperations\Actions\Proposal; use App\Enums\ApprovalStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; use App\Models\CooperationProposal; +use App\Models\User; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Forms\Components\Textarea; use Filament\Notifications\Notification; @@ -34,10 +37,24 @@ protected function setUp(): void $record->rejectionReasons()->create(['reason' => $data['reason']]); Notification::make() - ->title('Proposal Ditolak') - ->body("Proposal dari {$record->partnerMedia->name} telah ditolak.") + ->title('Berhasil Diproses! ✨') + ->body("Proposal dari {$record->partnerMedia->name} telah ditolak. Notifikasi sudah dikirim! 🙏") ->warning() ->send(); + + // Notify User (Partner) + $partnerUser = $record->partnerMedia?->company?->user; + if ($partnerUser) { + $partnerUser->notify(new BroadcastNotification([ + 'title' => 'Maaf, Proposal Belum Diterima 😔', + 'body' => "Yah, maaf banget... proposal kerja sama Anda belum bisa kami terima saat ini. 💔\n\nAlasan: {$data['reason']}", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->cooperation_id])), + ], + ])); + } }) ->visible(fn (CooperationProposal $record): bool => ! auth()->user()->hasRole('Perusahaan') && $record->status === ApprovalStatus::PENDING) ->modalWidth(Width::Large); diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php index 4fcf825..5f2c068 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Report/AcceptAction.php @@ -3,7 +3,9 @@ namespace App\Filament\Resources\Manage\Cooperations\Actions\Report; use App\Enums\ApprovalStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; use App\Models\Report; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Notifications\Notification; use Filament\Support\Icons\Heroicon; @@ -23,10 +25,30 @@ protected function setUp(): void ]); Notification::make() - ->title('Laporan Diterima') - ->body("Laporan '{$record->title}' telah diterima.") + ->title('Berhasil Diproses! ✨') + ->body("Laporan '{$record->title}' telah diterima. Notifikasi sudah dikirim ke media! 💪") ->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])), + ], + ])); + } + } }) ->visible(fn (Report $record): bool => ! auth()->user()->hasRole('Perusahaan') && $record->status === ApprovalStatus::PENDING); } diff --git a/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php b/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php index ec105d7..0fc8334 100644 --- a/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php +++ b/app/Filament/Resources/Manage/Cooperations/Actions/Report/RejectAction.php @@ -3,7 +3,9 @@ namespace App\Filament\Resources\Manage\Cooperations\Actions\Report; use App\Enums\ApprovalStatus; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; use App\Models\Report; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Forms\Components\Textarea; use Filament\Notifications\Notification; @@ -22,7 +24,7 @@ protected function setUp(): void ->schema([ Textarea::make('reason') ->label('Alasan Penolakan') - ->placeholder('...') + ->placeholder('Jelaskan apa yang perlu diperbaiki...') ->required(), ]) ->action(function (Report $record, array $data): void { @@ -33,10 +35,30 @@ protected function setUp(): void $record->rejectionReasons()->create(['reason' => $data['reason']]); Notification::make() - ->title('Laporan Ditolak') - ->body("Laporan '{$record->title}' telah ditolak.") + ->title('Berhasil Diproses! ✨') + ->body("Laporan '{$record->title}' telah ditolak. Media terkait akan segera mendapatkan notifikasi perbaikan. 🙏") ->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])), + ], + ])); + } + } }) ->visible(fn (Report $record): bool => ! auth()->user()->hasRole('Perusahaan') && $record->status === ApprovalStatus::PENDING) ->modalWidth(Width::Large); diff --git a/app/Filament/Resources/Manage/Cooperations/Pages/CreateCooperation.php b/app/Filament/Resources/Manage/Cooperations/Pages/CreateCooperation.php index 46f4513..0c87567 100644 --- a/app/Filament/Resources/Manage/Cooperations/Pages/CreateCooperation.php +++ b/app/Filament/Resources/Manage/Cooperations/Pages/CreateCooperation.php @@ -3,6 +3,7 @@ namespace App\Filament\Resources\Manage\Cooperations\Pages; use App\Filament\Resources\Manage\Cooperations\CooperationResource; +use App\Notifications\BroadcastNotification; use Filament\Actions\Action; use Filament\Resources\Pages\CreateRecord; use Illuminate\Support\Facades\Storage; @@ -55,5 +56,22 @@ protected function afterCreate(): void 'doc_type' => 'proposal-template', ]) ->toMediaCollection('cooperations'); + + // Notify selected partners + $record->load('partnerMedia.company.user'); + $record->partnerMedia->each(function ($media) use ($record) { + $partnerUser = $media->company?->user; + if ($partnerUser) { + $partnerUser->notify(new BroadcastNotification([ + 'title' => 'Ada Penawaran Kerja Sama Baru! 🤝✨', + 'body' => "Halo! Admin baru saja menawarkan kerja sama \"{$record->title}\" untuk media {$media->name}. Yuk, cek detailnya dan berikan tanggapanmu! 😊", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $record->id])), + ], + ])); + } + }); } } diff --git a/app/Filament/Resources/Manage/Cooperations/Pages/ViewCooperation.php b/app/Filament/Resources/Manage/Cooperations/Pages/ViewCooperation.php index 4f42292..b51ae68 100644 --- a/app/Filament/Resources/Manage/Cooperations/Pages/ViewCooperation.php +++ b/app/Filament/Resources/Manage/Cooperations/Pages/ViewCooperation.php @@ -7,10 +7,8 @@ use Filament\Actions\Action; use Filament\Infolists\Components\RepeatableEntry; use Filament\Infolists\Components\TextEntry; -use Filament\Notifications\Notification; use Filament\Resources\Pages\ViewRecord; use Filament\Support\Enums\FontWeight; -use Illuminate\Support\Facades\Auth; class ViewCooperation extends ViewRecord { @@ -240,68 +238,13 @@ protected function getCompletionDate($record) protected function getHeaderActions(): array { - $isPerusahaan = Auth::user()->hasRole('Perusahaan'); - $record = $this->getRecord(); - - $actions = []; - - if (! $isPerusahaan) { - $actions[] = Action::make('markCompleted') - ->label('Tandai Selesai') - ->icon('heroicon-o-check-circle') - ->color('success') - ->action(function (): void { - // Logic to mark cooperation as completed - Notification::make() - ->title('Kerja Sama Ditandai Selesai') - ->success() - ->send(); - }) - ->visible(fn (): bool => $this->canMarkAsCompleted()); - } else { - // For Perusahaan users, add accept/reject actions if they have a cooperation media record - $cooperationMedia = $record->cooperationMedia() - ->whereHas('partnerMedia', function (\Illuminate\Database\Eloquent\Builder $query): void { - $query->whereHas('company', function (\Illuminate\Database\Eloquent\Builder $subQuery): void { - $subQuery->where('user_id', Auth::id()); - }); - }) - ->first(); - - if ($cooperationMedia && $cooperationMedia->status === ApprovalStatus::PENDING) { - $actions[] = Action::make('accept') - ->label('Terima Kerja Sama') - ->icon('heroicon-o-check-circle') - ->color('success') - ->action(function () use ($cooperationMedia): void { - $cooperationMedia->update(['status' => ApprovalStatus::ACCEPTED]); - Notification::make() - ->title('Kerja Sama Diterima') - ->success() - ->send(); - }); - - $actions[] = Action::make('reject') - ->label('Tolak Kerja Sama') - ->icon('heroicon-o-x-circle') - ->color('danger') - ->action(function () use ($cooperationMedia): void { - $cooperationMedia->update(['status' => ApprovalStatus::REJECTED]); - Notification::make() - ->title('Kerja Sama Ditolak') - ->success() - ->send(); - }); - } - } - - $actions[] = Action::make('back') - ->label('Kembali') - ->url(fn (): string => static::getResource()::getUrl('index')) - ->outlined() - ->color('secondary'); - - return $actions; + return [ + Action::make('back') + ->label('Kembali') + ->url(fn (): string => static::getResource()::getUrl('index')) + ->outlined() + ->color('secondary'), + ]; } protected function canMarkAsCompleted(): bool diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php index 28a9879..5d32354 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/CooperationProposalRelationManager.php @@ -5,6 +5,7 @@ use App\Enums\ApprovalStatus; use App\Filament\Resources\Manage\Cooperations\Actions\Proposal\AcceptAction; use App\Filament\Resources\Manage\Cooperations\Actions\Proposal\RejectAction; +use App\Models\CooperationProposal; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; use Filament\Actions\ViewAction; @@ -34,7 +35,7 @@ public function table(Table $table): Table TextColumn::make('e_catalog') ->label('E-Catalog') - ->url(fn (\App\Models\CooperationProposal $record): ?string => $record->e_catalog) + ->url(fn (CooperationProposal $record): ?string => $record->e_catalog) ->openUrlInNewTab() ->color('primary'), @@ -100,10 +101,10 @@ public function infolist(Schema $infolist): Schema TextEntry::make('e_catalog') ->label('E-Catalog') - ->url(fn (\App\Models\CooperationProposal $record): ?string => $record->e_catalog) + ->url(fn (CooperationProposal $record): ?string => $record->e_catalog) ->openUrlInNewTab() ->color('primary') - ->visible(fn (\App\Models\CooperationProposal $record): ?string => $record->e_catalog), + ->visible(fn (CooperationProposal $record): ?string => $record->e_catalog), TextEntry::make('description') ->label('Deskripsi') @@ -111,13 +112,13 @@ public function infolist(Schema $infolist): Schema PdfViewerEntry::make('proposal_attachment') ->label('Lampiran Proposal') - ->getStateUsing(fn (\App\Models\CooperationProposal $record): ?string => optional($record->getMedia('cooperations')->where('custom_properties.doc_type', 'proposal-attachment')->sortByDesc('created_at')->first())->getPathRelativeToRoot()) + ->getStateUsing(fn (CooperationProposal $record): ?string => optional($record->getMedia('cooperations')->where('custom_properties.doc_type', 'proposal-attachment')->sortByDesc('created_at')->first())->getPathRelativeToRoot()) ->disk(config('filesystems.default')), TextEntry::make('rejectionReasons.reason') ->label('Alasan Penolakan') ->listWithLineBreaks() - ->visible(fn (\App\Models\CooperationProposal $record): bool => $record->status === ApprovalStatus::REJECTED && $record->rejectionReasons->isNotEmpty()) + ->visible(fn (CooperationProposal $record): bool => $record->status === ApprovalStatus::REJECTED && $record->rejectionReasons->isNotEmpty()) ->columnSpanFull(), ]) ->columns(1); diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php index efe1aaf..f173926 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/ReportRelationManager.php @@ -2,18 +2,26 @@ namespace App\Filament\Resources\Manage\Cooperations\RelationManagers; +use App\Enums\ApprovalStatus; use App\Filament\Resources\Manage\Cooperations\Actions\Report\AcceptAction; use App\Filament\Resources\Manage\Cooperations\Actions\Report\RejectAction; +use App\Filament\Resources\Manage\Cooperations\CooperationResource; +use App\Models\Report; +use App\Models\User; +use App\Notifications\BroadcastNotification; +use Filament\Actions\Action; use Filament\Actions\CreateAction; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\SpatieMediaLibraryFileUpload; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; +use Filament\Notifications\Notification; use Filament\Resources\RelationManagers\RelationManager; use Filament\Schemas\Schema; use Filament\Support\Enums\Width; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; +use Illuminate\Database\Eloquent\Model; class ReportRelationManager extends RelationManager { @@ -26,6 +34,11 @@ public function isReadOnly(): bool return false; } + public static function getBadge(Model $ownerRecord, string $pageClass): ?string + { + return $ownerRecord->reports()->count(); + } + public function form(Schema $schema): Schema { return $schema @@ -86,7 +99,7 @@ public function table(Table $table): Table TextColumn::make('link') ->label('Link') ->limit(30) - ->url(fn (\App\Models\Report $record): string => $record->link) + ->url(fn (Report $record): string => $record->link) ->openUrlInNewTab(), TextColumn::make('status') @@ -113,14 +126,45 @@ public function table(Table $table): Table return $data; }) + ->after(function (Report $record): void { + Notification::make() + ->title('Laporan Berhasil Terkirim! 🚀✨') + ->body('Hore! Laporan Anda sudah masuk ke sistem. Admin akan segera memeriksanya. Terima kasih atas kerja kerasnya! 💪') + ->success() + ->send(); + + // Notify Admins + User::superAdmin() + ->get() + ->each(function ($admin) use ($record): void { + $user = auth()->user(); + $admin->notify(new BroadcastNotification([ + 'title' => 'Ada Laporan Baru! 📝✨', + 'body' => "Halo Admin! {$user->name} baru saja mengirimkan laporan \"{$record->title}\" untuk kerja sama \"{$this->getOwnerRecord()->title}\". Yuk, dicek! 😊", + 'action' => [ + Action::make('view') + ->label('Lihat') + ->url(CooperationResource::getUrl('view', ['record' => $this->getOwnerRecord()->id, 'relation' => 3])), + ], + ])); + }); + }) ->visible(function (): bool { + $user = auth()->user(); + + if (! $user || ! $user->hasRole('Perusahaan')) { + return false; + } + $taskAssignment = $this->getOwnerRecord()->taskAssignments()->first(); if (! $taskAssignment) { return false; } - return $taskAssignment->reports()->count() < $taskAssignment->report_amount; + return $taskAssignment->reports() + ->where('status', '!=', ApprovalStatus::REJECTED) + ->count() < $taskAssignment->report_amount; }), ]) ->toolbarActions([]); diff --git a/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php b/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php index 76bb0ac..96ab2c8 100644 --- a/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php +++ b/app/Filament/Resources/Manage/Cooperations/RelationManagers/TaskAssignmentRelationManager.php @@ -3,7 +3,6 @@ namespace App\Filament\Resources\Manage\Cooperations\RelationManagers; use App\Enums\ApprovalStatus; -use App\Models\TaskAssignment; use Filament\Actions\BulkActionGroup; use Filament\Actions\CreateAction; use Filament\Actions\DeleteAction; @@ -97,12 +96,6 @@ public function table(Table $table): Table TextColumn::make('report_amount') ->label('Jumlah Laporan'), - - TextColumn::make('report_count') - ->label('Laporan Diterima') - ->getStateUsing(function (TaskAssignment $record): string { - return $record->reports()->count().' / '.($record->report_amount * $record->partnerMedia()->count()); - }), ]) ->filters([ // diff --git a/app/Models/User.php b/app/Models/User.php index 9117a81..b1e6cbf 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Enums\IsActive; +use App\Enums\RoleEnum; use Filament\Models\Contracts\FilamentUser; use Filament\Panel; use Illuminate\Contracts\Auth\MustVerifyEmail; @@ -42,7 +43,7 @@ public function canAccessPanel(Panel $panel): bool protected function withoutDeveloper(Builder $query): void { $query->whereDoesntHave('roles', function ($q) { - $q->where('name', 'Developer'); + $q->where('name', RoleEnum::DEVELOPER); }); } @@ -52,6 +53,14 @@ protected function active(Builder $query): void $query->where('is_active', IsActive::ACTIVE); } + #[Scope] + protected function superAdmin(Builder $query): void + { + $query->whereHas('roles', function ($q) { + $q->whereIn('name', [RoleEnum::DEVELOPER, RoleEnum::ADMINISTRATOR]); + }); + } + public function company(): HasOne { return $this->hasOne(Company::class); diff --git a/app/Notifications/BroadcastNotification.php b/app/Notifications/BroadcastNotification.php index 2045a76..777aaa0 100644 --- a/app/Notifications/BroadcastNotification.php +++ b/app/Notifications/BroadcastNotification.php @@ -23,9 +23,14 @@ public function via($notifiable): array public function toDatabase($notifiable): array { - return FilamentNotification::make() + $notification = FilamentNotification::make() ->title($this->payload['title']) - ->body($this->payload['body']) - ->getDatabaseMessage(); + ->body($this->payload['body']); + + if (! empty($this->payload['action'])) { + $notification->actions($this->payload['action']); + } + + return $notification->getDatabaseMessage(); } }