// routes/landlord/properties/[id]/index.tsx // Property details page with complete payment history and expenses import { define } from "../../../../utils.ts"; import { page } from "fresh"; import { getPropertyById } from "../../../../services/propertyService.ts"; import { getLeasesByProperty } from "../../../../services/leaseService.ts"; import { getUserById } from "../../../../services/userService.ts"; import { getExpensesByProperty, type PropertyExpense, } from "../../../../services/expenseService.ts"; import { getRentHistory, type RentChange, } from "../../../../services/rentChangeService.ts"; import { db } from "../../../../db/db.ts"; import { leases, payment_transactions } from "../../../../db/schema.ts"; import { eq } from "drizzle-orm"; import type { Property, Lease, User } from "../../../../db/interfaces.ts"; interface PropertyDetailsData { property: Property | null; leases: Lease[]; payments: Array<{ id: string; amount: number | null; payment_date: number | null; payment_method: string | null; transaction_status: string | null; tenant_name?: string; }>; expenses: PropertyExpense[]; rentHistory: Array<{ effective_date: number; monthly_rent: number; previous_rent: number | null; reason: string | null; is_current: boolean; }>; error?: string; } function buildPropertyAddress(property: Property | null): string { if (!property) return ""; return [ property.address_1, property.address_2, property.city, property.state, property.zip, ].filter(Boolean).join(", "); } export const handler = define.handlers({ async GET(ctx) { const { id } = ctx.params; try { // Fetch property const property = await getPropertyById(id); if (!property) { return page({ property: null, leases: [], payments: [], expenses: [], rentHistory: [], error: "Property not found", } as PropertyDetailsData); } // Fetch all leases for this property const leases = await getLeasesByProperty(id); // Fetch all payment transactions for these leases const payments: PropertyDetailsData["payments"] = []; for (const lease of leases) { if (lease.id) { const leasePayments = await db.select({ id: payment_transactions.id, amount: payment_transactions.amount, payment_date: payment_transactions.payment_date, payment_method: payment_transactions.payment_method, transaction_status: payment_transactions.transaction_status, }) .from(payment_transactions) .where(eq(payment_transactions.lease_id, lease.id)); // Fetch tenant name for the lease let tenant_name = undefined; if (lease.tenant_id) { const tenant = await getUserById(lease.tenant_id); tenant_name = tenant?.name || undefined; } for (const payment of leasePayments) { payments.push({ ...payment, tenant_name, }); } } } // Sort payments by date (newest first) payments.sort((a, b) => (b.payment_date || 0) - (a.payment_date || 0) ); // Fetch all expenses for this property const expenses = await getExpensesByProperty(id); // Fetch rent history const rentHistory = await getRentHistory(id); return page({ property, leases, payments, expenses, rentHistory, } as PropertyDetailsData); } catch (error) { console.error("Error loading property details:", error); return page({ property: null, leases: [], payments: [], expenses: [], rentHistory: [], error: "Failed to load property details", } as PropertyDetailsData); } }, }); export default define.page(function PropertyDetailsPage({ data }: { data: PropertyDetailsData }) { const { property, leases, payments, expenses, rentHistory, error } = data; if (error) { return (

Error

{error}

Back to Properties
); } if (!property) { return (

Property Not Found

The property you're looking for doesn't exist.

Back to Properties
); } const propertyAddress = buildPropertyAddress(property); const totalPayments = payments.reduce((sum, p) => sum + (p.amount || 0), 0); const totalExpenses = expenses.reduce((sum, e) => sum + (e.amount || 0), 0); const netIncome = totalPayments - totalExpenses; // Calculate expenses by category const expensesByCategory: Record = {}; for (const expense of expenses) { const category = expense.category || "uncategorized"; expensesByCategory[category] = (expensesByCategory[category] || 0) + (expense.amount || 0); } return (
{/* Header */}

Property Details

{property.title || propertyAddress}

{/* Property Info Card */}

Property Information

Address
{propertyAddress || "N/A"}
Monthly Rent
${property.monthly_rent?.toLocaleString() || "N/A"}
Status
{property.rental_status || "unknown"}
Bedrooms / Bathrooms
{property.bedrooms || 0}bd / {property.bathrooms || 0}ba
Square Footage
{property.square_footage?.toLocaleString() || "N/A"} sq ft
Property Type
{property.property_type || "N/A"}
{/* Financial Summary */}
Total Payments Received
${totalPayments.toLocaleString()}
Total Expenses
${totalExpenses.toLocaleString()}
Net Income
= 0 ? "text-success" : "text-danger" }`}> ${netIncome.toLocaleString()}
Active Leases
{leases.filter((l) => l.lease_status === "active").length}
{/* Two column layout */}
{/* Payment History */}

Payment History

{payments.length} payments received
{payments && payments.length > 0 ? ( payments.map((payment) => ( )) ) : ( )}
Date Tenant Method Amount Status
{payment.payment_date ? new Date(payment.payment_date) .toLocaleDateString() : "N/A"} {payment.tenant_name || "Unknown"} {payment.payment_method || "N/A"} ${payment.amount?.toLocaleString() || "0"} {payment.transaction_status || "unknown"}
No payment history yet
{/* Expenses by Category */} {Object.keys(expensesByCategory).length > 0 && (

Expenses by Category

{Object.entries(expensesByCategory) .sort(([, a], [, b]) => b - a) .map(([category, amount]) => (

{category}

${amount.toLocaleString()}
))}
)} {/* Expense History */}

Expense History

Add Expense
{expenses && expenses.length > 0 ? ( expenses.map((expense) => ( )) ) : ( )}
Date Category Description Vendor Amount
{expense.expense_date ? new Date(expense.expense_date) .toLocaleDateString() : "N/A"} {expense.category || "N/A"} {expense.description || "-"} {expense.vendor || "-"} ${expense.amount?.toLocaleString() || "0"}
No expenses recorded yet
{/* Active Leases */}

Active Leases

{leases && leases.length > 0 ? ( leases .filter((l) => l.lease_status === "active") .map((lease) => (

{lease.title || `Lease #${lease.id.slice(0, 8)}`}

{lease.start_date ? new Date(lease.start_date) .toLocaleDateString() : ""}{" "} -{" "} {lease.end_date ? new Date(lease.end_date) .toLocaleDateString() : ""}

${lease.rent?.toLocaleString() || "0"}/mo
)) ) : (

No active leases

)}
{/* Rent History */}

Rent History

Change Rent
{rentHistory && rentHistory.length > 0 ? ( rentHistory.map((entry, index) => { const previousRent = entry.previous_rent || 0; const newRent = entry.monthly_rent || 0; const changeAmount = newRent - previousRent; const isIncrease = changeAmount > 0; const isDecrease = changeAmount < 0; return ( ); }) ) : ( )}
Effective Date Previous Rent New Rent Change Reason
{entry.effective_date ? new Date(entry.effective_date) .toLocaleDateString() : "N/A"} {entry.is_current && ( Current )} ${previousRent.toLocaleString()} ${newRent.toLocaleString()} {previousRent > 0 && ( {isIncrease && "+"} ${changeAmount.toLocaleString()} {changeAmount !== 0 && ` (${ Math.round( (changeAmount / previousRent) * 100 ) }%)`} )} {previousRent === 0 && -} {entry.reason || "-"}
No rent history available
); });