integratedhub/islands/LandlordPendingApprovals.tsx

221 lines
7.3 KiB
TypeScript

import { useState } from "preact/hooks";
import type { ConfirmAction } from "./LeaseApprovalConfirmDialog.tsx";
import LeaseApprovalConfirmDialog from "./LeaseApprovalConfirmDialog.tsx";
export interface PendingApprovalItem {
id: string;
property?: {
title?: string | null;
address_1?: string | null;
city?: string | null;
state?: string | null;
zip?: string | null;
monthly_rent?: number | null;
bedrooms?: number | null;
bathrooms?: number | null;
} | null;
property_address?: string | null;
tenant?: { name?: string | null; email?: string | null } | null;
approval_requested_at?: number | null;
}
interface LandlordPendingApprovalsProps {
approvals: PendingApprovalItem[];
}
function formatAddress(prop: PendingApprovalItem["property"]): string | null {
if (!prop) return null;
const parts = [
prop.address_1,
[prop.city, prop.state].filter(Boolean).join(", "),
prop.zip,
].filter(Boolean);
return parts.length > 0 ? parts.join(", ") : null;
}
function formatRentBeds(prop: PendingApprovalItem["property"]): string | null {
if (!prop) return null;
const hasRent = prop.monthly_rent != null;
const hasBeds = prop.bedrooms != null || prop.bathrooms != null;
if (!hasRent && !hasBeds) return null;
const rent = hasRent
? `$${Number(prop.monthly_rent).toLocaleString()}/mo`
: null;
const beds = hasBeds
? `${prop.bedrooms ?? "—"} bed, ${prop.bathrooms ?? "—"} bath`
: null;
return [rent, beds].filter(Boolean).join(" · ");
}
export default function LandlordPendingApprovals({
approvals,
}: LandlordPendingApprovalsProps) {
const [confirmState, setConfirmState] = useState<
{
action: ConfirmAction;
leaseId: string;
} | null
>(null);
const [submitting, setSubmitting] = useState(false);
const handleApproveClick = (e: Event, leaseId: string) => {
e.preventDefault();
setConfirmState({ action: "approve", leaseId });
};
const handleRejectClick = (e: Event, leaseId: string) => {
e.preventDefault();
setConfirmState({ action: "reject", leaseId });
};
const handleConfirm = async () => {
if (!confirmState) return;
setSubmitting(true);
const url = confirmState.action === "approve"
? "/api/lease/approve"
: "/api/lease/reject";
const body = confirmState.action === "reject"
? { lease_id: confirmState.leaseId, rejection_reason: "" }
: { lease_id: confirmState.leaseId };
try {
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (res.redirected) {
window.location.href = res.url;
return;
}
if (res.ok) {
window.location.reload();
return;
}
} finally {
setSubmitting(false);
setConfirmState(null);
}
};
const handleCancel = () => {
if (!submitting) setConfirmState(null);
};
return (
<>
<div class="dashboard-card dashboard-card--no-overflow">
<div class="dashboard-card-body">
<div class="dashboard-section-header">
<h3 class="dashboard-section-title">
Pending Lease Approvals
</h3>
<a href="/landlord/approvals" class="dashboard-section-link">
View all
</a>
</div>
<div class="dashboard-list-spaced">
{approvals && approvals.length > 0
? (
approvals.slice(0, 3).map((approval) => {
const prop = approval.property;
const addressLine = formatAddress(prop);
const rentBeds = formatRentBeds(prop);
return (
<div
key={approval.id}
class="dashboard-row dashboard-row--blue"
>
<div class="dashboard-approval-details">
<p class="dashboard-row-label">
{prop?.title || approval.property_address ||
"Property"}
</p>
{addressLine && (
<p class="dashboard-row-muted">{addressLine}</p>
)}
{rentBeds && (
<p class="dashboard-row-meta">{rentBeds}</p>
)}
<p class="dashboard-row-muted">
Tenant: {approval.tenant?.name ||
approval.tenant?.email || "Unknown"}
</p>
<p class="dashboard-row-meta">
Requested {approval.approval_requested_at
? new Date(
approval.approval_requested_at as number,
)
.toLocaleDateString()
: "Unknown date"}
</p>
</div>
<div class="dashboard-actions-inline">
<form
method="POST"
action="/api/lease/approve"
onSubmit={(e) => {
e.preventDefault();
handleApproveClick(e, approval.id);
}}
>
<input
type="hidden"
name="lease_id"
value={approval.id}
/>
<button
type="submit"
class="dashboard-badge dashboard-badge--green"
disabled={submitting}
>
Approve
</button>
</form>
<form
method="POST"
action="/api/lease/reject"
onSubmit={(e) => {
e.preventDefault();
handleRejectClick(e, approval.id);
}}
>
<input
type="hidden"
name="lease_id"
value={approval.id}
/>
<button
type="submit"
class="dashboard-badge dashboard-badge--red"
disabled={submitting}
>
Reject
</button>
</form>
</div>
</div>
);
})
)
: (
<div class="dashboard-empty">
<p class="dashboard-empty-text">
No pending lease approvals
</p>
</div>
)}
</div>
</div>
</div>
<LeaseApprovalConfirmDialog
open={confirmState !== null}
action={confirmState?.action ?? "approve"}
leaseId={confirmState?.leaseId ?? ""}
onConfirm={handleConfirm}
onCancel={handleCancel}
/>
</>
);
}