feat: add rental payment history section and interface to admin rental details page
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user