diff --git a/src/features/receipts/components/receipts-client.tsx b/src/features/receipts/components/receipts-client.tsx index 6de34de..e0ee442 100644 --- a/src/features/receipts/components/receipts-client.tsx +++ b/src/features/receipts/components/receipts-client.tsx @@ -2,6 +2,8 @@ import { Printer, Tags } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; +import JsBarcode from "jsbarcode"; +import QRCode from "qrcode"; import { useLocale } from "@/components/providers/locale-provider"; import { composeGradeLabel } from "@/lib/grade-display"; @@ -264,7 +266,7 @@ export function ReceiptsClient() { } } - function printLotLabels(detail: ReceiptDetail) { + async function printLotLabels(detail: ReceiptDetail) { setError(null); if (detail.generated_lots.length === 0) { setError(locale === "id" ? "Belum ada lot yang bisa dicetak. Buat lot terlebih dahulu." : "No lot labels to print yet. Generate lots first."); @@ -279,7 +281,29 @@ export function ReceiptsClient() { try { printWindow.document.open(); - printWindow.document.write(buildLotLabelsPrintHtml(detail, locale)); + printWindow.document.write(`Menyiapkan Label${locale === "id" ? "Menyiapkan label lot..." : "Preparing lot labels..."}`); + printWindow.document.close(); + + const printableLots = await Promise.all( + detail.generated_lots.map(async (lot) => { + const qrValue = lot.qr_code_value || lot.barcode_value || lot.lot_code; + const barcodeValue = lot.barcode_value || lot.qr_code_value || lot.lot_code; + return { + lot, + qrValue, + barcodeValue, + qrDataUrl: await QRCode.toDataURL(qrValue, { + width: 160, + margin: 1, + errorCorrectionLevel: "M" + }), + barcodeSvg: buildBarcodeSvg(barcodeValue) + }; + }) + ); + + printWindow.document.open(); + printWindow.document.write(buildLotLabelsPrintHtml(detail, locale, printableLots)); printWindow.document.close(); } catch (err) { if (!printWindow.closed) { @@ -452,7 +476,7 @@ export function ReceiptsClient() { {locale === "id" ? "Cetak receipt" : "Print receipt"} {selectedReceipt.generated_lots.length > 0 ? ( - @@ -497,6 +521,23 @@ function escapeHtml(value: string) { .replaceAll("'", "'"); } +function buildBarcodeSvg(value: string) { + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + JsBarcode(svg, value, { + format: "CODE128", + displayValue: false, + margin: 0, + width: 1.5, + height: 44 + }); + + svg.setAttribute("width", "100%"); + svg.setAttribute("height", "52"); + svg.setAttribute("preserveAspectRatio", "none"); + + return svg.outerHTML; +} + function buildReceiptPrintHtml(detail: ReceiptDetail, locale: string) { const isIndonesian = locale === "id"; const safeReceiptNo = escapeHtml(detail.receipt_no); @@ -597,14 +638,25 @@ function buildReceiptPrintHtml(detail: ReceiptDetail, locale: string) { `; } -function buildLotLabelsPrintHtml(detail: ReceiptDetail, locale: string) { +function buildLotLabelsPrintHtml( + detail: ReceiptDetail, + locale: string, + printableLots: Array<{ + lot: ReceiptDetail["generated_lots"][number]; + qrValue: string; + barcodeValue: string; + qrDataUrl: string; + barcodeSvg: string; + }> +) { const isIndonesian = locale === "id"; - const labels = detail.generated_lots.map((lot) => { + const labels = printableLots.map(({ lot, qrValue, barcodeValue, qrDataUrl, barcodeSvg }) => { const safeLotCode = escapeHtml(lot.lot_code); const safeGrade = escapeHtml(lot.grade?.name ?? "-"); const safeWarehouse = escapeHtml(lot.warehouse.name); const safeLocation = escapeHtml(lot.location?.name ?? "-"); - const codeValue = escapeHtml(lot.qr_code_value || lot.barcode_value || lot.lot_code); + const safeQrValue = escapeHtml(qrValue); + const safeBarcodeValue = escapeHtml(barcodeValue); return `
@@ -614,12 +666,21 @@ function buildLotLabelsPrintHtml(detail: ReceiptDetail, locale: string) {
${escapeHtml(lot.status)}
-
${codeValue}
-
-
Grade${safeGrade}
-
Qty${escapeHtml(formatQuantity(lot.original_qty, locale, lot.unit.code))}
-
${isIndonesian ? "Gudang" : "Warehouse"}${safeWarehouse}
-
${isIndonesian ? "Lokasi" : "Location"}${safeLocation}
+
+
+ QR ${safeLotCode} +

${safeQrValue}

+
+
+
Grade${safeGrade}
+
Qty${escapeHtml(formatQuantity(lot.original_qty, locale, lot.unit.code))}
+
${isIndonesian ? "Gudang" : "Warehouse"}${safeWarehouse}
+
${isIndonesian ? "Lokasi" : "Location"}${safeLocation}
+
+
+
+
${barcodeSvg}
+

${safeBarcodeValue}

${isIndonesian ? "Receipt" : "Receipt"} ${escapeHtml(detail.receipt_no)} @@ -636,13 +697,18 @@ function buildLotLabelsPrintHtml(detail: ReceiptDetail, locale: string) {