feat(media monitoring): implement crud

- create enums for channel
- create model and migration
- create many to many relation to theme
- feat resource
This commit is contained in:
Yoga Pangestu 2025-12-09 11:27:59 +07:00
parent 6161126d9f
commit a6ea4cfdc5
12 changed files with 529 additions and 0 deletions

26
app/Enums/Channel.php Normal file
View File

@ -0,0 +1,26 @@
<?php
namespace App\Enums;
use App\Traits\Enum\WithComment;
use App\Traits\Enum\WithValue;
enum Channel: string
{
use WithComment, WithValue;
case WEBSITE = 'Website';
case TIKTOK = 'Tiktok';
case YOUTUBE = 'Youtube';
case FACEBOOK = 'Facebook';
case INSTAGRAM = 'Instagram';
case OTHER = 'Other';
public static function options(): array
{
return array_combine(
array_map(fn ($status) => $status->value, self::cases()),
array_map(fn ($status) => $status->value, self::cases())
);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\Filament\Resources\Monitoring\MediaMonitorings;
use App\Filament\Resources\Monitoring\MediaMonitorings\Pages\CreateMediaMonitoring;
use App\Filament\Resources\Monitoring\MediaMonitorings\Pages\EditMediaMonitoring;
use App\Filament\Resources\Monitoring\MediaMonitorings\Pages\ListMediaMonitorings;
use App\Filament\Resources\Monitoring\MediaMonitorings\Schemas\MediaMonitoringForm;
use App\Filament\Resources\Monitoring\MediaMonitorings\Tables\MediaMonitoringsTable;
use App\Models\MediaMonitoring;
use BackedEnum;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use UnitEnum;
class MediaMonitoringResource extends Resource
{
protected static ?string $model = MediaMonitoring::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::ClipboardDocumentCheck;
protected static ?string $navigationLabel = 'Monitoring Media';
protected static string|UnitEnum|null $navigationGroup = 'Monitoring';
protected static ?string $slug = 'monitoring/media-monitorings';
protected static ?string $recordTitleAttribute = 'media_name';
protected static ?int $navigationSort = 12;
public static function form(Schema $schema): Schema
{
return MediaMonitoringForm::configure($schema);
}
public static function table(Table $table): Table
{
return MediaMonitoringsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListMediaMonitorings::route('/'),
'create' => CreateMediaMonitoring::route('/create'),
'edit' => EditMediaMonitoring::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Filament\Resources\Monitoring\MediaMonitorings\Pages;
use App\Filament\Resources\Monitoring\MediaMonitorings\MediaMonitoringResource;
use App\Models\MediaMonitoring;
use Filament\Actions\Action;
use Filament\Resources\Pages\CreateRecord;
class CreateMediaMonitoring extends CreateRecord
{
protected static string $resource = MediaMonitoringResource::class;
protected static ?string $title = 'Tambah Monitoring Media';
protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('index');
}
protected function getHeaderActions(): array
{
return [
Action::make('back')
->label('Kembali')
->url(fn (): string => route('filament.dashboard.resources.monitoring.media-monitorings.index'))
->outlined()
->color('secondary'),
];
}
protected function mutateFormDataBeforeCreate(array $data): array
{
$latestCode = MediaMonitoring::max('code');
$lastNumber = $latestCode
? intval(substr($latestCode, 3))
: 0;
$newNumber = str_pad($lastNumber + 1, 6, '0', STR_PAD_LEFT);
$data['code'] = 'ADN'.$newNumber;
return $data;
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Filament\Resources\Monitoring\MediaMonitorings\Pages;
use App\Filament\Resources\Monitoring\MediaMonitorings\MediaMonitoringResource;
use Filament\Actions\Action;
use Filament\Resources\Pages\EditRecord;
class EditMediaMonitoring extends EditRecord
{
protected static string $resource = MediaMonitoringResource::class;
protected function getHeaderActions(): array
{
return [
Action::make('back')
->label('Kembali')
->url(fn (): string => route('filament.dashboard.resources.monitoring.media-monitorings.index'))
->outlined()
->color('secondary'),
];
}
protected function getRedirectUrl(): string
{
return $this->getResource()::getUrl('index');
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Filament\Resources\Monitoring\MediaMonitorings\Pages;
use App\Filament\Resources\Monitoring\MediaMonitorings\MediaMonitoringResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListMediaMonitorings extends ListRecords
{
protected static string $resource = MediaMonitoringResource::class;
protected static ?string $title = 'Monitoring Media';
protected function getHeaderActions(): array
{
return [
CreateAction::make()
->label('Tambah'),
];
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace App\Filament\Resources\Monitoring\MediaMonitorings\Schemas;
use App\Enums\Channel;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\SpatieMediaLibraryFileUpload;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class MediaMonitoringForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Section::make([
TextInput::make('media_name')
->label('Nama Media')
->placeholder('PWK News')
->autocomplete(false)
->autofocus()
->required()
->maxLength(50),
TextInput::make('title')
->label('Judul')
->placeholder('Berita Purwakarta Hari Ini')
->autocomplete(false)
->required()
->maxLength(255),
Grid::make(2)
->schema([
TextInput::make('writter')
->label('Penulis')
->placeholder('John Doe')
->autocomplete(false)
->required()
->maxLength(50),
TextInput::make('news_page')
->label('Halaman Berita')
->placeholder('Halaman 1 s/d 10')
->autocomplete(false)
->nullable()
->maxLength(20),
]),
TextInput::make('link')
->label('Tautan')
->placeholder('https://www.pwknews.com')
->autocomplete(false)
->nullable()
->maxLength(50)
->url(),
RichEditor::make('quote')
->label('Kutipan')
->placeholder('....')
->nullable(),
RichEditor::make('content')
->label('Konten')
->placeholder('....')
->required(),
])->columnSpan(2),
Section::make([
Select::make('channel')
->label('Kanal')
->options(Channel::options())
->native(false)
->required()
->in(implode(',', array_column(Channel::cases(), 'value'))),
Select::make('themes')
->label('Tema')
->relationship('themes', 'name')
->native(false)
->multiple()
->preload()
->required(),
DatePicker::make('release_date')
->label('Tanggal Rilis')
->placeholder(fn () => now()->format('Y-m-d'))
->native(false)
->displayFormat('l, d F Y')
->required(),
TextInput::make('influencer')
->label('Influencer')
->placeholder('Sekretariat Daerah')
->autocomplete(false)
->nullable()
->maxLength(50),
TextInput::make('keyword')
->label('Kata Kunci')
->placeholder('Purwakarta, Berita Terbaru, Hari Ini')
->autocomplete(false)
->required()
->maxLength(100),
SpatieMediaLibraryFileUpload::make('image')
->label('Gambar')
->disk(config('filesystems.default'))
->acceptedFileTypes(['images/*'])
->maxSize(1024 * 3)
->collection('media-monitorings')
->image()
->required(),
]),
])
->columns(3);
}
}

View File

@ -0,0 +1,104 @@
<?php
namespace App\Filament\Resources\Monitoring\MediaMonitorings\Tables;
use App\Filament\Actions\DefaultBulkActions;
use App\Filament\Columns\TimestampColumns;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\EditAction;
use Filament\Actions\ForceDeleteAction;
use Filament\Actions\RestoreAction;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\TrashedFilter;
use Filament\Tables\Table;
class MediaMonitoringsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('code')
->label('Kode')
->searchable()
->sortable(),
TextColumn::make('media_name')
->label('Nama media')
->searchable()
->sortable(),
TextColumn::make('title')
->label('Judul')
->searchable()
->sortable()
->wrap(),
TextColumn::make('channel')
->label('Kanal')
->searchable()
->sortable(),
TextColumn::make('writter')
->label('Penulis')
->searchable()
->sortable(),
TextColumn::make('link')
->label('Tautan')
->searchable()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('news_page')
->label('Halaman Berita')
->searchable()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('influencer')
->searchable()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('keyword')
->searchable()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
TextColumn::make('themes.name')
->label('Tema')
->searchable()
->sortable()
->wrap()
->badge(),
TextColumn::make('release_date')
->label('Tgl Rilis')
->searchable()
->sortable()
->date('l, d F Y'),
...TimestampColumns::make(),
])
->filters([
TrashedFilter::make()->native(false),
])
->recordActions([
EditAction::make(),
DeleteAction::make(),
ForceDeleteAction::make(),
RestoreAction::make(),
])
->toolbarActions([
BulkActionGroup::make([
...DefaultBulkActions::make('Monitoring Media'),
]),
])
->emptyStateIcon('heroicon-o-bookmark')
->emptyStateDescription('Setelah Anda membubat data pertama, maka akan muncul disini.')
->defaultSort('created_at', 'desc')
->deferFilters(false);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
class MediaMonitoring extends Model implements HasMedia
{
use InteractsWithMedia, SoftDeletes;
protected $guarded = ['id'];
public function themes(): BelongsToMany
{
return $this->belongsToMany(Theme::class);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class MediaMonitoringTheme extends Model
{
protected $table = 'media_monitoring_theme';
protected $guarded = ['id'];
}

View File

@ -4,6 +4,7 @@
use App\Enums\IsActive;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Theme extends Model
@ -19,4 +20,9 @@ protected function casts(): array
'sort_order' => 'integer',
];
}
public function mediaMonitorings(): BelongsToMany
{
return $this->belongsToMany(MediaMonitoring::class);
}
}

View File

@ -0,0 +1,41 @@
<?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('media_monitorings', function (Blueprint $table) {
$table->id();
$table->string('code', 10);
$table->string('media_name', 50);
$table->text('title', 255);
$table->string('channel', 20);
$table->string('writter', 50);
$table->text('link', 50)->nullable();
$table->string('news_page', 20)->nullable();
$table->text('quote')->nullable();
$table->text('content');
$table->string('influencer', 50)->nullable();
$table->string('keyword', 100);
$table->date('release_date');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('media_monitorings');
}
};

View File

@ -0,0 +1,32 @@
<?php
use App\Models\MediaMonitoring;
use App\Models\Theme;
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('media_monitoring_theme', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(MediaMonitoring::class);
$table->foreignIdFor(Theme::class);
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('media_monitoring_theme');
}
};