ERP-node/frontend/components/report/designer/properties/BarcodeProperties.tsx

359 lines
16 KiB
TypeScript

"use client";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { QrCode, X } from "lucide-react";
import { useReportDesigner } from "@/contexts/ReportDesignerContext";
import type { ComponentConfig } from "@/types/report";
interface Props {
component: ComponentConfig;
/** 우측 패널: "style" | 모달: "data" | 미전달: 전체 표시 (하위 호환) */
section?: "style" | "data";
}
export function BarcodeProperties({ component, section }: Props) {
const { updateComponent, queries, getQueryResult } = useReportDesigner();
const showStyle = !section || section === "style";
const showData = !section || section === "data";
return (
<>
{/* 바코드 스타일 — 우측 패널(section="style")에서 표시 */}
{showStyle && (
<div className="mt-4 space-y-3 rounded-xl border border-cyan-200 bg-cyan-50/50 p-4">
<div className="flex items-center gap-2 text-sm font-semibold text-cyan-700">
<QrCode className="h-4 w-4" />
</div>
{/* 1D 바코드 전용: 텍스트 표시 */}
{component.barcodeType !== "QR" && (
<div className="flex items-center gap-2">
<input
type="checkbox"
id="showBarcodeText"
checked={component.showBarcodeText !== false}
onChange={(e) => updateComponent(component.id, { showBarcodeText: e.target.checked })}
className="h-4 w-4 rounded border-gray-300"
/>
<Label htmlFor="showBarcodeText" className="text-xs">
</Label>
</div>
)}
{/* 색상 설정 */}
<div className="grid grid-cols-2 gap-2">
<div>
<Label className="text-xs"> </Label>
<Input
type="color"
value={component.barcodeColor || "#000000"}
onChange={(e) => updateComponent(component.id, { barcodeColor: e.target.value })}
className="h-9 w-full"
/>
</div>
<div>
<Label className="text-xs"> </Label>
<Input
type="color"
value={component.barcodeBackground || "#ffffff"}
onChange={(e) => updateComponent(component.id, { barcodeBackground: e.target.value })}
className="h-9 w-full"
/>
</div>
</div>
{/* 여백 */}
<div>
<Label className="text-xs"> (px)</Label>
<Input
type="number"
value={component.barcodeMargin ?? 10}
onChange={(e) => updateComponent(component.id, { barcodeMargin: Number(e.target.value) })}
min={0}
max={50}
className="h-9"
/>
</div>
</div>
)}
{/* 바코드 데이터 — 모달(section="data")에서 표시 */}
{showData && (
<div className="mt-4 space-y-3 rounded-xl border border-cyan-200 bg-cyan-50/50 p-4">
<div className="flex items-center gap-2 text-sm font-semibold text-cyan-700">
<QrCode className="h-4 w-4" />
</div>
{/* 바코드 타입 */}
<div>
<Label className="text-xs"> </Label>
<Select
value={component.barcodeType || "CODE128"}
onValueChange={(value) => {
const newType = value as "CODE128" | "CODE39" | "EAN13" | "EAN8" | "UPC" | "QR";
if (newType === "QR") {
const size = Math.max(component.width, component.height);
updateComponent(component.id, { barcodeType: newType, width: size, height: size });
} else {
updateComponent(component.id, { barcodeType: newType });
}
}}
>
<SelectTrigger className="h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="CODE128">CODE128 ()</SelectItem>
<SelectItem value="CODE39">CODE39 ()</SelectItem>
<SelectItem value="EAN13">EAN-13 ()</SelectItem>
<SelectItem value="EAN8">EAN-8 ()</SelectItem>
<SelectItem value="UPC">UPC ()</SelectItem>
<SelectItem value="QR">QR코드</SelectItem>
</SelectContent>
</Select>
</div>
{/* QR 오류 보정 수준 */}
{component.barcodeType === "QR" && (
<div>
<Label className="text-xs"> </Label>
<Select
value={component.qrErrorCorrectionLevel || "M"}
onValueChange={(value) =>
updateComponent(component.id, { qrErrorCorrectionLevel: value as "L" | "M" | "Q" | "H" })
}
>
<SelectTrigger className="h-9">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="L">L (7% )</SelectItem>
<SelectItem value="M">M (15% )</SelectItem>
<SelectItem value="Q">Q (25% )</SelectItem>
<SelectItem value="H">H (30% )</SelectItem>
</SelectContent>
</Select>
<p className="mt-1 text-[10px] text-gray-500"> </p>
</div>
)}
{/* 바코드 값 입력 (쿼리 연결 없을 때) */}
{!component.queryId && (
<div>
<Label className="text-xs"> </Label>
<Input
type="text"
value={component.barcodeValue || ""}
onChange={(e) => updateComponent(component.id, { barcodeValue: e.target.value })}
placeholder={
component.barcodeType === "EAN13"
? "13자리 숫자"
: component.barcodeType === "EAN8"
? "8자리 숫자"
: component.barcodeType === "UPC"
? "12자리 숫자"
: "바코드에 표시할 값"
}
className="h-9"
/>
{(component.barcodeType === "EAN13" ||
component.barcodeType === "EAN8" ||
component.barcodeType === "UPC") && (
<p className="mt-1 text-[10px] text-gray-500">
{component.barcodeType === "EAN13" && "EAN-13: 12~13자리 숫자 필요"}
{component.barcodeType === "EAN8" && "EAN-8: 7~8자리 숫자 필요"}
{component.barcodeType === "UPC" && "UPC: 11~12자리 숫자 필요"}
</p>
)}
</div>
)}
{/* 쿼리 연결 안내 — 인라인 안내 텍스트 (수정하지 않음) */}
{!component.queryId && (
<div className="rounded border border-cyan-200 bg-cyan-100 p-2 text-xs text-cyan-800">
.
</div>
)}
{/* 쿼리 연결 시 필드 선택 */}
{component.queryId && (
<>
{/* QR코드: 다중 필드 모드 토글 */}
{component.barcodeType === "QR" && (
<div className="flex items-center gap-2">
<input
type="checkbox"
id="qrUseMultiField"
checked={component.qrUseMultiField === true}
onChange={(e) =>
updateComponent(component.id, {
qrUseMultiField: e.target.checked,
...(e.target.checked && { barcodeFieldName: "" }),
})
}
className="h-4 w-4 rounded border-gray-300"
/>
<Label htmlFor="qrUseMultiField" className="text-xs">
(JSON )
</Label>
</div>
)}
{/* 단일 필드 모드 (1D 바코드 또는 QR 단일 모드) */}
{(component.barcodeType !== "QR" || !component.qrUseMultiField) && (
<div>
<Label className="text-xs"> </Label>
<Select
value={component.barcodeFieldName || "none"}
onValueChange={(value) =>
updateComponent(component.id, { barcodeFieldName: value === "none" ? "" : value })
}
>
<SelectTrigger className="h-9">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{(() => {
const query = queries.find((q) => q.id === component.queryId);
const result = query ? getQueryResult(query.id) : null;
if (result && result.fields) {
return result.fields.map((field: string) => (
<SelectItem key={field} value={field}>
{field}
</SelectItem>
));
}
return null;
})()}
</SelectContent>
</Select>
</div>
)}
{/* QR코드 다중 필드 모드 UI */}
{component.barcodeType === "QR" && component.qrUseMultiField && (
<div className="space-y-2">
<div className="flex items-center justify-between">
<Label className="text-xs">JSON </Label>
<Button
variant="outline"
size="sm"
className="h-6 px-2 text-xs"
onClick={() => {
const currentFields = component.qrDataFields || [];
updateComponent(component.id, {
qrDataFields: [...currentFields, { fieldName: "", label: "" }],
});
}}
>
+
</Button>
</div>
{/* 필드 목록 */}
<div className="max-h-[200px] space-y-2 overflow-y-auto">
{(component.qrDataFields || []).map((field, index) => (
<div key={index} className="flex items-center gap-1 rounded border p-2">
<div className="flex-1 space-y-1">
<Select
value={field.fieldName || "none"}
onValueChange={(value) => {
const newFields = [...(component.qrDataFields || [])];
newFields[index] = {
...newFields[index],
fieldName: value === "none" ? "" : value,
label: newFields[index].label || (value === "none" ? "" : value),
};
updateComponent(component.id, { qrDataFields: newFields });
}}
>
<SelectTrigger className="h-7 text-xs">
<SelectValue placeholder="필드 선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none"> </SelectItem>
{(() => {
const query = queries.find((q) => q.id === component.queryId);
const result = query ? getQueryResult(query.id) : null;
if (result && result.fields) {
return result.fields.map((f: string) => (
<SelectItem key={f} value={f}>
{f}
</SelectItem>
));
}
return null;
})()}
</SelectContent>
</Select>
<Input
type="text"
value={field.label || ""}
onChange={(e) => {
const newFields = [...(component.qrDataFields || [])];
newFields[index] = { ...newFields[index], label: e.target.value };
updateComponent(component.id, { qrDataFields: newFields });
}}
placeholder="JSON 키 이름"
className="h-7 text-xs"
/>
</div>
<Button
variant="ghost"
size="sm"
className="h-7 w-7 p-0 text-red-500 hover:text-red-700"
onClick={() => {
const newFields = (component.qrDataFields || []).filter((_, i) => i !== index);
updateComponent(component.id, { qrDataFields: newFields });
}}
>
<X className="h-4 w-4" />
</Button>
</div>
))}
</div>
{(component.qrDataFields || []).length === 0 && (
<p className="text-center text-xs text-gray-400"> </p>
)}
<p className="text-[10px] text-gray-500">
:{" "}
{component.qrIncludeAllRows
? `[{"${(component.qrDataFields || []).map((f) => f.label || "key").join('":"값","')}"}, ...]`
: `{"${(component.qrDataFields || []).map((f) => f.label || "key").join('":"값","')}":"값"}`}
</p>
</div>
)}
{/* QR코드 모든 행 포함 옵션 */}
{component.barcodeType === "QR" && component.queryId && (
<div className="flex items-center gap-2">
<input
type="checkbox"
id="qrIncludeAllRows"
checked={component.qrIncludeAllRows === true}
onChange={(e) => updateComponent(component.id, { qrIncludeAllRows: e.target.checked })}
className="h-4 w-4 rounded border-gray-300"
/>
<Label htmlFor="qrIncludeAllRows" className="text-xs">
()
</Label>
</div>
)}
</>
)}
</div>
)}
</>
);
}