feat(models): add relationships to PartnerMedia and User models

- Introduced a many-to-many relationship in PartnerMedia for announcements.
- Added a has-many relationship in User for news authored by the user.
- Implemented an active scope in User for filtering active users.
This commit is contained in:
Yoga Pangestu 2025-12-12 10:06:18 +07:00
parent 2c5b057958
commit 8386ea0d93
10 changed files with 385 additions and 0 deletions

View File

@ -0,0 +1,134 @@
<?php
namespace App\Filament\Resources\Publication\Announcements;
use App\Filament\Actions\DefaultBulkActions;
use App\Filament\Columns\TimestampColumns;
use App\Filament\Resources\Publication\Announcements\Pages\ManageAnnouncements;
use App\Models\Announcement;
use App\Models\PartnerMedia;
use BackedEnum;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\Textarea;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use UnitEnum;
class AnnouncementResource extends Resource
{
protected static ?string $model = Announcement::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::Megaphone;
protected static ?string $navigationLabel = 'Pengumuman';
protected static string|UnitEnum|null $navigationGroup = 'Publikasi';
protected static ?string $slug = 'publication/announcements';
protected static ?string $recordTitleAttribute = 'content';
protected static ?int $navigationSort = 2;
public static function form(Schema $schema): Schema
{
return $schema
->components([
Textarea::make('content')
->label('Konten')
->placeholder('Diberitahukan kepada seluruh media untuk update dokumen...')
->autocomplete(false)
->autofocus()
->required()
->rows(4)
->columnSpanFull(),
CheckboxList::make('partnerMedia')
->label('Kirim Kepada')
->required()
->relationship('partnerMedia', 'name')
->options(function () {
return PartnerMedia::query()->pluck('name', 'id');
})
->searchable()
->noSearchResultsMessage('Tidak ada media yang ditemukan.')
->helperText('Pilih media yang akan menerima pengumuman ini')
->columnSpanFull(),
])
->columns(1);
}
public static function table(Table $table): Table
{
return $table
->recordTitleAttribute('content')
->columns([
TextColumn::make('content')
->label('Konten')
->searchable()
->sortable()
->wrap()
->limit(100),
TextColumn::make('partnerMedia.name')
->label('Penerima')
->listWithLineBreaks()
->limitList(3)
->expandableLimitedList()
->placeholder('Tidak ada penerima'),
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()->native(false),
])
->recordActions([
EditAction::make()->modalWidth('lg'),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
...DefaultBulkActions::make('Pengumuman'),
]),
])
->emptyStateIcon('heroicon-o-megaphone')
->emptyStateDescription('Setelah Anda membuat data pertama, maka akan muncul disini.')
->defaultSort('created_at', 'desc')
->deferFilters(false);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ManageAnnouncements::route('/'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Filament\Resources\Publication\Announcements\Pages;
use App\Filament\Resources\Publication\Announcements\AnnouncementResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ManageRecords;
class ManageAnnouncements extends ManageRecords
{
protected static ?string $title = 'Pengumuman';
protected static string $resource = AnnouncementResource::class;
protected function getHeaderActions(): array
{
return [
CreateAction::make()
->label('Tambah')
->modalHeading('Tambah Pengumuman')
->modalSubmitActionLabel('Simpan')
->modalCancelActionLabel('Batal')
->extraModalFooterActions(fn (CreateAction $action): array => [
$action->makeModalSubmitAction('createAnother', arguments: ['another' => true])
->label('Simpan dan Tambah Lagi'),
])
->modalWidth('lg'),
];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Announcement extends Model
{
use HasFactory, SoftDeletes;
protected $guarded = ['id'];
public function partnerMedia(): BelongsToMany
{
return $this->belongsToMany(PartnerMedia::class, 'announcement_media');
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class AnnouncementUser extends Model
{
use HasFactory, SoftDeletes;
protected $guarded = ['id'];
public function announcement(): BelongsTo
{
return $this->belongsTo(Announcement::class);
}
public function partnerMedia(): BelongsTo
{
return $this->belongsTo(PartnerMedia::class);
}
}

View File

@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\MediaLibrary\HasMedia;
@ -35,4 +36,9 @@ public function journalists(): HasMany
{
return $this->hasMany(Journalist::class);
}
public function announcements(): BelongsToMany
{
return $this->belongsToMany(Announcement::class, 'announcement_media');
}
}

View File

@ -8,6 +8,7 @@
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
@ -44,8 +45,19 @@ protected function withoutDeveloper(Builder $query): void
});
}
#[Scope]
protected function active(Builder $query): void
{
$query->where('is_active', IsActive::ACTIVE);
}
public function company(): HasOne
{
return $this->hasOne(Company::class);
}
public function news(): HasMany
{
return $this->hasMany(News::class, 'author_id');
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace Database\Factories;
use App\Models\Announcement;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Announcement>
*/
class AnnouncementFactory extends Factory
{
protected $model = Announcement::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'content' => $this->faker->paragraphs(2, true),
];
}
/**
* Indicate that the announcement is important.
*/
public function important(): static
{
return $this->state(fn (array $attributes) => [
'content' => 'PENTING: '.$this->faker->sentence(10),
]);
}
/**
* Indicate that the announcement is informational.
*/
public function informational(): static
{
return $this->state(fn (array $attributes) => [
'content' => 'INFORMASI: '.$this->faker->paragraph(),
]);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace Database\Factories;
use App\Models\Announcement;
use App\Models\AnnouncementUser;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\AnnouncementUser>
*/
class AnnouncementUserFactory extends Factory
{
protected $model = AnnouncementUser::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'announcement_id' => Announcement::factory(),
'partner_media_id' => PartnerMedia::factory(),
];
}
/**
* Indicate that the announcement user is for a specific announcement.
*/
public function forAnnouncement(Announcement $announcement): static
{
return $this->state(fn (array $attributes) => [
'announcement_id' => $announcement->id,
]);
}
/**
* Indicate that the announcement user is for a specific partner media.
*/
public function forPartnerMedia(PartnerMedia $partnerMedia): static
{
return $this->state(fn (array $attributes) => [
'partner_media_id' => $partnerMedia->id,
]);
}
}

View File

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('announcements', function (Blueprint $table) {
$table->id();
$table->text('content');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('announcements');
}
};

View File

@ -0,0 +1,33 @@
<?php
use App\Models\Announcement;
use App\Models\PartnerMedia;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('announcement_media', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Announcement::class);
$table->foreignIdFor(PartnerMedia::class);
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('announcement_media');
}
};