Tutorial Android #8: Dashboard Petugas – Fitur Pencarian dan Lihat Mutasi Nasabah

Tutorial Android #8: Dashboard Petugas – Fitur Pencarian dan Lihat Mutasi Nasabah

Halo para detektif keuangan! Setelah bisa melakukan transaksi, sekarang petugas juga perlu melihat mutasi (riwayat transaksi) nasabah. Kita akan membuat fitur pencarian berdasarkan ID rekening, menampilkan detail rekening, dan daftar mutasi dalam RecyclerView. Siap jadi detektif? 

😂 Joke petugas: "Kenapa petugas perlu lihat mutasi? Biar tahu nasabah rajin nabung atau suka jajan!" 🍭

Apa yang akan kita lakukan?

  • ✅ Membuat activity baru: CariMutasiActivity.
  • ✅ Desain layout dengan form input ID rekening, tombol cari, dan area hasil.
  • ✅ Menampilkan info rekening (nama, nomor rekening, saldo) dalam CardView.
  • ✅ Menampilkan daftar mutasi dalam RecyclerView (bisa pakai adapter yang sama dari tutorial #4).
  • ✅ Menambahkan endpoint di ApiService untuk mengambil mutasi berdasarkan account_id.
  • ✅ Memanggil API dengan Retrofit, menampilkan loading, dan menangani error.
  • ✅ Menambahkan menu/button di PetugasDashboard untuk menuju ke activity ini.
  • ✅ Menangani kasus data tidak ditemukan.

Langkah 1: Membuat Activity dan Layout

Buat activity baru: CariMutasiActivity.java. Layoutnya activity_cari_mutasi.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#f5f5f5">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#0288d1"
        app:title="Cari Mutasi Nasabah"
        app:titleTextColor="#fff"
        app:navigationIcon="?attr/homeAsUpIndicator"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <!-- Form Pencarian -->
            <androidx.cardview.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:cardCornerRadius="12dp"
                app:cardElevation="4dp"
                android:layout_marginBottom="16dp">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="20dp">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Cari Berdasarkan ID Rekening"
                        android:textSize="16sp"
                        android:textStyle="bold"
                        android:layout_marginBottom="16dp"/>

                    <com.google.android.material.textfield.TextInputLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginBottom="16dp"
                        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">

                        <com.google.android.material.textfield.TextInputEditText
                            android:id="@+id/etAccountId"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="Masukkan ID Rekening"
                            android:inputType="number"/>
                    </com.google.android.material.textfield.TextInputLayout>

                    <Button
                        android:id="@+id/btnSearch"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Cari"
                        android:textAllCaps="false"
                        android:backgroundTint="#0288d1"/>
                </LinearLayout>
            </androidx.cardview.widget.CardView>

            <!-- ProgressBar -->
            <ProgressBar
                android:id="@+id/progressBar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"/>

            <!-- Info Rekening (akan muncul setelah pencarian) -->
            <androidx.cardview.widget.CardView
                android:id="@+id/cardInfo"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:cardCornerRadius="12dp"
                app:cardElevation="4dp"
                android:layout_marginBottom="16dp"
                android:visibility="gone">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:padding="20dp">

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Informasi Rekening"
                        android:textSize="16sp"
                        android:textStyle="bold"
                        android:layout_marginBottom="12dp"/>

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Nama Nasabah:"
                        android:textColor="#666"/>
                    <TextView
                        android:id="@+id/tvNama"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="-"
                        android:textSize="16sp"
                        android:layout_marginBottom="8dp"/>

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="No. Rekening:"
                        android:textColor="#666"/>
                    <TextView
                        android:id="@+id/tvNoRekening"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="-"
                        android:textSize="16sp"
                        android:layout_marginBottom="8dp"/>

                    <TextView
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="Saldo:"
                        android:textColor="#666"/>
                    <TextView
                        android:id="@+id/tvSaldo"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="-"
                        android:textSize="16sp"
                        android:textColor="#2e7d32"/>
                </LinearLayout>
            </androidx.cardview.widget.CardView>

            <!-- Daftar Mutasi -->
            <TextView
                android:id="@+id/tvMutasiLabel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Riwayat Transaksi"
                android:textSize="16sp"
                android:textStyle="bold"
                android:layout_marginBottom="8dp"
                android:visibility="gone"/>

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerViewMutasi"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:nestedScrollingEnabled="false"
                android:visibility="gone"/>

            <TextView
                android:id="@+id/tvEmpty"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Tidak ada data mutasi"
                android:textAlignment="center"
                android:padding="32dp"
                android:visibility="gone"/>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

Langkah 2: Model untuk Response Mutasi Petugas

Kita akan gunakan model yang mirip dengan tutorial #4. Buat class MutasiPetugasResponse.java (atau gunakan MutasiResponse yang sudah ada). Kita asumsikan response dari endpoint /api/mutasi/{account_id} adalah:

{
    "nasabah": "Budi",
    "no_rekening": "REK123",
    "saldo": 500000,
    "mutasi": [
        {
            "tanggal": "01-03-2025 10:30",
            "jenis": "Setor",
            "jumlah": 50000,
            "keterangan": "Setor tunai",
            "petugas": "Andi"
        }
    ]
}

Buat class:

package com.example.banktabunganapp;

import com.google.gson.annotations.SerializedName;
import java.util.List;

public class MutasiPetugasResponse {
    @SerializedName("nasabah")
    private String nasabah;

    @SerializedName("no_rekening")
    private String noRekening;

    @SerializedName("saldo")
    private double saldo;

    @SerializedName("mutasi")
    private List<TransaksiItem> mutasi;

    public static class TransaksiItem {
        @SerializedName("tanggal")
        private String tanggal;

        @SerializedName("jenis")
        private String jenis;

        @SerializedName("jumlah")
        private double jumlah;

        @SerializedName("keterangan")
        private String keterangan;

        @SerializedName("petugas")
        private String petugas;

        // getters
        public String getTanggal() { return tanggal; }
        public String getJenis() { return jenis; }
        public double getJumlah() { return jumlah; }
        public String getKeterangan() { return keterangan; }
        public String getPetugas() { return petugas; }
    }

    // getters
    public String getNasabah() { return nasabah; }
    public String getNoRekening() { return noRekening; }
    public double getSaldo() { return saldo; }
    public List<TransaksiItem> getMutasi() { return mutasi; }
}

Langkah 3: Menambahkan Endpoint di ApiService

Buka ApiService.java dan tambahkan method:

@GET("mutasi/{account_id}")
Call<MutasiPetugasResponse> getMutasiByAccountId(
    @Header("Authorization") String token,
    @Path("account_id") int accountId
);

Langkah 4: Membuat Adapter untuk Mutasi (jika belum ada)

Kita bisa gunakan adapter yang sama dengan tutorial #4, yaitu TransaksiAdapter. Pastikan adapter tersebut bisa menerima list MutasiPetugasResponse.TransaksiItem. Jika belum, sesuaikan.

Contoh adapter (salin dari tutorial #4):

public class TransaksiAdapter extends RecyclerView.Adapter<TransaksiAdapter.ViewHolder> {
    private List<MutasiPetugasResponse.TransaksiItem> transaksiList;

    public TransaksiAdapter(List<MutasiPetugasResponse.TransaksiItem> transaksiList) {
        this.transaksiList = transaksiList;
    }

    // ... onCreateViewHolder, onBindViewHolder, dll (sama seperti tutorial #4)
}

Langkah 5: Implementasi CariMutasiActivity

Buka CariMutasiActivity.java:

package com.example.banktabunganapp;

import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textfield.TextInputEditText;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Locale;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class CariMutasiActivity extends AppCompatActivity {
    private TextInputEditText etAccountId;
    private Button btnSearch;
    private ProgressBar progressBar;
    private CardView cardInfo;
    private TextView tvNama, tvNoRekening, tvSaldo;
    private RecyclerView recyclerView;
    private TransaksiAdapter adapter;
    private TextView tvMutasiLabel, tvEmpty;
    private SessionManager sessionManager;
    private ApiService apiService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_cari_mutasi);

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        sessionManager = new SessionManager(this);
        apiService = ApiClient.getService();

        etAccountId = findViewById(R.id.etAccountId);
        btnSearch = findViewById(R.id.btnSearch);
        progressBar = findViewById(R.id.progressBar);
        cardInfo = findViewById(R.id.cardInfo);
        tvNama = findViewById(R.id.tvNama);
        tvNoRekening = findViewById(R.id.tvNoRekening);
        tvSaldo = findViewById(R.id.tvSaldo);
        recyclerView = findViewById(R.id.recyclerViewMutasi);
        tvMutasiLabel = findViewById(R.id.tvMutasiLabel);
        tvEmpty = findViewById(R.id.tvEmpty);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new TransaksiAdapter(new ArrayList<>());
        recyclerView.setAdapter(adapter);

        btnSearch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                searchMutasi();
            }
        });
    }

    private void searchMutasi() {
        String accountIdStr = etAccountId.getText().toString().trim();
        if (TextUtils.isEmpty(accountIdStr)) {
            etAccountId.setError("ID Rekening harus diisi");
            return;
        }
        int accountId = Integer.parseInt(accountIdStr);

        // Sembunyikan hasil sebelumnya
        cardInfo.setVisibility(View.GONE);
        recyclerView.setVisibility(View.GONE);
        tvMutasiLabel.setVisibility(View.GONE);
        tvEmpty.setVisibility(View.GONE);
        progressBar.setVisibility(View.VISIBLE);
        btnSearch.setEnabled(false);

        String token = sessionManager.getToken();
        String authHeader = "Bearer " + token;

        Call call = apiService.getMutasiByAccountId(authHeader, accountId);
        call.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                progressBar.setVisibility(View.GONE);
                btnSearch.setEnabled(true);
                if (response.isSuccessful()) {
                    MutasiPetugasResponse data = response.body();
                    // Tampilkan info rekening
                    cardInfo.setVisibility(View.VISIBLE);
                    tvNama.setText(data.getNasabah());
                    tvNoRekening.setText(data.getNoRekening());
                    NumberFormat formatRupiah = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
                    tvSaldo.setText(formatRupiah.format(data.getSaldo()));

                    // Tampilkan mutasi
                    List mutasi = data.getMutasi();
                    if (mutasi != null && !mutasi.isEmpty()) {
                        adapter = new TransaksiAdapter(mutasi);
                        recyclerView.setAdapter(adapter);
                        recyclerView.setVisibility(View.VISIBLE);
                        tvMutasiLabel.setVisibility(View.VISIBLE);
                        tvEmpty.setVisibility(View.GONE);
                    } else {
                        recyclerView.setVisibility(View.GONE);
                        tvMutasiLabel.setVisibility(View.VISIBLE);
                        tvEmpty.setVisibility(View.VISIBLE);
                    }
                } else {
                    if (response.code() == 401) {
                        sessionManager.logout();
                        startActivity(new Intent(CariMutasiActivity.this, LoginActivity.class));
                        finish();
                    } else if (response.code() == 404) {
                        showError("Rekening tidak ditemukan");
                    } else {
                        showError("Gagal mengambil data");
                    }
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                progressBar.setVisibility(View.GONE);
                btnSearch.setEnabled(true);
                showError("Network error: " + t.getMessage());
            }
        });
    }

    private void showError(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
        cardInfo.setVisibility(View.GONE);
        recyclerView.setVisibility(View.GONE);
        tvMutasiLabel.setVisibility(View.GONE);
        tvEmpty.setVisibility(View.GONE);
    }

    @Override
    public boolean onSupportNavigateUp() {
        finish();
        return true;
    }
}

Langkah 6: Menambahkan Tombol di PetugasDashboard

Buka PetugasDashboardActivity.java (dari tutorial #7). Tambahkan tombol atau menu item untuk membuka CariMutasiActivity. Misal kita tambahkan di menu (bersama Profil dan Logout) atau di layout. Kita akan tambahkan menu item baru.

Update menu_dashboard.xml menjadi:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_cari_mutasi"
        android:title="Cari Mutasi"
        app:showAsAction="never"/>
    <item
        android:id="@+id/action_profile"
        android:title="Profil"
        app:showAsAction="never"/>
    <item
        android:id="@+id/action_logout"
        android:title="Logout"
        app:showAsAction="never"/>
</menu>

Di PetugasDashboardActivity, override onOptionsItemSelected:

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    if (item.getItemId() == R.id.action_cari_mutasi) {
        startActivity(new Intent(this, CariMutasiActivity.class));
        return true;
    } else if (item.getItemId() == R.id.action_profile) {
        startActivity(new Intent(this, ProfileActivity.class));
        return true;
    } else if (item.getItemId() == R.id.action_logout) {
        showLogoutConfirmation();
        return true;
    }
    return super.onOptionsItemSelected(item);
}

Langkah 7: Uji Coba

Login sebagai petugas. Buka menu "Cari Mutasi". Masukkan ID rekening yang valid (misal 1). Klik Cari. Jika berhasil, muncul info rekening dan daftar mutasi. Coba masukkan ID yang tidak valid, muncul pesan error.

💡 Catatan: Pastikan di backend endpoint /api/mutasi/{account_id} sudah dibuat dan hanya bisa diakses oleh petugas (middleware auth:sanctum dan isPetugas). Lihat tutorial Laravel #16.

Kesimpulan

  • ✅ Fitur pencarian mutasi nasabah berhasil dibuat.
  • ✅ Info rekening dan daftar transaksi ditampilkan dengan rapi.
  • ✅ Validasi dan error handling diterapkan.
  • ✅ Terintegrasi dengan API Laravel.

Di tutorial selanjutnya (#9: Dashboard Admin – Manajemen Pengguna) kita akan membuat fitur CRUD untuk admin. Sampai jumpa!


Daftar Tutorial Android (Lanjutan)

😆 "Dengan fitur ini, petugas bisa jadi detektif keuangan: melacak kemana uang nasabah pergi!" 🕵️‍♀️
Lebih baru Lebih lama

نموذج الاتصال