240 lines
14 KiB
JavaScript
240 lines
14 KiB
JavaScript
// Import wajib
|
|
import React, { useState, useEffect } from 'react';
|
|
import { Head } from '@inertiajs/react';
|
|
import { useDispatch } from 'react-redux';
|
|
import { setPageTitle } from '@/Components/features/common/headerSlice';
|
|
import { CurrencyDollarIcon } from '@heroicons/react/24/outline';
|
|
import { Inertia } from '@inertiajs/inertia';
|
|
|
|
export default function Transaction({ transaction }) {
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [filteredSantri, setFilteredSantri] = useState([]);
|
|
const [selectedSantri, setSelectedSantri] = useState(null);
|
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth() + 1);
|
|
const [selectedYear, setSelectedYear] = useState(new Date().getFullYear());
|
|
|
|
const dispatch = useDispatch();
|
|
const bulanIndo = [
|
|
'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
|
|
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
|
|
];
|
|
|
|
useEffect(() => {
|
|
dispatch(setPageTitle("Data Transaksi Santri"));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (transaction?.data) {
|
|
const filtered = transaction.data.filter(item => {
|
|
const matchSearch = item.nama.toLowerCase().includes(searchTerm.toLowerCase()) || item.nis?.toString().includes(searchTerm);
|
|
const hasPaymentThisMonthYear = item.payments?.some(payment =>
|
|
payment.detail_payments?.some(d =>
|
|
d.payment_month === selectedMonth &&
|
|
(selectedYear === 'all' || d.payment_year === selectedYear)
|
|
)
|
|
);
|
|
return matchSearch && hasPaymentThisMonthYear;
|
|
});
|
|
setFilteredSantri(filtered);
|
|
}
|
|
}, [searchTerm, selectedMonth, selectedYear, transaction]);
|
|
|
|
const closeModal = () => {
|
|
setIsModalOpen(false);
|
|
setSelectedSantri(null);
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<Head title="Transaksi Santri" />
|
|
<div className="card bg-base-100 shadow-xl">
|
|
<div className="card-body">
|
|
<div className="flex items-center mb-6">
|
|
<div className="bg-gradient-to-tr from-blue-400 to-blue-600 p-3 rounded-lg mr-3">
|
|
<CurrencyDollarIcon className="h-6 w-6 text-white" />
|
|
</div>
|
|
<h1 className="text-2xl font-bold">Data Transaksi User</h1>
|
|
</div>
|
|
|
|
<div className="flex justify-between items-center mb-4 gap-2">
|
|
<input
|
|
type="text"
|
|
placeholder="Cari Santri..."
|
|
className="input input-bordered"
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
<select
|
|
className="select select-bordered"
|
|
value={selectedMonth}
|
|
onChange={(e) => setSelectedMonth(Number(e.target.value))}
|
|
>
|
|
{bulanIndo.map((b, i) => <option key={i} value={i + 1}>{b}</option>)}
|
|
</select>
|
|
<select
|
|
className="select select-bordered"
|
|
value={selectedYear}
|
|
onChange={(e) => setSelectedYear(e.target.value === 'all' ? 'all' : Number(e.target.value))}
|
|
>
|
|
<option value="all">Semua Tahun</option>
|
|
{Array.from({ length: 5 }, (_, i) => {
|
|
const year = new Date().getFullYear() - 2 + i;
|
|
return <option key={year} value={year}>{year}</option>
|
|
})}
|
|
</select>
|
|
</div>
|
|
|
|
{/* TABEL SANTRI */}
|
|
<div className="overflow-x-auto">
|
|
<table className="table table-zebra w-full">
|
|
<thead>
|
|
<tr>
|
|
<th>Nama</th>
|
|
<th>Detail</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filteredSantri.length > 0 ? (
|
|
filteredSantri.map((item, i) => (
|
|
<tr key={i}>
|
|
<td>
|
|
<div className="flex items-center space-x-3">
|
|
<div className="avatar">
|
|
<div className="mask mask-squircle w-12 h-12">
|
|
<img src={`https://ui-avatars.com/api/?name=${item.nama}`} alt="Avatar" />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="font-bold">{item.nama}</div>
|
|
<div className="text-sm opacity-50">{item.level === 1 ? 'Admin' : 'Santri'}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<button
|
|
className='btn btn-success text-white'
|
|
onClick={() => {
|
|
setSelectedSantri(item);
|
|
setIsModalOpen(true);
|
|
}}
|
|
>
|
|
Detail
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))
|
|
) : (
|
|
<tr>
|
|
<td colSpan="2" className="text-center">Tidak ada data santri.</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{/* PAGINATION */}
|
|
<div className="flex justify-center mt-4">
|
|
{transaction.current_page > 1 && (
|
|
<button
|
|
className="btn btn-sm mx-1"
|
|
onClick={() =>
|
|
Inertia.visit(`${window.location.pathname}?page=${transaction.current_page - 1}`)
|
|
}
|
|
>«</button>
|
|
)}
|
|
{Array.from({ length: transaction.last_page }, (_, i) => i + 1)
|
|
.filter(page => page === 1 || page === transaction.last_page || Math.abs(page - transaction.current_page) <= 2)
|
|
.map((page, i, arr) => {
|
|
const prev = arr[i - 1];
|
|
const showDots = prev && page - prev > 1;
|
|
return (
|
|
<React.Fragment key={page}>
|
|
{showDots && <span className="btn btn-sm mx-1 pointer-events-none">...</span>}
|
|
<button
|
|
className={`btn btn-sm mx-1 ${transaction.current_page === page ? 'btn-active btn-primary text-white' : ''}`}
|
|
onClick={() =>
|
|
Inertia.visit(`${window.location.pathname}?page=${page}`)
|
|
}
|
|
>
|
|
{page}
|
|
</button>
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
{transaction.current_page < transaction.last_page && (
|
|
<button
|
|
className="btn btn-sm mx-1"
|
|
onClick={() =>
|
|
Inertia.visit(`${window.location.pathname}?page=${transaction.current_page + 1}`)
|
|
}
|
|
>»</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* MODAL DETAIL TRANSAKSI */}
|
|
{isModalOpen && selectedSantri && (
|
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" onClick={closeModal}>
|
|
<div className="bg-base-100 rounded-2xl shadow-xl p-6 w-11/12 max-w-3xl relative" onClick={e => e.stopPropagation()}>
|
|
<button onClick={closeModal} className="absolute top-3 right-3 text-red-500 hover:text-red-700">❌</button>
|
|
<h2 className="text-2xl font-bold mb-4 text-center text-gray-800">
|
|
Detail Transaksi <span className="text-blue-600">{selectedSantri.nama}</span>
|
|
</h2>
|
|
|
|
<div className="space-y-4 max-h-[70vh] overflow-y-auto">
|
|
{selectedSantri.payments
|
|
?.map(p => ({
|
|
...p,
|
|
details: p.detail_payments?.filter(d => d.payment_month === selectedMonth && (selectedYear === 'all' || d.payment_year === selectedYear))
|
|
}))
|
|
.filter(p => p.details?.length)
|
|
.map((payment, idx) => {
|
|
const total = payment.details.reduce((sum, d) => sum + Number(d.amount) + Number(d.penalty || 0), 0);
|
|
return (
|
|
<div key={idx} className="bg-base-200 p-4 rounded-xl shadow-md border border-gray-200">
|
|
<div className="flex flex-wrap justify-between items-center text-sm mb-2">
|
|
<div><strong>Order ID:</strong> {payment.order_id || '-'}</div>
|
|
<div className={payment.payment_status === 'success' ? 'bg-green-600 text-white px-2 py-1 rounded' : 'bg-orange-600 text-white px-2 py-1 rounded'}>
|
|
{payment.payment_status}
|
|
</div>
|
|
</div>
|
|
<table className="table w-full text-sm">
|
|
<thead>
|
|
<tr className="bg-gray-100 text-gray-700">
|
|
<th>Status</th>
|
|
<th>Jumlah</th>
|
|
<th>Jenis</th>
|
|
<th>Tipe</th>
|
|
<th>Denda</th>
|
|
<th>Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{payment.details.map((detail, i) => (
|
|
<tr key={i}>
|
|
<td>{detail.status}</td>
|
|
<td>Rp {Number(detail.amount).toLocaleString()}</td>
|
|
<td>{payment.transaction_type === 'payment' ? 'Pembayaran' : 'Topup'}</td>
|
|
<td>{detail.payment_type?.payment_type || '-'}</td>
|
|
<td>Rp {Number(detail.penalty || 0).toLocaleString()}</td>
|
|
<td>Rp {(Number(detail.amount) + Number(detail.penalty || 0)).toLocaleString()}</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
<div className="text-right mt-2 font-bold text-black">
|
|
Total: Rp {total.toLocaleString()}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|