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 "
-
- ";
- }
-
- 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(
- "",
- 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(
- "",
- 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 = "
+ ";
+
+ 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 "
+ ";
+ }
+
+ private static function header(string $type, string $title, string $desc, string $badge): string
+ {
+ $icon = self::icon($type);
+
+ return "
+ ";
+ }
+
+ 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' => [