Files
verify/frontend/components/ComplianceDetailModal.tsx
Mohamed Mathar Irfan ed6610d5d8 Initial project upload
2026-07-28 17:57:02 +05:30

169 lines
7.7 KiB
TypeScript

"use client";
import { useEffect, useRef, useState } from "react";
import { X, Pencil, Upload, ExternalLink, FileText } from "lucide-react";
import { api, apiBase } from "@/lib/api";
function imgSrc(url?: string) {
if (!url) return "";
if (url.startsWith("data:") || url.startsWith("http")) return url;
return `${apiBase}${url}`;
}
type Tab = "overview" | "documents" | "history";
const STATUS: Record<string, string> = {
complete: "bg-emerald-50 text-emerald-700",
pending: "bg-amber-50 text-amber-700",
expiring: "bg-amber-50 text-amber-700",
expired: "bg-rose-50 text-rose-700",
};
export default function ComplianceDetailModal({ cid, onClose, onEdit, onChanged }: {
cid: string; onClose: () => void; onEdit: () => void; onChanged: () => void;
}) {
const [d, setD] = useState<any>(null);
const [tab, setTab] = useState<Tab>("overview");
const fileRef = useRef<HTMLInputElement>(null);
function load() { api(`/compliance/${cid}/detail`).then(setD).catch(() => {}); }
useEffect(() => { load(); }, [cid]);
async function onUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
const r = new FileReader();
r.onload = async () => {
await api(`/compliance/${cid}/document`, {
method: "POST",
body: JSON.stringify({ data_url: r.result, filename: file.name }),
});
load(); onChanged();
};
r.readAsDataURL(file);
}
if (!d) return (
<div className="fixed inset-0 bg-black/40 z-50 grid place-items-center p-4" onClick={onClose}>
<div className="bg-white rounded-2xl w-full max-w-md p-5" onClick={(e) => e.stopPropagation()}>
<div className="skeleton h-40 rounded-xl" />
</div>
</div>
);
const expColor = d.days_left != null && d.days_left < 0 ? "text-rose-600"
: d.days_left != null && d.days_left < 30 ? "text-amber-600" : "text-slate-700";
return (
<div className="fixed inset-0 bg-black/40 z-50 grid place-items-center p-4 overflow-y-auto" onClick={onClose}>
<div className="bg-white rounded-2xl w-full max-w-md my-4 flex flex-col max-h-[92vh] overflow-y-auto" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100 sticky top-0 bg-white rounded-t-2xl">
<h3 className="font-bold">Compliance Details</h3>
<button onClick={onClose}><X className="h-5 w-5 text-slate-400" /></button>
</div>
<div className="p-5 space-y-4">
<div className="flex items-center gap-3">
{d.product_image
? <img src={imgSrc(d.product_image)} className="h-14 w-14 rounded-lg object-contain border border-slate-100" />
: <div className="h-14 w-14 rounded-lg bg-slate-100" />}
<div className="flex-1">
<div className="font-bold">{d.product_name ?? "—"}</div>
<div className="text-xs text-slate-400">SKU: {d.sku ?? "—"}</div>
</div>
<span className={`text-xs font-semibold px-2 py-0.5 rounded-full capitalize ${STATUS[d.status] ?? "bg-slate-100"}`}>{d.status}</span>
</div>
<div className="flex gap-1 border-b border-slate-200 text-sm">
{([["overview", "Overview"], ["documents", `Documents (${d.documents?.length ?? 0})`], ["history", "History"]] as const).map(([t, l]) => (
<button key={t} onClick={() => setTab(t)}
className={`px-3 py-2 border-b-2 -mb-px font-medium ${tab === t ? "border-brand-600 text-brand-700" : "border-transparent text-slate-500"}`}>{l}</button>
))}
</div>
{tab === "overview" && (
<div className="space-y-2 text-sm">
<Row label="Compliance Type" value={d.compliance_type} />
<Row label="License / Reg. No." value={d.license_number} />
<Row label="License Name" value={d.license_name} />
<Row label="Issue Date" value={fmt(d.issue_date)} />
<div className="flex justify-between border-b border-slate-50 py-1.5">
<span className="text-slate-400">Expiry Date</span>
<span className={`font-medium text-right ${expColor}`}>
{fmt(d.expiry_date)}
{d.days_left != null && <span className="block text-xs">
{d.days_left < 0 ? "(Expired)" : `(In ${d.days_left} days)`}
</span>}
</span>
</div>
<Row label="Status" value={d.status} cap />
<Row label="Issuing Authority" value={d.issuing_authority} />
<Row label="Country" value={d.country} />
<Row label="State" value={d.state} />
<Row label="Category" value={d.category} />
<Row label="Applicable To" value={d.applicable_to} />
<Row label="Manufacturing Plant" value={d.plant} />
<Row label="Remarks" value={d.remarks} />
<Row label="Created By" value={d.created_by} />
<Row label="Created On" value={fmt(d.created_at, true)} />
<Row label="Last Updated" value={fmt(d.updated_at, true)} />
</div>
)}
{tab === "documents" && (
<div className="space-y-2 text-sm">
{(d.documents ?? []).map((doc: any, i: number) => (
<a key={i} href={`${apiBase}${doc.url}`} target="_blank" rel="noreferrer"
className="flex items-center gap-2 border border-slate-100 rounded-lg px-3 py-2 hover:bg-slate-50">
<FileText className="h-4 w-4 text-slate-400" /> {doc.name}
</a>
))}
{(!d.documents || d.documents.length === 0) && <p className="text-slate-400">No documents uploaded.</p>}
</div>
)}
{tab === "history" && (
<div className="space-y-1 text-sm">
{(d.history ?? []).map((h: any, i: number) => (
<div key={i} className="flex justify-between border-b border-slate-50 py-1.5">
<span>{h.action} {h.by ? `· ${h.by}` : ""}</span>
<span className="text-slate-400">{fmt(h.at, true)}</span>
</div>
))}
</div>
)}
<div className="space-y-2 pt-2">
<div className="grid grid-cols-2 gap-2">
<button onClick={onEdit} className="flex items-center justify-center gap-1 border border-slate-200 rounded-lg py-2 text-sm font-medium">
<Pencil className="h-4 w-4" /> Edit
</button>
<button onClick={() => fileRef.current?.click()} className="flex items-center justify-center gap-1 bg-brand-600 text-white rounded-lg py-2 text-sm font-semibold">
<Upload className="h-4 w-4" /> Upload Document
</button>
<input ref={fileRef} type="file" accept="application/pdf,image/*" className="hidden" onChange={onUpload} />
</div>
<a href="#" onClick={(e) => e.preventDefault()}
className="flex items-center justify-center gap-1 border border-brand-200 text-brand-700 rounded-lg py-2 text-sm font-medium">
<ExternalLink className="h-4 w-4" /> View on Verification Page
</a>
</div>
</div>
</div>
</div>
);
}
function fmt(d?: string, time = false) {
if (!d) return "—";
const dt = new Date(d);
return isNaN(+dt) ? "—" : time ? dt.toLocaleString() : dt.toLocaleDateString();
}
function Row({ label, value, cap }: any) {
return (
<div className="flex justify-between border-b border-slate-50 py-1.5">
<span className="text-slate-400">{label}</span>
<span className={`font-medium text-slate-700 text-right ${cap ? "capitalize" : ""}`}>{value ?? "—"}</span>
</div>
);
}