summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-07 04:43:43 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-07 04:43:43 +0000
commit7f973de2a814a2040a646161b563c8990b7e2fd2 (patch)
treea00faad2246fe03f448de758c7171c63294819c9
parent18ca4ad784aeeab9ab7a13bbc8b3c13b42ca5e49 (diff)
(임수민) 기본계약서 서명란 변수 추가
-rw-r--r--lib/basic-contract/template/basic-contract-template-viewer.tsx40
-rw-r--r--lib/basic-contract/template/template-editor-wrapper.tsx97
2 files changed, 106 insertions, 31 deletions
diff --git a/lib/basic-contract/template/basic-contract-template-viewer.tsx b/lib/basic-contract/template/basic-contract-template-viewer.tsx
index 018db3a0..52ea4153 100644
--- a/lib/basic-contract/template/basic-contract-template-viewer.tsx
+++ b/lib/basic-contract/template/basic-contract-template-viewer.tsx
@@ -17,6 +17,7 @@ interface BasicContractTemplateViewerProps {
filePath?: string;
instance: WebViewerInstance | null;
setInstance: Dispatch<SetStateAction<WebViewerInstance | null>>;
+ onSignatureFieldFound?: (searchText: string) => void; // 서명란 발견 시 콜백
}
// 서명란 위치 정보 인터페이스
@@ -116,6 +117,7 @@ export function BasicContractTemplateViewer({
filePath,
instance,
setInstance,
+ onSignatureFieldFound,
}: BasicContractTemplateViewerProps) {
const [fileLoading, setFileLoading] = useState<boolean>(true);
const [signatureLocation, setSignatureLocation] = useState<SignatureFieldLocation | null>(null);
@@ -244,6 +246,10 @@ export function BasicContractTemplateViewer({
if (location) {
setSignatureLocation(location);
console.log("✅ 서명란 위치 발견:", location);
+ // 서명란 발견 시 부모 컴포넌트에 알림
+ if (onSignatureFieldFound) {
+ onSignatureFieldFound(location.searchText);
+ }
}
}, 2000);
};
@@ -256,6 +262,10 @@ export function BasicContractTemplateViewer({
const location = await findSignatureFieldLocation(instance);
if (location) {
setSignatureLocation(location);
+ // 서명란 발견 시 부모 컴포넌트에 알림
+ if (onSignatureFieldFound) {
+ onSignatureFieldFound(location.searchText);
+ }
}
}, 2000);
}
@@ -326,21 +336,6 @@ export function BasicContractTemplateViewer({
// 기존 SignViewer와 동일한 렌더링 (확대 문제 해결)
return (
<div className="relative w-full h-full overflow-hidden flex flex-col">
- {/* 서명란으로 이동 버튼 */}
- {signatureLocation && !fileLoading && (
- <div className="absolute top-2 right-2 z-20">
- <Button
- variant="outline"
- size="sm"
- className="h-7 px-2 text-xs bg-blue-50 hover:bg-blue-100 border-blue-200"
- onClick={() => navigateToSignatureField(instance, signatureLocation)}
- >
- <Target className="h-3 w-3 mr-1" />
- 서명란으로 이동
- </Button>
- </div>
- )}
-
<div
ref={viewer}
className="w-full h-full"
@@ -350,6 +345,21 @@ export function BasicContractTemplateViewer({
contain: 'layout style paint', // CSS containment로 격리
}}
>
+ {/* 서명란으로 이동 버튼 - 툴바 아래에 배치 (편집 도구와 겹치지 않도록) */}
+ {signatureLocation && !fileLoading && (
+ <div className="absolute right-4 z-30" style={{ top: '70px' }}>
+ <Button
+ variant="outline"
+ size="sm"
+ className="h-7 px-2 text-xs bg-blue-50 hover:bg-blue-100 border-blue-200 shadow-sm"
+ onClick={() => navigateToSignatureField(instance, signatureLocation)}
+ >
+ <Target className="h-3 w-3 mr-1" />
+ 서명란으로 이동
+ </Button>
+ </div>
+ )}
+
{fileLoading && (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-white bg-opacity-90 z-10">
<Loader2 className="h-8 w-8 text-blue-500 animate-spin mb-4" />
diff --git a/lib/basic-contract/template/template-editor-wrapper.tsx b/lib/basic-contract/template/template-editor-wrapper.tsx
index af5d42a8..2c1c7e4d 100644
--- a/lib/basic-contract/template/template-editor-wrapper.tsx
+++ b/lib/basic-contract/template/template-editor-wrapper.tsx
@@ -18,25 +18,35 @@ interface TemplateEditorWrapperProps {
}
const getVariablesForTemplate = (templateName: string): string[] => {
+ let variables: string[] = [];
+
// 정확한 매치 먼저 확인
if (TEMPLATE_VARIABLES_MAP[templateName as keyof typeof TEMPLATE_VARIABLES_MAP]) {
- return [...TEMPLATE_VARIABLES_MAP[templateName as keyof typeof TEMPLATE_VARIABLES_MAP]];
- }
-
- // GTC가 포함된 경우 확인
- if (templateName.includes("GTC")) {
- return [...TEMPLATE_VARIABLES_MAP["GTC"]];
- }
-
- // 다른 키워드들도 포함 관계로 확인
- for (const [key, variables] of Object.entries(TEMPLATE_VARIABLES_MAP)) {
- if (templateName.includes(key)) {
- return [...variables];
+ variables = [...TEMPLATE_VARIABLES_MAP[templateName as keyof typeof TEMPLATE_VARIABLES_MAP]];
+ } else if (templateName.includes("GTC")) {
+ // GTC가 포함된 경우 확인
+ variables = [...TEMPLATE_VARIABLES_MAP["GTC"]];
+ } else {
+ // 다른 키워드들도 포함 관계로 확인
+ let found = false;
+ for (const [key, vars] of Object.entries(TEMPLATE_VARIABLES_MAP)) {
+ if (templateName.includes(key)) {
+ variables = [...vars];
+ found = true;
+ break;
+ }
+ }
+
+ // 기본값 반환
+ if (!found) {
+ variables = ["company_name", "company_address", "representative_name", "signature_date"];
}
}
- // 기본값 반환
- return ["company_name", "company_address", "representative_name", "signature_date"];
+ // 모든 템플릿에 서명란 변수 추가 (중복 제거)
+ const signatureVars = ["vendor_signature", "buyer_signature"];
+ const allVariables = [...variables, ...signatureVars];
+ return [...new Set(allVariables)]; // 중복 제거
};
// 템플릿 이름별 변수 매핑
@@ -67,9 +77,23 @@ const VARIABLE_DESCRIPTION_MAP = {
"tax_id": "사업자등록번호",
"phone_number": "전화번호",
"phone": "전화번호",
- "email": "이메일"
+ "email": "이메일",
+ "vendor_signature": "협력업체 서명란",
+ "buyer_signature": "구매자 서명란",
+ "협력업체_서명란": "협력업체 서명란",
+ "삼성중공업_서명란": "구매자 서명란"
} as const;
+// 서명란 텍스트를 변수명으로 변환
+const getSignatureVariableName = (searchText: string): string => {
+ if (searchText === '협력업체_서명란') {
+ return 'vendor_signature';
+ } else if (searchText === '삼성중공업_서명란') {
+ return 'buyer_signature';
+ }
+ return searchText;
+};
+
// 변수 패턴 감지를 위한 정규식
const VARIABLE_PATTERN = /\{\{([^}]+)\}\}/g;
@@ -86,6 +110,7 @@ export function TemplateEditorWrapper({
const [documentVariables, setDocumentVariables] = React.useState<string[]>([]);
const [templateName, setTemplateName] = React.useState<string>("");
const [predefinedVariables, setPredefinedVariables] = React.useState<string[]>([]);
+ const [signatureVariables, setSignatureVariables] = React.useState<string[]>([]); // 서명란 변수
// 템플릿 이름 로드 및 변수 설정
React.useEffect(() => {
@@ -359,7 +384,7 @@ export function TemplateEditorWrapper({
</div>
{/* 변수 도구 */}
- {(documentVariables.length > 0 || predefinedVariables.length > 0) && (
+ {(documentVariables.length > 0 || predefinedVariables.length > 0 || signatureVariables.length > 0) && (
<div className="mt-3 p-3 bg-white rounded border">
<div className="mb-2">
<h4 className="text-sm font-medium flex items-center">
@@ -388,6 +413,36 @@ export function TemplateEditorWrapper({
</div>
)}
+ {/* 서명란 변수 */}
+ {signatureVariables.length > 0 && (
+ <div>
+ <p className="text-xs text-muted-foreground mb-2">
+ 서명란 변수 (클릭하여 복사):
+ </p>
+ <TooltipProvider>
+ <div className="flex flex-wrap gap-1">
+ {signatureVariables.map((variable, index) => (
+ <Tooltip key={`signature-${variable}-${index}`}>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-6 px-2 text-xs hover:bg-green-50 border border-green-200"
+ onClick={() => insertVariable(variable)}
+ >
+ {`{{${variable}}}`}
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p>{VARIABLE_DESCRIPTION_MAP[variable as keyof typeof VARIABLE_DESCRIPTION_MAP] || variable}</p>
+ </TooltipContent>
+ </Tooltip>
+ ))}
+ </div>
+ </TooltipProvider>
+ </div>
+ )}
+
{/* 템플릿별 미리 정의된 변수들 */}
{predefinedVariables.length > 0 && (
<div>
@@ -430,6 +485,16 @@ export function TemplateEditorWrapper({
filePath={filePath}
instance={instance}
setInstance={setInstance}
+ onSignatureFieldFound={(searchText) => {
+ // 서명란 발견 시 변수명으로 변환하여 추가
+ const variableName = getSignatureVariableName(searchText);
+ setSignatureVariables(prev => {
+ if (!prev.includes(variableName)) {
+ return [...prev, variableName];
+ }
+ return prev;
+ });
+ }}
/>
</div>
</div>