diff --git a/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php new file mode 100644 index 0000000..4b6fd09 --- /dev/null +++ b/app/Filament/Resources/Publication/Announcements/AnnouncementResource.php @@ -0,0 +1,134 @@ +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, + ]); + } +} diff --git a/app/Filament/Resources/Publication/Announcements/Pages/ManageAnnouncements.php b/app/Filament/Resources/Publication/Announcements/Pages/ManageAnnouncements.php new file mode 100644 index 0000000..b358a45 --- /dev/null +++ b/app/Filament/Resources/Publication/Announcements/Pages/ManageAnnouncements.php @@ -0,0 +1,30 @@ +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'), + ]; + } +} diff --git a/app/Models/Announcement.php b/app/Models/Announcement.php new file mode 100644 index 0000000..bc6c19b --- /dev/null +++ b/app/Models/Announcement.php @@ -0,0 +1,20 @@ +belongsToMany(PartnerMedia::class, 'announcement_media'); + } +} diff --git a/app/Models/AnnouncementMedia.php b/app/Models/AnnouncementMedia.php new file mode 100644 index 0000000..2f0c2b2 --- /dev/null +++ b/app/Models/AnnouncementMedia.php @@ -0,0 +1,25 @@ +belongsTo(Announcement::class); + } + + public function partnerMedia(): BelongsTo + { + return $this->belongsTo(PartnerMedia::class); + } +} diff --git a/app/Models/PartnerMedia.php b/app/Models/PartnerMedia.php index e53407a..6c3b215 100644 --- a/app/Models/PartnerMedia.php +++ b/app/Models/PartnerMedia.php @@ -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'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index edde80f..a463be0 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -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'); + } } diff --git a/database/factories/AnnouncementFactory.php b/database/factories/AnnouncementFactory.php new file mode 100644 index 0000000..e2cd522 --- /dev/null +++ b/database/factories/AnnouncementFactory.php @@ -0,0 +1,46 @@ + + */ +class AnnouncementFactory extends Factory +{ + protected $model = Announcement::class; + + /** + * Define the model's default state. + * + * @return array + */ + 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(), + ]); + } +} diff --git a/database/factories/AnnouncementUserFactory.php b/database/factories/AnnouncementUserFactory.php new file mode 100644 index 0000000..457cfb9 --- /dev/null +++ b/database/factories/AnnouncementUserFactory.php @@ -0,0 +1,49 @@ + + */ +class AnnouncementUserFactory extends Factory +{ + protected $model = AnnouncementUser::class; + + /** + * Define the model's default state. + * + * @return array + */ + 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, + ]); + } +} diff --git a/database/migrations/2025_12_12_090552_create_announcements_table.php b/database/migrations/2025_12_12_090552_create_announcements_table.php new file mode 100644 index 0000000..03c2eda --- /dev/null +++ b/database/migrations/2025_12_12_090552_create_announcements_table.php @@ -0,0 +1,30 @@ +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'); + } +}; diff --git a/database/migrations/2025_12_12_091146_create_announcement_media_table.php b/database/migrations/2025_12_12_091146_create_announcement_media_table.php new file mode 100644 index 0000000..f8afe1c --- /dev/null +++ b/database/migrations/2025_12_12_091146_create_announcement_media_table.php @@ -0,0 +1,33 @@ +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'); + } +};