Files
PM_test/views/main/jsm/officialDoc/docDataManager.js
2026-06-12 17:14:03 +09:00

1061 lines
40 KiB
JavaScript

import { vars } from '../archive/variable.js';
import { docVars } from './docVariable.js';
import { checkProjectInactive } from '../main.js';
import { openDocAiChoiceModal, openDocUpsertModal } from './docModalManager.js';
import { drawDocList, itemBoxSelected, renderDocViewer, toggleDocContextmenu } from './docPageRenderer.js';
import { splitBaseAndExt, hasSpecialChar } from '../archive/common.js';
import { resetViewer } from '../archive/pageRenderer.js';
let docMain = document.querySelector('.official-doc-main'); // 화면
let notDndArea = document.querySelector('.not-dnd-area');
let docMainList = document.querySelector('.official-doc-main .official-doc-list-container');
let docDndArea = document.querySelector('.official-doc-main .official-doc-list-container .official-doc-main-body .doc-dnd-area');
let docDndImage = docDndArea?.querySelector('.doc-img');
let docDndText = docDndArea?.querySelector('.doc-text');
let isDragging = false;
let isAiUsed = true; // 기본값은 true로 설정
// AI 사용 여부를 설정하는 함수
export function setAiUsed(value) {
isAiUsed = value;
}
// AI 사용 여부를 가져오는 함수
export function getAiUsed() {
return isAiUsed;
}
// dragover: 드래그 된 요소가 droppable 요소 위에 있는 동안 지속적으로 발생
docMain?.addEventListener('dragover', async (e) => {
const types = e.dataTransfer.types;
toggleDocContextmenu('.official-doc-contextmenu', false);
// 파일이 아닌 항목에 대해서는 dnd 안되게 // 글자가 dnd 되는 문제 때문에
if (types && types.includes('Files')) {
e.preventDefault();
// 페이지영역에 드래그 들어오면 notDndArea, docDndArea 표시
// notDndArea.style.display = 'flex';
docDndArea.style.display = 'flex';
// 드래그 상태 플래그 true
isDragging = true;
// dnd 영역 크기
let docMainListWidth = docMainList.offsetWidth;
let docMainListHeight = docMainList.offsetHeight;
let docMainListTop = docMainList.offsetTop;
docDndArea.style.width = `${docMainListWidth}px`;
docDndArea.style.minHeight = `${docMainListHeight}px`;
docDndArea.style.top = `${docMainListTop}px`;
if (e.target.matches('.doc-dnd-area')) {
// dndArea 안으로 이동
docDndArea.style.background = '#b5c6c3dd';
docDndArea.style.border = '2px solid #1e5149';
// docDndImage.style.backgroundImage = "url('/main/img/archive/upload_in_dnd.svg')";
docDndText.textContent = '파일을 여기에 드롭하세요.';
docDndText.style.color = '#1e5149';
} else {
// dndArea 밖으로 이동
docDndArea.style.background = '#e9eeeddd';
docDndArea.style.border = '2px solid #1e5149';
// docDndImage.style.backgroundImage = "url('/main/img/archive/upload_out_dnd.svg')";
docDndText.textContent = '파일을 여기에 드래그하세요.';
docDndText.style.color = '#a5b9b6';
}
} else {
return;
}
});
// dragleave: 드래그된 요소가 droppable 요소에서 벗어날 때 발생
docMain?.addEventListener('dragleave', async (e) => {
e.preventDefault();
if (e.target.matches('.doc-dnd-area')) {
// notDndArea에서 페이지영역 밖으로 나갈 때 notDndArea, dndArea 숨김
// notDndArea.style.display = 'none';
docDndArea.style.display = 'none';
// 드래그 상태 플래그 false
isDragging = false;
}
});
// 드래그 상태에서 웹페이지 밖으로 나갈 때 notDndArea, dndArea 남아있는 경우 초기화
docMain?.addEventListener('mousemove', () => {
if (isDragging) {
// notDndArea.style.display = 'none';
docDndArea.style.display = 'none';
isDragging = false;
}
});
// drop: 드래그된 요소가 droppable 요소에 놓일 때 발생
docDndArea?.addEventListener('drop', async (e) => {
e.preventDefault();
const droppedFiles = Array.from(e.dataTransfer.files);
if (!droppedFiles.length) return;
if (droppedFiles.length > 1) {
alert('파일은 하나씩 추가해주세요.');
return;
}
let resourcePathArr = [];
// 경로 존재 확인을 위해 resourcePathArr 저장
for (let i = 0; i < droppedFiles.length; i++) {
const file = droppedFiles[i];
let resourcePath = `/${file.name}`;
resourcePathArr.push(resourcePath);
}
// resourcePathArr을 사용해서 경로 존재 확인
let checkTargetExistsResult = await checkDocTargetExists('official', resourcePathArr);
// 이미 존재하는 경로만 existingPathArr에 저장
let existingPathArr = checkTargetExistsResult.existingPathArr;
if (existingPathArr.length != 0) {
// 중복파일 있음
for (let i = 0; i < droppedFiles.length; i++) {
const file = droppedFiles[i];
let resourcePath = `/${file.name}`;
let docTitle = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.doc_title;
let docDirection = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.doc_direction;
let recipientOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.recipient_org;
let senderOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.sender_org;
let objectKey;
let isExists = false;
if (existingPathArr.includes(resourcePath)) {
let isUpload = confirm(`'${file.name}' 파일이 이미 존재합니다.\n기존 파일을 새 파일로 교체하시겠습니까?\n[파일 제목] ${docTitle}\n[수신처] ${recipientOrg}\n[발신처] ${senderOrg}\n[위치] '${docDirection}' 공문 리스트`);
if (isUpload) objectKey = checkTargetExistsResult.existingObjectKey.keyMap[resourcePath];
if (!isUpload) continue;
}
if (objectKey) {
isExists = true;
const docId = checkTargetExistsResult.existingDocId.keyMap[resourcePath];
await axios.post(`${docVars.path_name}/deleteDocData`, { docId, objectKey, isExists });
}
}
const uploadResult = await uploadDocFile(droppedFiles, 'exist');
const newObjectKey = uploadResult.uploadParams.objectKeyArr[0];
const params = {};
for (let i = 0; i < droppedFiles.length; i++) {
const file = droppedFiles[i];
let resourcePath = `/${file.name}`;
const docId = checkTargetExistsResult.existingDocId.keyMap[resourcePath];
params[`doc_id`] = docId;
params[`object_key`] = newObjectKey;
params[`preview_key`] = newObjectKey;
params[`popup_key`] = newObjectKey;
}
await upsertDocData(params, 'exist');
} else {
// 중복파일 없음
const droppedFileName = droppedFiles[0].name;
const droppedFileExt = droppedFileName.split('.').pop().toLowerCase();
if (droppedFileExt != 'pdf') {
alert('PDF 파일만 지원합니다.');
return;
}
// pdf 10장 넘으면 리턴
let pageCount = await checkPdfPageCount(droppedFiles[0]);
if (pageCount >= 10) {
if (confirm('11장 이상의 PDF는 AI 요약을 지원하지 않습니다.\n사용자 입력모달로 이동하시겠습니까?')) {
setAiUsed(false);
openDocUpsertModal({}, droppedFiles, droppedFileName, 'input');
return;
}
return;
}
const formData = new FormData();
// 공문 파일
for (const file of droppedFiles) {
formData.append('input_file', file);
}
// 프롬프트 파일
try {
const res = await fetch('/main/jsm/officialDoc/prompt/해외공문 프롬프트250901(한글)_json.txt');
const blob = await res.blob();
const promptFile = new File([blob], '해외공문 프롬프트250901(한글)_json.txt', { type: 'text/plain' });
formData.append('prompt_file', promptFile);
} catch (error) {
console.error('프롬프트 파일 로드 실패:', error);
return;
}
// 스키마 파일
try {
const res = await fetch('/main/jsm/officialDoc/prompt/default_schema.json');
const blob = await res.blob();
const schemaFile = new File([blob], 'default_schema.json', { type: 'application/json' });
formData.append('schema_file', schemaFile);
} catch (error) {
console.error('스키마 파일 로드 실패:', error);
return;
}
// AI 선택 모달 열기 > 선택 완료되면 requestToAiWithChoice 호출
openDocAiChoiceModal((selectedAiType) => {
requestToAiWithChoice(formData, droppedFiles, selectedAiType);
});
}
// notDndArea에서 페이지영역 밖으로 나갈 때 notDndArea, dndArea 숨김
notDndArea.style.display = 'none';
docDndArea.style.display = 'none';
// 드래그 상태 플래그 false
isDragging = false;
});
async function checkPdfPageCount(file) {
const arrayBuffer = await file.arrayBuffer();
const loadingTask = window.pdfjsLib.getDocument({ data: new Uint8Array(arrayBuffer) });
const pdf = await loadingTask.promise;
return pdf.numPages;
}
async function requestToAiWithChoice(formData, droppedFiles, aiType) {
const files = formData.get('input_file');
const fileName = files ? files.name : '파일 없음';
try {
showFullScreenLoading(); // 로딩 화면 보이기
// AI 사용 안함
if (aiType == 'aiNotUse') {
hideFullScreenLoading();
setAiUsed(false);
openDocUpsertModal({}, droppedFiles, fileName, 'input');
return;
}
const apiKey = await getExtractKey(); // AICell에서 발급해준 apiKey
const url = {
outerAi: 'https://gateway.hmac.kr/general/outer/e2e',
innerAi: 'https://gateway.hmac.kr/extract/inner/structured',
};
const res = await fetch(url[aiType], {
method: 'POST',
headers: {
'x-API-key': apiKey,
},
body: formData,
});
const resData = await res.json();
// AICell API(outerAi, innerAi)의 경우, 응답받은 request_id를 통해 데이터 풀링
const requestId = resData.request_id;
if (!requestId) throw new Error('request_id 없음');
// api에 polling 요청
timeoutPolling(
() => checkAiProgress(requestId, droppedFiles, fileName, aiType),
5000, // 5초 간격
60, // 최대 60번 > 5분
droppedFiles,
fileName
);
} catch (err) {
handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달
}
}
async function checkAiProgress(requestId, droppedFiles, fileName, aiType) {
try {
const apiKey = await getExtractKey();
// aiType으로 데이터 풀링할 url (outerAi = general, innerAi = extract)
const url = aiType === 'outerAi' ? `https://gateway.hmac.kr/general/progress/${requestId}` : `https://gateway.hmac.kr/extract/progress/${requestId}`;
const res = await fetch(url, {
method: 'GET',
headers: {
'x-API-key': apiKey,
},
});
// console.log(`요청: /extract/progress/${requestId} → status: ${res.status}`);
if (!res.ok) {
// 404 등 실패 시, polling 종료
handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달
throw new Error('__STOP_POLLING__'); // polling 종료를 위한 에러 던지기
}
const data = await res.json();
// console.log('요약 진행 상태 응답: ', data);
// final_result가 최종 ai공문요약데이터
// 결과가 완료되지 않아도 (data.status 200 || data.final_result) 조건을 충족하기 때문에
// 결과 생성 전에는 다음 polling으로 넘김
if (data.final_result == '작업이 진행 중입니다. 결과는 아직 생성되지 않았습니다.') return;
// 결과에 에러 떴을 때
if (data.final_result?.error) {
handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달
throw new Error('__STOP_POLLING__'); // polling 종료를 위한 에러 던지기
}
// 성공 시 polling 중단하고 콜백 실행
if (data.status === 200 || data.final_result) {
// console.log('최최종: ', data.final_result[0]);
setAiUsed(true);
let generatedData = data.final_result.generated;
if (aiType === 'outerAi') generatedData = JSON.parse(data.final_result.generated);
if (aiType === 'innerAi') generatedData = data.final_result[0].generated;
openDocUpsertModal(generatedData, droppedFiles, fileName);
hideFullScreenLoading(); // 로딩 화면 숨기기
throw new Error('__STOP_POLLING__'); // 재귀 중단용 에러
}
} catch (error) {
if (error.message === '__STOP_POLLING__') {
throw error; // 재귀 멈추기 위한 예외는 다시 던짐
} else {
console.error('polling 중 에러: ', error);
handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달
throw new Error('__STOP_POLLING__'); // 재귀 중단용 에러
}
}
}
export const timeoutPolling = (func, timeout, maxAttempts = -1, droppedFiles = [], fileName = '') => {
if (maxAttempts === 0) {
handleServerError(droppedFiles, fileName); // 서버 에러 대비 사용자입력모달
return;
}
setTimeout(async () => {
try {
await func();
} catch (error) {
if (error.message === '__STOP_POLLING__') return; // 성공적 중단
console.error(error);
}
timeoutPolling(func, timeout, maxAttempts - 1, droppedFiles, fileName);
}, timeout);
};
// 서버 에러 대비 사용자입력모달
const handleServerError = (droppedFiles, fileName) => {
hideFullScreenLoading();
if (confirm('서버와의 연결이 불안정합니다.\n사용자 입력모달로 이동하시겠습니까?')) {
setAiUsed(false);
openDocUpsertModal({}, droppedFiles, fileName, 'input');
}
};
async function getExtractKey() {
try {
const res = await axios.post(`${docVars.path_name}/getExtractKey`);
if (res.data.message === 'getExtractKey_success') {
const apiKey = res.data.apiKey;
return apiKey;
} else {
throw new Error('getExtractKey 실패');
}
} catch (error) {
console.error('getExtractKey error: ', error);
return null;
}
}
// ai 응답 기다리면서 뜨는 로딩 화면
function showFullScreenLoading() {
document.querySelector('.doc-ai-loading').style.display = 'flex';
}
function hideFullScreenLoading() {
document.querySelector('.doc-ai-loading').style.display = 'none';
}
export async function uploadDocFile(files, mode) {
let dateArr = [],
resourcePathArr = [],
sizeArr = [],
objectKeyArr = [],
uploadSuccessFileArr = [],
uploadFailedFileArr = [];
let resourcePath;
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (mode === 'modal_add') {
resourcePath = `/${file.name}`;
} else if (mode === 'add_attach') {
const parentPathLi = docVars.lastClickedListTarget.closest('li');
const parentPath = parentPathLi.dataset.resourcePath;
resourcePath = `${parentPath}__attachment/${file.name}`;
} else if (mode === 'exist') {
resourcePath = `/${file.name}`;
}
try {
// 1) 서버에 presigned URL 요청
let axiosParams = {
resourcePath: resourcePath,
date: Date.now(),
};
let generateUploadUrlRes = await axios.post(`${docVars.path_name}/generateUploadDocUrl`, axiosParams);
if (generateUploadUrlRes.data.message == 'generateUploadDocUrl_success') {
let { url, objectKey, date } = generateUploadUrlRes.data.result;
// 2) presigned URL로 직접 파일 PUT 전송
await axios.put(url, file, {
headers: {
'Content-Type': file.type || 'application/octet-stream',
},
});
dateArr.push(date);
resourcePathArr.push(resourcePath);
sizeArr.push(file.size);
objectKeyArr.push(objectKey);
uploadSuccessFileArr.push(file.name);
}
} catch (error) {
console.error(`❌ 업로드 실패: ${resourcePath}`, error);
uploadFailedFileArr.push(file.name);
}
}
// 업로드 정보 리턴
return {
success: resourcePathArr.length > 0,
uploadParams: {
storageType: docVars.storageType,
dateArr: dateArr,
resourcePathArr: resourcePathArr,
sizeArr: sizeArr,
objectKeyArr: objectKeyArr,
},
fileArrReset: true,
uploadFailedFileArr,
uploadSuccessFileArr,
};
}
// DB 저장
export async function upsertDocData(formData, mode) {
try {
if (mode === 'modal_edit') {
formData.userInfoString = vars.userInfoString;
if (!formData.doc_id && docVars.currentDocId) {
formData.doc_id = docVars.currentDocId;
}
} else if (mode === 'exist') {
formData.userInfoString = vars.userInfoString;
}
const res = await axios.post(`${docVars.path_name}/uploadDocData`, { params: formData, mode });
if (res.status != 200) {
console.warn(`⚠️ ${mode} 실패`, res.data);
}
await syncDocInfo(['official', 'attach', null]);
await getDocDataBySelected();
} catch (error) {
console.error(`${mode} 요청 중 오류:`, error);
throw error;
}
}
export async function syncDocInfo(labels = null) {
try {
const labelArray = Array.isArray(labels) ? labels : [labels];
const resPromises = labelArray.map((label) => {
return axios.get(`${docVars.path_name}/getDocData`, {
params: label ? { label } : {},
});
});
const resData = await Promise.all(resPromises);
const result = {};
resData.forEach((res, index) => {
if (res.data.message === 'getDocData_success') {
const label = labelArray[index];
if (label === 'attach') {
result.allDocAttachData = res.data.data;
} else {
result.allDocData = res.data.data;
}
}
});
// 전역 변수에 저장
if (result.allDocOfficialData) docVars.allDocOfficialData = result.allDocOfficialData;
if (result.allDocAttachData) docVars.allDocAttachData = result.allDocAttachData;
if (result.allDocData) docVars.allDocData = result.allDocData;
return result;
} catch (error) {
console.error('syncDocInfo err ', error);
}
}
export async function getDocDataBySelected(direction) {
// console.log('선택된 파라미터:', JSON.stringify(docVars.selectParams, null, 2));
const docCategory = document.querySelector('.official-doc-category.official-doc-tab-on h6')?.innerText.trim();
const categoryValue = docCategory === '전체보기' ? '' : docCategory;
try {
const params = {
projectId: docVars.project_id,
companyType: docVars.selectParams.typeOptions,
base: docVars.selectParams.baseOptions,
target: docVars.selectParams.targetOptions,
category: categoryValue,
storageType: docVars.storageType,
direction: direction,
};
const res = await axios.get(`${docVars.path_name}/getDocDataBySelected`, {
params: params,
});
if (res.data.success) {
if (direction == '' || direction == undefined || direction == null) {
docVars.allDocOfficialData = res.data.listData;
await drawDocList(docVars.allDocOfficialData);
getDocCount(res.data.countData);
}
if (direction == '수신') {
docVars.allRecipientListByDirection = res.data.listData
await drawDocList(docVars.allRecipientListByDirection, direction);
}
if (direction == '발신') {
docVars.allSendingListByDirection = res.data.listData
await drawDocList(docVars.allSendingListByDirection, direction);
}
} else {
console.error('getDocDataBySelected error');
}
} catch (error) {
console.error('getDocDataBySelected error', error);
}
}
export function getDocCount(data) {
const categoryButtons = document.querySelectorAll('.official-doc-category');
// 화면 카테고리에 있는 분류들 이름 모아놓기
const categoryNamesOnPage = [];
for (let i = 0; i < categoryButtons.length; i++) {
const categoryText = categoryButtons[i].querySelector('.category-text');
categoryNamesOnPage.push(categoryText.innerText);
}
// 전체 카운트 계산용
let totalCnt = 0;
for (let i = 0; i < data.length; i++) {
totalCnt += parseInt(data[i].count);
}
for (let i = 0; i < categoryButtons.length; i++) {
const categoryText = categoryButtons[i].querySelector('.category-text');
const categoryName = categoryText.innerText;
const categoryCount = categoryButtons[i].querySelector('.category-count');
if (categoryName === '전체보기') {
categoryCount.innerText = `(${totalCnt})`;
} else {
let count = 0;
for (let j = 0; j < data.length; j++) {
if (data[j].doc_category === categoryName) {
count = data[j].count;
break;
}
}
categoryCount.innerText = `(${count})`;
}
}
}
export async function convertDocPdf(resourcePath, docId) {
const selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docId);
let objectKey = selectedDoc.object_key;
let convertPdfParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
objectKey: objectKey,
resourcePath: resourcePath,
docId: docId,
};
let convertPdfRes = await axios.post(`${docVars.path_name}/convertDocPdf`, { params: convertPdfParams });
// console.log(convertPdfRes);
}
// 셀렉트박스 회사리스트 DB에서 가져오기
export async function getGroupCompanyData() {
try {
const res = await axios.get(`${docVars.path_name}/getGroupCompanyData`, docVars.project_id);
if (res.data.success) {
docVars.groupCompanyData = res.data.data;
} else {
console.error('getGroupCompanyData err');
}
} catch (error) {
console.error('getGroupCompanyData err: ', error);
}
}
// console.log(docVars.groupCompanyData);
// docVars.groupCompanyData res 예시
// 발주처 :
// 기준 : ['SY_JV']
// 상대기관 : ['DPWH']
// 발주처외 :
// 기준 : ['삼안']
// 상대기관 : (4) ['서영', '진우', '경동', '한국수출입은행']
/* 컨텍스트메뉴 */
// 수정 // 수정모달 오픈
document.querySelector('#renameTargetDoc')?.addEventListener('click', () => {
const selectedDoc = docVars.allDocOfficialData.find((doc) => doc.doc_id === docVars.currentDocId);
if (!selectedDoc) {
console.error('DB에 없는 데이터');
return;
}
const transformed = {
공문아이디: selectedDoc.doc_id,
수신처: selectedDoc.recipient_org,
수신처약자: selectedDoc.recipient_org_abbr,
수신자: selectedDoc.recipient_name,
수신자약자: selectedDoc.recipient_name_abbr,
발신처: selectedDoc.sender_org,
발신처약자: selectedDoc.sender_org_abbr,
발신자: selectedDoc.sender_name,
발신자약자: selectedDoc.sender_name_abbr,
doc_direction: selectedDoc.doc_direction,
공문번호: selectedDoc.doc_number,
공문유형: selectedDoc.doc_type,
공문종류: selectedDoc.doc_category,
공문제목: selectedDoc.doc_title,
공문제목요약: selectedDoc.doc_title_summary,
공문간연계: selectedDoc.doc_related_docs,
공문일자: selectedDoc.doc_date,
첨부문서수: selectedDoc.attachment_count,
첨부문서제목: selectedDoc.attachment_title,
공문내용요약: selectedDoc.doc_content_summary,
doc_manager: selectedDoc.doc_manager,
doc_memo: selectedDoc.doc_memo,
file_path: selectedDoc.file_path,
project_id: selectedDoc.project_id,
};
openDocUpsertModal(transformed, null, null, 'edit');
});
// 첨부파일 추가
document.querySelector('.official-doc-contextmenu-item.add-on-file.attach')?.addEventListener('change', async (event) => {
let files = Array.from(event.target.files); // FileList를 배열로 변환
const selectedDocElement = document.querySelector('li.main-file-container > div.official-doc-file-info.item-selected')?.parentElement;
const currentGroupId = selectedDocElement.dataset.groupId;
const formData = {
doc_label: 'attach',
group_id: currentGroupId,
};
let resourcePathArr = [];
// 경로 존재 확인을 위해 resourcePathArr 저장
for (let i = 0; i < files.length; i++) {
const officialFilePath = docVars.lastClickedListTarget.closest('li').dataset.resourcePath;
const attachPrefix = `${officialFilePath}__attachment/`;
const file = files[i];
let resourcePath = `${attachPrefix}${file.name}`;
resourcePathArr.push(resourcePath);
}
// resourcePathArr을 사용해서 경로 존재 확인
let checkTargetExistsResult = await checkDocTargetExists('attach', resourcePathArr);
// 이미 존재하는 경로만 existingPathArr에 저장
let existingPathArr = checkTargetExistsResult.existingPathArr;
// resourcePathArr 초기화
resourcePathArr = [];
if (files.length > 0) {
const updatedFiles = [];
for (let i = 0; i < files.length; i++) {
let file = files[i];
const officialFilePath = docVars.lastClickedListTarget.closest('li').dataset.resourcePath;
const attachPrefix = `${officialFilePath}__attachment/`;
let resourcePath = `${attachPrefix}${file.name}`;
let docDirection = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.doc_direction;
let recipientOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.recipient_org;
let senderOrg = docVars.allDocData?.find((doc) => doc.file_path === resourcePath)?.sender_org;
let objectKey;
if (existingPathArr.includes(resourcePath)) {
let isUpload = confirm(`'${file.name}' 파일이 이미 존재합니다.\n기존 파일을 새 파일로 교체하시겠습니까?`);
if (isUpload) objectKey = checkTargetExistsResult.existingObjectKey.keyMap[resourcePath];
resetViewer();
if (!isUpload) continue;
}
if (objectKey) {
const docId = checkTargetExistsResult.existingDocId.keyMap[resourcePath];
await axios.post(`${docVars.path_name}/deleteDocData`, { docId, objectKey });
}
updatedFiles.push(file);
}
if (updatedFiles.length === 0) {
alert('업로드할 파일이 없습니다.');
return;
}
const uploadResult = await uploadDocFile(updatedFiles, 'add_attach');
if (!uploadResult.success) {
alert('파일 업로드에 실패했습니다.');
return;
}
formData.userInfoString = vars.userInfoString;
formData.uploadParams = uploadResult.uploadParams;
formData.uploadSuccessFileArr = uploadResult.uploadSuccessFileArr;
formData.uploadFailedFileArr = uploadResult.uploadFailedFileArr;
await upsertDocData(formData, 'add_attach');
} else {
alert('파일없음');
}
});
// 첨부파일 이름 변경
let isModalOpen = false;
document.querySelector('#renameAttachTargetDoc')?.addEventListener('click', () => {
if (checkProjectInactive()) return;
openDocRenameModal();
});
function openDocRenameModal() {
if (isModalOpen) return;
resetRenameModalContent();
isModalOpen = true;
const modal = document.querySelector('.official-doc-modal.official-doc-context-modal');
const modalBackground = document.querySelector('.official-doc-modal-background');
modal.style.display = 'flex';
modalBackground.style.display = 'flex';
let selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docVars.currentDocId);
const modalHeader = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-header');
const title = document.createElement('h3');
title.textContent = '이름 변경';
modalHeader.appendChild(title);
const modalBody = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-body');
const text = document.createElement('div');
text.classList.add('text-wrap');
text.innerHTML = `변경할 파일명을 입력한 후 확인을 눌러주세요.<br>이름 변경 대상 : ${selectedDoc.file_path}`;
modalBody.appendChild(text);
const oldName = selectedDoc.file_path.split('/').pop().split('.')[0];
const inputWrap = document.createElement('div');
inputWrap.classList.add('input-wrap');
const input = document.createElement('input');
input.type = 'text';
input.value = oldName;
input.spellcheck = false;
inputWrap.appendChild(input);
modalBody.appendChild(inputWrap);
input.focus();
let confirmBtn = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-positive-btn');
let cancelBtn = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-negative-btn');
const removeEventListeners = (oldChild) => {
const newChild = oldChild.cloneNode(true);
oldChild.parentNode.replaceChild(newChild, oldChild);
return newChild;
};
confirmBtn = removeEventListeners(confirmBtn);
cancelBtn = removeEventListeners(cancelBtn);
confirmBtn.addEventListener('click', () => {
handleRenameDoc(selectedDoc, inputWrap);
});
cancelBtn.addEventListener('click', () => {
closeDocRenameModal();
});
}
function closeDocRenameModal() {
const modal = document.querySelector('.official-doc-modal.official-doc-context-modal');
const modalBackground = document.querySelector('.official-doc-modal-background');
modal.style.display = 'none';
modalBackground.style.display = 'none';
resetRenameModalContent();
isModalOpen = false;
}
function resetRenameModalContent() {
const modalHeader = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-header');
const modalBody = document.querySelector('.official-doc-modal.official-doc-context-modal .official-doc-modal-body');
const textWrap = modalBody.querySelector('.text-wrap');
const inputWrap = modalBody.querySelector('.input-wrap');
if (modalHeader) modalHeader.innerHTML = '';
if (modalBody) modalBody.innerHTML = '';
if (textWrap) textWrap.innerHTML = '';
if (inputWrap) inputWrap.innerHTML = '';
}
async function handleRenameDoc(data, inputWrap) {
let oldName = data.file_path.split('/').pop();
let newName = inputWrap.getElementsByTagName('input')[0].value.trim();
let docId = data.doc_id;
let ext;
let split = splitBaseAndExt(oldName);
ext = split.ext;
oldName = `${split.base}.${ext}`;
newName = `${newName}.${ext}`;
let oldPath = data.file_path;
let parts = oldPath.split('/');
let topFolderPath = '/' + parts[1];
let newPath = topFolderPath == '/' ? `/${newName}` : `${topFolderPath}/${newName}`;
// 기존 경고문구 있으면 삭제
if (document.querySelector('.official-doc-modal.official-doc-context-modal .input-wrap .warn')) {
inputWrap.removeChild(document.querySelector('.official-doc-modal.official-doc-context-modal .input-wrap .warn'));
}
// 경고문구 dom 생성
let warn = document.createElement('div');
warn.classList.add('warn');
warn.style.top = `${inputWrap.offsetHeight}px`;
inputWrap.appendChild(warn);
let checkTargetExistsResult = await checkDocTargetExists('attach', newPath);
// 상황에 따라 경고문구 텍스트 추가 또는 renameTarget 진행
if (!newName || newName == '' || newName == null) {
// 빈문자 체크
warn.innerText = `파일명을 입력해주세요.`;
} else if (hasSpecialChar(newName)) {
// 특수문자 체크
warn.innerHTML = `다음 특수문자는 사용할 수 없습니다.<br>\\ / : * ? ' " \` < > | #`;
} else if (checkTargetExistsResult.isExists) {
// 동일이름 여부 체크
let text = `동일한 파일명이 존재합니다.`;
warn.innerText = text;
} else {
if (inputWrap.contains(warn)) {
inputWrap.removeChild(warn);
}
closeDocRenameModal();
let renameTargetParams = {
userInfoString: vars.userInfoString,
storageType: vars.storageType,
resourcePath: data.file_path,
newName: newName,
oldName: oldName,
newPath: newPath,
oldPath: oldPath,
docId: docId,
};
let li = docVars.lastClickedListTarget.closest('li');
let res = await axios.post(`${docVars.path_name}/renameDocTarget`, { params: renameTargetParams });
if (res.data.message == 'renameDocTarget_success') {
await syncDocInfo(['official', 'attach', null]);
await getDocDataBySelected();
let newLi = document.querySelector(`li[data-doc-id="${li.dataset.docId}"]`);
if (newLi) {
itemBoxSelected(newLi);
await renderDocViewer(newLi.dataset.resourcePath, newLi.dataset.docId);
}
}
}
}
// 삭제
async function handleDeleteDoc(selectedDoc) {
const docId = selectedDoc.doc_id;
const groupId = selectedDoc.group_id;
const objectKey = selectedDoc.object_key;
let fileName = selectedDoc?.file_path.split('/').pop();
const docTitle = selectedDoc?.doc_title;
const docLabel = selectedDoc?.doc_label;
if (confirm(`정말 삭제하시겠습니까?\n\n[파일 이름] ${fileName}\n[파일 제목] ${docTitle}`)) {
try {
const res = await axios.post(`${docVars.path_name}/deleteDocData`, { docId, groupId, objectKey });
if (res.data.message === 'delete_success') {
alert('삭제가 완료되었습니다.');
await syncDocInfo(['official', 'attach', null]);
await getDocDataBySelected();
docVars.selectedDoc = null;
// 미리보기 영역 초기화
document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--container').style.display = 'none';
document.querySelector('.official-doc-main .official-doc-preview .official-doc-preview--notice').style.display = 'flex';
} else {
alert('삭제에 실패했습니다.');
}
} catch (error) {
console.error('삭제 요청 중 오류 발생:', error);
alert('삭제 중 오류가 발생했습니다.');
}
}
}
// 삭제 버튼 클릭 이벤트
async function setupDeleteDocButtons() {
if (checkProjectInactive()) return;
const buttons = document.querySelectorAll('.official-doc-contextmenu-item.deleteTargetDoc');
buttons.forEach((btn) => {
btn.addEventListener('click', async () => {
await syncDocInfo(['official', 'attach', null]);
const selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docVars.currentDocId);
if (selectedDoc) {
await handleDeleteDoc(selectedDoc);
}
});
});
}
setupDeleteDocButtons();
// 다운로드
async function handleDownloadDoc() {
const selectedDoc = docVars.allDocData.find((doc) => doc.doc_id === docVars.currentDocId);
if (selectedDoc) {
let resourcePath = selectedDoc.file_path;
let objectKey = selectedDoc.object_key;
let generateDownloadUrlParams = {
objectKey: objectKey,
resourcePath: resourcePath,
};
try {
let generateDownloadUrlRes = await axios.post(`${docVars.path_name}/generateDownloadDocUrl`, generateDownloadUrlParams);
if (generateDownloadUrlRes.data.message === 'generateDownloadDocUrl_success') {
let url = generateDownloadUrlRes.data.url;
const a = document.createElement('a');
a.href = url;
a.download = resourcePath.split('/').pop();
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
await new Promise((resolve) => setTimeout(resolve, 300)); // 딜레이
}
} catch (error) {
console.error('다운로드 URL 생성 중 오류 발생:', error);
}
}
}
// 다운로드 버튼 클릭 이벤트 함수
function setupDownloadButton() {
if (checkProjectInactive()) return;
document.querySelectorAll('.official-doc-contextmenu-item.downloadTargetDoc').forEach((btn) => {
btn.addEventListener('click', handleDownloadDoc);
});
}
setupDownloadButton();
// 이미 존재하는 파일인지 확인
async function checkDocTargetExists(docLabel, resourcePathArr) {
let result = {};
if (!Array.isArray(resourcePathArr)) resourcePathArr = [resourcePathArr];
let checkTargetExistsParams = {
storageType: docVars.storageType,
docLabel: docLabel,
resourcePathArr: JSON.stringify(resourcePathArr),
};
let checkTargetExistsRes = await axios.post(`${docVars.path_name}/checkDocTargetExists`, { params: checkTargetExistsParams });
if (checkTargetExistsRes.data.message == 'checkDocTargetExists_success') {
let rows = checkTargetExistsRes.data.rows;
result.existingPathArr = [];
if (rows.length == 0) {
result.isExists = false;
} else {
result.rows = rows;
result.isExists = true;
result.existingPathArr = filterFilePath(rows);
result.existingObjectKey = filterObjectKey(rows);
result.existingDocId = filterDocId(rows);
}
return result;
}
}
function filterFilePath(rows) {
if (!Array.isArray(rows)) rows = [rows];
let result = [];
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < rows[i].rows.length; j++) {
result.push(rows[i].rows[j].file_path);
}
}
return result;
}
function filterObjectKey(rows) {
if (!Array.isArray(rows)) rows = [rows];
let result = [];
result.keyMap = {};
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < rows[i].rows.length; j++) {
const file_path = rows[i].rows[j].file_path;
const object_key = rows[i].rows[j].object_key;
result.keyMap[file_path] = object_key;
}
}
return result;
}
function filterDocId(rows) {
if (!Array.isArray(rows)) rows = [rows];
let result = [];
result.keyMap = {};
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < rows[i].rows.length; j++) {
const file_path = rows[i].rows[j].file_path;
const doc_id = rows[i].rows[j].doc_id;
result.keyMap[file_path] = doc_id;
}
}
return result;
}