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

160 lines
7.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { X, Download, Power, RefreshCw, Copy, Check } from "lucide-react";
import { api, apiBase } from "@/lib/api";
type Tab = "overview" | "stats" | "history";
export default function QrDetailDrawer({ qrId, onClose, onChanged }: {
qrId: string; onClose: () => void; onChanged: () => void;
}) {
const [d, setD] = useState<any>(null);
const [tab, setTab] = useState<Tab>("overview");
const [copied, setCopied] = useState(false);
function load() { api(`/qr/${qrId}/detail`).then(setD).catch(() => {}); }
useEffect(() => { load(); }, [qrId]);
async function disable() { await api(`/qr/${qrId}/disable`, { method: "PUT" }); load(); onChanged(); }
async function regenerate() { await api(`/qr/${qrId}/regenerate`, { method: "POST" }); load(); onChanged(); }
function copyUrl() {
navigator.clipboard?.writeText(d?.short_url ?? "");
setCopied(true); setTimeout(() => setCopied(false), 1500);
}
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 preview = d.image_url ? `${apiBase}${d.image_url}` : `${apiBase}/qr/${qrId}/download?format=png`;
const statusColor = d.status === "active" ? "bg-emerald-50 text-emerald-700" : "bg-rose-50 text-rose-600";
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-[90vh] 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">QR 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 gap-4">
<img src={preview} alt="QR" className="h-24 w-24 rounded-lg border border-slate-100 object-contain" />
<div className="text-sm space-y-1">
<span className={`text-xs font-semibold px-2 py-0.5 rounded-full ${statusColor} capitalize`}>{d.status}</span>
<div className="text-slate-400 text-xs mt-2">QR ID</div>
<div className="font-mono text-xs">{d.qr_id_label}</div>
<div className="text-slate-400 text-xs">Short URL</div>
<button onClick={copyUrl} className="text-brand-600 text-xs flex items-center gap-1">
{d.short_url?.replace(/^https?:\/\//, "")} {copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
</button>
</div>
</div>
{/* tabs */}
<div className="flex gap-1 border-b border-slate-200 text-sm">
{([["overview", "Overview"], ["stats", "Scan Statistics"], ["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="Product" value={d.overview.product} />
<Row label="SKU" value={d.overview.sku} />
<Row label="Batch No." value={d.overview.batch_number} />
<Row label="QR Type" value={d.qr_type} cap />
<Row label="Created By" value={d.overview.created_by} />
<Row label="Created On" value={d.overview.created_at ? new Date(d.overview.created_at).toLocaleString() : "—"} />
<div className="flex justify-between border-b border-slate-50 py-1.5">
<span className="text-slate-400">Redirect URL</span>
<a href={d.overview.redirect_url} target="_blank" rel="noreferrer"
className="text-brand-600 truncate max-w-[160px] text-right">{d.overview.redirect_url}</a>
</div>
<div className="flex justify-between border-b border-slate-50 py-1.5">
<span className="text-slate-400">Verification Enabled</span>
<span className={d.overview.verification_enabled ? "text-emerald-600 font-medium" : "text-rose-600 font-medium"}>
{d.overview.verification_enabled ? "✓ Yes" : "Disabled"}
</span>
</div>
<div className="flex justify-between border-b border-slate-50 py-1.5">
<span className="text-slate-400">Expiry</span>
<span className="text-emerald-600 font-medium">{d.overview.expiry}</span>
</div>
<Row label="Remarks" value={d.overview.remarks} />
</div>
)}
{tab === "stats" && (
<div className="grid grid-cols-3 gap-3 text-center">
<Stat label="Total" value={d.stats.total} />
<Stat label="Today" value={d.stats.today} />
<Stat label="This Week" value={d.stats.week} />
</div>
)}
{tab === "history" && (
<div className="space-y-1 text-sm max-h-72 overflow-y-auto">
{d.history.map((h: any, i: number) => (
<div key={i} className="flex justify-between border-b border-slate-100 py-1.5">
<span className="text-slate-500">{new Date(h.at).toLocaleString()}</span>
<span>{h.city ?? "—"} · {h.device ?? "—"}</span>
</div>
))}
{d.history.length === 0 && <p className="text-slate-400">No scans yet.</p>}
</div>
)}
{/* downloads */}
<div className="grid grid-cols-3 gap-2">
<a href={`${apiBase}/qr/${qrId}/download?format=png`}
className="flex items-center justify-center gap-1 bg-brand-600 text-white rounded-lg py-2 text-xs font-semibold">
<Download className="h-3.5 w-3.5" /> Download PNG
</a>
<a href={`${apiBase}/qr/${qrId}/download?format=svg`}
className="flex items-center justify-center gap-1 border border-emerald-300 text-emerald-700 bg-emerald-50 rounded-lg py-2 text-xs font-semibold">
<Download className="h-3.5 w-3.5" /> Download SVG
</a>
<a href={`${apiBase}/qr/${qrId}/download?format=pdf`}
className="flex items-center justify-center gap-1 border border-orange-300 text-orange-700 bg-orange-50 rounded-lg py-2 text-xs font-semibold">
<Download className="h-3.5 w-3.5" /> Download PDF
</a>
</div>
<div className="grid grid-cols-2 gap-2">
<button onClick={disable} className="flex items-center justify-center gap-1 border border-slate-200 rounded-lg py-2 text-sm font-medium">
<Power className="h-4 w-4" /> Disable QR
</button>
<button onClick={regenerate} className="flex items-center justify-center gap-1 bg-rose-50 text-rose-600 rounded-lg py-2 text-sm font-semibold">
<RefreshCw className="h-4 w-4" /> Regenerate QR
</button>
</div>
</div>
</div>
</div>
);
}
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 ${cap ? "capitalize" : ""}`}>{value ?? "—"}</span>
</div>
);
}
function Stat({ label, value }: any) {
return (
<div className="card p-3">
<div className="text-xl font-extrabold">{value}</div>
<div className="text-xs text-slate-500">{label}</div>
</div>
);
}