Files
property-management-network/components/forms/ai-maintenance-summary.tsx
T

174 lines
7.4 KiB
TypeScript
Raw Normal View History

"use client"
import { useState } from "react"
import { Sparkles, Loader2, X, AlertTriangle, TrendingUp, Wrench } from "lucide-react"
import { formatCurrency } from "@/lib/utils"
interface Summary {
reportTitle: string
reportDate: string
propertyName: string
totalRequests: number
openRequests: number
resolvedRequests: number
urgentItems: string[]
summary: string
recommendations: string[]
estimatedTotalCost: number
}
export function AiMaintenanceSummary({ propertyId }: { propertyId: string }) {
const [loading, setLoading] = useState(false)
const [summary, setSummary] = useState<Summary | null>(null)
const [error, setError] = useState<string | null>(null)
const [open, setOpen] = useState(false)
async function generate() {
setLoading(true)
setError(null)
setOpen(true)
try {
const res = await fetch("/api/ai/maintenance-summary", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ property_id: propertyId }),
})
const data = await res.json()
if (!res.ok) { setError(data.error ?? "Failed to generate summary"); return }
setSummary(data)
} catch {
setError("Network error. Please try again.")
} finally {
setLoading(false)
}
}
return (
<>
<button
onClick={generate}
disabled={loading}
className="flex items-center gap-1.5 rounded-xl border border-indigo-500/30 bg-indigo-500/10 px-3 py-2 text-xs font-medium text-indigo-300 hover:bg-indigo-500/20 transition-all disabled:opacity-60"
>
{loading ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Sparkles className="h-3.5 w-3.5" />}
AI Summary
</button>
{/* Slide-over panel */}
{open && (
<div className="fixed inset-0 z-50 flex justify-end">
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={() => setOpen(false)} />
<div className="relative w-full max-w-md bg-[#111118] border-l border-white/[0.06] overflow-y-auto shadow-2xl">
<div className="flex items-center justify-between border-b border-white/[0.06] px-5 py-4">
<div className="flex items-center gap-2">
<Sparkles className="h-4 w-4 text-indigo-400" />
<h2 className="text-sm font-semibold text-white">AI Maintenance Report</h2>
</div>
<button onClick={() => setOpen(false)} className="rounded-lg p-1.5 text-white/40 hover:text-white transition">
<X className="h-4 w-4" />
</button>
</div>
<div className="p-5">
{loading && (
<div className="flex flex-col items-center justify-center gap-3 py-16">
<Loader2 className="h-8 w-8 text-indigo-400 animate-spin" />
<p className="text-sm text-white/50">Analysing maintenance data</p>
</div>
)}
{error && (
<div className="rounded-xl border border-red-500/20 bg-red-500/5 p-4">
<p className="text-sm text-red-400">{error}</p>
</div>
)}
{summary && !loading && (
<div className="space-y-5">
<div>
<h3 className="text-base font-bold text-white">{summary.reportTitle}</h3>
<p className="text-xs text-white/40 mt-0.5">{summary.reportDate} · {summary.propertyName}</p>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-3">
{[
{ label: "Total", value: summary.totalRequests, color: "text-white" },
{ label: "Open", value: summary.openRequests, color: "text-amber-400" },
{ label: "Resolved", value: summary.resolvedRequests, color: "text-emerald-400" },
].map((s) => (
<div key={s.label} className="rounded-xl border border-white/[0.06] bg-white/[0.02] p-3 text-center">
<p className={`text-xl font-bold ${s.color}`}>{s.value}</p>
<p className="text-xs text-white/40 mt-0.5">{s.label}</p>
</div>
))}
</div>
{/* Cost */}
{summary.estimatedTotalCost > 0 && (
<div className="flex items-center justify-between rounded-xl border border-amber-500/20 bg-amber-500/5 px-4 py-3">
<span className="text-xs text-amber-400/80">Estimated total cost</span>
<span className="text-sm font-bold text-amber-400">{formatCurrency(summary.estimatedTotalCost)}</span>
</div>
)}
{/* Summary */}
<div>
<p className="text-xs font-medium uppercase tracking-wider text-white/30 mb-2 flex items-center gap-1.5">
<TrendingUp className="h-3 w-3" /> Overview
</p>
<p className="text-sm text-white/70 leading-relaxed">{summary.summary}</p>
</div>
{/* Urgent items */}
{summary.urgentItems?.length > 0 && (
<div>
<p className="text-xs font-medium uppercase tracking-wider text-red-400/70 mb-2 flex items-center gap-1.5">
<AlertTriangle className="h-3 w-3" /> Urgent Items
</p>
<ul className="space-y-1.5">
{summary.urgentItems.map((item, i) => (
<li key={i} className="text-sm text-white/70 flex items-start gap-2">
<span className="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-red-400" />
{item}
</li>
))}
</ul>
</div>
)}
{/* Recommendations */}
{summary.recommendations?.length > 0 && (
<div>
<p className="text-xs font-medium uppercase tracking-wider text-indigo-400/70 mb-2 flex items-center gap-1.5">
<Wrench className="h-3 w-3" /> Recommendations
</p>
<ul className="space-y-2">
{summary.recommendations.map((rec, i) => (
<li key={i} className="text-sm text-white/70 flex items-start gap-2">
<span className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-500/20 text-[10px] font-bold text-indigo-400">{i + 1}</span>
{rec}
</li>
))}
</ul>
</div>
)}
<button
onClick={generate}
className="w-full flex items-center justify-center gap-2 rounded-xl border border-white/[0.06] px-4 py-2.5 text-xs font-medium text-white/50 hover:text-white hover:border-white/20 transition"
>
<Sparkles className="h-3.5 w-3.5" />
Regenerate report
</button>
</div>
)}
</div>
</div>
</div>
)}
</>
)
}