Tutorial Android #7: Dashboard Petugas – Form Transaksi Setor dan Tarik Tunai

Tutorial Android #7: Dashboard Petugas – Form Transaksi Setor dan Tarik Tunai

Halo para teller bank! Sekarang kita akan membuat dashboard petugas yang sesungguhnya. Di sini petugas bisa melayani setor dan tarik tunai nasabah. Kita akan buat form input yang keren, validasi jumlah, dan mengirim data ke API Laravel yang sudah kita bangun. Siap jadi teller digital?

😂 Joke teller: "Kenapa petugas bank suka senyum? Karena setiap transaksi, saldo nasabah bisa bertambah atau berkurang, tapi senyum petugas tetap bertambah!" 😁

🎯 Apa yang akan kita lakukan?

  • ✅ Membuat PetugasDashboardActivity dengan dua tab atau dua card untuk setor dan tarik.
  • ✅ Mendesain layout dengan EditText untuk ID rekening, jumlah, keterangan, dan button.
  • ✅ Menambahkan validasi input (tidak kosong, jumlah minimal 1000).
  • ✅ Menambahkan endpoint di ApiService untuk deposit dan withdraw.
  • ✅ Mengirim data ke API menggunakan Retrofit dengan menyertakan token.
  • ✅ Menampilkan loading pada button saat memproses.
  • ✅ Menampilkan notifikasi sukses/gagal dengan Toast.
  • ✅ Membersihkan form setelah transaksi berhasil.

Langkah 1: Membuat Activity dan Layout

Buat activity baru: PetugasDashboardActivity.java (jika belum ada dari tutorial #6). Kita akan gunakan layout dengan dua card vertikal atau dua bagian.

Layout activity_petugas_dashboard.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="#28a745"
        app:title="Dashboard Petugas"
        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">

            <!-- Card Setor Tunai -->
            <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="💰 Setor Tunai"
                        android:textSize="18sp"
                        android:textStyle="bold"
                        android:textColor="#28a745"
                        android:layout_marginBottom="16dp"/>

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

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

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

                        <com.google.android.material.textfield.TextInputEditText
                            android:id="@+id/etDepositAmount"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="Jumlah (Rp)"
                            android:inputType="number"/>
                    </com.google.android.material.textfield.TextInputLayout>

                    <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/etDepositDescription"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="Keterangan (opsional)"/>
                    </com.google.android.material.textfield.TextInputLayout>

                    <Button
                        android:id="@+id/btnDeposit"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Setor"
                        android:textAllCaps="false"
                        android:backgroundTint="#28a745"/>
                </LinearLayout>
            </androidx.cardview.widget.CardView>

            <!-- Card Tarik Tunai -->
            <androidx.cardview.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:cardCornerRadius="12dp"
                app:cardElevation="4dp">

                <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="💸 Tarik Tunai"
                        android:textSize="18sp"
                        android:textStyle="bold"
                        android:textColor="#dc3545"
                        android:layout_marginBottom="16dp"/>

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

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

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

                        <com.google.android.material.textfield.TextInputEditText
                            android:id="@+id/etWithdrawAmount"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="Jumlah (Rp)"
                            android:inputType="number"/>
                    </com.google.android.material.textfield.TextInputLayout>

                    <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/etWithdrawDescription"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="Keterangan (opsional)"/>
                    </com.google.android.material.textfield.TextInputLayout>

                    <Button
                        android:id="@+id/btnWithdraw"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:text="Tarik"
                        android:textAllCaps="false"
                        android:backgroundTint="#dc3545"/>
                </LinearLayout>
            </androidx.cardview.widget.CardView>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

Langkah 2: Menambahkan Model untuk Response (BaseResponse)

Kita akan menggunakan BaseResponse yang sudah dibuat di tutorial #5 untuk menampung pesan dari server. Jika belum ada, buat class BaseResponse.java:

package com.example.banktabunganapp;

import com.google.gson.annotations.SerializedName;

public class BaseResponse {
    @SerializedName("message")
    private String message;

    public String getMessage() {
        return message;
    }
}

📞 Langkah 3: Menambahkan Endpoint di ApiService

Buka ApiService.java dan tambahkan method untuk deposit dan withdraw:

// Deposit
@FormUrlEncoded
@POST("deposit")
Call<BaseResponse> deposit(
    @Header("Authorization") String token,
    @Field("account_id") int accountId,
    @Field("amount") double amount,
    @Field("description") String description
);

// Withdraw
@FormUrlEncoded
@POST("withdraw")
Call<BaseResponse> withdraw(
    @Header("Authorization") String token,
    @Field("account_id") int accountId,
    @Field("amount") double amount,
    @Field("description") String description
);

Langkah 4: Implementasi PetugasDashboardActivity

Buka PetugasDashboardActivity.java dan isi dengan kode berikut:

package com.example.banktabunganapp;

import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.google.android.material.textfield.TextInputEditText;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class PetugasDashboardActivity extends AppCompatActivity {
    private TextInputEditText etDepositAccountId, etDepositAmount, etDepositDescription;
    private TextInputEditText etWithdrawAccountId, etWithdrawAmount, etWithdrawDescription;
    private Button btnDeposit, btnWithdraw;
    private SessionManager sessionManager;
    private ApiService apiService;

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

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

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

        // Cek session
        if (!sessionManager.isLoggedIn()) {
            startActivity(new Intent(this, LoginActivity.class));
            finish();
            return;
        }

        // Inisialisasi view
        etDepositAccountId = findViewById(R.id.etDepositAccountId);
        etDepositAmount = findViewById(R.id.etDepositAmount);
        etDepositDescription = findViewById(R.id.etDepositDescription);
        btnDeposit = findViewById(R.id.btnDeposit);

        etWithdrawAccountId = findViewById(R.id.etWithdrawAccountId);
        etWithdrawAmount = findViewById(R.id.etWithdrawAmount);
        etWithdrawDescription = findViewById(R.id.etWithdrawDescription);
        btnWithdraw = findViewById(R.id.btnWithdraw);

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

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

    private void processDeposit() {
        String accountIdStr = etDepositAccountId.getText().toString().trim();
        String amountStr = etDepositAmount.getText().toString().trim();
        String description = etDepositDescription.getText().toString().trim();

        // Validasi
        if (TextUtils.isEmpty(accountIdStr)) {
            etDepositAccountId.setError("ID Rekening harus diisi");
            return;
        }
        if (TextUtils.isEmpty(amountStr)) {
            etDepositAmount.setError("Jumlah harus diisi");
            return;
        }
        int accountId = Integer.parseInt(accountIdStr);
        double amount = Double.parseDouble(amountStr);
        if (amount < 1000) {
            etDepositAmount.setError("Minimal Rp 1.000");
            return;
        }

        // Disable button dan ubah teks
        btnDeposit.setEnabled(false);
        btnDeposit.setText("Memproses...");

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

        Call call = apiService.deposit(authHeader, accountId, amount, description);
        call.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                btnDeposit.setEnabled(true);
                btnDeposit.setText("Setor");
                if (response.isSuccessful()) {
                    Toast.makeText(PetugasDashboardActivity.this, response.body().getMessage(), Toast.LENGTH_SHORT).show();
                    // Kosongkan form
                    etDepositAccountId.setText("");
                    etDepositAmount.setText("");
                    etDepositDescription.setText("");
                } else {
                    if (response.code() == 401) {
                        sessionManager.logout();
                        startActivity(new Intent(PetugasDashboardActivity.this, LoginActivity.class));
                        finish();
                    } else {
                        try {
                            String errorMsg = response.errorBody().string();
                            Toast.makeText(PetugasDashboardActivity.this, "Error: " + errorMsg, Toast.LENGTH_SHORT).show();
                        } catch (Exception e) {
                            Toast.makeText(PetugasDashboardActivity.this, "Transaksi gagal", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                btnDeposit.setEnabled(true);
                btnDeposit.setText("Setor");
                Toast.makeText(PetugasDashboardActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void processWithdraw() {
        String accountIdStr = etWithdrawAccountId.getText().toString().trim();
        String amountStr = etWithdrawAmount.getText().toString().trim();
        String description = etWithdrawDescription.getText().toString().trim();

        if (TextUtils.isEmpty(accountIdStr)) {
            etWithdrawAccountId.setError("ID Rekening harus diisi");
            return;
        }
        if (TextUtils.isEmpty(amountStr)) {
            etWithdrawAmount.setError("Jumlah harus diisi");
            return;
        }
        int accountId = Integer.parseInt(accountIdStr);
        double amount = Double.parseDouble(amountStr);
        if (amount < 1000) {
            etWithdrawAmount.setError("Minimal Rp 1.000");
            return;
        }

        btnWithdraw.setEnabled(false);
        btnWithdraw.setText("Memproses...");

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

        Call call = apiService.withdraw(authHeader, accountId, amount, description);
        call.enqueue(new Callback() {
            @Override
            public void onResponse(Call call, Response response) {
                btnWithdraw.setEnabled(true);
                btnWithdraw.setText("Tarik");
                if (response.isSuccessful()) {
                    Toast.makeText(PetugasDashboardActivity.this, response.body().getMessage(), Toast.LENGTH_SHORT).show();
                    etWithdrawAccountId.setText("");
                    etWithdrawAmount.setText("");
                    etWithdrawDescription.setText("");
                } else {
                    if (response.code() == 401) {
                        sessionManager.logout();
                        startActivity(new Intent(PetugasDashboardActivity.this, LoginActivity.class));
                        finish();
                    } else {
                        try {
                            String errorMsg = response.errorBody().string();
                            Toast.makeText(PetugasDashboardActivity.this, "Error: " + errorMsg, Toast.LENGTH_SHORT).show();
                        } catch (Exception e) {
                            Toast.makeText(PetugasDashboardActivity.this, "Transaksi gagal", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
            }

            @Override
            public void onFailure(Call call, Throwable t) {
                btnWithdraw.setEnabled(true);
                btnWithdraw.setText("Tarik");
                Toast.makeText(PetugasDashboardActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_dashboard, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        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);
    }

    private void showLogoutConfirmation() {
        new AlertDialog.Builder(this)
            .setTitle("Konfirmasi Logout")
            .setMessage("Apakah Anda yakin ingin keluar?")
            .setPositiveButton("Ya", (dialog, which) -> {
                sessionManager.logout();
                startActivity(new Intent(this, LoginActivity.class));
                finish();
            })
            .setNegativeButton("Batal", null)
            .show();
    }

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

Langkah 5: Uji Coba

Pastikan server Laravel berjalan dan endpoint /api/deposit dan /api/withdraw sudah tersedia (tutorial #5). Login sebagai petugas, lalu coba lakukan setor dan tarik dengan data valid. Perhatikan Toast yang muncul.

  • Jika berhasil, form akan kosong dan muncul pesan sukses.
  • Jika jumlah kurang dari 1000, muncul error validasi.
  • Jika rekening tidak ditemukan atau saldo tidak cukup, server akan mengembalikan error dan ditampilkan di Toast.
  • Jika token expired, akan logout otomatis.
💡 Catatan: Pastikan di backend, endpoint deposit dan withdraw menggunakan middleware auth:sanctum dan isPetugas agar hanya petugas yang bisa mengakses. Juga pastikan validasi saldo dll sudah ditangani di server.

Kesimpulan

  • ✅ Dashboard petugas dengan dua form transaksi telah selesai.
  • ✅ Validasi client-side mencegah input salah.
  • ✅ Integrasi Retrofit berjalan lancar dengan pengiriman token.
  • ✅ Notifikasi memberi feedback ke petugas.
  • ✅ Penanganan error dan token expired sudah diterapkan.

Di tutorial selanjutnya (#8: Dashboard Petugas – Lihat Mutasi Nasabah) kita akan menambahkan fitur pencarian mutasi nasabah. Sampai jumpa!


Daftar Tutorial Android (Lanjutan)

😆 "Dengan form ini, petugas bisa setor dan tarik tunai tanpa perlu pegang uang fisik. Canggih kan?" 💳
Lebih baru Lebih lama

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