Tutorial #9: Dashboard Nasabah – Menampilkan Total Saldo dan Mutasi
Halo para nasabah setia! Hari ini kita akan membuat dashboard yang keren untuk nasabah melihat saldo dan mutasi. Kita akan pakai Bootstrap biar tabelnya rapi, SweetAlert untuk notifikasi, dan data langsung dari API yang sudah kita buat. Siap-siap jadi frontend developer handal!
Yang akan kita lakukan:
- ✅ Mempercantik tampilan dashboard dengan komponen Bootstrap.
- ✅ Menampilkan saldo dengan format Rupiah yang rapi.
- ✅ Membuat tabel mutasi dengan strip dan hover effect.
- ✅ Menambahkan indikator loading saat mengambil data.
- ✅ Menangani error jika data gagal dimuat.
- ✅ Menambahkan fitur refresh data (manual).
- ✅ Membuat ringkasan transaksi (total setor, total tarik).
Langkah 1: Dashboard yang Sudah Ada, Kita Poles
Di tutorial #8, kita sudah membuat dashboard yang bisa menampilkan saldo dan mutasi. Sekarang kita akan memperbaikinya dengan:
- Menambahkan card untuk informasi tambahan (no. rekening, total transaksi).
- Membuat tabel lebih menarik dengan class Bootstrap
table-striped table-hover. - Menampilkan loading spinner saat mengambil data.
- Menambahkan tombol refresh untuk memuat ulang data.
Langkah 2: Memperbarui DashboardPage.js
Buka file src/pages/DashboardPage.js. Kita akan tulis ulang dengan fitur yang lebih lengkap.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Swal from 'sweetalert2';
function DashboardPage() {
const [saldo, setSaldo] = useState(null);
const [mutasi, setMutasi] = useState([]);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
// Fungsi untuk mengambil data dari API
const fetchData = async () => {
const token = localStorage.getItem('token');
if (!token) return;
setRefreshing(true);
try {
// Ambil saldo
const saldoRes = await axios.get('http://127.0.0.1:8000/api/nasabah/saldo', {
headers: { Authorization: `Bearer ${token}` }
});
setSaldo(saldoRes.data);
// Ambil mutasi
const mutasiRes = await axios.get('http://127.0.0.1:8000/api/nasabah/mutasi', {
headers: { Authorization: `Bearer ${token}` }
});
setMutasi(mutasiRes.data.mutasi);
} catch (error) {
Swal.fire({
icon: 'error',
title: 'Gagal memuat data',
text: error.response?.data?.message || 'Terjadi kesalahan'
});
} finally {
setLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchData();
}, []);
// Hitung total setor dan tarik
const totalSetor = mutasi
.filter(trx => trx.jenis === 'Setor')
.reduce((sum, trx) => sum + trx.jumlah, 0);
const totalTarik = mutasi
.filter(trx => trx.jenis === 'Tarik')
.reduce((sum, trx) => sum + trx.jumlah, 0);
// Fungsi untuk format Rupiah
const formatRupiah = (angka) => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0
}).format(angka);
};
if (loading) {
return (
<div className="text-center my-5">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
<p className="mt-2">Mengambil data rekening...</p>
</div>
);
}
return (
<div>
{/* Header dengan tombol refresh */}
<div className="d-flex justify-content-between align-items-center mb-4">
<h2>Dashboard Nasabah</h2>
<button
className="btn btn-outline-primary"
onClick={fetchData}
disabled={refreshing}
>
{refreshing ? (
<>
<span className="spinner-border spinner-border-sm me-2" />
Memuat...
</>
) : (
<>Refresh</>
)}
</button>
</div>
{/* Card Informasi Rekening */}
<div className="row g-4 mb-4">
<div className="col-md-6">
<div className="card border-primary h-100">
<div className="card-header bg-primary text-white">
<h5 className="mb-0">Informasi Rekening</h5>
</div>
<div className="card-body">
<h6 className="card-subtitle mb-2 text-muted">No. Rekening</h6>
<p className="card-text fs-5">{saldo?.no_rekening}</p>
<h6 className="card-subtitle mb-2 text-muted">Nama Nasabah</h6>
<p className="card-text">{saldo?.nama}</p>
</div>
</div>
</div>
<div className="col-md-6">
<div className="card border-success h-100">
<div className="card-header bg-success text-white">
<h5 className="mb-0">Ringkasan Transaksi</h5>
</div>
<div className="card-body">
<h6 className="card-subtitle mb-2 text-muted">Total Setoran</h6>
<p className="card-text text-success">{formatRupiah(totalSetor)}</p>
<h6 className="card-subtitle mb-2 text-muted">Total Penarikan</h6>
<p className="card-text text-danger">{formatRupiah(totalTarik)}</p>
</div>
</div>
</div>
</div>
{/* Card Saldo Utama */}
<div className="card mb-4 text-center bg-light">
<div className="card-body">
<h5 className="card-title">Total Saldo</h5>
<p className="display-4 text-success fw-bold">
{formatRupiah(saldo?.saldo)}
</p>
</div>
</div>
{/* Tabel Mutasi */}
<h4 className="mb-3">Mutasi Rekening</h4>
{mutasi.length === 0 ? (
<div className="alert alert-info">
Belum ada transaksi. Ayo segera menabung!
</div>
) : (
<div className="table-responsive">
<table className="table table-striped table-hover">
<thead className="table-primary">
<tr>
<th>Tanggal</th>
<th>Jenis</th>
<th>Jumlah</th>
<th>Keterangan</th>
<th>Petugas</th>
</tr>
</thead>
<tbody>
{mutasi.map((trx, index) => (
<tr key={index}>
<td>{trx.tanggal}</td>
<td>
<span className={`badge ${trx.jenis === 'Setor' ? 'bg-success' : 'bg-danger'}`}>
{trx.jenis}
</span>
</td>
<td className={trx.jenis === 'Setor' ? 'text-success' : 'text-danger'}>
{formatRupiah(trx.jumlah)}
</td>
<td>{trx.keterangan}</td>
<td>{trx.petugas}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
export default DashboardPage;
Langkah 3: Menambahkan Styling Kustom (Opsional)
Kita bisa tambahkan sedikit CSS kustom di file src/index.css atau App.css untuk mempercantik. Misalnya:
.table thead th {
background-color: #00796b;
color: white;
}
.card {
border-radius: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.05);
}
.display-4 {
font-size: 3.5rem;
}
Tapi Bootstrap sudah cukup keren, jadi nggak wajib.
Langkah 4: Menambahkan Fitur Logout Otomatis Jika Token Kadaluarsa
Di method fetchData, kita bisa tambahkan penanganan jika response 401 (Unauthorized) yang berarti token sudah tidak valid. Maka kita hapus token dan redirect ke login.
Modifikasi bagian catch pada fetchData:
} catch (error) {
if (error.response?.status === 401) {
localStorage.removeItem('token');
localStorage.removeItem('user');
Swal.fire('Sesi habis', 'Silakan login ulang', 'warning')
.then(() => window.location.href = '/login');
} else {
Swal.fire({
icon: 'error',
title: 'Gagal memuat data',
text: error.response?.data?.message || 'Terjadi kesalahan'
});
}
}
Langkah 5: Uji Coba Dashboard
Pastikan backend Laravel berjalan dan frontend juga. Login sebagai nasabah, maka akan muncul dashboard seperti ini:
✨ Hasil yang diharapkan:
- Card informasi rekening (no. rekening, nama).
- Card ringkasan transaksi (total setor, total tarik).
- Saldo utama dengan angka besar.
- Tabel mutasi dengan badge warna (hijau untuk setor, merah untuk tarik).
- Tombol refresh untuk memuat ulang data.
- Loading spinner saat pertama kali buka atau refresh.
Bonus: Menambahkan Grafik Sederhana dengan Chart.js
Kalau mau lebih keren, kita bisa tambahkan grafik batang untuk visualisasi transaksi per bulan. Install dulu:
npm install chart.js react-chartjs-2
Kemudian buat komponen grafik. Tapi karena ini tutorial dasar, kita skip dulu. Mungkin di artikel bonus.
Kesimpulan
- ✅ Dashboard nasabah tampil cantik dengan Bootstrap.
- ✅ Saldo dan mutasi diambil dari API real-time.
- ✅ Ada ringkasan total setor dan tarik.
- ✅ Loading state dan error handling sudah ditangani.
- ✅ Tombol refresh memudahkan update data.
Di tutorial selanjutnya (#10: Notifikasi Interaktif dengan SweetAlert pada Aksi Nasabah) kita akan memperdalam penggunaan SweetAlert untuk konfirmasi logout, notifikasi error, dan lain-lain. Tapi sebenarnya kita sudah melakukannya di sini! Jadi tutorial #10 bisa fokus pada variasi SweetAlert atau fitur lain seperti ubah password. Sampai jumpa!