From 061a87dad9093d7b2ebbd6b2724455803335986c Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 19 Nov 2025 15:50:45 +0700 Subject: [PATCH] feat: add Location and SubLocation resources with management pages and migrations for locations and sub_locations tables --- .../ClassificationResource.php | 2 + .../Master/Locations/LocationResource.php | 140 ++++++++++++++++ .../Locations/Pages/ManageLocations.php | 30 ++++ .../SubClassificationResource.php | 2 + .../SubLocations/Pages/ManageSubLocations.php | 30 ++++ .../SubLocations/SubLocationResource.php | 155 ++++++++++++++++++ app/Models/Location.php | 28 ++++ app/Models/SubLocation.php | 28 ++++ ...25_11_19_084250_create_locations_table.php | 34 ++++ ...1_19_084252_create_sub_locations_table.php | 35 ++++ 10 files changed, 484 insertions(+) create mode 100644 app/Filament/Resources/Master/Locations/LocationResource.php create mode 100644 app/Filament/Resources/Master/Locations/Pages/ManageLocations.php create mode 100644 app/Filament/Resources/Master/SubLocations/Pages/ManageSubLocations.php create mode 100644 app/Filament/Resources/Master/SubLocations/SubLocationResource.php create mode 100644 app/Models/Location.php create mode 100644 app/Models/SubLocation.php create mode 100644 database/migrations/2025_11_19_084250_create_locations_table.php create mode 100644 database/migrations/2025_11_19_084252_create_sub_locations_table.php diff --git a/app/Filament/Resources/Master/Classifications/ClassificationResource.php b/app/Filament/Resources/Master/Classifications/ClassificationResource.php index 01b279f..aaf145b 100644 --- a/app/Filament/Resources/Master/Classifications/ClassificationResource.php +++ b/app/Filament/Resources/Master/Classifications/ClassificationResource.php @@ -41,6 +41,8 @@ class ClassificationResource extends Resource protected static ?string $recordTitleAttribute = 'name'; + protected static ?int $navigationSort = 1; + public static function form(Schema $schema): Schema { return $schema diff --git a/app/Filament/Resources/Master/Locations/LocationResource.php b/app/Filament/Resources/Master/Locations/LocationResource.php new file mode 100644 index 0000000..fccfdba --- /dev/null +++ b/app/Filament/Resources/Master/Locations/LocationResource.php @@ -0,0 +1,140 @@ +components([ + TextInput::make('name') + ->label('Nama') + ->placeholder('Pelaporan') + ->autocomplete(false) + ->autofocus() + ->required() + ->maxLength(50), + + RichEditor::make('description') + ->label('Keterangan') + ->placeholder('Silakan isi keterangan lokasi di atas') + ->nullable(), + ]) + ->columns(1); + } + + public static function table(Table $table): Table + { + return $table + ->recordTitleAttribute('name') + ->columns([ + TextColumn::make('name') + ->label('Nama') + ->searchable() + ->sortable(), + + ToggleColumn::make('is_active') + ->label('Aktif?') + ->getStateUsing(fn (Location $record) => $record->is_active === IsActive::ACTIVE) + ->updateStateUsing(function (Location $record, bool $state) { + $record->is_active = $state ? IsActive::ACTIVE : IsActive::INACTIVE; + $record->save(); + }) + ->sortable(), + + TextColumn::make('created_at') + ->label('Dibuat') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('updated_at') + ->label('Diperbarui') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('deleted_at') + ->label('Dihapus') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + TrashedFilter::make()->native(false), + ]) + ->recordActions([ + EditAction::make()->modalWidth('lg'), + DeleteAction::make(), + ForceDeleteAction::make(), + RestoreAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ForceDeleteBulkAction::make(), + RestoreBulkAction::make(), + ]), + ]) + ->emptyStateIcon('heroicon-o-bookmark') + ->emptyStateDescription('Setelah Anda menulis posting pertama, maka akan muncul disini.') + ->defaultSort('created_at', 'desc') + ->deferFilters(false) + ->reorderable('sort_order'); + } + + public static function getPages(): array + { + return [ + 'index' => ManageLocations::route('/'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/Master/Locations/Pages/ManageLocations.php b/app/Filament/Resources/Master/Locations/Pages/ManageLocations.php new file mode 100644 index 0000000..0d01910 --- /dev/null +++ b/app/Filament/Resources/Master/Locations/Pages/ManageLocations.php @@ -0,0 +1,30 @@ +label('Tambah') + ->modalHeading('Tambah Lokasi') + ->modalSubmitActionLabel('Simpan') + ->modalCancelActionLabel('Batal') + ->extraModalFooterActions(fn (CreateAction $action): array => [ + $action->makeModalSubmitAction('createAnother', arguments: ['another' => true]) + ->label('Simpan dan Tambah Lagi'), + ]) + ->modalWidth('lg'), + ]; + } +} diff --git a/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php b/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php index 26f8137..51dd998 100644 --- a/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php +++ b/app/Filament/Resources/Master/SubClassifications/SubClassificationResource.php @@ -43,6 +43,8 @@ class SubClassificationResource extends Resource protected static ?string $recordTitleAttribute = 'name'; + protected static ?int $navigationSort = 1; + public static function form(Schema $schema): Schema { return $schema diff --git a/app/Filament/Resources/Master/SubLocations/Pages/ManageSubLocations.php b/app/Filament/Resources/Master/SubLocations/Pages/ManageSubLocations.php new file mode 100644 index 0000000..b88f8bc --- /dev/null +++ b/app/Filament/Resources/Master/SubLocations/Pages/ManageSubLocations.php @@ -0,0 +1,30 @@ +label('Tambah') + ->modalHeading('Tambah Sub Lokasi') + ->modalSubmitActionLabel('Simpan') + ->modalCancelActionLabel('Batal') + ->extraModalFooterActions(fn (CreateAction $action): array => [ + $action->makeModalSubmitAction('createAnother', arguments: ['another' => true]) + ->label('Simpan dan Tambah Lagi'), + ]) + ->modalWidth('lg'), + ]; + } +} diff --git a/app/Filament/Resources/Master/SubLocations/SubLocationResource.php b/app/Filament/Resources/Master/SubLocations/SubLocationResource.php new file mode 100644 index 0000000..b0fb02b --- /dev/null +++ b/app/Filament/Resources/Master/SubLocations/SubLocationResource.php @@ -0,0 +1,155 @@ +components([ + Select::make('location_id') + ->label('Lokasi') + ->options(Location::query()->pluck('name', 'id')) + ->searchable() + ->native(false) + ->required() + ->rules(['exists:locations,id']), + + TextInput::make('name') + ->label('Nama') + ->placeholder('Pengaduan') + ->autocomplete(false) + ->autofocus() + ->required() + ->maxLength(50), + + RichEditor::make('description') + ->label('Keterangan') + ->placeholder('Silakan isi keterangan sub lokasi di atas') + ->nullable(), + ]) + ->columns(1); + } + + public static function table(Table $table): Table + { + return $table + ->recordTitleAttribute('name') + ->columns([ + TextColumn::make('location.name') + ->label('Lokasi') + ->searchable() + ->sortable(), + + TextColumn::make('name') + ->label('Nama') + ->searchable() + ->sortable(), + + ToggleColumn::make('is_active') + ->label('Aktif?') + ->getStateUsing(fn (SubLocation $record) => $record->is_active === IsActive::ACTIVE) + ->updateStateUsing(function (SubLocation $record, bool $state) { + $record->is_active = $state ? IsActive::ACTIVE : IsActive::INACTIVE; + $record->save(); + }) + ->sortable(), + + TextColumn::make('created_at') + ->label('Dibuat') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('updated_at') + ->label('Diperbarui') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + + TextColumn::make('deleted_at') + ->label('Dihapus') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + TrashedFilter::make()->native(false), + ]) + ->recordActions([ + EditAction::make()->modalWidth('lg'), + DeleteAction::make(), + ForceDeleteAction::make(), + RestoreAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ForceDeleteBulkAction::make(), + RestoreBulkAction::make(), + ]), + ]) + ->emptyStateIcon('heroicon-o-bookmark') + ->emptyStateDescription('Setelah Anda menulis posting pertama, maka akan muncul disini.') + ->defaultSort('created_at', 'desc') + ->deferFilters(false) + ->reorderable('sort_order'); + } + + public static function getPages(): array + { + return [ + 'index' => ManageSubLocations::route('/'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Models/Location.php b/app/Models/Location.php new file mode 100644 index 0000000..b301316 --- /dev/null +++ b/app/Models/Location.php @@ -0,0 +1,28 @@ + IsActive::class, + 'sort_order' => 'integer', + ]; + } + + public function subLocations(): HasMany + { + return $this->hasMany(SubLocation::class); + } +} diff --git a/app/Models/SubLocation.php b/app/Models/SubLocation.php new file mode 100644 index 0000000..adff43c --- /dev/null +++ b/app/Models/SubLocation.php @@ -0,0 +1,28 @@ + IsActive::class, + 'sort_order' => 'integer', + ]; + } + + public function location(): BelongsTo + { + return $this->belongsTo(Location::class); + } +} diff --git a/database/migrations/2025_11_19_084250_create_locations_table.php b/database/migrations/2025_11_19_084250_create_locations_table.php new file mode 100644 index 0000000..aa27036 --- /dev/null +++ b/database/migrations/2025_11_19_084250_create_locations_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('name', 50); + $table->text('description')->nullable(); + $table->enum('is_active', [IsActive::values()])->default(IsActive::ACTIVE->value)->comment(IsActive::comment()); + $table->integer('sort_order')->default(0); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('locations'); + } +}; diff --git a/database/migrations/2025_11_19_084252_create_sub_locations_table.php b/database/migrations/2025_11_19_084252_create_sub_locations_table.php new file mode 100644 index 0000000..fc8102e --- /dev/null +++ b/database/migrations/2025_11_19_084252_create_sub_locations_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('location_id')->constrained()->cascadeOnDelete(); + $table->string('name', 50); + $table->text('description')->nullable(); + $table->enum('is_active', [IsActive::values()])->default(IsActive::ACTIVE->value)->comment(IsActive::comment()); + $table->integer('sort_order')->default(0); + $table->timestamp('created_at')->useCurrent(); + $table->timestamp('updated_at')->nullable()->useCurrentOnUpdate(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('sub_locations'); + } +};