/* ====================================================================== KOREALM — Admin workspace Dashboard · Inspections · Applications · KYC · Rent & commission · Maintenance ====================================================================== */ function adminNav(db) { const insp = db.inspections.filter(i => i.status === "requested").length; const apps = db.applications.filter(a => a.status === "pending").length; const kyc = db.kyc.filter(k => k.status === "submitted").length; const maint = db.maintenance.filter(m => m.status === "open").length; return [ { view: "dashboard", label: "Overview", icon: "grid" }, { view: "inspections", label: "Inspections", icon: "calendar", count: insp }, { view: "applications", label: "Applications", icon: "doc", count: apps }, { view: "kyc", label: "Landlord KYC", icon: "shield", count: kyc }, { view: "rent", label: "Rent & commission", icon: "wallet" }, { view: "maintenance", label: "Maintenance", icon: "wrench", count: maint }, ]; } /* ==================================================================== DASHBOARD ==================================================================== */ function AdminDashboard({ app }) { const { db, go, user } = app; const reqInsp = db.inspections.filter(i => i.status === "requested"); const pendApps = db.applications.filter(a => a.status === "pending"); const subKyc = db.kyc.filter(k => k.status === "submitted"); const openMaint = db.maintenance.filter(m => m.status === "open"); const paidRent = db.payments.filter(p => p.type === "rent" && p.status === "paid"); const commission = db.payments.filter(p => p.status === "paid").reduce((s, p) => s + p.commissionAmount, 0); const gmv = db.payments.filter(p => p.status === "paid").reduce((s, p) => s + p.amount, 0); const leased = db.properties.filter(p => p.status === "leased").length; const queues = [ { label: "Inspections to confirm", count: reqInsp.length, icon: "calendar", view: "inspections", tone: "warning" }, { label: "Applications to review", count: pendApps.length, icon: "doc", view: "applications", tone: "warning" }, { label: "KYC to verify", count: subKyc.length, icon: "shield", view: "kyc", tone: "info" }, { label: "Maintenance to assign", count: openMaint.length, icon: "wrench", view: "maintenance", tone: "danger" }, ]; return (
k.status === "verified").length} icon="shield" />

Action queues

{queues.map(q => ( go(q.view)} className="anim-rise">
{q.count > 0 ? {q.count} waiting : Clear}
{q.count} {q.label}
Review
))}

Latest applications

{db.applications.slice(0, 4).map(a => { const p = db.properties.find(x => x.id === a.propertyId); const t = user(a.tenantId); return (
{t.name}
{p?.title}
); })}

Recent payments

{db.payments.filter(p => p.status === "paid").slice(0, 4).map(p => { const t = user(p.tenantId); return (
{p.type} · {t?.name.split(" ")[0]}
+{window.fmt.money(p.commissionAmount)} commission
{window.fmt.money(p.amount)}
); })}
); } /* ==================================================================== INSPECTIONS — confirm / decline / reschedule ==================================================================== */ function AdminInspections({ app }) { const { db, actions, user, toast } = app; const [tab, setTab] = useState("requested"); const [resch, setResch] = useState(null); const all = db.inspections.slice().sort((a, b) => new Date(b.requestedDate) - new Date(a.requestedDate)); const counts = { requested: all.filter(i => i.status === "requested").length, confirmed: all.filter(i => i.status === "confirmed").length, completed: all.filter(i => i.status === "completed").length, }; const list = all.filter(i => tab === "all" ? true : i.status === tab); return (
{list.length === 0 ? No inspections in this view. : list.map(i => { const p = db.properties.find(x => x.id === i.propertyId); const t = user(i.tenantId); const ll = user(p?.landlordId); const [dt, tm] = i.slot.split("|"); return (
{new Date(dt).toLocaleDateString("en-US", { month: "short" })}{new Date(dt).getDate()}

{p?.title}

{tm}
Tenant {t?.name} · Landlord {ll?.name} · requested {window.fmt.fmtShort(i.requestedDate)}
{i.notes &&
"{i.notes}"
}
{i.status === "requested" && <> } {i.status === "confirmed" && }
); })}
{resch && setResch(null)} onSave={(slot) => { actions.rescheduleInspection(resch.id, slot); toast("Rescheduled & confirmed — tenant notified", "ok"); setResch(null); }} />}
); } function RescheduleModal({ inspection, onClose, onSave }) { const [date, setDate] = useState(null); const [time, setTime] = useState(null); const days = useMemo(() => Array.from({ length: 6 }, (_, i) => { const d = window.fmt.today(); d.setDate(d.getDate() + i + 1); return d; }), []); const times = ["9:30 AM", "11:00 AM", "1:00 PM", "3:00 PM", "4:30 PM"]; return ( }> Date
{days.map((d, i) => )}
Time
{times.map(t => )}
); } /* ==================================================================== APPLICATIONS — approve / reject ==================================================================== */ function AdminApplications({ app }) { const { db, actions, user, toast } = app; const [tab, setTab] = useState("pending"); const [sel, setSel] = useState(null); const [reject, setReject] = useState(null); const all = db.applications.slice().sort((a, b) => new Date(b.submittedDate) - new Date(a.submittedDate)); const counts = { pending: all.filter(a => a.status === "pending").length, approved: all.filter(a => a.status === "approved").length, rejected: all.filter(a => a.status === "rejected").length }; const list = all.filter(a => tab === "all" ? true : a.status === tab); const selApp = all.find(a => a.id === sel); return (
{list.length === 0 ? Nothing in this view. : ( {list.map(a => { const p = db.properties.find(x => x.id === a.propertyId); const t = user(a.tenantId); const ratio = (a.income / p?.price); return ( setSel(a.id)}> ); })}
ApplicantPropertyIncomeRatioMove-inStatus
{t.name}
{a.employment}
{p?.title} {window.fmt.money(a.income)} = 2.5 ? "var(--success)" : "var(--warning)", fontWeight: 600 }}>{ratio.toFixed(1)}× {a.moveIn ? window.fmt.fmtShort(a.moveIn) : "—"}
)} {selApp && (() => { const p = db.properties.find(x => x.id === selApp.propertyId); const t = user(selApp.tenantId); const ratio = selApp.income / p?.price; return ( setSel(null)} footer={selApp.status === "pending" ? <> : }>

{t.name}

{t.email} · {t.phone}
= 2.5 ? "ok" : "warn")} style={{ marginBottom: 16 }}> = 2.5 ? "checkCircle" : "alert"} size={16} /> Income-to-rent ratio {ratio.toFixed(1)}× — {ratio >= 2.5 ? "meets" : "below"} the 2.5× requirement
Applicant details
{[["Property", p?.title], ["Monthly rent", window.fmt.money(p?.price)], ["Employment", selApp.employment], ["Gross income", window.fmt.money(selApp.income)], ["Occupants", selApp.occupants], ["Pets", selApp.pets], ["Move-in", selApp.moveIn ? window.fmt.fmtDate(selApp.moveIn) : "—"], ["Submitted", window.fmt.fmtDate(selApp.submittedDate)], ["Credit consent", selApp.creditConsent ? "Authorized" : "—"]].map(([k, v]) => (
{k}{v}
))}
{selApp.notes && <>Applicant note

"{selApp.notes}"

} {selApp.status === "rejected" && selApp.reason &&
{selApp.reason}
} {selApp.status === "approved" &&
Approved {window.fmt.fmtDate(selApp.decisionDate)} by {selApp.decisionBy}
}
); })()} {reject && setReject(null)} onConfirm={(reason) => { actions.rejectApplication(reject.id, reason); toast("Application rejected — tenant notified", "info"); setReject(null); setSel(null); }} />}
); } function RejectModal({ title, label, presets, onClose, onConfirm }) { const [reason, setReason] = useState(""); return ( }>