Skip to content

Perbaikan tampilan logo identitas dan perbaikan opsi undefined pada dropdown kabupaten - #1287

Open
habibie11 wants to merge 1 commit into
rilis-devfrom
dev-1281
Open

habibie11 wants to merge 1 commit into
rilis-devfrom
dev-1281

Conversation

@habibie11

Copy link
Copy Markdown
Contributor

issue # #1281

🎯 Deskripsi

Pull request ini memperbaiki dua permasalahan pada halaman Pengaturan Identitas (/pengaturan/identitas/{id}/edit):

  1. Logo Tidak Tampil Setelah Diunggah (Broken Image Icon):

    • Berkas logo tersimpan di storage/app/public/img, namun jika symlink penyimpanan (public/storage) belum terbentuk di lingkungan lokal/server atau bermasalah, browser gagal memuat berkas dan menghasilkan status 404 Not Found.
    • Selain itu, method IdentitasController::logo() sebelumnya hanya memeriksa public_path('storage/img/'), sehingga route /pengaturan/logo juga gagal menemukan berkas fisik di storage_path dan jatuh kembali ke gambar default.
    • Melalui PR ini, method IdentitasController::logo() diperkuat untuk memeriksa storage_path('app/public/img/...') terlebih dahulu sebelum public_path(...), tag <img> pada antarmuka ditambahkan penanganan fallback gambar menggunakan x-on:error ke opensid_logo.png, serta alur reload setelah unggah logo disinkronkan dengan penutupan pop-up konfirmasi SweetAlert.
  2. Dropdown "Pilih Kab" Menampilkan Nilai undefined (Contoh: ACEH - undefined, BALI - undefined):

    • Pada resources/views/identitas/edit.blade.php, Select2 provinsi dan kabupaten hanya menetapkan opsi awal melalui properti placeholder. Pada komponen Select2, placeholder tidak menghasilkan elemen <option> dan tidak mengubah nilai .val(), sehingga $('select[name=prov]').val() bernilai null.
    • Ketika dropdown kabupaten memanggil endpoint index.php/api/wilayah/list_wilayah tanpa parameter kode, API Pantau mengembalikan daftar Provinsi (kode_prov & nama_prov) alih-alih data kabupaten.
    • Frontend kemudian memetakan teks menggunakan value.nama_prov + ' - ' + value.nama_kab. Karena data yang diterima adalah provinsi, properti nama_kab dan kode_kab bernilai undefined, yang menyebabkan opsi dropdown bertuliskan [NAMA_PROVINSI] - undefined dengan ID undefined.
    • Melalui PR ini, opsi awal dibuat secara dinamis menggunakan new Option(...) dan di-append ke <select> sesuai data identitas yang ada. Parameter kode pada AJAX kabupaten dilengkapi dengan fallback nilai dataIdentitas.kode_provinsi, hasil respons difilter secara ketat hanya untuk item yang memiliki kode_kab dan nama_kab, serta ditambahkan sinkronisasi event pergantian provinsi untuk mereset kabupaten secara otomatis.

🛠️ Perubahan yang Dilakukan

1. resources/views/identitas/edit.blade.php

Fix — Inisialisasi opsi Select2, filter data kabupaten, event listener, dan penanganan fallback logo:

  • Menambahkan atribut x-on:error pada tag <img> logo untuk menangani kasus jika gambar gagal dimuat agar otomatis kembali ke logo default OpenSID tanpa menampilkan broken image.
  • Menginisialisasi elemen <option> terpilih pada selectProv dan selectKab berdasarkan data this.dataIdentitas saat pertama kali dimuat.
  • Menambahkan fallback $('select[name=prov]').val() || _this.dataIdentitas.kode_provinsi pada parameter kode saat melakukan pencarian kabupaten.
  • Menyaring data pada processResults dengan .filter(value => value.kode_kab && value.nama_kab) agar data non-kabupaten tidak diproses menjadi teks undefined.
  • Menambahkan sinkronisasi event select2:select dan select2:clear pada provinsi untuk mereset nilai kabupaten saat provinsi diubah atau dihapus.
  • Memperbaiki penanganan uploadGambar() agar me-reload halaman setelah modal SweetAlert selesai ditutup (.then(...)), konsisten dengan fungsi upload favicon.
@@ -22,6 +22,7 @@
                 <div class="widget-user-header text-center  p-4">
                     <img :src="dataIdentitas.logo ? '{{ asset('storage/img') }}/' + dataIdentitas.logo :
                         '{{ asset('assets/img/opensid_logo.png') }}'"
+                        x-on:error="$event.target.src = '{{ asset('assets/img/opensid_logo.png') }}'"
                         alt="Logo" width="150px">
                     <h5 class="mt-3" x-text="'Logo ' + dataIdentitas.nama_aplikasi">Logo {{ config('app.namaAplikasi') }}
                     </h5>
@@ -154,16 +155,18 @@
                                 };
                             },
                             processResults: function(data) {
-                                let results = data.results;
-                                return {
-                                    results: results.map(value => {
-                                        return {
-                                            'id': value.kode_prov,
-                                            'text': value.nama_prov,
-                                            'kode_prov': value.kode_prov,
-                                            'nama_prov': value.nama_prov
-                                        }
-                                    }),
+                                let results = data.results || [];
+                                return {
+                                    results: results
+                                        .filter(value => value.kode_prov && value.nama_prov)
+                                        .map(value => {
+                                            return {
+                                                'id': value.kode_prov,
+                                                'text': value.nama_prov,
+                                                'kode_prov': value.kode_prov,
+                                                'nama_prov': value.nama_prov
+                                            }
+                                        }),
                                     pagination: data.pagination,
                                 }
                             },
@@ -177,9 +180,25 @@
                         },
                     });
 
+                    if (this.dataIdentitas.kode_provinsi && this.dataIdentitas.nama_provinsi) {
+                        let optionProv = new Option(this.dataIdentitas.nama_provinsi, this.dataIdentitas.kode_provinsi, true, true);
+                        $(this.$refs.selectProv).append(optionProv).trigger('change');
+                    }
+
                     this.select2Prov.on("select2:select", (event) => {
                         this.dataIdentitas.kode_provinsi = event.params.data.kode_prov;
                         this.dataIdentitas.nama_provinsi = event.params.data.nama_prov;
+                        this.dataIdentitas.kode_kabupaten = null;
+                        this.dataIdentitas.nama_kabupaten = null;
+                        $(this.$refs.select).val(null).trigger('change');
+                    });
+
+                    this.select2Prov.on("select2:clear", () => {
+                        this.dataIdentitas.kode_provinsi = null;
+                        this.dataIdentitas.nama_provinsi = null;
+                        this.dataIdentitas.kode_kabupaten = null;
+                        this.dataIdentitas.nama_kabupaten = null;
+                        $(this.$refs.select).val(null).trigger('change');
                     });
                 },
 
@@ -186,3 +205,4 @@
+                    const _this = this;
                     this.select2 = $(this.$refs.select).select2({
                         ajax: {
                             url: function() {
@@ -193,23 +213,25 @@
                             data: function(params) {
                                 return {
                                     cari: params.term || '',
-                                    kode: $('select[name=prov]').val(),
+                                    kode: $('select[name=prov]').val() || _this.dataIdentitas.kode_provinsi,
                                     page: params.page || 1,
                                 };
                             },
                             processResults: function(data) {
-                                let results = data.results;
+                                let results = data.results || [];
                                 return {
-                                    results: results.map(value => {
-                                        return {
-                                            'id': value.kode_kab,
-                                            'text': value.nama_prov + ' - ' + value.nama_kab,
-                                            'kode_kab': value.kode_kab,
-                                            'kode_prov': value.kode_prov,
-                                            'nama_kab': value.nama_kab,
-                                            'nama_prov': value.nama_prov
-                                        }
-                                    }),
+                                    results: results
+                                        .filter(value => value.kode_kab && value.nama_kab)
+                                        .map(value => {
+                                            return {
+                                                'id': value.kode_kab,
+                                                'text': (value.nama_prov ? value.nama_prov + ' - ' : '') + value.nama_kab,
+                                                'kode_kab': value.kode_kab,
+                                                'kode_prov': value.kode_prov,
+                                                'nama_kab': value.nama_kab,
+                                                'nama_prov': value.nama_prov
+                                            }
+                                        }),
                                     pagination: data.pagination,
                                 }
                             },
@@ -223,6 +245,12 @@
                         },
                     });
 
+                    if (this.dataIdentitas.kode_kabupaten && this.dataIdentitas.nama_kabupaten) {
+                        let kabText = (this.dataIdentitas.nama_provinsi ? this.dataIdentitas.nama_provinsi + ' - ' : '') + this.dataIdentitas.nama_kabupaten;
+                        let optionKab = new Option(kabText, this.dataIdentitas.kode_kabupaten, true, true);
+                        $(this.$refs.select).append(optionKab).trigger('change');
+                    }
+
                     this.select2.on("select2:select", (event) => {
                         this.dataIdentitas.kode_kabupaten = event.params.data.kode_kab;
                         this.dataIdentitas.kode_provinsi = event.params.data.kode_prov;
@@ -229,5 +257,10 @@
                         this.dataIdentitas.nama_provinsi = event.params.data.nama_prov;
                     });
+
+                    this.select2.on("select2:clear", () => {
+                        this.dataIdentitas.kode_kabupaten = null;
+                        this.dataIdentitas.nama_kabupaten = null;
+                    });
                 },
 
                 uploadGambar() {
@@ -262,15 +295,15 @@
                                 contentType: false, // tell jQuery not to set contentType
                                 success: function(response) {
                                     if (response.success == true) {
-                                        this.data.logo = response.data
-                                        Swal.fire({
-                                            title: 'Simpan!',
-                                            text: 'Data berhasil tersimpan',
-                                            icon: 'success',
-                                            showConfirmButton: false,
-                                            timer: 1500,
-                                        })
-                                        location.reload();
+                                        Swal.fire({
+                                            title: 'Simpan!',
+                                            text: 'Data berhasil tersimpan',
+                                            icon: 'success',
+                                            showConfirmButton: true,
+                                            timer: 1500,
+                                        }).then(function() {
+                                            window.location.reload();
+                                        });

2. app/Http/Controllers/IdentitasController.php

Fix — Prioritaskan pencarian berkas logo pada storage_path dan sediakan fallback default:

  • Memeriksa ketersediaan berkas di storage_path('app/public/img/'.$path->logo) terlebih dahulu.
  • Jika tidak ada, memeriksa public_path('storage/img/'.$path->logo).
  • Jika logo tidak ada atau berkas fisik tidak ditemukan di kedua lokasi, fallback ke public_path('assets/img/opensid_logo.png').
@@ -28,9 +28,18 @@ public function edit()
     public function logo()
     {
         $path = Identitas::first();
-        if (is_null($path) || is_null($path->logo)) {
-            $path->logo = 'opensid_logo.png';
+        if ($path && $path->logo) {
+            $storagePath = storage_path('app/public/img/'.$path->logo);
+            if (file_exists($storagePath)) {
+                return ambilBerkas($storagePath);
+            }
+
+            $publicPath = public_path('storage/img/'.$path->logo);
+            if (file_exists($publicPath)) {
+                return ambilBerkas($publicPath);
+            }
         }
-        ambilBerkas(public_path('storage/img/').$path->logo);
+
+        return ambilBerkas(public_path('assets/img/opensid_logo.png'));
     }
 }

3. tests/Feature/IdentitasControllerApiTest.php

Test — Penambahan pengujian route /pengaturan/logo:

  • Menambahkan pengujian test_get_logo() untuk memverifikasi ketersediaan endpoint penyajian logo.
  • Menambahkan pengujian test_get_logo_fallback_when_null() untuk memverifikasi fallback logo default saat kolom logo kosong.
@@ -95,4 +95,27 @@ public function test_post_upload_favicon()
         $this->assertTrue(File::exists('public/favicons/favicon-96x96.png'));
         $this->assertDatabaseHas('identitas', $dataLogo);
     }
+
+    public function test_get_logo()
+    {
+        ob_start();
+        $response = $this->get('/pengaturan/logo');
+        ob_end_clean();
+
+        $response->assertStatus(Response::HTTP_OK);
+    }
+
+    public function test_get_logo_fallback_when_null()
+    {
+        $identitas = \App\Models\Identitas::first();
+        if ($identitas) {
+            $identitas->update(['logo' => null]);
+        }
+
+        ob_start();
+        $response = $this->get('/pengaturan/logo');
+        ob_end_clean();
+
+        $response->assertStatus(Response::HTTP_OK);
+    }
 }

✅ Test Cases yang Diimplementasikan

  • Berkas logo yang diunggah dapat diakses melalui URL storage publik dengan status 200 OK.
  • Endpoint /pengaturan/logo menyajikan berkas logo dari penyimpanan fisik secara tepat.
  • Endpoint /pengaturan/logo tetap aman dan menyajikan berkas fallback default opensid_logo.png jika identitas belum memiliki logo.
  • Opsi Select2 "Pilih Provinsi" otomatis terisi dengan nama dan kode provinsi dari data identitas yang sedang aktif.
  • Opsi Select2 "Pilih Kab" otomatis terisi dengan nama dan kode kabupaten dari data identitas yang sedang aktif.
  • Saat membuka dan mencari pada dropdown "Pilih Kab", seluruh opsi yang ditampilkan adalah kabupaten/kota yang valid dalam provinsi terpilih dan tidak ada teks undefined.
  • Saat provinsi diubah atau dihapus, nilai pilihan kabupaten pada dropdown otomatis ter-reset.
  • Tag gambar logo memiliki atribut x-on:error sehingga antarmuka tidak menampilkan ikon broken image jika berkas logo gagal dimuat.
  • Seluruh pengujian fitur di IdentitasControllerApiTest berhasil dijalankan dan lulus 100%.

📸 Cara Menjalankan Uji Coba Manual

  1. Pastikan symlink penyimpanan sudah terhubung dengan menjalankan php artisan storage:link.
  2. Masuk ke aplikasi dan navigasikan ke menu Pengaturan > Identitas (/pengaturan/identitas).
  3. Klik tombol Ubah Identitas untuk menuju ke halaman edit (/pengaturan/identitas/1/edit).
  4. Verifikasi Tampilan Logo:
    • Periksa kartu Logo Aplikasi di sebelah kiri. Logo identitas aplikasi harus tampil dengan jelas (bukan ikon gambar rusak/broken image).
    • Klik tombol Ganti Logo, pilih berkas gambar baru, lalu simpan. Tunggu notifikasi SweetAlert selesai hingga halaman otomatis memuat ulang dan menampilkan logo yang baru diperbarui.
  5. Verifikasi Dropdown Wilayah:
    • Periksa field Pilih Provinsi: opsi default terisi nama provinsi aktif (contoh: BALI).
    • Periksa field Pilih Kab: opsi default terisi nama kabupaten aktif (contoh: BALI - TABANAN).
    • Klik dropdown Pilih Kab: amati daftar opsi yang muncul.
      • Seharusnya: Muncul daftar kabupaten/kota di provinsi terkait (contoh: BALI - BADUNG, BALI - BANGLI, BALI - BULELENG, dst.) tanpa ada teks undefined.
    • Coba ubah provinsi ke provinsi lain (misal: JAWA TIMUR):
      • Seharusnya: Nilai kabupaten otomatis ter-reset kosong. Saat membuka dropdown kabupaten kembali, opsi yang muncul adalah daftar kabupaten di provinsi baru tersebut.

🤖 Cara Menjalankan Uji Coba Otomatis (Automated Test)

Jalankan perintah pengujian fitur melalui PHPUnit:

./vendor/bin/phpunit tests/Feature/IdentitasControllerApiTest.php

📸 Screenshot atau Video

Screenshot 2026-09-08 150513 (sebelum)

https://github.com/user-attachments/assets/09a92a6e-50e9-4f95-8e89-ca27700c8985
(sesudah)

⚠️ Catatan Penting

  • Pastikan perintah php artisan storage:link telah dijalankan pada setiap lingkungan deployment (staging/production) agar berkas publik yang diunggah ke storage/app/public dapat diakses langsung oleh web server.
  • Perubahan penanganan event error pada gambar menggunakan x-on:error (sintaks resmi Alpine.js) menggantikan @error untuk mencegah konflik parsing dengan direktif bawaan engine Blade Laravel.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🔄 AI PR Review sedang antri di server...

Proses review akan segera dimulai di background — hasil akan muncul sebagai komentar setelah selesai.
Powered by CrewAI · PR #1287

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant