import { and, desc, eq, gte, inArray, lte, asc } from "drizzle-orm" import { db } from "@/lib/db" import { properties, units, rent_payments, maintenance_requests, leases, expenses } from "@/lib/db/schema" import type { DashboardStats } from "@/types" export async function getDashboardStats(userId: string): Promise { // Properties + units const propertyRows = await db .select({ id: properties.id }) .from(properties) .where(eq(properties.user_id, userId)) const totalProperties = propertyRows.length let totalUnits = 0, occupiedUnits = 0, vacantUnits = 0 if (totalProperties > 0) { const unitRows = await db .select({ status: units.status }) .from(units) .where(eq(units.user_id, userId)) totalUnits = unitRows.length occupiedUnits = unitRows.filter((u) => u.status === "occupied").length vacantUnits = unitRows.filter((u) => u.status === "vacant").length } // Rent this month const now = new Date() const monthStart = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10) const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString().slice(0, 10) const lastMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1).toISOString().slice(0, 10) const lastMonthEnd = new Date(now.getFullYear(), now.getMonth(), 0).toISOString().slice(0, 10) const [rentPayments, lastMonthPayments] = await Promise.all([ db .select({ amount: rent_payments.amount, status: rent_payments.status }) .from(rent_payments) .where( and( eq(rent_payments.user_id, userId), gte(rent_payments.due_date, monthStart), lte(rent_payments.due_date, monthEnd) ) ), db .select({ amount: rent_payments.amount, status: rent_payments.status }) .from(rent_payments) .where( and( eq(rent_payments.user_id, userId), gte(rent_payments.due_date, lastMonthStart), lte(rent_payments.due_date, lastMonthEnd), eq(rent_payments.status, "paid") ) ), ]) const rentCollectedThisMonth = rentPayments .filter((p) => p.status === "paid") .reduce((sum, p) => sum + Number(p.amount), 0) const rentCollectedLastMonth = lastMonthPayments.reduce((sum, p) => sum + Number(p.amount), 0) const rentPendingThisMonth = rentPayments .filter((p) => p.status === "pending") .reduce((sum, p) => sum + Number(p.amount), 0) // Overdue rent (all time) const overduePayments = await db .select({ amount: rent_payments.amount }) .from(rent_payments) .where(and(eq(rent_payments.user_id, userId), eq(rent_payments.status, "overdue"))) const rentOverdue = overduePayments.reduce((sum, p) => sum + Number(p.amount), 0) // Open maintenance const openMaintenance = await db .select({ id: maintenance_requests.id }) .from(maintenance_requests) .where( and( eq(maintenance_requests.user_id, userId), inArray(maintenance_requests.status, ["open", "in_progress"]) ) ) // Expiring leases (within 60 days) const in60Days = new Date() in60Days.setDate(in60Days.getDate() + 60) const expiring = await db .select({ id: leases.id }) .from(leases) .where( and( eq(leases.user_id, userId), eq(leases.status, "active"), lte(leases.lease_end, in60Days.toISOString().slice(0, 10)) ) ) const occupancyRate = totalUnits > 0 ? Math.round((occupiedUnits / totalUnits) * 100) : 0 return { totalProperties, totalUnits, occupiedUnits, vacantUnits, occupancyRate, rentCollectedThisMonth, rentCollectedLastMonth, rentPendingThisMonth, rentOverdue, openMaintenanceRequests: openMaintenance.length, expiringLeases: expiring.length, } } export async function getRecentRentPayments(userId: string, limit = 5) { return db.query.rent_payments.findMany({ where: eq(rent_payments.user_id, userId), with: { tenant: { columns: { first_name: true, last_name: true } }, property: { columns: { name: true } }, }, orderBy: desc(rent_payments.created_at), limit, }) } export async function getOpenMaintenanceRequests(userId: string, limit = 5) { return db.query.maintenance_requests.findMany({ where: and( eq(maintenance_requests.user_id, userId), inArray(maintenance_requests.status, ["open", "in_progress"]) ), with: { property: { columns: { name: true } }, tenant: { columns: { first_name: true, last_name: true } }, }, orderBy: desc(maintenance_requests.created_at), limit, }) } export async function getMonthlyRevenue(userId: string) { const sixMonthsAgo = new Date() sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 5) sixMonthsAgo.setDate(1) const data = await db .select({ amount: rent_payments.amount, due_date: rent_payments.due_date }) .from(rent_payments) .where( and( eq(rent_payments.user_id, userId), eq(rent_payments.status, "paid"), gte(rent_payments.due_date, sixMonthsAgo.toISOString().slice(0, 10)) ) ) const months: Record = {} for (let i = 5; i >= 0; i--) { const d = new Date() d.setMonth(d.getMonth() - i) const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}` months[key] = 0 } data.forEach((p) => { const key = p.due_date.slice(0, 7) if (key in months) months[key] += Number(p.amount) }) return Object.entries(months).map(([month, amount]) => ({ month, label: new Date(month + "-01").toLocaleDateString("en-US", { month: "short" }), amount, })) } export async function getExpenseBreakdown(userId: string) { const sixMonthsAgo = new Date() sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 5) sixMonthsAgo.setDate(1) const data = await db .select({ category: expenses.category, amount: expenses.amount }) .from(expenses) .where( and( eq(expenses.user_id, userId), gte(expenses.expense_date, sixMonthsAgo.toISOString().slice(0, 10)) ) ) const totals: Record = {} data.forEach((e) => { totals[e.category] = (totals[e.category] ?? 0) + Number(e.amount) }) return Object.entries(totals) .map(([category, amount]) => ({ category, amount })) .sort((a, b) => b.amount - a.amount) } export async function getExpiringLeases(userId: string, limit = 5) { const in60Days = new Date() in60Days.setDate(in60Days.getDate() + 60) return db.query.leases.findMany({ where: and( eq(leases.user_id, userId), eq(leases.status, "active"), lte(leases.lease_end, in60Days.toISOString().slice(0, 10)) ), with: { tenant: { columns: { first_name: true, last_name: true } }, property: { columns: { name: true } }, }, orderBy: asc(leases.lease_end), limit, }) }