From f6a1abfa0e172aee6f2683580c8f76065ac83081 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 19 Feb 2026 11:04:47 +0700 Subject: [PATCH] feat: introduce expense management feature with dedicated model, migration, seeder, and Filament resource. --- .../Finance/Expenses/ExpenseResource.php | 148 ++++++++++++++++++ .../Finance/Expenses/Pages/ManageExpenses.php | 31 ++++ app/Models/Expense.php | 20 +++ app/Providers/Filament/AdminPanelProvider.php | 1 + database/factories/ExpenseFactory.php | 25 +++ ...026_02_19_035358_create_expenses_table.php | 29 ++++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/ExpenseSeeder.php | 17 ++ 8 files changed, 272 insertions(+) create mode 100644 app/Filament/Resources/Finance/Expenses/ExpenseResource.php create mode 100644 app/Filament/Resources/Finance/Expenses/Pages/ManageExpenses.php create mode 100644 app/Models/Expense.php create mode 100644 database/factories/ExpenseFactory.php create mode 100644 database/migrations/2026_02_19_035358_create_expenses_table.php create mode 100644 database/seeders/ExpenseSeeder.php diff --git a/app/Filament/Resources/Finance/Expenses/ExpenseResource.php b/app/Filament/Resources/Finance/Expenses/ExpenseResource.php new file mode 100644 index 0000000..8695c41 --- /dev/null +++ b/app/Filament/Resources/Finance/Expenses/ExpenseResource.php @@ -0,0 +1,148 @@ +components([ + DatePicker::make('expense_date') + ->label('Tanggal') + ->default(now()) + ->required() + ->native(false) + ->displayFormat('l, d F Y'), + + TextInput::make('amount') + ->label('Nominal') + ->placeholder('0') + ->autocomplete(false) + ->required() + ->currencyMask(thousandSeparator: '.', decimalSeparator: ',', precision: 0) + ->prefix('Rp') + ->dehydrateStateUsing(fn ($state) => (int) str()->replace('.', '', (string) ($state ?? 0))), + + Textarea::make('notes') + ->label('Keterangan / Catatan') + ->placeholder('Bayar listrik bulanan') + ->required() + ->autocomplete(false), + + SpatieMediaLibraryFileUpload::make('image') + ->label('Bukti') + ->disk(config('filesystems.default')) + ->acceptedFileTypes(['image/*']) + ->maxSize(1024 * 3) + ->collection('expenses') + ->customProperties(fn (): array => [ + 'feature' => 'expenses', + 'date' => now()->toDateString(), + ]) + ->image(), + ]) + ->columns(1); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + TextColumn::make('expense_date') + ->label('Tanggal') + ->date('l, d F Y') + ->sortable(), + + TextColumn::make('notes') + ->label('Keterangan') + ->searchable() + ->sortable() + ->limit(50), + + TextColumn::make('amount') + ->label('Nominal') + ->money('IDR', decimalPlaces: 0) + ->sortable(), + + ...TimestampColumns::make(), + ]) + ->filters([ + TrashedFilter::make() + ->native(false), + ]) + ->recordActions([ + EditAction::make() + ->modalWidth(Width::Large), + + DeleteAction::make(), + + ForceDeleteAction::make(), + + RestoreAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + ...DefaultBulkActions::make('Pengeluaran'), + ]), + ]) + ->emptyStateIcon(Heroicon::CreditCard) + ->emptyStateDescription('Setelah Anda membuat data pertama, maka akan muncul disini.') + ->defaultSort('expense_date', 'desc') + ->deferFilters(false); + } + + public static function getPages(): array + { + return [ + 'index' => ManageExpenses::route('/'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/Finance/Expenses/Pages/ManageExpenses.php b/app/Filament/Resources/Finance/Expenses/Pages/ManageExpenses.php new file mode 100644 index 0000000..1e9c497 --- /dev/null +++ b/app/Filament/Resources/Finance/Expenses/Pages/ManageExpenses.php @@ -0,0 +1,31 @@ +label('Tambah') + ->modalHeading('Tambah Pengeluaran') + ->modalSubmitActionLabel('Simpan') + ->modalCancelActionLabel('Batal') + ->extraModalFooterActions(fn (CreateAction $action): array => [ + $action->makeModalSubmitAction('createAnother', arguments: ['another' => true]) + ->label('Simpan dan Tambah Lagi'), + ]) + ->modalWidth(Width::Large), + ]; + } +} diff --git a/app/Models/Expense.php b/app/Models/Expense.php new file mode 100644 index 0000000..c4ada37 --- /dev/null +++ b/app/Models/Expense.php @@ -0,0 +1,20 @@ + 'date', + ]; +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 2a2e37a..4a046e8 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -70,6 +70,7 @@ public function panel(Panel $panel): Panel ->navigationGroups([ 'Master', 'Kelola', + 'Keuangan', 'Pelindung', 'Sistem', ]) diff --git a/database/factories/ExpenseFactory.php b/database/factories/ExpenseFactory.php new file mode 100644 index 0000000..f1a95c3 --- /dev/null +++ b/database/factories/ExpenseFactory.php @@ -0,0 +1,25 @@ + + */ +class ExpenseFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'expense_date' => fake()->dateTimeBetween('-1 month', 'now'), + 'amount' => fake()->numberBetween(10000, 5000000), + 'notes' => fake()->sentence(), + ]; + } +} diff --git a/database/migrations/2026_02_19_035358_create_expenses_table.php b/database/migrations/2026_02_19_035358_create_expenses_table.php new file mode 100644 index 0000000..1834ff8 --- /dev/null +++ b/database/migrations/2026_02_19_035358_create_expenses_table.php @@ -0,0 +1,29 @@ +id(); + $table->date('expense_date'); + $table->unsignedInteger('amount'); + $table->text('notes')->nullable(); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + public function down(): void + { + Schema::dropIfExists('expenses'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ebbd935..d87ee60 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -26,6 +26,7 @@ public function run(): void EggPriceSeeder::class, OrderSeeder::class, DelayedGoodSeeder::class, + ExpenseSeeder::class, ]); } } diff --git a/database/seeders/ExpenseSeeder.php b/database/seeders/ExpenseSeeder.php new file mode 100644 index 0000000..49c0f87 --- /dev/null +++ b/database/seeders/ExpenseSeeder.php @@ -0,0 +1,17 @@ +count(20)->create(); + } +}