231 lines
7.4 KiB
TypeScript
231 lines
7.4 KiB
TypeScript
"use client";
|
|
|
|
import { useRef, useState, useEffect } from "react";
|
|
import JsBarcode from "jsbarcode";
|
|
import QRCode from "qrcode";
|
|
import type { BarcodeRendererProps as BarcodeCanvasRendererProps } from "./types";
|
|
|
|
interface BarcodeProps {
|
|
value: string;
|
|
format: string;
|
|
width: number;
|
|
height: number;
|
|
displayValue: boolean;
|
|
lineColor: string;
|
|
background: string;
|
|
margin: number;
|
|
}
|
|
|
|
function Barcode1D({ value, format, width, height, displayValue, lineColor, background, margin }: BarcodeProps) {
|
|
const svgRef = useRef<SVGSVGElement>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!svgRef.current || !value) return;
|
|
setError(null);
|
|
|
|
try {
|
|
let isValid = true;
|
|
let errorMsg = "";
|
|
const trimmedValue = value.trim();
|
|
|
|
if (format === "EAN13" && !/^\d{12,13}$/.test(trimmedValue)) {
|
|
isValid = false;
|
|
errorMsg = "EAN-13: 12~13자리 숫자 필요";
|
|
} else if (format === "EAN8" && !/^\d{7,8}$/.test(trimmedValue)) {
|
|
isValid = false;
|
|
errorMsg = "EAN-8: 7~8자리 숫자 필요";
|
|
} else if (format === "UPC" && !/^\d{11,12}$/.test(trimmedValue)) {
|
|
isValid = false;
|
|
errorMsg = "UPC: 11~12자리 숫자 필요";
|
|
}
|
|
|
|
if (!isValid) {
|
|
setError(errorMsg);
|
|
return;
|
|
}
|
|
|
|
const barcodeFormat = format.toLowerCase();
|
|
const bgColor = background === "transparent" ? "" : background;
|
|
|
|
JsBarcode(svgRef.current, trimmedValue, {
|
|
format: barcodeFormat,
|
|
width: 2,
|
|
height: Math.max(30, height - (displayValue ? 30 : 10)),
|
|
displayValue,
|
|
lineColor,
|
|
background: bgColor,
|
|
margin,
|
|
fontSize: 12,
|
|
textMargin: 2,
|
|
});
|
|
} catch (err: any) {
|
|
setError(err?.message || "바코드 생성 실패");
|
|
}
|
|
}, [value, format, width, height, displayValue, lineColor, background, margin]);
|
|
|
|
return (
|
|
<div className="relative h-full w-full">
|
|
<svg ref={svgRef} className={`max-h-full max-w-full ${error ? "hidden" : ""}`} />
|
|
{error && (
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center text-xs text-red-500">
|
|
<span>{error}</span>
|
|
<span className="mt-1 text-gray-400">{value}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
interface QRProps {
|
|
value: string;
|
|
size: number;
|
|
fgColor: string;
|
|
bgColor: string;
|
|
level: "L" | "M" | "Q" | "H";
|
|
}
|
|
|
|
function QR({ value, size, fgColor, bgColor, level }: QRProps) {
|
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!canvasRef.current || !value) return;
|
|
setError(null);
|
|
const lightColor = bgColor === "transparent" ? "#ffffff" : bgColor;
|
|
|
|
QRCode.toCanvas(
|
|
canvasRef.current,
|
|
value,
|
|
{
|
|
width: Math.max(50, size),
|
|
margin: 2,
|
|
color: { dark: fgColor, light: lightColor },
|
|
errorCorrectionLevel: level,
|
|
},
|
|
(err) => {
|
|
if (err) setError(err.message || "QR코드 생성 실패");
|
|
},
|
|
);
|
|
}, [value, size, fgColor, bgColor, level]);
|
|
|
|
return (
|
|
<div className="relative h-full w-full">
|
|
<canvas ref={canvasRef} className={`max-h-full max-w-full ${error ? "hidden" : ""}`} />
|
|
{error && (
|
|
<div className="absolute inset-0 flex flex-col items-center justify-center text-xs text-red-500">
|
|
<span>{error}</span>
|
|
<span className="mt-1 text-gray-400">{value}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function BarcodeCanvasRenderer({ component, getQueryResult }: BarcodeCanvasRendererProps) {
|
|
const barcodeType = component.barcodeType || "CODE128";
|
|
const showBarcodeText = component.showBarcodeText !== false;
|
|
const barcodeColor = component.barcodeColor || "#000000";
|
|
const barcodeBackground = component.barcodeBackground || "transparent";
|
|
const barcodeMargin = component.barcodeMargin ?? 10;
|
|
const qrErrorLevel = component.qrErrorCorrectionLevel || "M";
|
|
|
|
const getBarcodeValue = (): string => {
|
|
if (
|
|
barcodeType === "QR" &&
|
|
component.qrUseMultiField &&
|
|
component.qrDataFields &&
|
|
component.qrDataFields.length > 0 &&
|
|
component.queryId
|
|
) {
|
|
const queryResult = getQueryResult(component.queryId);
|
|
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
|
|
if (component.qrIncludeAllRows) {
|
|
const allRowsData: Record<string, string>[] = [];
|
|
queryResult.rows.forEach((row) => {
|
|
const rowData: Record<string, string> = {};
|
|
component.qrDataFields!.forEach((field) => {
|
|
if (field.fieldName && field.label) {
|
|
const val = row[field.fieldName];
|
|
rowData[field.label] = val !== null && val !== undefined ? String(val) : "";
|
|
}
|
|
});
|
|
allRowsData.push(rowData);
|
|
});
|
|
return JSON.stringify(allRowsData);
|
|
}
|
|
const row = queryResult.rows[0];
|
|
const jsonData: Record<string, string> = {};
|
|
component.qrDataFields.forEach((field) => {
|
|
if (field.fieldName && field.label) {
|
|
const val = row[field.fieldName];
|
|
jsonData[field.label] = val !== null && val !== undefined ? String(val) : "";
|
|
}
|
|
});
|
|
return JSON.stringify(jsonData);
|
|
}
|
|
const placeholderData: Record<string, string> = {};
|
|
component.qrDataFields.forEach((field) => {
|
|
if (field.label) placeholderData[field.label] = `{${field.fieldName || "field"}}`;
|
|
});
|
|
return component.qrIncludeAllRows
|
|
? JSON.stringify([placeholderData, { "...": "..." }])
|
|
: JSON.stringify(placeholderData);
|
|
}
|
|
|
|
if (component.barcodeFieldName && component.queryId) {
|
|
const queryResult = getQueryResult(component.queryId);
|
|
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
|
|
if (barcodeType === "QR" && component.qrIncludeAllRows) {
|
|
const allValues = queryResult.rows
|
|
.map((row) => {
|
|
const val = row[component.barcodeFieldName!];
|
|
return val !== null && val !== undefined ? String(val) : "";
|
|
})
|
|
.filter((v) => v !== "");
|
|
return JSON.stringify(allValues);
|
|
}
|
|
const row = queryResult.rows[0];
|
|
const val = row[component.barcodeFieldName];
|
|
if (val !== null && val !== undefined) return String(val);
|
|
}
|
|
if (barcodeType === "QR" && component.qrIncludeAllRows) {
|
|
return JSON.stringify([`{${component.barcodeFieldName}}`, "..."]);
|
|
}
|
|
return `{${component.barcodeFieldName}}`;
|
|
}
|
|
return component.barcodeValue || "SAMPLE123";
|
|
};
|
|
|
|
const barcodeValue = getBarcodeValue();
|
|
const isQR = barcodeType === "QR";
|
|
|
|
return (
|
|
<div
|
|
className="flex h-full w-full items-center justify-center overflow-hidden"
|
|
style={{ backgroundColor: barcodeBackground }}
|
|
>
|
|
{isQR ? (
|
|
<QR
|
|
value={barcodeValue}
|
|
size={Math.min(component.width, component.height) - 10}
|
|
fgColor={barcodeColor}
|
|
bgColor={barcodeBackground}
|
|
level={qrErrorLevel}
|
|
/>
|
|
) : (
|
|
<Barcode1D
|
|
value={barcodeValue}
|
|
format={barcodeType}
|
|
width={component.width}
|
|
height={component.height}
|
|
displayValue={showBarcodeText}
|
|
lineColor={barcodeColor}
|
|
background={barcodeBackground}
|
|
margin={barcodeMargin}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|