From aac2ff67199458e807a0eb662ea3353815c97d5a Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 8 Apr 2026 08:21:28 +0700 Subject: [PATCH] refactor: introduce DataChangeInfolist and DataChangeTable classes for improved organization and maintainability of data change management --- .../DataChanges/DataChangesResource.php | 240 +----------------- .../Infolists/DataChangeInfolist.php | 144 +++++++++++ .../DataChanges/Tables/DataChangeTable.php | 114 +++++++++ 3 files changed, 266 insertions(+), 232 deletions(-) create mode 100644 app/Filament/Resources/Manage/DataChanges/Infolists/DataChangeInfolist.php create mode 100644 app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php diff --git a/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php b/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php index e7ca80d..da694ff 100644 --- a/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php +++ b/app/Filament/Resources/Manage/DataChanges/DataChangesResource.php @@ -2,32 +2,19 @@ namespace App\Filament\Resources\Manage\DataChanges; -use App\Enums\DataChangeStatus; use App\Enums\RoleEnum; -use App\Filament\Actions\DefaultBulkActions; -use App\Filament\Columns\RowIndexColumn; -use App\Filament\Resources\Manage\DataChanges\Actions\DataChangeRequest\AcceptAction; -use App\Filament\Resources\Manage\DataChanges\Actions\DataChangeRequest\RejectAction; +use App\Filament\Resources\Manage\DataChanges\Infolists\DataChangeInfolist; use App\Filament\Resources\Manage\DataChanges\Pages\ManageDataChanges; use App\Filament\Resources\Manage\DataChanges\Pages\ViewDataChange; -use App\Filament\Support\CheerfulNotification; -use App\Models\Company; +use App\Filament\Resources\Manage\DataChanges\Tables\DataChangeTable; use App\Models\DataChangeRequest; -use App\Models\Journalist; -use App\Models\PartnerMedia; use BackedEnum; -use Filament\Actions\BulkActionGroup; -use Filament\Actions\ViewAction; -use Filament\Infolists\Components\TextEntry; use Filament\Resources\Resource; use Filament\Schemas\Schema; use Filament\Support\Icons\Heroicon; -use Filament\Tables\Columns\TextColumn; -use Filament\Tables\Filters\SelectFilter; -use Filament\Tables\Filters\TrashedFilter; use Filament\Tables\Table; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Auth; use UnitEnum; class DataChangesResource extends Resource @@ -46,7 +33,7 @@ class DataChangesResource extends Resource public static function getNavigationBadge(): ?string { - if (auth()->user()->hasRole(RoleEnum::PERUSAHAAN)) { + if (Auth::user()?->hasRole(RoleEnum::PERUSAHAAN)) { return null; } @@ -55,230 +42,19 @@ public static function getNavigationBadge(): ?string public static function infolist(Schema $schema): Schema { - return $schema - ->schema([ - TextEntry::make('comparison') - ->hiddenLabel() - ->html() - ->state(function (DataChangeRequest $record): string { - $old = $record->old_data ?? []; - $new = $record->new_data ?? []; - - if (! is_array($old) || ! is_array($new)) { - return 'Format data tidak valid.'; - } - - $renderValue = function ($value) { - if (empty($value)) { - return 'Kosong'; - } - - $paths = is_array($value) ? $value : [$value]; - $isProbablyPath = false; - - foreach ($paths as $path) { - if (is_string($path) && (str_contains($path, '/') || str_contains($path, '\\'))) { - $isProbablyPath = true; - break; - } - } - - if ($isProbablyPath) { - return collect($paths) - ->map(function ($path) { - if (! is_string($path)) { - return e(json_encode($path)); - } - - $url = Storage::disk(config('filesystems.default'))->url($path); - $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); - - if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'])) { - return ""; - } - - if ($extension === 'pdf') { - return " -
- - ↗️ Buka PDF di Tab Baru -
- "; - } - - return "📎 Buka Dokumen (".strtoupper($extension).')'; - }) - ->implode('
'); - } - - return e(is_scalar($value) ? $value : json_encode($value)); - }; - - // 1. Deletion Request - if (isset($new['__delete_request__']) && $new['__delete_request__'] === true) { - return " -
-
".CheerfulNotification::getByKey('data_change.deletion_request_title')."
-

".CheerfulNotification::getByKey('data_change.deletion_request_desc').'

-
- '; - } - - // 2. New Addition Request - if (empty($old)) { - $rows = collect($new) - ->map(function ($value, $key) use ($renderValue) { - return sprintf( - "
-
%s
-
[BARU]
-
%s
-
", - e($key), - $renderValue($value) - ); - }) - ->implode(''); - - return " -
-
".CheerfulNotification::getByKey('data_change.addition_request_title')."
-

".CheerfulNotification::getByKey('data_change.addition_request_desc')."

-
- {$rows} - "; - } - - // 3. Standard Comparison (Update) - return collect($new) - ->map(function ($newValue, $key) use ($old, $renderValue) { - $oldValue = $old[$key] ?? null; - - if ($oldValue === $newValue) { - return null; - } - - return sprintf( - "
-
%s
-
-
- LAMA -
%s
-
-
- BARU -
%s
-
-
-
", - e($key), - $renderValue($oldValue), - $renderValue($newValue) - ); - }) - ->filter() - ->implode(''); - }) - ->columnSpanFull(), - ]) - ->columns(1); + return DataChangeInfolist::configure($schema); } public static function table(Table $table): Table { - return $table - ->columns([ - ...RowIndexColumn::make(), - - TextColumn::make('user.name') - ->label('Pemohon') - ->searchable() - ->sortable() - ->description(fn (DataChangeRequest $record): ?string => $record?->entity?->name) - ->visible(! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)), - - TextColumn::make('entity_type') - ->label('Jenis Data') - ->searchable() - ->sortable() - ->description(fn (DataChangeRequest $record): ?string => match (true) { - isset($record->new_data['__delete_request__']) => CheerfulNotification::getByKey('data_change.deletion'), - empty($record->old_data) => CheerfulNotification::getByKey('data_change.addition'), - default => CheerfulNotification::getByKey('data_change.change'), - }) - ->formatStateUsing(fn (DataChangeRequest $record): ?string => match ($record->entity_type) { - Company::class => 'Perusahaan', - PartnerMedia::class => 'Media', - Journalist::class => 'Jurnalis', - default => 'Lainnya', - }) - ->color(fn (DataChangeRequest $record): ?string => match ($record->entity_type) { - Company::class => 'success', - PartnerMedia::class => 'info', - Journalist::class => 'warning', - default => 'secondary', - }) - ->badge(), - - TextColumn::make('status') - ->label('Status') - ->searchable() - ->sortable() - ->badge() - ->formatStateUsing(fn (DataChangeStatus $state): string => $state->getLabel()) - ->color(fn (DataChangeStatus $state): string => $state->getColor()) - ->description(fn (DataChangeRequest $record): ?string => $record?->review ? 'Oleh: '.$record->review?->reviewer?->name : ''), - - TextColumn::make('change_reason') - ->label('Alasan Perubahan') - ->limit(50), - - TextColumn::make('review.note') - ->label('Alasan Penolakan') - ->limit(50), - - TextColumn::make('created_at') - ->label('Tgl Permohonan') - ->date() - ->sortable() - ->dateTime('l, d F Y H:i:s') - ->wrap(), - ]) - ->filters([ - SelectFilter::make('status') - ->label('Status') - ->options(DataChangeStatus::class) - ->native(false), - - TrashedFilter::make() - ->native(false) - ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), - ]) - ->recordActions([ - ViewAction::make(), - AcceptAction::make('accept'), - RejectAction::make('reject'), - ]) - ->toolbarActions([ - BulkActionGroup::make([ - ...DefaultBulkActions::make('permohonan perubahan data'), - ]), - ]) - ->emptyStateIcon(Heroicon::OutlinedDocumentText) - ->emptyStateHeading(fn () => CheerfulNotification::getByKey('data_change.empty_state_heading')) - ->emptyStateDescription(fn () => CheerfulNotification::getByKey('data_change.empty_state')) - ->defaultSort('created_at', 'desc') - ->deferFilters(false) - ->paginated([25, 50, 100, 'all']) - ->deferLoading(); + return DataChangeTable::configure($table); } public static function getEloquentQuery(): Builder { return parent::getEloquentQuery() - ->when(auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value), function (Builder $q): Builder { - return $q->where('user_id', auth()->id()); + ->when(Auth::user()?->hasRole(RoleEnum::PERUSAHAAN->value), function (Builder $q): Builder { + return $q->where('user_id', Auth::id()); }); } diff --git a/app/Filament/Resources/Manage/DataChanges/Infolists/DataChangeInfolist.php b/app/Filament/Resources/Manage/DataChanges/Infolists/DataChangeInfolist.php new file mode 100644 index 0000000..1c2eb31 --- /dev/null +++ b/app/Filament/Resources/Manage/DataChanges/Infolists/DataChangeInfolist.php @@ -0,0 +1,144 @@ +schema([ + TextEntry::make('comparison') + ->hiddenLabel() + ->html() + ->state(function (DataChangeRequest $record): string { + $old = $record->old_data ?? []; + $new = $record->new_data ?? []; + + if (! is_array($old) || ! is_array($new)) { + return 'Format data tidak valid.'; + } + + $renderValue = function ($value) { + if (empty($value)) { + return 'Kosong'; + } + + $paths = is_array($value) ? $value : [$value]; + $isProbablyPath = false; + + foreach ($paths as $path) { + if (is_string($path) && (str_contains($path, '/') || str_contains($path, '\\'))) { + $isProbablyPath = true; + break; + } + } + + if ($isProbablyPath) { + return collect($paths) + ->map(function ($path) { + if (! is_string($path)) { + return e(json_encode($path)); + } + + $url = Storage::disk(config('filesystems.default'))->url($path); + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + if (in_array($extension, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'])) { + return ""; + } + + if ($extension === 'pdf') { + return " +
+ + ↗️ Buka PDF di Tab Baru +
+ "; + } + + return "📎 Buka Dokumen (".strtoupper($extension).')'; + }) + ->implode('
'); + } + + return e(is_scalar($value) ? $value : json_encode($value)); + }; + + // 1. Deletion Request + if (isset($new['__delete_request__']) && $new['__delete_request__'] === true) { + return " +
+
".CheerfulNotification::getByKey('data_change.deletion_request_title')."
+

".CheerfulNotification::getByKey('data_change.deletion_request_desc').'

+
+ '; + } + + // 2. New Addition Request + if (empty($old)) { + $rows = collect($new) + ->map(function ($value, $key) use ($renderValue) { + return sprintf( + "
+
%s
+
[BARU]
+
%s
+
", + e($key), + $renderValue($value) + ); + }) + ->implode(''); + + return " +
+
".CheerfulNotification::getByKey('data_change.addition_request_title')."
+

".CheerfulNotification::getByKey('data_change.addition_request_desc')."

+
+ {$rows} + "; + } + + // 3. Standard Comparison (Update) + return collect($new) + ->map(function ($newValue, $key) use ($old, $renderValue) { + $oldValue = $old[$key] ?? null; + + if ($oldValue === $newValue) { + return null; + } + + return sprintf( + "
+
%s
+
+
+ LAMA +
%s
+
+
+ BARU +
%s
+
+
+
", + e($key), + $renderValue($oldValue), + $renderValue($newValue) + ); + }) + ->filter() + ->implode(''); + }) + ->columnSpanFull(), + ]) + ->columns(1); + } +} diff --git a/app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php b/app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php new file mode 100644 index 0000000..a0b35d2 --- /dev/null +++ b/app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php @@ -0,0 +1,114 @@ +columns([ + ...RowIndexColumn::make(), + + TextColumn::make('user.name') + ->label('Pemohon') + ->searchable() + ->sortable() + ->description(fn (DataChangeRequest $record): ?string => $record?->entity?->name) + ->visible(! auth()->user()->hasRole(RoleEnum::PERUSAHAAN->value)), + + TextColumn::make('entity_type') + ->label('Jenis Data') + ->searchable() + ->sortable() + ->description(fn (DataChangeRequest $record): ?string => match (true) { + isset($record->new_data['__delete_request__']) => CheerfulNotification::getByKey('data_change.deletion'), + empty($record->old_data) => CheerfulNotification::getByKey('data_change.addition'), + default => CheerfulNotification::getByKey('data_change.change'), + }) + ->formatStateUsing(fn (DataChangeRequest $record): ?string => match ($record->entity_type) { + Company::class => 'Perusahaan', + PartnerMedia::class => 'Media', + Journalist::class => 'Jurnalis', + default => 'Lainnya', + }) + ->color(fn (DataChangeRequest $record): ?string => match ($record->entity_type) { + Company::class => 'success', + PartnerMedia::class => 'info', + Journalist::class => 'warning', + default => 'secondary', + }) + ->badge(), + + TextColumn::make('status') + ->label('Status') + ->searchable() + ->sortable() + ->badge() + ->formatStateUsing(fn (DataChangeStatus $state): string => $state->getLabel()) + ->color(fn (DataChangeStatus $state): string => $state->getColor()) + ->description(fn (DataChangeRequest $record): ?string => $record?->review ? 'Oleh: '.$record->review?->reviewer?->name : ''), + + TextColumn::make('change_reason') + ->label('Alasan Perubahan') + ->limit(50), + + TextColumn::make('review.note') + ->label('Alasan Penolakan') + ->limit(50), + + TextColumn::make('created_at') + ->label('Tgl Permohonan') + ->date() + ->sortable() + ->dateTime('l, d F Y H:i:s') + ->wrap(), + ]) + ->filters([ + SelectFilter::make('status') + ->label('Status') + ->options(DataChangeStatus::class) + ->native(false), + + TrashedFilter::make() + ->native(false) + ->visible(fn () => auth()->user()?->hasRole(RoleEnum::DEVELOPER->value)), + ]) + ->recordActions([ + ViewAction::make(), + AcceptAction::make('accept'), + RejectAction::make('reject'), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + ...DefaultBulkActions::make('permohonan perubahan data'), + ]), + ]) + ->emptyStateIcon(Heroicon::OutlinedDocumentText) + ->emptyStateHeading(fn () => CheerfulNotification::getByKey('data_change.empty_state_heading')) + ->emptyStateDescription(fn () => CheerfulNotification::getByKey('data_change.empty_state')) + ->defaultSort('created_at', 'desc') + ->deferFilters(false) + ->paginated([25, 50, 100, 'all']) + ->deferLoading(); + } +}