Tutorial Android #10: Dashboard Admin – Laporan Transaksi dengan Filter dan Ekspor PDF

Tutorial Android #10: Dashboard Admin – Laporan Transaksi dengan Filter Tanggal dan Ekspor PDF

Halo para admin super! Ini adalah tutorial terakhir dalam seri Android kita. Kali ini kita akan membuat halaman laporan transaksi yang canggih. Admin bisa memilih rentang tanggal, melihat daftar transaksi, dan mengekspor ke file PDF. Siap jadi analis data?

😂 Joke admin: "Kenapa laporan transaksi harus difilter? Biar nggak pusing lihat data segunung!" 🏔️

Apa yang akan kita lakukan?

  • ✅ Membuat activity baru: AdminLaporanActivity.
  • ✅ Desain layout dengan dua DatePicker (dari tanggal, sampai tanggal), tombol filter, dan RecyclerView untuk menampilkan transaksi.
  • ✅ Menambahkan tombol untuk ekspor PDF.
  • ✅ Menambahkan endpoint di ApiService untuk mengambil transaksi dengan filter tanggal.
  • ✅ Menampilkan data transaksi di RecyclerView dengan adapter.
  • ✅ Mengimplementasikan export PDF menggunakan iText 7 (library open source).
  • ✅ Menyimpan PDF ke storage dan membagikannya atau membukanya dengan PDF viewer.
  • ✅ Menambahkan menu di AdminDashboard untuk membuka halaman laporan.
  • ✅ Menangani izin storage untuk Android 10 ke atas (scoped storage).

Langkah 1: Menambahkan Dependencies untuk iText PDF

Buka build.gradle (Module: app) dan tambahkan dependency iText 7 (versi gratis untuk pembelajaran).

dependencies {
    // ... dependencies yang sudah ada
    implementation 'com.itextpdf:itext7-core:7.2.5'
}

Catatan: iText 7 adalah library yang powerful dan gratis untuk penggunaan open source. Untuk produksi komersial, pastikan membaca lisensinya. Untuk tutorial ini, kita gunakan untuk keperluan edukasi.

Jangan lupa Sync Now setelah menambahkan.

Langkah 2: Membuat Model untuk Transaksi Laporan

Kita akan membuat model sederhana untuk menampung data transaksi yang diterima dari API. Buat class LaporanTransaksi.java:

package com.example.banktabunganapp;

import com.google.gson.annotations.SerializedName;

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

    @SerializedName("nasabah")
    private String nasabah;

    @SerializedName("no_rekening")
    private String noRekening;

    @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 getNasabah() { return nasabah; }
    public String getNoRekening() { return noRekening; }
    public String getJenis() { return jenis; }
    public double getJumlah() { return jumlah; }
    public String getKeterangan() { return keterangan; }
    public String getPetugas() { return petugas; }
}

Untuk response API, kita akan menerima List<LaporanTransaksi>.

Langkah 3: Menambahkan Endpoint di ApiService

Buka ApiService.java dan tambahkan method untuk mengambil transaksi dengan filter tanggal:

@GET("admin/transactions")
Call<List<LaporanTransaksi>> getLaporanTransaksi(
    @Header("Authorization") String token,
    @Query("from_date") String fromDate,
    @Query("to_date") String toDate
);

Parameter from_date dan to_date akan dikirim sebagai query string. Pastikan di backend endpoint ini sudah dibuat (lihat tutorial Laravel #19).

Langkah 4: Membuat Layout AdminLaporanActivity

Buat file activity_admin_laporan.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="#e65100"
        app:title="Laporan Transaksi"
        app:titleTextColor="#fff"
        app:navigationIcon="?attr/homeAsUpIndicator"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <!-- Filter Tanggal -->
            <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="Filter Tanggal"
                        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="12dp"
                        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">

                        <com.google.android.material.textfield.TextInputEditText
                            android:id="@+id/etFromDate"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="Dari Tanggal (YYYY-MM-DD)"
                            android:inputType="date"/>
                    </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/etToDate"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="Sampai Tanggal (YYYY-MM-DD)"
                            android:inputType="date"/>
                    </com.google.android.material.textfield.TextInputLayout>

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

            <!-- Tombol Export PDF -->
            <Button
                android:id="@+id/btnExportPdf"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="📄 Export ke PDF"
                android:textAllCaps="false"
                android:backgroundTint="#d32f2f"
                android:layout_marginBottom="16dp"
                android:visibility="gone"/>

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

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

            <androidx.recyclerview.widget.RecyclerView
                android:id="@+id/recyclerViewLaporan"
                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="Belum ada data. Pilih tanggal dan filter."
                android:textAlignment="center"
                android:padding="32dp"
                android:visibility="gone"/>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

Langkah 5: Membuat Adapter untuk RecyclerView

Buat LaporanAdapter.java untuk menampilkan item transaksi di RecyclerView:

package com.example.banktabunganapp;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.text.NumberFormat;
import java.util.List;
import java.util.Locale;

public class LaporanAdapter extends RecyclerView.Adapter<LaporanAdapter.ViewHolder> {
    private List<LaporanTransaksi> list;

    public LaporanAdapter(List<LaporanTransaksi> list) {
        this.list = list;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_laporan, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        LaporanTransaksi item = list.get(position);
        holder.tvTanggal.setText(item.getTanggal());
        holder.tvNasabah.setText(item.getNasabah());
        holder.tvNoRekening.setText(item.getNoRekening());
        holder.tvJenis.setText(item.getJenis());
        NumberFormat format = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
        holder.tvJumlah.setText(format.format(item.getJumlah()));
        holder.tvKeterangan.setText(item.getKeterangan());
        holder.tvPetugas.setText(item.getPetugas());

        if ("Setor".equalsIgnoreCase(item.getJenis())) {
            holder.tvJenis.setTextColor(holder.itemView.getContext().getColor(android.R.color.holo_green_dark));
            holder.tvJumlah.setTextColor(holder.itemView.getContext().getColor(android.R.color.holo_green_dark));
        } else {
            holder.tvJenis.setTextColor(holder.itemView.getContext().getColor(android.R.color.holo_red_dark));
            holder.tvJumlah.setTextColor(holder.itemView.getContext().getColor(android.R.color.holo_red_dark));
        }
    }

    @Override
    public int getItemCount() {
        return list.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvTanggal, tvNasabah, tvNoRekening, tvJenis, tvJumlah, tvKeterangan, tvPetugas;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvTanggal = itemView.findViewById(R.id.tvTanggal);
            tvNasabah = itemView.findViewById(R.id.tvNasabah);
            tvNoRekening = itemView.findViewById(R.id.tvNoRekening);
            tvJenis = itemView.findViewById(R.id.tvJenis);
            tvJumlah = itemView.findViewById(R.id.tvJumlah);
            tvKeterangan = itemView.findViewById(R.id.tvKeterangan);
            tvPetugas = itemView.findViewById(R.id.tvPetugas);
        }
    }
}

Buat layout item item_laporan.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp"
    android:background="?android:attr/selectableItemBackground">

    <TextView
        android:id="@+id/tvJenis"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textSize="16sp"/>

    <TextView
        android:id="@+id/tvJumlah"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:layout_marginTop="4dp"/>

    <TextView
        android:id="@+id/tvTanggal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:textColor="#666"/>

    <TextView
        android:id="@+id/tvNasabah"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="14sp"/>

    <TextView
        android:id="@+id/tvNoRekening"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:textColor="#888"/>

    <TextView
        android:id="@+id/tvKeterangan"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:textColor="#888"/>

    <TextView
        android:id="@+id/tvPetugas"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        android:textColor="#888"/>
</LinearLayout>

Langkah 6: Implementasi AdminLaporanActivity

Buat AdminLaporanActivity.java dengan logika filter dan ekspor PDF:

package com.example.banktabunganapp;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textfield.TextInputEditText;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.UnitValue;
import java.io.File;
import java.io.FileOutputStream;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class AdminLaporanActivity extends AppCompatActivity {
    private TextInputEditText etFromDate, etToDate;
    private Button btnFilter, btnExportPdf;
    private ProgressBar progressBar;
    private RecyclerView recyclerView;
    private LaporanAdapter adapter;
    private TextView tvTitle, tvEmpty;
    private List<LaporanTransaksi> transaksiList = new ArrayList<>();
    private SessionManager sessionManager;
    private ApiService apiService;
    private static final int REQUEST_WRITE_STORAGE = 1;

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

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

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

        etFromDate = findViewById(R.id.etFromDate);
        etToDate = findViewById(R.id.etToDate);
        btnFilter = findViewById(R.id.btnFilter);
        btnExportPdf = findViewById(R.id.btnExportPdf);
        progressBar = findViewById(R.id.progressBar);
        recyclerView = findViewById(R.id.recyclerViewLaporan);
        tvTitle = findViewById(R.id.tvTitle);
        tvEmpty = findViewById(R.id.tvEmpty);

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new LaporanAdapter(transaksiList);
        recyclerView.setAdapter(adapter);

        btnFilter.setOnClickListener(v -> filterData());
        btnExportPdf.setOnClickListener(v -> exportToPdf());
    }

    private void filterData() {
        String fromDate = etFromDate.getText().toString().trim();
        String toDate = etToDate.getText().toString().trim();

        if (TextUtils.isEmpty(fromDate) || TextUtils.isEmpty(toDate)) {
            Toast.makeText(this, "Isi kedua tanggal", Toast.LENGTH_SHORT).show();
            return;
        }

        progressBar.setVisibility(View.VISIBLE);
        btnFilter.setEnabled(false);
        btnExportPdf.setVisibility(View.GONE);
        tvTitle.setVisibility(View.GONE);
        recyclerView.setVisibility(View.GONE);
        tvEmpty.setVisibility(View.GONE);

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

        Call> call = apiService.getLaporanTransaksi(authHeader, fromDate, toDate);
        call.enqueue(new Callback>() {
            @Override
            public void onResponse(Call> call, Response> response) {
                progressBar.setVisibility(View.GONE);
                btnFilter.setEnabled(true);
                if (response.isSuccessful() && response.body() != null) {
                    transaksiList.clear();
                    transaksiList.addAll(response.body());
                    adapter.notifyDataSetChanged();
                    if (transaksiList.isEmpty()) {
                        tvEmpty.setVisibility(View.VISIBLE);
                        tvTitle.setVisibility(View.GONE);
                        recyclerView.setVisibility(View.GONE);
                        btnExportPdf.setVisibility(View.GONE);
                    } else {
                        tvEmpty.setVisibility(View.GONE);
                        tvTitle.setVisibility(View.VISIBLE);
                        recyclerView.setVisibility(View.VISIBLE);
                        btnExportPdf.setVisibility(View.VISIBLE);
                    }
                } else {
                    if (response.code() == 401) {
                        sessionManager.logout();
                        startActivity(new Intent(AdminLaporanActivity.this, LoginActivity.class));
                        finish();
                    } else {
                        Toast.makeText(AdminLaporanActivity.this, "Gagal memuat data", Toast.LENGTH_SHORT).show();
                    }
                }
            }

            @Override
            public void onFailure(Call> call, Throwable t) {
                progressBar.setVisibility(View.GONE);
                btnFilter.setEnabled(true);
                Toast.makeText(AdminLaporanActivity.this, "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void exportToPdf() {
        if (transaksiList.isEmpty()) {
            Toast.makeText(this, "Tidak ada data untuk diekspor", Toast.LENGTH_SHORT).show();
            return;
        }

        // Izin storage untuk Android 10 ke bawah
        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.Q) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_STORAGE);
                return;
            }
        }
        createPdf();
    }

    private void createPdf() {
        try {
            // Nama file dengan timestamp
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault());
            String fileName = "laporan_transaksi_" + sdf.format(new Date()) + ".pdf";
            File pdfFile;
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
                // Scoped storage: simpan di folder Downloads
                pdfFile = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), fileName);
            } else {
                pdfFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), fileName);
            }

            PdfWriter writer = new PdfWriter(new FileOutputStream(pdfFile));
            PdfDocument pdfDoc = new PdfDocument(writer);
            Document document = new Document(pdfDoc);

            // Judul
            document.add(new Paragraph("Laporan Transaksi")
                    .setTextAlignment(TextAlignment.CENTER)
                    .setFontSize(18)
                    .setBold());
            document.add(new Paragraph("Periode: " + etFromDate.getText().toString() + " s/d " + etToDate.getText().toString())
                    .setTextAlignment(TextAlignment.CENTER)
                    .setFontSize(12)
                    .setMarginBottom(20));

            // Tabel dengan 7 kolom
            Table table = new Table(UnitValue.createPercentArray(new float[]{15, 20, 15, 10, 15, 20, 15}));
            table.setWidth(UnitValue.createPercentValue(100));

            // Header
            String[] headers = {"Tanggal", "Nasabah", "No. Rekening", "Jenis", "Jumlah", "Keterangan", "Petugas"};
            for (String header : headers) {
                table.addCell(new Cell().add(new Paragraph(header)).setBold().setTextAlignment(TextAlignment.CENTER));
            }

            // Data
            NumberFormat rupiah = NumberFormat.getCurrencyInstance(new Locale("id", "ID"));
            for (LaporanTransaksi item : transaksiList) {
                table.addCell(new Cell().add(new Paragraph(item.getTanggal())));
                table.addCell(new Cell().add(new Paragraph(item.getNasabah())));
                table.addCell(new Cell().add(new Paragraph(item.getNoRekening())));
                table.addCell(new Cell().add(new Paragraph(item.getJenis())));
                table.addCell(new Cell().add(new Paragraph(rupiah.format(item.getJumlah()))));
                table.addCell(new Cell().add(new Paragraph(item.getKeterangan())));
                table.addCell(new Cell().add(new Paragraph(item.getPetugas())));
            }

            document.add(table);
            document.close();

            // Beri notifikasi dan buka file
            Toast.makeText(this, "PDF tersimpan di: " + pdfFile.getAbsolutePath(), Toast.LENGTH_LONG).show();

            // Buka file dengan intent
            Uri pdfUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", pdfFile);
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.setDataAndType(pdfUri, "application/pdf");
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            startActivity(Intent.createChooser(intent, "Buka PDF"));
        } catch (Exception e) {
            e.printStackTrace();
            Toast.makeText(this, "Gagal membuat PDF: " + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_WRITE_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            createPdf();
        } else {
            Toast.makeText(this, "Izin storage diperlukan untuk menyimpan PDF", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

Catatan: Untuk Android 7.0 ke atas, kita perlu menggunakan FileProvider untuk membagikan file PDF. Tambahkan di AndroidManifest.xml:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

Buat file res/xml/file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="." />
    <external-files-path name="external_files" path="." />
</paths>

Langkah 7: Menambahkan Menu di AdminDashboard

Di AdminDashboardActivity.java, tambahkan item menu untuk menuju ke laporan:

// Di menu_dashboard.xml, tambahkan item
<item
    android:id="@+id/action_report"
    android:title="Laporan Transaksi"
    app:showAsAction="never"/>

// Di onOptionsItemSelected
if (item.getItemId() == R.id.action_report) {
    startActivity(new Intent(this, AdminLaporanActivity.class));
    return true;
}

Langkah 8: Uji Coba

  1. Login sebagai admin.
  2. Buka menu "Laporan Transaksi".
  3. Pilih rentang tanggal (format YYYY-MM-DD).
  4. Klik Filter, tunggu data muncul.
  5. Klik "Export ke PDF". Izin storage akan diminta (jika belum).
  6. File PDF akan tersimpan dan langsung dibuka dengan aplikasi PDF viewer.
💡 Catatan: Pastikan backend endpoint /api/admin/transactions sudah dibuat dan mengembalikan data sesuai format yang diharapkan (lihat tutorial Laravel #19). Jika belum, tambahkan di routes/api.php dengan middleware isAdmin.

Kesimpulan

  • ✅ Admin dapat melihat laporan transaksi dengan filter tanggal.
  • ✅ Data ditampilkan dalam RecyclerView yang rapi.
  • ✅ Ekspor ke PDF menggunakan iText 7 berhasil dengan tabel dan format rupiah.
  • ✅ Integrasi dengan API Laravel selesai.

Selamat! Anda telah menyelesaikan seri tutorial Android untuk aplikasi bank tabungan. Kini Anda memiliki aplikasi lengkap yang dapat dijalankan di perangkat Android dan terhubung ke backend Laravel. Jangan lupa untuk terus mengembangkan dan memperbaiki fitur-fitur yang ada. Sampai jumpa di tutorial selanjutnya! 🎉


Daftar Semua Tutorial Android

  • #1: Persiapan Lingkungan dan Pengenalan Android Studio
  • #2: Mengintegrasikan Retrofit untuk Konsumsi API Laravel
  • #3: Halaman Login dan Registrasi Nasabah
  • #4: Dashboard Nasabah – Menampilkan Saldo dan Mutasi dengan RecyclerView
  • #5: Fitur Ubah Password dan Edit Profil
  • #6: Login Multi-Level (Admin, Petugas, Nasabah) dan Manajemen Session
  • #7: Dashboard Petugas – Form Transaksi Setor dan Tarik Tunai
  • #8: Dashboard Petugas – Fitur Pencarian dan Lihat Mutasi Nasabah
  • #9: Dashboard Admin – Manajemen Pengguna (CRUD)
  • #10: Dashboard Admin – Laporan Transaksi dengan Filter Tanggal dan Ekspor PDF
Lebih baru Lebih lama

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