feat: add rental payment history section and interface to admin rental details page

This commit is contained in:
sazzadulalambd
2026-05-12 19:59:32 +06:00
parent 8e6eadfac5
commit 9e907ec6ce

View File

@@ -72,6 +72,7 @@ interface Rental {
acceptedAt?: string; acceptedAt?: string;
activatedAt?: string; activatedAt?: string;
lockHistory?: LockEvent[]; lockHistory?: LockEvent[];
paymentHistory?: PaymentHistory[];
} }
interface LockEvent { interface LockEvent {
@@ -82,6 +83,16 @@ interface LockEvent {
performedAt: string; performedAt: string;
} }
interface PaymentHistory {
id: string;
amount: number;
type: 'daily' | 'weekly' | 'monthly' | 'deposit' | 'penalty';
date: string;
method: 'cash' | 'bank' | 'wallet' | 'bkash' | 'nagad';
status: 'paid' | 'pending' | 'failed';
note?: string;
}
const mockRentals: Rental[] = [ const mockRentals: Rental[] = [
{ {
id: 'RNT-001', id: 'RNT-001',
@@ -123,6 +134,12 @@ const mockRentals: Rental[] = [
createdAt: '2024-01-15', createdAt: '2024-01-15',
acceptedAt: '2024-01-15', acceptedAt: '2024-01-15',
activatedAt: '2024-01-16', activatedAt: '2024-01-16',
paymentHistory: [
{ id: 'PAY-001', amount: 3000, type: 'deposit', date: '2024-01-15', method: 'bkash', status: 'paid', note: 'Deposit payment' },
{ id: 'PAY-002', amount: 12000, type: 'monthly', date: '2024-02-15', method: 'bank', status: 'paid', note: 'February rent' },
{ id: 'PAY-003', amount: 12000, type: 'monthly', date: '2024-03-15', method: 'bank', status: 'paid', note: 'March rent' },
{ id: 'PAY-004', amount: 12000, type: 'monthly', date: '2024-04-15', method: 'bank', status: 'pending', note: 'April rent - pending' },
],
lockHistory: [ lockHistory: [
{ id: 'lh1', action: 'locked', reason: 'First payment overdue - Day 1 penalty', performedBy: 'System', performedAt: '2024-02-01' }, { id: 'lh1', action: 'locked', reason: 'First payment overdue - Day 1 penalty', performedBy: 'System', performedAt: '2024-02-01' },
{ id: 'lh2', action: 'unlocked', reason: 'Payment received', performedBy: 'Admin Manager', performedAt: '2024-02-03' }, { id: 'lh2', action: 'unlocked', reason: 'Payment received', performedBy: 'Admin Manager', performedAt: '2024-02-03' },
@@ -282,6 +299,7 @@ export default function RentalDetailPage() {
{ id: 'n2', text: 'First monthly payment received.', createdAt: '2024-02-15' }, { id: 'n2', text: 'First monthly payment received.', createdAt: '2024-02-15' },
]); ]);
const [newNote, setNewNote] = useState(''); const [newNote, setNewNote] = useState('');
const [paymentPage, setPaymentPage] = useState(0);
const [editForm, setEditForm] = useState<Partial<Rental>>({}); const [editForm, setEditForm] = useState<Partial<Rental>>({});
const [showLockModal, setShowLockModal] = useState(false); const [showLockModal, setShowLockModal] = useState(false);
const [showUnlockModal, setShowUnlockModal] = useState(false); const [showUnlockModal, setShowUnlockModal] = useState(false);
@@ -692,6 +710,7 @@ export default function RentalDetailPage() {
</div> </div>
</div> </div>
{/* Initial Condition Images */}
{/* {(rental.status === 'pending' || rental.status === 'accepted') && rental.initialImages && ( */} {/* {(rental.status === 'pending' || rental.status === 'accepted') && rental.initialImages && ( */}
{rental.initialImages && ( {rental.initialImages && (
<div className="bg-white p-4 rounded-xl border border-slate-200"> <div className="bg-white p-4 rounded-xl border border-slate-200">
@@ -710,7 +729,7 @@ export default function RentalDetailPage() {
</span> </span>
)} )}
</div> </div>
<div className="grid grid-cols-5 gap-2"> <div className="grid grid-cols-2 sm:grid-cols-5 gap-2">
{(['front', 'back', 'left', 'right', 'battery'] as const).map(type => { {(['front', 'back', 'left', 'right', 'battery'] as const).map(type => {
const img = rental.initialImages?.find(i => i.type === type); const img = rental.initialImages?.find(i => i.type === type);
return ( return (
@@ -730,8 +749,7 @@ export default function RentalDetailPage() {
</div> </div>
)} )}
{/* Notes */}
<div className="bg-slate-50 p-4 rounded-xl border border-slate-100"> <div className="bg-slate-50 p-4 rounded-xl border border-slate-100">
<h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2"> <h3 className="font-semibold text-slate-700 mb-3 flex items-center gap-2">
<MessageSquare className="w-5 h-5" /> Notes ({notes.length}) <MessageSquare className="w-5 h-5" /> Notes ({notes.length})
@@ -761,6 +779,61 @@ export default function RentalDetailPage() {
</button> </button>
</div> </div>
</div> </div>
{/* Rental Payment History */}
{rental.paymentHistory && rental.paymentHistory.length > 0 && (
<div className="bg-white p-4 rounded-xl border border-slate-200">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold text-slate-700 flex items-center gap-2">
<DollarSign className="w-5 h-5 text-emerald-500" /> Rental Payment History
</h3>
<span className="text-xs text-slate-500">{rental.paymentHistory.length} payments</span>
</div>
<div className="space-y-3">
{rental.paymentHistory.slice(paymentPage, paymentPage + 10).map(payment => (
<div key={payment.id} className="p-3 bg-slate-50 rounded-lg border border-slate-100">
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<span className={`text-xs font-semibold px-2 py-1 rounded-full ${payment.type === 'deposit' ? 'bg-purple-100 text-purple-700' : payment.type === 'penalty' ? 'bg-red-100 text-red-700' : 'bg-emerald-100 text-emerald-700'}`}>
{payment.type.charAt(0).toUpperCase() + payment.type.slice(1)}
</span>
<span className={`text-xs font-medium px-2 py-1 rounded-full ${payment.status === 'paid' ? 'bg-green-100 text-green-700' : payment.status === 'pending' ? 'bg-amber-100 text-amber-700' : 'bg-red-100 text-red-700'}`}>
{payment.status.charAt(0).toUpperCase() + payment.status.slice(1)}
</span>
</div>
<span className="text-lg font-bold text-slate-800">{payment.amount.toLocaleString()}</span>
</div>
<div className="flex items-center gap-4 text-xs text-slate-500">
<span>{payment.date}</span>
<span className="capitalize">{payment.method}</span>
</div>
{payment.note && <p className="text-xs text-slate-500 mt-1">{payment.note}</p>}
</div>
))}
</div>
{rental.paymentHistory.length > 1 && (
<div className="flex items-center justify-between mt-4 pt-3 border-t border-slate-100">
<button
onClick={() => setPaymentPage(p => Math.max(0, p - 1))}
disabled={paymentPage === 0}
className="px-3 py-1.5 text-sm border border-slate-200 rounded-lg disabled:opacity-40 hover:bg-slate-50"
>
Prev
</button>
<span className="text-sm text-slate-500">
Showing {paymentPage + 1}-{Math.min(paymentPage + 10, rental.paymentHistory.length)} of {rental.paymentHistory.length}
</span>
<button
onClick={() => setPaymentPage(p => Math.min(Math.ceil((rental.paymentHistory?.length ?? 1) / 10) * 10 - 10, p + 10))}
disabled={paymentPage + 10 >= (rental.paymentHistory?.length ?? 1)}
className="px-3 py-1.5 text-sm border border-slate-200 rounded-lg disabled:opacity-40 hover:bg-slate-50"
>
Next
</button>
</div>
)}
</div>
)}
</div> </div>
<div className="space-y-4"> <div className="space-y-4">