From 1f85251da3b8ac51b1216ecfae278f63950c1920 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 08:09:08 +0700 Subject: [PATCH 01/26] feat: icon link website publik di topbar panel --- app/Providers/AppServiceProvider.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 72104b9..90bc862 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -12,6 +12,9 @@ use App\Observers\CashRecordObserver; use App\Observers\PostObserver; use App\Observers\UserObserver; use App\Observers\VoteObserver; +use Filament\Support\Facades\FilamentView; +use Filament\View\PanelsRenderHook; +use Illuminate\Support\HtmlString; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -23,5 +26,19 @@ class AppServiceProvider extends ServiceProvider Activity::observe(ActivityObserver::class); Vote::observe(VoteObserver::class); Post::observe(PostObserver::class); + + FilamentView::registerRenderHook( + PanelsRenderHook::TOPBAR_LOGO_AFTER, + fn () => new HtmlString( + ' + + + + ' + ) + ); } } From 3f42da1e5fa4ebc6797400feb558eaf358562fff Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 08:33:04 +0700 Subject: [PATCH 02/26] feat: tambah halaman profil untuk edit data pribadi (nama, email, HP, alamat, password) --- app/Filament/Pages/EditProfile.php | 28 +++++++++++++++++++ app/Providers/Filament/AdminPanelProvider.php | 1 + 2 files changed, 29 insertions(+) create mode 100644 app/Filament/Pages/EditProfile.php diff --git a/app/Filament/Pages/EditProfile.php b/app/Filament/Pages/EditProfile.php new file mode 100644 index 0000000..e3670d0 --- /dev/null +++ b/app/Filament/Pages/EditProfile.php @@ -0,0 +1,28 @@ +components([ + $this->getNameFormComponent(), + $this->getEmailFormComponent(), + TextInput::make('phone') + ->label('Nomor HP') + ->tel() + ->maxLength(20), + TextInput::make('address') + ->label('Alamat') + ->maxLength(255), + $this->getPasswordFormComponent(), + $this->getPasswordConfirmationFormComponent(), + $this->getCurrentPasswordFormComponent(), + ]); + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index c81556a..749372e 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -33,6 +33,7 @@ class AdminPanelProvider extends PanelProvider ->path('dashboard') ->viteTheme('resources/css/filament/admin/theme.css') ->login() + ->profile(\App\Filament\Pages\EditProfile::class) ->colors([ 'primary' => Color::Amber, ]) From 006e4db3be932fd50fa20daaff15955108cc4164 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 09:06:31 +0700 Subject: [PATCH 03/26] chore: hapus halaman profile akun dari admin panel --- app/Filament/Pages/EditProfile.php | 28 ------------------- app/Providers/Filament/AdminPanelProvider.php | 2 +- 2 files changed, 1 insertion(+), 29 deletions(-) delete mode 100644 app/Filament/Pages/EditProfile.php diff --git a/app/Filament/Pages/EditProfile.php b/app/Filament/Pages/EditProfile.php deleted file mode 100644 index e3670d0..0000000 --- a/app/Filament/Pages/EditProfile.php +++ /dev/null @@ -1,28 +0,0 @@ -components([ - $this->getNameFormComponent(), - $this->getEmailFormComponent(), - TextInput::make('phone') - ->label('Nomor HP') - ->tel() - ->maxLength(20), - TextInput::make('address') - ->label('Alamat') - ->maxLength(255), - $this->getPasswordFormComponent(), - $this->getPasswordConfirmationFormComponent(), - $this->getCurrentPasswordFormComponent(), - ]); - } -} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 749372e..7942e6d 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -33,7 +33,7 @@ class AdminPanelProvider extends PanelProvider ->path('dashboard') ->viteTheme('resources/css/filament/admin/theme.css') ->login() - ->profile(\App\Filament\Pages\EditProfile::class) + ->profile(false) ->colors([ 'primary' => Color::Amber, ]) From a0f4882d0ba14a9033ac47dfd423914b1066a07b Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 09:08:34 +0700 Subject: [PATCH 04/26] feat: tambah custom EditProfile sebagai base halaman profile akun --- app/Filament/Pages/EditProfile.php | 20 +++++++++++++++++++ app/Providers/Filament/AdminPanelProvider.php | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 app/Filament/Pages/EditProfile.php diff --git a/app/Filament/Pages/EditProfile.php b/app/Filament/Pages/EditProfile.php new file mode 100644 index 0000000..dbfeda9 --- /dev/null +++ b/app/Filament/Pages/EditProfile.php @@ -0,0 +1,20 @@ +components([ + $this->getNameFormComponent(), + $this->getEmailFormComponent(), + $this->getPasswordFormComponent(), + $this->getPasswordConfirmationFormComponent(), + $this->getCurrentPasswordFormComponent(), + ]); + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 7942e6d..749372e 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -33,7 +33,7 @@ class AdminPanelProvider extends PanelProvider ->path('dashboard') ->viteTheme('resources/css/filament/admin/theme.css') ->login() - ->profile(false) + ->profile(\App\Filament\Pages\EditProfile::class) ->colors([ 'primary' => Color::Amber, ]) From 76a2125fb33be6e7086f3234b3c033bb2dd30679 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 09:10:46 +0700 Subject: [PATCH 05/26] feat: batasi akses MemberPointResource hanya untuk super_admin --- app/Filament/Resources/MemberPoints/MemberPointResource.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Filament/Resources/MemberPoints/MemberPointResource.php b/app/Filament/Resources/MemberPoints/MemberPointResource.php index ccad068..adb0da9 100644 --- a/app/Filament/Resources/MemberPoints/MemberPointResource.php +++ b/app/Filament/Resources/MemberPoints/MemberPointResource.php @@ -21,6 +21,11 @@ class MemberPointResource extends Resource protected static string|\UnitEnum|null $navigationGroup = 'Organisasi'; protected static ?string $navigationLabel = 'Poin Anggota'; + public static function canAccess(): bool + { + return auth()->user()->hasRole('super_admin'); + } + public static function form(Schema $schema): Schema { return MemberPointForm::configure($schema); From c5c0c5b2a60b4abc3db6fac7e5d530ed1aef78dd Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 21:27:28 +0700 Subject: [PATCH 06/26] chore: hapus menu Website Publik dari sidenav admin --- app/Providers/Filament/AdminPanelProvider.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 749372e..3f96bf7 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -7,7 +7,6 @@ use BezhanSalleh\FilamentShield\FilamentShieldPlugin; use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; use Filament\Http\Middleware\DispatchServingFilamentEvent; -use Filament\Navigation\NavigationItem; use Filament\Pages\Dashboard; use Filament\Panel; use Filament\PanelProvider; @@ -52,12 +51,7 @@ class AdminPanelProvider extends PanelProvider 'Konten', 'Organisasi', ]) - ->navigationItems([ - NavigationItem::make('Website Publik') - ->url('/', shouldOpenInNewTab: true) - ->icon('heroicon-o-globe-alt') - ->sort(99), - ]) + ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets') ->widgets([ AccountWidget::class, From 438fb96561b07cdff510d821d3bde9059fc8a5f9 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:23:12 +0700 Subject: [PATCH 07/26] feat: tambah role koordinator dengan akses buat/edit/hapus kegiatan milik sendiri --- app/Filament/Resources/Users/Schemas/UserForm.php | 15 ++++++++++++++- config/filament-shield.php | 5 +++-- database/seeders/PermissionSeeder.php | 15 ++++++++++++++- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/Filament/Resources/Users/Schemas/UserForm.php b/app/Filament/Resources/Users/Schemas/UserForm.php index c5aa66d..3cabc9a 100644 --- a/app/Filament/Resources/Users/Schemas/UserForm.php +++ b/app/Filament/Resources/Users/Schemas/UserForm.php @@ -36,7 +36,20 @@ class UserForm ->columnSpanFull(), DatePicker::make('last_activity_date')->label('Terakhir Aktif'), Select::make('roles')->relationship('roles', 'name') - ->multiple()->preload()->label('Role'), + ->multiple()->preload()->label('Role') + ->getOptionLabelFromRecordUsing(fn ($record) => $record->name) + ->options(function () { + $user = auth()->user(); + $query = \Spatie\Permission\Models\Role::query() + ->whereNotIn('name', ['super_admin', 'panel_user']); + + // Hanya ketua (AssignKoordinator) yang bisa assign role koordinator + if (! $user->can('AssignKoordinator')) { + $query->where('name', '!=', 'koordinator'); + } + + return $query->pluck('name', 'id'); + }), ]); } } diff --git a/config/filament-shield.php b/config/filament-shield.php index 9e7c472..8ce5475 100644 --- a/config/filament-shield.php +++ b/config/filament-shield.php @@ -233,8 +233,9 @@ return [ */ 'custom_permissions' => [ - 'ViewDraft:Activity', // Lihat kegiatan berstatus draft milik user lain (hanya super_admin) - 'Publish:Post', // Publish / unpublish artikel (editor) + 'ViewDraft:Activity', // Lihat kegiatan berstatus draft milik user lain (hanya super_admin) + 'Publish:Post', // Publish / unpublish artikel (editor) + 'AssignKoordinator', // Assign/cabut role koordinator ke anggota (hanya ketua) ], /* diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php index abef586..d20eb6c 100644 --- a/database/seeders/PermissionSeeder.php +++ b/database/seeders/PermissionSeeder.php @@ -13,7 +13,7 @@ class PermissionSeeder extends Seeder app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions(); // Buat roles jika belum ada - foreach (['super_admin', 'ketua', 'bendahara', 'pengurus', 'anggota', 'auditor', 'editor'] as $role) { + foreach (['super_admin', 'ketua', 'bendahara', 'pengurus', 'anggota', 'auditor', 'editor', 'koordinator'] as $role) { Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']); } @@ -29,12 +29,25 @@ class PermissionSeeder extends Seeder $anggota = Role::findByName('anggota'); $auditor = Role::findByName('auditor'); $editor = Role::findByName('editor'); + $koordinator = Role::findByName('koordinator'); $ketua->syncPermissions(Permission::where('name', 'not like', '%Role%') ->where('name', 'not like', '%Permission%') ->where('name', '!=', 'ViewDraft:Activity') ->get()); + // Pastikan ketua punya AssignKoordinator + if ($p = Permission::where('name', 'AssignKoordinator')->first()) { + $ketua->givePermissionTo($p); + } + + $koordinator->syncPermissions(Permission::whereIn('name', [ + 'ViewAny:Activity', 'View:Activity', 'Create:Activity', 'Update:Activity', 'Delete:Activity', + 'ViewAny:Vote', 'View:Vote', + 'ViewAny:Post', 'View:Post', 'Create:Post', 'Update:Post', 'Delete:Post', + 'ViewAny:MemberPoint', 'View:MemberPoint', + ])->get()); + $bendahara->syncPermissions(Permission::where('name', 'like', '%CashRecord%') ->orWhere('name', 'like', '%CashCategory%') ->orWhere('name', 'like', '%MemberDue%') From b77a67edbfc428984e048e488d80e731c1ec5fef Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:29:15 +0700 Subject: [PATCH 08/26] feat: tambah penanggung jawab divisi (leader_id) dari role pengurus --- .../Divisions/Schemas/DivisionForm.php | 8 +++++++ .../Divisions/Tables/DivisionsTable.php | 1 + app/Models/Division.php | 7 +++++- ...52811_add_leader_id_to_divisions_table.php | 23 +++++++++++++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2026_04_05_152811_add_leader_id_to_divisions_table.php diff --git a/app/Filament/Resources/Divisions/Schemas/DivisionForm.php b/app/Filament/Resources/Divisions/Schemas/DivisionForm.php index a849a1d..699e371 100644 --- a/app/Filament/Resources/Divisions/Schemas/DivisionForm.php +++ b/app/Filament/Resources/Divisions/Schemas/DivisionForm.php @@ -2,6 +2,8 @@ namespace App\Filament\Resources\Divisions\Schemas; +use App\Models\User; +use Filament\Forms\Components\Select; use Filament\Forms\Components\Textarea; use Filament\Forms\Components\TextInput; use Filament\Schemas\Schema; @@ -13,6 +15,12 @@ class DivisionForm return $schema->components([ TextInput::make('name')->label('Nama')->required(), Textarea::make('description')->label('Deskripsi')->rows(3)->columnSpanFull(), + Select::make('leader_id')->label('Penanggung Jawab') + ->options( + User::role('pengurus')->where('status', 'aktif')->pluck('name', 'id') + ) + ->searchable() + ->nullable(), ]); } } diff --git a/app/Filament/Resources/Divisions/Tables/DivisionsTable.php b/app/Filament/Resources/Divisions/Tables/DivisionsTable.php index 96a7ad6..71f82dd 100644 --- a/app/Filament/Resources/Divisions/Tables/DivisionsTable.php +++ b/app/Filament/Resources/Divisions/Tables/DivisionsTable.php @@ -16,6 +16,7 @@ class DivisionsTable ->columns([ TextColumn::make('name')->label('Nama')->searchable()->sortable(), TextColumn::make('description')->label('Deskripsi')->limit(50), + TextColumn::make('leader.name')->label('Penanggung Jawab')->default('-'), TextColumn::make('members_count')->counts('members')->label('Anggota'), ]) ->recordActions([EditAction::make()]) diff --git a/app/Models/Division.php b/app/Models/Division.php index a35c6dc..87789d3 100644 --- a/app/Models/Division.php +++ b/app/Models/Division.php @@ -7,10 +7,15 @@ use Illuminate\Database\Eloquent\Relations\HasMany; class Division extends Model { - protected $fillable = ['name', 'description']; + protected $fillable = ['name', 'description', 'leader_id']; public function members(): HasMany { return $this->hasMany(User::class); } + + public function leader(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(User::class, 'leader_id'); + } } diff --git a/database/migrations/2026_04_05_152811_add_leader_id_to_divisions_table.php b/database/migrations/2026_04_05_152811_add_leader_id_to_divisions_table.php new file mode 100644 index 0000000..d33a2ca --- /dev/null +++ b/database/migrations/2026_04_05_152811_add_leader_id_to_divisions_table.php @@ -0,0 +1,23 @@ +foreignId('leader_id')->nullable()->constrained('users')->nullOnDelete()->after('description'); + }); + } + + public function down(): void + { + Schema::table('divisions', function (Blueprint $table) { + $table->dropForeignIdFor(\App\Models\User::class, 'leader_id'); + $table->dropColumn('leader_id'); + }); + } +}; From 43a2fe4eea808cab606a5cbc42b898e7da567a42 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:35:53 +0700 Subject: [PATCH 09/26] chore: update UserSeeder agar tiap divisi punya 1 pengurus (leader) dan 3-8 anggota --- database/seeders/UserSeeder.php | 34 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 5f2a2d5..8a1d950 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use App\Models\Division; use App\Models\User; use Illuminate\Database\Seeder; @@ -10,9 +9,9 @@ class UserSeeder extends Seeder { public function run(): void { - $divisions = Division::pluck('id')->toArray(); + $divisions = \App\Models\Division::all(); - // super_admin + // super_admin (tanpa divisi) User::factory()->createOne([ 'name' => 'Super Admin', 'email' => 'admin@admin.com', @@ -21,22 +20,29 @@ class UserSeeder extends Seeder 'status' => 'aktif', ])->assignRole('super_admin'); - // 2 user per role - foreach (['ketua', 'bendahara', 'pengurus', 'auditor', 'anggota'] as $role) { - User::factory(2)->create(['division_id' => fake()->randomElement($divisions)]) - ->each(fn ($user) => $user->assignRole($role)); + // ketua, bendahara, auditor — tanpa divisi spesifik + foreach (['ketua', 'bendahara', 'auditor'] as $role) { + User::factory(2)->create()->each(fn ($u) => $u->assignRole($role)); } // 1 editor User::factory()->createOne([ - 'name' => 'Editor Konten', - 'email' => 'editor@persegi.test', - 'password' => bcrypt('password'), - 'status' => 'aktif', - 'division_id' => fake()->randomElement($divisions), + 'name' => 'Editor Konten', + 'email' => 'editor@persegi.test', + 'password' => bcrypt('password'), + 'status' => 'aktif', ])->assignRole('editor'); - // 2 user tanpa role - User::factory(2)->create(['division_id' => fake()->randomElement($divisions)]); + // Setiap divisi: 1 pengurus (jadi leader) + 3–8 anggota + foreach ($divisions as $division) { + $pengurus = User::factory()->create(['division_id' => $division->id]); + $pengurus->assignRole('pengurus'); + + $division->update(['leader_id' => $pengurus->id]); + + $count = rand(3, 8); + User::factory($count)->create(['division_id' => $division->id]) + ->each(fn ($u) => $u->assignRole('anggota')); + } } } From a234decae1a94b84ee8e4a84afa101785e0431ff Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:42:11 +0700 Subject: [PATCH 10/26] fix: user nonaktif tidak bisa akses panel Filament --- app/Models/User.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/User.php b/app/Models/User.php index 5de451b..aace0aa 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -67,7 +67,7 @@ class User extends Authenticatable implements FilamentUser public function canAccessPanel(Panel $panel): bool { - return true; + return $this->status === 'aktif'; } public function canImpersonate(): bool From 56bba3565b51322df164e30ec6d7a28ecd253cad Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:45:07 +0700 Subject: [PATCH 11/26] chore: sembunyikan role anggota dari dropdown UI (di-assign otomatis) --- app/Filament/Resources/Users/Schemas/UserForm.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/Filament/Resources/Users/Schemas/UserForm.php b/app/Filament/Resources/Users/Schemas/UserForm.php index 3cabc9a..cfcd5ef 100644 --- a/app/Filament/Resources/Users/Schemas/UserForm.php +++ b/app/Filament/Resources/Users/Schemas/UserForm.php @@ -41,9 +41,8 @@ class UserForm ->options(function () { $user = auth()->user(); $query = \Spatie\Permission\Models\Role::query() - ->whereNotIn('name', ['super_admin', 'panel_user']); + ->whereNotIn('name', ['super_admin', 'panel_user', 'anggota']); - // Hanya ketua (AssignKoordinator) yang bisa assign role koordinator if (! $user->can('AssignKoordinator')) { $query->where('name', '!=', 'koordinator'); } From b5ac4d892fda579d151081becaa2f1e21ecb140d Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:48:23 +0700 Subject: [PATCH 12/26] fix: role anggota tidak bisa dihapus via form edit user --- app/Observers/UserObserver.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php index f224712..3b11022 100644 --- a/app/Observers/UserObserver.php +++ b/app/Observers/UserObserver.php @@ -12,6 +12,11 @@ class UserObserver { public function updated(User $user): void { + // Pastikan role anggota selalu ada + if (! $user->hasRole('anggota')) { + $user->assignRole('anggota'); + } + // Log perubahan status anggota if ($user->wasChanged('status')) { MemberStatusLog::create([ From 446b869d75d398827ffb35ad476990ebe15ec389 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:52:07 +0700 Subject: [PATCH 13/26] chore: sembunyikan role anggota dari tabel dan form user --- app/Filament/Resources/Users/Tables/UsersTable.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Filament/Resources/Users/Tables/UsersTable.php b/app/Filament/Resources/Users/Tables/UsersTable.php index b28f59c..0d66d88 100644 --- a/app/Filament/Resources/Users/Tables/UsersTable.php +++ b/app/Filament/Resources/Users/Tables/UsersTable.php @@ -23,7 +23,9 @@ class UsersTable TextColumn::make('division.name')->label('Divisi')->sortable(), TextColumn::make('status')->badge() ->color(fn ($state) => $state === 'aktif' ? 'success' : 'danger'), - TextColumn::make('roles.name')->label('Role')->badge(), + TextColumn::make('roles.name')->label('Role')->badge() + ->getStateUsing(fn ($record) => $record->roles->pluck('name')->filter(fn ($r) => $r !== 'anggota')->values()) + ->placeholder('-'), ]) ->filters([ SelectFilter::make('status') From 57de63cee996617b41212230d3604c325431f517 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 22:55:20 +0700 Subject: [PATCH 14/26] chore: sembunyikan badge role anggota di field form edit user --- app/Filament/Resources/Users/Schemas/UserForm.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Filament/Resources/Users/Schemas/UserForm.php b/app/Filament/Resources/Users/Schemas/UserForm.php index cfcd5ef..fde2548 100644 --- a/app/Filament/Resources/Users/Schemas/UserForm.php +++ b/app/Filament/Resources/Users/Schemas/UserForm.php @@ -38,6 +38,12 @@ class UserForm Select::make('roles')->relationship('roles', 'name') ->multiple()->preload()->label('Role') ->getOptionLabelFromRecordUsing(fn ($record) => $record->name) + ->afterStateHydrated(function ($component, $state) { + if (is_array($state)) { + $filtered = array_filter($state, fn ($id) => \Spatie\Permission\Models\Role::find($id)?->name !== 'anggota'); + $component->state(array_values($filtered)); + } + }) ->options(function () { $user = auth()->user(); $query = \Spatie\Permission\Models\Role::query() From cbadc550fc97caa53c29b471d154a84b937086d6 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 23:06:42 +0700 Subject: [PATCH 15/26] fix: policy koordinator hilang, source_type hardcode, validasi executed_at salah kondisi --- .../ParticipantsRelationManager.php | 10 +++++----- app/Observers/ActivityObserver.php | 7 ------- app/Policies/ActivityPolicy.php | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/Filament/Resources/Activities/RelationManagers/ParticipantsRelationManager.php b/app/Filament/Resources/Activities/RelationManagers/ParticipantsRelationManager.php index de911d2..ebd48f2 100644 --- a/app/Filament/Resources/Activities/RelationManagers/ParticipantsRelationManager.php +++ b/app/Filament/Resources/Activities/RelationManagers/ParticipantsRelationManager.php @@ -59,7 +59,7 @@ class ParticipantsRelationManager extends RelationManager if (($data['status'] ?? 'hadir') === 'hadir') { $activity = $this->getOwnerRecord(); MemberPoint::firstOrCreate( - ['user_id' => $data['recordId'], 'source_type' => 'activity', 'source_id' => $activity->id], + ['user_id' => $data['recordId'], 'source_type' => \App\Models\Activity::class, 'source_id' => $activity->id], ['points' => 10, 'reason' => "Hadir di kegiatan: {$activity->title}"] ); } @@ -70,7 +70,7 @@ class ParticipantsRelationManager extends RelationManager ->after(function (EditAction $action, $record, array $data) { $activity = $this->getOwnerRecord(); $existing = MemberPoint::where('user_id', $record->id) - ->where('source_type', 'activity') + ->where('source_type', \App\Models\Activity::class) ->where('source_id', $activity->id) ->first(); @@ -79,7 +79,7 @@ class ParticipantsRelationManager extends RelationManager 'user_id' => $record->id, 'points' => 10, 'reason' => "Hadir di kegiatan: {$activity->title}", - 'source_type' => 'activity', + 'source_type' => \App\Models\Activity::class, 'source_id' => $activity->id, ]); } elseif (($data['status'] ?? 'hadir') !== 'hadir' && $existing) { @@ -90,7 +90,7 @@ class ParticipantsRelationManager extends RelationManager ->after(function ($record) { $activity = $this->getOwnerRecord(); MemberPoint::where('user_id', $record->id) - ->where('source_type', 'activity') + ->where('source_type', \App\Models\Activity::class) ->where('source_id', $activity->id) ->delete(); }), @@ -100,7 +100,7 @@ class ParticipantsRelationManager extends RelationManager DetachBulkAction::make() ->after(function ($records) { $activity = $this->getOwnerRecord(); - MemberPoint::where('source_type', 'activity') + MemberPoint::where('source_type', \App\Models\Activity::class) ->where('source_id', $activity->id) ->whereIn('user_id', $records->pluck('id')) ->delete(); diff --git a/app/Observers/ActivityObserver.php b/app/Observers/ActivityObserver.php index 691eb87..c8d6b23 100644 --- a/app/Observers/ActivityObserver.php +++ b/app/Observers/ActivityObserver.php @@ -30,13 +30,6 @@ class ActivityObserver return; } - if ($new === 'approved' && $activity->wasChanged('executed_at') && empty($activity->execution_notes)) { - Notification::make()->title('Catatan pelaksanaan wajib diisi') - ->danger()->send(); - $activity->executed_at = null; - return; - } - ActivityLog::create([ 'user_id' => Auth::id(), 'action' => 'status_changed', diff --git a/app/Policies/ActivityPolicy.php b/app/Policies/ActivityPolicy.php index 91e8221..5d48d81 100644 --- a/app/Policies/ActivityPolicy.php +++ b/app/Policies/ActivityPolicy.php @@ -29,12 +29,24 @@ class ActivityPolicy public function update(AuthUser $authUser, Activity $activity): bool { - return $authUser->can('Update:Activity'); + if ($authUser->can('Update:Activity')) { + return true; + } + + return $authUser->hasRole('koordinator') + && $activity->created_by === $authUser->id + && is_null($activity->approved_at); } public function delete(AuthUser $authUser, Activity $activity): bool { - return $authUser->can('Delete:Activity'); + if ($authUser->can('Delete:Activity')) { + return true; + } + + return $authUser->hasRole('koordinator') + && $activity->created_by === $authUser->id + && is_null($activity->approved_at); } public function deleteAny(AuthUser $authUser): bool From 4106eae5cfec303f61d1518a14b067b2c9379b1d Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 23:18:49 +0700 Subject: [PATCH 16/26] feat: tambah budget kegiatan dengan alur threshold approval/voting --- .../Activities/Schemas/ActivityForm.php | 2 ++ .../CashRecords/Schemas/CashRecordForm.php | 4 +++ app/Models/Activity.php | 7 ++++- app/Models/CashRecord.php | 8 ++++- app/Observers/ActivityObserver.php | 23 ++++++++++++++ ...vities_and_activity_id_to_cash_records.php | 31 +++++++++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2026_04_05_161652_add_budget_to_activities_and_activity_id_to_cash_records.php diff --git a/app/Filament/Resources/Activities/Schemas/ActivityForm.php b/app/Filament/Resources/Activities/Schemas/ActivityForm.php index ed9321e..53e7b52 100644 --- a/app/Filament/Resources/Activities/Schemas/ActivityForm.php +++ b/app/Filament/Resources/Activities/Schemas/ActivityForm.php @@ -17,6 +17,8 @@ class ActivityForm Textarea::make('description')->label('Deskripsi')->rows(3)->columnSpanFull(), DatePicker::make('start_date')->label('Mulai')->required(), DatePicker::make('end_date')->label('Selesai')->required(), + TextInput::make('budget')->label('Estimasi Budget (Rp)')->numeric() + ->helperText('Kosongkan jika tidak ada budget. < Rp500.000: langsung | Rp500.000–2.000.000: approval ketua | > Rp2.000.000: voting'), DateTimePicker::make('executed_at')->label('Waktu Pelaksanaan') ->visible(fn ($record) => $record?->status === 'approved'), Textarea::make('execution_notes')->label('Catatan Pelaksanaan')->rows(3)->columnSpanFull() diff --git a/app/Filament/Resources/CashRecords/Schemas/CashRecordForm.php b/app/Filament/Resources/CashRecords/Schemas/CashRecordForm.php index 9efdb0a..7a56eca 100644 --- a/app/Filament/Resources/CashRecords/Schemas/CashRecordForm.php +++ b/app/Filament/Resources/CashRecords/Schemas/CashRecordForm.php @@ -2,6 +2,7 @@ namespace App\Filament\Resources\CashRecords\Schemas; +use App\Models\Activity; use App\Models\CashCategory; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Placeholder; @@ -27,6 +28,9 @@ class CashRecordForm ->live(), Textarea::make('description')->label('Keterangan')->required()->columnSpanFull(), DatePicker::make('date')->label('Tanggal')->required(), + Select::make('activity_id')->label('Kegiatan Terkait') + ->options(Activity::whereIn('status', ['approved'])->pluck('title', 'id')) + ->searchable()->nullable(), ]); } } diff --git a/app/Models/Activity.php b/app/Models/Activity.php index f4a37f3..96e5c22 100644 --- a/app/Models/Activity.php +++ b/app/Models/Activity.php @@ -9,7 +9,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Activity extends Model { protected $fillable = [ - 'title', 'description', 'start_date', 'end_date', + 'title', 'description', 'budget', 'start_date', 'end_date', 'created_by', 'status', 'approved_by', 'approved_at', 'executed_at', 'execution_notes', ]; @@ -44,4 +44,9 @@ class Activity extends Model return $this->belongsToMany(User::class, 'activity_member') ->withPivot('status', 'notes'); } + + public function cashRecords(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(CashRecord::class); + } } diff --git a/app/Models/CashRecord.php b/app/Models/CashRecord.php index c53e270..ea1bceb 100644 --- a/app/Models/CashRecord.php +++ b/app/Models/CashRecord.php @@ -4,11 +4,12 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use App\Models\Activity; class CashRecord extends Model { protected $fillable = [ - 'amount', 'category_id', 'description', + 'amount', 'category_id', 'activity_id', 'description', 'date', 'created_by', 'verified_by', 'verified_at', ]; @@ -29,6 +30,11 @@ class CashRecord extends Model return $this->belongsTo(CashCategory::class, 'category_id'); } + public function activity(): BelongsTo + { + return $this->belongsTo(Activity::class); + } + public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); diff --git a/app/Observers/ActivityObserver.php b/app/Observers/ActivityObserver.php index c8d6b23..cc9ee93 100644 --- a/app/Observers/ActivityObserver.php +++ b/app/Observers/ActivityObserver.php @@ -4,6 +4,8 @@ namespace App\Observers; use App\Models\Activity; use App\Models\ActivityLog; +use App\Models\Approval; +use App\Models\Vote; use App\Services\NotificationService; use Filament\Notifications\Notification; use Illuminate\Support\Facades\Auth; @@ -42,6 +44,27 @@ class ActivityObserver NotificationService::toRole('ketua', 'Kegiatan Menunggu Persetujuan', "Kegiatan \"{$activity->title}\" diajukan untuk disetujui.", 'warning', route('filament.admin.resources.activities.edit', $activity)); + + // Threshold budget + $budget = $activity->budget; + if ($budget >= 500_000 && $budget <= 2_000_000) { + Approval::create([ + 'model_type' => Activity::class, + 'model_id' => $activity->id, + 'required_approvals' => 1, + 'status' => 'pending', + ]); + } elseif ($budget > 2_000_000) { + Vote::create([ + 'title' => "Persetujuan Budget Kegiatan: {$activity->title}", + 'description' => "Budget kegiatan senilai Rp " . number_format($budget, 0, ',', '.') . " memerlukan persetujuan voting.", + 'type' => 'finance', + 'related_id' => $activity->id, + 'status' => 'open', + 'deadline' => now()->addDays(3), + 'created_by' => Auth::id() ?? $activity->created_by, + ]); + } } if (in_array($new, ['approved', 'rejected']) && $activity->creator) { diff --git a/database/migrations/2026_04_05_161652_add_budget_to_activities_and_activity_id_to_cash_records.php b/database/migrations/2026_04_05_161652_add_budget_to_activities_and_activity_id_to_cash_records.php new file mode 100644 index 0000000..19b03d8 --- /dev/null +++ b/database/migrations/2026_04_05_161652_add_budget_to_activities_and_activity_id_to_cash_records.php @@ -0,0 +1,31 @@ +unsignedBigInteger('budget')->nullable()->after('description'); + }); + + Schema::table('cash_records', function (Blueprint $table) { + $table->foreignId('activity_id')->nullable()->constrained('activities')->nullOnDelete()->after('category_id'); + }); + } + + public function down(): void + { + Schema::table('cash_records', function (Blueprint $table) { + $table->dropForeignIdFor(\App\Models\Activity::class, 'activity_id'); + $table->dropColumn('activity_id'); + }); + + Schema::table('activities', function (Blueprint $table) { + $table->dropColumn('budget'); + }); + } +}; From db7c19b24994d20be21a9f5f496ffebe2b84155d Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 23:22:15 +0700 Subject: [PATCH 17/26] fix: null check budget, cegah duplikasi approval/vote, tambah related_type di votes --- app/Models/CashRecord.php | 1 - app/Models/Vote.php | 2 +- app/Observers/ActivityObserver.php | 40 +++++++++++-------- app/Observers/CashRecordObserver.php | 15 +++---- ...162107_add_related_type_to_votes_table.php | 22 ++++++++++ 5 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 database/migrations/2026_04_05_162107_add_related_type_to_votes_table.php diff --git a/app/Models/CashRecord.php b/app/Models/CashRecord.php index ea1bceb..d1bdf8b 100644 --- a/app/Models/CashRecord.php +++ b/app/Models/CashRecord.php @@ -4,7 +4,6 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use App\Models\Activity; class CashRecord extends Model { diff --git a/app/Models/Vote.php b/app/Models/Vote.php index 0a255a3..507ba6c 100644 --- a/app/Models/Vote.php +++ b/app/Models/Vote.php @@ -10,7 +10,7 @@ class Vote extends Model { protected $fillable = [ 'title', 'description', 'type', - 'related_id', 'status', 'deadline', 'created_by', + 'related_id', 'related_type', 'status', 'deadline', 'created_by', ]; protected $casts = [ diff --git a/app/Observers/ActivityObserver.php b/app/Observers/ActivityObserver.php index cc9ee93..67952dc 100644 --- a/app/Observers/ActivityObserver.php +++ b/app/Observers/ActivityObserver.php @@ -47,23 +47,29 @@ class ActivityObserver // Threshold budget $budget = $activity->budget; - if ($budget >= 500_000 && $budget <= 2_000_000) { - Approval::create([ - 'model_type' => Activity::class, - 'model_id' => $activity->id, - 'required_approvals' => 1, - 'status' => 'pending', - ]); - } elseif ($budget > 2_000_000) { - Vote::create([ - 'title' => "Persetujuan Budget Kegiatan: {$activity->title}", - 'description' => "Budget kegiatan senilai Rp " . number_format($budget, 0, ',', '.') . " memerlukan persetujuan voting.", - 'type' => 'finance', - 'related_id' => $activity->id, - 'status' => 'open', - 'deadline' => now()->addDays(3), - 'created_by' => Auth::id() ?? $activity->created_by, - ]); + if ($budget !== null && $budget >= 500_000 && $budget <= 2_000_000) { + Approval::firstOrCreate( + ['model_type' => Activity::class, 'model_id' => $activity->id], + ['required_approvals' => 1, 'status' => 'pending'] + ); + } elseif ($budget !== null && $budget > 2_000_000) { + $exists = Vote::where('related_type', Activity::class) + ->where('related_id', $activity->id) + ->where('type', 'finance') + ->exists(); + + if (! $exists) { + Vote::create([ + 'title' => "Persetujuan Budget Kegiatan: {$activity->title}", + 'description' => "Budget kegiatan senilai Rp " . number_format($budget, 0, ',', '.') . " memerlukan persetujuan voting.", + 'type' => 'finance', + 'related_id' => $activity->id, + 'related_type' => Activity::class, + 'status' => 'open', + 'deadline' => now()->addDays(3), + 'created_by' => Auth::id() ?? $activity->created_by, + ]); + } } } diff --git a/app/Observers/CashRecordObserver.php b/app/Observers/CashRecordObserver.php index 0302af8..09427ac 100644 --- a/app/Observers/CashRecordObserver.php +++ b/app/Observers/CashRecordObserver.php @@ -41,13 +41,14 @@ class CashRecordObserver // Threshold: > 2jt → buat voting + notif semua anggota if ($record->amount > 2_000_000) { Vote::create([ - 'title' => "Persetujuan Transaksi: {$record->description}", - 'description' => "Transaksi senilai Rp " . number_format($record->amount, 0, ',', '.') . " memerlukan persetujuan voting.", - 'type' => 'finance', - 'related_id' => $record->id, - 'status' => 'open', - 'deadline' => now()->addDays(3), - 'created_by' => Auth::id() ?? $record->created_by, + 'title' => "Persetujuan Transaksi: {$record->description}", + 'description' => "Transaksi senilai Rp " . number_format($record->amount, 0, ',', '.') . " memerlukan persetujuan voting.", + 'type' => 'finance', + 'related_id' => $record->id, + 'related_type' => CashRecord::class, + 'status' => 'open', + 'deadline' => now()->addDays(3), + 'created_by' => Auth::id() ?? $record->created_by, ]); NotificationService::toRole('ketua', diff --git a/database/migrations/2026_04_05_162107_add_related_type_to_votes_table.php b/database/migrations/2026_04_05_162107_add_related_type_to_votes_table.php new file mode 100644 index 0000000..7a842ac --- /dev/null +++ b/database/migrations/2026_04_05_162107_add_related_type_to_votes_table.php @@ -0,0 +1,22 @@ +string('related_type')->nullable()->after('related_id'); + }); + } + + public function down(): void + { + Schema::table('votes', function (Blueprint $table) { + $table->dropColumn('related_type'); + }); + } +}; From 00459dc98e5c0803f3fc3c71ce8e7dbe0ff1dbd4 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 23:33:40 +0700 Subject: [PATCH 18/26] chore: tambah divisi Teknologi Informasi ke DivisionSeeder --- app/Filament/Resources/Posts/PostResource.php | 2 +- database/seeders/DivisionSeeder.php | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Filament/Resources/Posts/PostResource.php b/app/Filament/Resources/Posts/PostResource.php index c5bcc67..644027d 100644 --- a/app/Filament/Resources/Posts/PostResource.php +++ b/app/Filament/Resources/Posts/PostResource.php @@ -18,7 +18,7 @@ class PostResource extends Resource protected static ?string $model = Post::class; protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-newspaper'; protected static string|\UnitEnum|null $navigationGroup = 'Blog'; - protected static ?string $navigationLabel = 'Post'; + protected static ?string $navigationLabel = 'Artikel'; // Label dinamis sesuai role public static function getModelLabel(): string diff --git a/database/seeders/DivisionSeeder.php b/database/seeders/DivisionSeeder.php index 84c02b6..5f85f95 100644 --- a/database/seeders/DivisionSeeder.php +++ b/database/seeders/DivisionSeeder.php @@ -15,6 +15,7 @@ class DivisionSeeder extends Seeder ['name' => 'Olahraga', 'description' => 'Bidang olahraga dan kesehatan'], ['name' => 'Seni & Budaya', 'description' => 'Bidang seni dan pelestarian budaya'], ['name' => 'Lingkungan', 'description' => 'Bidang lingkungan hidup'], + ['name' => 'Teknologi Informasi', 'description' => 'Bidang teknologi dan informasi digital'], ]; foreach ($divisions as $division) { From dd5762595dd3fe575abff8c77e12f1b246c1693d Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Sun, 5 Apr 2026 23:34:33 +0700 Subject: [PATCH 19/26] chore: auto-commit perubahan selesai --- app/Policies/ActivityPolicy.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/app/Policies/ActivityPolicy.php b/app/Policies/ActivityPolicy.php index 5d48d81..91e8221 100644 --- a/app/Policies/ActivityPolicy.php +++ b/app/Policies/ActivityPolicy.php @@ -29,24 +29,12 @@ class ActivityPolicy public function update(AuthUser $authUser, Activity $activity): bool { - if ($authUser->can('Update:Activity')) { - return true; - } - - return $authUser->hasRole('koordinator') - && $activity->created_by === $authUser->id - && is_null($activity->approved_at); + return $authUser->can('Update:Activity'); } public function delete(AuthUser $authUser, Activity $activity): bool { - if ($authUser->can('Delete:Activity')) { - return true; - } - - return $authUser->hasRole('koordinator') - && $activity->created_by === $authUser->id - && is_null($activity->approved_at); + return $authUser->can('Delete:Activity'); } public function deleteAny(AuthUser $authUser): bool From f64ec582b69e60ef2d5c1fe69122589a58dc54b0 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Mon, 6 Apr 2026 00:06:56 +0700 Subject: [PATCH 20/26] chore: auto-commit perubahan selesai --- public/images/logo.png | Bin 4939 -> 4919 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/images/logo.png b/public/images/logo.png index 87dbe56b10c33f03b567ff574b38fcda6904a6b9..9720b786cbce5bfe81b3d71272f008e048df8ab0 100644 GIT binary patch delta 4904 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 From faca225e082a07164d15969503220250ab758a42 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Mon, 6 Apr 2026 00:11:07 +0700 Subject: [PATCH 21/26] feat: tambah halaman panduan penggunaan (docs/USER_GUIDE.md + /panduan) --- app/Http/Controllers/PublicController.php | 5 +- docs/USER_GUIDE.md | 230 ++++++++++++++++++ resources/views/public/guide.blade.php | 282 ++++++++++++++++++++++ routes/web.php | 1 + 4 files changed, 517 insertions(+), 1 deletion(-) create mode 100644 docs/USER_GUIDE.md create mode 100644 resources/views/public/guide.blade.php diff --git a/app/Http/Controllers/PublicController.php b/app/Http/Controllers/PublicController.php index 460272b..ddc3d6d 100644 --- a/app/Http/Controllers/PublicController.php +++ b/app/Http/Controllers/PublicController.php @@ -63,7 +63,10 @@ class PublicController extends Controller return view('public.kontak'); } - public function kontakStore(\Illuminate\Http\Request $request) + public function guide() + { + return view('public.guide'); + } public function kontakStore(\Illuminate\Http\Request $request) { $data = $request->validate([ 'name' => 'required|string|max:100', diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md new file mode 100644 index 0000000..381e32c --- /dev/null +++ b/docs/USER_GUIDE.md @@ -0,0 +1,230 @@ +# Panduan Penggunaan Sistem Persegi + +Sistem manajemen internal Organisasi Pemuda Desa Karangdadap. +Akses panel: **https://persegi.nyawiji.net/admin** + +--- + +## Daftar Isi + +1. [Login & Akses Panel](#1-login--akses-panel) +2. [Dashboard](#2-dashboard) +3. [Manajemen Anggota](#3-manajemen-anggota) +4. [Divisi](#4-divisi) +5. [Kegiatan](#5-kegiatan) +6. [Keuangan (Kas)](#6-keuangan-kas) +7. [Iuran Anggota](#7-iuran-anggota) +8. [Voting](#8-voting) +9. [Approval](#9-approval) +10. [Audit Internal](#10-audit-internal) +11. [Konten & Blog](#11-konten--blog) +12. [Poin Anggota](#12-poin-anggota) +13. [Notifikasi](#13-notifikasi) +14. [Hak Akses per Role](#14-hak-akses-per-role) + +--- + +## 1. Login & Akses Panel + +1. Buka **https://persegi.nyawiji.net/admin** +2. Masukkan email dan password yang diberikan pengurus +3. Klik **Masuk** + +> Hanya anggota dengan status **aktif** yang bisa login. Jika tidak bisa masuk, hubungi pengurus. + +--- + +## 2. Dashboard + +Setelah login, halaman utama menampilkan: +- **Statistik** — jumlah anggota aktif, kegiatan, kas masuk/keluar +- **Log Aktivitas** — perubahan terbaru di sistem +- **Leaderboard Poin** — 10 anggota dengan poin tertinggi + +--- + +## 3. Manajemen Anggota + +**Menu:** Organisasi → Anggota + +### Tambah Anggota Baru +1. Klik tombol **Tambah Anggota** +2. Isi nama, email, nomor telepon, alamat, dan divisi +3. Atur status: **Aktif** atau **Nonaktif** +4. Klik **Simpan** + +> Anggota baru otomatis mendapat role `anggota` dan bisa login ke panel. + +### Nonaktifkan Anggota +1. Buka halaman edit anggota +2. Ubah status ke **Nonaktif** +3. Isi alasan nonaktif +4. Klik **Simpan** + +> Anggota nonaktif tidak bisa login ke panel. + +### Assign Role Tambahan +Role bisa ditambahkan di field **Role** saat edit anggota. +- `koordinator` — hanya bisa di-assign oleh **ketua** +- Role lain (`pengurus`, `bendahara`, dll) — bisa di-assign oleh yang punya akses + +--- + +## 4. Divisi + +**Menu:** Organisasi → Divisi + +- Tambah, edit, atau hapus divisi +- Setiap divisi bisa memiliki **Penanggung Jawab** — dipilih dari anggota dengan role `pengurus` + +--- + +## 5. Kegiatan + +**Menu:** Kegiatan → Kegiatan + +### Alur Status Kegiatan + +``` +Draft → Pending (diajukan) → Approved / Rejected +``` + +### Buat Kegiatan Baru (Pengurus / Koordinator) +1. Klik **Tambah Kegiatan** +2. Isi judul, deskripsi, tanggal mulai & selesai +3. Isi **Estimasi Budget** jika ada (opsional) +4. Klik **Simpan** — kegiatan tersimpan sebagai **Draft** + +### Ajukan Kegiatan +1. Buka kegiatan yang sudah dibuat +2. Ubah status ke **Pending** +3. Klik **Simpan** + +> Ketua akan mendapat notifikasi untuk menyetujui. +> Jika budget ≥ Rp500.000, akan dibuat **Approval** otomatis. +> Jika budget > Rp2.000.000, akan dibuat **Voting** otomatis. + +### Setujui / Tolak Kegiatan (Ketua) +1. Buka kegiatan dengan status **Pending** +2. Ubah status ke **Approved** atau **Rejected** +3. Klik **Simpan** + +### Catat Kehadiran Peserta +1. Buka kegiatan yang sudah **Approved** +2. Buka tab **Kehadiran Peserta** +3. Klik **Tambah Peserta** → pilih anggota → atur status kehadiran +4. Anggota yang hadir otomatis mendapat **+10 poin** + +--- + +## 6. Keuangan (Kas) + +**Menu:** Keuangan → Transaksi + +### Catat Transaksi Baru (Bendahara) +1. Klik **Tambah Transaksi** +2. Pilih kategori, isi jumlah, keterangan, dan tanggal +3. Pilih **Kegiatan Terkait** jika transaksi untuk kegiatan tertentu (opsional) +4. Klik **Simpan** + +### Alur Verifikasi Otomatis + +| Jumlah | Alur | +|---|---| +| < Rp500.000 | Bisa langsung diverifikasi | +| Rp500.000 – Rp2.000.000 | Approval ketua diperlukan | +| > Rp2.000.000 | Voting anggota diperlukan | + +### Verifikasi Transaksi (Ketua) +1. Buka transaksi yang menunggu verifikasi +2. Klik tombol **Verifikasi** + +> Transaksi yang sudah diverifikasi **tidak bisa diubah atau dihapus**. + +--- + +## 7. Iuran Anggota + +**Menu:** Organisasi → Iuran Anggota + +- Catat iuran per anggota per periode (format: `YYYY-MM`, contoh: `2026-04`) +- Status iuran: **Lunas** atau **Belum Lunas** + +--- + +## 8. Voting + +**Menu:** Keputusan → Voting + +- Voting dibuat otomatis oleh sistem saat ada transaksi atau budget kegiatan > Rp2.000.000 +- Anggota bisa melihat dan memilih suara di halaman detail voting +- Voting otomatis tertutup setelah deadline + +--- + +## 9. Approval + +**Menu:** Keputusan → Approval + +- Approval dibuat otomatis untuk transaksi atau budget kegiatan Rp500.000–Rp2.000.000 +- Ketua bisa menyetujui atau menolak di halaman detail approval + +--- + +## 10. Audit Internal + +**Menu:** Audit → Temuan Audit + +- Auditor bisa membuat temuan audit +- Pengurus terkait bisa memberikan respons terhadap temuan + +--- + +## 11. Konten & Blog + +**Menu:** Konten → Post + +### Buat Artikel +1. Klik **Tambah Post** +2. Isi judul, konten, dan slug +3. Simpan sebagai **Draft** atau langsung **Publish** + +> Artikel yang dipublish otomatis memberikan **+5 poin** ke penulis. +> Editor bisa me-review dan publish/unpublish artikel. + +--- + +## 12. Poin Anggota + +**Menu:** Organisasi → Poin Anggota + +Poin diberikan otomatis: +| Aktivitas | Poin | +|---|---| +| Hadir di kegiatan | +10 | +| Artikel dipublish | +5 | + +--- + +## 13. Notifikasi + +Ikon lonceng di pojok kanan atas menampilkan notifikasi masuk, seperti: +- Kegiatan menunggu persetujuan +- Status kegiatan diubah +- Transaksi butuh approval/voting +- Status keanggotaan diubah + +--- + +## 14. Hak Akses per Role + +| Role | Yang Bisa Dilakukan | +|---|---| +| `super_admin` | Semua akses | +| `ketua` | Approve kegiatan, verifikasi kas, lihat semua data | +| `bendahara` | Input kas & iuran | +| `pengurus` | Buat & ajukan kegiatan, lihat anggota & divisi | +| `koordinator` | Buat & kelola kegiatan milik sendiri (sebelum disetujui) | +| `anggota` | Lihat kegiatan, voting, poin, buat artikel | +| `auditor` | Lihat semua data + buat temuan audit | +| `editor` | Review & publish artikel | diff --git a/resources/views/public/guide.blade.php b/resources/views/public/guide.blade.php new file mode 100644 index 0000000..7e7091b --- /dev/null +++ b/resources/views/public/guide.blade.php @@ -0,0 +1,282 @@ +@extends('public.layout') +@section('title', 'Panduan Penggunaan') +@section('content') + +
+ +
+

+ Panduan +

+

Panduan
Penggunaan

+

Panduan lengkap penggunaan sistem manajemen internal Persegi.

+
+ +
+ + {{-- Sidebar navigasi --}} + + + {{-- Konten --}} +
+ + {{-- Login --}} +
+

1. Login & Akses Panel

+
    +
  1. Buka {{ config('app.url') }}/admin
  2. +
  3. Masukkan email dan password yang diberikan pengurus
  4. +
  5. Klik Masuk
  6. +
+

+ Hanya anggota dengan status aktif yang bisa login. Jika tidak bisa masuk, hubungi pengurus. +

+
+ + {{-- Dashboard --}} +
+

2. Dashboard

+

Setelah login, halaman utama menampilkan:

+
    +
  • Statistik — jumlah anggota aktif, kegiatan, kas masuk/keluar
  • +
  • Log Aktivitas — perubahan terbaru di sistem
  • +
  • Leaderboard Poin — 10 anggota dengan poin tertinggi
  • +
+
+ + {{-- Anggota --}} +
+

3. Manajemen Anggota

+

Menu: Organisasi → Anggota

+ +

Tambah Anggota Baru

+
    +
  1. Klik tombol Tambah Anggota
  2. +
  3. Isi nama, email, nomor telepon, alamat, dan divisi
  4. +
  5. Atur status: Aktif atau Nonaktif
  6. +
  7. Klik Simpan
  8. +
+ +

Nonaktifkan Anggota

+
    +
  1. Buka halaman edit anggota
  2. +
  3. Ubah status ke Nonaktif dan isi alasan
  4. +
  5. Klik Simpan
  6. +
+

+ Anggota nonaktif tidak bisa login ke panel. +

+
+ + {{-- Divisi --}} +
+

4. Divisi

+

Menu: Organisasi → Divisi

+
    +
  • Tambah, edit, atau hapus divisi
  • +
  • Setiap divisi bisa memiliki Penanggung Jawab — dipilih dari anggota dengan role pengurus
  • +
+
+ + {{-- Kegiatan --}} +
+

5. Kegiatan

+

Menu: Kegiatan → Kegiatan

+ +
+ Draft + + Pending + + Approved + / + Rejected +
+ +

Buat Kegiatan Baru

+
    +
  1. Klik Tambah Kegiatan
  2. +
  3. Isi judul, deskripsi, tanggal, dan estimasi budget (opsional)
  4. +
  5. Klik Simpan — tersimpan sebagai Draft
  6. +
  7. Ubah status ke Pending untuk mengajukan ke ketua
  8. +
+ +

+ Budget ≥ Rp500.000 → approval ketua otomatis dibuat.
+ Budget > Rp2.000.000 → voting otomatis dibuat. +

+ +

Catat Kehadiran Peserta

+
    +
  1. Buka kegiatan yang sudah Approved
  2. +
  3. Buka tab Kehadiran Peserta
  4. +
  5. Tambah peserta dan atur status kehadiran
  6. +
  7. Anggota yang hadir otomatis mendapat +10 poin
  8. +
+
+ + {{-- Kas --}} +
+

6. Keuangan (Kas)

+

Menu: Keuangan → Transaksi

+ +

Catat Transaksi

+
    +
  1. Klik Tambah Transaksi
  2. +
  3. Pilih kategori, isi jumlah, keterangan, dan tanggal
  4. +
  5. Pilih kegiatan terkait jika ada (opsional)
  6. +
  7. Klik Simpan
  8. +
+ +
+ + + + + + + + + + + + +
JumlahAlur
< Rp500.000Langsung diverifikasi
Rp500.000 – Rp2.000.000Perlu approval ketua
> Rp2.000.000Perlu voting anggota
+
+
+ + {{-- Iuran --}} +
+

7. Iuran Anggota

+

Menu: Organisasi → Iuran Anggota

+
    +
  • Catat iuran per anggota per periode (format: YYYY-MM, contoh: 2026-04)
  • +
  • Status: Lunas atau Belum Lunas
  • +
+
+ + {{-- Voting --}} +
+

8. Voting

+

Menu: Keputusan → Voting

+
    +
  • Voting dibuat otomatis saat transaksi atau budget kegiatan > Rp2.000.000
  • +
  • Anggota bisa memilih suara di halaman detail voting
  • +
  • Voting tertutup otomatis setelah deadline
  • +
+
+ + {{-- Approval --}} +
+

9. Approval

+

Menu: Keputusan → Approval

+
    +
  • Approval dibuat otomatis untuk transaksi atau budget Rp500.000–Rp2.000.000
  • +
  • Ketua bisa menyetujui atau menolak di halaman detail approval
  • +
+
+ + {{-- Audit --}} +
+

10. Audit Internal

+

Menu: Audit → Temuan Audit

+
    +
  • Auditor bisa membuat temuan audit
  • +
  • Pengurus terkait bisa memberikan respons terhadap temuan
  • +
+
+ + {{-- Konten --}} +
+

11. Konten & Blog

+

Menu: Konten → Post

+
    +
  1. Klik Tambah Post
  2. +
  3. Isi judul, konten, dan slug
  4. +
  5. Simpan sebagai Draft atau langsung Publish
  6. +
+

+ Artikel yang dipublish otomatis memberikan +5 poin ke penulis. +

+
+ + {{-- Poin --}} +
+

12. Poin Anggota

+

Menu: Organisasi → Poin Anggota

+ + + + + + + + + + + +
AktivitasPoin
Hadir di kegiatan+10
Artikel dipublish+5
+
+ + {{-- Notifikasi --}} +
+

13. Notifikasi

+

Ikon lonceng di pojok kanan atas menampilkan notifikasi masuk:

+
    +
  • Kegiatan menunggu persetujuan
  • +
  • Status kegiatan diubah
  • +
  • Transaksi butuh approval atau voting
  • +
  • Status keanggotaan diubah
  • +
+
+ + {{-- Role --}} +
+

14. Hak Akses per Role

+
+ + + + + + + + + + + + + + + + +
RoleYang Bisa Dilakukan
ketuaApprove kegiatan, verifikasi kas, lihat semua data
bendaharaInput kas & iuran
pengurusBuat & ajukan kegiatan, lihat anggota & divisi
koordinatorBuat & kelola kegiatan milik sendiri (sebelum disetujui)
anggotaLihat kegiatan, voting, poin, buat artikel
auditorLihat semua data + buat temuan audit
editorReview & publish artikel
+
+
+ +
+
+
+ +@endsection diff --git a/routes/web.php b/routes/web.php index 73e417f..198bbbe 100644 --- a/routes/web.php +++ b/routes/web.php @@ -10,3 +10,4 @@ Route::get('/blog', [\App\Http\Controllers\PublicController::class, 'blog'])->na Route::get('/blog/{post:slug}', [\App\Http\Controllers\PublicController::class, 'blogDetail'])->name('blog.detail'); Route::get('/kontak', [\App\Http\Controllers\PublicController::class, 'kontak'])->name('kontak'); Route::post('/kontak', [\App\Http\Controllers\PublicController::class, 'kontakStore'])->name('kontak.store'); +Route::get('/panduan', [\App\Http\Controllers\PublicController::class, 'guide'])->name('guide'); From 3a64bf29eb238b1b684c76c66a77d7acfd751c32 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Mon, 6 Apr 2026 00:18:33 +0700 Subject: [PATCH 22/26] feat: tambah link panduan di topbar panel admin --- app/Providers/Filament/AdminPanelProvider.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 3f96bf7..7655f09 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -11,8 +11,11 @@ use Filament\Pages\Dashboard; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; +use Filament\Support\Facades\FilamentView; +use Filament\View\PanelsRenderHook; use Filament\Widgets\AccountWidget; use Filament\Widgets\FilamentInfoWidget; +use Illuminate\Support\HtmlString; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\PreventRequestForgery; @@ -24,6 +27,21 @@ class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { + FilamentView::registerRenderHook( + PanelsRenderHook::TOPBAR_END, + fn () => new HtmlString( + ' + + + + Panduan + ' + ), + ); + return $panel ->default() ->brandLogo(asset('images/logo.png')) From 6cb0128eff40399c6a31247ca3c68ea9a9222b23 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Mon, 6 Apr 2026 00:20:07 +0700 Subject: [PATCH 23/26] chore: pindah link panduan dari topbar ke footer navigasi panel --- app/Providers/Filament/AdminPanelProvider.php | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 7655f09..8ab6247 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -28,17 +28,19 @@ class AdminPanelProvider extends PanelProvider public function panel(Panel $panel): Panel { FilamentView::registerRenderHook( - PanelsRenderHook::TOPBAR_END, + PanelsRenderHook::FOOTER, fn () => new HtmlString( - ' - - - - Panduan - ' + '' ), ); From a855ad92fbe61fe632945926a6ac68c4cd079b57 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Mon, 6 Apr 2026 00:22:08 +0700 Subject: [PATCH 24/26] chore: pindah link panduan ke footer navigasi website publik --- app/Providers/Filament/AdminPanelProvider.php | 20 ------------------- resources/views/public/layout.blade.php | 1 + 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 8ab6247..3f96bf7 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -11,11 +11,8 @@ use Filament\Pages\Dashboard; use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; -use Filament\Support\Facades\FilamentView; -use Filament\View\PanelsRenderHook; use Filament\Widgets\AccountWidget; use Filament\Widgets\FilamentInfoWidget; -use Illuminate\Support\HtmlString; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\PreventRequestForgery; @@ -27,23 +24,6 @@ class AdminPanelProvider extends PanelProvider { public function panel(Panel $panel): Panel { - FilamentView::registerRenderHook( - PanelsRenderHook::FOOTER, - fn () => new HtmlString( - '' - ), - ); - return $panel ->default() ->brandLogo(asset('images/logo.png')) diff --git a/resources/views/public/layout.blade.php b/resources/views/public/layout.blade.php index 348114e..dddcea4 100644 --- a/resources/views/public/layout.blade.php +++ b/resources/views/public/layout.blade.php @@ -69,6 +69,7 @@ From 9c457bffde20b717c7e178b6165791ac6ab89a04 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Mon, 6 Apr 2026 00:27:32 +0700 Subject: [PATCH 25/26] fix: PostObserver trigger published, submit cek kreator, toAll filter aktif, dead code --- app/Filament/Resources/Activities/Tables/ActivitiesTable.php | 3 ++- app/Observers/CashRecordObserver.php | 2 +- app/Observers/PostObserver.php | 2 +- app/Services/NotificationService.php | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Filament/Resources/Activities/Tables/ActivitiesTable.php b/app/Filament/Resources/Activities/Tables/ActivitiesTable.php index 3c07020..a63269a 100644 --- a/app/Filament/Resources/Activities/Tables/ActivitiesTable.php +++ b/app/Filament/Resources/Activities/Tables/ActivitiesTable.php @@ -52,7 +52,8 @@ class ActivitiesTable ->icon('heroicon-o-paper-airplane') ->color('info') ->requiresConfirmation() - ->visible(fn ($record) => $record->status === 'draft') + ->visible(fn ($record) => $record->status === 'draft' + && $record->created_by === auth()->id()) ->action(fn ($record) => $record->update(['status' => 'pending'])), Action::make('approve') ->label('Setujui') diff --git a/app/Observers/CashRecordObserver.php b/app/Observers/CashRecordObserver.php index 09427ac..043f515 100644 --- a/app/Observers/CashRecordObserver.php +++ b/app/Observers/CashRecordObserver.php @@ -23,7 +23,7 @@ class CashRecordObserver // Threshold: 500rb–2jt → buat approval ketua + notif if ($record->amount >= 500_000 && $record->amount <= 2_000_000) { - $approval = Approval::create([ + Approval::create([ 'model_type' => CashRecord::class, 'model_id' => $record->id, 'required_approvals' => 1, diff --git a/app/Observers/PostObserver.php b/app/Observers/PostObserver.php index 55d1fe1..eec7375 100644 --- a/app/Observers/PostObserver.php +++ b/app/Observers/PostObserver.php @@ -9,7 +9,7 @@ class PostObserver { public function updated(Post $post): void { - if ($post->wasChanged('status') && $post->status === 'approved') { + if ($post->wasChanged('status') && $post->status === 'published') { MemberPoint::firstOrCreate( [ 'user_id' => $post->author_id, diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php index 93fc9d0..73ac3bd 100644 --- a/app/Services/NotificationService.php +++ b/app/Services/NotificationService.php @@ -36,6 +36,6 @@ class NotificationService public static function toAll(string $title, string $body, string $color = 'info', ?string $url = null): void { - self::send(User::all(), $title, $body, $color, $url); + self::send(User::where('status', 'aktif')->get(), $title, $body, $color, $url); } } From fec9125c28d196a9e8ed757c3650b957e47f7426 Mon Sep 17 00:00:00 2001 From: tuxarmy Date: Mon, 6 Apr 2026 00:34:22 +0700 Subject: [PATCH 26/26] style: sederhanakan teks copyright di footer publik --- resources/views/public/layout.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/public/layout.blade.php b/resources/views/public/layout.blade.php index dddcea4..eff9f39 100644 --- a/resources/views/public/layout.blade.php +++ b/resources/views/public/layout.blade.php @@ -64,7 +64,7 @@