diff options
Diffstat (limited to 'lib/basic-contract')
| -rw-r--r-- | lib/basic-contract/service.ts | 12 | ||||
| -rw-r--r-- | lib/basic-contract/template/template-editor-wrapper.tsx | 103 | ||||
| -rw-r--r-- | lib/basic-contract/vendor-table/basic-contract-columns.tsx | 38 | ||||
| -rw-r--r-- | lib/basic-contract/vendor-table/basic-contract-sign-dialog.tsx | 10 | ||||
| -rw-r--r-- | lib/basic-contract/viewer/basic-contract-sign-viewer.tsx | 11 |
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]); // 간소화된 문서 로드 함수 |
