summaryrefslogtreecommitdiff
path: root/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract/viewer/basic-contract-sign-viewer.tsx')
-rw-r--r--lib/basic-contract/viewer/basic-contract-sign-viewer.tsx164
1 files changed, 129 insertions, 35 deletions
diff --git a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
index 7f5fa027..adc735e9 100644
--- a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
+++ b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
@@ -697,6 +697,9 @@ export function BasicContractSignViewer({
}: BasicContractSignViewerProps) {
const { toast } = useToast();
+ // πŸ”½ μΆ”κ°€
+ const signatureFieldsRef = useRef<string[]>([]);
+
const [fileLoading, setFileLoading] = useState<boolean>(true);
const [activeTab, setActiveTab] = useState<string>("main");
const [surveyData, setSurveyData] = useState<any>({});
@@ -822,6 +825,11 @@ export function BasicContractSignViewer({
setTimeout(() => cleanupHtmlStyle(), 100);
};
+ // πŸ”½ μ΅œμ‹  μ„œλͺ… ν•„λ“œ 이름듀을 ref에 동기화
+ useEffect(() => {
+ signatureFieldsRef.current = signatureFields;
+ }, [signatureFields]);
+
useEffect(() => {
setShowDialog(isOpen);
@@ -960,33 +968,57 @@ export function BasicContractSignViewer({
documentViewer.addEventListener('documentLoaded', handleDocumentLoaded);
- // ꡬ맀자 λͺ¨λ“œκ°€ 아닐 λ•Œλ§Œ μžλ™ μ„œλͺ… 적용
- if (mode !== 'buyer') {
- annotationManager.addEventListener('annotationChanged', async (annotList, type) => {
- for (const annot of annotList) {
- const { fieldName, X, Y, Width, Height, PageNumber } = annot;
-
- if (type === "add" && annot.Subject === "Widget") {
- const signatureImage = await getVendorSignatureFile()
-
- const stamp = new Annotations.StampAnnotation();
- stamp.PageNumber = PageNumber;
- stamp.X = X;
- stamp.Y = Y;
- stamp.Width = Width;
- stamp.Height = Height;
-
- const dataUrl = signatureImage?.data?.dataUrl;
- if (dataUrl) {
- await stamp.setImageData(dataUrl);
- annot.sign(stamp);
- annot.setFieldFlag(WidgetFlags.READ_ONLY, true);
- }
-
+ // πŸ” μ„œλͺ… κ΄€λ ¨ annotation μ œμ–΄
+ annotationManager.addEventListener('annotationChanged', async (annotList, type) => {
+ if (type !== 'add') return;
+
+ for (const annot of annotList) {
+ const isWidgetSignature =
+ annot instanceof Annotations.SignatureWidgetAnnotation;
+
+ // 1) ν•„λ“œ μ—†λŠ”(Signature) μ„œλͺ…은 μ „λΆ€ 막기
+ // - WebViewer κΈ°λ³Έ "μ–΄λ””λ‚˜ ν•œ 번 μ°λŠ”" μ„œλͺ…은 보톡 Subject κ°€ "Signature" 둜 λ“€μ–΄μ˜΅λ‹ˆλ‹€.
+ // - ν˜Ήμ‹œ λ‹€λ₯΄λ©΄ console.log(annot) μ°μ–΄μ„œ Subject λ₯Ό 맞좰주면 λ©λ‹ˆλ‹€.
+ if (!isWidgetSignature) {
+ if (annot.Subject === 'Signature') {
+ // μ§€μ •λœ μ„œλͺ…λž€ μ™Έ μ„œλͺ… β†’ μ¦‰μ‹œ μ‚­μ œ
+ annotationManager.deleteAnnotation(annot, false);
}
+ continue;
}
- });
- }
+
+ // 2) μ„œλͺ… μœ„μ ―(ν•„λ“œ)은 "μš°λ¦¬κ°€ μƒμ„±ν•œ ν•„λ“œ"만 ν—ˆμš©
+ const field = annot.getField && annot.getField();
+ const name = field?.name as string | undefined;
+ const allowed = signatureFieldsRef.current;
+
+ if (name && allowed.length > 0 && !allowed.includes(name)) {
+ // μš°λ¦¬κ°€ λ§Œλ“  μ„œλͺ… ν•„λ“œκ°€ μ•„λ‹ˆλ©΄ 막기
+ annotationManager.deleteAnnotation(annot, false);
+ continue;
+ }
+
+ // 3) μ—¬κΈ°κΉŒμ§€ ν†΅κ³Όν•œ 경우 = μš°λ¦¬κ°€ λ§Œλ“  μ„œλͺ… ν•„λ“œμ— λŒ€ν•œ μ„œλͺ…
+ // β†’ κΈ°μ‘΄ μžλ™ μ„œλͺ…(ν˜‘λ ₯사 μ„œλͺ…) 둜직 μœ μ§€
+ if (mode !== 'buyer') {
+ const signatureImage = await getVendorSignatureFile();
+ if (!signatureImage?.data?.dataUrl) continue;
+
+ const stamp = new Annotations.StampAnnotation();
+ stamp.PageNumber = annot.PageNumber;
+ stamp.X = annot.X;
+ stamp.Y = annot.Y;
+ stamp.Width = annot.Width;
+ stamp.Height = annot.Height;
+
+ await stamp.setImageData(signatureImage.data.dataUrl);
+
+ const { WidgetFlags } = Annotations;
+ annot.sign(stamp);
+ annot.setFieldFlag(WidgetFlags.READ_ONLY, true);
+ }
+ }
+ });
newInstance.UI.setMinZoomLevel('25%');
newInstance.UI.setMaxZoomLevel('400%');
@@ -1329,6 +1361,15 @@ export function BasicContractSignViewer({
{/* ꡬ맀자 λͺ¨λ“œμ—μ„œλŠ” νƒ­ 없이 단일 λ·°μ–΄λ§Œ ν‘œμ‹œ */}
{allFiles.length > 1 && mode !== 'buyer' ? (
<Tabs value={activeTab} onValueChange={handleTabChange} className="h-full flex flex-col">
+ {/* 쀀법섀문 λ―Έμ™„λ£Œ μ•Œλ¦Ό λ°°λ„ˆ */}
+ {isComplianceTemplate && !surveyData.completed && (
+ <div className="bg-amber-50 border-b-2 border-amber-400 px-4 py-2 flex items-center justify-center space-x-2">
+ <AlertTriangle className="h-4 w-4 text-amber-600" />
+ <span className="text-sm font-semibold text-amber-700">
+ ⚠️ 쀀법 섀문쑰사λ₯Ό λ¨Όμ € μ™„λ£Œν•΄μ£Όμ„Έμš”. μ„œλͺ…을 μ§„ν–‰ν•˜λ €λ©΄ "쀀법 섀문쑰사" 탭을 ν΄λ¦­ν•˜μ„Έμš”.
+ </span>
+ </div>
+ )}
<div className="border-b bg-gray-50 px-3 py-2 flex-shrink-0">
<TabsList className="grid w-full h-8" style={{ gridTemplateColumns: `repeat(${allFiles.length}, 1fr)` }}>
{allFiles.map((file, index) => {
@@ -1344,19 +1385,41 @@ export function BasicContractSignViewer({
tabId = `file-${fileOnlyIndex}`;
}
+ const isSurveyTab = file.type === 'survey';
+ const isSurveyIncomplete = isSurveyTab && !surveyData.completed;
+
return (
- <TabsTrigger key={tabId} value={tabId} className="text-xs">
+ <TabsTrigger
+ key={tabId}
+ value={tabId}
+ className={`text-xs relative ${
+ isSurveyIncomplete
+ ? 'bg-amber-50 border-2 border-amber-400 shadow-md'
+ : isSurveyTab
+ ? 'bg-blue-50 border border-blue-300'
+ : ''
+ }`}
+ >
<div className="flex items-center space-x-1">
{file.type === 'survey' ? (
- <ClipboardList className="h-3 w-3" />
+ <ClipboardList className={`h-3 w-3 ${isSurveyIncomplete ? 'text-amber-600' : 'text-blue-600'}`} />
) : file.type === 'clauses' ? (
<BookOpen className="h-3 w-3" />
) : (
<FileText className="h-3 w-3" />
)}
- <span className="truncate">{file.name}</span>
- {file.type === 'survey' && surveyData.completed && (
- <Badge variant="secondary" className="ml-1 h-4 px-1 text-xs">μ™„λ£Œ</Badge>
+ <span className={`truncate font-medium ${isSurveyIncomplete ? 'text-amber-700' : ''}`}>
+ {file.name}
+ </span>
+ {isSurveyTab && !surveyData.completed && (
+ <Badge variant="destructive" className="ml-1 h-4 px-1.5 text-xs bg-amber-500 text-white animate-bounce">
+ ν•„μˆ˜
+ </Badge>
+ )}
+ {isSurveyTab && surveyData.completed && (
+ <Badge variant="secondary" className="ml-1 h-4 px-1 text-xs bg-green-500 text-white">
+ μ™„λ£Œ
+ </Badge>
)}
{file.type === 'clauses' && gtcCommentStatus.hasComments && (
<Badge variant="destructive" className="ml-1 h-4 px-1 text-xs">
@@ -1529,6 +1592,15 @@ export function BasicContractSignViewer({
{/* ꡬ맀자 λͺ¨λ“œμ—μ„œλŠ” νƒ­ 없이 단일 λ·°μ–΄λ§Œ ν‘œμ‹œ */}
{allFiles.length > 1 && mode !== 'buyer' ? (
<Tabs value={activeTab} onValueChange={handleTabChange} className="h-full flex flex-col">
+ {/* 쀀법섀문 λ―Έμ™„λ£Œ μ•Œλ¦Ό λ°°λ„ˆ */}
+ {isComplianceTemplate && !surveyData.completed && (
+ <div className="bg-amber-50 border-b-2 border-amber-400 px-4 py-2 flex items-center justify-center space-x-2">
+ <AlertTriangle className="h-4 w-4 text-amber-600" />
+ <span className="text-sm font-semibold text-amber-700">
+ ⚠️ 쀀법 섀문쑰사λ₯Ό λ¨Όμ € μ™„λ£Œν•΄μ£Όμ„Έμš”. μ„œλͺ…을 μ§„ν–‰ν•˜λ €λ©΄ "쀀법 섀문쑰사" 탭을 ν΄λ¦­ν•˜μ„Έμš”.
+ </span>
+ </div>
+ )}
<div className="border-b bg-gray-50 px-3 py-2 flex-shrink-0">
<TabsList className="grid w-full h-8" style={{ gridTemplateColumns: `repeat(${allFiles.length}, 1fr)` }}>
{allFiles.map((file, index) => {
@@ -1544,19 +1616,41 @@ export function BasicContractSignViewer({
tabId = `file-${fileOnlyIndex}`;
}
+ const isSurveyTab = file.type === 'survey';
+ const isSurveyIncomplete = isSurveyTab && !surveyData.completed;
+
return (
- <TabsTrigger key={tabId} value={tabId} className="text-xs">
+ <TabsTrigger
+ key={tabId}
+ value={tabId}
+ className={`text-xs relative ${
+ isSurveyIncomplete
+ ? 'bg-amber-50 border-2 border-amber-400 shadow-md'
+ : isSurveyTab
+ ? 'bg-blue-50 border border-blue-300'
+ : ''
+ }`}
+ >
<div className="flex items-center space-x-1">
{file.type === 'survey' ? (
- <ClipboardList className="h-3 w-3" />
+ <ClipboardList className={`h-3 w-3 ${isSurveyIncomplete ? 'text-amber-600' : 'text-blue-600'}`} />
) : file.type === 'clauses' ? (
<BookOpen className="h-3 w-3" />
) : (
<FileText className="h-3 w-3" />
)}
- <span className="truncate">{file.name}</span>
- {file.type === 'survey' && surveyData.completed && (
- <Badge variant="secondary" className="ml-1 h-4 px-1 text-xs">μ™„λ£Œ</Badge>
+ <span className={`truncate font-medium ${isSurveyIncomplete ? 'text-amber-700' : ''}`}>
+ {file.name}
+ </span>
+ {isSurveyTab && !surveyData.completed && (
+ <Badge variant="destructive" className="ml-1 h-4 px-1.5 text-xs bg-amber-500 text-white animate-bounce">
+ ν•„μˆ˜
+ </Badge>
+ )}
+ {isSurveyTab && surveyData.completed && (
+ <Badge variant="secondary" className="ml-1 h-4 px-1 text-xs bg-green-500 text-white">
+ μ™„λ£Œ
+ </Badge>
)}
{file.type === 'clauses' && gtcCommentStatus.hasComments && (
<Badge variant="destructive" className="ml-1 h-4 px-1 text-xs">