Tutorial #19: Laporan Transaksi (Admin) – Filter dan Export ke Excel/PDF
Halo para admin super! Hari ini kita akan membuat halaman laporan transaksi yang canggih. Admin bisa melihat semua transaksi (setor & tarik) yang terjadi, memfilter berdasarkan rentang tanggal, akun tertentu, dan bahkan mengekspor ke file Excel atau PDF. Siap jadi analis data?
Yang akan kita lakukan:
- ✅ Membuat endpoint API di Laravel untuk mengambil data transaksi dengan filter (tanggal, akun).
- ✅ Membuat halaman baru di React:
AdminReports.js. - ✅ Menambahkan form filter dengan input tanggal (dari & sampai) dan pilihan akun.
- ✅ Menampilkan data transaksi dalam tabel Bootstrap yang rapi.
- ✅ Menambahkan tombol export ke Excel (menggunakan
xlsx) dan PDF (menggunakanjspdf). - ✅ Menambahkan proteksi route hanya untuk admin.
Langkah 1: Membuat Endpoint API di Laravel dengan Filter
Kita perlu endpoint yang bisa menerima parameter filter. Buka routes/api.php dan tambahkan route di dalam group middleware isAdmin:
Route::middleware(['auth:sanctum', 'isAdmin'])->group(function () {
Route::get('/admin/transactions', [App\Http\Controllers\Api\AdminController::class, 'transactions']);
});
Buat controller baru atau gunakan yang sudah ada. Misal buat AdminController:
php artisan make:controller Api/AdminController
Isi method transactions:
public function transactions(Request $request)
{
$query = Transaction::with(['account.user', 'petugas']);
// Filter berdasarkan tanggal (from_date dan to_date)
if ($request->has('from_date')) {
$query->whereDate('created_at', '>=', $request->from_date);
}
if ($request->has('to_date')) {
$query->whereDate('created_at','<=', $request->to_date);
}
// Filter berdasarkan account_id
if ($request->has('account_id')) {
$query->where('account_id', $request->account_id);
}
// Urutkan descending (terbaru)
$transactions = $query->orderBy('created_at', 'desc')->get();
// Format data untuk frontend
$data = $transactions->map(function ($trx) {
return [
'id' => $trx->id,
'tanggal' => $trx->created_at->format('d-m-Y H:i'),
'nasabah' => $trx->account->user->name ?? 'Unknown',
'no_rekening' => $trx->account->account_number ?? 'Unknown',
'jenis' => $trx->type == 'debit' ? 'Setor' : 'Tarik',
'jumlah' => $trx->amount,
'keterangan' => $trx->description,
'petugas' => $trx->petugas->name ?? 'Unknown',
];
});
return response()->json($data);
}
Jangan lupa import model Transaction di atas.
isAdmin. Filter menggunakan query string, misal: /api/admin/transactions?from_date=2025-01-01&to_date=2025-01-31&account_id=1.
Langkah 2: Install Library untuk Export di React
Buka terminal di folder frontend dan install dua package ini:
npm install xlsx jspdf jspdf-autotable
xlsxuntuk export ke Excel.jspdfdanjspdf-autotableuntuk export ke PDF dengan tabel.
📁 Langkah 3: Membuat Halaman AdminReports.js
Buat file src/pages/AdminReports.js. Kita akan buat komponen dengan state untuk filter, data, dan loading.
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Swal from 'sweetalert2';
import * as XLSX from 'xlsx';
import jsPDF from 'jspdf';
import 'jspdf-autotable';
function AdminReports() {
const [transactions, setTransactions] = useState([]);
const [loading, setLoading] = useState(false);
const [filters, setFilters] = useState({
from_date: '',
to_date: '',
account_id: ''
});
const [accounts, setAccounts] = useState([]); // untuk dropdown pilihan akun
// Ambil daftar akun untuk dropdown (opsional)
useEffect(() => {
fetchAccounts();
}, []);
const fetchAccounts = async () => {
const token = localStorage.getItem('token');
try {
const response = await axios.get('http://127.0.0.1:8000/api/accounts', {
headers: { Authorization: `Bearer ${token}` }
});
setAccounts(response.data);
} catch (error) {
console.error('Gagal ambil akun');
}
};
const fetchTransactions = async () => {
setLoading(true);
const token = localStorage.getItem('token');
try {
const params = new URLSearchParams();
if (filters.from_date) params.append('from_date', filters.from_date);
if (filters.to_date) params.append('to_date', filters.to_date);
if (filters.account_id) params.append('account_id', filters.account_id);
const response = await axios.get(`http://127.0.0.1:8000/api/admin/transactions?${params}`, {
headers: { Authorization: `Bearer ${token}` }
});
setTransactions(response.data);
} catch (error) {
Swal.fire('Error', 'Gagal mengambil data transaksi', 'error');
} finally {
setLoading(false);
}
};
const handleFilterChange = (e) => {
setFilters({ ...filters, [e.target.name]: e.target.value });
};
const handleSearch = (e) => {
e.preventDefault();
fetchTransactions();
};
const resetFilters = () => {
setFilters({ from_date: '', to_date: '', account_id: '' });
fetchTransactions(); // ambil semua
};
// Export ke Excel
const exportToExcel = () => {
const ws = XLSX.utils.json_to_sheet(transactions);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'Transaksi');
XLSX.writeFile(wb, 'laporan_transaksi.xlsx');
};
// Export ke PDF
const exportToPDF = () => {
const doc = new jsPDF();
doc.text('Laporan Transaksi', 14, 10);
const tableColumn = ['Tanggal', 'Nasabah', 'No. Rekening', 'Jenis', 'Jumlah', 'Keterangan', 'Petugas'];
const tableRows = transactions.map(trx => [
trx.tanggal,
trx.nasabah,
trx.no_rekening,
trx.jenis,
`Rp ${trx.jumlah.toLocaleString()}`,
trx.keterangan,
trx.petugas
]);
doc.autoTable({
head: [tableColumn],
body: tableRows,
startY: 20,
});
doc.save('laporan_transaksi.pdf');
};
Langkah 4: Membuat Tampilan Form Filter dan Tabel
Tambahkan JSX di dalam return:
return (
<div className="container mt-4">
<h2>Laporan Transaksi</h2>
<div className="card mb-4">
<div className="card-header bg-primary text-white">
<h5 className="mb-0">Filter Transaksi</h5>
</div>
<div className="card-body">
<form onSubmit={handleSearch}>
<div className="row">
<div className="col-md-3 mb-3">
<label className="form-label">Dari Tanggal</label>
<input
type="date"
className="form-control"
name="from_date"
value={filters.from_date}
onChange={handleFilterChange}
/>
</div>
<div className="col-md-3 mb-3">
<label className="form-label">Sampai Tanggal</label>
<input
type="date"
className="form-control"
name="to_date"
value={filters.to_date}
onChange={handleFilterChange}
/>
</div>
<div className="col-md-3 mb-3">
<label className="form-label">Pilih Akun</label>
<select className="form-select" name="account_id" value={filters.account_id} onChange={handleFilterChange}>
<option value="">Semua Akun</option>
{accounts.map(acc => (
<option key={acc.id} value={acc.id}>{acc.account_number} - {acc.user?.name}</option>
))}
</select>
</div>
<div className="col-md-3 mb-3 d-flex align-items-end">
<button type="submit" className="btn btn-primary me-2">Cari</button>
<button type="button" className="btn btn-secondary" onClick={resetFilters}>Reset</button>
</div>
</div>
</form>
</div>
</div>
<div className="export-buttons">
<button className="btn btn-success" onClick={exportToExcel}>📊 Export Excel</button>
<button className="btn btn-danger" onClick={exportToPDF}>📄 Export PDF</button>
</div>
{loading ? (
<div className="text-center">
<div className="spinner-border text-primary" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
) : (
<div className="table-responsive">
<table className="table table-striped table-hover">
<thead>
<tr>
<th>Tanggal</th>
<th>Nasabah</th>
<th>No. Rekening</th>
<th>Jenis</th>
<th>Jumlah</th>
<th>Keterangan</th>
<th>Petugas</th>
</tr>
</thead>
<tbody>
{transactions.length > 0 ? (
transactions.map((trx, idx) => (
<tr key={idx}>
<td>{trx.tanggal}</td>
<td>{trx.nasabah}</td>
<td>{trx.no_rekening}</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'}>
Rp {trx.jumlah.toLocaleString()}
</td>
<td>{trx.keterangan}</td>
<td>{trx.petugas}</td>
</tr>
))
) : (
<tr>
<td colSpan="7" className="text-center">Tidak ada data transaksi</td>
</tr>
)}
</tbody>
</table>
</div>
)}
</div>
);
Jangan lupa export komponen.
/api/accounts untuk dropdown perlu dibuat jika belum ada. Bisa ditambahkan di AdminController dengan method accounts() yang mengembalikan daftar akun (id, account_number, user.name).
Langkah 5: Menambahkan Route dengan Proteksi Admin
Di src/App.js, tambahkan route baru:
import AdminReports from './pages/AdminReports';
// ... di dalam Routes
<Route path="/admin/reports" element={
<PrivateRoute allowedRoles={['admin']}>
<AdminReports />
</PrivateRoute>
} />
Jangan lupa update PrivateRoute untuk mendukung allowedRoles (sudah dibuat di tutorial #13).
Langkah 6: Menambahkan Link di Navbar Admin
Di Navbar.js, untuk role admin tambahkan menu "Laporan Transaksi":
{user.role === 'admin' && (
<li className="nav-item">
<Link className="nav-link" to="/admin/reports">Laporan</Link>
</li>
)}
Langkah 7: Uji Coba
- Login sebagai admin.
- Masuk ke halaman Laporan Transaksi (misal
/admin/reports). - Tanpa filter, seharusnya muncul semua transaksi.
- Coba filter berdasarkan tanggal (misal 1-31 Maret 2025) dan klik Cari. Data berubah.
- Pilih akun tertentu, filter.
- Klik Export Excel → file .xlsx terdownload, buka dengan Excel.
- Klik Export PDF → file .pdf terdownload, berisi tabel.
- Klik Reset → filter kembali kosong, data kembali semua.
Kesimpulan
- ✅ Endpoint API dengan filter berhasil dibuat.
- ✅ Halaman laporan di React dengan filter tanggal dan akun.
- ✅ Export ke Excel dan PDF berfungsi dengan baik.
- ✅ Hanya admin yang bisa mengakses.
Di tutorial terakhir (#20: Finalisasi dan Penyempurnaan Aplikasi) kita akan melakukan pengecekan akhir, memperbaiki bug, dan menambahkan sentuhan akhir agar aplikasi siap digunakan. Sampai jumpa!
Daftar Tutorial
- Tutorial #20: Finalisasi dan Penyempurnaan Aplikasi