import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; import { accounting } from "@/lib/accountingClient"; import { useCompanyId } from "./lib/useCompanyId"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Card, CardContent } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"; import { Plus, Wrench, Search, ChevronRight, Loader2 } from "lucide-react"; import { toast } from "sonner"; import { money, fmtDate } from "./lib/format"; const PRIORITIES = [ { value: "low", label: "Low", cls: "bg-slate-100 text-slate-700" }, { value: "normal", label: "Normal", cls: "bg-blue-100 text-blue-700" }, { value: "high", label: "High", cls: "bg-amber-100 text-amber-700" }, { value: "urgent", label: "Urgent", cls: "bg-red-100 text-red-700" }, ]; const STATUSES = [ { value: "open", label: "Open", cls: "bg-blue-100 text-blue-700" }, { value: "in_progress", label: "In Progress", cls: "bg-amber-100 text-amber-700" }, { value: "on_hold", label: "On Hold", cls: "bg-slate-100 text-slate-700" }, { value: "completed", label: "Completed", cls: "bg-emerald-100 text-emerald-700" }, { value: "cancelled", label: "Cancelled", cls: "bg-red-100 text-red-700" }, ]; const CATEGORIES = [ "General Maintenance", "Plumbing", "Electrical", "HVAC", "Landscaping", "Roofing", "Painting", "Cleaning", "Pest Control", "Security", "Pool/Spa", "Other", ]; const EMPTY = { title: "", description: "", priority: "normal", status: "open", category: "", customer_id: "", vendor_id: "", assigned_to: "", property_address: "", requested_date: new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" }), scheduled_date: "", completed_date: "", estimated_cost: "", actual_cost: "", notes: "", }; export default function AccountingWorkOrdersPage() { const { companyId, loading: companyLoading, error: companyError, associationId } = useCompanyId(); const cid = companyId ?? ""; const cur = "USD"; const qc = useQueryClient(); const [open, setOpen] = useState(false); const [editId, setEditId] = useState(null); const [detailId, setDetailId] = useState(null); const [form, setForm] = useState({ ...EMPTY }); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); const [saving, setSaving] = useState(false); const f = (patch: Partial) => setForm((prev) => ({ ...prev, ...patch })); const { data: workOrders = [] } = useQuery({ queryKey: ["work-orders", cid], enabled: !!cid, queryFn: async () => (await accounting.from("work_orders").select("*, customers(name), vendors(name)").eq("company_id", cid).order("requested_date", { ascending: false })).data ?? [], }); const { data: homeowners = [] } = useQuery({ queryKey: ["customers", cid], enabled: !!cid, queryFn: async () => (await accounting.from("customers").select("id,name,property_address").eq("company_id", cid).order("name")).data ?? [], }); const { data: vendors = [] } = useQuery({ queryKey: ["vendors-lookup", cid], enabled: !!cid, queryFn: async () => (await accounting.from("vendors").select("id,name").eq("company_id", cid).order("name")).data ?? [], }); const filtered = (workOrders as any[]).filter((wo) => { if (statusFilter !== "all" && wo.status !== statusFilter) return false; if (search) { const q = search.toLowerCase(); if (!(wo.number + wo.title + (wo.customers?.name ?? "") + (wo.property_address ?? "")).toLowerCase().includes(q)) return false; } return true; }); const detail = detailId ? (workOrders as any[]).find((w) => w.id === detailId) : null; const nextNumber = async () => { const { count } = await accounting.from("work_orders").select("*", { count: "exact", head: true }).eq("company_id", cid); return `WO-${String((count ?? 0) + 1).padStart(5, "0")}`; }; const openNew = async () => { setEditId(null); setForm({ ...EMPTY, requested_date: new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" }) }); setOpen(true); }; const openEdit = (wo: any) => { setEditId(wo.id); setForm({ title: wo.title, description: wo.description ?? "", priority: wo.priority, status: wo.status, category: wo.category ?? "", customer_id: wo.customer_id ?? "", vendor_id: wo.vendor_id ?? "", assigned_to: wo.assigned_to ?? "", property_address: wo.property_address ?? "", requested_date: wo.requested_date, scheduled_date: wo.scheduled_date ?? "", completed_date: wo.completed_date ?? "", estimated_cost: wo.estimated_cost ? String(wo.estimated_cost) : "", actual_cost: wo.actual_cost ? String(wo.actual_cost) : "", notes: wo.notes ?? "", }); setOpen(true); }; const save = async () => { if (!form.title.trim()) return toast.error("Title required"); setSaving(true); try { const payload: any = { company_id: cid, title: form.title, description: form.description || null, priority: form.priority, status: form.status, category: form.category || null, customer_id: form.customer_id || null, vendor_id: form.vendor_id || null, assigned_to: form.assigned_to || null, property_address: form.property_address || null, requested_date: form.requested_date, scheduled_date: form.scheduled_date || null, completed_date: form.completed_date || null, estimated_cost: form.estimated_cost ? parseFloat(form.estimated_cost) : null, actual_cost: form.actual_cost ? parseFloat(form.actual_cost) : null, notes: form.notes || null, }; if (editId) { const { error } = await accounting.from("work_orders").update(payload).eq("id", editId); if (error) throw new Error(error.message); toast.success("Work order updated"); } else { payload.number = await nextNumber(); const { error } = await accounting.from("work_orders").insert(payload); if (error) throw new Error(error.message); toast.success("Work order created"); } setOpen(false); qc.invalidateQueries({ queryKey: ["work-orders", cid] }); } catch (e: any) { toast.error(e?.message ?? "Failed to save"); } finally { setSaving(false); } }; const remove = async (id: string) => { if (!confirm("Delete this work order?")) return; await accounting.from("work_orders").delete().eq("id", id); if (detailId === id) setDetailId(null); qc.invalidateQueries({ queryKey: ["work-orders", cid] }); toast.success("Deleted"); }; const priorityMeta = (v: string) => PRIORITIES.find((p) => p.value === v) ?? PRIORITIES[1]; const statusMeta = (v: string) => STATUSES.find((s) => s.value === v) ?? STATUSES[0]; // Summary counts const counts = (workOrders as any[]).reduce((acc: any, wo: any) => { acc[wo.status] = (acc[wo.status] ?? 0) + 1; return acc; }, {} as Record); if (!associationId) return

Select an association.

; if (companyLoading) return
; if (companyError || !companyId) return

{companyError || "Accounting setup is not ready."}

; return (

Work Orders

Track maintenance requests, repairs, and vendor assignments

{/* Status summary strip */}
{STATUSES.map((s) => ( ))} {statusFilter !== "all" && ( )}
setSearch(e.target.value)} />
# Title Homeowner Category Priority Status Scheduled Est. Cost {filtered.map((wo: any) => { const p = priorityMeta(wo.priority); const s = statusMeta(wo.status); return ( setDetailId(wo.id)}> {wo.number} {wo.title} {wo.customers?.name ?? "—"} {wo.category ?? "—"} {p.label} {s.label} {wo.scheduled_date ? fmtDate(wo.scheduled_date) : "—"} {wo.estimated_cost ? money(wo.estimated_cost, cur) : "—"} ); })} {filtered.length === 0 && ( No work orders{statusFilter !== "all" ? ` with status "${statusFilter}"` : ""} yet. )}
{/* Create / Edit dialog */} { if (!o) setEditId(null); setOpen(o); }}> {editId ? "Edit Work Order" : "New Work Order"}
f({ title: e.target.value })} placeholder="e.g. Repair pool pump" />
f({ assigned_to: e.target.value })} placeholder="Staff name or role" />
f({ property_address: e.target.value })} placeholder="Auto-filled from homeowner" />