// routes/landlord/payments/index.tsx import { define } from "../../../utils.ts"; import { page } from "fresh"; import { getLeasesByOwner } from "../../../services/leaseService.ts"; import { getPaymentTransactionsByOwner } from "../../../services/paymentService.ts"; import { getUserById } from "../../../services/userService.ts"; import { Lease, PaymentTransaction } from "../../../db/interfaces.ts"; const LATE_FEE_AMOUNT = 50; function formatCurrency(value: number): string { return `$${value.toLocaleString()}`; } function getTransactionTimestamp(transaction: PaymentTransaction): number { return transaction.payment_date ?? transaction.created_on ?? 0; } function isLatePayment(transaction: PaymentTransaction): boolean { const date = new Date(getTransactionTimestamp(transaction)); return date.getUTCDate() > 5; } export const handler = define.handlers({ async GET(ctx) { const { user_id } = ctx.state.data; try { const leases = await getLeasesByOwner(user_id); const transactions = await getPaymentTransactionsByOwner(user_id); const tenantIds = Array.from( new Set( leases .map((lease) => lease.tenant_id) .filter((tenantId): tenantId is string => Boolean(tenantId)), ), ); const tenantNames: Record = {}; for (const tenantId of tenantIds) { const tenant = await getUserById(tenantId); if (tenant?.name) { tenantNames[tenantId] = tenant.name; } } return page({ leases, transactions, tenantNames, }); } catch (error) { console.error("Error loading rent collection:", error); return page({ error: "Failed to load rent collection", }); } }, }); export default define.page(function LandlordPayments({ data }) { const { leases, transactions, tenantNames, error } = data as { leases: Lease[]; transactions: PaymentTransaction[]; tenantNames: Record; error?: string; }; const leaseMap = new Map(leases.map((lease) => [lease.id, lease])); const transactionsWithMeta = (transactions || []).map((transaction) => { const lease = leaseMap.get(transaction.lease_id || ""); const tenantName = lease?.tenant_id ? tenantNames[lease.tenant_id] || "Tenant" : "Tenant"; const status = transaction.transaction_status || "pending"; const lateFee = isLatePayment(transaction) ? LATE_FEE_AMOUNT : 0; return { ...transaction, lease, tenantName, status, lateFee, }; }); const now = Date.now(); const nowDate = new Date(now); const startOfMonth = new Date( nowDate.getFullYear(), nowDate.getMonth(), 1, ).getTime(); const endOfMonth = new Date( nowDate.getFullYear(), nowDate.getMonth() + 1, 0, 23, 59, 59, 999, ).getTime(); const monthlyTransactions = transactionsWithMeta.filter((transaction) => { const timestamp = getTransactionTimestamp(transaction); return timestamp >= startOfMonth && timestamp <= endOfMonth; }); const expectedThisMonth = leases .filter((lease) => lease.lease_status === "active") .reduce((sum, lease) => sum + (lease.rent || 0), 0); const receivedThisMonth = monthlyTransactions .filter((transaction) => transaction.status === "completed") .reduce((sum, transaction) => sum + (transaction.amount || 0), 0); const overdueCount = monthlyTransactions.filter((transaction) => transaction.status !== "completed" ).length; const lateFeesDue = monthlyTransactions .filter((transaction) => transaction.status !== "completed" ) .reduce((sum, transaction) => sum + (transaction.lateFee || 0), 0); return (

Rent Collection

Track rent payments across your portfolio

{error && (
{error}
)}
Expected This Month
{formatCurrency(expectedThisMonth)}
Received This Month
{formatCurrency(receivedThisMonth)}
Overdue Payments
{overdueCount}
Late Fees Due
{formatCurrency(lateFeesDue)}
{transactionsWithMeta.length > 0 ? ( transactionsWithMeta.map((transaction) => ( )) ) : ( )}
Tenant Property Amount Status Late Fee Date
{transaction.tenantName} {transaction.lease?.property_address || "Property"} {formatCurrency(transaction.amount || 0)} {transaction.status} {transaction.lateFee ? formatCurrency(transaction.lateFee) : "-"} {new Date(getTransactionTimestamp(transaction)) .toLocaleDateString()}
No payment history yet
); });