Add PDF417 barcode rendering component
Implements client-side PDF417 barcode rendering with React component. Uses useEffect to prevent hydration mismatch by generating barcodes only after component mount. Integrates barcode display in location cards. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
51
app/lib/pdf/renderBarcode.ts
Normal file
51
app/lib/pdf/renderBarcode.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { BarcodeArray } from './pdf417';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a PDF417 barcode matrix to a canvas and returns it as a data URL.
|
||||||
|
*
|
||||||
|
* This function creates an HTML canvas element, draws the barcode by iterating through
|
||||||
|
* the barcode matrix, and converts the canvas to a base64-encoded PNG data URL that
|
||||||
|
* can be used as an image source.
|
||||||
|
*
|
||||||
|
* @param barcodeMatrix - The barcode array generated by the PDF417 encoder containing
|
||||||
|
* the barcode matrix data with dimensions and binary code values
|
||||||
|
* @param blockWidth - The width in pixels of each individual barcode module (bar/space unit)
|
||||||
|
* @param blockHeight - The height in pixels of each individual barcode module (bar/space unit)
|
||||||
|
*
|
||||||
|
* @returns A data URL string (base64-encoded PNG) representing the rendered barcode image,
|
||||||
|
* suitable for use in an HTML img src attribute
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const pdf417 = createPDF417();
|
||||||
|
* pdf417.init("Hello World", 2, 2);
|
||||||
|
* const barcodeArray = pdf417.getBarcodeArray();
|
||||||
|
* const dataUrl = renderBarcode(barcodeArray, 2, 4);
|
||||||
|
* // dataUrl can now be used: <img src={dataUrl} />
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function renderBarcode(barcodeMatrix: BarcodeArray, blockWidth: number, blockHeight: number) {
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = barcodeMatrix.num_cols * blockWidth;
|
||||||
|
canvas.height = barcodeMatrix.num_rows * blockHeight;
|
||||||
|
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
||||||
|
|
||||||
|
let positionY = 0;
|
||||||
|
for (let row = 0; row < barcodeMatrix.num_rows; row += 1) {
|
||||||
|
let positionX = 0;
|
||||||
|
|
||||||
|
for (let col = 0; col < barcodeMatrix.num_cols; col += 1) {
|
||||||
|
if (barcodeMatrix.bcode[row][col] === 1) {
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
} else {
|
||||||
|
ctx.fillStyle = '#FFF';
|
||||||
|
}
|
||||||
|
ctx.fillRect(positionX, positionY, blockWidth, blockHeight);
|
||||||
|
positionX += blockWidth;
|
||||||
|
}
|
||||||
|
positionY += blockHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas.toDataURL();
|
||||||
|
}
|
||||||
50
app/ui/Pdf417Barcode.tsx
Normal file
50
app/ui/Pdf417Barcode.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { generateBarcode } from '../lib/pdf/pdf417';
|
||||||
|
import { renderBarcode } from '../lib/pdf/renderBarcode';
|
||||||
|
import { EncodePayment } from 'hub-3a-payment-encoder';
|
||||||
|
|
||||||
|
export const Pdf417Barcode = () => {
|
||||||
|
const [bitmapData, setBitmapData] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
const paymentParams = {
|
||||||
|
Iznos:"123,66", // NOTE: use comma, not period!
|
||||||
|
ImePlatitelja:"Ivan Horvat",
|
||||||
|
AdresaPlatitelja:"Ilica 23",
|
||||||
|
SjedistePlatitelja:"10000 Zagreb",
|
||||||
|
Primatelj:"VODOOPSKRBA I ODV. D.O.O.",
|
||||||
|
AdresaPrimatelja:"FOLNEGOVIĆEVA 1",
|
||||||
|
SjedistePrimatelja:"ZAGREB",
|
||||||
|
IBAN:"HR8924020061100679445",
|
||||||
|
ModelPlacanja: "HR00", // MUST contain "HR" prefix!
|
||||||
|
PozivNaBroj:"2025-05",
|
||||||
|
SifraNamjene:"",
|
||||||
|
OpisPlacanja:"Budakova Režije",
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const hub3a_text = EncodePayment(paymentParams);
|
||||||
|
|
||||||
|
const barcodeMatrix = generateBarcode(hub3a_text);
|
||||||
|
const bitmap = renderBarcode(barcodeMatrix, 2, 2);
|
||||||
|
setBitmapData(bitmap);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Don't render until bitmap is generated (prevents hydration mismatch)
|
||||||
|
if (!bitmapData) {
|
||||||
|
return (
|
||||||
|
<div style={{ width: "350px", height: "92px" }} className="flex items-center justify-center">
|
||||||
|
<span className="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<img src={bitmapData} style={{ width: "350px", height: "92px" }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { formatYearMonth } from "../lib/format";
|
|||||||
import { formatCurrency } from "../lib/formatStrings";
|
import { formatCurrency } from "../lib/formatStrings";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { ViewBillBadge } from "./ViewBillBadge";
|
import { ViewBillBadge } from "./ViewBillBadge";
|
||||||
|
import { Pdf417Barcode } from "./Pdf417Barcode";
|
||||||
|
|
||||||
export interface ViewLocationCardProps {
|
export interface ViewLocationCardProps {
|
||||||
location: BillingLocation
|
location: BillingLocation
|
||||||
@@ -34,6 +35,7 @@ export const ViewLocationCard:FC<ViewLocationCardProps> = ({location: { _id, nam
|
|||||||
</p>
|
</p>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
<Pdf417Barcode />
|
||||||
</div>
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user