"use client"; import { useState, useEffect } from "react"; import Image from "next/image"; import { LotDetail } from "@/actions/route-trace/lots"; // One-color outline icons const Icons = { printer: (className: string) => ( ), leaf: (className: string) => ( ), warehouse: (className: string) => ( ), }; type StickerType = "field" | "shed"; type StickerSize = "4x2" | "4x3"; const COPY_OPTIONS = [1, 2, 3, 5, 10]; export default function StickerPreviewModal({ lot, onClose }: { lot: LotDetail; onClose: () => void }) { const [stickerType, setStickerType] = useState("field"); const [stickerSize, setStickerSize] = useState("4x2"); const [copies, setCopies] = useState(1); const [loading, setLoading] = useState(false); const [qrDataUrl, setQrDataUrl] = useState(null); const baseUrl = typeof window !== "undefined" ? window.location.origin : "http://localhost:3000"; const traceUrl = `${baseUrl}/trace/${lot.lot_number}`; useEffect(() => { import("qrcode").then(({ toDataURL }) => { toDataURL(traceUrl, { width: 200, margin: 1, color: { dark: "#000000", light: "#FFFFFF" } }) .then(setQrDataUrl) .catch(() => {}); }); }, [traceUrl]); async function handlePrint() { setLoading(true); try { const url = `/api/route-trace/sticker-pdf?lotId=${lot.lot_id}&type=${stickerType}&size=${stickerSize}&copies=${copies}`; const res = await fetch(url); if (!res.ok) throw new Error("Failed to generate PDF"); const blob = await res.blob(); const blobUrl = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = blobUrl; a.download = `${lot.lot_number}-${stickerType}-${stickerSize}.pdf`; a.target = "_blank"; a.click(); URL.revokeObjectURL(blobUrl); } catch (e) { alert("Failed to generate PDF. Please try again."); } finally { setLoading(false); } } // Actual thermal label dimensions in points: 4x2" = 288×144, 4x3" = 288×216 const LABEL_W = 288; const LABEL_H = stickerSize === "4x3" ? 216 : 144; // Scale preview to ~1.8x so it's readable in the modal const scale = 1.8; const previewW = LABEL_W * scale; const previewH = LABEL_H * scale; const qrSize = stickerSize === "4x3" ? 130 : 110; const qrPreviewSize = qrSize * scale * 0.55; // scaled down for preview return ( { if (e.key === "Escape") onClose(); }} > e.stopPropagation()}> {Icons.printer("h-5 w-5")} Print Sticker {lot.lot_number} · {lot.crop_type} {/* Type + Size selectors */} Sticker Type {(["field", "shed"] as StickerType[]).map((t) => { const label = t === "field" ? "Field" : "Shed"; const desc = t === "field" ? "After harvest" : "At shed arrival"; return ( setStickerType(t)} className={`w-full rounded-xl border px-3 py-2.5 text-left transition-colors ${ stickerType === t ? "border-emerald-600 bg-emerald-600 text-white" : "border-stone-200 hover:bg-stone-50" }`} > {label} Sticker {desc} ); })} Label Size {(["4x2", "4x3"] as StickerSize[]).map((s) => { const w = s === "4x2" ? "2 in" : "3 in"; const h = s === "4x2" ? "2 in" : "3 in"; return ( setStickerSize(s)} className={`w-full rounded-xl border px-3 py-2.5 text-left transition-colors ${ stickerSize === s ? "border-emerald-600 bg-emerald-600 text-white" : "border-stone-200 hover:bg-stone-50" }`} > {w} × {h} {s === "4x2" ? "Field sticker" : "Shed sticker"} ); })} Copies {COPY_OPTIONS.map((n) => ( setCopies(n)} className={`rounded-lg border py-2 text-center text-sm font-bold transition-colors ${ copies === n ? "border-emerald-600 bg-emerald-600 text-white" : "border-stone-200 text-stone-600 hover:bg-stone-50" }`} > {n} ))} labels per sheet {/* Sticker preview — actual proportions, scaled */} Preview {/* Brand row */} Route Trace {stickerSize}" {/* Lot number — dominant */} {lot.lot_number} {/* Crop */} {lot.crop_type} {/* Data rows */} {stickerType === "field" ? ( <> {lot.harvest_date && Harvested: {lot.harvest_date}} {lot.quantity_lbs && ( {Number(lot.quantity_lbs).toLocaleString()} lbs )} {lot.field_location && {lot.field_location}} {lot.field_block && Block: {lot.field_block}} {lot.worker_name && {lot.worker_name}} {lot.yield_estimate_lbs && ( Est: {Number(lot.yield_estimate_lbs).toLocaleString()} {lot.yield_unit ?? "lbs"} )} > ) : ( <> {lot.quantity_lbs && ( {Number(lot.quantity_lbs).toLocaleString()} {lot.yield_unit ?? "lbs"} )} {lot.harvest_date && Packed: {lot.harvest_date}} {lot.field_location && From: {lot.field_location}} {lot.worker_name && {lot.worker_name}} {lot.destination_stop_id && ( Dest: #{lot.destination_stop_id.slice(0, 8)} )} > )} {/* Right — bin/container */} {lot.bin_id && ( BIN {lot.bin_id} )} {lot.container_id && ( CONT {lot.container_id} )} {lot.pallets && ( {lot.pallets} PLT )} {lot.field_block && stickerType === "shed" && ( Block: {lot.field_block} )} {/* QR — bottom right */} {qrDataUrl ? ( ) : ( QR )} {/* /trace url — bottom left */} /trace/{lot.lot_number} {/* Print specs */} Direct Thermal · {stickerSize}" · Black on White · Large QR Helvetica Bold · High-contrast QR · No margins · 2 per sheet {lot.lot_number} 2 per sheet Cancel {loading ? "Generating PDF..." : <>{Icons.printer("h-4 w-4")} Print {copies === 1 ? "" : `${copies}× `}{stickerType === "field" ? "Field" : "Shed"} Sticker{copies > 1 ? "s" : ""}>} ); }
{lot.lot_number} · {lot.crop_type}
Sticker Type
Label Size
Copies
labels per sheet
Preview
Direct Thermal · {stickerSize}" · Black on White · Large QR
Helvetica Bold · High-contrast QR · No margins · 2 per sheet
{lot.lot_number}
2 per sheet