Tutorial #15: Dashboard Petugas – Form Transaksi Setor dan Tarik Tunai
Halo para teller bank! Hari ini kita akan membangun form transaksi setor dan tarik tunai di dashboard petugas. Petugas bisa melayani nasabah dengan memasukkan ID rekening, jumlah uang, dan keterangan. Transaksi akan langsung tercatat dan saldo nasabah otomatis berubah. Siap jadi teller handal?
Yang akan kita lakukan:
- ✅ Membuat dua form terpisah: Setor Tunai (deposit) dan Tarik Tunai (withdraw).
- ✅ Validasi input di sisi klien (React) sebelum dikirim ke API.
- ✅ Mengirim data ke endpoint API yang sudah dibuat di tutorial #5.
- ✅ Menampilkan notifikasi SweetAlert untuk transaksi sukses/gagal.
- ✅ Menambahkan fitur pencarian data nasabah (opsional) untuk memudahkan.
- ✅ Mengosongkan form setelah transaksi berhasil.
Langkah 1: Struktur Dasar PetugasDashboard
Kita akan gunakan komponen PetugasDashboard.js yang sudah dibuat di tutorial #13. Jika belum ada, buat file tersebut di folder src/pages/.
Pertama, import yang diperlukan dan siapkan state:
import React, { useState } from 'react';
import axios from 'axios';
import Swal from 'sweetalert2';
function PetugasDashboard() {
// State untuk form deposit
const [depositForm, setDepositForm] = useState({
account_id: '',
amount: '',
description: ''
});
// State untuk form withdraw
const [withdrawForm, setWithdrawForm] = useState({
account_id: '',
amount: '',
description: ''
});
const [loading, setLoading] = useState({ deposit: false, withdraw: false });
// Handler untuk mengubah input
const handleDepositChange = (e) => {
setDepositForm({ ...depositForm, [e.target.name]: e.target.value });
};
const handleWithdrawChange = (e) => {
setWithdrawForm({ ...withdrawForm, [e.target.name]: e.target.value });
};
// ... fungsi deposit dan withdraw akan ditambahkan
}
Langkah 2: Fungsi Deposit (Setor Tunai)
const handleDeposit = async (e) => {
e.preventDefault();
// Validasi sederhana
if (!depositForm.account_id || !depositForm.amount) {
Swal.fire('Peringatan', 'ID Rekening dan Jumlah harus diisi', 'warning');
return;
}
if (depositForm.amount < 1000) {
Swal.fire('Peringatan', 'Minimal setor Rp 1.000', 'warning');
return;
}
setLoading({ ...loading, deposit: true });
const token = localStorage.getItem('token');
try {
const response = await axios.post('http://127.0.0.1:8000/api/deposit', depositForm, {
headers: { Authorization: `Bearer ${token}` }
});
Swal.fire({
icon: 'success',
title: 'Setor Tunai Berhasil',
text: `Rp ${depositForm.amount} telah ditambahkan ke rekening ${depositForm.account_id}`,
timer: 2000,
showConfirmButton: false
});
// Reset form
setDepositForm({ account_id: '', amount: '', description: '' });
} catch (error) {
const message = error.response?.data?.message || 'Terjadi kesalahan';
Swal.fire('Gagal', message, 'error');
} finally {
setLoading({ ...loading, deposit: false });
}
};
Langkah 3: Fungsi Withdraw (Tarik Tunai)
const handleWithdraw = async (e) => {
e.preventDefault();
// Validasi
if (!withdrawForm.account_id || !withdrawForm.amount) {
Swal.fire('Peringatan', 'ID Rekening dan Jumlah harus diisi', 'warning');
return;
}
if (withdrawForm.amount < 1000) {
Swal.fire('Peringatan', 'Minimal tarik Rp 1.000', 'warning');
return;
}
setLoading({ ...loading, withdraw: true });
const token = localStorage.getItem('token');
try {
const response = await axios.post('http://127.0.0.1:8000/api/withdraw', withdrawForm, {
headers: { Authorization: `Bearer ${token}` }
});
Swal.fire({
icon: 'success',
title: 'Tarik Tunai Berhasil',
text: `Rp ${withdrawForm.amount} telah ditarik dari rekening ${withdrawForm.account_id}`,
timer: 2000,
showConfirmButton: false
});
// Reset form
setWithdrawForm({ account_id: '', amount: '', description: '' });
} catch (error) {
const message = error.response?.data?.message || 'Terjadi kesalahan';
Swal.fire('Gagal', message, 'error');
} finally {
setLoading({ ...loading, withdraw: false });
}
};
Langkah 4: Membuat Tampilan Form dengan Bootstrap
Kita akan membuat dua kolom dengan Bootstrap grid. Masing-masing kolom berisi card dengan form.
return (
<div className="container mt-4">
<h2 className="mb-4">Dashboard Petugas</h2>
<div className="row">
{/* Form Setor Tunai */}
<div className="col-md-6">
<div className="card mb-4">
<div className="card-header bg-success text-white">
<h5 className="mb-0">💰 Setor Tunai</h5>
</div>
<div className="card-body">
<form onSubmit={handleDeposit}>
<div className="mb-3">
<label className="form-label">ID Rekening</label>
<input
type="number"
className="form-control"
name="account_id"
value={depositForm.account_id}
onChange={handleDepositChange}
placeholder="Contoh: 1"
required
/>
</div>
<div className="mb-3">
<label className="form-label">Jumlah (Rp)</label>
<input
type="number"
className="form-control"
name="amount"
value={depositForm.amount}
onChange={handleDepositChange}
placeholder="Minimal 1000"
required
/>
</div>
<div className="mb-3">
<label className="form-label">Keterangan (opsional)</label>
<input
type="text"
className="form-control"
name="description"
value={depositForm.description}
onChange={handleDepositChange}
placeholder="Misal: Setor tunai"
/>
</div>
<button
type="submit"
className="btn btn-success w-100"
disabled={loading.deposit}
>
{loading.deposit ? 'Memproses...' : 'Setor'}
</button>
</form>
</div>
</div>
</div>
{/* Form Tarik Tunai */}
<div className="col-md-6">
<div className="card mb-4">
<div className="card-header bg-danger text-white">
<h5 className="mb-0">💸 Tarik Tunai</h5>
</div>
<div className="card-body">
<form onSubmit={handleWithdraw}>
<div className="mb-3">
<label className="form-label">ID Rekening</label>
<input
type="number"
className="form-control"
name="account_id"
value={withdrawForm.account_id}
onChange={handleWithdrawChange}
placeholder="Contoh: 1"
required
/>
</div>
<div className="mb-3">
<label className="form-label">Jumlah (Rp)</label>
<input
type="number"
className="form-control"
name="amount"
value={withdrawForm.amount}
onChange={handleWithdrawChange}
placeholder="Minimal 1000"
required
/>
</div>
<div className="mb-3">
<label className="form-label">Keterangan (opsional)</label>
<input
type="text"
className="form-control"
name="description"
value={withdrawForm.description}
onChange={handleWithdrawChange}
placeholder="Misal: Tarik tunai"
/>
</div>
<button
type="submit"
className="btn btn-danger w-100"
disabled={loading.withdraw}
>
{loading.withdraw ? 'Memproses...' : 'Tarik'}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
);
type="number" untuk memudahkan, tapi pastikan di backend menerima integer. Bisa juga menggunakan nomor rekening (string), sesuaikan dengan API.
Bonus: Menambahkan Pencarian Data Nasabah
Untuk memudahkan petugas, kita bisa menambahkan fitur pencarian nasabah berdasarkan ID atau nomor rekening. Berikut contoh sederhana menampilkan informasi saldo sebelum transaksi.
Tambahkan state baru:
const [searchResult, setSearchResult] = useState(null); const [searchLoading, setSearchLoading] = useState(false);
Buat fungsi cari nasabah:
const searchAccount = async (accountId) => {
if (!accountId) return;
setSearchLoading(true);
const token = localStorage.getItem('token');
try {
const response = await axios.get(`http://127.0.0.1:8000/api/accounts/${accountId}`, {
headers: { Authorization: `Bearer ${token}` }
});
setSearchResult(response.data);
} catch (error) {
Swal.fire('Tidak ditemukan', 'Rekening tidak valid', 'error');
setSearchResult(null);
} finally {
setSearchLoading(false);
}
};
Panggil fungsi ini saat tombol cari ditekan atau saat ID rekening diisi. Bisa ditambahkan di samping input.
Langkah 5: Uji Coba Transaksi
- Login sebagai petugas.
- Isi form setor dengan ID rekening yang valid (misal 1), jumlah 50000, keterangan "Setor awal".
- Klik Setor. Muncul notifikasi sukses, form kosong.
- Cek dashboard nasabah (login sebagai nasabah tersebut) untuk melihat saldo bertambah dan mutasi tercatat.
- Uji tarik tunai dengan saldo yang cukup. Pastikan notifikasi error muncul jika saldo tidak cukup.
Kesimpulan
- ✅ Petugas dapat melakukan transaksi setor dan tarik tunai melalui form yang intuitif.
- ✅ Validasi client-side membantu mencegah kesalahan input.
- ✅ SweetAlert memberikan notifikasi yang jelas dan menarik.
- ✅ Form otomatis reset setelah transaksi sukses.
Di tutorial selanjutnya (#16: Fitur Ubah Password untuk Semua Level Pengguna) kita akan menambahkan halaman pengaturan password agar pengguna bisa mengganti password mereka sendiri. Sampai jumpa!
Daftar Tutorial Lanjutan
- Tutorial #16: Fitur Ubah Password untuk Semua Level Pengguna
- Tutorial #17: Menambahkan Fitur Profil dan Edit Profil
- Tutorial #18: Laporan Transaksi (Admin) – Filter dan Export ke Excel/PDF
- Tutorial #19: Finalisasi dan Penyempurnaan Aplikasi