summaryrefslogtreecommitdiff
path: root/lib/basic-contract
diff options
context:
space:
mode:
Diffstat (limited to 'lib/basic-contract')
-rw-r--r--lib/basic-contract/service.ts12
-rw-r--r--lib/basic-contract/template/template-editor-wrapper.tsx103
-rw-r--r--lib/basic-contract/vendor-table/basic-contract-columns.tsx38
-rw-r--r--lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx10
-rw-r--r--lib/basic-contract/viewer/basic-contract-sign-viewer.tsx11
5 files changed, 125 insertions, 49 deletions
diff --git a/lib/basic-contract/service.ts b/lib/basic-contract/service.ts
index 9b5505b5..03b27f96 100644
--- a/lib/basic-contract/service.ts
+++ b/lib/basic-contract/service.ts
@@ -1129,4 +1129,16 @@ export async function getExistingTemplateNames(): Promise<string[]> {
.groupBy(basicContractTemplates.templateName);
return rows.map((r) => r.templateName);
+}
+
+export async function getExistingTemplateNamesById(id:number): Promise<string> {
+ const rows = await db
+ .select({
+ templateName: basicContractTemplates.templateName,
+ })
+ .from(basicContractTemplates)
+ .where(and(eq(basicContractTemplates.status,"ACTIVE"),eq(basicContractTemplates.id,id)))
+ .limit(1)
+
+ return rows[0].templateName;
} \ No newline at end of file
diff --git a/lib/basic-contract/template/template-editor-wrapper.tsx b/lib/basic-contract/template/template-editor-wrapper.tsx
index ea353b91..96e2330f 100644
--- a/lib/basic-contract/template/template-editor-wrapper.tsx
+++ b/lib/basic-contract/template/template-editor-wrapper.tsx
@@ -7,7 +7,7 @@ import { Save, RefreshCw, Type, FileText, AlertCircle } from "lucide-react";
import type { WebViewerInstance } from "@pdftron/webviewer";
import { Badge } from "@/components/ui/badge";
import { BasicContractTemplateViewer } from "./basic-contract-template-viewer";
-import { saveTemplateFile } from "../service";
+import { getExistingTemplateNamesById, saveTemplateFile } from "../service";
interface TemplateEditorWrapperProps {
templateId: string | number;
@@ -16,9 +16,27 @@ interface TemplateEditorWrapperProps {
refreshAction?: () => Promise<void>;
}
+// 템플릿 이름별 변수 매핑 (영문 변수명 사용)
+const TEMPLATE_VARIABLES_MAP = {
+ "준법서약 (한글)": ["vendor_name", "address", "representative_name", "today_date"],
+ "준법서약 (영문)": ["vendor_name", "address", "representative_name", "today_date"],
+ "기술자료 요구서": ["vendor_name", "address", "representative_name", "today_date"],
+ "비밀유지 계약서": ["vendor_name", "address", "representative_name", "today_date"],
+ "표준하도급기본 계약서": ["vendor_name", "address", "representative_name", "today_date"],
+ "GTC": ["vendor_name", "address", "representative_name", "today_date"],
+ "안전보건관리 약정서": ["vendor_name", "address", "representative_name", "today_date"],
+ "동반성장": ["vendor_name", "address", "representative_name", "today_date"],
+ "윤리규범 준수 서약서": ["vendor_name", "address", "representative_name", "today_date"],
+ "기술자료 동의서": ["vendor_name", "address", "representative_name", "today_date"],
+ "내국신용장 미개설 합의서": ["vendor_name", "address", "representative_name", "today_date"],
+ "직납자재 하도급대급등 연동제 의향서": ["vendor_name", "address", "representative_name", "today_date"]
+} as const;
+
// 변수 패턴 감지를 위한 정규식
const VARIABLE_PATTERN = /\{\{([^}]+)\}\}/g;
+
+
export function TemplateEditorWrapper({
templateId,
filePath,
@@ -28,6 +46,35 @@ export function TemplateEditorWrapper({
const [instance, setInstance] = React.useState<WebViewerInstance | null>(null);
const [isSaving, setIsSaving] = React.useState(false);
const [documentVariables, setDocumentVariables] = React.useState<string[]>([]);
+ const [templateName, setTemplateName] = React.useState<string>("");
+ const [predefinedVariables, setPredefinedVariables] = React.useState<string[]>([]);
+
+ console.log(templateId, "templateId");
+
+ // 템플릿 이름 로드 및 변수 설정
+ React.useEffect(() => {
+ const loadTemplateInfo = async () => {
+ try {
+ const name = await getExistingTemplateNamesById(Number(templateId));
+ setTemplateName(name);
+
+ // 템플릿 이름에 따른 변수 설정
+ const variables = TEMPLATE_VARIABLES_MAP[name as keyof typeof TEMPLATE_VARIABLES_MAP] || [];
+ setPredefinedVariables(variables);
+
+ console.log("🏷️ 템플릿 이름:", name);
+ console.log("📝 할당된 변수들:", variables);
+ } catch (error) {
+ console.error("템플릿 정보 로드 오류:", error);
+ // 기본 변수 설정
+ setPredefinedVariables(["회사명", "주소", "대표자명", "오늘날짜"]);
+ }
+ };
+
+ if (templateId) {
+ loadTemplateInfo();
+ }
+ }, [templateId]);
// 문서에서 변수 추출
const extractVariablesFromDocument = async () => {
@@ -220,13 +267,6 @@ export function TemplateEditorWrapper({
window.location.reload();
};
- // 미리 정의된 변수들
- const predefinedVariables = [
- "회사명", "계약자명", "계약일자", "계약금액",
- "납기일자", "담당자명", "담당자연락처", "프로젝트명",
- "계약번호", "사업부", "부서명", "승인자명"
- ];
-
return (
<div className="h-full flex flex-col">
{/* 상단 도구 모음 */}
@@ -267,6 +307,12 @@ export function TemplateEditorWrapper({
<FileText className="mr-1 h-3 w-3" />
{fileName}
</Badge>
+ {templateName && (
+ <Badge variant="secondary">
+ <Type className="mr-1 h-3 w-3" />
+ {templateName}
+ </Badge>
+ )}
{documentVariables.length > 0 && (
<Badge variant="secondary">
<Type className="mr-1 h-3 w-3" />
@@ -283,6 +329,11 @@ export function TemplateEditorWrapper({
<h4 className="text-sm font-medium flex items-center">
<Type className="mr-2 h-4 w-4 text-blue-500" />
변수 관리
+ {templateName && (
+ <span className="ml-2 text-xs text-muted-foreground">
+ ({templateName})
+ </span>
+ )}
</h4>
</div>
@@ -301,23 +352,27 @@ export function TemplateEditorWrapper({
</div>
)}
- {/* 미리 정의된 변수들 */}
- <div>
- <p className="text-xs text-muted-foreground mb-2">자주 사용하는 변수 (클릭하여 복사):</p>
- <div className="flex flex-wrap gap-1">
- {predefinedVariables.map((variable, index) => (
- <Button
- key={index}
- variant="ghost"
- size="sm"
- className="h-6 px-2 text-xs"
- onClick={() => insertVariable(variable)}
- >
- {`{{${variable}}}`}
- </Button>
- ))}
+ {/* 템플릿별 미리 정의된 변수들 */}
+ {predefinedVariables.length > 0 && (
+ <div>
+ <p className="text-xs text-muted-foreground mb-2">
+ {templateName ? `${templateName}에 권장되는 변수` : "자주 사용하는 변수"} (클릭하여 복사):
+ </p>
+ <div className="flex flex-wrap gap-1">
+ {predefinedVariables.map((variable, index) => (
+ <Button
+ key={index}
+ variant="ghost"
+ size="sm"
+ className="h-6 px-2 text-xs hover:bg-blue-50"
+ onClick={() => insertVariable(variable)}
+ >
+ {`{{${variable}}}`}
+ </Button>
+ ))}
+ </div>
</div>
- </div>
+ )}
</div>
</div>
)}
diff --git a/lib/basic-contract/vendor-table/basic-contract-columns.tsx b/lib/basic-contract/vendor-table/basic-contract-columns.tsx
index b79487d7..c9e8da53 100644
--- a/lib/basic-contract/vendor-table/basic-contract-columns.tsx
+++ b/lib/basic-contract/vendor-table/basic-contract-columns.tsx
@@ -8,6 +8,7 @@ import { toast } from "sonner"
import { getErrorMessage } from "@/lib/handle-error"
import { formatDate, formatDateTime } from "@/lib/utils"
+import { downloadFile } from "@/lib/file-download"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
@@ -36,28 +37,24 @@ interface GetColumnsProps {
/**
* 파일 다운로드 함수
*/
-/**
- * 파일 다운로드 함수
- */
-const handleFileDownload = (filePath: string | null, fileName: string | null) => {
+const handleFileDownload = async (filePath: string | null, fileName: string | null) => {
if (!filePath || !fileName) {
toast.error("파일 정보가 없습니다.");
return;
}
try {
- // 전체 URL 생성
- const fullUrl = `${window.location.origin}${filePath}`;
+ // /api/files/ 엔드포인트를 통한 안전한 다운로드
+ const apiFilePath = `/api/files/${filePath.startsWith('/') ? filePath.substring(1) : filePath}`;
- // a 태그를 생성하여 다운로드 실행
- const link = document.createElement('a');
- link.href = fullUrl;
- link.download = fileName; // 다운로드될 파일명 설정
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
-
- toast.success("파일 다운로드를 시작합니다.");
+ const result = await downloadFile(apiFilePath, fileName, {
+ action: 'download',
+ showToast: true
+ });
+
+ if (!result.success) {
+ console.error("파일 다운로드 실패:", result.error);
+ }
} catch (error) {
console.error("파일 다운로드 오류:", error);
toast.error("파일 다운로드 중 오류가 발생했습니다.");
@@ -104,16 +101,19 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<BasicCo
id: "download",
header: "",
cell: ({ row }) => {
- const template = row.original;
- const filePath = template.status === "PENDING" ? template.filePath : template.signedFilePath
+ const contract = row.original;
+ // PENDING 상태일 때는 원본 PDF 파일 (signedFilePath), COMPLETED일 때는 서명된 파일 (signedFilePath)
+ const filePath = contract.signedFilePath;
+ const fileName = contract.signedFileName;
return (
<Button
variant="ghost"
size="icon"
- onClick={() => handleFileDownload(filePath, template.fileName)}
- title={`${template.fileName} 다운로드`}
+ onClick={() => handleFileDownload(filePath, fileName)}
+ title={`${fileName} 다운로드`}
className="hover:bg-muted"
+ disabled={!filePath || !fileName}
>
<Paperclip className="h-4 w-4" />
<span className="sr-only">다운로드</span>
diff --git a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx
index 28a4fd71..7bffdac9 100644
--- a/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx
+++ b/lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx
@@ -68,7 +68,7 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS
const term = searchTerm.toLowerCase();
return contracts.filter(contract =>
(contract.templateName || '').toLowerCase().includes(term) ||
- (contract.userName || '').toLowerCase().includes(term)
+ (contract.requestedByName || '').toLowerCase().includes(term)
);
}, [contracts, searchTerm]);
@@ -98,7 +98,7 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS
const formData = new FormData();
formData.append('file', new Blob([data], { type: 'application/pdf' }));
formData.append('tableRowId', selectedContract.id.toString());
- formData.append('templateName', selectedContract.fileName || '');
+ formData.append('templateName', selectedContract.signedFileName || '');
// API 호출
const response = await fetch('/api/upload/signed-contract', {
@@ -227,7 +227,7 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS
<div className="grid grid-cols-2 gap-1 mt-1 text-xs text-gray-500">
<div className="flex items-center">
<User className="h-3 w-3 mr-1" />
- <span className="truncate">{contract.userName || '알 수 없음'}</span>
+ <span className="truncate">{contract.requestedByName || '알 수 없음'}</span>
</div>
<div className="flex items-center justify-end">
<Calendar className="h-3 w-3 mr-1" />
@@ -255,7 +255,7 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS
<div className="flex justify-between items-center mt-1 text-xs text-gray-500">
<span className="flex items-center">
<User className="h-3 w-3 mr-1" />
- 요청자: {selectedContract.userName || '알 수 없음'}
+ 요청자: {selectedContract.requestedByName || '알 수 없음'}
</span>
<span className="flex items-center">
<Clock className="h-3 w-3 mr-1" />
@@ -266,7 +266,7 @@ export function BasicContractSignDialog({ contracts, onSuccess }: BasicContractS
<div className="flex-grow overflow-hidden border-b">
<BasicContractSignViewer
contractId={selectedContract.id}
- filePath={selectedContract.filePath || undefined}
+ filePath={selectedContract.signedFilePath || undefined}
instance={instance}
setInstance={setInstance}
/>
diff --git a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
index 0409151e..8995c560 100644
--- a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
+++ b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
@@ -108,8 +108,17 @@ export function BasicContractSignViewer({
// 문서 로드
useEffect(() => {
if (!instance || !filePath) return;
+ console.log("📄 파일 로드 시도:", { filePath });
- loadDocument(instance, filePath);
+
+ // filePath를 /api/files/ 엔드포인트를 통해 접근하도록 변환
+ // 한글 파일명의 경우 URL 인코딩 처리
+ const normalizedPath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
+ const encodedPath = normalizedPath.split('/').map(part => encodeURIComponent(part)).join('/');
+ const apiFilePath = `/api/files/${encodedPath}`;
+
+ console.log("📄 파일 로드 시도:", { originalPath: filePath, encodedPath: apiFilePath });
+ loadDocument(instance, apiFilePath);
}, [instance, filePath]);
// 간소화된 문서 로드 함수