Tutorial Laravel & ReactJS #15: Dashboard Petugas – Form Transaksi Setor/Tarik

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? 

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

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>
);
💡 Catatan: Input ID Rekening menggunakan 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

  1. Login sebagai petugas.
  2. Isi form setor dengan ID rekening yang valid (misal 1), jumlah 50000, keterangan "Setor awal".
  3. Klik Setor. Muncul notifikasi sukses, form kosong.
  4. Cek dashboard nasabah (login sebagai nasabah tersebut) untuk melihat saldo bertambah dan mutasi tercatat.
  5. Uji tarik tunai dengan saldo yang cukup. Pastikan notifikasi error muncul jika saldo tidak cukup.
😆 "Transaksi setor itu kayak nambahin bensin ke motor: begitu masuk, langsung bisa jalan lagi!" 🏍️

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
Lebih baru Lebih lama

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