"use client" import { useState, useMemo } from "react" import Link from "next/link" import { ChevronLeft, ChevronRight, ArrowUpDown, ArrowUp, ArrowDown, Check, Loader2 } from "lucide-react" import { RentStatusBadge } from "@/components/dashboard/rent-status-badge" import { RentActions } from "@/components/forms/rent-actions" import { RentReceiptButton } from "@/components/forms/rent-receipt-button" import { LateNoticeButton } from "@/components/forms/late-notice-button" import { formatCurrency, formatDate } from "@/lib/utils" import { cn } from "@/lib/utils" import { toast } from "sonner" function monthLabel(d: Date) { return d.toLocaleDateString("en-US", { month: "long", year: "numeric" }) } const STATUSES = ["pending", "paid", "overdue"] as const type Status = typeof STATUSES[number] // Inline status picker โ€” click badge to cycle through statuses function InlineStatusEdit({ payment }: { payment: any }) { const [status, setStatus] = useState(payment.status) const [open, setOpen] = useState(false) const [saving, setSaving] = useState(false) async function updateStatus(next: Status) { if (next === status) { setOpen(false); return } setSaving(true) setOpen(false) const res = await fetch(`/api/rent/${payment.id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ status: next }), }) setSaving(false) if (res.ok) { setStatus(next) toast.success("Status updated") } else { toast.error("Failed to update status") } } if (saving) return return (
{open && (
{STATUSES.map((s) => ( ))}
)}
) } type SortKey = "tenant" | "due_date" | "amount" | "status" type SortDir = "asc" | "desc" function SortTh({ label, col, active, dir, onClick }: { label: string; col: SortKey; active: SortKey; dir: SortDir; onClick: () => void }) { return ( {label} {active !== col ? : dir === "asc" ? : } ) } export function RentTable({ payments }: { payments: any[] }) { const now = new Date() const [year, setYear] = useState(now.getFullYear()) const [month, setMonth] = useState(now.getMonth()) const [sortKey, setSortKey] = useState("due_date") const [sortDir, setSortDir] = useState("asc") function prevMonth() { if (month === 0) { setMonth(11); setYear((y) => y - 1) } else setMonth((m) => m - 1) } function nextMonth() { if (month === 11) { setMonth(0); setYear((y) => y + 1) } else setMonth((m) => m + 1) } function toggleSort(key: SortKey) { if (sortKey === key) setSortDir((d) => (d === "asc" ? "desc" : "asc")) else { setSortKey(key); setSortDir("asc") } } const filtered = useMemo(() => payments .filter((p) => { const d = new Date(p.due_date) return d.getFullYear() === year && d.getMonth() === month }) .sort((a, b) => { let av: string | number = "" let bv: string | number = "" if (sortKey === "tenant") { av = `${a.tenant?.first_name} ${a.tenant?.last_name}`; bv = `${b.tenant?.first_name} ${b.tenant?.last_name}` } if (sortKey === "due_date") { av = a.due_date ?? ""; bv = b.due_date ?? "" } if (sortKey === "amount") { av = Number(a.amount); bv = Number(b.amount) } if (sortKey === "status") { av = a.status ?? ""; bv = b.status ?? "" } if (av < bv) return sortDir === "asc" ? -1 : 1 if (av > bv) return sortDir === "asc" ? 1 : -1 return 0 }), [payments, year, month, sortKey, sortDir] ) const stats = useMemo(() => ({ collected: filtered.filter((p) => p.status === "paid").reduce((s, p) => s + Number(p.amount), 0), pending: filtered.filter((p) => p.status === "pending").reduce((s, p) => s + Number(p.amount), 0), overdue: filtered.filter((p) => p.status === "overdue").reduce((s, p) => s + Number(p.amount), 0), }), [filtered]) const isCurrentMonth = year === now.getFullYear() && month === now.getMonth() return (
{/* Month navigator */}
{monthLabel(new Date(year, month, 1))} {!isCurrentMonth && ( )}

{filtered.length} record{filtered.length !== 1 ? "s" : ""}

{/* Stats */}
{[ { label: "Collected", value: stats.collected, color: "text-emerald-400", bar: "bg-emerald-500" }, { label: "Pending", value: stats.pending, color: "text-amber-400", bar: "bg-amber-500" }, { label: "Overdue", value: stats.overdue, color: "text-red-400", bar: "bg-red-500" }, ].map((s) => (

{s.label}

{formatCurrency(s.value)}

0 ? `${Math.round((s.value / (stats.collected + stats.pending + stats.overdue || 1)) * 100)}%` : "0%" }} />
))}
{/* Table */} {filtered.length === 0 ? (

No payments in {monthLabel(new Date(year, month, 1))}

) : ( <>
toggleSort("tenant")} /> toggleSort("due_date")} /> toggleSort("amount")} /> toggleSort("status")} /> {filtered.map((p) => ( ))}
Property / Unit
{p.tenant?.first_name} {p.tenant?.last_name}

{p.property?.name}

Unit {p.unit?.unit_number ?? "โ€”"}

{formatDate(p.due_date)}

{p.paid_date &&

Paid {formatDate(p.paid_date)}

}

{formatCurrency(p.amount)}

{/* Mobile cards */}
{filtered.map((p) => (
{p.tenant?.first_name} {p.tenant?.last_name}

{p.property?.name} ยท Unit {p.unit?.unit_number ?? "โ€”"}

Due {formatDate(p.due_date)}

{formatCurrency(p.amount)}

))}
)}
) }