diff --git a/app/Filament/Pages/System/Changelog/Actions/CreateChangelogAction.php b/app/Filament/Pages/System/Changelog/Actions/CreateChangelogAction.php index 8b639b8..549c6c3 100644 --- a/app/Filament/Pages/System/Changelog/Actions/CreateChangelogAction.php +++ b/app/Filament/Pages/System/Changelog/Actions/CreateChangelogAction.php @@ -26,7 +26,7 @@ protected function setUp(): void $action->makeModalSubmitAction('createAnother', arguments: ['another' => true]) ->label('Simpan dan Tambah Lagi'), ]) - ->modalWidth(Width::ThreeExtraLarge) + ->modalWidth(Width::FourExtraLarge) ->schema(fn ($livewire) => $livewire->changelogFormSchema()); } } diff --git a/app/Filament/Pages/System/Changelog/Actions/EditChangelogAction.php b/app/Filament/Pages/System/Changelog/Actions/EditChangelogAction.php index 2227502..bc28e0f 100644 --- a/app/Filament/Pages/System/Changelog/Actions/EditChangelogAction.php +++ b/app/Filament/Pages/System/Changelog/Actions/EditChangelogAction.php @@ -27,7 +27,7 @@ protected function setUp(): void ->modalHeading('Ubah Data') ->modalSubmitActionLabel('Simpan') ->modalCancelActionLabel('Batal') - ->modalWidth(Width::ThreeExtraLarge) + ->modalWidth(Width::FourExtraLarge) ->schema(fn ($livewire) => $livewire->changelogFormSchema()) ->fillForm(fn (Changelog $record): array => $record->toArray()); } diff --git a/app/Filament/Pages/System/Changelog/Actions/MarkAsReadAction.php b/app/Filament/Pages/System/Changelog/Actions/MarkAsReadAction.php new file mode 100644 index 0000000..f64474d --- /dev/null +++ b/app/Filament/Pages/System/Changelog/Actions/MarkAsReadAction.php @@ -0,0 +1,36 @@ +record(fn (array $arguments): Changelog => Changelog::findOrFail($arguments['record'])) + ->label('Tandai Sudah Baca') + ->icon('heroicon-m-check-badge') + ->size('sm') + ->color('primary') + ->action(function (Changelog $record, $livewire) { + if (! $record->users()->where('user_id', auth()->id())->exists()) { + $record->users()->attach(auth()->id(), ['read_at' => now()]); + + SystemNotification::send('changelog_read', ['title' => $record->title])->send(); + + $livewire->dispatch('refresh-changelog'); + $livewire->dispatch('refresh-expansion', id: $livewire->changelogs()->first()?->id); + } + }); + } +} diff --git a/app/Filament/Pages/System/Changelog/Index.php b/app/Filament/Pages/System/Changelog/Index.php index 2bb8dcb..c88441d 100644 --- a/app/Filament/Pages/System/Changelog/Index.php +++ b/app/Filament/Pages/System/Changelog/Index.php @@ -6,6 +6,7 @@ use App\Filament\Pages\System\Changelog\Actions\CreateChangelogAction; use App\Filament\Pages\System\Changelog\Actions\DeleteChangelogAction; use App\Filament\Pages\System\Changelog\Actions\EditChangelogAction; +use App\Filament\Pages\System\Changelog\Actions\MarkAsReadAction; use App\Filament\Support\SystemNotification; use App\Models\Changelog; use BackedEnum; @@ -44,6 +45,13 @@ class Index extends Page implements HasActions, HasForms protected string $view = 'filament.pages.system.changelog.index'; + public static function getNavigationBadge(): ?string + { + $unreadCount = Changelog::whereDoesntHave('users', fn ($query) => $query->where('user_id', auth()->id()))->count(); + + return $unreadCount > 0 ? (string) $unreadCount : null; + } + public static function getPagePermission(): string { return 'View:Changelog'; @@ -69,7 +77,15 @@ public function techStack(): array #[Computed] public function changelogs(): Collection { - return Changelog::latest('release_date')->get(); + return Changelog::with([ + 'users' => fn ($query) => $query->where('user_id', auth()->id()), + ]) + ->get() + ->sortBy([ + fn ($a, $b) => $a->is_read <=> $b->is_read, + ['release_date', 'desc'], + ]) + ->values(); } protected function getHeaderActions(): array @@ -92,6 +108,11 @@ public function deleteChangelogAction(): Action ->visible(fn () => auth()->user()?->can('Delete:Changelog')); } + public function markAsReadAction(): Action + { + return MarkAsReadAction::make(); + } + #[Computed] public function emptyHeading(): string { diff --git a/app/Models/Changelog.php b/app/Models/Changelog.php index abe60d6..c21701a 100644 --- a/app/Models/Changelog.php +++ b/app/Models/Changelog.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; class Changelog extends Model @@ -54,4 +55,16 @@ protected function formattedUpdatedAt(): Attribute { return Attribute::get(fn () => $this->updated_at?->translatedFormat('l, d M Y H:i')); } + + protected function isRead(): Attribute + { + return Attribute::get(fn () => $this->users->isNotEmpty()); + } + + // --- Relations --- + + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class)->withPivot('read_at')->withTimestamps(); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 2394af9..25081fa 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; @@ -78,6 +79,11 @@ protected function formattedUpdatedAt(): Attribute // --- Relations --- + public function changelogs(): BelongsToMany + { + return $this->belongsToMany(Changelog::class)->withPivot('read_at')->withTimestamps(); + } + public function settings(): HasOne { return $this->hasOne(UserSetting::class)->withDefault([ diff --git a/database/migrations/2026_04_07_085720_create_changelog_user_table.php b/database/migrations/2026_04_07_085720_create_changelog_user_table.php new file mode 100644 index 0000000..1edcaee --- /dev/null +++ b/database/migrations/2026_04_07_085720_create_changelog_user_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('changelog_id')->constrained()->cascadeOnDelete(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->timestamp('read_at')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); + + $table->unique(['changelog_id', 'user_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('changelog_user'); + } +}; diff --git a/database/seeders/ShieldSeeder.php b/database/seeders/ShieldSeeder.php index aa4627c..dcf3246 100644 --- a/database/seeders/ShieldSeeder.php +++ b/database/seeders/ShieldSeeder.php @@ -158,6 +158,8 @@ public function run(): void "name": "Mahasiswa", "guard_name": "web", "permissions" : [ + "View:Changelog", + "View:Assignment", "ViewAny:Assignment", @@ -181,6 +183,8 @@ public function run(): void "name": "Kosma", "guard_name": "web", "permissions" : [ + "View:Changelog", + "Create:Assignment", "Delete:Assignment", "ForceDelete:Assignment", diff --git a/lang/id/notif.php b/lang/id/notif.php index abe156e..32dba5a 100644 --- a/lang/id/notif.php +++ b/lang/id/notif.php @@ -141,6 +141,10 @@ 'title' => 'Pengguna Dinonaktifkan ⛔👋', 'body' => 'Status akun pengguna telah berhasil dinonaktifkan. Istirahat dulu ya... 😴', ], + 'changelog_read' => [ + 'title' => 'Mantap! Sudah Dibaca ✅✨', + 'body' => 'Pembaruan ":title" sudah ditandai. Makin update makin jago! 🚀🔥', + ], // UI Labels & Descriptions 'labels' => [ @@ -429,6 +433,10 @@ 'title' => 'Penonaktifan Akun Berhasil', 'body' => 'Status akun pengguna terpilih telah diubah menjadi tidak aktif.', ], + 'changelog_read' => [ + 'title' => 'Pembaruan Berhasil Ditandai', + 'body' => 'Catatan rilis ":title" telah berhasil ditandai sebagai sudah dibaca.', + ], // UI Labels & Descriptions 'labels' => [ diff --git a/resources/views/filament/pages/system/changelog/index.blade.php b/resources/views/filament/pages/system/changelog/index.blade.php index 8a24859..33b8618 100644 --- a/resources/views/filament/pages/system/changelog/index.blade.php +++ b/resources/views/filament/pages/system/changelog/index.blade.php @@ -19,8 +19,8 @@
+
{{ $tech }}
@@ -33,67 +33,126 @@
+ @foreach ($log->changes as $change)
+
- @foreach ($log->changes as $change)
-
+