/* ======================================================================
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. : (
| Property | Submitted | Move-in | Income | Status | |
{apps.map(a => { const p = db.properties.find(x => x.id === a.propertyId); return (
setSel(a.id)}>
{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
| Type | Property | Date | Method | Amount | Status |
{pays.map(p => { const prop = db.properties.find(x => x.id === p.propertyId); return (
{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.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} />
Add photosDrag & drop or click to upload (optional)
);
}
/* ---------------- Router ---------------- */
function TenantScreen({ view, params, app }) {
switch (view) {
case "dashboard": return ;
case "applications": return ;
case "inspections": return ;
case "payments": return ;
case "maintenance": return ;
case "book-inspection": return ;
case "apply": return ;
case "onboarding": return ;
default: return ;
}
}
window.KOREALM.roles.tenant = { nav: tenantNav, Screen: TenantScreen };