From 5e5a7caeb54c25cd247ff725398d259387060cd1 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Thu, 9 Apr 2026 11:06:17 +0700 Subject: [PATCH] refactor: modularize data change comparison views and update media disk configuration --- .../{DataChangeRequest => }/AcceptAction.php | 2 +- .../{DataChangeRequest => }/RejectAction.php | 2 +- .../Infolists/DataChangeInfolist.php | 297 +++++++++++------- .../DataChanges/Tables/DataChangeTable.php | 12 +- app/Http/Middleware/DynamicFilamentTheme.php | 88 ++++++ lang/id/notif.php | 56 ++++ 6 files changed, 340 insertions(+), 117 deletions(-) rename app/Filament/Resources/Manage/DataChanges/Actions/{DataChangeRequest => }/AcceptAction.php (98%) rename app/Filament/Resources/Manage/DataChanges/Actions/{DataChangeRequest => }/RejectAction.php (97%) diff --git a/app/Filament/Resources/Manage/DataChanges/Actions/DataChangeRequest/AcceptAction.php b/app/Filament/Resources/Manage/DataChanges/Actions/AcceptAction.php similarity index 98% rename from app/Filament/Resources/Manage/DataChanges/Actions/DataChangeRequest/AcceptAction.php rename to app/Filament/Resources/Manage/DataChanges/Actions/AcceptAction.php index 3a12cb1..ffb24f9 100644 --- a/app/Filament/Resources/Manage/DataChanges/Actions/DataChangeRequest/AcceptAction.php +++ b/app/Filament/Resources/Manage/DataChanges/Actions/AcceptAction.php @@ -1,6 +1,6 @@ new_data ?? []; if (! is_array($old) || ! is_array($new)) { - return 'Format data tidak valid.'; + return self::alert('Format data tidak valid.', 'warning'); } - $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').'

-
- '; + return self::deletionView($old); } - // 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} - "; + return self::additionView($new); } - // 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(''); + return self::updateView($old, $new); }), ]), ]) ->columns(1); } + + private static function deletionView(array $old): string + { + $title = CheerfulNotification::getByKey('data_change.deletion_request_title'); + $desc = CheerfulNotification::getByKey('data_change.deletion_request_desc'); + + $rows = collect($old) + ->map(fn ($v, $k) => self::row($k, self::renderValue($v))) + ->implode(''); + + return self::card( + header: self::header('del', $title, $desc, self::badge('Penghapusan', 'del')), + body: $rows ?: self::emptyRow(), + ); + } + + private static function additionView(array $new): string + { + $title = CheerfulNotification::getByKey('data_change.addition_request_title'); + $desc = CheerfulNotification::getByKey('data_change.addition_request_desc'); + + $rows = collect($new) + ->map(fn ($v, $k) => self::row($k, self::renderValue($v))) + ->implode(''); + + return self::card( + header: self::header('add', $title, $desc, self::badge('Penambahan Baru', 'add')), + body: $rows ?: self::emptyRow(), + ); + } + + private static function updateView(array $old, array $new): string + { + $changed = collect($new) + ->map(function ($newVal, $key) use ($old) { + $oldVal = $old[$key] ?? null; + + if ($oldVal === $newVal) { + return null; + } + + return " +
+
".e($key)."
+
+
".self::renderValue($oldVal)."
+
".self::renderValue($newVal).'
+
+
'; + }) + ->filter(); + + if ($changed->isEmpty()) { + return self::alert('Tidak ada perubahan data yang terdeteksi.', 'info'); + } + + $count = $changed->count(); + $label = $count === 1 ? '1 field diubah' : "{$count} field diubah"; + + $diffHeader = " +
+
+
+
Sebelum
+
Sesudah
+
+
"; + + return self::card( + header: self::header('edit', CheerfulNotification::getByKey('data_change.comparison_title'), CheerfulNotification::getByKey('data_change.comparison_desc'), self::badge($label, 'default')), + body: $diffHeader.$changed->implode(''), + ); + } + + private static function card(string $header, string $body): string + { + return " +
+ {$header} +
{$body}
+
"; + } + + private static function header(string $type, string $title, string $desc, string $badge): string + { + $icon = self::icon($type); + + return " +
+ {$icon} +
+
{$title}
+
{$desc}
+
+ {$badge} +
"; + } + + private static function row(string $key, string $value): string + { + return " +
+
".e($key)."
+
{$value}
+
"; + } + + private static function emptyRow(): string + { + return "
Tidak ada data tersedia.
"; + } + + private static function badge(string $label, string $variant): string + { + [$bg, $color, $border] = match ($variant) { + 'del' => ['var(--dcr-badge-del-bg)', 'var(--dcr-badge-del-color)', 'var(--dcr-badge-del-border)'], + 'add' => ['var(--dcr-badge-add-bg)', 'var(--dcr-badge-add-color)', 'var(--dcr-badge-add-border)'], + default => ['var(--dcr-badge-bg)', 'var(--dcr-badge-color)', 'var(--dcr-badge-border)'], + }; + + return "{$label}"; + } + + private static function alert(string $message, string $type = 'info'): string + { + $icon = $type === 'warning' ? '⚠' : 'ℹ'; + + return " +
+ {$icon} + {$message} +
"; + } + + private static function icon(string $type): string + { + return match ($type) { + 'del' => "", + 'add' => "", + 'edit' => "", + default => '', + }; + } + + private static function renderValue(mixed $value): string + { + if ($value === null || $value === '' || (is_array($value) && empty($value))) { + return ""; + } + + $paths = is_array($value) ? $value : [$value]; + + return collect($paths)->map(function ($item) { + if (! is_string($item)) { + return "".e(json_encode($item)).''; + } + + if (preg_match('/^https?:\/\//i', $item)) { + return "".e($item).''; + } + + if ((str_contains($item, '/') || str_contains($item, '\\')) && ! str_contains($item, ' ')) { + $url = Storage::disk(config('filesystems.default'))->url($item); + $ext = strtolower(pathinfo($item, PATHINFO_EXTENSION)); + + if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'])) { + return ""; + } + + if ($ext === 'pdf') { + return " +
+ +
"; + } + + $extUp = $ext ? strtoupper($ext) : 'FILE'; + + return " + + Dokumen {$extUp} + "; + } + + return "".e($item).''; + })->implode('
'); + } } diff --git a/app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php b/app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php index 12333bf..cf4ee5e 100644 --- a/app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php +++ b/app/Filament/Resources/Manage/DataChanges/Tables/DataChangeTable.php @@ -6,8 +6,8 @@ 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\Actions\AcceptAction; +use App\Filament\Resources\Manage\DataChanges\Actions\RejectAction; use App\Filament\Support\CheerfulNotification; use App\Models\Company; use App\Models\DataChangeRequest; @@ -29,22 +29,16 @@ public static function configure(Table $table): Table ->columns([ ...RowIndexColumn::make(), - TextColumn::make('user.name') + TextColumn::make('user.company.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', diff --git a/app/Http/Middleware/DynamicFilamentTheme.php b/app/Http/Middleware/DynamicFilamentTheme.php index 04081ca..1ab1ec3 100644 --- a/app/Http/Middleware/DynamicFilamentTheme.php +++ b/app/Http/Middleware/DynamicFilamentTheme.php @@ -74,6 +74,94 @@ public function handle(Request $request, Closure $next): Response :root { --font-family: '{$font}', sans-serif; --c-border-radius: {$radius}; + + /* ── DataChangeInfolist tokens (light) ── */ + --dcr-bg: #ffffff; + --dcr-border: #e4e4e7; + --dcr-header-bg: #fafafa; + --dcr-header-border: #e4e4e7; + --dcr-title: #18181b; + --dcr-desc: #71717a; + --dcr-key-bg: #fafafa; + --dcr-key-border: #f4f4f5; + --dcr-key-color: #71717a; + --dcr-val-color: #18181b; + --dcr-row-border: #f4f4f5; + --dcr-muted: #a1a1aa; + /* diff */ + --dcr-old-bg: #fff5f5; + --dcr-old-border: #fee2e2; + --dcr-old-color: #18181b; + --dcr-new-bg: #f0fdf4; + --dcr-new-color: #18181b; + /* badge: neutral */ + --dcr-badge-bg: #f4f4f5; + --dcr-badge-color: #52525b; + --dcr-badge-border: #e4e4e7; + /* badge: destructive */ + --dcr-badge-del-bg: #fef2f2; + --dcr-badge-del-color: #b91c1c; + --dcr-badge-del-border: #fecaca; + /* badge: success */ + --dcr-badge-add-bg: #f0fdf4; + --dcr-badge-add-color: #15803d; + --dcr-badge-add-border: #bbf7d0; + /* alert */ + --dcr-alert-bg: #f8fafc; + --dcr-alert-border: #e2e8f0; + --dcr-alert-color: #475569; + /* icon */ + --dcr-icon-del: #b91c1c; + --dcr-icon-add: #15803d; + --dcr-icon-edit: #52525b; + /* file */ + --dcr-link: #3b82f6; + --dcr-img-border: #e4e4e7; + } + + .dark { + /* ── DataChangeInfolist tokens (dark) ── */ + --dcr-bg: #09090b; + --dcr-border: #27272a; + --dcr-header-bg: #18181b; + --dcr-header-border: #27272a; + --dcr-title: #fafafa; + --dcr-desc: #71717a; + --dcr-key-bg: #18181b; + --dcr-key-border: #27272a; + --dcr-key-color: #71717a; + --dcr-val-color: #e4e4e7; + --dcr-row-border: #27272a; + --dcr-muted: #52525b; + /* diff */ + --dcr-old-bg: #1c0c0c; + --dcr-old-border: #3f1c22; + --dcr-old-color: #fca5a5; + --dcr-new-bg: #0c1c0e; + --dcr-new-color: #86efac; + /* badge: neutral */ + --dcr-badge-bg: #27272a; + --dcr-badge-color: #a1a1aa; + --dcr-badge-border: #3f3f46; + /* badge: destructive */ + --dcr-badge-del-bg: #1c0c0c; + --dcr-badge-del-color: #f87171; + --dcr-badge-del-border: #7f1d1d; + /* badge: success */ + --dcr-badge-add-bg: #0c1c0e; + --dcr-badge-add-color: #4ade80; + --dcr-badge-add-border: #14532d; + /* alert */ + --dcr-alert-bg: #18181b; + --dcr-alert-border: #27272a; + --dcr-alert-color: #a1a1aa; + /* icon */ + --dcr-icon-del: #f87171; + --dcr-icon-add: #4ade80; + --dcr-icon-edit: #a1a1aa; + /* file */ + --dcr-link: #60a5fa; + --dcr-img-border: #3f3f46; } /* Override Filament maximum width if centered */ diff --git a/lang/id/notif.php b/lang/id/notif.php index 2d92b94..6b058a4 100644 --- a/lang/id/notif.php +++ b/lang/id/notif.php @@ -652,6 +652,62 @@ 'cheerful' => 'Belum Ada Pengajuan Perubahan! 📂✨', 'formal' => 'Tidak Ada Pengajuan Perubahan', ], + // Action modal headings + 'reject_title' => [ + 'cheerful' => 'Yakin Mau Ditolak? 🛑🤔', + 'formal' => 'Konfirmasi Penolakan Perubahan Data', + ], + 'approve_title' => [ + 'cheerful' => 'Udah Dicek Semua? Kita Setujui! ✅🚀', + 'formal' => 'Konfirmasi Persetujuan Perubahan Data', + ], + 'approve_desc' => [ + 'cheerful' => 'Yakin nih udah dicek semua? Kalau udah, yuk langsung disetujui biar datanya segera update! ✨🚀', + 'formal' => 'Apakah Anda yakin ingin menyetujui perubahan data ini? Data pada sistem akan diperbarui secara otomatis.', + ], + // Infolist card headers — addition request + 'addition_request_title' => [ + 'cheerful' => 'Ada Data Baru yang Mau Masuk! 🆕🎉', + 'formal' => 'Permohonan Penambahan Data', + ], + 'addition_request_desc' => [ + 'cheerful' => 'Cek dulu yuk, ada data baru yang menunggu persetujuan buat masuk ke sistem! 👀✨', + 'formal' => 'Berikut adalah data baru yang diajukan untuk ditambahkan ke dalam sistem.', + ], + // Infolist card headers — deletion request + 'deletion_request_title' => [ + 'cheerful' => 'Ada yang Mau Dihapus Nih! 🗑️⚠️', + 'formal' => 'Permohonan Penghapusan Data', + ], + 'deletion_request_desc' => [ + 'cheerful' => 'Hati-hati ya! Data di bawah ini diajukan untuk dihapus. Pastikan sudah diperiksa dengan teliti sebelum disetujui! 👀', + 'formal' => 'Berikut adalah data yang diajukan untuk dihapus secara permanen dari sistem. Tinjau dengan seksama.', + ], + // Infolist card headers — update/comparison view + 'comparison_title' => [ + 'cheerful' => 'Nih, Ada yang Berubah! 📝🔄', + 'formal' => 'Perbandingan Perubahan Data', + ], + 'comparison_desc' => [ + 'cheerful' => 'Yuk bandingkan data lamanya dengan yang baru. Pastikan perubahannya sudah sesuai sebelum disetujui! 🧐', + 'formal' => 'Berikut perbandingan antara data lama dan data baru yang diajukan untuk ditinjau.', + ], + 'change_approved' => [ + 'cheerful' => 'Perubahan Disetujui! ✅🚀', + 'formal' => 'Perubahan Disetujui', + ], + 'change_success_desc' => [ + 'cheerful' => 'Yeay! Perubahan data telah disetujui dan berhasil diterapkan ke sistem. Mantap! ✨', + 'formal' => 'Perubahan data telah disetujui dan berhasil diterapkan.', + ], + 'request_rejected' => [ + 'cheerful' => 'Yah, Perubahan Ditolak 😔', + 'formal' => 'Perubahan Ditolak', + ], + 'request_rejected_desc' => [ + 'cheerful' => 'Maaf ya, pengajuan perubahan data kamu terpaksa ditolak untuk saat ini. Tetap semangat! 💪', + 'formal' => 'Pengajuan perubahan data Anda tidak dapat disetujui.', + ], ], 'visitor' => [ 'empty_state' => [