/* ====================================================================== KOREALM — Tenant workspace ====================================================================== */ function tenantNav(db, me) { const apps = db.applications.filter(a => a.tenantId === me.id); const insp = db.inspections.filter(i => i.tenantId === me.id && i.status === "requested"); const pendingRent = db.payments.filter(p => p.tenantId === me.id && p.type === "rent" && p.status === "pending"); const maint = db.maintenance.filter(m => m.tenantId === me.id && m.status !== "resolved"); return [ { view: "dashboard", label: "Dashboard", icon: "home" }, { view: "applications", label: "Applications", icon: "doc", count: apps.filter(a => a.status === "pending").length }, { view: "inspections", label: "Inspections", icon: "calendar", count: insp.length }, { view: "payments", label: "Payments", icon: "card", count: pendingRent.length }, { view: "maintenance", label: "Maintenance", icon: "wrench", count: maint.length }, ]; } /* ---------------- Dashboard ---------------- */ function TenantDashboard({ app }) { const { db, session, go, user } = app; const me = user(session.tenantId); const apps = db.applications.filter(a => a.tenantId === me.id); const approved = apps.find(a => a.status === "approved" && !db.payments.some(p => p.tenantId === me.id && p.type === "onboarding" && p.propertyId === a.propertyId)); const insp = db.inspections.filter(i => i.tenantId === me.id); const upcoming = insp.filter(i => ["requested", "confirmed"].includes(i.status)).sort((a, b) => new Date(a.slot.split("|")[0]) - new Date(b.slot.split("|")[0])); const nextRent = db.payments.filter(p => p.tenantId === me.id && p.type === "rent" && p.status === "pending")[0]; const openMaint = db.maintenance.filter(m => m.tenantId === me.id && m.status !== "resolved"); const leased = db.properties.find(p => p.status === "leased" && db.payments.some(pm => pm.tenantId === me.id && pm.propertyId === p.id)); return (
app.setRole("public")}>Find a home} /> {approved && (

Your application was approved

{user(approved.tenantId).name.split(" ")[0]}, complete your move-in payment for {db.properties.find(p => p.id === approved.propertyId)?.title}.

)}
a.status === "pending").length} icon="doc" />

Upcoming inspections

{upcoming.length === 0 ? Find a home and book a viewing. : (
{upcoming.slice(0, 3).map(i => { const p = db.properties.find(x => x.id === i.propertyId); const [dt, tm] = i.slot.split("|"); return (
{new Date(dt).toLocaleDateString("en-US", { month: "short" })}{new Date(dt).getDate()}
{p?.title}
{tm} · {p?.neighborhood}
); })}
)}

Application status

{apps.length === 0 ? Apply to a home you love. : (
{apps.slice(0, 3).map(a => { const p = db.properties.find(x => x.id === a.propertyId); return (
{p?.title}
Applied {window.fmt.fmtShort(a.submittedDate)}
); })}
)}
{leased && ( Your home

{leased.title}

{leased.address}
{window.fmt.money(leased.price)}/moLease active
)}
); } /* ---------------- Applications ---------------- */ function TenantApplications({ app }) { const { db, session, go, user } = app; const me = user(session.tenantId); const apps = db.applications.filter(a => a.tenantId === me.id).sort((a, b) => new Date(b.submittedDate) - new Date(a.submittedDate)); const [sel, setSel] = useState(null); const selApp = apps.find(a => a.id === sel); return (
{apps.length === 0 ? app.setRole("public")}>Browse homes}>When you apply to a home, it'll show up here. : ( {apps.map(a => { const p = db.properties.find(x => x.id === a.propertyId); return ( setSel(a.id)}> ); })}
PropertySubmittedMove-inIncomeStatus
{p?.title}
{p?.neighborhood}
{window.fmt.fmtDate(a.submittedDate)} {a.moveIn ? window.fmt.fmtShort(a.moveIn) : "—"} {window.fmt.money(a.income)}
)} {selApp && (() => { const p = db.properties.find(x => x.id === selApp.propertyId); const paid = db.payments.some(pm => pm.tenantId === me.id && pm.type === "onboarding" && pm.propertyId === selApp.propertyId); return ( setSel(null)} footer={selApp.status === "approved" && !paid ? : }>
Submitted {window.fmt.fmtDate(selApp.submittedDate)}
{selApp.status === "approved" &&
Approved on {window.fmt.fmtDate(selApp.decisionDate)}. {paid ? "Move-in paid." : "Complete your move-in payment."}
} {selApp.status === "rejected" &&
{selApp.reason || "Not approved."}
}
{[["Property", p?.title], ["Rent", window.fmt.money(p?.price)], ["Occupants", selApp.occupants], ["Move-in", selApp.moveIn ? window.fmt.fmtDate(selApp.moveIn) : "—"], ["Pets", selApp.pets], ["Employment", selApp.employment], ["Monthly income", window.fmt.money(selApp.income)], ["Income-to-rent", (selApp.income / p?.price).toFixed(1) + "×"]].map(([k, v]) =>
{k}{v}
)}
); })()}
); } /* ---------------- Inspections ---------------- */ function TenantInspections({ app }) { const { db, session, user } = app; const me = user(session.tenantId); const insp = db.inspections.filter(i => i.tenantId === me.id).sort((a, b) => new Date(b.requestedDate) - new Date(a.requestedDate)); return (
app.setRole("public")}>Find homes} /> {insp.length === 0 ? Book a viewing from any listing. : (
{insp.map(i => { const p = db.properties.find(x => x.id === i.propertyId); const [dt, tm] = i.slot.split("|"); return (
Requested {window.fmt.fmtShort(i.requestedDate)}
{new Date(dt).toLocaleDateString("en-US", { month: "short" })}{new Date(dt).getDate()}

{p?.title}

{p?.address}
{tm}
{i.status === "confirmed" &&
Confirmed by Korealm — see you there!
} {i.status === "requested" &&
Awaiting confirmation from Korealm
}
); })}
)}
); } /* ---------------- Payments ---------------- */ function TenantPayments({ app }) { const { db, session, user, actions, toast } = app; const me = user(session.tenantId); const pays = db.payments.filter(p => p.tenantId === me.id).sort((a, b) => new Date(b.date) - new Date(a.date)); const pending = pays.filter(p => p.status === "pending"); const [payModal, setPayModal] = useState(null); const totalPaid = pays.filter(p => p.status === "paid").reduce((s, p) => s + p.amount, 0); return (
s + p.amount, 0)) + " due" : "All settled"} />
{pending.length > 0 && (

Rent due — {pending[0].period || window.fmt.fmtDate(pending[0].date)}

{window.fmt.money(pending[0].amount)} for your home.

)}

Transaction history

{pays.map(p => { const prop = db.properties.find(x => x.id === p.propertyId); return ( ); })}
TypePropertyDateMethodAmountStatus
{p.type}{p.period ? {p.period} : null}
{prop?.title} {window.fmt.fmtDate(p.date)} {p.method} {window.fmt.money(p.amount)} {p.status === "pending" ? : }
{payModal && setPayModal(null)} onPaid={() => { actions.payRent(payModal.id, "Card ····4242"); toast("Rent paid — thank you!", "ok"); setPayModal(null); }} />}
); } function PayRentModal({ payment, onClose, onPaid }) { const { property } = useApp(); const [proc, setProc] = useState(false); const p = property(payment.propertyId); return ( }>
Monthly rent{window.fmt.money(payment.amount)}
Total{window.fmt.money(payment.amount)}
Visa ···· 4242
); } /* ---------------- Maintenance ---------------- */ function TenantMaintenance({ app }) { const { db, session, user, actions, toast } = app; const me = user(session.tenantId); const reqs = db.maintenance.filter(m => m.tenantId === me.id).sort((a, b) => new Date(b.createdDate) - new Date(a.createdDate)); const leased = db.properties.filter(p => db.payments.some(pm => pm.tenantId === me.id && pm.propertyId === p.id)) ; const [open, setOpen] = useState(false); return (
setOpen(true)}>New request} /> {reqs.length === 0 ? setOpen(true)}>Report an issue}>Report a problem and we'll dispatch a vendor. : (
{reqs.map(m => { const p = db.properties.find(x => x.id === m.propertyId); const v = db.vendors.find(x => x.id === m.vendorId); return (

{m.title}

{m.category} · {p?.title} · {window.fmt.fmtShort(m.createdDate)}

{m.description}

{v &&
Assigned to {v.name} · {v.trade}
}
); })}
)} {open && setOpen(false)} onCreate={(data) => { actions.addMaintenance({ tenantId: me.id, ...data }); toast("Maintenance request submitted", "ok"); setOpen(false); }} />}
); } function NewMaintenance({ properties, onClose, onCreate }) { const [d, setD] = useState({ propertyId: properties[0]?.id, category: "Plumbing", priority: "medium", title: "", description: "" }); const [err, setErr] = useState({}); const submit = () => { const e = {}; if (!d.title.trim()) e.title = "Add a short title"; if (!d.description.trim()) e.description = "Describe the issue"; setErr(e); if (Object.keys(e).length) return; onCreate(d); }; return ( }>
setD({ ...d, title: e.target.value })} error={err.title} />