// routes/tenant/dashboard/index.tsx import { define } from "../../../utils.ts"; import { getUserById } from "../../../services/userService.ts"; import { getLeasesByTenant } from "../../../services/leaseService.ts"; import { getMessagesByUser } from "../../../services/messageService.ts"; import { getMaintenanceRequestsByUser } from "../../../services/maintenanceRequestService.ts"; import { getPaymentTransactionsByUser } from "../../../services/paymentService.ts"; import { getPropertyById } from "../../../services/propertyService.ts"; import { getTenantInvitesByEmail } from "../../../services/tenantInviteService.ts"; import { page } from "fresh"; import { Lease, MaintenanceRequest, Message, PaymentTransaction, Property, TenantInvite, User, } from "../../../db/interfaces.ts"; type InviteNotice = { id: string; propertyId?: string | null; propertyLabel: string; propertyAddress: string; inviteUrl: string; }; function buildPropertyLabel(property: Property | null) { return property?.title || property?.address_1 || "Property Invite"; } function buildPropertyAddress(property: Property | null) { if (!property) return ""; return [ property.address_1, property.address_2, property.city, property.state, property.zip, ].filter(Boolean).join(", "); } async function buildInviteNotices( invites: TenantInvite[], leases: Lease[], ) { const leasePropertyIds = new Set( leases.map((lease) => lease.property_id).filter(Boolean) as string[], ); const pendingInvites = invites.filter((invite) => { const propertyId = invite.property_id || ""; return !propertyId || !leasePropertyIds.has(propertyId); }); const notices = await Promise.all(pendingInvites.map(async (invite) => { const property = invite.property_id ? await getPropertyById(invite.property_id) : null; return { id: invite.id, propertyId: invite.property_id, propertyLabel: buildPropertyLabel(property), propertyAddress: buildPropertyAddress(property), inviteUrl: `/tenant/invite/${encodeURIComponent(invite.id)}`, }; })); return notices; } export const handler = define.handlers({ async GET(ctx) { const { user_id } = ctx.state.data; try { const user = await getUserById(user_id); const leases = await getLeasesByTenant(user_id); const messages = await getMessagesByUser(user_id); const maintenanceRequests = await getMaintenanceRequestsByUser(user_id); const recentTransactions = await getPaymentTransactionsByUser(user_id); const invites = user?.email ? await getTenantInvitesByEmail(user.email) : []; const inviteNotices = await buildInviteNotices(invites, leases || []); return page({ user, leases, messages, maintenanceRequests, recentTransactions, inviteNotices, }); } catch (error) { console.error("Error loading tenant dashboard:", error); return page({ error: "Failed to load dashboard data", }); } }, }); export default define.page(function TenantDashboard({ data }) { const { user, leases, messages, maintenanceRequests, recentTransactions, inviteNotices, error, } = data as { user: User; leases: Lease[]; messages: Message[]; maintenanceRequests: MaintenanceRequest[]; recentTransactions: PaymentTransaction[]; inviteNotices?: InviteNotice[]; error?: string; }; if (error) { return (

Dashboard Error

{error}

); } const currentLease = leases?.[0]; // Assuming tenant has one active lease const unreadMessageCount = messages?.filter((message) => !message.is_read) .length || 0; const openRequestCount = maintenanceRequests?.filter((request) => request.status === "open" || request.status === "in_progress" ).length || 0; const inviteCount = inviteNotices?.length || 0; const hasInvites = inviteCount > 0; const notificationTotal = unreadMessageCount + openRequestCount + inviteCount; return (
{/* Header */}

Tenant Portal

Welcome back, {user?.name}!

{/* Notifications */}

Notification Center

Your invites, updates, and action items in one place.

{notificationTotal} total notifications
{hasInvites && ( )}

Invites

Pending property invitations

{inviteCount}
{hasInvites ? ( inviteNotices?.slice(0, 2).map((invite) => (

{invite.propertyLabel}

{invite.propertyAddress && (

{invite.propertyAddress}

)}
Review
)) ) : (

You have no pending invites.

)}

Messages

Unread landlord updates

{unreadMessageCount}

Maintenance

Open or active requests

{openRequestCount}
{/* Property Overview */} {currentLease && (

Your Rental Property

Address
{currentLease.property_address || "Property Address"}
Monthly Rent
${currentLease.rent?.toLocaleString() || "N/A"}
Lease End Date
{currentLease.end_date ? new Date(currentLease.end_date).toLocaleDateString() : "N/A"}
Status
{currentLease.lease_status || "active"}
)} {/* Quick Actions */}
{/* Recent Messages */}

Recent Messages

View all
{messages && messages.length > 0 ? ( messages.slice(0, 3).map((message) => (
L

Landlord

{message.subject}

{new Date(message.created_on as number) .toLocaleDateString()}

{!message.is_read && (
)}
)) ) : (

No messages yet

)}
{/* Maintenance Requests */}

Maintenance Requests

View all
{maintenanceRequests && maintenanceRequests.length > 0 ? ( maintenanceRequests.slice(0, 3).map((request) => (

{request.request_type}

Priority: {request.priority}

{new Date(request.created_on as number) .toLocaleDateString()}

{request.status?.replace("_", " ")}
)) ) : (

No maintenance requests

Submit a request
)}
{/* Recent Payments */}

Recent Payments

View all
{recentTransactions && recentTransactions.length > 0 ? ( recentTransactions.map((transaction) => ( )) ) : ( )}
Date Description Amount Status
{new Date(transaction.created_on as number) .toLocaleDateString()} {transaction.payment_method} ${transaction.amount?.toLocaleString()} {transaction.transaction_status}
No payment history yet
); });