diff --git a/app-modules/catalog/database/factories/DestinationFactory.php b/app-modules/catalog/database/factories/DestinationFactory.php new file mode 100644 index 0000000..f9047fc --- /dev/null +++ b/app-modules/catalog/database/factories/DestinationFactory.php @@ -0,0 +1,34 @@ + + */ +class DestinationFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->city(), + 'mm_name' => fake()->city(), + 'region' => fake()->state(), + 'description' => fake()->sentence(), + 'mm_description' => null, + 'meta_keyword' => null, + 'meta_description' => null, + 'latitude' => fake()->latitude(), + 'longitude' => fake()->longitude(), + 'is_active' => true, + 'popular' => false, + ]; + } +} diff --git a/app-modules/catalog/database/migrations/2026_08_05_141513_create_destinations_table.php b/app-modules/catalog/database/migrations/2026_08_05_141513_create_destinations_table.php new file mode 100644 index 0000000..ea31670 --- /dev/null +++ b/app-modules/catalog/database/migrations/2026_08_05_141513_create_destinations_table.php @@ -0,0 +1,38 @@ +id(); + $table->string('name'); + $table->string('mm_name'); + $table->string('region')->nullable(); + $table->text('description')->nullable(); + $table->text('mm_description')->nullable(); + $table->text('meta_keyword')->nullable(); + $table->text('meta_description')->nullable(); + $table->string('latitude')->nullable(); + $table->string('longitude')->nullable(); + $table->boolean('is_active')->default(true); + $table->boolean('popular')->default(false); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('destinations'); + } +}; diff --git a/app-modules/catalog/src/Filament/Resources/Destinations/DestinationResource.php b/app-modules/catalog/src/Filament/Resources/Destinations/DestinationResource.php new file mode 100644 index 0000000..bf9c5dc --- /dev/null +++ b/app-modules/catalog/src/Filament/Resources/Destinations/DestinationResource.php @@ -0,0 +1,51 @@ + ListDestinations::route('/'), + 'create' => CreateDestination::route('/create'), + 'edit' => EditDestination::route('/{record}/edit'), + ]; + } +} diff --git a/app-modules/catalog/src/Filament/Resources/Destinations/Pages/CreateDestination.php b/app-modules/catalog/src/Filament/Resources/Destinations/Pages/CreateDestination.php new file mode 100644 index 0000000..163bfdb --- /dev/null +++ b/app-modules/catalog/src/Filament/Resources/Destinations/Pages/CreateDestination.php @@ -0,0 +1,11 @@ +components([ + TextInput::make('name') + ->required() + ->maxLength(255), + TextInput::make('mm_name') + ->required() + ->maxLength(255), + TextInput::make('region') + ->maxLength(255), + Textarea::make('description') + ->columnSpanFull(), + Textarea::make('mm_description') + ->columnSpanFull(), + Textarea::make('meta_keyword') + ->columnSpanFull(), + Textarea::make('meta_description') + ->columnSpanFull(), + TextInput::make('latitude') + ->maxLength(255), + TextInput::make('longitude') + ->maxLength(255), + Toggle::make('is_active') + ->required() + ->default(true), + Toggle::make('popular') + ->required() + ->default(false), + ]); + } +} diff --git a/app-modules/catalog/src/Filament/Resources/Destinations/Tables/DestinationsTable.php b/app-modules/catalog/src/Filament/Resources/Destinations/Tables/DestinationsTable.php new file mode 100644 index 0000000..a9a016a --- /dev/null +++ b/app-modules/catalog/src/Filament/Resources/Destinations/Tables/DestinationsTable.php @@ -0,0 +1,48 @@ +columns([ + TextColumn::make('name') + ->searchable() + ->sortable(), + TextColumn::make('region') + ->searchable() + ->sortable(), + IconColumn::make('is_active') + ->boolean(), + IconColumn::make('popular') + ->boolean(), + TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->defaultSort('created_at', 'desc') + ->filters([ + TernaryFilter::make('is_active'), + TernaryFilter::make('popular'), + ]) + ->recordActions([ + EditAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]); + } +} diff --git a/app-modules/catalog/src/Models/Destination.php b/app-modules/catalog/src/Models/Destination.php new file mode 100644 index 0000000..15bb2cc --- /dev/null +++ b/app-modules/catalog/src/Models/Destination.php @@ -0,0 +1,41 @@ + */ + use HasFactory; + + /** + * @var list + */ + protected $fillable = [ + 'name', + 'mm_name', + 'region', + 'description', + 'mm_description', + 'meta_keyword', + 'meta_description', + 'latitude', + 'longitude', + 'is_active', + 'popular', + ]; + + /** + * @return array + */ + protected function casts(): array + { + return [ + 'is_active' => 'boolean', + 'popular' => 'boolean', + ]; + } +} diff --git a/app-modules/catalog/tests/Feature/DestinationResourceTest.php b/app-modules/catalog/tests/Feature/DestinationResourceTest.php new file mode 100644 index 0000000..1a35615 --- /dev/null +++ b/app-modules/catalog/tests/Feature/DestinationResourceTest.php @@ -0,0 +1,61 @@ +admin = User::factory()->create()->assignRole('admin'); + $this->actingAs($this->admin); +}); + +test('can list destinations', function () { + $destinations = Destination::factory()->count(3)->create(); + + Livewire::test(ListDestinations::class) + ->assertOk() + ->assertCanSeeTableRecords($destinations); +}); + +test('can create a destination', function () { + $destination = Destination::factory()->make(); + + Livewire::test(CreateDestination::class) + ->fillForm([ + 'name' => $destination->name, + 'mm_name' => $destination->mm_name, + 'region' => $destination->region, + 'is_active' => true, + ]) + ->call('create') + ->assertNotified() + ->assertRedirect(); + + assertDatabaseHas(Destination::class, [ + 'name' => $destination->name, + 'region' => $destination->region, + ]); +}); + +test('can edit a destination', function () { + $destination = Destination::factory()->create(); + + Livewire::test(EditDestination::class, ['record' => $destination->getRouteKey()]) + ->assertOk() + ->fillForm(['name' => 'Updated Name']) + ->call('save') + ->assertNotified(); + + assertDatabaseHas(Destination::class, [ + 'id' => $destination->id, + 'name' => 'Updated Name', + ]); +});