바코드/QR코드 투명 배경 처리 및 QR코드 에러 복구 버그 수정
This commit is contained in:
parent
506a31df02
commit
c5cb4336e5
|
|
@ -19,7 +19,16 @@ interface BarcodeRendererProps {
|
||||||
margin: number;
|
margin: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function BarcodeRenderer({ value, format, width, height, displayValue, lineColor, background, margin }: BarcodeRendererProps) {
|
function BarcodeRenderer({
|
||||||
|
value,
|
||||||
|
format,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
displayValue,
|
||||||
|
lineColor,
|
||||||
|
background,
|
||||||
|
margin,
|
||||||
|
}: BarcodeRendererProps) {
|
||||||
const svgRef = useRef<SVGSVGElement>(null);
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
|
@ -53,6 +62,8 @@ function BarcodeRenderer({ value, format, width, height, displayValue, lineColor
|
||||||
|
|
||||||
// JsBarcode는 format을 소문자로 받음
|
// JsBarcode는 format을 소문자로 받음
|
||||||
const barcodeFormat = format.toLowerCase();
|
const barcodeFormat = format.toLowerCase();
|
||||||
|
// transparent는 빈 문자열로 변환 (SVG 배경 없음)
|
||||||
|
const bgColor = background === "transparent" ? "" : background;
|
||||||
|
|
||||||
JsBarcode(svgRef.current, trimmedValue, {
|
JsBarcode(svgRef.current, trimmedValue, {
|
||||||
format: barcodeFormat,
|
format: barcodeFormat,
|
||||||
|
|
@ -60,7 +71,7 @@ function BarcodeRenderer({ value, format, width, height, displayValue, lineColor
|
||||||
height: Math.max(30, height - (displayValue ? 30 : 10)),
|
height: Math.max(30, height - (displayValue ? 30 : 10)),
|
||||||
displayValue: displayValue,
|
displayValue: displayValue,
|
||||||
lineColor: lineColor,
|
lineColor: lineColor,
|
||||||
background: background,
|
background: bgColor,
|
||||||
margin: margin,
|
margin: margin,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
textMargin: 2,
|
textMargin: 2,
|
||||||
|
|
@ -74,10 +85,7 @@ function BarcodeRenderer({ value, format, width, height, displayValue, lineColor
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full">
|
<div className="relative h-full w-full">
|
||||||
{/* SVG는 항상 렌더링 (에러 시 숨김) */}
|
{/* SVG는 항상 렌더링 (에러 시 숨김) */}
|
||||||
<svg
|
<svg ref={svgRef} className={`max-h-full max-w-full ${error ? "hidden" : ""}`} />
|
||||||
ref={svgRef}
|
|
||||||
className={`max-h-full max-w-full ${error ? "hidden" : ""}`}
|
|
||||||
/>
|
|
||||||
{/* 에러 메시지 오버레이 */}
|
{/* 에러 메시지 오버레이 */}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center text-xs text-red-500">
|
<div className="absolute inset-0 flex flex-col items-center justify-center text-xs text-red-500">
|
||||||
|
|
@ -103,7 +111,14 @@ function QRCodeRenderer({ value, size, fgColor, bgColor, level }: QRCodeRenderer
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canvasRef.current && value) {
|
if (!canvasRef.current || !value) return;
|
||||||
|
|
||||||
|
// 매번 에러 상태 초기화 후 재시도
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
// qrcode 라이브러리는 hex 색상만 지원, transparent는 흰색으로 대체
|
||||||
|
const lightColor = bgColor === "transparent" ? "#ffffff" : bgColor;
|
||||||
|
|
||||||
QRCode.toCanvas(
|
QRCode.toCanvas(
|
||||||
canvasRef.current,
|
canvasRef.current,
|
||||||
value,
|
value,
|
||||||
|
|
@ -112,31 +127,32 @@ function QRCodeRenderer({ value, size, fgColor, bgColor, level }: QRCodeRenderer
|
||||||
margin: 2,
|
margin: 2,
|
||||||
color: {
|
color: {
|
||||||
dark: fgColor,
|
dark: fgColor,
|
||||||
light: bgColor,
|
light: lightColor,
|
||||||
},
|
},
|
||||||
errorCorrectionLevel: level,
|
errorCorrectionLevel: level,
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
setError("QR코드 생성 실패");
|
// 실제 에러 메시지 표시
|
||||||
} else {
|
setError(err.message || "QR코드 생성 실패");
|
||||||
setError(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}, [value, size, fgColor, bgColor, level]);
|
}, [value, size, fgColor, bgColor, level]);
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col items-center justify-center text-xs text-red-500">
|
<div className="relative h-full w-full">
|
||||||
|
{/* Canvas는 항상 렌더링 (에러 시 숨김) */}
|
||||||
|
<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>{error}</span>
|
||||||
<span className="mt-1 text-gray-400">{value}</span>
|
<span className="mt-1 text-gray-400">{value}</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return <canvas ref={canvasRef} className="max-h-full max-w-full" />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CanvasComponentProps {
|
interface CanvasComponentProps {
|
||||||
|
|
@ -814,7 +830,12 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 쿼리 바인딩된 값 가져오기
|
// 쿼리 바인딩된 값 가져오기
|
||||||
const getCalcItemValue = (item: { label: string; value: number | string; operator: string; fieldName?: string }): number => {
|
const getCalcItemValue = (item: {
|
||||||
|
label: string;
|
||||||
|
value: number | string;
|
||||||
|
operator: string;
|
||||||
|
fieldName?: string;
|
||||||
|
}): number => {
|
||||||
if (item.fieldName && component.queryId) {
|
if (item.fieldName && component.queryId) {
|
||||||
const queryResult = getQueryResult(component.queryId);
|
const queryResult = getQueryResult(component.queryId);
|
||||||
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
|
if (queryResult && queryResult.rows && queryResult.rows.length > 0) {
|
||||||
|
|
@ -831,12 +852,16 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||||
if (calcItems.length === 0) return 0;
|
if (calcItems.length === 0) return 0;
|
||||||
|
|
||||||
// 첫 번째 항목은 기준값
|
// 첫 번째 항목은 기준값
|
||||||
let result = getCalcItemValue(calcItems[0] as { label: string; value: number | string; operator: string; fieldName?: string });
|
let result = getCalcItemValue(
|
||||||
|
calcItems[0] as { label: string; value: number | string; operator: string; fieldName?: string },
|
||||||
|
);
|
||||||
|
|
||||||
// 두 번째 항목부터 연산자 적용
|
// 두 번째 항목부터 연산자 적용
|
||||||
for (let i = 1; i < calcItems.length; i++) {
|
for (let i = 1; i < calcItems.length; i++) {
|
||||||
const item = calcItems[i];
|
const item = calcItems[i];
|
||||||
const val = getCalcItemValue(item as { label: string; value: number | string; operator: string; fieldName?: string });
|
const val = getCalcItemValue(
|
||||||
|
item as { label: string; value: number | string; operator: string; fieldName?: string },
|
||||||
|
);
|
||||||
switch (item.operator) {
|
switch (item.operator) {
|
||||||
case "+":
|
case "+":
|
||||||
result += val;
|
result += val;
|
||||||
|
|
@ -861,7 +886,11 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||||
{/* 항목 목록 */}
|
{/* 항목 목록 */}
|
||||||
<div className="flex-1 overflow-auto px-2 py-1">
|
<div className="flex-1 overflow-auto px-2 py-1">
|
||||||
{calcItems.map((item: { label: string; value: number | string; operator: string; fieldName?: string }, index: number) => {
|
{calcItems.map(
|
||||||
|
(
|
||||||
|
item: { label: string; value: number | string; operator: string; fieldName?: string },
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
const itemValue = getCalcItemValue(item);
|
const itemValue = getCalcItemValue(item);
|
||||||
return (
|
return (
|
||||||
<div key={index} className="flex items-center justify-between py-1">
|
<div key={index} className="flex items-center justify-between py-1">
|
||||||
|
|
@ -886,13 +915,11 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
},
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* 구분선 */}
|
{/* 구분선 */}
|
||||||
<div
|
<div className="mx-1 flex-shrink-0 border-t" style={{ borderColor: component.borderColor || "#374151" }} />
|
||||||
className="mx-1 flex-shrink-0 border-t"
|
|
||||||
style={{ borderColor: component.borderColor || "#374151" }}
|
|
||||||
/>
|
|
||||||
{/* 결과 */}
|
{/* 결과 */}
|
||||||
<div className="flex items-center justify-between px-2 py-2">
|
<div className="flex items-center justify-between px-2 py-2">
|
||||||
<span
|
<span
|
||||||
|
|
@ -923,7 +950,7 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||||
const barcodeType = component.barcodeType || "CODE128";
|
const barcodeType = component.barcodeType || "CODE128";
|
||||||
const showBarcodeText = component.showBarcodeText !== false;
|
const showBarcodeText = component.showBarcodeText !== false;
|
||||||
const barcodeColor = component.barcodeColor || "#000000";
|
const barcodeColor = component.barcodeColor || "#000000";
|
||||||
const barcodeBackground = component.barcodeBackground || "#ffffff";
|
const barcodeBackground = component.barcodeBackground || "transparent";
|
||||||
const barcodeMargin = component.barcodeMargin ?? 10;
|
const barcodeMargin = component.barcodeMargin ?? 10;
|
||||||
const qrErrorLevel = component.qrErrorCorrectionLevel || "M";
|
const qrErrorLevel = component.qrErrorCorrectionLevel || "M";
|
||||||
|
|
||||||
|
|
@ -1101,15 +1128,16 @@ export function CanvasComponent({ component }: CanvasComponentProps) {
|
||||||
component.type === "divider"
|
component.type === "divider"
|
||||||
? component.orientation === "vertical"
|
? component.orientation === "vertical"
|
||||||
? "bottom-0 left-1/2 cursor-s-resize" // 세로 구분선: 하단 중앙
|
? "bottom-0 left-1/2 cursor-s-resize" // 세로 구분선: 하단 중앙
|
||||||
: "right-0 top-1/2 cursor-e-resize" // 가로 구분선: 우측 중앙
|
: "top-1/2 right-0 cursor-e-resize" // 가로 구분선: 우측 중앙
|
||||||
: "right-0 bottom-0 cursor-se-resize" // 일반 컴포넌트: 우하단
|
: "right-0 bottom-0 cursor-se-resize" // 일반 컴포넌트: 우하단
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
transform: component.type === "divider"
|
transform:
|
||||||
|
component.type === "divider"
|
||||||
? component.orientation === "vertical"
|
? component.orientation === "vertical"
|
||||||
? "translate(-50%, 50%)" // 세로 구분선
|
? "translate(-50%, 50%)" // 세로 구분선
|
||||||
: "translate(50%, -50%)" // 가로 구분선
|
: "translate(50%, -50%)" // 가로 구분선
|
||||||
: "translate(50%, 50%)" // 일반 컴포넌트
|
: "translate(50%, 50%)", // 일반 컴포넌트
|
||||||
}}
|
}}
|
||||||
onMouseDown={handleResizeStart}
|
onMouseDown={handleResizeStart}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ export function ReportDesignerCanvas() {
|
||||||
barcodeFieldName: "",
|
barcodeFieldName: "",
|
||||||
showBarcodeText: true,
|
showBarcodeText: true,
|
||||||
barcodeColor: "#000000",
|
barcodeColor: "#000000",
|
||||||
barcodeBackground: "#ffffff",
|
barcodeBackground: "transparent",
|
||||||
barcodeMargin: 10,
|
barcodeMargin: 10,
|
||||||
qrErrorCorrectionLevel: "M" as const,
|
qrErrorCorrectionLevel: "M" as const,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue