import { redirect } from "next/navigation" import { eq, desc } from "drizzle-orm" import { db } from "@/lib/db" import { properties as propertiesTable } from "@/lib/db/schema" import { getSessionUser } from "@/lib/session" import Link from "next/link" import { Building2, MapPin, BedDouble, ArrowRight, TrendingUp } from "lucide-react" import { EmptyState } from "@/components/shared/empty-state" import { formatCurrency, getOccupancyRate } from "@/lib/utils" export const metadata = { title: "Properties" } export default async function PropertiesPage() { const user = await getSessionUser() if (!user) redirect("/login") const properties = await db.query.properties.findMany({ where: eq(propertiesTable.user_id, user.id), with: { units: { columns: { id: true, status: true, rent_amount: true } }, }, orderBy: desc(propertiesTable.created_at), }) const totalMonthly = (properties ?? []).reduce((sum: number, p: any) => { return sum + (p.units ?? []).filter((u: any) => u.status === "occupied").reduce((s: number, u: any) => s + Number(u.rent_amount), 0) }, 0) return (
{/* Page header */}

Properties

{properties?.length ?? 0} {(properties?.length ?? 0) === 1 ? "property" : "properties"} {totalMonthly > 0 && ยท {formatCurrency(totalMonthly)}/mo}

{!properties?.length ? ( ) : (
{(properties as any[]).map((property) => { const units = property.units ?? [] const occupied = units.filter((u: any) => u.status === "occupied").length const totalUnits = units.length const occupancy = getOccupancyRate(occupied, totalUnits) const monthlyRent = units .filter((u: any) => u.status === "occupied") .reduce((sum: number, u: any) => sum + Number(u.rent_amount), 0) const potential = units.reduce((sum: number, u: any) => sum + Number(u.rent_amount), 0) const occupancyColor = occupancy === 100 ? "text-emerald-400" : occupancy >= 50 ? "text-amber-400" : "text-red-400" const barColor = occupancy === 100 ? "bg-emerald-500" : occupancy >= 50 ? "bg-amber-500" : "bg-red-500" return ( {/* Top row */}
= 50 ? "bg-amber-500/10 text-amber-400 ring-1 ring-amber-500/20" : "bg-red-500/10 text-red-400 ring-1 ring-red-500/20" }`}> {occupancy}% full
{/* Name + address */}

{property.name}

{property.address_line1 ? `${property.address_line1}, ` : ""}{property.city}{property.state ? `, ${property.state}` : ""}
{/* Occupancy bar */}
{occupied} of {totalUnits} units occupied {totalUnits - occupied} vacant
{/* Stats */}

Collected / mo

{formatCurrency(monthlyRent)}

Potential / mo

{formatCurrency(potential)}

{/* Hover arrow */}
View details
) })}
)}
) }