feat(content recap): implement crud

- create model and migration
- relation to classification
- create resource
- create enum for support option
This commit is contained in:
Yoga Pangestu 2025-12-09 15:36:50 +07:00
parent 2f2de29dd5
commit f9f1e60b2e
11 changed files with 429 additions and 0 deletions

23
app/Enums/ContentType.php Normal file
View File

@ -0,0 +1,23 @@
<?php
namespace App\Enums;
use App\Traits\Enum\WithValue;
enum ContentType: string
{
use WithValue;
case FOTO = 'Foto';
case TEXT = 'Teks';
case GRAPHIC = 'Grafis';
case VIDEO = 'Video';
public static function options(): array
{
return array_combine(
array_map(fn ($status) => $status->value, self::cases()),
array_map(fn ($status) => $status->value, self::cases())
);
}
}

22
app/Enums/SocialMedia.php Normal file
View File

@ -0,0 +1,22 @@
<?php
namespace App\Enums;
use App\Traits\Enum\WithValue;
enum SocialMedia: string
{
use WithValue;
case PPID_DISKOMINFO_PURWAKARTA = 'PPID Diskominfo Purwakarta';
case DISKOMINFO_PURWAKARTA = 'Diskominfo Purwakarta';
case PURWAKARTA_SABER_HOAKS = 'Purwakarta Saber Hoaks';
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\ContentRecaps;
use App\Filament\Resources\Monitoring\ContentRecaps\Pages\CreateContentRecap;
use App\Filament\Resources\Monitoring\ContentRecaps\Pages\EditContentRecap;
use App\Filament\Resources\Monitoring\ContentRecaps\Pages\ListContentRecaps;
use App\Filament\Resources\Monitoring\ContentRecaps\Schemas\ContentRecapForm;
use App\Filament\Resources\Monitoring\ContentRecaps\Tables\ContentRecapsTable;
use App\Models\ContentRecap;
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 ContentRecapResource extends Resource
{
protected static ?string $model = ContentRecap::class;
protected static string|BackedEnum|null $navigationIcon = Heroicon::ClipboardDocumentList;
protected static ?string $navigationLabel = 'Rekap Konten';
protected static string|UnitEnum|null $navigationGroup = 'Monitoring';
protected static ?string $slug = 'monitoring/content-recaps';
protected static ?string $recordTitleAttribute = 'title';
protected static ?int $navigationSort = 13;
public static function form(Schema $schema): Schema
{
return ContentRecapForm::configure($schema);
}
public static function table(Table $table): Table
{
return ContentRecapsTable::configure($table);
}
public static function getRelations(): array
{
return [
//
];
}
public static function getPages(): array
{
return [
'index' => ListContentRecaps::route('/'),
'create' => CreateContentRecap::route('/create'),
'edit' => EditContentRecap::route('/{record}/edit'),
];
}
public static function getRecordRouteBindingEloquentQuery(): Builder
{
return parent::getRecordRouteBindingEloquentQuery()
->withoutGlobalScopes([
SoftDeletingScope::class,
]);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Filament\Resources\Monitoring\ContentRecaps\Pages;
use App\Filament\Resources\Monitoring\ContentRecaps\ContentRecapResource;
use Filament\Actions\Action;
use Filament\Resources\Pages\CreateRecord;
class CreateContentRecap extends CreateRecord
{
protected static string $resource = ContentRecapResource::class;
protected static ?string $title = 'Tambah Rekap Konten';
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.content-recaps.index'))
->outlined()
->color('secondary'),
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Filament\Resources\Monitoring\ContentRecaps\Pages;
use App\Filament\Resources\Monitoring\ContentRecaps\ContentRecapResource;
use Filament\Actions\Action;
use Filament\Resources\Pages\EditRecord;
class EditContentRecap extends EditRecord
{
protected static string $resource = ContentRecapResource::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\ContentRecaps\Pages;
use App\Filament\Resources\Monitoring\ContentRecaps\ContentRecapResource;
use Filament\Actions\CreateAction;
use Filament\Resources\Pages\ListRecords;
class ListContentRecaps extends ListRecords
{
protected static string $resource = ContentRecapResource::class;
protected static ?string $title = 'Rekap Konten';
protected function getHeaderActions(): array
{
return [
CreateAction::make()
->label('Tambah'),
];
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace App\Filament\Resources\Monitoring\ContentRecaps\Schemas;
use App\Enums\Channel;
use App\Enums\ContentType;
use App\Enums\SocialMedia;
use Filament\Forms\Components\DatePicker;
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 ContentRecapForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Section::make([
TextInput::make('title')
->label('Judul')
->placeholder('Reels Kerja Sama DPMPTSP')
->autocomplete(false)
->autofocus()
->required()
->maxLength(255),
TextInput::make('link')
->label('Tautan')
->placeholder('https://www.pwknews.com')
->autocomplete(false)
->nullable()
->maxLength(50)
->url(),
Grid::make(2)
->schema([
Select::make('type')
->label('Tipe')
->options(ContentType::options())
->native(false)
->required()
->in(implode(',', array_column(ContentType::cases(), 'value'))),
Select::make('channel')
->label('Kanal')
->options(Channel::options())
->native(false)
->required()
->in(implode(',', array_column(Channel::cases(), 'value'))),
Select::make('social_media')
->label('Media Sosial')
->options(SocialMedia::options())
->native(false)
->required()
->in(implode(',', array_column(SocialMedia::cases(), 'value'))),
DatePicker::make('posting_date')
->label('Tanggal Posting')
->placeholder(fn () => now()->format('Y-m-d'))
->native(false)
->displayFormat('l, d F Y')
->required(),
]),
])->columnSpan(2),
Section::make([
Select::make('classification_id')
->label('Klasifikasi')
->relationship('classification', 'name')
->native(false)
->preload()
->required(),
SpatieMediaLibraryFileUpload::make('image')
->label('Gambar')
->disk(config('filesystems.default'))
->acceptedFileTypes(['images/*'])
->maxSize(1024 * 3)
->collection('content-recpaps')
->image()
->required(),
]),
])
->columns(3);
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace App\Filament\Resources\Monitoring\ContentRecaps\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 ContentRecapsTable
{
public static function configure(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')
->label('Judul')
->searchable()
->sortable()
->wrap(),
TextColumn::make('type')
->label('Tipe')
->searchable()
->sortable(),
TextColumn::make('channel')
->label('Kanal')
->searchable()
->sortable(),
TextColumn::make('social_media')
->label('Media Sosial')
->searchable()
->sortable(),
TextColumn::make('classification.name')
->label('Klasifikasi')
->searchable()
->sortable(),
TextColumn::make('link')
->label('Tautan')
->searchable()
->sortable(),
TextColumn::make('posting_date')
->label('Tgl Posting')
->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('Rekap Konten'),
]),
])
->emptyStateIcon('heroicon-o-bookmark')
->emptyStateDescription('Setelah Anda membubat data pertama, maka akan muncul disini.')
->defaultSort('created_at', 'desc')
->deferFilters(false);
}
}

View File

@ -25,4 +25,9 @@ public function subClassifications(): HasMany
{
return $this->hasMany(SubClassification::class);
}
public function contentRecaps(): HasMany
{
return $this->hasMany(ContentRecap::class);
}
}

View File

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

View File

@ -0,0 +1,38 @@
<?php
use App\Enums\ContentType;
use App\Models\Classification;
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('content_recaps', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Classification::class)->constrained();
$table->string('title', 255);
$table->text('link', 50)->nullable();
$table->date('posting_date');
$table->enum('type', [ContentType::values()])->default(ContentType::FOTO);
$table->string('channel', 20);
$table->string('social_media', 50);
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable()->useCurrentOnUpdate();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('content_recaps');
}
};