"use client" import { useEffect, useState } from "react" import { useParams } from "next/navigation" import { AlertCircle, Calendar, Car, CheckCircle2, ChevronRight, ClipboardList, Gauge, Loader2, Printer, ShieldAlert, ShieldCheck, User, } from "lucide-react" type Severity = "good" | "attention" | "critical" | "na" | "not_inspected" type Photo = { id: number; url: string | null; caption?: string | null } type CheckPoint = { name: string description?: string | null severity: Severity technician_notes?: string | null condition_rate?: number | null photos?: Photo[] } type Section = { name: string; items: CheckPoint[] } type PublicInspection = { title?: string order_number?: string date?: string time?: string status?: string odometer?: number | null note?: string | null description?: string | null customer?: { name?: string } | null vehicle?: { make?: string; model?: string; year?: string | number; license_plate?: string; vin?: string } | null technician?: { name?: string } | null department?: string | null template?: string | null totals: { good: number; attention: number; critical: number; na: number; not_inspected: number; considered: number; all: number } score: number | null sections: Section[] share_expires_at?: string | null } const SEVERITY_LABEL: Record = { good: "Good", attention: "Attention", critical: "Critical", na: "N/A", not_inspected: "Not Inspected", } const SEVERITY_DOT_BG: Record = { good: "bg-emerald-500", attention: "bg-amber-500", critical: "bg-rose-500", na: "bg-slate-400", not_inspected: "bg-gray-300", } const SEVERITY_PILL: Record = { good: "bg-emerald-50 text-emerald-700 border-emerald-200", attention: "bg-amber-50 text-amber-800 border-amber-200", critical: "bg-rose-50 text-rose-700 border-rose-200", na: "bg-slate-50 text-slate-700 border-slate-200", not_inspected: "bg-gray-50 text-gray-600 border-gray-200", } const SEVERITY_BORDER: Record = { good: "border-l-emerald-500", attention: "border-l-amber-500", critical: "border-l-rose-500", na: "border-l-slate-400", not_inspected: "border-l-gray-300", } function ScoreRing({ score }: { score: number | null }) { if (score === null) { return (
Not scored
) } const color = score >= 80 ? "text-emerald-600" : score >= 50 ? "text-amber-600" : "text-rose-600" const stroke = score >= 80 ? "stroke-emerald-500" : score >= 50 ? "stroke-amber-500" : "stroke-rose-500" const r = 56 const c = 2 * Math.PI * r const offset = c - (score / 100) * c return (
{score}
Health
) } export default function PublicInspectionPage() { const params = useParams<{ token: string }>() const token = params.token const [data, setData] = useState(null) const [error, setError] = useState(null) const [loading, setLoading] = useState(true) const [lightbox, setLightbox] = useState(null) useEffect(() => { const base = process.env.NEXT_PUBLIC_API_URL ?? "" fetch(`${base.replace(/\/$/, "")}/api/public/inspections/${token}`, { headers: { Accept: "application/json", "X-Lang": "en" }, }) .then(async (res) => { if (!res.ok) { const body = await res.json().catch(() => ({} as any)) throw new Error(body.message ?? `Failed (HTTP ${res.status})`) } return res.json() }) .then((json: { data: PublicInspection }) => setData(json.data)) .catch((e: Error) => setError(e.message)) .finally(() => setLoading(false)) }, [token]) if (loading) { return (
Loading inspection…
) } if (error) { return (

Can't open this inspection

{error}

) } if (!data) return null const vehicleLine = data.vehicle ? [data.vehicle.year, data.vehicle.make, data.vehicle.model].filter(Boolean).join(" ") : "—" const criticalCount = data.totals.critical const attentionCount = data.totals.attention const headlineStatus = criticalCount > 0 ? { Icon: ShieldAlert, color: "text-rose-600", bg: "bg-rose-50", label: `${criticalCount} critical issue${criticalCount === 1 ? "" : "s"} found` } : attentionCount > 0 ? { Icon: AlertCircle, color: "text-amber-600", bg: "bg-amber-50", label: `${attentionCount} item${attentionCount === 1 ? "" : "s"} need attention` } : { Icon: ShieldCheck, color: "text-emerald-600", bg: "bg-emerald-50", label: "No issues found" } return (
{/* Print is handled by downloading the backend-generated PDF (see the Print button), so this page no longer needs print CSS. */} {/* Print bar */}
Inspection report
Print PDF
{/* Hero card */}
Vehicle inspection report

{data.title ?? "Inspection"}

{data.order_number && (
#{data.order_number}
)}
{headlineStatus.label}
{/* Severity summary */}
{(["good", "attention", "critical", "na", "not_inspected"] as Severity[]).map((s) => (
{data.totals[s]}
{SEVERITY_LABEL[s]}
))}
{/* Sections */} {data.sections.length === 0 ? (
This inspection has no recorded checkpoints yet.
) : ( data.sections.map((section) => (

{section.name}

{section.items.length} {section.items.length === 1 ? "item" : "items"}
    {section.items.map((cp, i) => (
  • {/* Head: dot + name + severity pill (pill stays inline next to name) */}
    {cp.name} {SEVERITY_LABEL[cp.severity]}
    {cp.description && (

    {cp.description}

    )} {cp.technician_notes && (

    "{cp.technician_notes}"

    )} {cp.condition_rate != null && (
    {cp.condition_rate}%
    )} {cp.photos && cp.photos.length > 0 && (
    {cp.photos.map((p) => ( p.url ? ( ) : null ))}
    )}
  • ))}
)) )} {(data.note || data.description) && (

Additional notes from your technician

{data.note &&

{data.note}

} {data.description && (

{data.description}

)}
)} {/* Technician footer */} {data.technician?.name && (
{data.technician.name .split(" ") .map((p) => p[0]) .filter(Boolean) .slice(0, 2) .join("")}
Inspected by
{data.technician.name}
{data.department && ( {data.department} )} {data.template && ( {data.template} )}
)}
{data.share_expires_at && (
Link valid until {new Date(data.share_expires_at).toLocaleDateString()}
)}
{/* Lightbox */} {lightbox && lightbox.url && (
setLightbox(null)} role="dialog" aria-modal > {/* eslint-disable-next-line @next/next/no-img-element */} {lightbox.caption e.stopPropagation()} /> {lightbox.caption && (
{lightbox.caption}
)}
)}
) } function InfoCell({ icon: Icon, label, value, subtitle, }: { icon: React.ComponentType<{ className?: string }> label: string value: string subtitle?: string }) { return (
{label}
{value}
{subtitle && (
{subtitle}
)}
) }