Files
acmcc/src/pages/OwnerProfilePage.tsx
T
admin d8465f2297 RV/Boat Lots: link rentals to owner/unit + Notice to Vacate toggle
Phase 2: rental form now has optional Owner and Unit selectors (auto-fills
renter info/unit from the chosen owner), persisting owner_id/unit_id.

Phase 3: add RvRentalVacateButton on Owner and Unit profiles — shows only
when that owner/unit has an active/vacating RV-boat rental, and toggles the
rental status between active (renting) and vacating. Active Rentals tab now
includes vacating rentals with a badge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 18:20:58 -04:00

561 lines
27 KiB
TypeScript

import { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import { supabase } from "@/integrations/supabase/client";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { Separator } from "@/components/ui/separator";
import RvRentalVacateButton from "@/components/RvRentalVacateButton";
import {
ArrowLeft, Mail, Phone, MapPin, Send, AlertTriangle, StickyNote,
Download, Filter, FileText, Shield, ClipboardCheck, MessageCircle,
FolderOpen, Activity, Calendar, DollarSign, User, Home, Clock,
ChevronRight, ExternalLink
} from "lucide-react";
import { format } from "date-fns";
import { useToast } from "@/hooks/use-toast";
import UnitLedgerView from "@/components/unit-profile/UnitLedgerView";
interface Owner {
id: string;
first_name: string;
last_name: string;
email: string | null;
phone: string | null;
street_address: string | null;
mailing_address: string | null;
property_address: string | null;
balance: number | null;
unit_id: string | null;
association_id: string;
electronic_consent: boolean;
zoho_contact_id: string | null;
created_at: string;
units?: { unit_number: string; address: string | null; status: string | null; account_number: string | null } | null;
associations?: { name: string } | null;
}
export default function OwnerProfilePage() {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { toast } = useToast();
const [owner, setOwner] = useState<Owner | null>(null);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState("ledger");
// Sub-data
const [ledger, setLedger] = useState<any[]>([]);
const [computedBalance, setComputedBalance] = useState<number | null>(null);
const [violations, setViolations] = useState<any[]>([]);
const [arcApps, setArcApps] = useState<any[]>([]);
const [requests, setRequests] = useState<any[]>([]);
const [documents, setDocuments] = useState<any[]>([]);
const [activity, setActivity] = useState<any[]>([]);
useEffect(() => {
if (id) fetchOwner();
}, [id]);
useEffect(() => {
if (owner) fetchTabData();
}, [owner, activeTab]);
const fetchOwner = async () => {
setLoading(true);
const { data, error } = await supabase
.from("owners")
.select("*, units(unit_number, address, status, account_number), associations(name)")
.eq("id", id!)
.single();
if (error) {
toast({ title: "Error", description: "Owner not found", variant: "destructive" });
navigate("/dashboard/owner-roster");
return;
}
setOwner(data as Owner);
// Compute live balance from ledger entries at the UNIT level
// This ensures co-owners see the full unit balance, not just their individual entries
const unitId = (data as Owner).unit_id;
if (unitId) {
const { data: ledgerEntries } = await supabase
.from("owner_ledger_entries")
.select("debit, credit")
.eq("unit_id", unitId);
const liveBalance = (ledgerEntries || []).reduce((acc, e) => acc + (Number(e.debit) || 0) - (Number(e.credit) || 0), 0);
setComputedBalance(liveBalance);
} else {
// Fallback: no unit linked, use owner_id
const { data: ledgerEntries } = await supabase
.from("owner_ledger_entries")
.select("debit, credit")
.eq("owner_id", id!);
const liveBalance = (ledgerEntries || []).reduce((acc, e) => acc + (Number(e.debit) || 0) - (Number(e.credit) || 0), 0);
setComputedBalance(liveBalance);
}
setLoading(false);
};
const fetchTabData = async () => {
if (!owner) return;
switch (activeTab) {
case "ledger": {
const { data } = await supabase
.from("owner_ledger_entries")
.select("*")
.eq("owner_id", owner.id)
.order("date", { ascending: false })
.limit(100);
setLedger(data || []);
break;
}
case "violations": {
// Fetch violations linked by owner_id, unit_id, or property address
const filters: string[] = [`owner_id.eq.${owner.id}`];
if (owner.unit_id) filters.push(`unit_id.eq.${owner.unit_id}`);
if (owner.property_address) filters.push(`address.eq.${owner.property_address}`);
const { data } = await supabase
.from("violations")
.select("*")
.or(filters.join(","))
.order("created_at", { ascending: false });
// Deduplicate by id
const seen = new Set<string>();
const unique = (data || []).filter(v => { if (seen.has(v.id)) return false; seen.add(v.id); return true; });
setViolations(unique);
break;
}
case "arc": {
const { data } = await supabase
.from("arc_applications")
.select("*")
.eq("owner_id", owner.id)
.order("created_at", { ascending: false });
setArcApps(data || []);
break;
}
case "requests": {
const { data } = await supabase
.from("homeowner_requests")
.select("*")
.eq("owner_id", owner.id)
.order("created_at", { ascending: false });
setRequests(data || []);
break;
}
case "documents": {
// Only show documents tied to this owner's unit
if (!owner.unit_id) {
setDocuments([]);
break;
}
const { data } = await supabase
.from("unit_documents")
.select("*")
.eq("unit_id", owner.unit_id)
.order("created_at", { ascending: false })
.limit(100);
setDocuments(data || []);
break;
}
case "activity": {
// Build activity from multiple sources
const items: any[] = [];
const { data: ledgerData } = await supabase
.from("owner_ledger_entries")
.select("id, date, description, transaction_type, debit, credit")
.eq("owner_id", owner.id)
.order("date", { ascending: false })
.limit(20);
(ledgerData || []).forEach(e => items.push({
id: e.id, type: "payment", text: e.description || e.transaction_type, date: e.date,
icon: "dollar"
}));
const violFilters: string[] = [`owner_id.eq.${owner.id}`];
if (owner.unit_id) violFilters.push(`unit_id.eq.${owner.unit_id}`);
if (owner.property_address) violFilters.push(`address.eq.${owner.property_address}`);
const { data: violData } = await supabase
.from("violations")
.select("id, created_at, violation_type, status")
.or(violFilters.join(","))
.order("created_at", { ascending: false })
.limit(10);
const violSeen = new Set<string>();
(violData || []).filter(v => { if (violSeen.has(v.id)) return false; violSeen.add(v.id); return true; }).forEach(v => items.push({
id: v.id, type: "violation", text: `Violation: ${v.violation_type || "Notice"}`, date: v.created_at,
icon: "shield"
}));
items.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
setActivity(items.slice(0, 20));
break;
}
}
};
if (loading || !owner) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary" />
</div>
);
}
const fullName = `${owner.first_name} ${owner.last_name}`;
const balance = computedBalance ?? owner.balance ?? 0;
const ownerActionParams = new URLSearchParams({
associationId: owner.association_id,
ownerId: owner.id,
name: fullName,
});
if (owner.email) ownerActionParams.set("email", owner.email);
if (owner.unit_id) ownerActionParams.set("unitId", owner.unit_id);
return (
<div className="max-w-[1440px] mx-auto p-5 space-y-5">
{/* Back nav */}
<Button variant="ghost" size="sm" className="text-[13px] text-muted-foreground hover:text-foreground gap-1.5 -ml-2 h-8" onClick={() => navigate("/dashboard/owner-roster")}>
<ArrowLeft className="w-3.5 h-3.5" /> Owner Roster
</Button>
{/* Header */}
<div className="flex flex-col md:flex-row md:items-start justify-between gap-4">
<div className="flex items-start gap-4">
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10 shrink-0">
<User className="w-5 h-5 text-primary" />
</div>
<div>
<div className="flex items-center gap-2.5">
<h1 className="text-[22px] font-semibold tracking-tight">{fullName}</h1>
<Badge variant="outline" className={`text-2xs ${balance > 0 ? "cc-badge-danger" : "cc-badge-success"}`}>
{balance > 0 ? "Balance Due" : "Current"}
</Badge>
</div>
<p className="text-[13px] text-muted-foreground mt-0.5">
{owner.property_address || owner.units?.address || "No property address"}
</p>
<div className="flex items-center gap-3 mt-1">
{owner.units?.account_number && (
<span className="text-[12px] text-muted-foreground">Acct #{owner.units.account_number}</span>
)}
<span className="text-[12px] text-muted-foreground"></span>
<span className="text-[12px] text-muted-foreground">{owner.associations?.name || "—"}</span>
</div>
</div>
</div>
{/* Balance & Actions */}
<div className="flex items-center gap-3">
<div className="text-right mr-2">
<p className="text-[11px] text-muted-foreground uppercase tracking-wide font-medium">Account Balance</p>
<p className={`text-[24px] font-semibold tracking-tight leading-none mt-0.5 ${balance > 0 ? "text-destructive" : "text-emerald-600"}`}>
${Math.abs(balance).toLocaleString("en-US", { minimumFractionDigits: 2 })}
</p>
</div>
<Separator orientation="vertical" className="h-10" />
<div className="flex items-center gap-1.5">
<Button size="sm" variant="outline" className="h-8 text-[12px] gap-1.5" onClick={() => navigate(`/dashboard/compose-email?${ownerActionParams.toString()}`)}>
<Send className="w-3.5 h-3.5" /> Message
</Button>
<Button size="sm" variant="outline" className="h-8 text-[12px] gap-1.5" onClick={() => navigate(`/dashboard/violations?new=1&${ownerActionParams.toString()}`)}>
<AlertTriangle className="w-3.5 h-3.5" /> Violation
</Button>
<Button size="sm" variant="outline" className="h-8 text-[12px] gap-1.5" onClick={() => navigate(`/dashboard/owner-updates?${ownerActionParams.toString()}`)}>
<StickyNote className="w-3.5 h-3.5" /> Note
</Button>
<RvRentalVacateButton ownerId={owner.id} />
</div>
</div>
</div>
{/* Profile Summary + Property */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Contact Info */}
<Card className="border shadow-sm">
<CardHeader className="pb-2 px-4 pt-4">
<CardTitle className="text-[15px] font-semibold flex items-center gap-2">
<Mail className="w-4 h-4 text-primary" /> Contact Information
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4 space-y-3">
<InfoRow icon={<Phone className="w-3.5 h-3.5" />} label="Phone" value={owner.phone || "—"} />
<InfoRow icon={<Mail className="w-3.5 h-3.5" />} label="Email" value={owner.email || "—"} />
<InfoRow icon={<MapPin className="w-3.5 h-3.5" />} label="Mailing Address" value={owner.mailing_address || "—"} />
<InfoRow icon={<MapPin className="w-3.5 h-3.5" />} label="Street Address" value={owner.street_address || "—"} />
</CardContent>
</Card>
{/* Property Details */}
<Card className="border shadow-sm">
<CardHeader className="pb-2 px-4 pt-4">
<CardTitle className="text-[15px] font-semibold flex items-center gap-2">
<Home className="w-4 h-4 text-primary" /> Property Details
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4 space-y-3">
<InfoRow icon={<Home className="w-3.5 h-3.5" />} label="Unit" value={owner.units?.unit_number || "—"} />
<InfoRow icon={<MapPin className="w-3.5 h-3.5" />} label="Property Address" value={owner.property_address || owner.units?.address || "—"} />
<InfoRow icon={<Shield className="w-3.5 h-3.5" />} label="Status" value={owner.units?.status || "Active"} />
<InfoRow icon={<Calendar className="w-3.5 h-3.5" />} label="Member Since" value={format(new Date(owner.created_at), "MMM d, yyyy")} />
</CardContent>
</Card>
</div>
{/* Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="bg-muted/50 h-9">
<TabsTrigger value="ledger" className="text-[13px] h-7 gap-1.5"><DollarSign className="w-3.5 h-3.5" /> Ledger</TabsTrigger>
<TabsTrigger value="violations" className="text-[13px] h-7 gap-1.5"><Shield className="w-3.5 h-3.5" /> Violations</TabsTrigger>
<TabsTrigger value="arc" className="text-[13px] h-7 gap-1.5"><ClipboardCheck className="w-3.5 h-3.5" /> ARC</TabsTrigger>
<TabsTrigger value="requests" className="text-[13px] h-7 gap-1.5"><MessageCircle className="w-3.5 h-3.5" /> Requests</TabsTrigger>
<TabsTrigger value="documents" className="text-[13px] h-7 gap-1.5"><FolderOpen className="w-3.5 h-3.5" /> Documents</TabsTrigger>
<TabsTrigger value="notes" className="text-[13px] h-7 gap-1.5"><StickyNote className="w-3.5 h-3.5" /> Notes</TabsTrigger>
<TabsTrigger value="activity" className="text-[13px] h-7 gap-1.5"><Activity className="w-3.5 h-3.5" /> Activity</TabsTrigger>
</TabsList>
{/* LEDGER */}
<TabsContent value="ledger">
{owner.unit_id ? (
<UnitLedgerView unitId={owner.unit_id} associationId={owner.association_id} />
) : (
<Card className="border shadow-sm">
<CardContent className="py-8 text-center text-muted-foreground text-sm">
No unit linked to this owner. Assign a unit to view the full ledger.
</CardContent>
</Card>
)}
</TabsContent>
{/* VIOLATIONS */}
<TabsContent value="violations">
<Card className="border shadow-sm">
<CardHeader className="px-4 pt-4 pb-2">
<CardTitle className="text-[15px] font-semibold">Violations</CardTitle>
</CardHeader>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent">
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Type</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Status</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Date Issued</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Due Date</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Stage</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4 w-8"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{violations.length === 0 ? (
<TableRow><TableCell colSpan={6} className="text-center text-[13px] text-muted-foreground py-8">No violations</TableCell></TableRow>
) : (
violations.map((v) => (
<TableRow key={v.id} className="hover:bg-muted/30 cursor-pointer" onClick={() => navigate(`/dashboard/violations`)}>
<TableCell className="text-[13px] py-2.5 px-4 font-medium">{v.violation_type || "—"}</TableCell>
<TableCell className="py-2.5 px-4">
<StatusBadge status={v.status} />
</TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{v.created_at ? format(new Date(v.created_at), "MMM d, yyyy") : "—"}</TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{v.due_date ? format(new Date(v.due_date), "MMM d, yyyy") : "—"}</TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{v.current_stage || "—"}</TableCell>
<TableCell className="py-2.5 px-4"><ChevronRight className="w-3.5 h-3.5 text-muted-foreground/40" /></TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
{/* ARC */}
<TabsContent value="arc">
<Card className="border shadow-sm">
<CardHeader className="px-4 pt-4 pb-2">
<CardTitle className="text-[15px] font-semibold">ARC Applications</CardTitle>
</CardHeader>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent">
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Title</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Type</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Status</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Submitted</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Review Date</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{arcApps.length === 0 ? (
<TableRow><TableCell colSpan={5} className="text-center text-[13px] text-muted-foreground py-8">No ARC applications</TableCell></TableRow>
) : (
arcApps.map((a) => (
<TableRow key={a.id} className="hover:bg-muted/30">
<TableCell className="text-[13px] py-2.5 px-4 font-medium">{a.title}</TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{a.project_type || "—"}</TableCell>
<TableCell className="py-2.5 px-4"><StatusBadge status={a.status} /></TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{a.submitted_date ? format(new Date(a.submitted_date), "MMM d, yyyy") : "—"}</TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{a.review_date ? format(new Date(a.review_date), "MMM d, yyyy") : "—"}</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
{/* REQUESTS */}
<TabsContent value="requests">
<Card className="border shadow-sm">
<CardHeader className="px-4 pt-4 pb-2">
<CardTitle className="text-[15px] font-semibold">Homeowner Requests</CardTitle>
</CardHeader>
<CardContent className="p-0">
<Table>
<TableHeader>
<TableRow className="hover:bg-transparent">
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Subject</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Category</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Status</TableHead>
<TableHead className="text-[11px] font-semibold uppercase tracking-wider text-muted-foreground bg-muted/50 py-2 px-4">Submitted</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{requests.length === 0 ? (
<TableRow><TableCell colSpan={4} className="text-center text-[13px] text-muted-foreground py-8">No requests</TableCell></TableRow>
) : (
requests.map((r) => (
<TableRow key={r.id} className="hover:bg-muted/30">
<TableCell className="text-[13px] py-2.5 px-4 font-medium">{r.subject || r.title || "—"}</TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{r.category || "—"}</TableCell>
<TableCell className="py-2.5 px-4"><StatusBadge status={r.status} /></TableCell>
<TableCell className="text-[13px] py-2.5 px-4">{r.created_at ? format(new Date(r.created_at), "MMM d, yyyy") : "—"}</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</CardContent>
</Card>
</TabsContent>
{/* DOCUMENTS */}
<TabsContent value="documents">
<Card className="border shadow-sm">
<CardHeader className="px-4 pt-4 pb-2">
<CardTitle className="text-[15px] font-semibold">Documents</CardTitle>
</CardHeader>
<CardContent className="p-0">
<div className="divide-y divide-border">
{documents.length === 0 ? (
<p className="text-center text-[13px] text-muted-foreground py-8">No documents</p>
) : (
documents.map((doc) => (
<div key={doc.id} className="flex items-center justify-between px-4 py-3 hover:bg-muted/30 transition-colors">
<div className="flex items-center gap-3 min-w-0">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary/10 shrink-0">
<FileText className="w-4 h-4 text-primary" />
</div>
<div className="min-w-0">
<p className="text-[13px] font-medium truncate">{doc.title}</p>
<p className="text-[11px] text-muted-foreground">{doc.category || "General"} {doc.created_at ? format(new Date(doc.created_at), "MMM d, yyyy") : ""}</p>
</div>
</div>
{doc.file_url && (
<Button variant="ghost" size="sm" className="h-7 text-[11px] gap-1 text-muted-foreground hover:text-primary">
<ExternalLink className="w-3 h-3" /> View
</Button>
)}
</div>
))
)}
</div>
</CardContent>
</Card>
</TabsContent>
{/* NOTES */}
<TabsContent value="notes">
<Card className="border shadow-sm">
<CardHeader className="px-4 pt-4 pb-2">
<CardTitle className="text-[15px] font-semibold">Notes</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
<p className="text-[13px] text-muted-foreground text-center py-8">No notes yet. Click "Note" above to add one.</p>
</CardContent>
</Card>
</TabsContent>
{/* ACTIVITY */}
<TabsContent value="activity">
<Card className="border shadow-sm">
<CardHeader className="px-4 pt-4 pb-2">
<CardTitle className="text-[15px] font-semibold flex items-center gap-2">
<Activity className="w-4 h-4 text-primary" /> Activity Timeline
</CardTitle>
</CardHeader>
<CardContent className="px-4 pb-4">
{activity.length === 0 ? (
<p className="text-[13px] text-muted-foreground text-center py-8">No recent activity</p>
) : (
<div className="relative pl-6 space-y-0">
{/* Timeline line */}
<div className="absolute left-[9px] top-2 bottom-2 w-px bg-border" />
{activity.map((item, i) => (
<div key={item.id + i} className="relative flex items-start gap-3 py-2.5">
<div className={`absolute left-[-15px] top-3 flex h-5 w-5 items-center justify-center rounded-full border-2 border-background ${item.icon === "dollar" ? "bg-emerald-100" : "bg-destructive/10"}`}>
{item.icon === "dollar" ? (
<DollarSign className="w-2.5 h-2.5 text-emerald-600" />
) : (
<Shield className="w-2.5 h-2.5 text-destructive" />
)}
</div>
<div className="flex-1 min-w-0">
<p className="text-[13px] font-medium text-foreground">{item.text}</p>
<p className="text-[11px] text-muted-foreground mt-0.5">{item.date ? format(new Date(item.date), "MMM d, yyyy") : ""}</p>
</div>
</div>
))}
</div>
)}
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
/* ── Helpers ────────────────────── */
function InfoRow({ icon, label, value }: { icon: React.ReactNode; label: string; value: string }) {
return (
<div className="flex items-start gap-3">
<div className="flex h-7 w-7 items-center justify-center rounded-md bg-muted/80 text-muted-foreground shrink-0 mt-0.5">
{icon}
</div>
<div>
<p className="text-[11px] text-muted-foreground font-medium uppercase tracking-wide">{label}</p>
<p className="text-[13px] text-foreground">{value}</p>
</div>
</div>
);
}
function StatusBadge({ status }: { status: string | null }) {
const s = (status || "").toLowerCase();
const cls = s === "open" || s === "submitted" || s === "pending"
? "cc-badge-warning"
: s === "approved" || s === "resolved" || s === "completed" || s === "current"
? "cc-badge-success"
: s === "denied" || s === "closed"
? "cc-badge-danger"
: "cc-badge-neutral";
return (
<span className={`inline-flex items-center rounded-full px-2 py-0.5 text-2xs font-medium border capitalize ${cls}`}>
{status || "—"}
</span>
);
}