초기 PM 소스 전체 업로드
This commit is contained in:
11
views/main/jsm/archive/ai_prompt/prompt.json
Normal file
11
views/main/jsm/archive/ai_prompt/prompt.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"title": "DocumentSummary",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"파일제목": { "type": "string" },
|
||||
"파일내용요약": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"파일제목", "파일내용요약"
|
||||
]
|
||||
}
|
||||
7
views/main/jsm/archive/ai_prompt/prompt.txt
Normal file
7
views/main/jsm/archive/ai_prompt/prompt.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
오타나 줄바꿈 오류가 있을 수 있으니 의미를 유추하여 정확한 정보를 추출해주세요.
|
||||
정확성이 매우 중요하므로 반드시 파일에 포함된 텍스트만 사용하여 작성해주세요. 추론하거나 임의로 보충하지 마세요.
|
||||
불필요한 설명 문구(예: "다음은 문서에 작성된 내용입니다", "문서 기반으로 요약해 드립니다" 등)와 특수문자(*등)는 포함하지 마세요.
|
||||
한 문장이 끝나면 다음줄에서 문장을 시작해주세요. 가독성 좋게 표출해주세요.
|
||||
모든 문서는 꼭 개조식으로 정리해서 표출해주세요.
|
||||
|
||||
1. 파일 내용 요약: 파일 내용을 요약해주세요. 반드시 한글로 작성합니다.
|
||||
5
views/main/jsm/archive/ai_prompt/rpsm_prompt.txt
Normal file
5
views/main/jsm/archive/ai_prompt/rpsm_prompt.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
project_name 값을 실제 프로젝트명으로 사용하고, "project_name"이라는 단어를 그대로 쓰지 마.
|
||||
project_name 값이 null 이면 'err: project name is null' 이라는 단어가 출력되게 해줘
|
||||
project_name에 대한 온라인 반응을 최신 보도와 공식 문서 중심으로 정리해줘. 추측은 금지야 .
|
||||
아래 예제 형식처럼 글만 나오게 해줘. '결과입니다' 같은 불필요한 말은 하지마.
|
||||
글 정리는 개조식으로 진행해줘.
|
||||
503
views/main/jsm/archive/common.js
Normal file
503
views/main/jsm/archive/common.js
Normal file
@@ -0,0 +1,503 @@
|
||||
import { vars } from './variable.js';
|
||||
|
||||
// 파일 사이즈 변환
|
||||
export function formatBytes(bytes) {
|
||||
const abs = Math.abs(bytes);
|
||||
let size, unit;
|
||||
|
||||
if (abs < 1024) {
|
||||
size = addCommasToNumber(bytes);
|
||||
unit = ' Bytes';
|
||||
} else if (abs < 1048576) {
|
||||
size = addCommasToNumber((bytes / 1024).toFixed(2));
|
||||
unit = ' KB';
|
||||
} else if (abs < 1073741824) {
|
||||
size = addCommasToNumber((bytes / 1048576).toFixed(2));
|
||||
unit = ' MB';
|
||||
} else if (abs < 1099511627776) {
|
||||
size = addCommasToNumber((bytes / 1073741824).toFixed(2));
|
||||
unit = ' GB';
|
||||
} else {
|
||||
size = addCommasToNumber((bytes / 1099511627776).toFixed(2));
|
||||
unit = ' TB';
|
||||
}
|
||||
|
||||
if (size != 0) {
|
||||
let decimalPointNum = size.split('.')[1];
|
||||
if (decimalPointNum) {
|
||||
// 소수점 아래 숫자가 00인 경우
|
||||
if (decimalPointNum == '00') size = size.split('.')[0];
|
||||
// 소수점 아래 두번째 숫자가 0인 경우
|
||||
else if (decimalPointNum[1] == '0') size = `${size.split('.')[0]}.${decimalPointNum[0]}`;
|
||||
}
|
||||
}
|
||||
|
||||
return size + unit;
|
||||
}
|
||||
|
||||
// 1000 단위 콤마 추가
|
||||
// export function addCommasToNumber(num) {
|
||||
// return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
// }
|
||||
|
||||
export function addCommasToNumber(num) {
|
||||
const isNegative = Number(num) < 0;
|
||||
const parts = Math.abs(Number(num)).toString().split('.');
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
||||
|
||||
const formatted = parts.join('.');
|
||||
return isNegative ? `-${formatted}` : formatted;
|
||||
}
|
||||
|
||||
export function getDepth(path) {
|
||||
path = path.startsWith('/') ? path.slice(1) : path;
|
||||
let pathSplit = path.split('/');
|
||||
let result = pathSplit.length;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function updatePartialPath(sourcePath, targetPath) {
|
||||
let sourcePathSplit = sourcePath.split('/');
|
||||
let targetPathSplit = targetPath.split('/');
|
||||
for (let i = 0; i < sourcePathSplit.length; i++) {
|
||||
targetPathSplit[i] = sourcePathSplit[i];
|
||||
}
|
||||
|
||||
return targetPathSplit.join('/');
|
||||
}
|
||||
|
||||
// 파일명, 확장자 분리
|
||||
export function splitBaseAndExt(target, isLowerExt) {
|
||||
let targetSplit = target.split('.');
|
||||
let ext = targetSplit.pop();
|
||||
if (isLowerExt) ext = ext.toLowerCase();
|
||||
let base = targetSplit.join('.');
|
||||
return { ext: ext, base: base }
|
||||
}
|
||||
|
||||
// 선택한 대상이 포함된 상위 폴더 경로 추출
|
||||
export function splitTopPathAndTargetName(resourcePath) {
|
||||
let resourcePathSplit = resourcePath.split('/');
|
||||
let targetName = resourcePathSplit.pop();
|
||||
let topPath = resourcePathSplit.join('/');
|
||||
if (!topPath || topPath == '' || topPath == null) topPath = '/';
|
||||
return { targetName: targetName, topPath: topPath };
|
||||
}
|
||||
|
||||
export function extractPathByLength(target, count) {
|
||||
if (!Array.isArray(target)) {
|
||||
target = target.split('/');
|
||||
}
|
||||
const parts = target.filter(Boolean).slice(0, count);
|
||||
return '/' + parts.join('/');
|
||||
}
|
||||
|
||||
// 특수문자를 체크하는 정규표현식
|
||||
export function hasSpecialChar(target) {
|
||||
// const pattern = /[\/\\;:?`'"<>|#%&*]/;
|
||||
const pattern = /[\/\\:*?'"`<>|#]/;
|
||||
return pattern.test(target);
|
||||
}
|
||||
|
||||
// 동일한 폴더명/파일명 존재 여부 확인
|
||||
export function existEqualName(type, comparisonPath, targetName) {
|
||||
let result;
|
||||
if (comparisonPath != '' && comparisonPath != undefined) {
|
||||
result = getTypeDataFromFolderData(type, comparisonPath)[targetName];
|
||||
} else {
|
||||
// result = vars.currentTreeObject[type][targetName];
|
||||
result = vars.allTreeObject[type][targetName];
|
||||
}
|
||||
|
||||
// 헤더 버튼 이름 변경 시 페이지 버튼과 이름 같은 경우 변경 불가
|
||||
let pageBtnNameArr = ['과업개요', '공문'];
|
||||
if (comparisonPath == '/' && result == undefined) result = pageBtnNameArr.includes(targetName);
|
||||
|
||||
return result;
|
||||
|
||||
// folderData에서 타입(폴더/파일)과 경로를 사용해서 해당 경로의 타입별 데이터 가져오기
|
||||
function getTypeDataFromFolderData(type, path) {
|
||||
let data;
|
||||
if (type == 'folder') data = vars.allTreeObject[type];
|
||||
if (type == 'file') data = vars.currentTreeObject[type];
|
||||
|
||||
let pathSplit = path.split('/');
|
||||
pathSplit.shift();
|
||||
// if (pathSplit[0] == '') return data;
|
||||
|
||||
for (let i = 0; i < pathSplit.length; i++) {
|
||||
if (data[pathSplit[i]]) {
|
||||
if (type == 'folder') data = data[pathSplit[i]]['child'][type];
|
||||
if (type == 'file') data = data;
|
||||
} else {
|
||||
data = data;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export function getNextFileName(nameArr, newName) {
|
||||
const dotIdx = newName.lastIndexOf('.');
|
||||
const base = dotIdx !== -1 ? newName.slice(0, dotIdx) : newName;
|
||||
const ext = dotIdx !== -1 ? newName.slice(dotIdx) : '';
|
||||
const set = new Set(nameArr);
|
||||
|
||||
let maxNum = 0;
|
||||
const regex = new RegExp(`^${escapeRegExp(base)}(?: \\((\\d+)\\))?${escapeRegExp(ext)}$`);
|
||||
for (const name of set) {
|
||||
const match = name.match(regex);
|
||||
if (match) {
|
||||
const num = match[1] ? parseInt(match[1], 10) : 0;
|
||||
if (num > maxNum) maxNum = num;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxNum === 0 && !set.has(newName)) return newName;
|
||||
return `${base} (${maxNum + 1})${ext}`;
|
||||
|
||||
function escapeRegExp(str) {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
}
|
||||
|
||||
export function getDataFromTreeObject(path, type, data) {
|
||||
let parentData;
|
||||
if (!data) data = vars.allTreeObject;
|
||||
let pathSplit = path.split('/');
|
||||
pathSplit.shift();
|
||||
|
||||
for (let i = 0; i < pathSplit.length; i++) {
|
||||
if (i != pathSplit.length - 1) {
|
||||
if (data['folder'][pathSplit[i]]) {
|
||||
data = data['folder'][pathSplit[i]]['child'];
|
||||
}
|
||||
} else {
|
||||
if (type == 'folder') {
|
||||
parentData = data;
|
||||
data = data['folder'][pathSplit[i]];
|
||||
}
|
||||
if (type == 'file') {
|
||||
if (data['file'][pathSplit[i]]) {
|
||||
parentData = data;
|
||||
data = data['file'][pathSplit[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return { parentData: parentData, data: data };
|
||||
}
|
||||
|
||||
export function getCurrentTreeObject(path) {
|
||||
let data = vars.allTreeObject;
|
||||
let pathSplit = path.split('/');
|
||||
pathSplit.shift();
|
||||
for (let i = 0; i < pathSplit.length; i++) {
|
||||
if (i != pathSplit.length - 1) {
|
||||
if (data['folder'][pathSplit[i]]) {
|
||||
data = data['folder'][pathSplit[i]]['child'];
|
||||
}
|
||||
} else {
|
||||
if (data['folder'][pathSplit[i]]) data = data['folder'][pathSplit[i]]['child'];
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export function targetFocus(target, behavior = 'smooth') {
|
||||
target.scrollIntoView({
|
||||
behavior: behavior, // 부드럽게
|
||||
block: 'center', // 화면 중앙에 위치하도록 (start, end, nearest 도 가능)
|
||||
inline: 'center'
|
||||
});
|
||||
target.focus();
|
||||
}
|
||||
|
||||
export function pxToRem(px) {
|
||||
let remPx = parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
return px / remPx;
|
||||
}
|
||||
|
||||
export function buildResourcePathFromSegments(rows) {
|
||||
if (!Array.isArray(rows)) rows = [rows];
|
||||
let result = [];
|
||||
rows.map(row => {
|
||||
let resourcePath = '/';
|
||||
let maxDepth = 8;
|
||||
let arr = [];
|
||||
for (let i = 0; i < maxDepth; i++) {
|
||||
let num = i+1;
|
||||
let segment = row[`path${num}`];
|
||||
if (segment) arr.push(segment);
|
||||
}
|
||||
if (arr.length != 0) resourcePath += arr.join('/');
|
||||
result.push(resourcePath);
|
||||
})
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function headerBtnForceClick(headerBtn) {
|
||||
headerBtn.click();
|
||||
}
|
||||
|
||||
export function treeBtnForceClick(treeBtn) {
|
||||
treeBtn.click();
|
||||
}
|
||||
|
||||
export function closeInitProgress() {
|
||||
document.querySelector('.init-progress').style.display = 'none';
|
||||
}
|
||||
|
||||
export async function getMyDownloadList(){
|
||||
let res = await axios.get(`${vars.path_name}/getMyDownloadList`, {params : {user_id : JSON.parse(vars.userInfoString).user_id}});
|
||||
// console.log(res.data);
|
||||
if(res.data.length > 0){
|
||||
if(document.querySelector('.download-btn .download-reddot')){
|
||||
document.querySelector('.download-btn .download-reddot').style.display = 'block';
|
||||
}
|
||||
}else{
|
||||
if(document.querySelector('.download-btn .download-reddot')){
|
||||
document.querySelector('.download-btn .download-reddot').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
let ul = document.querySelector('.download-modal__body_list');
|
||||
ul.innerHTML = '';
|
||||
for(let i = 0; i < res.data.length; i++){
|
||||
let li = document.createElement('li');
|
||||
li.classList.add('download-modal__body_list_item');
|
||||
|
||||
let expireDate = new Date(res.data[i].expire_date);
|
||||
let now = new Date();
|
||||
let yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
|
||||
let state = (expireDate < yesterday)? `<img class="--button__xsmall-icon --disable" src="/main/img/archive/icon__download--111.svg" alt="/" />`: (!res.data[i].made)?`준비중`:`<img class="--button__xsmall-icon downloadFolder__btn__icon" src="/main/img/archive/icon__download--111.svg" alt="/" />`
|
||||
let expire =(state == '준비중')?state :(expireDate < yesterday)? '만료됨': formatDateTime(expireDate);
|
||||
|
||||
li.innerHTML = `
|
||||
<img class="--icon" src="/main/img/header/header-folder-icon.svg" alt="/" />
|
||||
<div class="download-modal__body_list_item_text">
|
||||
<h4 class="download-modal__body_list_item_text_title">
|
||||
${res.data[i].name}
|
||||
</h4>
|
||||
<p class="download-modal__body_list_item_text_subtitle">
|
||||
${res.data[i].project_nm}${res.data[i].path}
|
||||
</p>
|
||||
</div>
|
||||
<h6 class="download-modal__body_list_item_time">
|
||||
${expire}
|
||||
</h6>
|
||||
<div class="download-modal__body_list_item_status">${state}</div>
|
||||
`;
|
||||
|
||||
ul.appendChild(li);
|
||||
|
||||
li.querySelector('.downloadFolder__btn__icon')?.addEventListener('click', () => {
|
||||
window.location.href = res.data[i].url;
|
||||
})
|
||||
}
|
||||
|
||||
function formatDateTime(date) {
|
||||
const padZero = (num) => String(num).padStart(2, '0');
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = padZero(date.getMonth() + 1); // getMonth()는 0부터 시작하므로 +1
|
||||
const day = padZero(date.getDate());
|
||||
const hours = padZero(date.getHours());
|
||||
const minutes = padZero(date.getMinutes());
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function openNewWindowViewer() {
|
||||
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
||||
let lastListItem = (isRecycleBinModal) ? vars.lastListItem_bin : vars.lastListItem;
|
||||
|
||||
let resourcePath = lastListItem.dataset.resourcePath;
|
||||
let dataId = lastListItem.dataset.id;
|
||||
let isLowerExt = true, ext = splitBaseAndExt(resourcePath, isLowerExt).ext;
|
||||
|
||||
let thumbnail_key = getDataFromTreeObject(resourcePath, 'file', vars.currentTreeObject).data?.thumbnailKey;
|
||||
|
||||
//Presigned URL
|
||||
let PresignedUrl = undefined;
|
||||
let getDataInfoParams = {
|
||||
userInfoString: vars.userInfoString,
|
||||
storageType: vars.storageType,
|
||||
dataIdArr: [dataId],
|
||||
isRemoved: isRecycleBinModal,
|
||||
debug: "'우측 미리보기 새 창으로 열기 버튼 클릭 이벤트'에서 실행"
|
||||
}
|
||||
let getDataInfoRes = await axios.post(`${vars.path_name}/getDataInfo`, { params: getDataInfoParams } );
|
||||
if (getDataInfoRes.data.message == 'getDataInfo_success') {
|
||||
let objectKey = getDataInfoRes.data.result.popup_key;
|
||||
if(objectKey == undefined || objectKey == `` || objectKey == null){
|
||||
return;
|
||||
}
|
||||
let generateDownloadUrlParams = {
|
||||
objectKey: objectKey,
|
||||
resourcePath: resourcePath
|
||||
}
|
||||
let generateDownloadUrlRes = await axios.post(`${vars.path_name}/generateDownloadUrl`, generateDownloadUrlParams);
|
||||
if (generateDownloadUrlRes.data.message == 'generateDownloadUrl_success') {
|
||||
PresignedUrl = generateDownloadUrlRes.data.url;
|
||||
}
|
||||
}
|
||||
//Presigned URL end
|
||||
|
||||
|
||||
thumbnail_key = encodeURIComponent(thumbnail_key);
|
||||
resourcePath = encodeURIComponent(resourcePath);
|
||||
let fullPath = encodeURIComponent(PresignedUrl);
|
||||
|
||||
// 뷰어 기본 width값은 현재 화면 width값의 절반
|
||||
let denominator = 2;
|
||||
// url 뷰어는 현재 화면 width값과 동일하게 설정
|
||||
if (ext == 'url') denominator = 1;
|
||||
|
||||
let width = screen.width / denominator;
|
||||
let height = screen.height;
|
||||
let left = screen.width * 2 + 10;
|
||||
|
||||
let open_ext = `pdf`;
|
||||
switch(ext){
|
||||
case 'pdf' :
|
||||
case 'hwp' :
|
||||
case 'hwpx' :
|
||||
case 'xls' :
|
||||
case 'xlsm' :
|
||||
case 'ppt' :
|
||||
case 'pptx' :
|
||||
case 'doc' :
|
||||
case 'docx' :
|
||||
case 'dwg' :
|
||||
case 'dxf' :
|
||||
case 'grm' :
|
||||
open_ext = 'pdf';
|
||||
break
|
||||
case 'gsim' :
|
||||
open_ext = 'gsim';
|
||||
break
|
||||
case 'ifc' :
|
||||
open_ext = 'ifc';
|
||||
break
|
||||
case 'png' :
|
||||
case 'jpg' :
|
||||
case 'jpeg' :
|
||||
case 'webp' :
|
||||
case 'gif' :
|
||||
open_ext = 'png';
|
||||
break
|
||||
case 'mp4' :
|
||||
case 'mov' :
|
||||
case 'webm' :
|
||||
open_ext = 'mp4';
|
||||
break
|
||||
case 'log' :
|
||||
case 'txt' :
|
||||
case 'md' :
|
||||
open_ext = 'txt';
|
||||
break
|
||||
case 'url' :
|
||||
open_ext = 'url';
|
||||
break;
|
||||
case 'zip' :
|
||||
open_ext = 'zip';
|
||||
break
|
||||
case 'glb':
|
||||
case 'gltf':
|
||||
case 'obj':
|
||||
case 'stl':
|
||||
case 'fbx':
|
||||
case '3dm':
|
||||
open_ext = 'glb';
|
||||
break;
|
||||
case 'html':
|
||||
open_ext = 'html';
|
||||
break;
|
||||
}
|
||||
|
||||
//presigned url은 ext를 읽을수 없엉...
|
||||
const jsonData = JSON.stringify({$ext : open_ext});
|
||||
const encodedData = btoa(jsonData);
|
||||
|
||||
// 3d모델뷰어 썸네일 생성을 위해 dataId, path_name 추가
|
||||
// let popup = window.open(`/popup?path=${fullPath}&data=${encodedData}`, '', `width=${width}, height=${height}, left=${left},`);
|
||||
let popup = window.open(`/popup?thumbnail_key=${thumbnail_key}&path_name=${vars.path_name}&resourcePath=${resourcePath}&dataId=${dataId}&path=${fullPath}&data=${encodedData}`, '', `width=${width}, height=${height}, left=${left},`);
|
||||
}
|
||||
|
||||
export function initFileAreaUI(fileAreaMode) {
|
||||
let listContainer = document.querySelector('.archive-main .archive-main-center .list-container');
|
||||
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
||||
if (isRecycleBinModal) listContainer = document.querySelector('.recycle-bin-modal .recycle-bin-wrap .list-container');
|
||||
|
||||
let listHeader = listContainer.querySelector('.list-header');
|
||||
let listBody = listContainer.querySelector('.list-body');
|
||||
let listBodyItemWrap = listBody.querySelector('.list-item-wrap');
|
||||
let mapContainer = listBody.querySelector('.map-container');
|
||||
|
||||
let archiveMainRight = document.querySelector('.archive-main-right');
|
||||
let viewerHeader = archiveMainRight.querySelector('.viewer-header');
|
||||
|
||||
if (fileAreaMode == 'map') {
|
||||
listHeader.style.display = 'none';
|
||||
listBody.style.height = '100%';
|
||||
listBody.style.overflowY = 'hidden';
|
||||
listBodyItemWrap.style.display = 'none';
|
||||
if (mapContainer) mapContainer.style.display = 'flex';
|
||||
|
||||
archiveMainRight.style.minWidth = '36rem';
|
||||
archiveMainRight.style.maxWidth = '36rem';
|
||||
archiveMainRight.style.height = 'calc(100% - 2.75rem)';
|
||||
archiveMainRight.style.position = 'absolute';
|
||||
archiveMainRight.style.right = '0.625rem';
|
||||
archiveMainRight.style.top = '0.5rem';
|
||||
archiveMainRight.style.background = '#fffa';
|
||||
archiveMainRight.style.backdropFilter = 'blur(0.2rem)';
|
||||
archiveMainRight.style.webkitBackdropFilter = 'blur(0.2rem)';
|
||||
archiveMainRight.style.borderRadius = '0.25rem';
|
||||
archiveMainRight.style.boxShadow = '#ccc 0 0 0rem 0.0625rem, #0c0c0db3 0rem 0.5rem 1rem -0.25rem';
|
||||
viewerHeader.style.borderRadius = '0.25rem 0.25rem 0 0';
|
||||
} else {
|
||||
listHeader.style.display = 'flex';
|
||||
listBody.style.height = 'calc(100% - 2.25rem)';
|
||||
listBody.style.overflowY = 'scroll';
|
||||
listBodyItemWrap.style.display = 'flex';
|
||||
if (mapContainer) mapContainer.style.display = 'none';
|
||||
|
||||
archiveMainRight.style.minWidth = '41rem';
|
||||
archiveMainRight.style.maxWidth = '41rem';
|
||||
archiveMainRight.style.height = '100%';
|
||||
archiveMainRight.style.position = 'relative';
|
||||
archiveMainRight.style.right = 'unset';
|
||||
archiveMainRight.style.top = 'unset';
|
||||
archiveMainRight.style.background = '#fff';
|
||||
archiveMainRight.style.backdropFilter = 'none';
|
||||
archiveMainRight.style.webkitBackdropFilter = 'none';
|
||||
archiveMainRight.style.borderRadius = 'unset';
|
||||
archiveMainRight.style.boxShadow = 'none';
|
||||
viewerHeader.style.borderRadius = 'unset';
|
||||
}
|
||||
}
|
||||
|
||||
export function syncGroupStyle() {
|
||||
let listContainer = document.querySelector('.archive-main .archive-main-center .list-container');
|
||||
let isRecycleBinModal = document.querySelector('.recycle-bin-modal').style.display == 'flex';
|
||||
if (isRecycleBinModal) listContainer = document.querySelector('.recycle-bin-modal .recycle-bin-wrap .list-container');
|
||||
|
||||
listContainer.querySelectorAll('.main-list-item').forEach(item => {
|
||||
if (listContainer.querySelectorAll(`.list-item.selected[data-main-file-name="${item.dataset.mainFileName}"]`).length > 0) {
|
||||
listContainer.querySelectorAll(`.list-item[data-main-file-name="${item.dataset.mainFileName}"]`).forEach(item => {
|
||||
item.classList.add('group-style');
|
||||
item.classList.remove('non-selected');
|
||||
})
|
||||
} else {
|
||||
listContainer.querySelectorAll(`.list-item[data-main-file-name="${item.dataset.mainFileName}"]`).forEach(item => {
|
||||
item.classList.remove('group-style');
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
2190
views/main/jsm/archive/dataManager.js
Normal file
2190
views/main/jsm/archive/dataManager.js
Normal file
File diff suppressed because it is too large
Load Diff
2976
views/main/jsm/archive/eventManager.js
Normal file
2976
views/main/jsm/archive/eventManager.js
Normal file
File diff suppressed because it is too large
Load Diff
348
views/main/jsm/archive/fileDrag.js
Normal file
348
views/main/jsm/archive/fileDrag.js
Normal file
@@ -0,0 +1,348 @@
|
||||
import { vars } from './variable.js';
|
||||
import {
|
||||
targetFocus,
|
||||
syncGroupStyle
|
||||
} from './common.js';
|
||||
import {
|
||||
resetViewer,
|
||||
} from './pageRenderer.js';
|
||||
|
||||
let archiveMain = document.querySelector('.archive-main');
|
||||
let viewerContainer = archiveMain?.querySelector('.archive-main-right .viewer-container');
|
||||
|
||||
addFileDragEvent(document.querySelector('.archive-main-center .list-container .list-body'), 'file-area');
|
||||
addFileDragEvent(document.querySelector('.recycle-bin-modal .list-container .list-body'), 'recycle-bin');
|
||||
|
||||
export function addFileDragEvent(listBody, target) {
|
||||
let draggingStartTarget, draggingEndTarget;
|
||||
let draggingStart = false, isDragging = false;
|
||||
let isCtrl = false, isShift = false;
|
||||
let processedItems = new Set();
|
||||
let originalSelection = new Set();
|
||||
|
||||
let multiSelectBox = document.createElement('div');
|
||||
multiSelectBox.style.position = 'absolute';
|
||||
multiSelectBox.style.border = '1px solid rgba(0, 120, 215, 1)';
|
||||
multiSelectBox.style.background = 'rgba(0, 120, 215, 0.2)';
|
||||
multiSelectBox.style.zIndex = '9999';
|
||||
multiSelectBox.style.pointerEvents = 'none';
|
||||
multiSelectBox.style.display = 'none';
|
||||
listBody.appendChild(multiSelectBox);
|
||||
|
||||
vars.preventNextClick = false;
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let prevClientX = 0;
|
||||
let prevClientY = 0;
|
||||
|
||||
let autoScrollDirection = null;
|
||||
let autoScrollFrameId = null;
|
||||
const scrollSpeed = 8;
|
||||
const scrollMargin = 36;
|
||||
|
||||
function autoScrollLoop() {
|
||||
if (!autoScrollDirection) return;
|
||||
|
||||
// 더 작으면 느리고 부드러움
|
||||
if (autoScrollDirection === 'down') {
|
||||
listBody.scrollTop += scrollSpeed;
|
||||
} else if (autoScrollDirection === 'up') {
|
||||
listBody.scrollTop -= scrollSpeed;
|
||||
}
|
||||
|
||||
autoScrollFrameId = requestAnimationFrame(autoScrollLoop);
|
||||
}
|
||||
|
||||
listBody.addEventListener('pointerdown', (e) => {
|
||||
// 마우스 좌클릭이 아닌 경우 리턴
|
||||
if (e.button !== 0) return;
|
||||
// 갤러리 폴더 지도화면인 경우 리턴
|
||||
if (target == 'file-area' && listBody.querySelector('.map-container').style.display === 'flex') return;
|
||||
|
||||
draggingStartTarget = e.target;
|
||||
draggingStart = true;
|
||||
isDragging = false;
|
||||
isCtrl = e.ctrlKey;
|
||||
isShift = e.shiftKey;
|
||||
processedItems.clear();
|
||||
if (target == 'file-area') originalSelection = new Set(vars.multiSelectListItemArr);
|
||||
if (target == 'recycle-bin') originalSelection = new Set(vars.multiSelectListItemArr_bin);
|
||||
vars.preventNextClick = false;
|
||||
|
||||
// if (isCtrl) {
|
||||
// vars.lastListItem = e.target.closest('.list-item');
|
||||
// } else {
|
||||
// listBody.querySelectorAll('.list-item').forEach(listItem => {
|
||||
// listItem.classList.remove('selected');
|
||||
// listItem.classList.remove('group-style');
|
||||
// })
|
||||
|
||||
// vars.multiSelectListItemArr = [];
|
||||
// }
|
||||
|
||||
const dragStartListItem = e.target.closest('.list-item');
|
||||
if (dragStartListItem && !isShift) {
|
||||
if (target == 'file-area') vars.lastListItem = dragStartListItem;
|
||||
if (target == 'recycle-bin') vars.lastListItem_bin = dragStartListItem;
|
||||
}
|
||||
|
||||
if (!isCtrl) {
|
||||
// Ctrl이 아닐 경우 기존 선택 모두 해제
|
||||
listBody.querySelectorAll('.list-item').forEach(listItem => {
|
||||
listItem.classList.remove('selected');
|
||||
listItem.classList.remove('group-style');
|
||||
});
|
||||
|
||||
if (target == 'file-area') vars.multiSelectListItemArr = [];
|
||||
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = [];
|
||||
}
|
||||
|
||||
const bodyRect = listBody.getBoundingClientRect();
|
||||
startX = e.clientX + listBody.scrollLeft - bodyRect.left;
|
||||
startY = e.clientY + listBody.scrollTop - bodyRect.top;
|
||||
|
||||
prevClientX = e.clientX;
|
||||
prevClientY = e.clientY;
|
||||
|
||||
Object.assign(multiSelectBox.style, {
|
||||
left: `${startX}px`,
|
||||
top: `${startY}px`,
|
||||
width: `0px`,
|
||||
height: `0px`,
|
||||
display: 'none'
|
||||
});
|
||||
});
|
||||
|
||||
listBody.addEventListener('pointermove', (e) => {
|
||||
// 마우스 좌클릭이 아닌 경우 리턴
|
||||
if (e.buttons !== 1) return;
|
||||
// list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴
|
||||
if (!draggingStart) return;
|
||||
|
||||
if (e.clientX === prevClientX && e.clientY === prevClientY) return;
|
||||
prevClientX = e.clientX;
|
||||
prevClientY = e.clientY;
|
||||
|
||||
const listBodyRect = listBody.getBoundingClientRect();
|
||||
|
||||
const currX = e.clientX + listBody.scrollLeft - listBodyRect.left;
|
||||
const currY = e.clientY + listBody.scrollTop - listBodyRect.top;
|
||||
|
||||
const dx = currX - startX;
|
||||
const dy = currY - startY;
|
||||
|
||||
if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
|
||||
isDragging = true;
|
||||
vars.preventNextClick = true;
|
||||
|
||||
const left = Math.min(startX, currX);
|
||||
const top = Math.min(startY, currY);
|
||||
const width = Math.abs(dx);
|
||||
const height = Math.abs(dy);
|
||||
|
||||
Object.assign(multiSelectBox.style, {
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
display: 'block'
|
||||
});
|
||||
|
||||
const boxRect = multiSelectBox.getBoundingClientRect();
|
||||
|
||||
listBody.querySelectorAll('.list-item .wrap').forEach(elem => {
|
||||
const rect = elem.getBoundingClientRect();
|
||||
const overlaps = !(
|
||||
rect.right < boxRect.left ||
|
||||
rect.left > boxRect.right ||
|
||||
rect.bottom < boxRect.top ||
|
||||
rect.top > boxRect.bottom
|
||||
);
|
||||
|
||||
let listItem = elem.parentElement;
|
||||
|
||||
if (isCtrl) {
|
||||
if (overlaps && !processedItems.has(listItem)) {
|
||||
processedItems.add(listItem);
|
||||
|
||||
// ✅ 토글: 원래 선택되어 있었으면 해제, 아니면 선택
|
||||
if (originalSelection.has(listItem)) {
|
||||
listItem.classList.remove('selected');
|
||||
listItem.classList.remove('group-style');
|
||||
if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem);
|
||||
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item !== listItem);
|
||||
} else {
|
||||
listItem.classList.add('selected');
|
||||
listItem.classList.add('group-style');
|
||||
if (target == 'file-area') vars.multiSelectListItemArr.push(listItem);
|
||||
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin.push(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 박스에서 벗어난 항목 복원
|
||||
if (!overlaps && processedItems.has(listItem)) {
|
||||
processedItems.delete(listItem);
|
||||
|
||||
if (originalSelection.has(listItem)) {
|
||||
listItem.classList.add('selected');
|
||||
listItem.classList.add('group-style');
|
||||
if (target == 'file-area') {
|
||||
if (!vars.multiSelectListItemArr.includes(listItem)) vars.multiSelectListItemArr.push(listItem);
|
||||
}
|
||||
if (target == 'recycle-bin') {
|
||||
if (!vars.multiSelectListItemArr_bin.includes(listItem)) vars.multiSelectListItemArr_bin.push(listItem);
|
||||
}
|
||||
} else {
|
||||
listItem.classList.remove('selected');
|
||||
listItem.classList.remove('group-style');
|
||||
if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item !== listItem);
|
||||
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item !== listItem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
listItem.classList.toggle('selected', overlaps);
|
||||
listItem.classList.toggle('group-style', overlaps);
|
||||
|
||||
if (target == 'file-area') {
|
||||
if (overlaps && !vars.multiSelectListItemArr.includes(listItem)) vars.multiSelectListItemArr.push(listItem);
|
||||
}
|
||||
if (target == 'recycle-bin') {
|
||||
if (overlaps && !vars.multiSelectListItemArr_bin.includes(listItem)) vars.multiSelectListItemArr_bin.push(listItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (e.clientY > listBodyRect.bottom - scrollMargin) {
|
||||
if (autoScrollDirection !== 'down') {
|
||||
cancelAnimationFrame(autoScrollFrameId);
|
||||
autoScrollDirection = 'down';
|
||||
autoScrollLoop();
|
||||
}
|
||||
} else if (e.clientY < listBodyRect.top + scrollMargin) {
|
||||
if (autoScrollDirection !== 'up') {
|
||||
cancelAnimationFrame(autoScrollFrameId);
|
||||
autoScrollDirection = 'up';
|
||||
autoScrollLoop();
|
||||
}
|
||||
} else {
|
||||
if (autoScrollDirection !== null) {
|
||||
cancelAnimationFrame(autoScrollFrameId);
|
||||
autoScrollDirection = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('pointerup', async (e) => {
|
||||
// list-body에서 마우스 드래그가 시작된게 아닌 경우 리턴
|
||||
if (!draggingStart) return;
|
||||
|
||||
if (target == 'file-area') {
|
||||
if (vars.lastHeaderBtn) targetFocus(vars.lastHeaderBtn);
|
||||
if (vars.lastMainTreeItem) targetFocus(vars.lastMainTreeItem);
|
||||
}
|
||||
|
||||
if (isShift && isDragging) {
|
||||
const dragStartListItem = draggingStartTarget.closest('.list-item');
|
||||
if (dragStartListItem) {
|
||||
if (target == 'file-area') vars.lastListItem = dragStartListItem;
|
||||
if (target == 'recycle-bin') vars.lastListItem_bin = dragStartListItem;
|
||||
}
|
||||
}
|
||||
|
||||
multiSelectBox.style.display = 'none';
|
||||
draggingEndTarget = e.target;
|
||||
draggingStart = false;
|
||||
isDragging = false;
|
||||
isCtrl = false;
|
||||
isShift = false;
|
||||
|
||||
// 드래그가 아이템 바깥 영역에서만 진행되고, 드래그에 포함된 아이템이 하나도 없을 때 뷰어 초기화
|
||||
if (
|
||||
(
|
||||
(draggingStartTarget.matches('.list-body') && draggingEndTarget.matches('.list-body'))
|
||||
|| (draggingStartTarget.matches('.list-item-wrap') && draggingEndTarget.matches('.list-item-wrap'))
|
||||
)
|
||||
&&
|
||||
(
|
||||
(target == 'file-area' && vars.multiSelectListItemArr.length == 0)
|
||||
|| (target == 'recycle-bin' && vars.multiSelectListItemArr_bin.length == 0)
|
||||
)
|
||||
)
|
||||
{
|
||||
// 모든 아이템에서 non-selected 클래스 삭제
|
||||
document.querySelectorAll('.grid-item').forEach(gridItem => {
|
||||
gridItem.classList.remove('non-selected')
|
||||
})
|
||||
// if (viewerContainer.style.display == 'flex') resetViewer();
|
||||
resetViewer();
|
||||
|
||||
vars.lastContextTarget = undefined;
|
||||
if (target == 'file-area') {
|
||||
vars.lastListGroupTarget = undefined;
|
||||
vars.lastListItem = undefined;
|
||||
// vars.lastContextTarget = undefined;
|
||||
}
|
||||
if (target == 'recycle-bin') {
|
||||
vars.lastListGroupTarget_bin = undefined;
|
||||
vars.lastListItem_bin = undefined;
|
||||
// vars.lastContextTarget_bin = undefined;
|
||||
}
|
||||
|
||||
// userSelected 삭제
|
||||
let me = JSON.parse(vars.userInfoString);
|
||||
me.selected = undefined;
|
||||
vars.socket.emit('fileSelect',{me : me});
|
||||
} else {
|
||||
if (target == 'file-area') {
|
||||
// 모든 아이템에 non-selected 클래스 추가
|
||||
document.querySelectorAll('.grid-item').forEach(gridItem => {
|
||||
gridItem.classList.add('non-selected')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 드래그 시작한 대상과 종료된 대상이 다르면 뷰어 초기화
|
||||
if (draggingStartTarget != draggingEndTarget) {
|
||||
if (target == 'file-area') {
|
||||
vars.lastListItem = undefined;
|
||||
vars.lastListGroupTarget = undefined;
|
||||
}
|
||||
if (target == 'recycle-bin') {
|
||||
vars.lastListItem_bin = undefined;
|
||||
vars.lastListGroupTarget_bin = undefined;
|
||||
}
|
||||
if (viewerContainer.style.display == 'flex') resetViewer();
|
||||
}
|
||||
|
||||
// 드래그가 끝나고 pointerup 이벤트가 발생하는 시점에 selected 클래스가 있는 listItem만 필터링
|
||||
if (target == 'file-area') vars.multiSelectListItemArr = vars.multiSelectListItemArr.filter(item => item.classList.contains('selected'));
|
||||
if (target == 'recycle-bin') vars.multiSelectListItemArr_bin = vars.multiSelectListItemArr_bin.filter(item => item.classList.contains('selected'));
|
||||
|
||||
// vars.multiSelectListItemArr/vars.multiSelectListItemArr_bin에 포함된 아이템에서 non-selected 클래스 삭제
|
||||
if (target == 'file-area') {
|
||||
vars.multiSelectListItemArr.forEach(item => {
|
||||
item.classList.remove('non-selected');
|
||||
})
|
||||
}
|
||||
if (target == 'recycle-bin') {
|
||||
vars.multiSelectListItemArr_bin.forEach(item => {
|
||||
item.classList.remove('non-selected');
|
||||
})
|
||||
}
|
||||
|
||||
await syncGroupStyle();
|
||||
|
||||
// 드래그 시작한 대상의 부모요소와 종료된 대상의 부모요소가 다르면 뷰어 초기화
|
||||
// if (draggingStartTarget.parentElement != draggingEndTarget.parentElement) {
|
||||
// if (viewerContainer.style.display == 'flex') resetViewer();
|
||||
// }
|
||||
|
||||
if (target == 'file-area') vars.lastSelectTarget = vars.multiSelectListItemArr[0];
|
||||
if (target == 'recycle-bin') vars.lastSelectTarget_bin = vars.multiSelectListItemArr_bin[0];
|
||||
|
||||
cancelAnimationFrame(autoScrollFrameId);
|
||||
autoScrollDirection = null;
|
||||
});
|
||||
}
|
||||
59
views/main/jsm/archive/index.js
Normal file
59
views/main/jsm/archive/index.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import { vars } from './variable.js';
|
||||
import * as socketManager from './socketManager.js';
|
||||
import * as eventManager from './eventManager.js';
|
||||
import * as fileDrag from './fileDrag.js';
|
||||
|
||||
import {
|
||||
preparePageRendering,
|
||||
renderSizeBar,
|
||||
renderLog
|
||||
} from './pageRenderer.js';
|
||||
|
||||
export let isLoaded = false;
|
||||
export function loadArchive() {
|
||||
isLoaded = true;
|
||||
}
|
||||
|
||||
//// 뷰어 요약 ai버튼 삭제
|
||||
// document.querySelector('.ai-btn').remove();
|
||||
|
||||
//// archive controller에 useEncrypt 변수 설정
|
||||
// let setUseEncryptRes = await axios.put(`${vars.path_name}/setUseEncrypt`, {useEncrypt: vars.convertOption.useEncrypt});
|
||||
|
||||
let pageRanderingOption = {
|
||||
isInit: true,
|
||||
// favorite: 즐겨찾기 한 경로
|
||||
scope: 'headerBtn'
|
||||
}
|
||||
await preparePageRendering(pageRanderingOption);
|
||||
|
||||
//// 폴더 사용 용량 표시
|
||||
await renderSizeBar();
|
||||
|
||||
//// 활동 정보
|
||||
let isInit = true;
|
||||
await renderLog(isInit);
|
||||
|
||||
// ol.map = undefined;
|
||||
// ol.map = new ol.Map({
|
||||
// target: 'map-container',
|
||||
// layers: [vars.baseLayer],
|
||||
// // layers: [vars.roadLayer, vars.satelliteLayer, vars.hybridLayer],
|
||||
// view: new ol.View({
|
||||
// projection: 'EPSG:3857',
|
||||
// center: ol.proj.fromLonLat([127.8, 35.9]),
|
||||
// zoom: 7,
|
||||
// constrainResolution: true,
|
||||
// smoothResolutionConstraint: true
|
||||
// })
|
||||
// });
|
||||
|
||||
// proj4.defs([
|
||||
// ['EPSG:5185', '+proj=tmerc +lat_0=38 +lon_0=125 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
// ['EPSG:5186', '+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
// ['EPSG:5187', '+proj=tmerc +lat_0=38 +lon_0=129 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
// ['EPSG:5188', '+proj=tmerc +lat_0=38 +lon_0=131 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
// ['EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'],
|
||||
// ['EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs'],
|
||||
// ])
|
||||
// ol.proj.proj4.register(proj4);
|
||||
241
views/main/jsm/archive/logFilter.js
Normal file
241
views/main/jsm/archive/logFilter.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import { vars } from './variable.js';
|
||||
import { renderLog } from './pageRenderer.js';
|
||||
|
||||
const modal = document.querySelector('.log-filter');
|
||||
const customDisplay = modal.querySelector('.custom-select-display');
|
||||
const customList = modal.querySelector('.custom-select-list');
|
||||
const userSelect = document.querySelector('#log-user-select');
|
||||
|
||||
// 기본 로그필터 정보 불러오기
|
||||
export async function getDefaultFilter() {
|
||||
// 해당 프로젝트에 존재하는 모든 사용자
|
||||
let userLogRes = await axios.get(`${vars.path_name}/getUserLog`);
|
||||
if (userLogRes.data.message == 'getUserLog_success') {
|
||||
const userLogDataArr = Object.values(userLogRes.data.logData);
|
||||
|
||||
// user_id -> user_nm 매핑 (중복 제거)
|
||||
const userMap = new Map();
|
||||
userLogDataArr.forEach(data => {
|
||||
if(!userMap.has(data.user_id)) {
|
||||
userMap.set(data.user_id, data.user_nm);
|
||||
}
|
||||
});
|
||||
|
||||
// selectbox 사용자 리스트 추가
|
||||
userSelect.innerHTML = '';
|
||||
userSelect.insertAdjacentHTML('beforeend', '<option value="allUser">모든 사용자</option>');
|
||||
|
||||
// Map -> Array로 변환 후 user_id로 정렬
|
||||
const sortedUsers = [...userMap.entries()].sort((a, b) => {
|
||||
const userIdA = a[0];
|
||||
const userIdB = b[0];
|
||||
return userIdA.localeCompare(userIdB, 'ko-KR');
|
||||
});
|
||||
|
||||
// 정렬된 사용자 추가
|
||||
sortedUsers.forEach(([userId, userNm]) => {
|
||||
let option = document.createElement('option');
|
||||
option.value = userId;
|
||||
option.textContent = `${userId} (${userNm})`;
|
||||
userSelect.appendChild(option);
|
||||
});
|
||||
|
||||
// custom select 리스트에도 반영
|
||||
customList.innerHTML = '';
|
||||
Array.from(userSelect.options).forEach(opt => {
|
||||
const li = document.createElement('li');
|
||||
li.dataset.value = opt.value;
|
||||
li.textContent = opt.textContent;
|
||||
customList.appendChild(li);
|
||||
});
|
||||
|
||||
// 기본값으로 설정
|
||||
customDisplay.textContent = "모든 사용자";
|
||||
userSelect.value = "allUser";
|
||||
customList.style.display = "none"; // 혹시 열려있으면 닫기
|
||||
}
|
||||
|
||||
// 활동유형 초기화
|
||||
let activityCheckbox = modal.querySelectorAll('.body .log-activity label input[type="checkbox"]');
|
||||
activityCheckbox.forEach(checkbox => {
|
||||
checkbox.checked = true;
|
||||
})
|
||||
|
||||
// 활동로그에 존재하는 '오늘 ~ 일주일' 간의 로그 기록
|
||||
let logRes = await axios.get(`${vars.path_name}/getLog`);
|
||||
if (logRes.data.message == 'getLog_success') {
|
||||
const logDataArr = Object.values(logRes.data.logData);
|
||||
|
||||
// 기본 활동시간 - 오늘 ~ 일주일로 설정
|
||||
const dateInputs = modal.querySelectorAll('.log-date .log-date-wrap input[type="date"]');
|
||||
|
||||
const toDateStr = (d) => d.toISOString().slice(0, 10);
|
||||
|
||||
const today = new Date();
|
||||
const weekAgo = new Date(today)
|
||||
weekAgo.setDate(today.getDate() - 7);
|
||||
|
||||
let startDate = toDateStr(weekAgo);
|
||||
let endDate = toDateStr(today);
|
||||
|
||||
// 달력창 UI
|
||||
dateInputs[0].value = startDate;
|
||||
dateInputs[1].value = endDate;
|
||||
|
||||
return { logDataArr }
|
||||
}
|
||||
}
|
||||
|
||||
// custom select 동작
|
||||
customDisplay.addEventListener('click', () => {
|
||||
customList.style.display = customList.style.display === 'flex' ? 'none': 'flex';
|
||||
});
|
||||
|
||||
// 항목 선택 시
|
||||
customList.addEventListener('click', (e) => {
|
||||
if (e.target.tagName === 'LI') {
|
||||
const value = e.target.dataset.value;
|
||||
const text = e.target.textContent;
|
||||
|
||||
// 표시 UI 업데이트
|
||||
customDisplay.textContent = text;
|
||||
|
||||
// 실제 select 값도 변경
|
||||
userSelect.value = value;
|
||||
|
||||
// 리스트 닫기
|
||||
customList.style.display = 'none';
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
// 로그필터 저장 버튼 클릭 이벤트
|
||||
modal.querySelector('.foot ._button-medium').addEventListener('click', async() => {
|
||||
// date 불러오기
|
||||
let { startDate, endDate } = getLogFilterDate();
|
||||
|
||||
// user 불러오기
|
||||
let { user } = getLogFilterUser();
|
||||
|
||||
// activity 불러오기
|
||||
let { activity } = getLogFilterActivity();
|
||||
|
||||
// 서버로 전달한 파라미터
|
||||
const logFilterParams = {
|
||||
startDate: startDate,
|
||||
endDate: endDate,
|
||||
user: user,
|
||||
activity: activity
|
||||
};
|
||||
|
||||
let filterLogRes = await axios.get(`${vars.path_name}/getFilterLog`, { params: logFilterParams });
|
||||
if (filterLogRes.data.message == 'getFilterLog_success') {
|
||||
const isInit = true;
|
||||
const addFooterLog = false;
|
||||
|
||||
const archiveModalLogList = document.querySelector('.archive-modal > .wrap .modal-wrap .modal-body .log-wrap .log-item-wrap.log-body');
|
||||
archiveModalLogList.scrollTop = 0;
|
||||
|
||||
renderLog(isInit, addFooterLog, filterLogRes.data.logData)
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
document.querySelector('.archive-modal').addEventListener('click', async(e) => {
|
||||
if (modal.style.display == 'flex') {
|
||||
// 로그 필터 켜져있을 때에만 이벤트 작동
|
||||
if (e.target.classList.contains('custom-select-display') || e.target.classList.contains('custom-select-list') || e.target.closest('.custom-select-list')) {
|
||||
// 사용자 셀렉트 박스 또는 옵션 영역 클릭했을 때
|
||||
} else {
|
||||
// 사용자 셀렉트 박스 또는 옵션 영역을 제외한 나머지 모달 영역 클릭했을 때
|
||||
customList.style.display = 'none';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// date 불러오기
|
||||
function getLogFilterDate() {
|
||||
// 날짜 선택 (start, end)
|
||||
const dateInputs = modal.querySelectorAll('.log-date .log-date-wrap input[type="date"]');
|
||||
let startDate = dateInputs[0].value; // 시작일
|
||||
let endDate = dateInputs[1].value; // 종료일
|
||||
|
||||
return { startDate, endDate };
|
||||
}
|
||||
|
||||
// user 불러오기
|
||||
function getLogFilterUser() {
|
||||
// 선택한 사용자 user에 추가
|
||||
const userSelect = modal.querySelector('.body .log-user select');
|
||||
const selectValue = userSelect.value;
|
||||
let user = [];
|
||||
|
||||
if(selectValue == 'allUser') {
|
||||
// 전체 사용자 선택
|
||||
user = Array.from(userSelect.options)
|
||||
.map(opt => opt.value)
|
||||
.filter(value => value !== 'allUser' && value !== '');
|
||||
} else {
|
||||
user.push(selectValue);
|
||||
}
|
||||
|
||||
|
||||
return { user };
|
||||
}
|
||||
|
||||
// activity 불러오기
|
||||
function getLogFilterActivity() {
|
||||
// 체크한 활동유형 activity에 추가
|
||||
const activityMapping = {
|
||||
'uploadData_file' : ['uploadData_file', 'addOn_version', 'addOn_attachment'],
|
||||
'renameTarget' : ['renameTarget_folder', 'renameTarget_file'],
|
||||
'removeTarget' : ['removeTarget_folder', 'removeTarget_file', 'removeTarget_folder_expired'],
|
||||
'downloadTarget': ['downloadTarget_folder', 'downloadTarget_file'],
|
||||
'addPermission': ['addPermission_worker', 'addPermission_viewer', 'addPermission_subMaster', 'addPermission_securityWorker'],
|
||||
'deletePermission': ['deletePermission_worker', 'deletePermission_viewer', 'deletePermission_subMaster', 'deletePermission_securityWorker']
|
||||
}
|
||||
|
||||
const activity = Array.from(modal.querySelectorAll('.body .log-activity label input[type="checkbox"]:checked')).flatMap(check => activityMapping[check.value] || [check.value]);
|
||||
|
||||
return { activity };
|
||||
}
|
||||
|
||||
// 초기화 / 전체해제 버튼 클릭 이벤트
|
||||
const logFilterBtn = modal.querySelectorAll('._button-xsmall');
|
||||
logFilterBtn.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
if(e.target.classList.contains('reset')) {
|
||||
// 활동시간 초기화- '오늘~일주일'
|
||||
const dateInputs = modal.querySelectorAll('.log-date .log-date-wrap input[type="date"]');
|
||||
const toDateStr = (d) => d.toISOString().slice(0, 10);
|
||||
|
||||
const today = new Date();
|
||||
const weekAgo = new Date(today);
|
||||
weekAgo.setDate(today.getDate() - 7);
|
||||
|
||||
let startDate = toDateStr(weekAgo);
|
||||
let endDate = toDateStr(today);
|
||||
|
||||
dateInputs[0].value = startDate;
|
||||
dateInputs[1].value = endDate;
|
||||
|
||||
// 사용자 초기화 - '모든 사용자'
|
||||
customDisplay.textContent = '모든 사용자';
|
||||
userSelect.value = 'allUser';
|
||||
customList.style.display = 'none';
|
||||
}
|
||||
|
||||
const inputs = modal.querySelectorAll('.body .log-activity label input[type="checkbox"]');
|
||||
|
||||
inputs.forEach(input => {
|
||||
if(e.target.classList.contains('reset')) {
|
||||
input.checked = true;
|
||||
} else if (e.target.classList.contains('select-all')) {
|
||||
input.checked = true;
|
||||
} else if (e.target.classList.contains('clear-all')) {
|
||||
input.checked = false;
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
32
views/main/jsm/archive/managementFunctions.js
Normal file
32
views/main/jsm/archive/managementFunctions.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { vars } from './variable.js';
|
||||
import { toggleContextmenu, toggleContextFocusBox } from './eventManager.js'
|
||||
|
||||
export async function mgmtFunc_changeProjectState(toggleParams) {
|
||||
if (toggleParams.type == 'changeProjectState') toggleParams.targetColumn = 'is_active';
|
||||
await axios.post('/common/mgmtFunc_updateProject', { params: toggleParams });
|
||||
}
|
||||
|
||||
export async function mgmtFunc_changeBannerNotice(toggleParams) {
|
||||
if (toggleParams.type == 'changeBannerNotice') toggleParams.targetColumn = 'banner_notice';
|
||||
await axios.post('/common/mgmtFunc_updateProject', { params: toggleParams });
|
||||
}
|
||||
|
||||
export async function mgmtFunc_resetConvert(resourcePath, dataId) {
|
||||
let resetConvertParams = {
|
||||
resourcePath: resourcePath,
|
||||
dataId: dataId
|
||||
}
|
||||
|
||||
let resetConvertRes = await axios.post(`${vars.path_name}/mgmtFunc_resetConvert`, { params: resetConvertParams });
|
||||
if (resetConvertRes.data.message == 'mgmtFunc_resetConvert_success') {
|
||||
toggleContextmenu(false);
|
||||
toggleContextFocusBox(false);
|
||||
}
|
||||
}
|
||||
|
||||
export async function mgmtFunc_addClickLog(params) {
|
||||
// params.dataId -> 컨트롤러에서 숫자형으로 형변환해서 db추가
|
||||
// project_id, activity, user_id, user_ip는 컨트롤러에서 params에 추가 (user_id는 params.userInfoString 사용)
|
||||
let addClickLogRes = await axios.post(`${vars.path_name}/mgmtFunc_addClickLog`, { params: params });
|
||||
// console.log(addClickLogRes.data.message);
|
||||
}
|
||||
72
views/main/jsm/archive/manualSwiper.js
Normal file
72
views/main/jsm/archive/manualSwiper.js
Normal file
@@ -0,0 +1,72 @@
|
||||
let archiveManualSwiper = undefined;
|
||||
export async function renderManual() {
|
||||
await createManualSwiperArea();
|
||||
|
||||
archiveManualSwiper = new Swiper('.modal-body > .manual-wrap .manual-swiper', {
|
||||
slidesPerView: 1,
|
||||
spaceBetween: 0,
|
||||
centeredSlides: true,
|
||||
speed: 400,
|
||||
loop: true,
|
||||
navigation: {
|
||||
nextEl: '.swiper.manual-swiper .button-next',
|
||||
prevEl: '.swiper.manual-swiper .button-prev',
|
||||
},
|
||||
pagination: {
|
||||
el: '.swiper.manual-swiper .swiper-pagination',
|
||||
clickable: true,
|
||||
renderBullet: function (index, className) {
|
||||
return '<span class="' + className + '">' + (index + 1) + "</span>";
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** swiper area
|
||||
* [div] manual-wrap
|
||||
* [div] manual-swiper (v) <-- swiper
|
||||
* [ul] manual-swiper-wrap <-- swiper-wrapper
|
||||
* [li] manual-swiper-item <-- swiper-slide
|
||||
* [img] manual-img
|
||||
* [div] button-prev
|
||||
* [div] button-next
|
||||
* [div] swiper-pagination
|
||||
*/
|
||||
|
||||
export async function createManualSwiperArea() {
|
||||
const manualWrap = document.querySelector('.modal-body > .manual-wrap');
|
||||
manualWrap.innerHTML = ''; // swiper 초기화
|
||||
|
||||
const manualSwiper = document.createElement('div');
|
||||
manualSwiper.classList.add('swiper', 'manual-swiper');
|
||||
const manualSwiperWrap = document.createElement('ul');
|
||||
manualSwiperWrap.classList.add('swiper-wrapper', 'manual-swiper-wrap');
|
||||
manualSwiper.appendChild(manualSwiperWrap);
|
||||
manualWrap.appendChild(manualSwiper);
|
||||
|
||||
// swiper 동적 생성
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const manualSwiperItem = document.createElement('li');
|
||||
manualSwiperItem.classList.add('swiper-slide', 'manual-swiper-item');
|
||||
manualSwiperItem.dataset.save = true;
|
||||
manualSwiperItem.dataset.fileName = `${[i+1]}.jpg`;
|
||||
|
||||
const manualImg = document.createElement('img');
|
||||
manualImg.classList.add('manual-img');
|
||||
manualImg.src = `/main/img/manual/${[i+1]}.jpg`;
|
||||
|
||||
manualSwiperItem.appendChild(manualImg);
|
||||
manualSwiperWrap.appendChild(manualSwiperItem);
|
||||
}
|
||||
|
||||
// 이전버튼, 다음버튼, 페이지네이션버튼 생성
|
||||
const swiperButtonPrev = document.createElement('div');
|
||||
swiperButtonPrev.classList.add('button-prev');
|
||||
const swiperButtonNext = document.createElement('div');
|
||||
swiperButtonNext.classList.add('button-next');
|
||||
const swiperPagination = document.createElement('div');
|
||||
swiperPagination.classList.add('swiper-pagination');
|
||||
manualSwiper.appendChild(swiperButtonPrev);
|
||||
manualSwiper.appendChild(swiperButtonNext);
|
||||
manualSwiper.appendChild(swiperPagination);
|
||||
}
|
||||
1314
views/main/jsm/archive/modalManager.js
Normal file
1314
views/main/jsm/archive/modalManager.js
Normal file
File diff suppressed because it is too large
Load Diff
754
views/main/jsm/archive/olPmtiles.js
Normal file
754
views/main/jsm/archive/olPmtiles.js
Normal file
@@ -0,0 +1,754 @@
|
||||
import { PMTiles } from 'https://esm.sh/pmtiles@3.0.7';
|
||||
|
||||
const MAX_LEVEL = 20;
|
||||
const CLICK_HIGHLIGHT_OUTER_WIDTH = 10; // 바깥쪽 흰색 테두리 두께
|
||||
const CLICK_HIGHLIGHT_INNER_WIDTH = 5; // 안쪽 선 두께
|
||||
const HOVER_HIGHLIGHT_OUTER_WIDTH = 6; // 바깥쪽 흰색 테두리 두께
|
||||
const HOVER_HIGHLIGHT_INNER_WIDTH = 2; // 안쪽 선 두께
|
||||
|
||||
// 선택/호버 상태 추적
|
||||
let selectedFeature = null;
|
||||
let hoveredFeature = null;
|
||||
let selectLayer = null;
|
||||
let hoverLayer = null;
|
||||
// 현재 선택된 feature 저장
|
||||
let currentSelectedFeature = null;
|
||||
// 선택된 feature의 레이어 키 추가
|
||||
let currentSelectedLayerKey = null;
|
||||
|
||||
// ---- OpenLayers VectorTile Layer ----
|
||||
class OLVectorLayer {
|
||||
constructor(olMap, layerId) {
|
||||
this.olMap = olMap;
|
||||
this.layerId = layerId;
|
||||
this.type = null;
|
||||
this.templateUrl = null;
|
||||
this.pmtilesInstance = null;
|
||||
this.maxLevel = MAX_LEVEL;
|
||||
this.vectorLayer = null;
|
||||
}
|
||||
|
||||
async setTileSource(source, type, zoomRange = null) {
|
||||
this.type = type;
|
||||
|
||||
if (type === 'pbf') {
|
||||
this.templateUrl = source;
|
||||
|
||||
try {
|
||||
const baseUrl = source.replace('/{z}/{x}/{y}.pbf', '');
|
||||
const metadataUrl = `${baseUrl}/metadata.json`;
|
||||
const response = await fetch(metadataUrl);
|
||||
const metadata = await response.json();
|
||||
|
||||
if (metadata.maxzoom) {
|
||||
this.maxLevel = parseInt(metadata.maxzoom);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[${this.layerId}] Could not read metadata.json`);
|
||||
}
|
||||
|
||||
this.vectorLayer = new ol.layer.VectorTile({
|
||||
source: new ol.source.VectorTile({
|
||||
format: new ol.format.MVT(),
|
||||
url: source,
|
||||
maxZoom: this.maxLevel
|
||||
}),
|
||||
style: styleFunction,
|
||||
renderMode: 'vector',
|
||||
zIndex: 1,
|
||||
// 줌 범위 설정
|
||||
minZoom: zoomRange ? zoomRange.min : undefined,
|
||||
maxZoom: zoomRange ? zoomRange.max : undefined
|
||||
});
|
||||
|
||||
this.olMap.addLayer(this.vectorLayer);
|
||||
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
||||
console.log(`[${this.layerId}] PBF Vector layer initialized${zoomInfo}`);
|
||||
|
||||
} else if (type === 'pmtiles') {
|
||||
this.pmtilesInstance = source;
|
||||
|
||||
try {
|
||||
const metadata = await this.pmtilesInstance.getMetadata();
|
||||
if (metadata.maxzoom) {
|
||||
this.maxLevel = parseInt(metadata.maxzoom);
|
||||
}
|
||||
// console.log(`[${this.layerId}] PMTiles metadata:`, metadata);
|
||||
} catch (error) {
|
||||
console.warn(`[${this.layerId}] Could not read PMTiles metadata`);
|
||||
}
|
||||
|
||||
this.vectorLayer = new ol.layer.VectorTile({
|
||||
source: new ol.source.VectorTile({
|
||||
format: new ol.format.MVT(),
|
||||
tileUrlFunction: (tileCoord) => {
|
||||
const z = tileCoord[0];
|
||||
const x = tileCoord[1];
|
||||
const y = tileCoord[2];
|
||||
return `pmtiles://${this.layerId}/${z}/${x}/${y}`;
|
||||
},
|
||||
tileLoadFunction: async (tile, url) => {
|
||||
const urlParts = url.split('/');
|
||||
const z = parseInt(urlParts[urlParts.length - 3]);
|
||||
const x = parseInt(urlParts[urlParts.length - 2]);
|
||||
const y = parseInt(urlParts[urlParts.length - 1]);
|
||||
|
||||
// console.log(`Loading PMTiles tile: ${z}/${x}/${y}`);
|
||||
|
||||
try {
|
||||
const tileData = await this.pmtilesInstance.getZxy(z, x, y);
|
||||
if (tileData && tileData.data) {
|
||||
// console.log(`PMTiles tile ${z}/${x}/${y} loaded, size: ${tileData.data.byteLength} bytes`);
|
||||
|
||||
const format = new ol.format.MVT();
|
||||
|
||||
const tileGrid = ol.tilegrid.createXYZ({maxZoom: this.maxLevel});
|
||||
const tileExtent = tileGrid.getTileCoordExtent([z, x, y]);
|
||||
|
||||
const features = format.readFeatures(tileData.data, {
|
||||
extent: tileExtent,
|
||||
featureProjection: 'EPSG:3857'
|
||||
});
|
||||
|
||||
// console.log(`PMTiles tile ${z}/${x}/${y} features: ${features.length}`);
|
||||
|
||||
if (tile.setFeatures) {
|
||||
tile.setFeatures(features);
|
||||
} else {
|
||||
tile.setLoader(() => Promise.resolve(features));
|
||||
}
|
||||
} else {
|
||||
// console.log(`PMTiles tile ${z}/${x}/${y} - no data`);
|
||||
if (tile.setFeatures) {
|
||||
tile.setFeatures([]);
|
||||
} else {
|
||||
tile.setLoader(() => Promise.resolve([]));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// console.error(`Failed to load PMTiles tile ${z}/${x}/${y}:`, error);
|
||||
if (tile.setFeatures) {
|
||||
tile.setFeatures([]);
|
||||
} else {
|
||||
tile.setLoader(() => Promise.resolve([]));
|
||||
}
|
||||
}
|
||||
},
|
||||
maxZoom: this.maxLevel
|
||||
}),
|
||||
style: styleFunction,
|
||||
zIndex: 1,
|
||||
updateWhileAnimating: false,
|
||||
updateWhileInteracting: false,
|
||||
renderBuffer: 50,
|
||||
declutter: false,
|
||||
// 줌 범위 설정
|
||||
minZoom: zoomRange ? zoomRange.min : undefined,
|
||||
maxZoom: zoomRange ? zoomRange.max : undefined
|
||||
});
|
||||
|
||||
this.olMap.addLayer(this.vectorLayer);
|
||||
|
||||
|
||||
|
||||
// this.pmtilesInstance = source;
|
||||
|
||||
// try {
|
||||
// const metadata = await this.pmtilesInstance.getMetadata();
|
||||
// if (metadata.maxzoom) this.maxLevel = parseInt(metadata.maxzoom, 10);
|
||||
// } catch (e) {
|
||||
// console.warn(`[${this.layerId}] Could not read PMTiles metadata`);
|
||||
// }
|
||||
|
||||
// // PMTiles가 512 기준이면 512 권장
|
||||
// const tileSize = 256;
|
||||
// const tileGrid = ol.tilegrid.createXYZ({
|
||||
// maxZoom: this.maxLevel,
|
||||
// tileSize
|
||||
// });
|
||||
|
||||
// // 기존 styleFunction 그대로 사용
|
||||
// const styleFn = styleFunction;
|
||||
|
||||
// // 벡터 → 래스터 변환용 레이어
|
||||
// this.rasterLayer = new ol.layer.Tile({
|
||||
// source: new ol.source.TileImage({
|
||||
// tileGrid,
|
||||
// tileUrlFunction: (tileCoord) => {
|
||||
// const [z, x, y] = tileCoord;
|
||||
// return `pmtiles://${this.layerId}/${z}/${x}/${y}`;
|
||||
// },
|
||||
// tileLoadFunction: async (imageTile, url) => {
|
||||
// const parts = url.split('/');
|
||||
// const z = parseInt(parts[parts.length - 3], 10);
|
||||
// const x = parseInt(parts[parts.length - 2], 10);
|
||||
// const y = parseInt(parts[parts.length - 1], 10);
|
||||
|
||||
// // 타일 extent (EPSG:3857)
|
||||
// const tileExtent = tileGrid.getTileCoordExtent([z, x, y]);
|
||||
// const resolution = tileGrid.getResolution(z);
|
||||
|
||||
// // 해상도/부하 트레이드오프
|
||||
// const pixelRatio = 1; // 필요시 window.devicePixelRatio
|
||||
|
||||
// const canvas = document.createElement('canvas');
|
||||
// canvas.width = tileSize * pixelRatio;
|
||||
// canvas.height = tileSize * pixelRatio;
|
||||
// const ctx = canvas.getContext('2d');
|
||||
|
||||
// // ★ extent 반영하여 벡터 컨텍스트 생성
|
||||
// const vectorCtx = ol.render.toContext(ctx, {
|
||||
// size: [tileSize * pixelRatio, tileSize * pixelRatio],
|
||||
// pixelRatio,
|
||||
// extent: tileExtent // ← 중요: 지도좌표 → 픽셀 변환 기준
|
||||
// });
|
||||
|
||||
// try {
|
||||
// const tileData = await this.pmtilesInstance.getZxy(z, x, y);
|
||||
|
||||
// if (tileData && tileData.data) {
|
||||
// // MVT 파싱 (extent는 featureProjection과 동일 좌표계여야 함)
|
||||
// const format = new ol.format.MVT();
|
||||
// const features = format.readFeatures(tileData.data, {
|
||||
// extent: tileExtent, // EPSG:3857
|
||||
// featureProjection: 'EPSG:3857'
|
||||
// });
|
||||
|
||||
// // zIndex 반영을 위해 (feature, style) 쌍 정렬
|
||||
// const drawQueue = [];
|
||||
// for (const f of features) {
|
||||
// let s = styleFn ? styleFn(f, resolution) : null;
|
||||
// if (!s) continue;
|
||||
// const styles = Array.isArray(s) ? s : [s];
|
||||
// for (const one of styles) {
|
||||
// if (one && typeof one.getZIndex === 'function') {
|
||||
// const zi = one.getZIndex() ?? 0;
|
||||
// drawQueue.push({ f, style: one, z: zi });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// drawQueue.sort((a, b) => a.z - b.z);
|
||||
// for (const { f, style } of drawQueue) {
|
||||
// vectorCtx.drawFeature(f, style);
|
||||
// }
|
||||
// } else {
|
||||
// // 데이터 없음 → 투명 타일
|
||||
// ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
// }
|
||||
|
||||
// imageTile.getImage().src = canvas.toDataURL('image/png');
|
||||
// } catch (err) {
|
||||
// console.warn(`PMTiles raster tile load failed ${z}/${x}/${y}`, err);
|
||||
// const empty = document.createElement('canvas');
|
||||
// empty.width = tileSize;
|
||||
// empty.height = tileSize;
|
||||
// imageTile.getImage().src = empty.toDataURL();
|
||||
// }
|
||||
// },
|
||||
// // crossOrigin: 'anonymous',
|
||||
// }),
|
||||
// zIndex: 100000,
|
||||
// minZoom: zoomRange ? zoomRange.min : undefined,
|
||||
// maxZoom: zoomRange ? zoomRange.max : undefined
|
||||
// });
|
||||
|
||||
// this.olMap.addLayer(this.rasterLayer);
|
||||
|
||||
|
||||
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
||||
console.log(`[${this.layerId}] PMTiles Vector layer initialized${zoomInfo}`);
|
||||
|
||||
try {
|
||||
const metadata = await this.pmtilesInstance.getMetadata();
|
||||
// PMTiles metadata에서 bounds 정보 확인
|
||||
if (metadata && (metadata.bounds || metadata.antimeridian_adjusted_bounds || metadata.center)) {
|
||||
let extent;
|
||||
let bounds;
|
||||
if (metadata.bounds) bounds = metadata.bounds;
|
||||
if (metadata.antimeridian_adjusted_bounds) bounds = metadata.antimeridian_adjusted_bounds.split(',');
|
||||
|
||||
if (bounds) {
|
||||
// bounds: [minLon, minLat, maxLon, maxLat]
|
||||
const [minLon, minLat, maxLon, maxLat] = bounds;
|
||||
const bottomLeft = ol.proj.fromLonLat([minLon, minLat]);
|
||||
const topRight = ol.proj.fromLonLat([maxLon, maxLat]);
|
||||
extent = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
|
||||
} else if (metadata.center) {
|
||||
// center와 zoom을 이용한 대략적인 영역 계산
|
||||
const [centerLon, centerLat, centerZoom] = metadata.center;
|
||||
const centerPoint = ol.proj.fromLonLat([centerLon, centerLat]);
|
||||
const resolution = this.olMap.getView().getResolutionForZoom(centerZoom || 10);
|
||||
const size = 1000; // 대략적인 크기
|
||||
extent = [
|
||||
centerPoint[0] - size * resolution,
|
||||
centerPoint[1] - size * resolution,
|
||||
centerPoint[0] + size * resolution,
|
||||
centerPoint[1] + size * resolution
|
||||
];
|
||||
}
|
||||
|
||||
let pmtilesFitBtn = document.querySelector('.map-container .control-btn-wrap .pmtiles-fit-btn');
|
||||
if (extent) {
|
||||
if (ol.map.getOverlays().getLength() == 0) {
|
||||
this.olMap.getView().fit(extent, {
|
||||
padding: [50, 50, 50, 50],
|
||||
// duration: 500,
|
||||
// constrainResolution: true,
|
||||
maxZoom: 18
|
||||
});
|
||||
// console.log(`[${this.layerId}] PMTiles layer fitted to bounds`);
|
||||
}
|
||||
|
||||
pmtilesFitBtn.style.display = 'flex';
|
||||
pmtilesFitBtn.addEventListener('click', async() => {
|
||||
this.olMap.getView().fit(extent, {
|
||||
padding: [50, 50, 50, 50],
|
||||
// duration: 500,
|
||||
// constrainResolution: true,
|
||||
maxZoom: 18
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[${this.layerId}] Could not fit PMTiles bounds:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GeoJSON/KML용 벡터 소스 설정 메서드
|
||||
async setVectorSource(url, type, zoomRange = null) {
|
||||
this.type = type;
|
||||
|
||||
if (type === 'geojson') {
|
||||
console.log(`[${this.layerId}] Loading GeoJSON from: ${url}`);
|
||||
|
||||
this.vectorLayer = new ol.layer.Vector({
|
||||
source: new ol.source.Vector({
|
||||
url: url,
|
||||
format: new ol.format.GeoJSON()
|
||||
}),
|
||||
style: styleFunction,
|
||||
zIndex: 1,
|
||||
updateWhileAnimating: false,
|
||||
updateWhileInteracting: false,
|
||||
renderBuffer: 50,
|
||||
declutter: false,
|
||||
// 줌 범위 설정
|
||||
minZoom: zoomRange ? zoomRange.min : undefined,
|
||||
maxZoom: zoomRange ? zoomRange.max : undefined
|
||||
});
|
||||
|
||||
this.olMap.addLayer(this.vectorLayer);
|
||||
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
||||
console.log(`[${this.layerId}] GeoJSON Vector layer initialized${zoomInfo}`);
|
||||
|
||||
// GeoJSON 로드 완료 이벤트 처리
|
||||
const source = this.vectorLayer.getSource();
|
||||
source.once('change', () => {
|
||||
if (source.getState() === 'ready') {
|
||||
const features = source.getFeatures();
|
||||
const featureCount = features.length;
|
||||
console.log(`[${this.layerId}] GeoJSON loaded: ${featureCount} features`);
|
||||
|
||||
// feature 수에 따른 경고
|
||||
if (featureCount > 10000) {
|
||||
console.warn(`⚠️ Large dataset detected (${featureCount} features). Performance may be affected.`);
|
||||
}
|
||||
|
||||
// 로드된 features가 있으면 해당 영역으로 자동 줌 (단독 레이어인 경우만)
|
||||
if (features.length > 0 && !zoomRange) {
|
||||
const extent = source.getExtent();
|
||||
this.olMap.getView().fit(extent, {
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 18
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'kml') {
|
||||
console.log(`[${this.layerId}] Loading KML from: ${url}`);
|
||||
|
||||
this.vectorLayer = new ol.layer.Vector({
|
||||
source: new ol.source.Vector({
|
||||
url: url,
|
||||
format: new ol.format.KML({
|
||||
extractStyles: false,
|
||||
showPointNames: false
|
||||
})
|
||||
}),
|
||||
style: styleFunction,
|
||||
zIndex: 1,
|
||||
updateWhileAnimating: false,
|
||||
updateWhileInteracting: false,
|
||||
renderBuffer: 50,
|
||||
declutter: false,
|
||||
// 줌 범위 설정
|
||||
minZoom: zoomRange ? zoomRange.min : undefined,
|
||||
maxZoom: zoomRange ? zoomRange.max : undefined
|
||||
});
|
||||
|
||||
this.olMap.addLayer(this.vectorLayer);
|
||||
const zoomInfo = zoomRange ? ` (zoom ${zoomRange.min}-${zoomRange.max})` : '';
|
||||
console.log(`[${this.layerId}] KML Vector layer initialized${zoomInfo}`);
|
||||
|
||||
// KML 로드 완료 이벤트 처리
|
||||
const source = this.vectorLayer.getSource();
|
||||
source.once('change', () => {
|
||||
if (source.getState() === 'ready') {
|
||||
const features = source.getFeatures();
|
||||
console.log(`[${this.layerId}] KML loaded: ${features.length} features`);
|
||||
|
||||
if (features.length > 0 && !zoomRange) {
|
||||
const extent = source.getExtent();
|
||||
this.olMap.getView().fit(extent, {
|
||||
padding: [50, 50, 50, 50],
|
||||
maxZoom: 18
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.vectorLayer) {
|
||||
this.olMap.removeLayer(this.vectorLayer);
|
||||
this.vectorLayer = null;
|
||||
console.log(`[${this.layerId}] Layer cleared`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 통합 멀티 레이어 관리자 (OpenLayers) ----
|
||||
export class OLMultiLayerManager {
|
||||
constructor(olMap) {
|
||||
this.olMap = olMap;
|
||||
this.layerMap = new Map();
|
||||
this.pmtilesInstances = new Map();
|
||||
|
||||
console.log('OLMultiLayerManager initialized');
|
||||
}
|
||||
|
||||
async toggleLayer(layerId, format) {
|
||||
const key = `${format}_${layerId}`;
|
||||
|
||||
if (this.layerMap.has(key)) {
|
||||
// 레이어 제거
|
||||
// PMTiles 제거 시 연결된 detail GeoJSON도 함께 제거
|
||||
|
||||
const layer = this.layerMap.get(key);
|
||||
layer.clear();
|
||||
this.layerMap.delete(key);
|
||||
|
||||
if (this.pmtilesInstances.has(key)) {
|
||||
this.pmtilesInstances.delete(key);
|
||||
}
|
||||
|
||||
console.log(`[${key}] Layer removed`);
|
||||
return false;
|
||||
} else {
|
||||
// 레이어 추가 로직
|
||||
if (format === 'pbf') {
|
||||
const layer = new OLVectorLayer(this.olMap, key);
|
||||
const templateUrl = `http://172.16.41.52:3003/vector_tile_pbf/${layerId}/{z}/{x}/{y}.pbf`;
|
||||
await layer.setTileSource(templateUrl, 'pbf');
|
||||
this.layerMap.set(key, layer);
|
||||
|
||||
} else if (format === 'pmtiles') {
|
||||
// console.log(`🚀 Creating hybrid PMTiles + GeoJSON layers for: ${layerId}`);
|
||||
|
||||
// // 1. PMTiles 레이어 추가 (zoom 0-17만 표시)
|
||||
// const pmtilesLayer = new OLVectorLayer(this.olMap, key);
|
||||
// const pmtilesPath = `http://172.16.41.52:3003/vector_tile_pmtiles/${layerId}.pmtiles`;
|
||||
// const pmtiles = new PMTiles(pmtilesPath);
|
||||
// this.pmtilesInstances.set(key, pmtiles);
|
||||
// await pmtilesLayer.setTileSource(pmtiles, 'pmtiles', { min: 0, max: 18.1 });
|
||||
// this.layerMap.set(key, pmtilesLayer);
|
||||
// console.log(`✅ [${key}] PMTiles layer added (zoom 0-17)`);
|
||||
|
||||
// // 2. GeoJSON 레이어 추가 (zoom 18-24만 표시)
|
||||
// const detailGeojsonKey = `geojson_detail_${layerId}`;
|
||||
// const detailGeojsonLayer = new OLVectorLayer(this.olMap, detailGeojsonKey);
|
||||
// const geojsonUrl = `http://172.16.41.52:3003/vector_tile_geojson/${layerId}.geojson`;
|
||||
// await detailGeojsonLayer.setVectorSource(geojsonUrl, 'geojson', { min: 17.9, max: 24 });
|
||||
// this.layerMap.set(detailGeojsonKey, detailGeojsonLayer);
|
||||
// console.log(`✅ [${detailGeojsonKey}] Detail GeoJSON layer added (zoom 18-24)`);
|
||||
|
||||
// console.log(`🎯 Hybrid layer setup complete. PMTiles will show at zoom 0-17, GeoJSON at zoom 18+`);
|
||||
|
||||
|
||||
const pmtilesLayer = new OLVectorLayer(this.olMap, key);
|
||||
|
||||
// const pmtilesPath = `http://172.16.41.52:3003/vector_tile_pmtiles/${layerId}.pmtiles`;
|
||||
let pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/${layerId}.pmtiles`;
|
||||
if (layerId == 'testbim') pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/dsdj2.pmtiles`;
|
||||
|
||||
// let mapName = layerId;
|
||||
// if (layerId == 'testbim') mapName = 'dsdj2';
|
||||
// let pmtilesPath = `https://gsim-model.digitalarchive.work/pmtiles/vector/${mapName}.pmtiles`;
|
||||
|
||||
let checkUrlExistsResult = await checkUrlExists(pmtilesPath);
|
||||
if (checkUrlExistsResult == true) {
|
||||
const pmtiles = new PMTiles(pmtilesPath);
|
||||
this.pmtilesInstances.set(key, pmtiles);
|
||||
await pmtilesLayer.setTileSource(pmtiles, 'pmtiles');
|
||||
this.layerMap.set(key, pmtilesLayer);
|
||||
} else {
|
||||
let pmtilesFitBtn = document.querySelector('.map-container .control-btn-wrap .pmtiles-fit-btn');
|
||||
pmtilesFitBtn.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
} else if (format === 'geojson') {
|
||||
const layer = new OLVectorLayer(this.olMap, key);
|
||||
const geojsonUrl = `http://172.16.41.52:3003/vector_tile_geojson/${layerId}.geojson`;
|
||||
await layer.setVectorSource(geojsonUrl, 'geojson');
|
||||
this.layerMap.set(key, layer);
|
||||
|
||||
} else if (format === 'kml') {
|
||||
const layer = new OLVectorLayer(this.olMap, key);
|
||||
const kmlUrl = `http://172.16.41.52:3003/vector_tile_kml/${layerId}.kml`;
|
||||
await layer.setVectorSource(kmlUrl, 'kml');
|
||||
this.layerMap.set(key, layer);
|
||||
}
|
||||
|
||||
// console.log(`[${key}] Layer added`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
clearAll() {
|
||||
this.layerMap.forEach(layer => layer.clear());
|
||||
this.layerMap.clear();
|
||||
this.pmtilesInstances.clear();
|
||||
console.log('All OpenLayers layers cleared');
|
||||
}
|
||||
|
||||
getActiveLayers() {
|
||||
return Array.from(this.layerMap.keys());
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 피처별 스타일 함수 ----
|
||||
function styleFunction(feature, resolution) {
|
||||
const props = feature.getProperties();
|
||||
|
||||
const entityType = props?.EntityType;
|
||||
let color = props.Color || '#ff0000';
|
||||
const constWidth = props.constWidth || 0;
|
||||
const ltscale = props.ltscale || 1.0;
|
||||
|
||||
// let dashPattern = props.dashPattern;
|
||||
// if (dashPattern && typeof dashPattern === 'string') {
|
||||
// try {
|
||||
// // "[0.5,-0.25]" → [0.5, -0.25]
|
||||
// dashPattern = JSON.parse(dashPattern);
|
||||
// } catch (e) {
|
||||
// console.warn('dashPattern 파싱 실패:', dashPattern, e);
|
||||
// dashPattern = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
// ⭐ constWidth에 따른 선 두께 결정
|
||||
let strokeWidth = 1; // 기본값
|
||||
// if (constWidth === 0) {
|
||||
// strokeWidth = 1;
|
||||
// } else if (constWidth === 1) {
|
||||
// strokeWidth = 5;
|
||||
// } else {
|
||||
// // 향후 다른 값 대비
|
||||
// // strokeWidth = 2 + (constWidth * 2);
|
||||
// // strokeWidth = 1 + (constWidth * 5);
|
||||
// strokeWidth = constWidth * 5;
|
||||
// }
|
||||
|
||||
// if (constWidth === 0) {
|
||||
// strokeWidth = 1;
|
||||
// } else {
|
||||
// // 향후 다른 값 대비
|
||||
// // strokeWidth = 2 + (constWidth * 2);
|
||||
// // strokeWidth = 1 + (constWidth * 5);
|
||||
// strokeWidth = constWidth * 10;
|
||||
// }
|
||||
|
||||
// if (constWidth === 0) {
|
||||
// strokeWidth = 1;
|
||||
// } else if (constWidth > 0 && constWidth <= 1) {
|
||||
// strokeWidth = constWidth * 10;
|
||||
// } else {
|
||||
// strokeWidth = constWidth * 5;
|
||||
// }
|
||||
|
||||
// if (constWidth === 0) {
|
||||
// strokeWidth = 1;
|
||||
// } else {
|
||||
// console.log(constWidth);
|
||||
// strokeWidth = constWidth*2;
|
||||
// }
|
||||
|
||||
// // ⭐ dashPattern을 OpenLayers lineDash로 변환
|
||||
// let lineDash = null;
|
||||
// if (dashPattern && Array.isArray(dashPattern) && dashPattern.length > 0) {
|
||||
// // const zoomFactor = Math.min(1 / resolution, 1.0);
|
||||
// const scaleFactor = 10; // 조정 가능 (0.3 ~ 1.0 추천)
|
||||
// lineDash = dashPattern.map(value =>
|
||||
// Math.abs(value) * ltscale * scaleFactor
|
||||
// );
|
||||
// }
|
||||
|
||||
// 투명도 처리
|
||||
const hexToRgba = (hex, alpha = 1) => {
|
||||
if (!hex || hex.length < 7) return `rgba(255, 0, 0, ${alpha})`;
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
};
|
||||
|
||||
const geomType = feature.getGeometry().getType();
|
||||
if (entityType) {
|
||||
if (entityType === 'TEXT_POLYGON' || entityType === 'LINETYPE_PATTERN_TEXT') {
|
||||
return new ol.style.Style({
|
||||
fill: new ol.style.Fill({
|
||||
color: hexToRgba(color, 1.0)
|
||||
}),
|
||||
zIndex: 40
|
||||
});
|
||||
} else if (entityType === 'LINETYPE_PATTERN_SHAPE') {
|
||||
return new ol.style.Style({
|
||||
fill: new ol.style.Fill({
|
||||
color: hexToRgba(color, 0.4)
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: hexToRgba(color, 1.0),
|
||||
width: strokeWidth,
|
||||
// lineDash: lineDash,
|
||||
lineCap: 'butt',
|
||||
lineJoin: 'miter'
|
||||
}),
|
||||
zIndex: 20
|
||||
});
|
||||
} else if (entityType === 'HATCH') {
|
||||
return new ol.style.Style({
|
||||
fill: new ol.style.Fill({
|
||||
color: hexToRgba(color, 0.4)
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: hexToRgba(color, 1.0),
|
||||
width: strokeWidth,
|
||||
// lineDash: lineDash,
|
||||
lineCap: 'butt',
|
||||
lineJoin: 'miter'
|
||||
}),
|
||||
zIndex: 10
|
||||
});
|
||||
} else if (entityType === 'SOLID_LINE_POLYGON' || entityType === 'DASHED_LINE_POLYGON') {
|
||||
return new ol.style.Style({
|
||||
fill: new ol.style.Fill({
|
||||
color: hexToRgba(color, 1)
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: hexToRgba(color, 1.0),
|
||||
width: strokeWidth,
|
||||
// lineDash: lineDash,
|
||||
lineCap: 'butt',
|
||||
lineJoin: 'miter'
|
||||
}),
|
||||
zIndex: 10
|
||||
});
|
||||
} else if (entityType === 'LINETYPE_PATTERN_SHAPE_LINE') {
|
||||
return new ol.style.Style({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: hexToRgba(color, 1.0),
|
||||
width: strokeWidth,
|
||||
// lineDash: lineDash,
|
||||
lineCap: 'butt',
|
||||
lineJoin: 'miter'
|
||||
}),
|
||||
zIndex: 30
|
||||
});
|
||||
} else if (entityType === 'LWPOLYLINE' || entityType === 'LINE') {
|
||||
return new ol.style.Style({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: hexToRgba(color, 1.0),
|
||||
width: strokeWidth,
|
||||
// lineDash: lineDash,
|
||||
lineCap: 'butt',
|
||||
lineJoin: 'miter'
|
||||
}),
|
||||
zIndex: 30
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (geomType === 'Polygon' || geomType === 'MultiPolygon') {
|
||||
return new ol.style.Style({
|
||||
fill: new ol.style.Fill({
|
||||
color: hexToRgba(color, 0.4)
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: hexToRgba(color, 1.0),
|
||||
width: strokeWidth,
|
||||
// lineDash: lineDash,
|
||||
lineCap: 'butt',
|
||||
lineJoin: 'miter'
|
||||
}),
|
||||
zIndex: 20
|
||||
});
|
||||
} else if (geomType === 'LineString' || geomType === 'MultiLineString') {
|
||||
// let strokeWidth, color;
|
||||
// if (props.DIVI == '주곡선') {
|
||||
// strokeWidth = 2;
|
||||
// color = '#cccccc';
|
||||
// } else {
|
||||
// strokeWidth = 1;
|
||||
// color = '#777777';
|
||||
// }
|
||||
return new ol.style.Style({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: hexToRgba(color, 1.0),
|
||||
width: strokeWidth,
|
||||
// lineDash: lineDash,
|
||||
lineCap: 'butt',
|
||||
lineJoin: 'miter'
|
||||
}),
|
||||
zIndex: 30
|
||||
});
|
||||
} else if (geomType === 'Point' || geomType === 'MultiPoint') {
|
||||
return new ol.style.Style({
|
||||
image: new ol.style.Circle({
|
||||
radius: 4,
|
||||
fill: new ol.style.Fill({
|
||||
color: hexToRgba(color, 1.0)
|
||||
})
|
||||
}),
|
||||
zIndex: 30
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// async function checkUrlExists(url) {
|
||||
// try {
|
||||
// const res = await fetch(url, { method: "HEAD" });
|
||||
|
||||
// if (res.ok) {
|
||||
// return true; // 200~299 → 정상 URL
|
||||
// }
|
||||
// return false; // 404, 403 등
|
||||
// } catch (e) {
|
||||
// return false; // 네트워크 오류, CORS 오류 포함
|
||||
// }
|
||||
// }
|
||||
|
||||
async function checkUrlExists(url) {
|
||||
try {
|
||||
const res = await fetch(url, { method: "HEAD" });
|
||||
|
||||
// 200~299
|
||||
return res.ok;
|
||||
} catch (e) {
|
||||
// 여기서 swallow(삼키기)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
7548
views/main/jsm/archive/pageRenderer.js
Normal file
7548
views/main/jsm/archive/pageRenderer.js
Normal file
File diff suppressed because it is too large
Load Diff
710
views/main/jsm/archive/projectSetting.js
Normal file
710
views/main/jsm/archive/projectSetting.js
Normal file
@@ -0,0 +1,710 @@
|
||||
import { typeStep2Kor } from '../main.js';
|
||||
|
||||
let projectTypeBtn;
|
||||
let projectTypeCapsule;
|
||||
let projectTypeList = null;
|
||||
let projectStepList = null;
|
||||
let mainLayerState = null; // 메인 레이어 상태
|
||||
|
||||
// 프로젝트 설정 저장 버튼 클릭 이벤트
|
||||
document.getElementById('project-save-btn').addEventListener('click', async () => {
|
||||
const projectSaveBtn = document.getElementById('project-save-btn');
|
||||
const projectStepBtn = document.getElementById('project-step-btn');
|
||||
const projectNameInput = document.getElementById('project-name-input');
|
||||
|
||||
if (projectSaveBtn.textContent === '저장') {
|
||||
// type
|
||||
let typeVal = projectTypeBtn.children[0].textContent;
|
||||
let typeValResult = stepTypeEng(typeVal);
|
||||
|
||||
// step
|
||||
let stepVal = projectStepBtn.children[0].textContent;
|
||||
let stepValResult = stepTypeEng(stepVal);
|
||||
|
||||
// project_name
|
||||
let nameVal = projectNameInput.value;
|
||||
|
||||
let projectInfoParams = {
|
||||
projectId : vars.project.project_id,
|
||||
category : vars.project.category,
|
||||
project_type : typeValResult,
|
||||
step : stepValResult,
|
||||
project_nm : nameVal,
|
||||
}
|
||||
|
||||
let res = await axios.post('/common/updateProjectInfo', {params: projectInfoParams});
|
||||
if (res.data.message == 'updateProjectInfo_success') {
|
||||
// 수정된 프로젝트 정보 가져와서 전역변수에 저장
|
||||
let projectRes = await axios.get('/common/getProject');
|
||||
for (let project of projectRes.data.data) {
|
||||
vars.allProject[project.project_id] = project;
|
||||
}
|
||||
vars.project = vars.allProject[vars.project_id];
|
||||
|
||||
setReadOnlyMode(vars.project.category);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 프로젝트 설정 취소 클릭 이벤트
|
||||
document.querySelector('#project-cancel-btn').addEventListener('click', () => {
|
||||
const projectSaveBtn = document.getElementById('project-save-btn');
|
||||
if (projectSaveBtn.textContent === '저장') projectSaveBtn.textContent = '수정';
|
||||
|
||||
const list = document.querySelector('#project-type-wrap-overseas .project-type__list');
|
||||
list.classList.remove('--project-list__open');
|
||||
|
||||
setReadOnlyMode(vars.project.category);
|
||||
});
|
||||
|
||||
export function initProjectSetting(param) {
|
||||
const projectSaveBtn = document.getElementById('project-save-btn');
|
||||
const projectCancelBtn = document.getElementById('project-cancel-btn');
|
||||
const projectLocationBtn = document.getElementById('project-location-btn');
|
||||
let projectTypeWrap = document.getElementById('project-type-wrap');
|
||||
let projectTypeWrapOverseas = document.getElementById('project-type-wrap-overseas');
|
||||
|
||||
if (param === undefined) {
|
||||
// overseas, bim 아닌 경우
|
||||
projectTypeWrap.style.display = 'none';
|
||||
projectTypeWrapOverseas.style.display = 'none';
|
||||
|
||||
projectTypeBtn = document.getElementById('project-type-btn');
|
||||
projectTypeCapsule = document.getElementById('project-type-capsule');
|
||||
|
||||
} else {
|
||||
if (param === 'overseas') {
|
||||
// overseas
|
||||
projectTypeWrap.style.display = 'none';
|
||||
projectTypeWrapOverseas.style.display = 'flex';
|
||||
|
||||
projectTypeBtn = document.getElementById('project-type-btn-overseas');
|
||||
projectTypeCapsule = document.getElementById('project-type-capsule-overseas');
|
||||
|
||||
} else {
|
||||
//bim
|
||||
projectTypeWrap.style.display = 'flex';
|
||||
projectTypeWrapOverseas.style.display = 'none';
|
||||
|
||||
projectTypeBtn = document.getElementById('project-type-btn');
|
||||
projectTypeCapsule = document.getElementById('project-type-capsule');
|
||||
}
|
||||
|
||||
adjustSelectBoxWidth(projectTypeBtn);
|
||||
}
|
||||
|
||||
setReadOnlyMode(param);
|
||||
|
||||
// 권한이 있는 경우 저장 버튼 이벤트 추가
|
||||
if (vars.permission.checkPermission('change-project-btn')) {
|
||||
// 기존 리스너 제거 후 추가 (중복 방지)
|
||||
projectSaveBtn.textContent = '수정';
|
||||
projectSaveBtn.removeEventListener('click', handleProjectSaveClick);
|
||||
projectSaveBtn.addEventListener('click', () => handleProjectSaveClick(param));
|
||||
} else {
|
||||
// 권한 없으면 버튼 제거
|
||||
projectSaveBtn.remove();
|
||||
projectCancelBtn.remove();
|
||||
projectLocationBtn.remove();
|
||||
}
|
||||
|
||||
// 이벤트 리스너 초기화
|
||||
initEventListeners();
|
||||
}
|
||||
|
||||
function handleProjectSaveClick(param) {
|
||||
const projectSaveBtn = document.getElementById('project-save-btn');
|
||||
|
||||
if (projectSaveBtn.textContent === '저장') {
|
||||
// 저장 버튼 클릭 : 읽기 모드로 전환
|
||||
projectSaveBtn.textContent = '수정';
|
||||
setReadOnlyMode(param);
|
||||
} else {
|
||||
// 수정 버튼 클릭: 편집 모드로 전환
|
||||
projectSaveBtn.textContent = '저장';
|
||||
setEditMode(param);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 읽기 모드 : 관리자 이상이면서 읽기모드 or 일반유저
|
||||
function setReadOnlyMode(param) {
|
||||
|
||||
const projectStepBtn = document.getElementById('project-step-btn');
|
||||
const projectStepCapsule = document.getElementById('project-step-capsule');
|
||||
const projectNameView = document.getElementById('project-name-view');
|
||||
const projectNameInput = document.getElementById('project-name-input');
|
||||
const projectSaveBtn = document.getElementById('project-save-btn');
|
||||
const projectCancelBtn = document.getElementById('project-cancel-btn');
|
||||
|
||||
// step - 버튼 숨기고 캡슐 표시
|
||||
projectStepBtn.style.display = 'none';
|
||||
projectStepCapsule.style.display = 'flex';
|
||||
projectStepCapsule.innerHTML = typeStep2Kor(vars.project.step);
|
||||
projectStepCapsule.className = `project-step-capsule --step-capsule__${vars.project.step}`;
|
||||
|
||||
// type - 버튼 숨기고 캡슐 표시
|
||||
projectTypeBtn.style.display = 'none';
|
||||
projectTypeCapsule.style.display = 'flex';
|
||||
projectTypeCapsule.innerHTML = typeStep2Kor(vars.project.project_type);
|
||||
|
||||
// name - input 숨기고 view 표시
|
||||
projectNameInput.style.display = 'none';
|
||||
projectNameView.style.display = 'flex';
|
||||
projectNameView.innerHTML = vars.project.short_nm;
|
||||
|
||||
if(param !== 'overseas' && param !== 'bimproject') {
|
||||
projectStepCapsule.style.display = 'none';
|
||||
projectTypeCapsule.style.display = 'none';
|
||||
|
||||
projectNameView.innerHTML = vars.project.project_nm;
|
||||
projectSaveBtn.style.display = 'none';
|
||||
projectCancelBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// location - 관리자이상:버튼 o , 관리자이하: 버튼x
|
||||
if (vars.permission.checkPermission('change-project-btn')) {
|
||||
document.querySelector('#project-location-btn').style.display = 'flex';
|
||||
}
|
||||
const lat = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lat');
|
||||
lat.innerHTML =`<div class="project-location-lat">위도 ${vars.project.lat}</div>`
|
||||
const lon = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lon');
|
||||
lon.innerHTML =`<div class="project-location-lon">경도 ${vars.project.lon}</div>`
|
||||
}
|
||||
|
||||
// 편집모드 : 관리자 이상이면서 편집모드
|
||||
function setEditMode(param) {
|
||||
|
||||
const projectStepBtn = document.getElementById('project-step-btn');
|
||||
const projectStepCapsule = document.getElementById('project-step-capsule');
|
||||
const projectNameView = document.getElementById('project-name-view');
|
||||
const projectNameInput = document.getElementById('project-name-input');
|
||||
|
||||
// step - 캡슐 숨기고 버튼 표시
|
||||
projectStepCapsule.style.display = 'none';
|
||||
projectStepBtn.style.display = 'flex';
|
||||
projectStepBtn.innerHTML = `
|
||||
<h5 class="project-step__label --step__${vars.project.step}">${typeStep2Kor(vars.project.step)}</h5>
|
||||
<i class="project-step__icon"></i>
|
||||
`;
|
||||
|
||||
// type - 캡슐 숨기고 버튼 표시
|
||||
projectTypeCapsule.style.display = 'none';
|
||||
projectTypeBtn.style.display = 'flex';
|
||||
projectTypeBtn.innerHTML = `
|
||||
<h5 class="project-type__label --type__${vars.project.project_type}">${typeStep2Kor(vars.project.project_type)}</h5>
|
||||
<i class="project-type__icon"></i>
|
||||
`;
|
||||
|
||||
// name - view 숨기고 input 표시
|
||||
projectNameView.style.display = 'none';
|
||||
projectNameInput.style.display = 'flex';
|
||||
projectNameInput.value = projectNameView.textContent;
|
||||
|
||||
if(param !== 'overseas' && param !== 'bimproject') {
|
||||
projectStepBtn.style.display = 'none';
|
||||
projectTypeBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
// location - 수정 버튼, 위경도 위치 표시
|
||||
document.querySelector('#project-location-btn').style.display = 'flex';
|
||||
const lat = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lat');
|
||||
lat.innerHTML =`<div class="project-location-lat">위도 ${vars.project.lat}</div>`
|
||||
const lon = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lon');
|
||||
lon.innerHTML =`<div class="project-location-lon">경도 ${vars.project.lon}</div>`
|
||||
}
|
||||
|
||||
// 접속 인원 모달 - 프로젝트 type, step 변경 이벤트
|
||||
function initEventListeners() {
|
||||
// project-type-btn 클릭 이벤트
|
||||
if (projectTypeBtn) {
|
||||
// 기존 이벤트 제거 후 추가 (중복 방지)
|
||||
const newBtn = projectTypeBtn.cloneNode(true);
|
||||
projectTypeBtn.parentNode.replaceChild(newBtn, projectTypeBtn);
|
||||
projectTypeBtn = newBtn;
|
||||
|
||||
projectTypeList = projectTypeBtn.parentElement.querySelector('.project-type__list');
|
||||
|
||||
projectTypeBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
projectTypeList?.classList.toggle('--project-list__open');
|
||||
});
|
||||
}
|
||||
|
||||
// project-step-btn 클릭 이벤트
|
||||
const projectStepBtn = document.getElementById('project-step-btn');
|
||||
if (projectStepBtn) {
|
||||
projectStepList = document.querySelector('.project-step__list');
|
||||
|
||||
projectStepBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
projectStepList?.classList.toggle('--project-list__open');
|
||||
});
|
||||
}
|
||||
|
||||
// modal-wrap 클릭 이벤트
|
||||
if (projectTypeBtn) {
|
||||
const modalWrap = projectTypeBtn.closest('.modal-wrap');
|
||||
if (modalWrap) {
|
||||
modalWrap.addEventListener('click', (e) => {
|
||||
projectTypeList?.classList.remove('--project-list__open');
|
||||
projectStepList?.classList.remove('--project-list__open');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// project-type__list_item 클릭 이벤트
|
||||
if (projectTypeList) {
|
||||
projectTypeList.querySelectorAll('.project-type__list_item').forEach(item => {
|
||||
item.addEventListener('click', async(e) => {
|
||||
e.stopPropagation();
|
||||
projectTypeList.classList.remove('--project-list__open');
|
||||
let type = e.target.classList[1].split('__')[1];
|
||||
let kor = e.target.textContent;
|
||||
projectTypeBtn.innerHTML = `
|
||||
<h5 class="project-type__label --type__${type}">${kor}</h5>
|
||||
<i class="project-type__icon"></i>`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// project-step__list_item 클릭 이벤트
|
||||
if (projectStepList) {
|
||||
projectStepList.querySelectorAll('.project-step__list_item').forEach(item => {
|
||||
item.addEventListener('click', async(e) => {
|
||||
e.stopPropagation();
|
||||
projectStepList.classList.remove('--project-list__open');
|
||||
let step = e.target.classList[1].split('__')[1];
|
||||
let btn = document.getElementById('project-step-btn');
|
||||
let kor = typeStep2Kor(step);
|
||||
if (btn) {
|
||||
btn.innerHTML = `
|
||||
<h5 class="project-step__label --step__${step}">${kor}</h5>
|
||||
<i class="project-step__icon"></i>`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stepTypeEng(param) {
|
||||
let result = param;
|
||||
switch(param) {
|
||||
case '완료':
|
||||
result = 'done';
|
||||
break;
|
||||
case '진행':
|
||||
result = 'active';
|
||||
break;
|
||||
case '중지':
|
||||
result = 'stop';
|
||||
break;
|
||||
case '대기':
|
||||
result = 'wait';
|
||||
break;
|
||||
|
||||
case '시공':
|
||||
result = 'construction';
|
||||
break;
|
||||
case '설계':
|
||||
result = 'design';
|
||||
break;
|
||||
case '제안':
|
||||
result = 'surgest';
|
||||
break;
|
||||
case '연구':
|
||||
result = 'research';
|
||||
break;
|
||||
case '지원':
|
||||
result = 'support';
|
||||
break;
|
||||
case '센터':
|
||||
result = 'center';
|
||||
break;
|
||||
case '측량':
|
||||
result = 'survey';
|
||||
break;
|
||||
|
||||
case 'MP (기본계획)':
|
||||
result = 'MP';
|
||||
break;
|
||||
case 'DD (실시설계)':
|
||||
result = 'DD';
|
||||
break;
|
||||
case 'FS (타당성조사)':
|
||||
result = 'FS';
|
||||
break;
|
||||
case 'PD (기본설계)':
|
||||
result = 'PD';
|
||||
break;
|
||||
case 'DS (설계감리)':
|
||||
result = 'DS';
|
||||
break;
|
||||
case 'CS (시공감리)':
|
||||
result = 'CS';
|
||||
break;
|
||||
case 'PMC (실시설계)':
|
||||
result = 'PMC';
|
||||
break;
|
||||
case 'IDC (타당성조사)':
|
||||
result = 'IDC';
|
||||
break;
|
||||
case 'DR (설계검토)':
|
||||
result = 'DR';
|
||||
break;
|
||||
case 'BD (수주영업)':
|
||||
result = 'BD';
|
||||
break;
|
||||
case 'ETC (기타)':
|
||||
result = 'ETC';
|
||||
break;
|
||||
|
||||
default:
|
||||
result = '없음';
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 셀렉트박스 크기 조정
|
||||
function adjustSelectBoxWidth(button) {
|
||||
if (!button) return;
|
||||
|
||||
const list = button.parentElement.querySelector('.project-type__list');
|
||||
if (!list) return;
|
||||
|
||||
// canvas를 사용한 텍스트 너비 측정
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
|
||||
// 현재 폰트 스타일 가져오기
|
||||
const computedStyle = window.getComputedStyle(button);
|
||||
context.font =`${computedStyle.fontSize} ${computedStyle.fontFamily}`;
|
||||
|
||||
let maxWidth = 0;
|
||||
const items = list.querySelectorAll('.project-type__list_item');
|
||||
|
||||
items.forEach(item => {
|
||||
const text = item.textContent.trim();
|
||||
const metrics = context.measureText(text);
|
||||
const width = metrics.width;
|
||||
|
||||
if (width > maxWidth) {
|
||||
maxWidth = width;
|
||||
}
|
||||
});
|
||||
|
||||
// 적용
|
||||
if (maxWidth > 0) {
|
||||
const finalWidth = maxWidth + 25;
|
||||
|
||||
// 버튼에 적용
|
||||
button.style.minWidth = `${finalWidth}px`;
|
||||
button.style.textAlign = 'center'; // 가운데 정렬
|
||||
button.style.justifyContent = 'center'; // flex인 경우
|
||||
button.style.display = 'flex'; // flex 적용
|
||||
button.style.alignItems = 'center'; // 세로 가운데 정렬
|
||||
|
||||
// 캡슐에 적용
|
||||
const capsule = button.parentElement.querySelector('.--type-capsule');
|
||||
if (capsule) {
|
||||
capsule.style.minWidth = `${finalWidth}px`;
|
||||
capsule.style.textAlign = 'center'; // 가운데 정렬
|
||||
capsule.style.justifyContent = 'center'; // flex인 경우
|
||||
capsule.style.display = 'flex'; // flex 적용
|
||||
capsule.style.alignItems = 'center'; // 세로 가운데 정렬
|
||||
|
||||
}
|
||||
|
||||
// 리스트에 적용
|
||||
list.style.minWidth = `${finalWidth}px`;
|
||||
|
||||
// 리스트 글씨 정렬
|
||||
items.forEach(item => {
|
||||
item.style.paddingLeft = '5px';
|
||||
item.style.paddingRight = '5px';
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 프로젝트 위치 수정 버튼 클릭 이벤트
|
||||
document.getElementById('project-location-btn').addEventListener('click', () => {
|
||||
document.querySelector('.project-location-modal').style.display = 'flex';
|
||||
openLocationModal();
|
||||
})
|
||||
|
||||
// 지도 모달창 닫기 버튼, 취소 버튼 클릭 이벤트
|
||||
document.querySelector('.project-location-modal .modal-wrap .modal-head .close').addEventListener('click', () => {
|
||||
closeLocationModal();
|
||||
});
|
||||
document.querySelector('.project-location-modal .modal-wrap .modal-foot .button').addEventListener('click', () => {
|
||||
closeLocationModal();
|
||||
});
|
||||
|
||||
// 기본 지도 모달창 버튼 클릭 이벤트
|
||||
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .xs-button').addEventListener('click', () => {
|
||||
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .base-map').style.display = 'flex';
|
||||
});
|
||||
// 기본 지도 모달창 닫기 버튼 클릭 이벤트
|
||||
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .base-map .text-wrap .close').addEventListener('click', () => {
|
||||
document.querySelector('.project-location-modal .modal-wrap .modal-body .map-wrap .base-map').style.display = 'none';
|
||||
});
|
||||
|
||||
// 지도 모달창 저장 버튼 클릭 이벤트
|
||||
document.querySelector('.project-location-modal .modal-wrap .modal-foot .primary-button').addEventListener('click', async() => {
|
||||
if(!ol.locationMap || !ol.locationMap.clickCoord) {
|
||||
alert('위치를 선택해주세요');
|
||||
return;
|
||||
}
|
||||
|
||||
let updateLocationParams = {
|
||||
projectId: vars.project.project_id,
|
||||
category: vars.project.category,
|
||||
project_nm: vars.project.project_nm,
|
||||
lon: ol.locationMap.clickCoord.lon,
|
||||
lat: ol.locationMap.clickCoord.lat
|
||||
};
|
||||
|
||||
let res = await axios.post('/common/updateLocationInfo', {params: updateLocationParams});
|
||||
if (res.data.message == 'updateLocationInfo_success') {
|
||||
vars.project.lon = res.data.data.lon;
|
||||
vars.project.lat = res.data.data.lat;
|
||||
|
||||
closeLocationModal();
|
||||
|
||||
const lat = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lat');
|
||||
lat.innerHTML = `<div class="project-location-lat">위도 ${res.data.data.lat}</div>`;
|
||||
|
||||
const lon = document.querySelector('.archive-modal .wrap .modal-wrap .modal-body .project-setting-wrap .project-location-wrap .project-location-lon');
|
||||
lon.innerHTML = `<div class="project-location-lon">경도 ${res.data.data.lon}</div>`;
|
||||
}
|
||||
})
|
||||
|
||||
// 프로젝트 위치 수정
|
||||
function openLocationModal() {
|
||||
// 메인 지도 레이어 상태 백업
|
||||
if (vars.roadLayer && vars.satelliteLayer && vars.hybridLayer) {
|
||||
mainLayerState = {
|
||||
road: vars.roadLayer.getVisible(),
|
||||
satellite: vars.satelliteLayer.getVisible(),
|
||||
hybrid: vars.hybridLayer.getVisible()
|
||||
};
|
||||
}
|
||||
|
||||
initLocationMap();
|
||||
|
||||
// 기존 좌표가 있을 때만 블루 마커 생성
|
||||
if (vars.project.lon && vars.project.lat) {
|
||||
if (!ol.locationMap.blueMarkerOverlay) {
|
||||
// 블루 마커 (저장된 위치)
|
||||
let blueMarker = document.createElement('div');
|
||||
blueMarker.style.width = '1rem';
|
||||
blueMarker.style.height = '1rem';
|
||||
blueMarker.style.backgroundColor = '#0D8DF2';
|
||||
blueMarker.style.borderRadius = '50%';
|
||||
blueMarker.style.border = '0.1875rem solid #fff';
|
||||
blueMarker.style.boxShadow = '0 0.25rem 0.5rem rgba(0, 0, 0, 0.05)';
|
||||
|
||||
ol.locationMap.blueMarkerOverlay = new ol.Overlay({
|
||||
element: blueMarker,
|
||||
positioning: 'center-center',
|
||||
stopEvent: false,
|
||||
zIndex: 9998
|
||||
});
|
||||
ol.locationMap.addOverlay(ol.locationMap.blueMarkerOverlay);
|
||||
}
|
||||
|
||||
if (!ol.locationMap.blueLabelOverlay) {
|
||||
let blueLabel = document.createElement('div');
|
||||
|
||||
ol.locationMap.blueLabelOverlay = new ol.Overlay({
|
||||
element: blueLabel,
|
||||
positioning: 'bottom-center',
|
||||
offset: [0, -10],
|
||||
stopEvent: false,
|
||||
zIndex: 9998,
|
||||
});
|
||||
ol.locationMap.addOverlay(ol.locationMap.blueLabelOverlay);
|
||||
}
|
||||
|
||||
const position3857 = ol.proj.fromLonLat([vars.project.lon, vars.project.lat]);
|
||||
|
||||
ol.locationMap.blueMarkerOverlay.setPosition(position3857);
|
||||
ol.locationMap.blueLabelOverlay.setPosition(position3857);
|
||||
ol.locationMap.blueLabelOverlay.element.innerHTML = `
|
||||
<div style="background: #fff; color: #111; padding: 0.25rem 0.5rem; border-radius: 0.25rem; border: 0.1875rem solid #0D8DF2; box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.05); font-size: 0.875rem; font-weight: 500; line-height: 1.25rem; letter-spacing: -0.0175rem;">
|
||||
저장된 위치 <br>
|
||||
경도 : ${vars.project.lon}<br>
|
||||
위도 : ${vars.project.lat}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// 지도 초기화
|
||||
function initLocationMap() {
|
||||
// 위치 모달 지도만 삭제
|
||||
if (ol.locationMap) {
|
||||
ol.locationMap.getOverlays()?.clear();
|
||||
ol.locationMap.getLayers()?.forEach(l => {
|
||||
const src = l.getSource?.();
|
||||
if (src && src instanceof ol.source.Vector) src.clear(true);
|
||||
});
|
||||
ol.locationMap.getLayers()?.clear();
|
||||
if (typeof ol.locationMap.dispose === 'function') {
|
||||
ol.locationMap.dispose();
|
||||
}
|
||||
ol.locationMap = null;
|
||||
}
|
||||
|
||||
// 위치 설정 모달용 임시 레이어 생성
|
||||
const tempRoadLayer = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: vars.roadLayer.getSource().getUrls()[0]
|
||||
}),
|
||||
visible: mainLayerState?.road ?? true
|
||||
});
|
||||
const tempSatelliteLayer = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: vars.satelliteLayer.getSource().getUrls()[0]
|
||||
}),
|
||||
visible: mainLayerState?.satellite ?? false
|
||||
});
|
||||
const tempHybridLayer = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: vars.hybridLayer.getSource().getUrls()[0]
|
||||
}),
|
||||
visible: mainLayerState?.hybrid ?? false
|
||||
});
|
||||
|
||||
ol.locationMap = new ol.Map({
|
||||
target: 'map-location',
|
||||
layers: [tempRoadLayer, tempSatelliteLayer, tempHybridLayer],
|
||||
view: new ol.View({
|
||||
projection: 'EPSG:3857',
|
||||
center: ol.proj.fromLonLat([127.8, 35.9]),
|
||||
zoom: 7,
|
||||
constrainResolution: true,
|
||||
smoothResolutionConstraint: true
|
||||
})
|
||||
});
|
||||
|
||||
// 임시 레이어 참조 저장
|
||||
ol.locationMap.tempRoadLayer = tempRoadLayer;
|
||||
ol.locationMap.tempSatelliteLayer = tempSatelliteLayer;
|
||||
ol.locationMap.tempHybridLayer = tempHybridLayer;
|
||||
|
||||
// proj4 설정
|
||||
proj4.defs([
|
||||
['EPSG:5185', '+proj=tmerc +lat_0=38 +lon_0=125 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
['EPSG:5186', '+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
['EPSG:5187', '+proj=tmerc +lat_0=38 +lon_0=129 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
['EPSG:5188', '+proj=tmerc +lat_0=38 +lon_0=131 +k=1 +x_0=200000 +y_0=600000 +ellps=GRS80 +units=m +no_defs'],
|
||||
['EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'],
|
||||
['EPSG:4326', '+proj=longlat +datum=WGS84 +no_defs'],
|
||||
]);
|
||||
ol.proj.proj4.register(proj4);
|
||||
|
||||
// 레드 마커 생성
|
||||
let marker = document.createElement('div');
|
||||
marker.classList.add('marker');
|
||||
marker.style.width = '1rem';
|
||||
marker.style.height = '1rem';
|
||||
marker.style.backgroundColor = '#F21D0D';
|
||||
marker.style.borderRadius = '50%';
|
||||
marker.style.border = '0.1875rem solid #fff';
|
||||
marker.style.boxShadow = '0 0.25rem 0.5rem rgba(0, 0, 0, 0.05)';
|
||||
|
||||
ol.locationMap.redMarkerOverlay = new ol.Overlay({
|
||||
element: marker,
|
||||
positioning: 'center-center',
|
||||
stopEvent: false,
|
||||
zIndex: 9999
|
||||
});
|
||||
ol.locationMap.addOverlay(ol.locationMap.redMarkerOverlay);
|
||||
|
||||
let label = document.createElement('div');
|
||||
|
||||
ol.locationMap.redLabelOverlay = new ol.Overlay({
|
||||
element: label,
|
||||
positioning: 'bottom-center',
|
||||
offset: [0, -10],
|
||||
stopEvent: false,
|
||||
zIndex: 9999,
|
||||
});
|
||||
ol.locationMap.addOverlay(ol.locationMap.redLabelOverlay);
|
||||
|
||||
// 클릭 이벤트
|
||||
ol.locationMap.on('click', function(e) {
|
||||
const lonLat = ol.proj.toLonLat(e.coordinate);
|
||||
const lon = lonLat[0].toFixed(6);
|
||||
const lat = lonLat[1].toFixed(6);
|
||||
|
||||
ol.locationMap.clickCoord = {
|
||||
lon: Number(lon),
|
||||
lat: Number(lat),
|
||||
coordinate3857: e.coordinate
|
||||
};
|
||||
|
||||
ol.locationMap.redMarkerOverlay.setPosition(e.coordinate);
|
||||
|
||||
ol.locationMap.redLabelOverlay.element.innerHTML = `
|
||||
<div style="background: #fff; color: #111; padding: 0.25rem 0.5rem; border-radius: 0.25rem; border: 0.1875rem solid #F21D0D; box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.05); font-size: 0.875rem; font-weight:500; line-height: 1.25rem; letter-spacing: -0.0175rem;">
|
||||
선택한 위치 <br>
|
||||
경도 : ${lon} <br>
|
||||
위도 : ${lat}
|
||||
</div>
|
||||
`;
|
||||
|
||||
ol.locationMap.redLabelOverlay.setPosition(e.coordinate);
|
||||
});
|
||||
}
|
||||
|
||||
// 기본지도 타입 라디오 버튼 클릭 이벤트
|
||||
document.querySelectorAll('.project-location-modal input[name="location-base-map"]').forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
if (!ol.locationMap) return;
|
||||
|
||||
const type = this.value;
|
||||
|
||||
// 위치 설정 모달의 임시 레이어만 변경
|
||||
ol.locationMap.tempRoadLayer.setVisible(false);
|
||||
ol.locationMap.tempSatelliteLayer.setVisible(false);
|
||||
ol.locationMap.tempHybridLayer.setVisible(false);
|
||||
|
||||
switch(type) {
|
||||
case 'road':
|
||||
ol.locationMap.tempRoadLayer.setVisible(true);
|
||||
break;
|
||||
case 'hybrid':
|
||||
ol.locationMap.tempHybridLayer.setVisible(true);
|
||||
break;
|
||||
case 'satellite':
|
||||
ol.locationMap.tempSatelliteLayer.setVisible(true);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 모달 닫기 함수
|
||||
function closeLocationModal() {
|
||||
document.querySelector('.project-location-modal').style.display = 'none';
|
||||
|
||||
if (ol.locationMap) {
|
||||
ol.locationMap.setTarget(null);
|
||||
if (typeof ol.locationMap.dispose === 'function') {
|
||||
ol.locationMap.dispose();
|
||||
}
|
||||
ol.locationMap = null;
|
||||
}
|
||||
|
||||
// 메인 지도 레이어 상태 복원
|
||||
if (mainLayerState) {
|
||||
vars.roadLayer.setVisible(mainLayerState.road);
|
||||
vars.satelliteLayer.setVisible(mainLayerState.satellite);
|
||||
vars.hybridLayer.setVisible(mainLayerState.hybrid);
|
||||
mainLayerState = null;
|
||||
}
|
||||
}
|
||||
|
||||
2119
views/main/jsm/archive/socketManager.js
Normal file
2119
views/main/jsm/archive/socketManager.js
Normal file
File diff suppressed because it is too large
Load Diff
663
views/main/jsm/archive/userPermission.js
Normal file
663
views/main/jsm/archive/userPermission.js
Normal file
@@ -0,0 +1,663 @@
|
||||
import { vars } from './variable.js';
|
||||
|
||||
//설정창 close 25-08-18 권한 설정 모달 사용 중에 모달이 꺼지는일이 많아 주석처리로 꺼지는 기능을 막음
|
||||
// document.querySelector('.permission-modal').addEventListener('click', (e) => {
|
||||
// if (e.target.className.includes('permission-modal')) document.querySelector('.permission-modal').style.display = 'none';
|
||||
// })
|
||||
document.querySelector('.permission-modal img[alt="닫기"]').addEventListener('click', () => {
|
||||
// 이름 검색 결과창 초기화
|
||||
searchResultInit();
|
||||
document.querySelector('.permission-modal').style.display = 'none';
|
||||
})
|
||||
|
||||
//설정창 저장
|
||||
document.getElementById('permission-submit').addEventListener('click', () => {
|
||||
if(vars.permissionList.changed && Object.keys(vars.permissionList.changed).length > 0){
|
||||
if (confirm(`${Object.keys(vars.permissionList.changed).length}개의 변경사항을 저장하시겠습니까?`)) {
|
||||
upsertAuthList();
|
||||
}
|
||||
} else {
|
||||
searchResultInit();
|
||||
document.querySelector('.permission-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
//설정창 취소
|
||||
document.getElementById('permission-cancel').addEventListener('click', () => {
|
||||
if (vars.permissionList.changed && Object.keys(vars.permissionList.changed).length > 0) {
|
||||
if (!confirm(`${Object.keys(vars.permissionList.changed).length}개의 변경사항을 저장하지 않고 취소하시겠습니까?`)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 이름 검색 결과창 초기화
|
||||
searchResultInit();
|
||||
document.querySelector('.permission-modal').style.display = 'none';
|
||||
});
|
||||
|
||||
//tab 변경
|
||||
document.querySelectorAll('.permission-modal input[type="radio"]').forEach(ele => {
|
||||
ele.addEventListener('click', (e) => {
|
||||
document.querySelector('.permission-modal .modal-wrap .left .search-result-container').style.display = 'none';
|
||||
document.querySelector('.permission-modal .modal-wrap .left ul').style.display = 'block';
|
||||
|
||||
let id = e.target.id;
|
||||
let tab = document.querySelector('.permission-modal .select-list-' + id);
|
||||
|
||||
drawUserPermissionList(vars.permissionList, id);
|
||||
|
||||
[...tab.parentElement.children].forEach(element => {
|
||||
if (element.classList.contains('select-list')) element.style.display = 'none';
|
||||
});
|
||||
tab.style.display = 'block';
|
||||
})
|
||||
});
|
||||
|
||||
export async function getList() {
|
||||
let param = {
|
||||
// project_id : JSON.parse(localStorage.getItem('data')).id
|
||||
project_id: vars.project_id
|
||||
}
|
||||
let res = await axios.get('/auth/getMemberList', { params: param });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
// 유저권한설정창
|
||||
export async function drawUserPermissionList(list, rightId = 'sub-master') {
|
||||
document.querySelector('.permission-modal .left ul').innerHTML = '';
|
||||
document.querySelectorAll(`.permission-modal .select-list ul`).forEach(ul => ul.innerHTML = '');
|
||||
|
||||
// DB에 저장횐 company-list가 6개 외에 더 있어서 회사 리스트 고정
|
||||
const companyList = [ '한맥기술', '삼안', '장헌산업', `(주)장헌` , '피티씨' , '한라산업개발', '바론컨설턴트' , '기타'];
|
||||
// 기존 권한을 가진 인원(key-value)들을 배열화
|
||||
const permission = Object.values(list.changePermission);
|
||||
|
||||
try {
|
||||
// changePermission에는 user_id - lev만 있기때문에 배열을 DB로 보내 유저 정보를 가져온다
|
||||
const usersInfoRes = await axios.post('/auth/getPermissionUserInfo', {permission : permission});
|
||||
const userInfoArr = usersInfoRes.data.result;
|
||||
const rightUl = document.querySelector(`.permission-modal .select-list.select-list-${rightId} ul`);
|
||||
|
||||
userInfoArr.forEach(user => {
|
||||
if (findPermission(user.user_id, permission) !== rightId) return;
|
||||
|
||||
if (rightUl.querySelector(`#_${user.user_id}`)) return;
|
||||
// 유저 리스트 DOM 생성
|
||||
const userLi = drawUserList(user, rightId, permission, true);
|
||||
rightUl.appendChild(userLi);
|
||||
// 클릭 이벤트
|
||||
bindUserLiClick(userLi, user, rightId, permission, true);
|
||||
});
|
||||
// 유저 수 체크
|
||||
checkPermissionUserNum(rightId);
|
||||
} catch (err) {
|
||||
console.error('getPermissionUserInfo', err);
|
||||
}
|
||||
|
||||
// 회사 리스트 생성
|
||||
companyList.forEach(company => {
|
||||
const companyTitle = drawCompanyTitle(company);
|
||||
const companyToggleImg = companyTitle.querySelector('.company-toggle img');
|
||||
|
||||
// 회사 리스트 클릭시 부서명 생성
|
||||
companyTitle.addEventListener('click', async () => {
|
||||
let deptTitle = companyTitle.nextElementSibling;
|
||||
|
||||
// 부서명 최초 1회 DOM 생성 이후에 토글
|
||||
if (!deptTitle || !deptTitle.classList.contains('dept-container')) {
|
||||
companyToggleImg.src = '/main/img/permission/down.svg';
|
||||
deptTitle = document.createElement('ul');
|
||||
deptTitle.className = 'dept-container _scrollbar';
|
||||
deptTitle.dataset.company = company;
|
||||
companyTitle.insertAdjacentElement('afterend', deptTitle);
|
||||
|
||||
try {
|
||||
// 회사명을 통해 부서리스트 조회
|
||||
const deptRes = await axios.get('/auth/getDeptList', { params: { company } });
|
||||
if (deptRes.data.message === '200') {
|
||||
const deptList = deptRes.data.result;
|
||||
|
||||
// 한글 정렬
|
||||
deptList.sort((a, b) => {
|
||||
if (!a.dept || !b.dept) return 0;
|
||||
return a.dept.localeCompare(b.dept, 'ko');
|
||||
});
|
||||
|
||||
// 부서 리스트 생성
|
||||
for (const depts of deptList) {
|
||||
if (!depts.dept) continue;
|
||||
|
||||
const deptLi = drawDeptTitle(company, depts.dept);
|
||||
const deptToggleImg = deptLi.querySelector('.dept-toggle img');
|
||||
const deptCheckBox = deptLi.querySelector('input[type="checkbox"]');
|
||||
const deptToggelWrap = deptLi.querySelector('.toggle-wrap');
|
||||
|
||||
// 부서 개별 체크 박스 change 이벤트
|
||||
deptCheckBox.addEventListener('change', async (e) => {
|
||||
const isChecked = deptCheckBox.checked;
|
||||
const rightUl = document.querySelector(`.permission-modal .right .select-list.select-list-${rightId} ul`);
|
||||
try {
|
||||
// 회사명 - 부서를 통해 부서의 유저를 전부 조회
|
||||
const userRes = await axios.get('/auth/getUserList', { params: { company, dept: depts.dept }});
|
||||
|
||||
const userList = userRes.data.result;
|
||||
// 부서 전체 선택시에 이미 권한이 있어 권한을 부여할 수 없는 인원 수를 체크하는 배열
|
||||
const diffGroupArr = [];
|
||||
|
||||
// 유저 리스트 생성
|
||||
for (let user of userList) {
|
||||
const existingRight = rightUl.querySelector(`#_${user.user_id}`);
|
||||
const existingLeft = document.querySelector(`.permission-modal .left ul li#_${user.user_id} .user-wrap`);
|
||||
const lev = findPermission(user.user_id, Object.values(vars.permissionList.changePermission));
|
||||
// 전체 선택시
|
||||
if (isChecked) {
|
||||
// 이미 오른쪽(권한이 있던)에 있는 인원과 권한이 다른 인원은 continue로 건너뛰기
|
||||
if (existingRight) continue;
|
||||
if (lev && lev !== rightId){
|
||||
diffGroupArr.push(user);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 부관리자 10명 제한
|
||||
if (rightId === 'sub-master' && rightUl.children.length >= 10) {
|
||||
updateCheckboxState(user.company, user.dept);
|
||||
return alert('부관리자는 최대 10명까지 선택 가능합니다.');
|
||||
}
|
||||
// 전체선택으로 권한 변경된 유저 권한 변경
|
||||
changePermission(user.user_id, rightId, undefined);
|
||||
// 오른쪽 리스트에 생성
|
||||
const copy = drawUserList(user, rightId, permission, true);
|
||||
rightUl.appendChild(copy);
|
||||
// 클릭이벤트 추카
|
||||
bindUserLiClick(copy, user, rightId, permission, true);
|
||||
|
||||
// 왼쪽 유저리스트에 권한 뱃지 생성
|
||||
if (existingLeft) {
|
||||
const roleDiv = createRoleDiv(rightId);
|
||||
existingLeft.insertAdjacentHTML('beforeend', roleDiv);
|
||||
existingLeft.classList.add('selected_' + rightId);
|
||||
existingLeft.style.display = 'flex';
|
||||
}
|
||||
} else {
|
||||
// 전체 선택해제
|
||||
if (existingRight) {
|
||||
// 오른쪽 DOM 삭제와 권한 해제
|
||||
existingRight.remove();
|
||||
changePermission(user.user_id, undefined, rightId);
|
||||
}
|
||||
// 왼쪽 유저 리스트에 권한 뱃지 삭제
|
||||
existingLeft?.querySelector(`.user-permission-${rightId}`)?.remove();
|
||||
existingLeft?.classList.remove(`selected_${rightId}`);
|
||||
}
|
||||
}
|
||||
// 체크 박스 상태 변화 감지
|
||||
updateCheckboxState(company, depts.dept);
|
||||
if (diffGroupArr.length > 0){
|
||||
alert(`이미 권한을 가진 ${diffGroupArr.length}명은 ${{'sub-master' : '부관리자', 'security-worker' : '보안참여자', 'worker' : '일반참여자', 'viewer' : '참관자'}[rightId]} 권한을 부여할 수 없습니다.`);
|
||||
deptCheckBox.checked = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('getUserList Error (checkbox): ', err);
|
||||
}
|
||||
});
|
||||
|
||||
// 체크 박스 상태 변화 감지
|
||||
updateCheckboxState(company, depts.dept);
|
||||
|
||||
// 부서명 클릭 이벤트
|
||||
deptToggelWrap.addEventListener('click', async () => {
|
||||
let userTitle = deptLi.nextElementSibling;
|
||||
// 유저 최초 1회 생성 이후에 토글
|
||||
if (!userTitle || !userTitle.classList.contains('user-container')) {
|
||||
deptToggleImg.src = '/main/img/permission/down.svg';
|
||||
userTitle = document.createElement('ul');
|
||||
userTitle.className = 'user-container _scrollbar';
|
||||
deptLi.insertAdjacentElement('afterend', userTitle);
|
||||
|
||||
// 회사명,부서명으로 유저리스트 조회
|
||||
const userRes = await axios.get('/auth/getUserList', { params: { company, dept: depts.dept }});
|
||||
|
||||
if (userRes.data.message === '200') {
|
||||
const userList = userRes.data.result;
|
||||
// 유저리스트 생성
|
||||
userList.forEach(user => {
|
||||
// 유저 DOM 생성
|
||||
const userLi = drawUserList(user, rightId, permission);
|
||||
userTitle.appendChild(userLi);
|
||||
// 유저 클릭 이벤트
|
||||
bindUserLiClick(userLi, user, rightId, permission);
|
||||
});
|
||||
}
|
||||
}
|
||||
// 유저 토글 처리
|
||||
userTitle.style.display = userTitle.style.display === 'block' ? 'none' : 'block';
|
||||
deptToggleImg.src = userTitle.style.display === 'block' ? '/main/img/permission/down.svg' : '/main/img/permission/up.svg';
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('getDeptList Error', error);
|
||||
}
|
||||
}
|
||||
// 부서 토글처리
|
||||
deptTitle.style.display = deptTitle.style.display === 'block' ? 'none' : 'block';
|
||||
companyToggleImg.src = deptTitle.style.display === 'block' ? '/main/img/permission/down.svg' : '/main/img/permission/up.svg';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 유저 검색 기능 엔터 이벤트
|
||||
document.getElementById('permission-search').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
// input 값 추출 이후 DOM 생성
|
||||
const keyword = e.target.value.trim().toLowerCase();
|
||||
searchResultUser(keyword);
|
||||
}
|
||||
});
|
||||
|
||||
// 유저 검색 기능 클릭 이벤트
|
||||
document.querySelector('.permission-modal .modal-wrap .left .search-box .wrap .search-image').addEventListener('click', () => {
|
||||
const keyword = document.querySelector('.permission-modal .modal-wrap .left .search-input').value.trim().toLowerCase();
|
||||
searchResultUser(keyword);
|
||||
});
|
||||
|
||||
// 유저 검색 기능(엔터, 검색바 클릭)
|
||||
function searchResultUser(keyword) {
|
||||
// 프로젝트 관리자를 제외하고 검색하기 위해 필터링
|
||||
const users = vars.permissionList.all.filter(user => user.user_id !== vars.project.user_id);
|
||||
const searchResult = document.querySelector('.permission-modal .modal-wrap .left .search-result-container');
|
||||
const permissionLeftUl = document.querySelector('.permission-modal .modal-wrap .left ul');
|
||||
const permission = Object.values(vars.permissionList.changePermission);
|
||||
const rightId = document.querySelector('.permission-modal .modal-wrap .tab-input:checked').id;
|
||||
const positionMap = {'회장': 1, '부회장':2, '사장':3, '상임고문':4, '기술위원':5, '부사장':6, '고문':7, '전무이사':8, '수석연구원':9, '상무이사':10, '이사':11, '책임연구원':12, '부장':13, '차장':14, '선임연구원':15, '과장':16, '연구원':17, '대리':18, '사원':19};
|
||||
if (keyword !== '') {
|
||||
// 이름으로 필터링 이후 직급으로 정렬
|
||||
const result = users.filter(user => user.user_nm?.toLowerCase().includes(keyword)).sort((a,b) => {
|
||||
const aPosition = positionMap[a.position] ?? 99;
|
||||
const bPosition = positionMap[b.position] ?? 99;
|
||||
|
||||
if(aPosition !== bPosition) return aPosition - bPosition;
|
||||
return a.user_id.localeCompare(b.user_id);
|
||||
})
|
||||
|
||||
if (result.length === 0) {
|
||||
searchResult.style.display = 'none';
|
||||
permissionLeftUl.style.display = 'block';
|
||||
return alert('일치하는 사용자가 없습니다.');
|
||||
}
|
||||
|
||||
searchResult.innerHTML = '';
|
||||
// 조회된 유저 있을때
|
||||
result.forEach(user => {
|
||||
//유저 DOM 생성
|
||||
const userLi = drawUserList(user, rightId, permission);
|
||||
searchResult.appendChild(userLi);
|
||||
//클릭이벤트
|
||||
bindUserLiClick(userLi, user, rightId, permission, false);
|
||||
});
|
||||
// 검색결과창 띄우기
|
||||
searchResult.style.display = 'block';
|
||||
permissionLeftUl.style.display = 'none';
|
||||
} else {
|
||||
// 빈 값 입력시 검색결과창 내리기
|
||||
searchResult.style.display = 'none';
|
||||
permissionLeftUl.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 유저 검색결과 초기화(닫기버튼, X버튼 클릭시 실행)
|
||||
function searchResultInit(){
|
||||
const searchResult = document.querySelector('.search-result-container');
|
||||
searchResult.innerHTML = '';
|
||||
searchResult.style.display = 'none';
|
||||
document.querySelector('.permission-modal .modal-wrap .left ul').style.display = 'block';
|
||||
document.querySelector('.permission-modal .left .search-input').value = '';
|
||||
}
|
||||
|
||||
// 회사명 DOM 생성
|
||||
function drawCompanyTitle(company){
|
||||
if(company == '(주)장헌') company = '장헌';
|
||||
if(company == '피티씨') company = 'PTC';
|
||||
|
||||
const companyTitle = document.createElement('li');
|
||||
companyTitle.className = 'company-title';
|
||||
companyTitle.innerHTML += `
|
||||
<img class="" src="/main/img/permission/s-icon__${company}-large-logo.svg" alt="" />
|
||||
<div class="toggle-wrap">
|
||||
<span class="company-name">${(company == '기타')? '' :"(주)"}${company}</span>
|
||||
<div class="company-toggle">
|
||||
<img src="/main/img/permission/up.svg" alt="방향표">
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
document.querySelector('.permission-modal .left ul').append(companyTitle);
|
||||
|
||||
return companyTitle;
|
||||
}
|
||||
|
||||
// 부서명 DOM 생성
|
||||
function drawDeptTitle(company, dept){
|
||||
const deptLi = document.createElement('li');
|
||||
deptLi.className = 'dept-title';
|
||||
deptLi.dataset.company = company;
|
||||
deptLi.dataset.dept = dept;
|
||||
deptLi.innerHTML = `
|
||||
<label class="custom-checkbox">
|
||||
<input type="checkbox">
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
<div class="toggle-wrap">
|
||||
<span class="dept-name">${dept}</span>
|
||||
<div class="dept-toggle">
|
||||
<img src="/main/img/permission/up.svg" alt="방향표">
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.querySelector(`.permission-modal .left ul .dept-container[data-company="${company}"]`).appendChild(deptLi)
|
||||
|
||||
return deptLi;
|
||||
}
|
||||
// 유저리스트 DOM 생성
|
||||
function drawUserList(user, rightId, permissionList, isRightSide = false) {
|
||||
const li = document.createElement('li');
|
||||
li.className = 'user-li';
|
||||
li.id = `_${user.user_id}`;
|
||||
li.dataset.company = user.company;
|
||||
li.dataset.dept = user.dept;
|
||||
|
||||
const userWrap = document.createElement('div');
|
||||
userWrap.className = `user-wrap${isRightSide ? ` selected_${rightId} _scrollbar` : ''}`;
|
||||
|
||||
|
||||
userWrap.innerHTML += `
|
||||
<div class="user-img">
|
||||
<img draggable="false" src="http://erp.${(user.company === '장헌산업' ? 'jangheon' : 'hanmaceng')}.co.kr/erpphoto/${user.user_id}.jpg" alt="이미지" onerror="this.onerror=null; this.src='/main/img/archive/empty-profile.svg'">
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<div class="user-path">
|
||||
<img class="user-logo" src="/main/img/permission/s-icon__${(user.company == '피티씨')?'PTC':user.company}-small-logo.svg" alt="" />
|
||||
<span class="user-team">${user.dept}</span>
|
||||
</div>
|
||||
<div class="user-top">
|
||||
<span class="user-name">${user.user_nm}</span>
|
||||
<span class="user-position">${user.position ?? ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
// 유저 권한 확인
|
||||
const lev = findPermission(user.user_id, Object.values(vars.permissionList.changePermission));
|
||||
const role = isRightSide ? rightId : lev;
|
||||
// 유저권한 생성
|
||||
if (role) {
|
||||
const roleDiv = createRoleDiv(role);
|
||||
userWrap.insertAdjacentHTML('beforeend', roleDiv);
|
||||
if (!isRightSide && lev !== rightId) userWrap.classList.add('disabled');
|
||||
if (!isRightSide) userWrap.classList.add(`selected_${lev}`);
|
||||
}
|
||||
|
||||
li.appendChild(userWrap);
|
||||
return li;
|
||||
}
|
||||
|
||||
// 유저 권한 뱃지 생성
|
||||
function createRoleDiv(role){
|
||||
return `<div class="user-permission-${role}">
|
||||
<h6>${{'sub-master' : '부관리', 'security-worker' : '보안', 'worker' : '일반', 'viewer' : '참관'}[role]}</h6>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// 유저 클릭이벤트 바인딩
|
||||
function bindUserLiClick(userLi, user, rightId, permissionList, isRightSide = false) {
|
||||
const userWrap = userLi.querySelector('.user-wrap');
|
||||
const rightUl = document.querySelector(`.permission-modal .select-list.select-list-${rightId} ul`);
|
||||
|
||||
userLi.addEventListener('click', () => {
|
||||
const rightUser = rightUl.querySelector(`li#_${user.user_id}`);
|
||||
const leftUserWrap = document.querySelector(`.permission-modal .left ul li#_${user.user_id} .user-wrap`);
|
||||
const leftUserPermission = leftUserWrap?.querySelector(`.user-permission-${rightId}`);
|
||||
|
||||
if (userWrap.classList.contains('disabled')) return alert('다른 그룹에 등록된 인원입니다.');
|
||||
|
||||
const alreadySelected = userWrap.classList.contains(`selected_${rightId}`);
|
||||
// 이미 권한을 가진 인원 클릭했을때 해제 이벤트
|
||||
if (alreadySelected) {
|
||||
changePermission(user.user_id, undefined, rightId);
|
||||
// 오른쪽 유저 삭제(오른쪽 리스트 유저 삭제)
|
||||
rightUser.remove();
|
||||
// 왼쪽 유저 삭제(뱃지삭제 -> userWrap 클래스 삭제) 리스트가 펼쳐지지않아 아직 DOM 생성전에는 삭제가 불가능하기 때문에 옵셔널체이닝 추가
|
||||
leftUserPermission?.remove();
|
||||
leftUserWrap?.classList.remove(`selected_${rightId}`);
|
||||
} else {
|
||||
// 권한 부여 이벤트
|
||||
if (rightId === 'sub-master' && rightUl.children.length >= 10) return alert('부관리자는 최대 10명까지 선택 가능합니다.');
|
||||
|
||||
changePermission(user.user_id, rightId, undefined);
|
||||
userWrap.classList.add(`selected_${rightId}`);
|
||||
userWrap.insertAdjacentHTML('beforeend', createRoleDiv(rightId));
|
||||
|
||||
// 왼쪽 유저리스트 클릭시 오른쪽에 복사하여 생성+이벤트 추가
|
||||
const copy = drawUserList(user, rightId, permissionList, true);
|
||||
rightUl.appendChild(copy);
|
||||
bindUserLiClick(copy, user, rightId, permissionList, true);
|
||||
}
|
||||
updateCheckboxState(user.company, user.dept);
|
||||
// 유저 수 조회 이후 갱신
|
||||
checkPermissionUserNum(rightId);
|
||||
});
|
||||
}
|
||||
|
||||
document.querySelector('.permission-modal #help-btn').addEventListener('click', () => toggleHelp(true));
|
||||
|
||||
document.querySelector('.permission-modal .modal-permission-help .modal-permission-help__head img').addEventListener('click', ()=>toggleHelp(false));
|
||||
|
||||
function toggleHelp(show){
|
||||
const helpModal = document.querySelector('.permission-modal .modal-permission-help');
|
||||
const modalBack = document.querySelector('.permission-modal .modal-background');
|
||||
|
||||
helpModal.style.display = show ? 'block' : 'none';
|
||||
modalBack.style.display = show ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// 권한을 통해 유저 수 체크
|
||||
function checkPermissionUserNum(rightId){
|
||||
// 오른쪽 리스트에 몇명이 있는지 체크
|
||||
const userNum = document.querySelector(`.permission-modal .modal-wrap .right .select-list.select-list-${rightId} ul`).children.length;
|
||||
// 유저 수를 나타낼 dom
|
||||
const headerUserNum = document.querySelector('.permission-modal .modal-header .header-user-num-wrap .user-num');
|
||||
// 부관리자 10명이 넘을때 강조효과
|
||||
let text = `현재 ${userNum}명`;
|
||||
let color = '#111';
|
||||
|
||||
if(rightId === 'sub-master'){
|
||||
text += ' (최대 10명)';
|
||||
if(userNum >= 10) color = '#F21D0D';
|
||||
}
|
||||
|
||||
headerUserNum.innerText = text;
|
||||
headerUserNum.style.color = color;
|
||||
}
|
||||
|
||||
// 체크박스 DOM, 회사명, 부서를 통해 체크박스 상태 반영 함수
|
||||
async function updateCheckboxState(company, dept){
|
||||
const deptUsers = document.querySelectorAll(`.permission-modal .modal-wrap .right ul li[data-company="${company}"][data-dept="${dept}"]`);
|
||||
const checkbox = document.querySelector(`.permission-modal .modal-wrap .left li[data-company="${company}"][data-dept="${dept}"] input[type=checkbox]`);
|
||||
const checkmark = document.querySelector(`.permission-modal .modal-wrap .left li[data-company="${company}"][data-dept="${dept}"] label .checkmark`);
|
||||
const rightId = document.querySelector('.permission-modal .modal-wrap .tab-input:checked').id;
|
||||
const key = `${company}_${dept}`;
|
||||
//permissionList.deptUserCount와 현재 권한 부여된 유저의 길이를 비교해 체크박스 상태변화
|
||||
if(vars.permissionList.deptUserCount[key]){
|
||||
if(!checkbox) return;
|
||||
|
||||
if(deptUsers.length === 0){
|
||||
checkbox.checked = false;
|
||||
checkmark.classList.remove('indeterminate');
|
||||
} else if(deptUsers.length === vars.permissionList.deptUserCount[key]) {
|
||||
checkbox.checked = true;
|
||||
checkmark.classList.remove('indeterminate');
|
||||
} else {
|
||||
checkbox.checked = false;
|
||||
checkmark.classList.add('indeterminate');
|
||||
}
|
||||
}
|
||||
checkPermissionUserNum(rightId);
|
||||
}
|
||||
|
||||
function findPermission(id, permissionList) {
|
||||
for (let i = 0; i < permissionList.length; i++) {
|
||||
if (permissionList[i].user_id.toUpperCase() == id.toUpperCase()) return permissionList[i].lev;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getUserInfo(id) {
|
||||
for (let i = 0; i < vars.permissionList.all.length; i++) {
|
||||
if (vars.permissionList.all[i].user_id == id) return vars.permissionList.all[i];
|
||||
}
|
||||
}
|
||||
|
||||
function changePermission(id, group, rightId) {
|
||||
if (!vars.permissionList.changed) vars.permissionList.changed = [];
|
||||
if (!vars.permissionList.changePermission) vars.permissionList.changePermission = {};
|
||||
id = id.toUpperCase();
|
||||
/*
|
||||
vars.permissionList.changed => 변화객체
|
||||
vars.permissionList.permission => 원본 permission
|
||||
vars.permissionList.changePermission => 변화후 permission
|
||||
*/
|
||||
if (group) {
|
||||
//그룹에 넣을때 key, value 형태로 저장
|
||||
vars.permissionList.changePermission[id] = {user_id : id, lev : group}
|
||||
} else {
|
||||
//그룹에서 제외
|
||||
delete vars.permissionList.changePermission[id];
|
||||
}
|
||||
|
||||
// 기존 permission에서 이전 Lev 가져오기
|
||||
const original = vars.permissionList.permission?.find(p => p.user_id.toUpperCase() === id);
|
||||
const originalLev = original?.lev;
|
||||
|
||||
// 기존 permission과 새로운 permission을 비교해 변화 감지
|
||||
if(group === originalLev || (!group && !originalLev)){
|
||||
delete vars.permissionList.changed[id];
|
||||
return;
|
||||
}
|
||||
|
||||
// 변화감지하여 변화객체 저장 (중복성 피하기 위해)
|
||||
vars.permissionList.changed[id] = {user_id : id, lev : group, before : originalLev, user_nm : getUserInfo(id)?.user_nm}
|
||||
}
|
||||
|
||||
async function upsertAuthList() {
|
||||
//lev undefined는 deleteAuthList
|
||||
let upsertArr = [];
|
||||
let deleteArr = [];
|
||||
|
||||
//key-value 형태로 만들었던 changed를 배열로 전환
|
||||
const changedArr = Object.values(vars.permissionList.changed);
|
||||
if (changedArr.length > 0) {
|
||||
for (let i = 0; i < changedArr.length; i++) {
|
||||
if (changedArr[i].lev == undefined) {
|
||||
deleteArr.push(changedArr[i]);
|
||||
} else {
|
||||
upsertArr.push(changedArr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
let upsertMessage = (upsertArr.length > 0) ? false : true;
|
||||
let deleteMessage = (deleteArr.length > 0) ? false : true;
|
||||
console.log(upsertArr);
|
||||
console.log(deleteArr);
|
||||
|
||||
if (upsertArr.length > 0) {
|
||||
let upsertRes = await axios.post('/auth/upsertPermission', { project_id: vars.project_id, targetArr: upsertArr, userInfoString: vars.userInfoString });
|
||||
if (upsertRes.data.message == 'upsert success'){
|
||||
const logs = upsertRes.data.logs;
|
||||
if(logs.length > 0){
|
||||
try{ // upsert 이후 로그 추가
|
||||
const logRes = await axios.post(`${vars.path_name}/addPermissionLog`, {logs});
|
||||
if(logRes.data.message === 'addPermissionLog success') upsertMessage = true;
|
||||
} catch(err) {
|
||||
console.error('upsert addPermissionLog Error', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (deleteArr.length > 0) {
|
||||
let deleteRes = await axios.post('/auth/deletePermission', { project_id: vars.project_id, targetArr: deleteArr, userInfoString: vars.userInfoString });
|
||||
if (deleteRes.data.message == 'delete success'){
|
||||
const logs = deleteRes.data.logs;
|
||||
if(logs.length > 0){
|
||||
try{ // delete 이후 로그 추가
|
||||
const logRes = await axios.post(`${vars.path_name}/addPermissionLog`, {logs});
|
||||
if(logRes.data.message === 'addPermissionLog success') deleteMessage = true;
|
||||
} catch(err) {
|
||||
console.error('delete addPermssionLog Error', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (upsertMessage && deleteMessage) {
|
||||
alert(`${Object.keys(vars.permissionList.changed).length}개의 변경사항이 저장되었습니다.`);
|
||||
}
|
||||
}
|
||||
searchResultInit();
|
||||
document.querySelector('.permission-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
export class Permission {
|
||||
constructor(){
|
||||
//permission 정의
|
||||
this.permissionDef = {
|
||||
1535 : '개발자',
|
||||
255 : '관리자',
|
||||
191 : '부관리자',
|
||||
15 : '보안참여자',
|
||||
7 : '일반참여자',
|
||||
1 : '참관자'
|
||||
}
|
||||
//기능 정의 -'기능명': 허용 권한 code -> ex) 'headerPlusBtn' : 191
|
||||
this.funcDef = {
|
||||
'dev-menu' : 1535,
|
||||
'permission-btn' : 191,
|
||||
'overview-left-edit' : 191,
|
||||
'overview-middle-edit' : 191,
|
||||
'overview-task-history-add' : 191,
|
||||
'overview-task-history-save' : 191,
|
||||
'overview-task-history-delete' : 191,
|
||||
'overview-schedule-add' : 7,
|
||||
'overview-schedule-edit' : 7,
|
||||
'overview-issue-edit' : 191,
|
||||
'header-menu-add' : 191,
|
||||
'memo-ai' : 7,
|
||||
'memo-edit' : 7,
|
||||
'memo-text' : 7,
|
||||
'set-user-permission' : 191,
|
||||
'context-menu-viewer' : 7,
|
||||
'context-menu-sub-master' : 191,
|
||||
'add-badge-master' : 255,
|
||||
'add-badge-sub-master' : 191,
|
||||
'add-badge-security-worker': 15,
|
||||
'add-badge-worker': 7,
|
||||
'add-badge-viewer': 1,
|
||||
'convert-btn-viewer': 7,
|
||||
'project-inactive-sign' : 1535,
|
||||
'createFolder-gallery' : 1535,
|
||||
'download-folder' : 255,
|
||||
'change-project-btn' : 255,
|
||||
|
||||
}
|
||||
|
||||
|
||||
this.user = (vars.userInfoString)?JSON.parse(vars.userInfoString) : undefined;
|
||||
this.permission = this.user.permission;
|
||||
this.permissionString = this._toPermissionString();
|
||||
}
|
||||
|
||||
_toPermissionString(){
|
||||
return this.permissionDef[`${this.permission}`];
|
||||
}
|
||||
|
||||
checkPermission(func){
|
||||
return this.permission >= this.funcDef[func];
|
||||
}
|
||||
}
|
||||
213
views/main/jsm/archive/variable.js
Normal file
213
views/main/jsm/archive/variable.js
Normal file
@@ -0,0 +1,213 @@
|
||||
let path_name = decodeURIComponent(window.location.pathname);
|
||||
let project_id = path_name.split('/')[1];
|
||||
|
||||
//////// pm-bcmf 연결용 테스트 코드 - project_id가 pm-bcmf일 때 project_id는 dsdj2, path_name은 /dsdj2/archive로 변경
|
||||
if (project_id == 'pm-bcmf') {
|
||||
project_id = 'dsdj2';
|
||||
path_name = '/dsdj2/archive';
|
||||
}
|
||||
|
||||
//세종안성(sjas)(gtb.) and 청용천교(cheongyong)(cheongyong.) 연결용 테스트 코드
|
||||
if(project_id == '' || project_id == undefined) {
|
||||
if(window.location.origin.includes('gtb.')){
|
||||
project_id = 'sjas';
|
||||
path_name = '/sjas/archive';
|
||||
}/* else if(window.location.origin.includes('cheongyong.')){
|
||||
project_id = 'cheongyong';
|
||||
path_name = '/cheongyong/archive';
|
||||
} */
|
||||
}
|
||||
|
||||
export const vars = {
|
||||
// 아카이브 라우터 연결을 위한 변수
|
||||
path_name: path_name,
|
||||
|
||||
// 프로젝트 id
|
||||
project_id: project_id,
|
||||
|
||||
// 오브젝트 스토리지 타입 (ONPREMISE / CLOUD)
|
||||
storageType: undefined,
|
||||
|
||||
// 클라우드 타입 (ONPREMISE / CLOUD)
|
||||
cloudType: undefined,
|
||||
|
||||
// 버킷 (프로젝트 id와 동일)
|
||||
bucket: project_id,
|
||||
|
||||
// 현재 로그인 한 유저 정보
|
||||
userInfoString: undefined,
|
||||
|
||||
// 현재 로그인 한 유저의 permission 관리 객체
|
||||
permission: undefined,
|
||||
|
||||
// 현재 로그인 한 유저의 마우스커서 정보를 담는 객체
|
||||
cursors: {},
|
||||
|
||||
// 전체 프로젝트 정보
|
||||
allProject: {},
|
||||
|
||||
// 현재 프로젝트 정보
|
||||
project: undefined,
|
||||
|
||||
// 최상위 폴더 사용 용량 / 남은 용량 / 저장 가능한 최대 용량 (50 GB) -> DB에서 가져오기 / 폴더별 사이즈 (아카이브, 공문, 과업개요, GSIM 모델)
|
||||
bucketSize: undefined, remainingSize: undefined, bucketMaxSize: 50*1024*1024*1024, foldersSize: undefined,
|
||||
|
||||
// 최상위 폴더 아래에 있는 모든 폴더/파일 데이터
|
||||
allTreeObject: undefined,
|
||||
|
||||
// 현재 선택된 폴더 아래에 있는 모든 폴더/파일 데이터
|
||||
currentTreeObject: undefined,
|
||||
|
||||
// 휴지통에 있는 모든 파일 데이터
|
||||
recycleBinObject: undefined,
|
||||
|
||||
// 마지막으로 선택한 header-btn/tree-item/list-item -> 스타일 변경에 사용 - changeListItemStyle(listItem)
|
||||
lastHeaderBtn: undefined, lastMainTreeItem: undefined, lastModalTreeItem: undefined, lastListItem: undefined, lastListGroupTarget: undefined,
|
||||
// lastMainTreeDepth2Item: undefined, lastModalTreeDepth2Item: undefined,
|
||||
// lastMainTreeDepth3Item: undefined, lastModalTreeDepth3Item: undefined,
|
||||
|
||||
// 마지막으로 우클릭한 타겟 -> 컨텍스트 메뉴에 사용
|
||||
lastContextTarget: undefined,
|
||||
|
||||
// 마지막으로 선택한 header/tree/list item
|
||||
lastSelectTarget: undefined,
|
||||
|
||||
// 드래그가 시작된 list-item
|
||||
dragStartListItem: undefined,
|
||||
|
||||
// 클릭, 더블클릭 구분을 위한 시간
|
||||
clickDuration: 500,
|
||||
|
||||
// 업로드, 다운로드 완료 후 프로그레스 종료 전 완료 후 잠깐 대기하는 시간
|
||||
progressDuration: 500,
|
||||
|
||||
// 현재 변환중인 파일 정보를 저장하는 배열
|
||||
convertingDataArr: [],
|
||||
|
||||
// 현재 요약중인 파일 정보를 저장하는 배열
|
||||
summarizeAiDataArr: [],
|
||||
|
||||
// file area list에서 다중 선택된 item 배열
|
||||
multiSelectListItemArr: [],
|
||||
|
||||
// 휴지통에서 사용할 lastListItem, lastListGroupTarget, lastContextTarget, lastSelectTarget, multiSelectListItemArr
|
||||
// 휴지통에서 다중 선택된 item 배열
|
||||
lastListItem_bin: undefined,
|
||||
lastListGroupTarget_bin: undefined,
|
||||
lastContextTarget_bin: undefined,
|
||||
lastSelectTarget_bin: undefined,
|
||||
multiSelectListItemArr_bin: [],
|
||||
|
||||
// 로그 데이터 -> 활동 정보 activity-info-item(로그 데이터)에 사용 (활동 정보 아이템 클릭 시 상세 정보 표시에 사용)
|
||||
logData: undefined,
|
||||
|
||||
// 로그 모달창이 열렸는지 안열렸는지 판단하는 변수 -> 최초에만 false이고 한 번이라도 열리면 true로 유지
|
||||
isLogModalOpened: false,
|
||||
|
||||
// 새로 추가되는 로그 아이템을 저장하는 배열
|
||||
newLogItemArr: [],
|
||||
|
||||
// 일반아이템리스트/휴지통아이템리스트 구분에 사용
|
||||
listItemWrap: document.querySelector('.archive-main-center .list-container .list-body .list-item-wrap'),
|
||||
|
||||
// 미리보기 뷰어
|
||||
viewer: undefined,
|
||||
|
||||
// 메모 임시 저장
|
||||
tempMemo: [],
|
||||
|
||||
// 파일 리스트 sort용 현재 컬럼, 현재 정렬순
|
||||
curSortCol : 'name',
|
||||
curSortOrder : 'asc',
|
||||
|
||||
// 휴지통 모달창 sort용 현재 컬럼, 현재 정렬순
|
||||
curSortCol_bin : 'remove-date',
|
||||
curSortOrder_bin : 'desc',
|
||||
|
||||
// 리스트/그리드 파일 목록에서 파일 선택했을 때 뷰어에 연결중 표시에 사용할 시간
|
||||
viewerConnectingTime: 0, viewerConnectingTimeValue: 700,
|
||||
|
||||
// 트리 영역에서 마지막으로 선택한 폴더 타입
|
||||
lastFolderType: undefined,
|
||||
|
||||
// 컨트롤 박스에서 마지막으로 선택한 파일 영역 모드
|
||||
// lastFileAreaMode: undefined,
|
||||
lastFileAreaMode: 'list',
|
||||
|
||||
// 지도 모드 (normal <-> edit)
|
||||
mapMode: 'normal',
|
||||
|
||||
// 배경지도 url (일반, 위성, 하이브리드)
|
||||
road: 'https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&hl=ko&&apistyle=s.e:l.i|p.v:off',
|
||||
satellite: 'https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}&hl=ko&apistyle=s.e:l.i|p.v:off',
|
||||
hybrid: 'https://mt1.google.com/vt/lyrs=y&x={x}&y={y}&z={z}&hl=ko&apistyle=s.e:l.i|p.v:off',
|
||||
|
||||
// 클러스터 거리 (픽셀 기준)
|
||||
clusterDistance: 1,
|
||||
|
||||
// 마지막으로 선택한 클러스터
|
||||
lastSelectCluster: undefined,
|
||||
|
||||
// 모든 오버레이를 표시하는 줌 레벨
|
||||
allOverlayVisibleZoom: 19,
|
||||
|
||||
// 클러스터 레이어, 포인트 레이어
|
||||
olClusterLayer: undefined,
|
||||
olPointLayer: undefined,
|
||||
|
||||
listOrder: [],
|
||||
|
||||
countdownTimer: undefined,
|
||||
}
|
||||
|
||||
// 오픈레이어스 타일 레이어
|
||||
vars.baseLayer = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: vars.road,
|
||||
crossOrigin: 'anonymous',
|
||||
transition: 0
|
||||
})
|
||||
});
|
||||
|
||||
vars.roadLayer = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: vars.road,
|
||||
crossOrigin: 'anonymous',
|
||||
transition: 0
|
||||
}),
|
||||
visible: true
|
||||
});
|
||||
vars.satelliteLayer = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: vars.satellite,
|
||||
crossOrigin: 'anonymous',
|
||||
transition: 0
|
||||
}),
|
||||
visible: false
|
||||
});
|
||||
vars.hybridLayer = new ol.layer.Tile({
|
||||
source: new ol.source.XYZ({
|
||||
url: vars.hybrid,
|
||||
crossOrigin: 'anonymous',
|
||||
transition: 0
|
||||
}),
|
||||
visible: false
|
||||
});
|
||||
|
||||
// 구성 popup ver 위한 전역 함수
|
||||
// vars.navigateToPath = function(path) {
|
||||
// const resourcePath = `/${path}`;
|
||||
|
||||
// // 헤더 버튼 클릭 (탭 선택)
|
||||
// const tabPath = resourcePath.split('/')[1]; // '모델뷰어테스트/모델/3dm' -> '모델뷰어테스트'
|
||||
// const headerBtn = document.querySelector(`[data-resource-path="/${tabPath}"]`);
|
||||
// if (headerBtn) headerBtn.click();
|
||||
|
||||
// // 트리 아이템 클릭 (폴더 선택)
|
||||
// setTimeout(() => {
|
||||
// const treeItem = document.querySelector(`.tree-item-wrap[data-resource-path="${resourcePath}"] .tree-item`);
|
||||
// if (treeItem) treeItem.click();
|
||||
// }, 500);
|
||||
// }
|
||||
|
||||
window.vars = vars;
|
||||
856
views/main/jsm/main.js
Normal file
856
views/main/jsm/main.js
Normal file
@@ -0,0 +1,856 @@
|
||||
import { AntiDebug } from '../../anti_debugging.js';
|
||||
let antiDebug = new AntiDebug();
|
||||
|
||||
import { vars } from './archive/variable.js';
|
||||
import { Permission } from './archive/userPermission.js';
|
||||
import { docVars } from './officialDoc/docVariable.js';
|
||||
import { overviewVars } from './overview/overviewVariable.js';
|
||||
import {getMyDownloadList} from './archive/common.js';
|
||||
// import iconv from 'https://cdn.jsdelivr.net/npm/iconv-lite@0.6.3/lib/index.min.js';
|
||||
import { initProjectSetting } from './archive/projectSetting.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
//// .env파일에 저장된 배포 타입, 클라우드 타입 가져와서 전역변수에 storageType, cloudType 으로 저장
|
||||
let getEnvDataRes = await axios.get('/common/getEnvData');
|
||||
vars.storageType = getEnvDataRes.data.deploymentType;
|
||||
vars.cloudType = getEnvDataRes.data.cloudType;
|
||||
vars.serviceName = getEnvDataRes.data.serviceName;
|
||||
docVars.storageType = getEnvDataRes.data.deploymentType;
|
||||
|
||||
//// 프로젝트 정보 가져와서 전역변수에 저장
|
||||
let projectRes = await axios.get('/common/getProject');
|
||||
for (let project of projectRes.data.data) {
|
||||
vars.allProject[project.project_id] = project;
|
||||
}
|
||||
vars.project = vars.allProject[vars.project_id];
|
||||
|
||||
//// 타이틀 설정
|
||||
await setTitle();
|
||||
|
||||
//// 프로젝트별 최대 용량 설정
|
||||
vars.bucketMaxSize = (vars.project) ? parseInt(vars.project.storage_byte) * 1024 * 1024 * 1024 : 0;
|
||||
|
||||
//// 유저 정보 객체 가져와서 스트링으로 전역 변수에 저장
|
||||
let authStatusRes = await axios.get('/auth/status',{ params: { project_id: vars.project_id } });
|
||||
let userInfo = authStatusRes.data.user;
|
||||
vars.userInfoString = JSON.stringify(userInfo);
|
||||
|
||||
//////// pm-bcmf 연결용 테스트 코드 - BCMF 유저 정보 생성, 타이틀 커서 스타일 변경, 자동 경로 이동용 변수 설정
|
||||
let getBcmfUrlQueryRes = await axios.get('/auth/getBcmfUrlQuery');
|
||||
if (getBcmfUrlQueryRes.data.bcmfId && getBcmfUrlQueryRes.data.bcmfId.includes('bcmf-')) {
|
||||
// BCMF 유저 정보 생성
|
||||
let permission = (getBcmfUrlQueryRes.data.bcmfId.split('-')[1] == 'viewer') ? 1 : 7
|
||||
userInfo = {
|
||||
user_id: getBcmfUrlQueryRes.data.bcmfId,
|
||||
user_nm: "BCMF",
|
||||
company: "BCMF",
|
||||
dept: "대산당진 2공구",
|
||||
position: "",
|
||||
group: "bcmf",
|
||||
is_resigned: false,
|
||||
permission: permission,
|
||||
bookmark: null,
|
||||
}
|
||||
vars.userInfoString = JSON.stringify(userInfo);
|
||||
|
||||
// 타이틀에 마우스 올렸을 때 기본 커서로 표시
|
||||
document.querySelector('body > .header .left .title').style.cursor = 'default';
|
||||
|
||||
// bcmf에서 iframe으로 pm 화면 연결하는 경우 헤더, 푸터 숨김
|
||||
document.querySelector('body > .header').style.display = 'none';
|
||||
document.querySelector('body > .footer').style.display = 'none';
|
||||
// 헤더가 숨겨져 있으므로 높이에서 footer height(2.25rem)만 빼고, top은 0으로 설정
|
||||
document.querySelectorAll('body > .main').forEach(main => {
|
||||
main.style.height = '100dvh';
|
||||
main.style.top = '0';
|
||||
})
|
||||
|
||||
// 시작경로를 파라미터로 전달한 경우 해당 경로로 자동 이동을 위한 변수 설정
|
||||
if (getBcmfUrlQueryRes.data.startPath) {
|
||||
let startPath = getBcmfUrlQueryRes.data.startPath;
|
||||
let startPathSplit = startPath.split('/');
|
||||
vars.startPathDepth1 = startPathSplit[0];
|
||||
vars.startPathDepth2 = startPathSplit[1];
|
||||
vars.startPathDepth3 = startPathSplit[2];
|
||||
}
|
||||
}
|
||||
|
||||
if (checkProjectInactive()) setProjectInactive('lock');
|
||||
|
||||
// vars.allProject에서 각 프로젝트별 배너 공지(banner_notice) 사용해서 배너 공지 표시/해제 설정
|
||||
toggleBannerNoticeArea();
|
||||
|
||||
//// 접속 인원 모달창에 프로젝트 담당자 표시
|
||||
// let projectManagerName = document.querySelector('.archive-modal .modal-header > .title .right-wrap .project-manager-name');
|
||||
let projectManagerName = document.querySelector('.archive-modal .modal-wrap .modal-body .connected-users-wrap .project-setting-wrap .project-manager-wrap .project-manager-name');
|
||||
projectManagerName.textContent = (vars.project) ? `${vars.project.user_nm} ${vars.project.position}` : '-';
|
||||
if (vars.project && !vars.project.user_nm) projectManagerName.textContent = '-';
|
||||
|
||||
// 유저 정보로 permission 객체 생성
|
||||
vars.permission = new Permission();
|
||||
//permission 객체 수정 못하도록 freeze
|
||||
Object.freeze(vars.permission);
|
||||
Object.freeze(Object.prototype);
|
||||
|
||||
// 조건 위치를 permission 객체 함수를 사용하기 위해 객체 생성 이후로 위치 조정 main.js/33 -> main.js/47
|
||||
// 개발자 계정인 경우
|
||||
if (vars.permission.checkPermission('project-inactive-sign')) {
|
||||
// 프로젝트가 비활성화 상태이면 project-inactive-sign on
|
||||
if (!vars.project.is_active) {
|
||||
document.querySelector('.project-inactive-sign').style.display = 'flex';
|
||||
}
|
||||
}
|
||||
|
||||
//// 각 페이지별 index.js 임포트
|
||||
const archiveIndex = await import(`./archive/index.js`);
|
||||
if (!archiveIndex.isLoaded) {
|
||||
archiveIndex.loadArchive();
|
||||
}
|
||||
|
||||
// 과업개요 index.js 임포트
|
||||
if(vars.project && vars.project.overview) await import(`./overview/index.js`);
|
||||
|
||||
//// 유저 권한에 따라 초기 dom객체 표시 or 삭제
|
||||
await removeDomByPermission(userInfo);
|
||||
|
||||
//// 컴포넌트 초기 설정
|
||||
await componentsInit();
|
||||
|
||||
//// 실시간 마우스 표시
|
||||
document.addEventListener('mousemove', throttle((event) => {
|
||||
vars.socket.emit('setMouseInfo', {
|
||||
x: event.clientX / window.innerWidth,
|
||||
y: event.clientY / window.innerHeight,
|
||||
});
|
||||
}, 10));
|
||||
|
||||
vars.socket.on('setMouse', (data) => {
|
||||
// 자신의 커서는 생성하거나 업데이트하지 않음
|
||||
if (data.clientId === vars.socket.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vars.cursors[data.clientId]) {
|
||||
// 새로운 클라이언트 ID에 대한 커서 생성
|
||||
let cursor = document.createElement('div');
|
||||
cursor.className = 'cursor';
|
||||
|
||||
let wrap = document.createElement('div');
|
||||
wrap.className = 'wrap';
|
||||
|
||||
let userInfo = data;
|
||||
let userColor = data.userColor;
|
||||
let userText = document.createElement('div');
|
||||
userText.className = 'user-text';
|
||||
userText.textContent = `${userInfo.user_nm} ${userInfo.position}`;
|
||||
// userText.style.color = userColor;
|
||||
userText.style.background = userColor;
|
||||
|
||||
let cursorSvg = `
|
||||
<svg width="30" height="30" viewBox="0 0 50 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.6279 2L14.611 43.1659L26.1686 30.0568L43.605 31.2398L14.6279 2Z"
|
||||
stroke="#cccccc" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.7041 4.61523L15.6894 40.3646L25.7263 28.9804L40.8684 30.0077L15.7041 4.61523Z"
|
||||
fill="${userColor}" stroke="#ffffff" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
`;
|
||||
// let cursorSvg = `
|
||||
// <svg width="30" height="30" viewBox="0 0 50 52" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
// <path d="M15.7041 4.61523L15.6894 40.3646L25.7263 28.9804L40.8684 30.0077L15.7041 4.61523Z"
|
||||
// fill="${userColor}"/>
|
||||
// </svg>
|
||||
// `;
|
||||
cursor.innerHTML = cursorSvg;
|
||||
|
||||
wrap.appendChild(userText);
|
||||
cursor.appendChild(wrap);
|
||||
document.body.appendChild(cursor);
|
||||
vars.cursors[data.clientId] = cursor;
|
||||
vars.cursors[data.clientId].id = data.clientId;
|
||||
vars.cursors[data.clientId].timer = setTimeout(() => {
|
||||
if (vars.cursors[clientId]) {
|
||||
vars.cursors[clientId].remove();
|
||||
delete vars.cursors[clientId];
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 커서 위치 업데이트
|
||||
vars.cursors[data.clientId].style.left = (data.x * window.innerWidth - 7) + 'px';
|
||||
vars.cursors[data.clientId].style.top = data.y * window.innerHeight + 'px';
|
||||
//타이머 재설정
|
||||
if (vars.cursors[data.clientId].timer) {
|
||||
clearTimeout(vars.cursors[data.clientId].timer);
|
||||
}
|
||||
vars.cursors[data.clientId].timer = setTimeout(() => {
|
||||
if (vars.cursors[data.clientId]) {
|
||||
vars.cursors[data.clientId].remove();
|
||||
delete vars.cursors[data.clientId];
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
vars.socket.on('userDisconnected', (clientId) => {
|
||||
// 연결이 끊긴 클라이언트의 커서 제거
|
||||
if (vars.cursors[clientId]) {
|
||||
vars.cursors[clientId].remove();
|
||||
delete vars.cursors[clientId];
|
||||
}
|
||||
});
|
||||
|
||||
save_user();
|
||||
});
|
||||
|
||||
// ** 권한 관련
|
||||
async function removeDomByPermission(userInfo) {
|
||||
if (userInfo.permission != undefined || userInfo.permission != null || userInfo.permission != '') {
|
||||
// 기능별 권한이 변경될 경우를 고려해서 각자 개별 분리 시켜주었습니다.
|
||||
// 개발자도구 모달
|
||||
const devMenuModal = document.querySelector('.dev-menu-modal');
|
||||
// 개발자도구 모달은 키는 함수가 따로 있기 때문에 권한이 없다면 DOM 삭제만 진행
|
||||
if(devMenuModal){
|
||||
if(!vars.permission.checkPermission('dev-menu')) devMenuModal.remove();
|
||||
}
|
||||
|
||||
// 헤더 폴더 추가 버튼 (부관리자 이상)
|
||||
const headerMenuAddBtn = document.querySelector('.menu-add');
|
||||
if(headerMenuAddBtn){
|
||||
if(vars.permission.checkPermission('set-user-permission')){
|
||||
headerMenuAddBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
headerMenuAddBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 유저 권한 설정 버튼 (부관리자 이상)
|
||||
const setUserPermissionBtn = document.querySelector('.set-user-permission-btn');
|
||||
if(setUserPermissionBtn){
|
||||
if(vars.permission.checkPermission('set-user-permission')){
|
||||
setUserPermissionBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
setUserPermissionBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 과업개요 왼쪽 영역 수정 버튼 (부관리자 이상)
|
||||
const overviewSectionLeftBtn = document.querySelector('.overview .section-left-edit');
|
||||
if(overviewSectionLeftBtn){
|
||||
if(vars.permission.checkPermission('overview-left-edit')){
|
||||
overviewSectionLeftBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
overviewSectionLeftBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 과업개요 가운데 영역 수정 버튼 (부관리자 이상)
|
||||
const overviewSectionMiddleBtn = document.querySelector('.overview .section-middle-edit');
|
||||
if(overviewSectionMiddleBtn){
|
||||
if(vars.permission.checkPermission('overview-middle-edit')){
|
||||
overviewSectionMiddleBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
overviewSectionMiddleBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 과업개요 주요 현안 및 이슈 수정 버튼 (부관리자 이상)
|
||||
const overviewIssueBtn = document.querySelector('.overview .issue-edit');
|
||||
if(overviewIssueBtn){
|
||||
if(vars.permission.checkPermission('overview-issue-edit')){
|
||||
overviewIssueBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
overviewIssueBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 과업개요 - 과업기간 - 변동내역 모달 - 리스트 추가 버튼 (부관리자 이상)
|
||||
const overviewTaskHistoryAddBtn = document.querySelector('.overview-modal .task-history-add');
|
||||
if(overviewTaskHistoryAddBtn){
|
||||
if(vars.permission.checkPermission('overview-task-history-add')){
|
||||
overviewTaskHistoryAddBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
overviewTaskHistoryAddBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 과업개요 - 과업기간 - 변동내역 모달 - 저장 버튼 (부관리자 이상)
|
||||
const overviewTaskHistorySaveBtn = document.querySelector('.overview-modal .task-history-save');
|
||||
if(overviewTaskHistorySaveBtn){
|
||||
if(vars.permission.checkPermission('overview-task-history-save')){
|
||||
overviewTaskHistorySaveBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
overviewTaskHistorySaveBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 과업개요 - 주요일정 - 일정 추가버튼 (일반 참여자 이상)
|
||||
const overviewScheduleAddBtn = document.querySelector('.overview .add-schedule-btn');
|
||||
if(overviewScheduleAddBtn){
|
||||
if(vars.permission.checkPermission('overview-schedule-add')){
|
||||
overviewScheduleAddBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
overviewScheduleAddBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 아카이브 - 메모 - AI버튼 (일반 참여자 이상)
|
||||
const memoAiBtn = document.querySelector('.memo .ai-btn');
|
||||
if(memoAiBtn){
|
||||
if(vars.permission.checkPermission('memo-ai')){
|
||||
memoAiBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
memoAiBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 아카이브 - 메모 - 수정버튼 (일반 참여자 이상)
|
||||
const memoEditBtn = document.querySelector('.memo .edit-btn');
|
||||
if(memoEditBtn){
|
||||
if(vars.permission.checkPermission('memo-edit')){
|
||||
memoEditBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
memoEditBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 폴더 다운로드 버튼 - 관리자 이상
|
||||
const downloadFolderBtn = document.querySelector('.header .download-btn');
|
||||
if(downloadFolderBtn){
|
||||
if(vars.permission.checkPermission('download-folder')){
|
||||
await getMyDownloadList();
|
||||
downloadFolderBtn.style.setProperty('display', 'flex', 'important');
|
||||
} else {
|
||||
downloadFolderBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 프로젝트 step, type, name 변경 - 관리자 이상
|
||||
const projectStepBtn = document.getElementById('project-step-btn');
|
||||
const projectStepCapsule = document.getElementById('project-step-capsule');
|
||||
const projectTypeBtn = document.getElementById('project-type-btn');
|
||||
const projectTypeCapsule = document.getElementById('project-type-capsule');
|
||||
if(projectStepBtn && projectStepCapsule && projectTypeBtn && projectTypeCapsule) {
|
||||
|
||||
if(vars.project.category === 'overseas') {
|
||||
initProjectSetting('overseas');
|
||||
} else if (vars.project.category === 'bimproject') {
|
||||
initProjectSetting('bimproject');
|
||||
} else {
|
||||
initProjectSetting();
|
||||
}
|
||||
|
||||
}
|
||||
// if(projectStepBtn && projectStepCapsule && projectTypeBtn && projectTypeCapsule && (window.location.href.toLowerCase().includes('bim.') || window.location.href.toLowerCase().includes('overseas.'))){
|
||||
// if(projectStepBtn && projectStepCapsule && projectTypeBtn && projectTypeCapsule && (window.location.href.toLowerCase().includes('172.') || window.location.href.toLowerCase().includes('overseas.'))){
|
||||
// initProjectSetting();
|
||||
// }
|
||||
|
||||
//#임시# 프로젝트 step, type 변경 - 관리자 이상
|
||||
//step
|
||||
// const projectStepBtn = document.getElementById('project-step-btn');
|
||||
// const projectStepCapsule = document.getElementById('project-step-capsule');
|
||||
// if(projectStepBtn && projectStepCapsule && (window.location.href.toLowerCase().includes('bim.') || window.location.href.toLowerCase().includes('overseas.'))){
|
||||
// if(vars.permission.checkPermission('change-project-step')) {
|
||||
// projectStepCapsule.remove();
|
||||
// projectStepBtn.innerHTML = `
|
||||
// <h5 class="project-step__label --step__${vars.project.step}">${typeStep2Kor(vars.project.step)}</h5>
|
||||
// <i class="project-step__icon"></i>`;
|
||||
// } else {
|
||||
// projectStepBtn.remove();
|
||||
// projectStepCapsule.innerHTML = typeStep2Kor(vars.project.step);
|
||||
// projectStepCapsule.classList.add(`--step-capsule__${vars.project.step}`);
|
||||
// }
|
||||
// }else{
|
||||
// projectStepCapsule.remove();
|
||||
// projectStepBtn.remove();
|
||||
// }
|
||||
// //type
|
||||
// const projectTypeBtn = document.getElementById('project-type-btn');
|
||||
// const projectTypeCapsule = document.getElementById('project-type-capsule');
|
||||
// if(projectTypeBtn && projectTypeCapsule && window.location.href.toLowerCase().includes('bim.')){
|
||||
// if(vars.permission.checkPermission('change-project-type')) {
|
||||
// projectTypeCapsule.remove();
|
||||
// projectTypeBtn.innerHTML = `
|
||||
// <h5 class="project-type__label --type__${vars.project.project_type}">${typeStep2Kor(vars.project.project_type)}</h5>
|
||||
// <i class="project-type__icon"></i>`;
|
||||
// } else {
|
||||
// projectTypeBtn.remove();
|
||||
// projectTypeCapsule.innerHTML = typeStep2Kor(vars.project.project_type);
|
||||
// }
|
||||
// }else{
|
||||
// projectTypeCapsule.remove();
|
||||
// projectTypeBtn.remove();
|
||||
// }
|
||||
|
||||
// //// 권한이 개발자인 경우
|
||||
// document.querySelectorAll('.permission-min-dev').forEach(elem => {
|
||||
// if (vars.permission.checkPermission('dev-menu')) { // 나중에 개발자 코드 변경될 예정 // Permission Class Test 250621
|
||||
// if (elem.classList.contains('dev-menu-modal')) return; // 개발자 메뉴 모달은 바로 flex로 설정할 필요 없으므로 제외
|
||||
// elem.style.setProperty('display', 'flex', 'important');
|
||||
// } else {
|
||||
// elem.remove();
|
||||
// }
|
||||
// })
|
||||
|
||||
//// 권한이 부관리자 이상인 경우
|
||||
// document.querySelectorAll('.permission-min-sub-master').forEach(elem => {
|
||||
// if (vars.permission.checkPermission('permission-btn') || vars.permission.checkPermission('overview-left-edit')) {
|
||||
// elem.style.setProperty('display', 'flex', 'important');
|
||||
// } else {
|
||||
// elem.remove();
|
||||
// }
|
||||
// })
|
||||
|
||||
|
||||
// //// 권한이 보안참여자 미만인 경우
|
||||
// document.querySelectorAll('.permission-min-security-worker').forEach(elem => {
|
||||
// if (userInfo.permission < 15) {
|
||||
// elem.remove();
|
||||
// } else {
|
||||
// elem.style.setProperty('display', 'flex', 'important');
|
||||
// }
|
||||
// })
|
||||
|
||||
// //// 권한이 일반참여자 미만인 경우
|
||||
// document.querySelectorAll('.permission-min-worker').forEach(elem => {
|
||||
// // 현재 main.html에서 .ai-btn, .edit-btn (메모 ai, 수정 버튼)클래스에 permission-min-worker 추가
|
||||
// if (userInfo.permission < 7) {
|
||||
// elem.remove();
|
||||
// } else {
|
||||
// elem.style.setProperty('display', 'flex', 'important');
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
|
||||
//type, step 한글화
|
||||
export function typeStep2Kor(param){
|
||||
let result = undefined;
|
||||
switch(param){
|
||||
case 'done':
|
||||
result = '완료';
|
||||
break;
|
||||
case 'active':
|
||||
result = '진행';
|
||||
break;
|
||||
case 'stop':
|
||||
result = '중지';
|
||||
break;
|
||||
case 'wait':
|
||||
result = '대기';
|
||||
break;
|
||||
|
||||
case 'construction':
|
||||
result = '시공';
|
||||
break;
|
||||
case 'design':
|
||||
result = '설계';
|
||||
break;
|
||||
case 'surgest':
|
||||
result = '제안';
|
||||
break;
|
||||
case 'research':
|
||||
result = '연구';
|
||||
break;
|
||||
case 'support':
|
||||
result = '지원';
|
||||
break;
|
||||
case 'center':
|
||||
result = '센터';
|
||||
break;
|
||||
case 'survey':
|
||||
result = '측량';
|
||||
break;
|
||||
// case 'ETC':
|
||||
// result = '기타';
|
||||
// break;
|
||||
|
||||
case 'MP':
|
||||
result = 'MP (기본계획)';
|
||||
break;
|
||||
case 'DD':
|
||||
result = 'DD (실시설계)';
|
||||
break;
|
||||
case 'FS':
|
||||
result = 'FS (타당성조사)';
|
||||
break;
|
||||
case 'PD':
|
||||
result = 'PD (기본설계)';
|
||||
break;
|
||||
case 'DS':
|
||||
result = 'DS (설계감리)';
|
||||
break;
|
||||
case 'CS':
|
||||
result = 'CS (시공감리)';
|
||||
break;
|
||||
case 'PMC':
|
||||
result = 'PMC (실시설계)';
|
||||
break;
|
||||
case 'IDC':
|
||||
result = 'IDC (타당성조사)';
|
||||
break;
|
||||
case 'DR':
|
||||
result = 'DR (설계검토)';
|
||||
break;
|
||||
case 'ETC':
|
||||
result = 'ETC (기타)';
|
||||
break;
|
||||
|
||||
default:
|
||||
result = '없음';
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function setTitle() {
|
||||
// 타이틀 카테고리 이미지 및 프로젝트명 설정 후 마르퀴효과 적용
|
||||
// document.querySelector('.header .left .title .marquee-track .category').src = `/main/img/header/header-logo-${vars.project.category}.svg`;
|
||||
let category = '-';
|
||||
let projectNm = '-';
|
||||
if (vars.project) {
|
||||
category = vars.project.category.toUpperCase();
|
||||
projectNm = vars.project.project_nm;
|
||||
}
|
||||
document.querySelector('.header .left .title .marquee-track .category').innerHTML = category;
|
||||
document.querySelector('.header .left .title .marquee-track .project-name').innerHTML = projectNm;
|
||||
if (vars.project) await startMarquee('title');
|
||||
}
|
||||
|
||||
async function componentsInit() {
|
||||
// 헤더 - 프로젝트에 포함되지 않은 계정인 경우 헤더 버튼 삭제
|
||||
// 헤더 - 프로젝트 db에서 과업개요, 공문, 위치기반모델 사용 여부에 따라 헤더 버튼 삭제
|
||||
// 푸터 - 프로젝트에 포함되지 않은 계정인 경우 푸터 저장공간/활동로그 삭제
|
||||
|
||||
// 프로젝트가 비활성화 상태면 리턴
|
||||
if (checkProjectInactive()) return;
|
||||
|
||||
let overviewMain = document.querySelector('.overview-main');
|
||||
let overviewBtn = document.querySelector('.header .overview-btn');
|
||||
let overviewModalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
let officialDocMain = document.querySelector('.official-doc-main');
|
||||
let officialDocBtn = document.querySelector('.header .official-doc-btn');
|
||||
let officialDocModalBackground = document.querySelector('.official-doc-modal-background');
|
||||
let officialDocModalAll = document.querySelectorAll('.official-doc-modal');
|
||||
|
||||
let modelBtn = document.querySelector('.header .model-btn');
|
||||
|
||||
let mainNotice = document.querySelector('.main-notice');
|
||||
|
||||
let userInfo = JSON.parse(vars.userInfoString);
|
||||
if ((userInfo.permission == null && typeof userInfo.permission != 'number')) {
|
||||
// 프로젝트에 포함되어 있지 않은 사용자는 프로젝트 비활성화
|
||||
setProjectInactive('permission');
|
||||
} else {
|
||||
// ** 권한 관련
|
||||
// 참관자 권한일 때 활동로그 삭제
|
||||
// if (userInfo.permission == 1) logWrap.remove();
|
||||
|
||||
// 프로젝트 db에서 과업개요, 공문, 위치기반모델 사용 여부에 따라 버튼 삭제
|
||||
if (vars.project.overview) {
|
||||
overviewBtn.style.display = 'flex';
|
||||
} else {
|
||||
if (overviewMain) overviewMain.remove();
|
||||
if (overviewBtn) overviewBtn.remove();
|
||||
if (overviewModalWrapper) overviewModalWrapper.remove();
|
||||
}
|
||||
|
||||
if (vars.project.official_doc) {
|
||||
officialDocBtn.style.display = 'flex';
|
||||
} else {
|
||||
if (officialDocMain) officialDocMain.remove();
|
||||
if (officialDocBtn) officialDocBtn.remove();
|
||||
if (officialDocModalBackground) officialDocModalBackground.remove();
|
||||
if (officialDocModalAll.length != 0) {
|
||||
officialDocModalAll.forEach(elem => {
|
||||
elem.remove();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (vars.project.gsim) modelBtn.style.display = 'flex';
|
||||
else modelBtn.remove();
|
||||
}
|
||||
}
|
||||
|
||||
async function startMarquee(type) {
|
||||
let trackContainer;
|
||||
if (type == 'title') trackContainer = document.querySelector('body > .header .left .title');
|
||||
if (type == 'bannerNotice') trackContainer = document.querySelector('body > .banner-notice-area .content');
|
||||
|
||||
let originalTrack = trackContainer.querySelector('.marquee-track');
|
||||
if(window.location.origin.includes('gtb.')){
|
||||
// gtb 임시코드
|
||||
// document.querySelectorAll('.marquee-track-wrap .pm, .marquee-track-wrap .symbol, .marquee-track-wrap .category').forEach(e=>e.style.display='none');
|
||||
originalTrack.querySelector('.pm').style.display = 'none';
|
||||
originalTrack.querySelectorAll('.symbol').forEach(e => e.style.display = 'none');
|
||||
originalTrack.querySelector('.category').style.display = 'none';
|
||||
}
|
||||
|
||||
const createMarqueeInstance = async (container, originalTrack) => {
|
||||
const wrap = document.createElement('div');
|
||||
wrap.classList.add('marquee-track-wrap');
|
||||
|
||||
const cloneTrack1 = originalTrack.cloneNode(true);
|
||||
const cloneTrack2 = originalTrack.cloneNode(true);
|
||||
|
||||
wrap.appendChild(originalTrack.cloneNode(true));
|
||||
wrap.appendChild(cloneTrack1);
|
||||
wrap.appendChild(cloneTrack2);
|
||||
|
||||
container.innerHTML = '';
|
||||
container.appendChild(wrap);
|
||||
|
||||
const setMarquee = (entries, wrap) => {
|
||||
const pixelsPerSecond = 40;
|
||||
const firstTrack = wrap.querySelector('.marquee-track:first-child');
|
||||
if (!firstTrack) return;
|
||||
|
||||
const trackWidth = firstTrack.getBoundingClientRect().width;
|
||||
if (trackWidth === 0) return;
|
||||
|
||||
const duration = trackWidth / pixelsPerSecond;
|
||||
|
||||
wrap.style.setProperty('--duration', `${duration}s`);
|
||||
wrap.style.setProperty('--translateX', `-${trackWidth}px`);
|
||||
};
|
||||
|
||||
// 초기 마르퀴 설정
|
||||
// setTimeout(async function () {
|
||||
// await setMarquee(null, wrap);
|
||||
// }, 500);
|
||||
|
||||
// 타이틀/배너 공지 크기 변화 감지되면 다시 마르퀴 설정
|
||||
setTimeout(async function () {
|
||||
let observer = new ResizeObserver(async (entries, observer) => {
|
||||
await setMarquee(entries, wrap);
|
||||
});
|
||||
observer.observe(container);
|
||||
}, 500);
|
||||
|
||||
//// 초기 마르퀴 설정 안해도 정상 작동?
|
||||
};
|
||||
|
||||
createMarqueeInstance(trackContainer, originalTrack);
|
||||
}
|
||||
|
||||
//user정보 저장
|
||||
function save_user() {
|
||||
if (vars.lastHeaderBtn) {
|
||||
let me = JSON.parse(vars.userInfoString);
|
||||
me.project_id = vars.project_id;
|
||||
me.userColor = getRandomColor();
|
||||
me.curPath = vars.lastHeaderBtn.dataset.resourcePath;
|
||||
me.origin = window.location.origin;
|
||||
vars.socket.emit('setUser', { me: me, stat: 'first', origin: window.location.origin});
|
||||
}
|
||||
}
|
||||
|
||||
export function checkProjectInactive() {
|
||||
// vars.project.is_active는 프로젝트가 활성화 되어 있으면 true, 비활성화 되어 있으면 false
|
||||
|
||||
// checkProjectInactive() 함수는 프로젝트가 비활성화 상태인지를 체크하는 함수이므로
|
||||
// 활성화 되어 있으면 true, 비활성화 되어 있으면 false를 리턴해야됨 (vars.project.is_active와 반대)
|
||||
|
||||
// 비활성화 된 프로젝트 배열 안에 현재 프로젝트 아이디가 포함되어 있으면 비활성화 상태(true)로 리턴
|
||||
let result = (vars.project) ? !vars.project.is_active : false;
|
||||
|
||||
// 관리자 계정인 경우 무조건 활성화 상태(false)로 리턴
|
||||
if (JSON.parse(vars.userInfoString).group == 'dev') result = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function toggleBannerNoticeArea() {
|
||||
let userInfo = JSON.parse(vars.userInfoString);
|
||||
let userId = userInfo.user_id;
|
||||
|
||||
// if (userId.includes('bcmf-')) {
|
||||
|
||||
// } else {
|
||||
let bannerNoticeArea = document.querySelector('body > .banner-notice-area');
|
||||
let bannerNoticeContent = bannerNoticeArea.querySelector('.content');
|
||||
let header = document.querySelector('body > .header');
|
||||
let mainAll = document.querySelectorAll('body > .main');
|
||||
|
||||
bannerNoticeArea.style.justifyContent = 'center';
|
||||
bannerNoticeContent.innerHTML = '';
|
||||
|
||||
let textWrap = document.createElement('div');
|
||||
textWrap.classList.add('text-wrap');
|
||||
let text = document.createElement('div');
|
||||
text.classList.add('text');
|
||||
|
||||
textWrap.appendChild(text);
|
||||
bannerNoticeContent.appendChild(textWrap);
|
||||
|
||||
let noticeText = (vars.project) ? vars.project.banner_notice : '';
|
||||
|
||||
if (noticeText == '' || noticeText == undefined || noticeText == null) {
|
||||
bannerNoticeArea.style.display = 'none';
|
||||
text.innerHTML = '';
|
||||
header.style.top = '0';
|
||||
mainAll.forEach(main => {
|
||||
if (userId.includes('bcmf-')) {
|
||||
// bcmf에서 iframe으로 pm 화면 연결하는 경우 헤더, 푸터가 숨겨져 있으므로 높이는 100dvh로, top은 0으로 설정
|
||||
main.style.height = '100dvh';
|
||||
main.style.top = '0';
|
||||
} else {
|
||||
main.style.height = 'calc(100dvh - 2.25rem - 2.25rem)';
|
||||
main.style.top = '2.25rem';
|
||||
}
|
||||
})
|
||||
} else {
|
||||
bannerNoticeArea.style.display = 'flex';
|
||||
text.innerHTML = noticeText;
|
||||
header.style.top = '2.25rem';
|
||||
mainAll.forEach(main => {
|
||||
if (userId.includes('bcmf-')) {
|
||||
// bcmf에서 iframe으로 pm 화면 연결하는 경우 헤더, 푸터가 숨겨져 있으므로 높이에서 banner-notice-area height(2.25rem) 빼고, top은 2.25rem으로 설정
|
||||
main.style.height = 'calc(100dvh - 2.25rem)';
|
||||
main.style.top = '2.25rem';
|
||||
} else {
|
||||
main.style.height = 'calc(100dvh - 2.25rem - 2.25rem - 2.25rem)';
|
||||
main.style.top = '4.5rem';
|
||||
}
|
||||
})
|
||||
|
||||
if (textWrap.getBoundingClientRect().width > bannerNoticeArea.getBoundingClientRect().width - 10) {
|
||||
textWrap.classList.add('marquee-track');
|
||||
bannerNoticeArea.style.justifyContent = 'flex-start';
|
||||
await startMarquee('bannerNotice');
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
export function setProjectInactive(type, text) {
|
||||
// 프로젝트 비활성화 된 경우 초기 프로그레스 종료
|
||||
document.querySelector('.init-progress').style.display = 'none';
|
||||
|
||||
let archiveMain = document.querySelector('.archive-main');
|
||||
|
||||
let permissionModal = document.querySelector('.permission-modal');
|
||||
|
||||
let overviewMain = document.querySelector('.overview-main');
|
||||
let overviewBtn = document.querySelector('.header .overview-btn');
|
||||
let overviewModalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
let officialDocMain = document.querySelector('.official-doc-main');
|
||||
let officialDocBtn = document.querySelector('.header .official-doc-btn');
|
||||
let officialDocModalBackground = document.querySelector('.official-doc-modal-background');
|
||||
let officialDocModalAll = document.querySelectorAll('.official-doc-modal');
|
||||
|
||||
let headerCenterLeft = document.querySelector('.header > .center .left.wrap');
|
||||
let headerCenterRight = document.querySelector('.header > .center .right.wrap');
|
||||
let sizeWrap = document.querySelector('.footer .size-wrap');
|
||||
let logWrap = document.querySelector('.footer .log-wrap');
|
||||
let setUserPermissionBtn = document.querySelector('.archive-modal .set-user-permission-btn');
|
||||
|
||||
let mainNotice = document.querySelector('.main-notice');
|
||||
|
||||
if (archiveMain) archiveMain.remove();
|
||||
|
||||
if (permissionModal) permissionModal.remove();
|
||||
|
||||
if (overviewMain) overviewMain.remove();
|
||||
if (overviewBtn) overviewBtn.remove();
|
||||
if (overviewModalWrapper) overviewModalWrapper.remove();
|
||||
|
||||
if (officialDocMain) officialDocMain.remove();
|
||||
if (officialDocBtn) officialDocBtn.remove();
|
||||
if (officialDocModalBackground) officialDocModalBackground.remove();
|
||||
if (officialDocModalAll.length != 0) {
|
||||
officialDocModalAll.forEach(elem => {
|
||||
elem.remove();
|
||||
})
|
||||
}
|
||||
|
||||
if (headerCenterLeft) headerCenterLeft.remove();
|
||||
if (headerCenterRight) headerCenterRight.remove();
|
||||
if (sizeWrap) sizeWrap.remove();
|
||||
if (logWrap) logWrap.remove();
|
||||
if (setUserPermissionBtn) setUserPermissionBtn.remove();
|
||||
|
||||
let mainNoticeText = '';
|
||||
if (type == 'permission') {
|
||||
// mainNoticeText = `
|
||||
// <div>${JSON.parse(vars.userInfoString).user_nm} 님은 ${vars.project.project_nm} 프로젝트에 포함되어 있지 않습니다.</div>
|
||||
// <div>프로젝트 참가 신청 또는 문의사항은 프로젝트 담당자에게 연락해 주시기 바랍니다.</div>
|
||||
// <div>프로젝트 담당자 : ${vars.project.user_nm} ${vars.project.position}</div>
|
||||
// `;
|
||||
mainNoticeText = `
|
||||
<div>${JSON.parse(vars.userInfoString).user_nm} 님은 프로젝트에 포함되어 있지 않습니다.</div>
|
||||
`;
|
||||
}
|
||||
if (type == 'lock') {
|
||||
if (text) {
|
||||
mainNoticeText = text;
|
||||
} else {
|
||||
mainNoticeText = `
|
||||
<div>프로젝트가 비활성화 상태입니다.</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
mainNotice.style.display = 'flex';
|
||||
mainNotice.innerHTML = mainNoticeText;
|
||||
}
|
||||
|
||||
export function getRandomColor() {
|
||||
let r, g, b;
|
||||
let brightness;
|
||||
|
||||
while (true) {
|
||||
r = Math.floor(Math.random() * 256);
|
||||
g = Math.floor(Math.random() * 256);
|
||||
b = Math.floor(Math.random() * 256);
|
||||
|
||||
// 밝기 계산 (0 ~ 255)
|
||||
brightness = (r * 299 + g * 587 + b * 114) / 1000;
|
||||
|
||||
// 너무 밝거나 어두운 색상 필터링
|
||||
if (brightness > 0 && brightness < 150) {
|
||||
// 각 색상 채널의 차이가 너무 작으면 필터링
|
||||
if (Math.abs(r - g) > 20 || Math.abs(r - b) > 20 || Math.abs(g - b) > 20) {
|
||||
break; // 조건을 만족하면 루프 종료
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 16진수 문자열로 변환
|
||||
const hexR = r.toString(16).padStart(2, '0');
|
||||
const hexG = g.toString(16).padStart(2, '0');
|
||||
const hexB = b.toString(16).padStart(2, '0');
|
||||
|
||||
return `#${hexR}${hexG}${hexB}`;
|
||||
}
|
||||
|
||||
// 임시 스로틀
|
||||
export function throttle(func, limit) {
|
||||
let lastFunc;
|
||||
let lastRan;
|
||||
return function (...args) {
|
||||
const context = this;
|
||||
if (!lastRan) {
|
||||
func.apply(context, args);
|
||||
lastRan = Date.now();
|
||||
} else {
|
||||
clearTimeout(lastFunc);
|
||||
lastFunc = setTimeout(function () {
|
||||
if ((Date.now() - lastRan) >= limit) {
|
||||
func.apply(context, args);
|
||||
lastRan = Date.now();
|
||||
}
|
||||
}, limit - (Date.now() - lastRan));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
window.dtest = async function() {
|
||||
let res = await axios.post(`${vars.path_name}/dtest`);
|
||||
console.log(res.data.message);
|
||||
}
|
||||
window.otest = async function() {
|
||||
let res = await axios.post(`${vars.path_name}/otest`);
|
||||
console.log(res.data.message);
|
||||
}
|
||||
1060
views/main/jsm/officialDoc/docDataManager.js
Normal file
1060
views/main/jsm/officialDoc/docDataManager.js
Normal file
File diff suppressed because it is too large
Load Diff
1094
views/main/jsm/officialDoc/docModalManager.js
Normal file
1094
views/main/jsm/officialDoc/docModalManager.js
Normal file
File diff suppressed because it is too large
Load Diff
1451
views/main/jsm/officialDoc/docPageRenderer.js
Normal file
1451
views/main/jsm/officialDoc/docPageRenderer.js
Normal file
File diff suppressed because it is too large
Load Diff
44
views/main/jsm/officialDoc/docSocketManager.js
Normal file
44
views/main/jsm/officialDoc/docSocketManager.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { vars } from '../archive/variable.js';
|
||||
import { docVars } from './docVariable.js';
|
||||
import { renderDocViewer } from './docPageRenderer.js';
|
||||
|
||||
let socket = vars.socket;
|
||||
|
||||
// 변환시작, 변환완료 등 상태에 따른 실시간 UI 변경 처리는 socket.on에서 처리
|
||||
// 서버에 현재 변환중인 파일 경로를 보내는 등 로직 처리는 working
|
||||
|
||||
//// 변환 시작
|
||||
socket.on('convertDoc_start', (resultData) => {
|
||||
let resourcePath = resultData.resourcePath;
|
||||
|
||||
let state = document.querySelector(`.attach-item[data-resource-path="${resourcePath}"] .state`);
|
||||
if (!state) return;
|
||||
state.classList.remove('convert');
|
||||
state.classList.add('working');
|
||||
|
||||
let convertBtnText = state.querySelector('.convert-btn-text');
|
||||
if (convertBtnText) convertBtnText.innerHTML = '변환중';
|
||||
});
|
||||
|
||||
//// 변환 완료
|
||||
socket.on('convertPdf_success', async (resultData) => {
|
||||
let resourcePath = resultData.resourcePath;
|
||||
|
||||
docVars.convertingPathArr = resultData.convertingPathArr;
|
||||
|
||||
let state = document.querySelector(`.attach-item[data-resource-path="${resourcePath}"] .state`);
|
||||
if (!state) return;
|
||||
state.classList.remove('working');
|
||||
state.classList.add('viewable');
|
||||
|
||||
let stateText = state.querySelector('.state-text');
|
||||
if (stateText) stateText.innerHTML = '열람가능';
|
||||
|
||||
let convertBtn = state.querySelector('.convert-btn');
|
||||
if (convertBtn) convertBtn.remove();
|
||||
|
||||
if (docVars.lastClickedListTarget) {
|
||||
let lastClickedListTargetPath = docVars.lastClickedListTarget.dataset.resourcePath;
|
||||
if (resourcePath == lastClickedListTargetPath) renderDocViewer(resourcePath, resultData.dataId);
|
||||
}
|
||||
});
|
||||
63
views/main/jsm/officialDoc/docVariable.js
Normal file
63
views/main/jsm/officialDoc/docVariable.js
Normal file
@@ -0,0 +1,63 @@
|
||||
let PathNameWindow = decodeURIComponent(window.location.pathname); // /gsimteam/archive
|
||||
let path_name = PathNameWindow.replace('archive', 'officialDoc'); // /gsimteam/officialDoc
|
||||
let project_id = PathNameWindow.split('/')[1]; // gsimteam
|
||||
|
||||
export const docVars = {
|
||||
// 라우터 연결을 위한 변수
|
||||
path_name: path_name,
|
||||
|
||||
// 프로젝트 id
|
||||
project_id: project_id,
|
||||
|
||||
// 오브젝트 스토리지 타입 (ONPREMISE / CLOUD)
|
||||
storageType: undefined,
|
||||
|
||||
// 버킷 (프로젝트 id와 동일)
|
||||
bucket: project_id,
|
||||
|
||||
// 마지막으로 클릭한 타켓
|
||||
lastClickedListTarget: undefined,
|
||||
|
||||
// 마지막으로 우클릭한 타겟 -> 컨텍스트 메뉴에 사용
|
||||
lastContextTarget: undefined,
|
||||
|
||||
// 로그 데이터 -> 활동 정보 activity-info-item(로그 데이터)에 사용 (활동 정보 아이템 클릭 시 상세 정보 표시에 사용)
|
||||
logData: undefined,
|
||||
|
||||
// 변환중인 파일 경로를 저장하는 배열
|
||||
workingFileArr: [],
|
||||
|
||||
// 미리보기 뷰어
|
||||
viewer: undefined,
|
||||
|
||||
// 수신/발신
|
||||
direction: undefined,
|
||||
allRecipientListByDirection: undefined,
|
||||
allSendingListByDirection: undefined,
|
||||
|
||||
// 파일 리스트 sort용 현재 컬럼, 현재 정렬순
|
||||
curSortCol : 'number',
|
||||
curSortOrder : 'asc',
|
||||
|
||||
//
|
||||
allDocData: [],
|
||||
allDocOfficialData: [],
|
||||
allDocAttachData: [],
|
||||
|
||||
currentDroppedFiles: [],
|
||||
currentDocId: null,
|
||||
currentGroupId: null,
|
||||
|
||||
selectedDoc: null,
|
||||
selectedAttachItem: null,
|
||||
|
||||
selectParams: {
|
||||
typeOptions: '', // depth1: 발주처/외
|
||||
baseOptions: '', // depth2: 기준
|
||||
targetOptions: '', // depth3: 상대기관
|
||||
},
|
||||
groupCompanyData: {},
|
||||
originCompanyList: [],
|
||||
};
|
||||
|
||||
// window.docVars = docVars;
|
||||
12
views/main/jsm/officialDoc/index.js
Normal file
12
views/main/jsm/officialDoc/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as docSocketManager from './docSocketManager.js';
|
||||
import { initDocSlider, initCategoryClicked, initCustomSelectBoxes, activateSelectUI, showInstructions } from './docPageRenderer.js';
|
||||
|
||||
// 셀렉트박스 클릭 이벤트
|
||||
await initCustomSelectBoxes();
|
||||
activateSelectUI();
|
||||
// 카테고리 클릭 이벤트
|
||||
initCategoryClicked();
|
||||
// 화면 위아래 슬라이드
|
||||
initDocSlider();
|
||||
// 설정모달: 초기화면 도움말
|
||||
showInstructions();
|
||||
21
views/main/jsm/officialDoc/prompt/250425.txt
Normal file
21
views/main/jsm/officialDoc/prompt/250425.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
반드시 아래 지침을 따를 것:
|
||||
- **공문에 실제로 쓰인 텍스트만** 사용해. 추정 금지.
|
||||
- 모르면 "확인필요", 없으면 "없음"이라고 써.
|
||||
- 한글로 쓰라고 한 항목은 반드시 한글로 써.
|
||||
|
||||
1. 공문 번호: Ref. No. (reference no.는 12번 항목)
|
||||
2. 공문 일자: 날짜
|
||||
3. 수신처: 대표 기관 이름 하나만 (예:Seoyoung Engineering Co., Ltd)
|
||||
4. 수신자: 받는 사람 직책 (예: Project Director)
|
||||
5. 수신자 약자: (예: Project Director → PD)
|
||||
6. 발신처: 대표 기관 이름
|
||||
7. 발신자: 보낸 사람 직책(예: Team Leader)
|
||||
8. 발신자 약자: (예: Team Leader → TL)
|
||||
9. 공문 제목: Subject 가장 위에 한 줄만 (bold체)
|
||||
10. 공문 제목 요약: 한글로 10~20자
|
||||
11. 공문 내용 요약: 한글로 간단하게
|
||||
12. 공문간 연계: 관련된 다른 공문의 번호 (reference no.)
|
||||
13. 공문 종류: [행정/일반:(직원, 파견, 동원, 조직, 비용, 계약 등), 기술/성과물:(일정 협의, 작업계획, 성과물 제출, 기술적 업무 회의, 성과물 전달 등), 회의/기타:(회의록 등 위에 내용 이외의 것) 중 하나
|
||||
14. 공문 유형: [보고:사실과 계획 일방적으로 통보, 요청:회신 또는 행동 부탁, 지시:권한 있는 주체가 수행을 명령, 회신:기존 공무에 대해 응답하거나 의견을 제공, 계약:계약조건 변경과 관련] 중 하나
|
||||
15. 첨부문서 제목: Enclosures: 목록 전체
|
||||
16. 첨부문서 수: 숫자만
|
||||
33
views/main/jsm/officialDoc/prompt/default_prompt.txt
Normal file
33
views/main/jsm/officialDoc/prompt/default_prompt.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
아래 문서에서 정보를 추출하여 JSON 객체로만 출력해주세요.
|
||||
- 절대 ```json 등 코드블록으로 감싸지 마세요.
|
||||
- 불필요한 설명 금지, 추론 금지, 파일에 있는 내용만 사용
|
||||
- 값이 없는 항목은 "없음"으로 표기
|
||||
- 숫자 값은 숫자 그대로
|
||||
- 결과값을 **일반적인 JSON 형식**으로 그대로 출력해주세요.
|
||||
- 이스케이프 문자 (예: \", \n 등)는 사용하지 말고, 결과값은 **그대로** JSON 형태로 출력해주세요.
|
||||
{
|
||||
1. "공문번호": 공문 번호로 Ref. No.를 의미합니다. 없는 경우는 없음으로 표기해주세요. (예시: "Ref. No. SYJV-250031")
|
||||
2. "공문일자": 공문에 적혀 있는 날짜입니다. 번역하지 않고 그대로 표기해주세요. (예시: "Mar / 28 / 2025")
|
||||
3. "수신처": 공문을 받는 사람이 속한 조직명 (예시: "Department of Public Works and Highways")
|
||||
4. "수신자": 공문을 받은 사람의 직책 (예시: "Project Director")
|
||||
5. "수신자약자": 수신자 직책 약자 (예시: "PD")
|
||||
6. "발신처": 공문을 보낸 사람이 속한 조직명 (예시: "SEOYOUNG JOINT VENTURE")
|
||||
7. "발신자": 공문을 보낸 사람의 직책 (예시: "Team Leader")
|
||||
8. "발신자약자": 발신자 직책 약자 (예시: "TL")
|
||||
9. "공문제목": 공문의 제목으로 SUBJECT 의미합니다. 적당한 길이로 끊어야 하는데 윗 문장이 프로젝트 이름으로 판단되는 경우, 9.1 프로젝트 항목을 신설해 리턴 (예시: "Submission of Comment Matrix for Design Deliverable")
|
||||
10. "공문제목요약": 공문 제목을 10~20자 사이로 요약해주세요. 반드시 한글로 작성합니다.
|
||||
11. "공문내용요약": 공문 내용을 요약해주세요. 반드시 한글로 작성합니다.
|
||||
12. "공문간연계": 연계된 공문이 있으면 공문번호를 알려주세요. 공문번호만 필요합니다. 없는 경우는 없음으로 표기해주세요.
|
||||
13. "공문종류": 공문 종류는 공문의 내용을 분석해서 다음 3가지 중 반드시 하나를 선택합니다.
|
||||
* 행정/일반 – 인사, 파견, 조직, 비용(예산), 계약 등 경영/행정 관련
|
||||
* 기술/성과물 – 일정 협의, 작업계획, 성과물 제출, 기술적 업무 회의, 성과물 전달 등
|
||||
* 회의/기타 – 회의록 등 위에 내용 이외의 것
|
||||
14. "공문유형": 공문 유형은 공문의 내용을 분석해서 다음의 5가지 중 반드시 하나를 선택합니다.
|
||||
* 보고 : 완료된 사실이나 계획을 일방적으로 알리는 공문
|
||||
* 요청 : 상대방의 행동 또는 답변을 유도하는 공문
|
||||
* 지시 : 권한 있는 주체가 수행을 명령하는 공문
|
||||
* 회신 : 기존 공무에 대해 응답하거나 의견을 제공하는 공문
|
||||
* 계약 : 계약조건 변경과 관련된 공식 공문
|
||||
15. "첨부문서제목": 공문의 첨부 문서는 Enclosures: 를 의미합니다. 없는 경우는 없음으로 표기해주세요. (예시: "1. Comment Matrix_4.4.2 Draft Detailed Engineer Design Report (Section A)" )
|
||||
16. "첨부문서수": 찾은 첨부문서 개수를 알려주세요.
|
||||
}
|
||||
33
views/main/jsm/officialDoc/prompt/default_schema.json
Normal file
33
views/main/jsm/officialDoc/prompt/default_schema.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"title": "DocumentSummary",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"공문번호": { "type": "string" },
|
||||
"공문일자": { "type": "string" },
|
||||
"수신처": { "type": "string" },
|
||||
"수신자": { "type": "string" },
|
||||
"수신자약자": { "type": "string" },
|
||||
"발신처": { "type": "string" },
|
||||
"발신자": { "type": "string" },
|
||||
"발신자약자": { "type": "string" },
|
||||
"공문제목": { "type": "string" },
|
||||
"공문제목요약": { "type": "string" },
|
||||
"공문내용요약": { "type": "string" },
|
||||
"공문간연계": { "type": "string" },
|
||||
"공문종류": {
|
||||
"type": "string",
|
||||
"enum": ["행정/일반", "기술/성과물", "회의/기타"]
|
||||
},
|
||||
"공문유형": {
|
||||
"type": "string",
|
||||
"enum": ["보고", "요청", "지시", "회신", "계약"]
|
||||
},
|
||||
"첨부문서제목": { "type": "string" },
|
||||
"첨부문서수": { "type": "integer" }
|
||||
},
|
||||
"required": [
|
||||
"공문번호", "공문일자", "수신처", "수신자", "수신자약자",
|
||||
"발신처", "발신자", "발신자약자", "공문제목", "공문제목요약",
|
||||
"공문내용요약", "공문간연계", "공문종류", "공문유형", "첨부문서제목", "첨부문서수"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
주의:
|
||||
- **정확성이 매우 중요합니다.**
|
||||
- **19.1 SDZR/AERA/RCM** 형태는 공문 번호가 아닙니다.
|
||||
- 한글로 쓰라고 한 항목은 반드시 한글로 작성
|
||||
- 반드시 아래 **1~13번 항목만** 출력
|
||||
- 모르면 "확인필요", 없으면 "없음"이라고 작성
|
||||
- 결과값을 **일반적인 JSON 형식**으로 그대로 출력해주세요
|
||||
- 절대 ```json 등 코드블록으로 감싸지 마세요.
|
||||
|
||||
1. "공문번호": "영문-숫자" 형태
|
||||
2. "공문일자": YYYY-MM-DD
|
||||
3. "수신자": 수신자의 직책
|
||||
4. "수신자약자": 수신자 직책의 약자
|
||||
5. "발신자": 발신자의 직책 (예: Project Director), 조직명은 제외
|
||||
6. "발신자약자": 발신자 직책의 약자
|
||||
7. "공문제목": Subject **독립된 첫 구문만**, 프로젝트 이름/설명 등 부연 문장은 제외
|
||||
8. "공문제목요약": **한글로** 10~20자
|
||||
9. "공문내용요약": **한글로** 간단하게
|
||||
10. "공문간연계": 연계된 공문이 있으면 "있음", 없으면 "없음"
|
||||
11. "공문종류": '행정/일반', '기술/성과물', ‘회의/기타’ 중 하나
|
||||
12. "공문유형": 보고, 요청, 지시, 회신, 계약 중 하나
|
||||
13. "첨부문서수": 숫자만
|
||||
@@ -0,0 +1,27 @@
|
||||
주의:
|
||||
- **정확성이 매우 중요합니다.**
|
||||
- **19.1 SDZR/AERA/RCM** 형태는 공문 번호가 아닙니다.
|
||||
- 한글로 쓰라고 한 항목은 반드시 한글로 작성
|
||||
- 반드시 아래 13개 항목명만 키로 사용:
|
||||
"공문번호", "공문일자", "수신자", "수신자약자", "발신자", "발신자약자", "공문제목", "공문제목요약", "공문내용요약", "공문간연계", "공문종류", "공문유형", "첨부문서수"
|
||||
- 키 값(항목명)에 숫자나 마크 추가 금지. 반드시 순수 텍스트(한글/영문)만 사용.
|
||||
- 모르면 "확인필요", 없으면 "없음"이라고 작성
|
||||
- 결과값을 **일반적인 JSON 형식**으로 그대로 출력해주세요
|
||||
- 절대 ```json 등 코드블록으로 감싸지 마세요.
|
||||
- 잘못된 형식이 나오면 위 조건에 맞는 JSON으로 다시 출력하세요.
|
||||
|
||||
{
|
||||
"공문번호": "영문-숫자" 형태
|
||||
"공문일자": YYYY-MM-DD
|
||||
"수신자": 수신자의 직책
|
||||
"수신자약자": 수신자 직책의 약자
|
||||
"발신자": 발신자의 직책 (예: Project Director), 조직명은 제외
|
||||
"발신자약자": 발신자 직책의 약자
|
||||
"공문제목": Subject **독립된 첫 구문만**, 프로젝트 이름/설명 등 부연 문장은 제외
|
||||
"공문제목요약": **한글로** 10~20자
|
||||
"공문내용요약": **한글로** 간단하게
|
||||
"공문간연계": 연계된 공문이 있으면 "있음", 없으면 "없음"
|
||||
"공문종류": '행정/일반', '기술/성과물', ‘회의/기타’ 중 하나
|
||||
"공문유형": 보고, 요청, 지시, 회신, 계약 중 하나
|
||||
"첨부문서수": 숫자만
|
||||
}
|
||||
9
views/main/jsm/overview/index.js
Normal file
9
views/main/jsm/overview/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getData } from './overviewDataManager.js';
|
||||
|
||||
await getData();
|
||||
|
||||
// overviewCommon.js : 아래에 있는 js에서 사용하는 함수들을 모아놓은 js
|
||||
// overviewDataManager.js : 과업개요 db 통신 추가/수정/삭제
|
||||
// overviewModalManager.js : 과업개요 모달 인터랙션 js
|
||||
// overviewPageRenderer.js : 과업개요 페이지를 그리는 랜더링 js
|
||||
// overviewVariable.js : 과업개요에서 사용 되는 전역변수 js
|
||||
514
views/main/jsm/overview/overviewCommon.js
Normal file
514
views/main/jsm/overview/overviewCommon.js
Normal file
@@ -0,0 +1,514 @@
|
||||
import { vars } from '../archive/variable.js';
|
||||
import { overviewVars } from './overviewVariable.js';
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 공용 함수 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// 공동도급 형식 자르기
|
||||
export function splitStr(data) {
|
||||
if (data === null || data === undefined) return;
|
||||
|
||||
if (data.includes('^&')) {
|
||||
const splitStr = data.split('^&');
|
||||
return splitStr;
|
||||
}
|
||||
}
|
||||
|
||||
// 날짜 포맷팅
|
||||
export function sliceDate(date){
|
||||
if(!date) return null;
|
||||
|
||||
const formattedDate = date.slice(0,4) + '-' + date.slice(4,6) + '-' + date.slice(6,8);
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
export function overviewVarsInit() {
|
||||
if(overviewVars.sectionTabData)overviewVars.sectionTabData = JSON.parse(JSON.stringify(overviewVars.originalSectionTabData))
|
||||
overviewVars.deleteTaskHistory = [];
|
||||
if (!arraysEqual(overviewVars.filesArr, overviewVars.originalFilesArr)) overviewVars.filesArr = [...overviewVars.originalFilesArr];
|
||||
if (!arraysEqual(overviewVars.filesSizeArr, overviewVars.originalFilesSizeArr))overviewVars.filesSizeArr = [...overviewVars.originalFilesSizeArr];
|
||||
if (!arraysEqual(overviewVars.filesNameArr, overviewVars.originalFilesNameArr)) overviewVars.filesNameArr = [...overviewVars.originalFilesNameArr];
|
||||
|
||||
overviewDropboxInit();
|
||||
}
|
||||
|
||||
function arraysEqual(a, b) {
|
||||
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
return a.every((value, index) => value === b[index]);
|
||||
}
|
||||
|
||||
export function setOverviewType(){
|
||||
// overseas분기용
|
||||
if(!window.location.host.toLowerCase().includes('overseas.')){
|
||||
document.querySelectorAll('.only-overseas').forEach(ele => {
|
||||
ele.remove();
|
||||
})
|
||||
overviewVars.overseas = false;
|
||||
} else {
|
||||
document.querySelectorAll('.no-overseas').forEach(ele => {
|
||||
ele.remove();
|
||||
});
|
||||
overviewVars.overseas = true;
|
||||
}
|
||||
}
|
||||
//🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 공용 함수 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Section-Left 사용 함수 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// 이미지 중복 이름, 확장자 체크 함수
|
||||
export function checkDuplicatesFileNameAndExt(files){
|
||||
const duplicateNameArr = [];
|
||||
const notAllowExtArr = [];
|
||||
const acceptExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg']
|
||||
files.forEach(file => {
|
||||
const fileExt = file.name.split('.').pop().toLowerCase();
|
||||
if(!acceptExt.includes(fileExt)){
|
||||
notAllowExtArr.push(file);
|
||||
return;
|
||||
}
|
||||
|
||||
const isDuplicateFile = overviewVars.filesArr.some(f => f.name === file.name);
|
||||
const isDuplicateFileName = overviewVars.filesNameArr.includes('overview/' + file.name);
|
||||
const isDuplicateFileSize = overviewVars.filesSizeArr.some(f => f.size === file.size);
|
||||
if(!isDuplicateFile && !isDuplicateFileName && !isDuplicateFileSize){
|
||||
overviewVars.filesArr.push(file);
|
||||
overviewVars.filesNameArr.push(file.name);
|
||||
if(overviewVars.filesSizeArr[0] === 0 ) overviewVars.filesSizeArr = [];
|
||||
overviewVars.filesSizeArr.push(file.size);
|
||||
} else {
|
||||
duplicateNameArr.push(file);
|
||||
}
|
||||
});
|
||||
|
||||
if(duplicateNameArr.length > 0 || notAllowExtArr.length > 0){
|
||||
alert(`다음 확장자만 등록할 수 있습니다 : \n- ${acceptExt}\n \n` + '그 외에 확장자 또는 중복된 파일은 등록할 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
// DropBox에 파일 리스트 생성 함수
|
||||
export function displayFileName(){
|
||||
const dropbox = document.querySelector('.overview-modal.section-left .dropbox');
|
||||
if(overviewVars.filesNameArr.length > 0 ){
|
||||
dropbox.innerHTML = '';
|
||||
dropbox.style.backgroundColor = '#fff'
|
||||
dropbox.style.alignItems = 'stretch';
|
||||
dropbox.style.justifyContent = 'normal';
|
||||
}
|
||||
|
||||
overviewVars.filesNameArr.forEach(file => {
|
||||
const originalName = file;
|
||||
const displayName = file.replace('overview/', '');
|
||||
const wrapDiv = document.createElement('div');
|
||||
wrapDiv.classList.add('file-wrap-div');
|
||||
wrapDiv.dataset.filepath = file;
|
||||
const iconImg = document.createElement('img');
|
||||
iconImg.src = '/main/img/overview/icon-imageFile.svg';
|
||||
iconImg.classList.add('dropbox-file-img');
|
||||
const fileName = document.createElement('h3');
|
||||
fileName.innerText = displayName;
|
||||
fileName.style.width = '95%';
|
||||
const button = document.createElement('button');
|
||||
button.classList.add('xs-btn');
|
||||
button.addEventListener('click', async ()=> {
|
||||
const fileIndex = overviewVars.filesArr.findIndex(f => f.name === displayName);
|
||||
if(fileIndex !== -1) overviewVars.filesArr.splice(fileIndex, 1);
|
||||
const nameIndex = overviewVars.filesNameArr.findIndex(f => f === file);
|
||||
// 배열 삭제 전 지워야될 key 값 저장
|
||||
let key = overviewVars.filesNameArr[nameIndex];
|
||||
if(nameIndex !== -1){
|
||||
overviewVars.filesNameArr.splice(nameIndex, 1);
|
||||
overviewVars.filesSizeArr.splice(nameIndex, 1);
|
||||
}
|
||||
// object-storage에 저장된 file만 s3 api 이용해서 삭제
|
||||
if(document.querySelector(`.overview .swiper-slide[data-filepath="${file}"]`)){
|
||||
overviewVars.deleteImgArr.push(key);
|
||||
}
|
||||
overviewDropboxInit()
|
||||
wrapDiv.remove();
|
||||
|
||||
});
|
||||
const buttonimg = document.createElement('img');
|
||||
buttonimg.src = '/main/img/overview/icon-close-111.svg';
|
||||
|
||||
button.appendChild(buttonimg);
|
||||
wrapDiv.append(iconImg, fileName, button);
|
||||
dropbox.appendChild(wrapDiv);
|
||||
});
|
||||
}
|
||||
|
||||
export function overviewDropboxInit(){
|
||||
if(overviewVars.filesNameArr.length === 0){
|
||||
const dropbox = document.querySelector('.overview-modal.section-left .dropbox');
|
||||
dropbox.innerHTML = `<div class="dropbox-top">
|
||||
<img class="icon" src="/main/img/overview/icon-imageFile.svg"
|
||||
alt="icon-dropfile">
|
||||
<p>
|
||||
여기로 파일을 드래그하거나 <br>
|
||||
<strong style="border-bottom: 0.063rem solid #777;">우측 상단에서 이미지 파일을
|
||||
업로드하세요.</strong>
|
||||
</p>
|
||||
</div>`
|
||||
dropbox.style.justifyContent = 'center';
|
||||
dropbox.style.alignItems = 'center';
|
||||
dropbox.style.backgroundColor = '#eee';
|
||||
}
|
||||
}
|
||||
|
||||
// 접근용 presignedUrl 발급
|
||||
export async function getPresignedURL(key) {
|
||||
const res = await axios.get(`/${vars.project_id}/overview/generateGetImgUrl`, { params: { key: key } });
|
||||
const url = res.data;
|
||||
return url;
|
||||
};
|
||||
|
||||
// 삭제용 presignedUrl 발급 이후에 minIO 삭제요청
|
||||
export async function generateDeleteImgUrl(key) {
|
||||
if (key === undefined) return;
|
||||
|
||||
try {
|
||||
const res = await axios.delete(`/${vars.project_id}/overview/generateDeleteImgUrl`, { data: { key } });
|
||||
let { url } = res.data;
|
||||
await axios.delete(url)
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
// 업로드용 presignedURL 발급
|
||||
export async function uploadImgData(file) {
|
||||
try {
|
||||
const presignedUrlRes = await axios.post(`/${vars.project_id}/overview/generateUploadUrl`, { fileName: file.name });
|
||||
|
||||
let { url, key } = presignedUrlRes.data;
|
||||
|
||||
const bucketRes = await axios.put(url, file, { headers: { 'Content-type': file.type || 'application/octet-stream' } });
|
||||
|
||||
if (bucketRes.status == 200) {
|
||||
return key;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('이미지 업로드 실패: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
//🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Section-Left 사용 함수 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Section-Middle 사용 함수 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// 공동도급표 구분자 추가 함수
|
||||
export function addJointContractDelimiter() {
|
||||
let representativeCompany = document.querySelector('.overview-modal .joint-contract-company-name')?.innerText;
|
||||
let companyName = '';
|
||||
document.querySelectorAll('.overview-modal .joint-contract-company-name').forEach((name) => {
|
||||
companyName += name.innerText.trim().replace(/,/g, '') + '^&';
|
||||
if (companyName === '^&') companyName = '';
|
||||
});
|
||||
|
||||
let companyShares = '';
|
||||
document.querySelectorAll('.overview-modal .joint-contract-company-shares').forEach((shares) => {
|
||||
companyShares += shares.innerText.trim().replace(/,/g, '') + '^&';
|
||||
if (companyShares === '^&') companyShares = '';
|
||||
});
|
||||
|
||||
let contractKrw = '';
|
||||
document.querySelectorAll('.overview-modal .joint-contract-krw').forEach((krw) => {
|
||||
contractKrw += krw.innerText.trim().replace(/,/g, '') + '^&';
|
||||
if (contractKrw === '^&') contractKrw = '';
|
||||
});
|
||||
|
||||
let contractUsd = '';
|
||||
document.querySelectorAll('.overview-modal .joint-contract-usd').forEach((usd) => {
|
||||
contractUsd += usd.innerText.trim().replace(/,/g, '') + '^&';
|
||||
if (contractUsd === '^&') contractUsd = '';
|
||||
});
|
||||
|
||||
return { representativeCompany, companyName, companyShares, contractKrw, contractUsd }
|
||||
}
|
||||
|
||||
// 공동도급 문자열 입력 제한 함수
|
||||
export function restrictToNumber(element, event) {
|
||||
let text = element.innerText;
|
||||
|
||||
const shares = element.classList.contains('joint-contract-company-shares');
|
||||
const payment = element.classList.contains('joint-contract-krw') || element.classList.contains('joint-contract-usd');
|
||||
|
||||
// 지분율 소수점 허용
|
||||
if (shares) {
|
||||
text = text.replace(/[^0-9.]/g, '');
|
||||
// 소수점 중복 제한 (숫자, .)
|
||||
const parts = text.split('.');
|
||||
let integerPart = parts[0].replace(/^0+(?=\d)/,'');
|
||||
if(integerPart === '') integerPart = '0'
|
||||
if (parts.length > 1) {
|
||||
text = integerPart + '.' + parts.slice(1).join('');
|
||||
} else {
|
||||
text = integerPart;
|
||||
}
|
||||
element.innerText = text;
|
||||
} else {
|
||||
// 문자열 제한 (오직 숫자만)
|
||||
text = text.replace(/[^0-9]/g, '');
|
||||
}
|
||||
// 계약금 , 생성
|
||||
if (payment) {
|
||||
//계약금 ,(콤마) 생성
|
||||
if (isNaN(parseFloat(text))) {
|
||||
element.innerText = '';
|
||||
} else {
|
||||
element.innerText = parseFloat(text).toLocaleString();
|
||||
}
|
||||
|
||||
if (!(event.data >= '0' && event.data <= '9')) showToolTip(element, '숫자만 입력 가능합니다.');
|
||||
} else {
|
||||
element.innerText = text;
|
||||
if (!((event.data >= '0' && event.data <= '9') || event.data == '.')) showToolTip(element, '숫자와 .만 입력 가능합니다.');
|
||||
}
|
||||
|
||||
|
||||
// 커서 맨끝으로 이동시키기
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
range.selectNodeContents(element);
|
||||
range.collapse(false);
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
|
||||
// 문자열 입력했을때 툴팁
|
||||
export function showToolTip(element, message) {
|
||||
let tooltip = document.querySelector('.overview-modal-tooltip');
|
||||
|
||||
if (!tooltip) {
|
||||
tooltip = document.createElement('div');
|
||||
tooltip.className = 'overview-modal-tooltip';
|
||||
document.body.appendChild(tooltip);
|
||||
}
|
||||
|
||||
if (tooltip.dataset.active === 'true') return;
|
||||
|
||||
tooltip.textContent = message;
|
||||
tooltip.style.opacity = '0.8';
|
||||
tooltip.style.visibility = 'visible';
|
||||
tooltip.dataset.active = 'true';
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
tooltip.style.left = `${rect.left + window.scrollX + (rect.width / 2) - (tooltip.offsetWidth / 2)}px`;
|
||||
tooltip.style.top = `${rect.bottom + window.scrollY + 5}px`;
|
||||
|
||||
setTimeout(() => {
|
||||
tooltip.style.opacity = '0';
|
||||
tooltip.style.visibility = 'hidden';
|
||||
tooltip.dataset.active = 'false'
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 공동도급 지분율,계약금 계산
|
||||
export function calculateTotalJointContract(element) {
|
||||
let totalValue = 0;
|
||||
let className = element.className;
|
||||
|
||||
if (className === 'joint-contract-company-name' || className === 'total-joint-contract-company-name' || className === 'total-joint-contract-company-shares' || className === 'total-joint-contract-krw'|| className === 'total-joint-contract-usd' || className === 'sticky') return;
|
||||
|
||||
document.querySelectorAll(`.overview-modal .${className}`).forEach(joint => {
|
||||
let value = parseFloat(joint.innerText.replaceAll(',', '').trim());
|
||||
if (isNaN(value)) value = 0;
|
||||
totalValue += value;
|
||||
});
|
||||
|
||||
if (isNaN(totalValue)) totalValue = 0;
|
||||
|
||||
if (className === 'joint-contract-company-shares') {
|
||||
// 지분율 100%를 넘겼을 때 강제로 남은 지분율만큼만 입력되도록
|
||||
if(totalValue > 100){
|
||||
const excess = totalValue - 100;
|
||||
let current = parseFloat(element.innerText);
|
||||
element.innerText = current-excess;
|
||||
document.querySelector(`.overview-modal .total-${className}`).innerText = 100;
|
||||
showToolTip(element, '지분율은 100%를 넘길 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
document.querySelector(`.overview-modal .total-${className}`).innerText = totalValue;
|
||||
} else {
|
||||
document.querySelector(`.overview-modal .total-${className}`).innerText = totalValue.toLocaleString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function restrictDuplicatedTabName(){
|
||||
// 중복 탭 이름 제한
|
||||
const tabs = document.querySelectorAll('.overview-modal .section-tabs .tab');
|
||||
const tabValues = [];
|
||||
for(let tab of tabs){
|
||||
const input = tab.querySelector('input');
|
||||
const val = input ? input.value.trim() : '';
|
||||
|
||||
if(tabValues.includes(val)){
|
||||
alert('중복된 탭 이름이 있습니다.')
|
||||
return false;
|
||||
}
|
||||
tabValues.push(val);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Section-Middle 사용 함수 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Section-Right 사용 함수 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
export function formatHour(hour) {
|
||||
const normalized = (hour + 24) % 24;
|
||||
const h = Math.floor(normalized); // 정수 시간
|
||||
const m = Math.round((normalized - h) * 60); // 소수점 -> 분
|
||||
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
export function parseTimeToDecimal(timeStr) {
|
||||
const [h, m] = timeStr.split(':').map(Number);
|
||||
return h + (m / 60);
|
||||
}
|
||||
|
||||
// 1시간당 1.75rem 기준으로 위치 계산
|
||||
export function calculateRemPosition(currentTime) {
|
||||
const currentHour = parseInt(currentTime.split(':')[0]);
|
||||
const currentMinute = parseInt(currentTime.split(':')[1]);
|
||||
const hourRem = 1.75
|
||||
const minuteRem = 1.75 / 60;
|
||||
|
||||
const moveHour = currentHour * hourRem;
|
||||
const moveMinute = currentMinute * minuteRem;
|
||||
// left 0으로 놓았을때 00:00에 위치하도록 보정
|
||||
const moveRem = moveHour + moveMinute - 3.5;
|
||||
|
||||
return moveRem
|
||||
}
|
||||
|
||||
// 타임존 계산을 통해 어느 장소에서든 동일한 결과값을 보장
|
||||
export function getTimeToTimeZone(timeZone) {
|
||||
const now = new Date();
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: timeZone
|
||||
});
|
||||
|
||||
return formatter.format(now);
|
||||
}
|
||||
|
||||
// 연속일정 Color 변경 함수 --> 연속일정에 백그라운드 컬러를 그대로 넣었다간 일정이 보여지지 않아 투명도를 높인 색깔로 변경하여 연속일정에 적용
|
||||
export function changeColor(color) {
|
||||
let dataColor = null;
|
||||
switch (color) {
|
||||
case "#F21D0D": dataColor = "#FEE9E7"; break;
|
||||
case "#B92ED1": dataColor = "#F8EBFB"; break;
|
||||
case "#6D3DC2": dataColor = "#F1ECF9"; break;
|
||||
case "#0D8DF2": dataColor = "#E7F4FE"; break;
|
||||
case "#4DB251": dataColor = "#EEF8EE"; break;
|
||||
case "#FFBF00": dataColor = "#FFF9E6"; break;
|
||||
case "#A0705F": dataColor = "#F6F1EF"; break;
|
||||
case "#7F7F7F": dataColor = "#F3F3F3"; break;
|
||||
case "#688897": dataColor = "#F0F4F5"; break;
|
||||
default: dataColor = "#FFFFFF";
|
||||
}
|
||||
return dataColor;
|
||||
}
|
||||
|
||||
// 주요일정 모달 작동시 현재시간 적용
|
||||
export function setDefaultScheduleTime() {
|
||||
const now = new Date();
|
||||
|
||||
const yyyy = now.getFullYear();
|
||||
const mm = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
const dd = now.getDate().toString().padStart(2, '0');
|
||||
const hh = now.getHours().toString().padStart(2, '0');
|
||||
const min = now.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
const today = `${yyyy}-${mm}-${dd}`;
|
||||
const timeNow = `${hh}:${min}`;
|
||||
|
||||
const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
const hh2 = oneHourLater.getHours().toString().padStart(2, '0');
|
||||
const min2 = oneHourLater.getMinutes().toString().padStart(2, '0');
|
||||
const timeLater = `${hh2}:${min2}`;
|
||||
|
||||
document.querySelector('.startDate').value = today;
|
||||
document.querySelector('.endDate').value = today;
|
||||
document.querySelector('.startTime').value = timeNow;
|
||||
document.querySelector('.endTime').value = timeLater;
|
||||
document.querySelector('.startTime').disabled = false
|
||||
document.querySelector('.endTime').disabled = false
|
||||
};
|
||||
|
||||
export function setIssueEditMode(isEdit){
|
||||
const editBtn = document.querySelector('.overview .xs-btn-type.issue-edit');
|
||||
const saveBtn = document.querySelector('.overview .xs-btn-type.issue-save');
|
||||
const cancleBtn = document.querySelector('.overview .xs-btn-type.issue-cancle');
|
||||
const issueMessage = document.querySelector('.overview .box.issue .wrap .message')
|
||||
const issueContent = document.querySelector('.overview .box.issue .wrap .issue-content');
|
||||
|
||||
editBtn.style.display = isEdit ? 'none' : 'block';
|
||||
saveBtn.style.display = isEdit ? 'block' : 'none';
|
||||
cancleBtn.style.display = isEdit ? 'block' : 'none';
|
||||
|
||||
issueContent.disabled = !isEdit;
|
||||
issueContent.style.color = isEdit ? '#ff3d00' : '#111';
|
||||
|
||||
if(isEdit){
|
||||
issueMessage.classList.add('active');
|
||||
issueMessage.innerText = '내용을 작성/수정한 후 저장 버튼을 눌러주세요';
|
||||
issueMessage.style.color = '#111';
|
||||
} else {
|
||||
issueMessage.classList.remove('active');
|
||||
issueMessage.innerText = '수정 버턴을 눌러 내용을 작성/수정할 수 있습니다.';
|
||||
issueMessage.style.color = '#777';
|
||||
}
|
||||
|
||||
if(isEdit && issueContent.value.length > 0) issueMessage.style.color = '#ddd';
|
||||
}
|
||||
|
||||
export function applyUtcOffsetTime(offset){
|
||||
const now = new Date();
|
||||
|
||||
const hourOffset = Math.trunc(offset/3600);
|
||||
let minuteOffset = 0;
|
||||
if(offset % 3600 !==0){
|
||||
minuteOffset = (offset/3600) - hourOffset;
|
||||
if(minuteOffset == 0.75) minuteOffset = 45;
|
||||
if(minuteOffset == 0.5) minuteOffset = 30;
|
||||
if(minuteOffset == 0.25) minuteOffset = 15;
|
||||
}
|
||||
|
||||
const hours = now.getUTCHours() + hourOffset;
|
||||
const minute = now.getUTCMinutes() + minuteOffset;
|
||||
|
||||
const date = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), hours, minute))
|
||||
|
||||
return `${date.getUTCHours().toString().padStart(2,'0')}:${date.getUTCMinutes().toString().padStart(2,'0')}`;
|
||||
}
|
||||
|
||||
export function fillPartialBlock(block, fraction, isStart) {
|
||||
if (!block || fraction === 0) return;
|
||||
const halfBlock = block.querySelectorAll('.timetable-half-block');
|
||||
const quarterBlock = block.querySelectorAll('.timetable-quarter-block');
|
||||
|
||||
if(isStart){
|
||||
if(fraction == 0.25){
|
||||
quarterBlock[1].classList.add('work-hour');
|
||||
halfBlock[1].classList.add('work-hour');
|
||||
}
|
||||
if(fraction == 0.5)halfBlock[1].classList.add('work-hour');
|
||||
if(fraction == 0.75)quarterBlock[3].classList.add('work-hour');
|
||||
} else {
|
||||
if(fraction == 0.25)quarterBlock[0].classList.add('work-hour');
|
||||
if(fraction == 0.5)halfBlock[0].classList.add('work-hour');
|
||||
if(fraction == 0.75){
|
||||
halfBlock[0].classList.add('work-hour');
|
||||
quarterBlock[2].classList.add('work-hour');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Section-Right 사용 함수 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
453
views/main/jsm/overview/overviewDataManager.js
Normal file
453
views/main/jsm/overview/overviewDataManager.js
Normal file
@@ -0,0 +1,453 @@
|
||||
import { vars } from '../archive/variable.js';
|
||||
import { checkProjectInactive } from '../main.js';
|
||||
import { drawList, generateCalendar, startTimer, updateTimeBar, makeTaskHistory } from './overviewPageRenderer.js';
|
||||
import { fileDragAndDrop } from './overviewModalManager.js';
|
||||
import { addJointContractDelimiter, restrictDuplicatedTabName, uploadImgData, setIssueEditMode, generateDeleteImgUrl, setOverviewType } from './overviewCommon.js';
|
||||
import { headerBtnForceClick, closeInitProgress } from '../archive/common.js';
|
||||
import { overviewVars } from './overviewVariable.js';
|
||||
|
||||
// overviewDataManager.js는 db 통신처럼 데이터가 실질적으로 저장되고 삭제되는 함수 및 dom 조작을 모아놓은 js이다.
|
||||
|
||||
// getData는 tb_overview에 데이터를 가져와서 데이터를 함수들에게 뿌려주는 역할을 한다.
|
||||
export async function getData() {
|
||||
if (checkProjectInactive()) return;
|
||||
|
||||
try {
|
||||
const res = await axios.get(`/${vars.project_id}/overview/getData`, {
|
||||
params: { projectId: vars.project_id },
|
||||
});
|
||||
if (res.data.success) {
|
||||
const data = res.data.data;
|
||||
await setOverviewType();
|
||||
await drawList(data);
|
||||
|
||||
//위치도 개요도 드래그앤 드롭
|
||||
fileDragAndDrop();
|
||||
// 현재 날짜로 달력 생성
|
||||
generateCalendar(overviewVars.currentYear, overviewVars.currentMonth);
|
||||
|
||||
// 교류시간 동작
|
||||
if(overviewVars.overseas)startTimer(data[0]?.nation_nm);
|
||||
|
||||
//////// pm-bcmf 연결용 테스트 코드 - 파라미터로 전달받은 시작경로로 화면 전환 (depth1)
|
||||
if (vars.startPathDepth1) {
|
||||
let headerBtn = document.querySelector(`body > .header .center .left.wrap .menu-tab .btn[data-resource-path="/${vars.startPathDepth1}"]`);
|
||||
if (headerBtn) await headerBtnForceClick(headerBtn);
|
||||
}
|
||||
|
||||
// 과업개요 생성 후
|
||||
if (!vars.startPathDepth1 && !vars.startPathDepth2 && !vars.startPathDepth3) {
|
||||
// // 시작경로가 없으면 초기 프로그레스 종료
|
||||
// console.log('******** 과업개요 렌더링 후 초기 프로그레스 종료 (시작경로 없음)');
|
||||
await closeInitProgress();
|
||||
} else {
|
||||
if (document.querySelectorAll('body > .header .center .menu-tab .folder-btn').length == 0) {
|
||||
// // 시작경로가 있는데 헤더폴더버튼이 없으면 초기 프로그레스 종료
|
||||
// console.log('******** 과업개요 렌더링 후 초기 프로그레스 종료 (시작경로 있음, 헤더폴더버튼 없음)');
|
||||
await closeInitProgress();
|
||||
} else {
|
||||
if (JSON.parse(vars.userInfoString).user_id.includes('bcmf-') && vars.project_id == 'dsdj2') {
|
||||
// 시작경로, 헤더폴더버튼이 있고 현재 계정이 bcmf계정이고 현재 프로젝트가 대산당진2공구인 경우 과업개요 숨김
|
||||
document.querySelector('.overview-main').style.display = 'none';
|
||||
} else {
|
||||
// 시작경로, 헤더폴더버튼이 있고 현재 계정이 bcmf계정이 아니거나 현재 프로젝트가 대산당진2공구가 아닌 경우 과업개요 숨김
|
||||
// console.log('******** 과업개요 렌더링 후 초기 프로그레스 종료 (현재 계정이 bcmf계정이 아니거나 현재 프로젝트가 대산당진2공구가 아님)');
|
||||
// console.log(`현재 계정: ${JSON.parse(vars.userInfoString).user_id}`);
|
||||
// console.log(`현재 프로젝트: ${vars.project_id}`);
|
||||
closeInitProgress();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error("getData error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("getData error", error);
|
||||
}
|
||||
}
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Section-Left 저장 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// Section-left 저장
|
||||
document.querySelector('.overview-modal.section-left .section-left-save')?.addEventListener('click', async () => {
|
||||
const modal = document.querySelector('.overview-modal.section-left');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
// // 시설규모 탭 제목만 입력 후 저장할때 경고문
|
||||
// for(const key in overviewVars.sectionTabData){
|
||||
// if (overviewVars.sectionTabData[key].length === 0) return alert('시설규모 탭에 입력되지 않은 항목이 있습니다. 작성 후 저장해 주세요.');
|
||||
// }
|
||||
|
||||
// // 중복된 탭 이름 사용하지 못하게
|
||||
// const duplicatedTabName = restrictDuplicatedTabName();
|
||||
// if(!duplicatedTabName) return;
|
||||
|
||||
let locationImgKey;
|
||||
let originFileSize;
|
||||
|
||||
try{
|
||||
// 이미지 파일 업로드
|
||||
for(let i = 0; overviewVars.filesArr.length > i; i++){
|
||||
await uploadImgData(overviewVars.filesArr[i], overviewVars.filesArr.type);
|
||||
}
|
||||
|
||||
// 이미지 파일 objectKey 형식으로 변환
|
||||
for(let j = 0; overviewVars.filesNameArr.length > j; j++){
|
||||
if(!overviewVars.filesNameArr[j].includes('overview/')) overviewVars.filesNameArr[j] = 'overview/'+ overviewVars.filesNameArr[j];
|
||||
}
|
||||
|
||||
// JSON 문자화 시킨다음에 db에 삽입
|
||||
locationImgKey = JSON.stringify(overviewVars.filesNameArr);
|
||||
originFileSize = JSON.stringify(overviewVars.filesSizeArr);
|
||||
// 파일 사이즈가 빈 배열일때 기본값 설정
|
||||
if(originFileSize === '[]') originFileSize = '[0]';
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if(overviewVars.deleteImgArr.length > 0){
|
||||
for(let i = 0; overviewVars.deleteImgArr.length > i; i++){
|
||||
await generateDeleteImgUrl(overviewVars.deleteImgArr[i]);
|
||||
}
|
||||
await axios.post(`/${vars.project_id}/overview/updateOverviewImgData`, { projectId: vars.project_id, locationImgKey : locationImgKey, originFileSize : originFileSize });
|
||||
}
|
||||
const projectId = vars.project_id;
|
||||
const businessPurpose = modal.querySelector('.business-purpose').value;
|
||||
const performanceArea = modal.querySelector('.performance-area').value;
|
||||
const referenceArea = modal.querySelector('.reference-area').value;
|
||||
const facilityOverview = modal.querySelector('.facility-overview').value;
|
||||
|
||||
let data = {projectId, businessPurpose, performanceArea, referenceArea, facilityOverview, originFileSize, locationImgKey };
|
||||
|
||||
//overseas용 분기
|
||||
if(!overviewVars.overseas){
|
||||
data.nation = modal.querySelector('.nation').value;
|
||||
data.continent = modal.querySelector('.continent').value;
|
||||
} else {
|
||||
data.nation = modal.querySelector('.nation').innerText;
|
||||
data.continent = modal.querySelector('.continent').innerText;
|
||||
}
|
||||
|
||||
// minIO에 저장, DB 저장이 완료되면 minIO에 기존에 있던 사진을 삭제
|
||||
try {
|
||||
const res = await axios.post(`/${projectId}/overview/saveSectionLeftData`, data);
|
||||
if (res.data.message === '200') {
|
||||
try{
|
||||
// 탭만 입력했을때 기본값 세팅
|
||||
for (const title in overviewVars.sectionTabData) {
|
||||
if (!overviewVars.sectionTabData[title] || overviewVars.sectionTabData[title].length === 0) {
|
||||
// 기본값 세팅
|
||||
overviewVars.sectionTabData[title] = [ { key: '', value: '', id: null, projectId } ];
|
||||
}
|
||||
}
|
||||
await upsertSectionTabData();
|
||||
const secondRes = await axios.post(`/${projectId}/overview/saveSectionLeftTabData`, overviewVars.sectionTabData);
|
||||
if (secondRes.data.message === '200') overviewVars.sectionTabData = {};
|
||||
} catch(err){
|
||||
console.error(err, 'sectionLeftTabData 저장 오류')
|
||||
}
|
||||
alert('저장이 완료되었습니다.');
|
||||
modal.style.display = 'none';
|
||||
modalWrapper.style.display = 'none';
|
||||
getData();
|
||||
updateTimeBar();
|
||||
} else {
|
||||
alert('파일 저장에 실패하였습니다.');
|
||||
}
|
||||
}catch(err){
|
||||
console.error('S3 이미지 삭제 오류 : ' , err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
async function upsertSectionTabData() {
|
||||
const deleteArr = [];
|
||||
|
||||
// 섹션별 비교
|
||||
for (let sectionName in overviewVars.originalSectionTabData) {
|
||||
const originalArr = overviewVars.originalSectionTabData[sectionName];
|
||||
const currentArr = overviewVars.sectionTabData[sectionName] || [];
|
||||
|
||||
// currentArr에 없는 항목만 삭제 대상으로
|
||||
const toDelete = originalArr.filter(item => {
|
||||
return !currentArr.some(c => c.id === item.id);
|
||||
});
|
||||
|
||||
if (toDelete.length > 0) {
|
||||
deleteArr.push(...toDelete);
|
||||
}
|
||||
}
|
||||
|
||||
// 삭제 API 호출
|
||||
try {
|
||||
const deleteRes = await axios.delete(`/${vars.project_id}/overview/deleteCellData`, { data: { deleteArr: deleteArr }});
|
||||
if(deleteRes.data.message === 200)console.log('overviewSectionLeftCellData 삭제 완료');
|
||||
} catch (err) {
|
||||
console.error('삭제 실패', cell.id, err);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Section-Left 저장 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Section-Middle 저장, 삭제 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// section-middle 저장
|
||||
document.querySelector('.overview-modal.section-middle .btn-active')?.addEventListener('click', async () => {
|
||||
const modal = document.querySelector('.overview-modal.section-middle');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
const projectId = vars.project_id;
|
||||
const abbreviatedName = modal.querySelector('.abbreviated-name').value;
|
||||
const taskNmEn = modal.querySelector('.task-nm-en').value;
|
||||
const taskPurpose = modal.querySelector('.task-purpose').value;
|
||||
const orderSizeUsd = modal.querySelector('.order-size-usd').value;
|
||||
const orderSizeKrw = modal.querySelector('.order-size-krw').value;
|
||||
const clientOrigin = modal.querySelector('.client-origin').value;
|
||||
const financial = modal.querySelector('.financial').value;
|
||||
const financialCountry = modal.querySelector('.financial-country').value;
|
||||
const selectionMethod = modal.querySelector('.selection-method').value;
|
||||
const supportDepartment = modal.querySelector('.support-department').value;
|
||||
const supportManagerNm = modal.querySelector('.support-manager-nm').value;
|
||||
const completionDate = modal.querySelector('.completion-date').value;
|
||||
|
||||
let data = {projectId, abbreviatedName, taskNmEn, taskPurpose, orderSizeUsd, orderSizeKrw, clientOrigin, financial, financialCountry, selectionMethod, supportDepartment, supportManagerNm, completionDate};
|
||||
|
||||
// overseas용 분기
|
||||
if(!overviewVars.overseas){
|
||||
data.projectNo = modal.querySelector('.project-no').value
|
||||
data.taskType = modal.querySelector('.task-type').value
|
||||
data.bid = modal.querySelector('.bid').value
|
||||
data.relativeClient = modal.querySelector('.client').value
|
||||
data.department = modal.querySelector('.department').value
|
||||
data.taskNmKr = modal.querySelector('.task-nm-kr').value;
|
||||
data.scheduledCommencementDate = modal.querySelector('.scheuled-commencement-date').value;
|
||||
data.contractPeriod = modal.querySelector('.contract-period').value;
|
||||
data.projectManagerNm = modal.querySelector('.projectmanager-nm').value;
|
||||
data.managerNm = modal.querySelector('.manager-nm').value;
|
||||
data.contractDate = modal.querySelector('.contract-date').value;
|
||||
data.commencementDate = modal.querySelector('.commencement-date').value;
|
||||
data.originalCompletionDate = modal.querySelector('.scheduled-completion-date').value;
|
||||
// 공동도급 데이터 DB 삽입을 위한 식별자(^&)추가
|
||||
const jointContract = addJointContractDelimiter();
|
||||
data.jointContractComapnyName = jointContract.companyName;
|
||||
data.jointContractShares = jointContract.companyShares;
|
||||
data.jointContractKrw = jointContract.contractKrw;
|
||||
data.jointContractUsd = jointContract.contractUsd;
|
||||
data.representativeCompany = jointContract.representativeCompany;
|
||||
} else {
|
||||
data.projectNo = modal.querySelector('.project-no').innerText;
|
||||
data.taskType = modal.querySelector('.task-type').innerText;
|
||||
data.bid = modal.querySelector('.bid').innerText;
|
||||
data.relativeClient = modal.querySelector('.client').innerText;
|
||||
data.department = modal.querySelector('.department').innerText;
|
||||
data.taskNmKr = modal.querySelector('.task-nm-kr').innerText;
|
||||
data.scheduledCommencementDate = modal.querySelector('.scheuled-commencement-date').innerText;
|
||||
data.contractPeriod = modal.querySelector('.contract-period').innerText.replace('개월', '');
|
||||
data.projectManagerNm = modal.querySelector('.projectmanager-nm').innerText;
|
||||
data.managerNm = modal.querySelector('.manager-nm').innerText;
|
||||
data.contractDate = modal.querySelector('.contract-date').innerText.replaceAll('-', '');
|
||||
data.commencementDate = modal.querySelector('.commencement-date').innerText.replaceAll('-', '');
|
||||
data.originalCompletionDate = modal.querySelector('.scheduled-completion-date').innerText.replaceAll('-', '');
|
||||
// 공동도급 데이터 DB 삽입을 위한 식별자(^&)추가
|
||||
const jointContract = addJointContractDelimiter();
|
||||
data.jointContractComapnyName = jointContract.companyName;
|
||||
data.jointContractShares = jointContract.companyShares;
|
||||
data.jointContractKrw = jointContract.contractKrw;
|
||||
data.jointContractUsd = jointContract.contractUsd;
|
||||
data.representativeCompany = jointContract.representativeCompany;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(`/${projectId}/overview/saveSectionMiddleData`, data);
|
||||
if (res.data.message = '200') {
|
||||
alert('저장이 완료되었습니다.');
|
||||
modal.style.display = 'none';
|
||||
modalWrapper.style.display = 'none';
|
||||
getData();
|
||||
updateTimeBar();
|
||||
} else {
|
||||
alert('오류가 발생하였습니다.');
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Section-Middle - 과업중지 이력 저장
|
||||
document.querySelector('.overview-modal.task-period .modal-footer .btn-active')?.addEventListener('click', async () => {
|
||||
const liList = document.querySelectorAll('.overview-modal.task-period .overview-modal-body ul li input');
|
||||
const modal = document.querySelector('.overview-modal.task-period');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
const saveLiArr = [];
|
||||
|
||||
if(overviewVars.deleteTaskHistory.length > 0)await deleteTaskHistory();
|
||||
|
||||
for (let i = 0; i < liList.length; i += 6) {
|
||||
// 과업중지이력의 Value가 전부 미입력 상태일때는 저장하지 않는다.
|
||||
const values = [liList[i].value, liList[i+1].value, liList[i+2].value, liList[i+3].value, liList[i+4].value, liList[i+5].value];
|
||||
if(values.every(value => value === ''))continue;
|
||||
|
||||
saveLiArr.push({
|
||||
projectId: vars.project_id,
|
||||
order: liList[i].value,
|
||||
suspensionDate: liList[i + 1].value,
|
||||
suspensionReason: liList[i + 2].value,
|
||||
resumptionDate: liList[i + 3].value,
|
||||
consultationContent: liList[i + 4].value,
|
||||
changeDate: liList[i + 5].value
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(`/${vars.project_id}/overview/saveTaskHistoryData`, saveLiArr);
|
||||
if (res.data.message === '200') {
|
||||
alert('과업중지 이력이 저장되었습니다.');
|
||||
makeTaskHistory();
|
||||
modal.style.display = 'none';
|
||||
modalWrapper.style.display = 'none';
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
alert('과업중지 이력 저장에 실패하였습니다.');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
export async function deleteTaskHistory() {
|
||||
if(overviewVars.deleteTaskHistory.length > 0){
|
||||
try{
|
||||
const res = await axios.delete(`/${vars.project_id}/overview/deleteTaskPeriodData`, { data: { deleteArr : overviewVars.deleteTaskHistory } });
|
||||
if(res.data.message === '200') overviewVars.deleteTaskHistory = [];
|
||||
} catch(err) {
|
||||
console.error(err, '과업중지이력 삭제 실패');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Section-Middle 저장, 삭제 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Section-Right 저장, 삭제 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// Section-Right calendar 주요일정 저장
|
||||
document.querySelector('.overview-modal.schedule .schedule-save')?.addEventListener('click', async () => {
|
||||
const modal = document.querySelector('.overview-modal.schedule');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
const projectId = vars.project_id;
|
||||
const country = document.querySelector('.overview-modal.schedule input[type=radio]:checked').value;
|
||||
const color = document.querySelector('.overview-modal .color .label-color.on').dataset.color;
|
||||
const title = document.querySelector('.overview-modal.schedule .schedule-title').value;
|
||||
const content = document.querySelector('.overview-modal.schedule .schedule-content').value;
|
||||
const startDate = document.querySelector('.overview-modal.schedule .startDate').value;
|
||||
const startTime = document.querySelector('.overview-modal.schedule .startTime').value;
|
||||
const endDate = document.querySelector('.overview-modal.schedule .endDate').value;
|
||||
const endTime = document.querySelector('.overview-modal.schedule .endTime').value;
|
||||
const scheduleId = document.querySelector('.overview-modal.schedule').dataset.scheduleId;
|
||||
|
||||
if (!title) return alert('일정의 제목을 입력해주세요.')
|
||||
|
||||
// 주요일정 하루종일 기능
|
||||
const alldayChecked = document.querySelector('.overview-modal.schedule .custom-checkbox.all-day input').checked;
|
||||
|
||||
const startDateTimeStr = `${startDate}T${startTime}`;
|
||||
const endDateTimeStr = `${endDate}T${endTime}`;
|
||||
|
||||
if(alldayChecked === true){
|
||||
//날짜 유효성 검증
|
||||
const startDay = new Date(startDate);
|
||||
const endDay = new Date(endDate);
|
||||
if(startDay > endDay) return alert('시작일은 종료일보다 늦을 수 없습니다. 다시 설정해주십시오.');
|
||||
} else {
|
||||
//날짜 + 시간 유효성 검증
|
||||
const startDateTime = new Date(startDateTimeStr);
|
||||
const endDateTime = new Date(endDateTimeStr);
|
||||
if (startDateTime > endDateTime)return alert('시작일은 종료일보다 늦을 수 없습니다. 다시 설정해주십시오.');
|
||||
}
|
||||
|
||||
let data = { scheduleId, projectId, country, color, title, content, startDateTimeStr, endDateTimeStr }
|
||||
// 기존일정 id 삽입
|
||||
if (scheduleId !== '') data.scheduleId = scheduleId;
|
||||
|
||||
try {
|
||||
const res = await axios.post(`/${projectId}/overview/saveScheduleData`, data);
|
||||
|
||||
if (res.data.message == '200') {
|
||||
modal.style.display = 'none';
|
||||
modalWrapper.style.display = 'none';
|
||||
alert('일정 저장이 완료되었습니다.');
|
||||
generateCalendar(overviewVars.currentYear, overviewVars.currentMonth);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
// Section-Right calendar 주요일정 삭제
|
||||
document.querySelector('.overview-modal.schedule .schedule-delete')?.addEventListener('click', async () => {
|
||||
if (confirm('일정을 삭제하시겠습니까?')) {
|
||||
const modal = document.querySelector('.overview-modal.schedule');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
const scheduleId = document.querySelector('.overview-modal.schedule').dataset.scheduleId;
|
||||
const res = await axios.delete(`/${vars.project_id}/overview/deleteScheduleData`, { data: { scheduleId } });
|
||||
|
||||
if (res.data.message = '200') {
|
||||
alert('일정이 삭제되었습니다.');
|
||||
modal.style.display = 'none';
|
||||
modalWrapper.style.display = 'none';
|
||||
generateCalendar(overviewVars.currentYear, overviewVars.currentMonth);
|
||||
} else {
|
||||
alert('삭제에 실패하였습니다.')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 주요 현안 및 이슈
|
||||
document.querySelector('.overview .xs-btn-type.issue-edit')?.addEventListener('click', () => {
|
||||
setIssueEditMode(true)
|
||||
});
|
||||
|
||||
// 메모 입력시 내용의 길이에 따라
|
||||
document.querySelector('.overview .box.issue .wrap .issue-content')?.addEventListener('input', () => {
|
||||
const issueMessage = document.querySelector('.overview .box.issue .wrap .message')
|
||||
const issueContent = document.querySelector('.overview .box.issue .wrap .issue-content');
|
||||
|
||||
if(issueContent.value.length > 0){
|
||||
issueMessage.style.color = '#ddd';
|
||||
} else {
|
||||
issueMessage.style.color = '#111';
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('.overview .xs-btn-type.issue-save')?.addEventListener('click', async () => {
|
||||
const projectId = vars.project_id;
|
||||
const issueContent = document.querySelector('.overview .box.issue .wrap .issue-content');
|
||||
const issueData = issueContent.value;
|
||||
|
||||
let data = {projectId, issueData}
|
||||
|
||||
try{
|
||||
const result = await axios.post(`/${projectId}/overview/saveIssueData`, data);
|
||||
|
||||
if(result.data.message === '200'){
|
||||
alert('주요 현안 및 이슈 저장 성공');
|
||||
setIssueEditMode(false)
|
||||
overviewVars.issueData = issueData;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
document.querySelector('.overview .xs-btn-type.issue-cancle')?.addEventListener('click', () => {
|
||||
const issueContent = document.querySelector('.overview .box.issue .wrap .issue-content');
|
||||
issueContent.value = overviewVars.issueData;
|
||||
|
||||
setIssueEditMode(false);
|
||||
});
|
||||
// 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Section-Right 저장, 삭제 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
765
views/main/jsm/overview/overviewModalManager.js
Normal file
765
views/main/jsm/overview/overviewModalManager.js
Normal file
@@ -0,0 +1,765 @@
|
||||
import { vars } from '../archive/variable.js';
|
||||
import { displayFileName, checkDuplicatesFileNameAndExt , restrictToNumber, calculateTotalJointContract, showToolTip, overviewVarsInit } from './overviewCommon.js';
|
||||
import { makeTaskHistory } from './overviewPageRenderer.js';
|
||||
import { overviewVars } from './overviewVariable.js';
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Overview 모달 공통 사용 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// 모달CLSOE
|
||||
const modalNames = ['facility-size', 'section-left', 'section-middle', 'schedule-list', 'schedule', 'task-period'];
|
||||
modalNames.forEach(name => {
|
||||
const modal = document.querySelector(`.overview-modal.${name}`);
|
||||
if (!modal) return;
|
||||
|
||||
const closeBtn = modal.querySelector('.overview-modal-header--end img');
|
||||
const footerCloseBtn = modal.querySelector('.modal-footer .btn-close');
|
||||
|
||||
closeBtn?.addEventListener('click', () => {
|
||||
modal.style.display = 'none';
|
||||
document.querySelector('.overview-modal-wrapper').style.display = 'none';
|
||||
// 변수 초기화
|
||||
overviewVarsInit();
|
||||
});
|
||||
|
||||
footerCloseBtn?.addEventListener('click', () => {
|
||||
modal.style.display = 'none';
|
||||
document.querySelector('.overview-modal-wrapper').style.display = 'none';
|
||||
// 변수 초기화
|
||||
overviewVarsInit();
|
||||
});
|
||||
});
|
||||
|
||||
// 모달 영역 외에 클릭했을때 모달 꺼지는 이벤트
|
||||
// document.querySelector('.overview-modal-wrapper')?.addEventListener('pointerdown', (e) => {
|
||||
// document.querySelector('.overview-modal-wrapper').style.display = 'none';
|
||||
// document.querySelectorAll('.overview-modal').forEach(modal => {
|
||||
// if (!modal.contains(e.target)) {
|
||||
// modal.style.display = 'none';
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
|
||||
// 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Overview 모달 공통 사용 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Secion-Left 모달 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// 위치도/개요도 드래그앤드롭
|
||||
export function fileDragAndDrop() {
|
||||
const dropbox = document.querySelector('.overview-modal .dropbox');
|
||||
|
||||
dropbox.addEventListener('dragenter', (e) => {
|
||||
e.preventDefault();
|
||||
dropbox.style.background = '#999';
|
||||
});
|
||||
|
||||
dropbox.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
dropbox.style.background = '#999';
|
||||
});
|
||||
|
||||
dropbox.addEventListener('dragleave', (e) => {
|
||||
e.preventDefault();
|
||||
dropbox.style.background = '#e6e8ea';
|
||||
});
|
||||
|
||||
dropbox.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
dropbox.style.background = '#e6e8ea';
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
|
||||
// 중복 이름과 확장자 체크
|
||||
checkDuplicatesFileNameAndExt(files);
|
||||
displayFileName();
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
// section-left 모달 열기
|
||||
document.querySelector('.overview .box-header--right.section-left-edit')?.addEventListener('click', () => {
|
||||
const modal = document.querySelector('.overview-modal.section-left');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
modal.querySelector('.business-purpose').value = document.querySelector('.overview .box-body.business-purpose').innerText;
|
||||
modal.querySelector('.performance-area').value = document.querySelector('.overview .box-body.location-map-detail .performance-area').innerText;
|
||||
modal.querySelector('.reference-area').value = document.querySelector('.overview .box-body.location-map-detail .reference-area').innerText;
|
||||
modal.querySelector('.facility-overview').value = document.querySelector('.overview .box-body.facility-size .facility-overview').innerText;
|
||||
|
||||
//overseas용 분기
|
||||
if(!overviewVars.overseas){
|
||||
modal.querySelector('.nation').value = document.querySelector('.overview .box-body.location-map-detail .nation-nm').innerText;
|
||||
modal.querySelector('.continent').value = document.querySelector('.overview .box-body.location-map-detail .continent').innerText;
|
||||
} else {
|
||||
modal.querySelector('.nation').innerText = document.querySelector('.overview .box-body.location-map-detail .nation-nm').innerText;
|
||||
modal.querySelector('.continent').innerText = document.querySelector('.overview .box-body.location-map-detail .continent').innerText;
|
||||
}
|
||||
|
||||
|
||||
overviewVarsInit();
|
||||
|
||||
if(overviewVars.filesNameArr.length > 0) displayFileName();
|
||||
|
||||
modal.querySelectorAll('input[type=file]').forEach(input => { input.value = ''; })
|
||||
|
||||
makeTabCell(overviewVars.originalSectionTabData);
|
||||
|
||||
// 토글 처리
|
||||
if (modal.style.display === 'block') {
|
||||
modal.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
modal.style.display = 'block';
|
||||
modalWrapper.style.display = 'block';
|
||||
});
|
||||
|
||||
// section-left 모달 시설규모 탭,셀 생성 함수
|
||||
export function makeTabCell(data) {
|
||||
const tabContainer = document.querySelector('.section-tabs');
|
||||
const contentContainer = document.querySelector('.section-content');
|
||||
|
||||
// 가로 스크롤 이벤트
|
||||
tabContainer.addEventListener('wheel', (e) => {
|
||||
e.preventDefault();
|
||||
tabContainer.scrollLeft += e.deltaY;
|
||||
});
|
||||
|
||||
tabContainer.innerHTML = '';
|
||||
contentContainer.innerHTML = '';
|
||||
|
||||
// 데이터 없을 때 기본값
|
||||
if (Object.keys(data).length === 0) {
|
||||
data = { '': [{ key: '', value: '', id: '' }] };
|
||||
}
|
||||
|
||||
const titles = Object.keys(data);
|
||||
titles.forEach((title, index) => {
|
||||
const tab = createTab(title, index === 0);
|
||||
bindTabEvents(tab, contentContainer);
|
||||
tabContainer.appendChild(tab);
|
||||
});
|
||||
|
||||
// 탭 추가 버튼
|
||||
const addBtn = document.createElement('button');
|
||||
addBtn.classList.add('xs-btn', 'add');
|
||||
addBtn.innerHTML = `<img class="icon" src="/main/img/overview/icon-add.svg" alt="icon-add">`;
|
||||
tabContainer.appendChild(addBtn);
|
||||
|
||||
addBtn.addEventListener('click', () => {
|
||||
const newTab = createTab('', true);
|
||||
bindTabEvents(newTab, contentContainer, true);
|
||||
tabContainer.insertBefore(newTab, addBtn);
|
||||
newTab.click();
|
||||
});
|
||||
|
||||
// 첫번째 탭 클릭
|
||||
if (titles.length > 0) {
|
||||
tabContainer.querySelector('.tab')?.click();
|
||||
}
|
||||
}
|
||||
|
||||
// 탭 생성
|
||||
function createTab(title, isActive) {
|
||||
const tab = document.createElement('div');
|
||||
tab.className = 'tab';
|
||||
if (isActive) tab.classList.add('click-on');
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.className = 'tab-style-input';
|
||||
input.type = 'text';
|
||||
input.value = title;
|
||||
input.placeholder = '탭 제목 입력';
|
||||
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.className = 'xs-btn';
|
||||
closeBtn.innerHTML = `<img class="icon" src="/main/img/overview/icon-close-111.svg">`;
|
||||
|
||||
tab.appendChild(input);
|
||||
tab.appendChild(closeBtn);
|
||||
|
||||
return tab;
|
||||
}
|
||||
|
||||
// 탭 클릭, 제목입력, 삭제버튼 이벤트
|
||||
function bindTabEvents(tab, contentContainer, isNew = false) {
|
||||
const input = tab.querySelector('input');
|
||||
const closeBtn = tab.querySelector('button');
|
||||
|
||||
// 탭 클릭 이벤트
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.section-tabs .tab').forEach(t => t.classList.remove('click-on'));
|
||||
tab.classList.add('click-on');
|
||||
renderCells(input.value.trim(), contentContainer);
|
||||
});
|
||||
|
||||
// 탭 제목 input 변경 이벤트
|
||||
let previousSection = input.value.trim();
|
||||
input.addEventListener('input', () => {
|
||||
const section = String(input.value.trim());
|
||||
// 탭 제목이 빈값 일때 삭제 처리
|
||||
if (section === '') {
|
||||
if (previousSection && overviewVars.sectionTabData[previousSection]) delete overviewVars.sectionTabData[previousSection];
|
||||
showToolTip(input, '탭 제목이 없어 셀 내용이 초기화되었습니다.')
|
||||
// 셀 초기화
|
||||
document.querySelectorAll('.section-content .section-content--cell').forEach((cell, index) => {
|
||||
if(index === 0){
|
||||
cell.querySelectorAll('input').forEach(input => input.value = '');
|
||||
} else{
|
||||
// 셀 추가 버튼을 건너뜀
|
||||
if(cell.className === 'section-content--cell add-cell-btn') return;
|
||||
cell.remove();
|
||||
}
|
||||
});
|
||||
previousSection = '';
|
||||
return;
|
||||
}
|
||||
// 탭 제목이 같을때 처리 막기
|
||||
if (section !== previousSection && overviewVars.sectionTabData[section]){
|
||||
showToolTip(input, '이미 존재하는 탭 이름입니다. 다른 이름을 입력해주세요.')
|
||||
input.value = previousSection;
|
||||
return;
|
||||
}
|
||||
|
||||
// 탭 이름이 변경될때
|
||||
if (previousSection && previousSection !== section) {
|
||||
if (overviewVars.sectionTabData[previousSection]) {
|
||||
overviewVars.sectionTabData[section] = overviewVars.sectionTabData[previousSection];
|
||||
delete overviewVars.sectionTabData[previousSection];
|
||||
} else {
|
||||
overviewVars.sectionTabData[section] = [];
|
||||
}
|
||||
} else if (!overviewVars.sectionTabData[section]) {
|
||||
overviewVars.sectionTabData[section] = [];
|
||||
}
|
||||
|
||||
previousSection = section;
|
||||
});
|
||||
|
||||
// 탭 삭제 이벤트
|
||||
closeBtn.addEventListener('click', async (e) => {
|
||||
e.stopPropagation();
|
||||
const title = input.value;
|
||||
const tabLength = document.querySelectorAll('.section-tabs .tab').length;
|
||||
const tabInput = document.querySelector('.section-tabs .tab input');
|
||||
const cell = document.querySelectorAll('.section-content--cell input');
|
||||
const emptyTab = !tabInput?.value.trim();
|
||||
const emptyCell = Array.from(cell).every(input => !input.value.trim());
|
||||
|
||||
// 탭이 한 개 남았고 공백일 경우 삭제 막기
|
||||
if (tabLength === 1 && emptyTab && emptyCell) return;
|
||||
|
||||
try {
|
||||
if (tabLength === 1) {
|
||||
delete overviewVars.sectionTabData[title];
|
||||
document.querySelector('.section-tabs .tab input').value = '';
|
||||
document.querySelectorAll('.section-content .section-content--cell').forEach((cell, index) => {
|
||||
if(index === 0){
|
||||
cell.querySelectorAll('input').forEach(input => input.value = '');
|
||||
} else{
|
||||
// 셀 추가 버튼을 건너뜀
|
||||
if(cell.className === 'section-content--cell add-cell-btn') return;
|
||||
cell.remove();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
delete overviewVars.sectionTabData[title];
|
||||
tab.remove();
|
||||
const allTabs = document.querySelectorAll('.section-tabs .tab');
|
||||
const lastTab = allTabs[allTabs.length - 1];
|
||||
if (lastTab) lastTab.click();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 셀 렌더딩
|
||||
function renderCells(title, container) {
|
||||
container.innerHTML = '';
|
||||
let cells = overviewVars.sectionTabData[title] || [{ key: '', value: '', id: '' }];
|
||||
// 타이틀만 있고 셀의 내용이 없을때 기본값 설정
|
||||
if(cells.length === 0) cells = [{ key: '', value: '', id: '' }];
|
||||
cells.forEach(cell => {
|
||||
const row = createCell(cell);
|
||||
bindCellEvents(row, title);
|
||||
container.appendChild(row);
|
||||
});
|
||||
|
||||
if (!container._hasInputEvent) {
|
||||
container.addEventListener('input', (e) => {
|
||||
if (!e.target.matches('input')) return;
|
||||
|
||||
const section = document.querySelector('.tab.click-on input').value.trim();
|
||||
|
||||
if (section === '') {
|
||||
container.querySelectorAll('.section-content--cell input').forEach(cell => {
|
||||
cell.value = ''
|
||||
});
|
||||
showToolTip(e.target, '탭의 제목을 먼저 입력해주세요')
|
||||
return
|
||||
}
|
||||
|
||||
const keyInputs = container.querySelectorAll('input.short-input');
|
||||
const valueInputs = container.querySelectorAll('input.middle-input');
|
||||
|
||||
const keyValueList = [];
|
||||
for (let i = 0; i < keyInputs.length; i++) {
|
||||
const key = keyInputs[i].value;
|
||||
const value = valueInputs[i] ? valueInputs[i].value : '';
|
||||
const id = keyInputs[i].dataset.id ? parseInt(keyInputs[i].dataset.id) : null;
|
||||
const projectId = vars.project_id;
|
||||
keyValueList.push({ key, value, id, projectId });
|
||||
}
|
||||
|
||||
overviewVars.sectionTabData[section] = keyValueList;
|
||||
});
|
||||
container._hasInputEvent = true;
|
||||
}
|
||||
|
||||
// 셀 추가 버튼
|
||||
const addCell = document.createElement('div');
|
||||
addCell.className = 'section-content--cell add-cell-btn';
|
||||
addCell.innerHTML = `
|
||||
<img class="icon" src="/main/img/overview/icon-add-primary.svg" alt="icon-add-primary">
|
||||
<h3>셀추가</h3>`;
|
||||
addCell.addEventListener('click', () => {
|
||||
const newCell = createCell({ key: '', value: '', id: null });
|
||||
bindCellEvents(newCell, title);
|
||||
container.insertBefore(newCell, addCell);
|
||||
});
|
||||
container.appendChild(addCell);
|
||||
}
|
||||
|
||||
// 셀 생성
|
||||
function createCell(cell) {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'section-content--cell';
|
||||
row.innerHTML = `
|
||||
<input class="short-input" type="text" value="${cell.key ?? ''}" data-id="${cell.id ?? ''}" placeholder="셀 제목 입력">
|
||||
<input class="middle-input" type="text" value="${cell.value ?? ''}" data-id="${cell.id ?? ''}" placeholder="셀 내용 입력">
|
||||
`;
|
||||
const btn = document.createElement('button');
|
||||
btn.classList.add('xs-btn', 'cell-remove');
|
||||
btn.innerHTML = `<img src="/main/img/overview/icon-close-111.svg">`;
|
||||
row.appendChild(btn);
|
||||
return row;
|
||||
}
|
||||
|
||||
// 셀 삭제 버튼 이벤트 추가
|
||||
function bindCellEvents(row, title) {
|
||||
const btn = row.querySelector('.cell-remove');
|
||||
const cellId = row.querySelector('input')?.dataset.id;
|
||||
btn.addEventListener('click', async () => {
|
||||
try {
|
||||
// 기본생성된 셀 이벤트에서 title이 들어오지 않는 경우가 있어서 값을 직접 넣어줌
|
||||
title = document.querySelector('.section-tabs .tab.click-on input').value;
|
||||
const cells = document.querySelectorAll('.section-content--cell');
|
||||
|
||||
if (cellId) {
|
||||
// DB 삭제 성공 시 sectionTabData 업데이트
|
||||
const index = overviewVars.sectionTabData[title].findIndex(item => item.id == cellId);
|
||||
if (index !== -1) overviewVars.sectionTabData[title].splice(index, 1);
|
||||
} else {
|
||||
// 탭 입력이 되지않고 sectionTabData에 data가 없을때 row 삭제처리
|
||||
if(!overviewVars.sectionTabData[title]){
|
||||
if(cells.length > 2){
|
||||
return row.remove();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const index = overviewVars.sectionTabData[title].findIndex(item => item.key === row.querySelector('.short-input')?.value && item.value === row.querySelector('.middle-input')?.value);
|
||||
if(index !== -1) overviewVars.sectionTabData[title].splice(index, 1);
|
||||
}
|
||||
|
||||
// 셀이 하나뿐이면 value 초기화, 아니면 삭제
|
||||
if (cells.length === 2) {
|
||||
row.querySelectorAll('input').forEach(input => input.value = '');
|
||||
} else {
|
||||
row.remove();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Section-left 시설규모 모달 OPEN
|
||||
document.querySelector('.overview .box-header--right.facility-size button')?.addEventListener('click', async () => {
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
const modal = document.querySelector('.overview-modal.facility-size');
|
||||
|
||||
//토글
|
||||
if (modal.style.display === 'block') {
|
||||
modal.style.display = 'none';
|
||||
modalWrapper.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await axios.get(`/${vars.project_id}/overview/getFacilitySizeData`, { params: { projectId: vars.project_id } });
|
||||
if (res.data.message === '200') {
|
||||
const modalBody = modal.querySelector('.overview-modal-body');
|
||||
modalBody.innerHTML = '';
|
||||
|
||||
const dataArr = res.data.data;
|
||||
if (dataArr.length === 0) {
|
||||
modalBody.innerHTML = `<img src="/main/img/overview/non-facilitySize-image.svg">`
|
||||
}
|
||||
|
||||
let currentTitle = '';
|
||||
|
||||
for (let i = 0; i < dataArr.length; i++) {
|
||||
if (dataArr[i].title !== currentTitle) {
|
||||
currentTitle = dataArr[i].title;
|
||||
|
||||
const boxHeader = document.createElement('div');
|
||||
boxHeader.className = 'box-header';
|
||||
const h3 = document.createElement('h3');
|
||||
h3.textContent = currentTitle;
|
||||
boxHeader.appendChild(h3);
|
||||
modalBody.appendChild(boxHeader);
|
||||
|
||||
}
|
||||
|
||||
const typeWrap = document.createElement('div');
|
||||
typeWrap.className = 'type--wrap';
|
||||
|
||||
const left = document.createElement('div');
|
||||
left.className = 'type--wrap-left';
|
||||
const p = document.createElement('p');
|
||||
p.textContent = dataArr[i].key;
|
||||
left.appendChild(p);
|
||||
|
||||
const right = document.createElement('div');
|
||||
right.className = 'type--wrap-right';
|
||||
const h4 = document.createElement('h4');
|
||||
h4.textContent = dataArr[i].value;
|
||||
right.appendChild(h4);
|
||||
|
||||
typeWrap.appendChild(left);
|
||||
typeWrap.appendChild(right);
|
||||
|
||||
modalBody.appendChild(typeWrap);
|
||||
}
|
||||
}
|
||||
|
||||
modal.style.display = 'block';
|
||||
modalWrapper.style.display = 'block';
|
||||
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// section-left 위치도/개요도 이미지 저장
|
||||
document.querySelector('.overview-modal.section-left .overview-img-file-input')?.addEventListener('change', () => {
|
||||
const files = Array.from(document.querySelector('.overview-modal.section-left .overview-img-file-input').files);
|
||||
|
||||
// 중복 이름과 확장자 체크
|
||||
checkDuplicatesFileNameAndExt(files);
|
||||
// 등록된 파일의 이름을 추가
|
||||
displayFileName();
|
||||
|
||||
// 초기화
|
||||
document.querySelector('.overview-modal.section-left .overview-img-file-input').value = '';
|
||||
});
|
||||
|
||||
// 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Secion-Left 모달 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Secion-Middle 모달 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// section-middle 모달 열기
|
||||
document.querySelector('.overview .box-header--right.section-middle-edit')?.addEventListener('click', () => {
|
||||
const modal = document.querySelector('.overview-modal.section-middle');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
modal.querySelector('.abbreviated-name').value = document.querySelector('.overview .abbreviated-name').innerText;
|
||||
modal.querySelector('.task-nm-en').value = document.querySelector('.overview .task-nm-en').innerText;
|
||||
modal.querySelector('.task-purpose').value = document.querySelector('.overview .task-purpose').innerText;
|
||||
modal.querySelector('.order-size-usd').value = document.querySelector('.overview .order-size-usd').innerText;
|
||||
modal.querySelector('.order-size-krw').value = document.querySelector('.overview .order-size-krw').innerText;
|
||||
modal.querySelector('.client-origin').value = document.querySelector('.overview .client-origin').innerText;
|
||||
modal.querySelector('.financial').value = document.querySelector('.financial').innerText;
|
||||
modal.querySelector('.financial-country').value = document.querySelector('.overview .financial-country').innerText;
|
||||
modal.querySelector('.selection-method').value = document.querySelector('.overview .selection-method').innerText;
|
||||
modal.querySelector('.overview-table-container').innerHTML = document.querySelector('.overview .overview-table-container ').innerHTML;
|
||||
modal.querySelector('.support-department').value = document.querySelector('.overview .support-department').innerText;
|
||||
modal.querySelector('.support-manager-nm').value = document.querySelector('.overview .support-manager-nm').innerText;
|
||||
modal.querySelector('.completion-date').value = document.querySelector('.overview .completion-date').innerText;
|
||||
|
||||
// overseas용 분기
|
||||
if(!overviewVars.overseas){
|
||||
modal.querySelector('.project-no').value = document.querySelector('.overview .project-no').innerText;
|
||||
modal.querySelector('.task-type').value = document.querySelector('.overview .task-type').innerText;
|
||||
modal.querySelector('.bid').value = document.querySelector('.overview .bid').innerText;
|
||||
modal.querySelector('.client').value = document.querySelector('.overview .client').innerText;
|
||||
modal.querySelector('.department').value = document.querySelector('.overview .department').innerText;
|
||||
modal.querySelector('.currency-code').value = document.querySelector('.overview .currency-code').innerText;
|
||||
modal.querySelector('.task-nm-kr').value = document.querySelector('.overview .task-nm-kr').innerText;
|
||||
modal.querySelector('.contract-period').value = document.querySelector('.overview .contract-period').innerText;
|
||||
modal.querySelector('.projectmanager-nm').value = document.querySelector('.overview .projectmanager-nm').innerText;
|
||||
modal.querySelector('.manager-nm').value = document.querySelector('.overview .manager-nm').innerText;
|
||||
modal.querySelector('.contract-date').value = document.querySelector('.overview .contract-date').innerText;
|
||||
modal.querySelector('.commencement-date').value = document.querySelector('.overview .commencement-date').innerText;
|
||||
modal.querySelector('.scheduled-completion-date').value = document.querySelector('.overview .scheduled-completion-date').innerText;
|
||||
modal.querySelector('.scheuled-commencement-date').value = document.querySelector('.overview .scheuled-commencement-date').innerText;
|
||||
|
||||
modal.querySelectorAll('.joint-contract-company-name').forEach(company => { company.contentEditable = true; company.style.color = '#FF3D00'; })
|
||||
// 공동도급 수정, 색상변화, 숫자 제한
|
||||
modal.querySelectorAll('.joint-contract-company-shares, .joint-contract-krw, .joint-contract-usd').forEach(joint => {
|
||||
joint.contentEditable = true;
|
||||
joint.style.color = '#FF3D00';
|
||||
joint.addEventListener('input', (event) => {
|
||||
restrictToNumber(joint, event);
|
||||
calculateTotalJointContract(joint);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
modal.querySelector('.project-no').innerText = document.querySelector('.overview .project-no').innerText;
|
||||
modal.querySelector('.task-type').innerText = document.querySelector('.overview .task-type').innerText;
|
||||
modal.querySelector('.bid').innerText = document.querySelector('.overview .bid').innerText;
|
||||
modal.querySelector('.client').innerText = document.querySelector('.overview .client').innerText;
|
||||
modal.querySelector('.department').innerText = document.querySelector('.overview .department').innerText;
|
||||
modal.querySelector('.currency-code').innerText = document.querySelector('.overview .currency-code').innerText;
|
||||
modal.querySelector('.task-nm-kr').innerText = document.querySelector('.overview .task-nm-kr').innerText;
|
||||
modal.querySelector('.contract-period').innerText = document.querySelector('.overview .contract-period').innerText;
|
||||
modal.querySelector('.projectmanager-nm').innerText = document.querySelector('.overview .projectmanager-nm').innerText;
|
||||
modal.querySelector('.manager-nm').innerText = document.querySelector('.overview .manager-nm').innerText;
|
||||
modal.querySelector('.contract-date').innerText = document.querySelector('.overview .contract-date').innerText;
|
||||
modal.querySelector('.commencement-date').innerText = document.querySelector('.overview .commencement-date').innerText;
|
||||
modal.querySelector('.scheduled-completion-date').innerText = document.querySelector('.overview .scheduled-completion-date').innerText;
|
||||
modal.querySelector('.scheuled-commencement-date').innerText = document.querySelector('.overview .scheuled-commencement-date').innerText;
|
||||
}
|
||||
|
||||
const jointTable = modal.querySelector('.overview-table-container')
|
||||
// 탭 컨테이너 가로스크롤 휠 이벤트 추가
|
||||
jointTable.addEventListener('wheel', (e)=> {
|
||||
e.preventDefault();
|
||||
jointTable.scrollLeft += e.deltaY;
|
||||
});
|
||||
|
||||
// 토글 처리
|
||||
if (modal.style.display === 'block') {
|
||||
modal.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
modal.style.display = 'block';
|
||||
modalWrapper.style.display = 'block';
|
||||
|
||||
});
|
||||
|
||||
// section-middle 과업기간 모달 열기
|
||||
document.querySelector('.overview .box-header--right.task-period')?.addEventListener('click', async () => {
|
||||
const modal = document.querySelector('.overview-modal.task-period');
|
||||
const modalWrapper = document.querySelector('.overview-modal-wrapper');
|
||||
|
||||
modal.querySelector('.contract-date').innerText = document.querySelector('.overview .contract-date').innerText;
|
||||
modal.querySelector('.commencement-date').innerText = document.querySelector('.overview .commencement-date').innerText;
|
||||
modal.querySelector('.scheduled-completion-date').innerText = document.querySelector('.overview .scheduled-completion-date').innerText;
|
||||
modal.querySelector('.completion-date').innerText = document.querySelector('.overview .completion-date').innerText;
|
||||
|
||||
// 과업 중지 이력 생성
|
||||
makeTaskHistory();
|
||||
|
||||
// 토글 처리
|
||||
if (modal.style.display === 'block') {
|
||||
modal.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
modal.style.display = 'block';
|
||||
modalWrapper.style.display = 'block';
|
||||
});
|
||||
|
||||
// section-middle 과업중지 이력 리스트 추가
|
||||
document.querySelector('.overview-modal.task-period .box-header--right button')?.addEventListener('click', () => {
|
||||
const ul = document.querySelector('.overview-modal.task-period .overview-modal-body ul');
|
||||
const li = document.createElement('li');
|
||||
|
||||
// 차수
|
||||
const numberDiv = document.createElement('div');
|
||||
numberDiv.className = 'work-list-number';
|
||||
const orderInput = document.createElement('input');
|
||||
orderInput.type = 'text';
|
||||
orderInput.className = 'order work-list-number';
|
||||
|
||||
numberDiv.appendChild(orderInput);
|
||||
|
||||
// 중지일자
|
||||
const dateDiv = document.createElement('div');
|
||||
dateDiv.className = 'work-list-date';
|
||||
const dateInput = document.createElement('input');
|
||||
dateInput.className = 'suspension-date work-list-date';
|
||||
dateInput.type = 'date';
|
||||
|
||||
dateDiv.appendChild(dateInput);
|
||||
|
||||
// 중지사유
|
||||
const reasonDiv = document.createElement('div');
|
||||
reasonDiv.className = 'work-list-script';
|
||||
const reasonInput = document.createElement('input');
|
||||
reasonInput.className = 'suspension-reason work-list-script';
|
||||
reasonInput.type = 'text';
|
||||
|
||||
reasonDiv.appendChild(reasonInput);
|
||||
|
||||
// 재개일자
|
||||
const redateDiv = document.createElement('div');
|
||||
redateDiv.className = 'work-list-redate';
|
||||
const redateInput = document.createElement('input');
|
||||
redateInput.className = 'resumption-date work-list-redate';
|
||||
redateInput.type = 'date';
|
||||
|
||||
redateDiv.appendChild(redateInput);
|
||||
|
||||
// 협의내용
|
||||
const detailDiv = document.createElement('div');
|
||||
detailDiv.className = 'work-list-detail';
|
||||
const consultInput = document.createElement('input');
|
||||
consultInput.className = 'consuletation-content work-list-detail';
|
||||
consultInput.type = 'text';
|
||||
|
||||
detailDiv.appendChild(consultInput);
|
||||
|
||||
// 변경 일자
|
||||
const changeDiv = document.createElement('div');
|
||||
changeDiv.className = 'work-list-changedate';
|
||||
const changeInput = document.createElement('input');
|
||||
changeInput.className = 'change-date work-list-changedate';
|
||||
changeInput.type = 'date';
|
||||
|
||||
changeDiv.appendChild(changeInput);
|
||||
|
||||
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'xs-btn-type';
|
||||
const img = document.createElement('img');
|
||||
img.className = 'icon';
|
||||
img.src = '/main/img/overview/icon-close-111.svg';
|
||||
img.alt = 'icon-close-111';
|
||||
btn.appendChild(img);
|
||||
// 과업 중지이력 리스트 개별 삭제버튼
|
||||
btn.addEventListener('click', async (e) => {
|
||||
li.remove();
|
||||
});
|
||||
|
||||
li.append(numberDiv, dateDiv, reasonDiv, redateDiv, detailDiv, changeDiv, btn);
|
||||
|
||||
ul.appendChild(li);
|
||||
|
||||
});
|
||||
|
||||
if(!overviewVars.overseas){
|
||||
// section-middle 공동도급 열 추가
|
||||
document.querySelector('.overview-modal.section-middle .joint-contract-add')?.addEventListener('click', () => {
|
||||
const table = document.querySelector('.overview-modal.section-middle .overview-table-container table');
|
||||
const headerRow = table.querySelector('thead tr');
|
||||
const currentCols = headerRow.querySelectorAll('th').length;
|
||||
|
||||
// 총계와 주관사
|
||||
const newIndex = currentCols - 1;
|
||||
document.querySelector('.overview-modal .total-joint-contract-company-name').innerText = newIndex;
|
||||
const newTh = document.createElement('th');
|
||||
if (newIndex < 2) {
|
||||
newTh.innerText = '주관사';
|
||||
} else {
|
||||
newTh.innerText = `공동도급${newIndex - 1}`;
|
||||
}
|
||||
headerRow.appendChild(newTh);
|
||||
|
||||
const tbodyRows = table.querySelectorAll('tbody tr');
|
||||
|
||||
const defaultValues = ['회사명', '0', '0', '0'];
|
||||
const classList = ['joint-contract-company-name', 'joint-contract-company-shares', 'joint-contract-krw', 'joint-contract-usd'];
|
||||
|
||||
tbodyRows.forEach((row, idx) => {
|
||||
const td = document.createElement('td');
|
||||
td.className = classList[idx];
|
||||
td.innerText = defaultValues[idx];
|
||||
td.contentEditable = true;
|
||||
td.style.color = '#FF3D00';
|
||||
// 공동도급 숫자 제한
|
||||
if (idx > 0) {
|
||||
td.addEventListener('input', (event) => {
|
||||
restrictToNumber(td, event);
|
||||
calculateTotalJointContract(td);
|
||||
});
|
||||
}
|
||||
row.appendChild(td);
|
||||
});
|
||||
});
|
||||
|
||||
// section-middle 공동도급 열 삭제
|
||||
document.querySelector('.overview-modal.section-middle .joint-contract-delete')?.addEventListener('click', () => {
|
||||
const table = document.querySelector('.overview-modal.section-middle .overview-table-container table');
|
||||
const theadRow = table.querySelector('thead tr');
|
||||
const ths = theadRow.querySelectorAll('th');
|
||||
// 주관사까지 삭제했을때 계산할 값이 없으므로 초기화
|
||||
if (ths.length === 3){
|
||||
document.querySelector('.overview-modal .total-joint-contract-company-name').innerText = 0;
|
||||
document.querySelectorAll('.overview-modal .total-joint-contract-company-shares, .overview-modal .total-joint-contract-krw, .overview-modal .total-joint-contract-usd').forEach(el => el.innerText = 0);
|
||||
}
|
||||
if (ths.length <= 2) {
|
||||
alert("삭제할 수 있는 열이 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 마지막 th 삭제
|
||||
theadRow.removeChild(ths[ths.length - 1]);
|
||||
|
||||
// tbody의 마지막 td 삭제
|
||||
const tbodyRows = table.querySelectorAll('tbody tr');
|
||||
tbodyRows.forEach(row => {
|
||||
const tds = row.querySelectorAll('td');
|
||||
if (tds.length > 2) {
|
||||
row.removeChild(tds[tds.length - 1]);
|
||||
}
|
||||
});
|
||||
// 삭제시에 총계 계산
|
||||
document.querySelectorAll('.overview-modal.section-middle .overview-table-container td').forEach(td => {
|
||||
calculateTotalJointContract(td);
|
||||
});
|
||||
|
||||
// 총 회사 수 업데이트
|
||||
document.querySelector('.overview-modal .total-joint-contract-company-name').innerText = ths.length - 3;
|
||||
});
|
||||
}
|
||||
|
||||
// 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Secion-Middle 모달 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
|
||||
//🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽 Secion-Right 모달 시작 🔽🔽🔽🔽🔽🔽🔽🔽🔽🔽
|
||||
|
||||
// section-right 캘린더 주요일정 하루종일 버튼
|
||||
document.querySelector('.overview-modal.schedule .custom-checkbox.all-day')?.addEventListener('change', () => {
|
||||
const allDay = document.querySelector('.overview-modal.schedule .custom-checkbox.all-day input');
|
||||
const startTime = document.querySelector('.overview-modal.schedule .startTime');
|
||||
const endTime = document.querySelector('.overview-modal.schedule .endTime');
|
||||
|
||||
const isChecked = allDay.checked;
|
||||
|
||||
startTime.disabled = isChecked;
|
||||
endTime.disabled = isChecked;
|
||||
|
||||
if (isChecked) {
|
||||
startTime.value = '';
|
||||
endTime.value = '';
|
||||
} else {
|
||||
startTime.value = '00:00';
|
||||
endTime.value = '23:59';
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// section-right 주요일정 Color 클릭 효과
|
||||
document.querySelectorAll('.overview-modal .color .label-color')?.forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
document.querySelectorAll('.overview-modal .color .label-color').forEach(label => {
|
||||
label.classList.remove('on');
|
||||
});
|
||||
e.target.classList.add('on');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼 Secion-Right 모달 끝 🔼🔼🔼🔼🔼🔼🔼🔼🔼🔼
|
||||
1130
views/main/jsm/overview/overviewPageRenderer.js
Normal file
1130
views/main/jsm/overview/overviewPageRenderer.js
Normal file
File diff suppressed because it is too large
Load Diff
44
views/main/jsm/overview/overviewVariable.js
Normal file
44
views/main/jsm/overview/overviewVariable.js
Normal file
@@ -0,0 +1,44 @@
|
||||
export const overviewVars = {
|
||||
// 시설규모 탭안에 셀요소들을 담아놓은 객체
|
||||
sectionTabData : {},
|
||||
|
||||
originalSectionTabData : {},
|
||||
|
||||
// 캘린더 현재년도
|
||||
currentYear : new Date().getFullYear(),
|
||||
|
||||
// 캘린더 현재월
|
||||
currentMonth : new Date().getMonth(),
|
||||
|
||||
// 공동도급 정렬 배열
|
||||
companyArr : [],
|
||||
|
||||
// 이미지 파일들을 담은 전역변수
|
||||
filesArr : [],
|
||||
|
||||
// 이미지 파일이름만을 담은 전역변수
|
||||
filesNameArr : [],
|
||||
|
||||
// 이미지 파일들의 size를 담은 전역변수
|
||||
filesSizeArr : [],
|
||||
|
||||
originalFilesArr : [],
|
||||
|
||||
originalFilesNameArr : [],
|
||||
|
||||
originalFilesSizeArr : [],
|
||||
|
||||
deleteImgArr : [],
|
||||
|
||||
// DB에 있는 주요 현안 및 이슈 담은 전역변수
|
||||
issueData : {},
|
||||
|
||||
// 삭제할 과업중지 이력을 모아놓을 배열
|
||||
deleteTaskHistory : [],
|
||||
}
|
||||
|
||||
// window.overviewVars = overviewVars;
|
||||
|
||||
|
||||
|
||||
|
||||
574
views/main/jsm/popup.js
Normal file
574
views/main/jsm/popup.js
Normal file
@@ -0,0 +1,574 @@
|
||||
//형태는 ?path=fullPath(ex.http://gsim.hanmac..../1.jpg)&data=eadNadfl
|
||||
//data는 json({key:value})을 stringify한 후에 btoa로 base64로 변경해서 달기.
|
||||
//ex)
|
||||
//const jsonData = JSON.stringify({ key1: "value1", key2: "value2" });
|
||||
//const encodedData = btoa(jsonData);
|
||||
//?path=${fullPath}&data=${encodedData}
|
||||
//넘어간 data는 모두 표출됨, 숨길 data는 key 앞에 $붙여주기(type, password는 그냥써도 무조건 표출X)
|
||||
|
||||
//받는 형태는 pdf, img(png, jpg, jpeg, panorama), mp4, gsim(gsimViewer로 redirect), ifc, 미지원 6가지
|
||||
const searchParam = new URLSearchParams(window.location.search);
|
||||
//serchParam 비워주기 - debug off
|
||||
// window.history.replaceState(null, null, '/')
|
||||
|
||||
const fullPath = searchParam.get('path');
|
||||
let ext = fullPath.toLowerCase().split('.').pop();//가장 마지막.이후 확장자
|
||||
|
||||
const dataA = searchParam.get('data');
|
||||
// title filename으로 변경
|
||||
document.title = fullPath.split('/').pop();
|
||||
let data = (dataA)?JSON.parse((dataA)?decodeURIComponent(escape(atob(dataA))):'{}'):undefined;
|
||||
//data로 ext 넘어오면 무조건 변경
|
||||
ext = data.$ext;
|
||||
|
||||
|
||||
// 🚩3d모델뷰어 썸네일 생성을 위해 아래의 변수 추가
|
||||
const thumbnail_key = decodeURIComponent(searchParam.get('thumbnail_key'));
|
||||
const path_name = searchParam.get('path_name');
|
||||
const resourcePath = decodeURIComponent(searchParam.get('resourcePath'));
|
||||
const dataId = searchParam.get('dataId');
|
||||
|
||||
if(data && Object.keys(data).length>0 && (data.$type == 'text'|| data.type == 'text')){
|
||||
//type text로 넘어왔을때 무조건 text뷰어로 연결
|
||||
_openText(fullPath);
|
||||
}else{
|
||||
switch(ext){
|
||||
case 'pdf':
|
||||
_openPdf(fullPath,data);
|
||||
break;
|
||||
case 'mp4':
|
||||
case 'mov':
|
||||
case 'webm':
|
||||
_openVideo(fullPath);
|
||||
break;
|
||||
case 'png':
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
case 'webp':
|
||||
case 'gif':
|
||||
if(data && Object.keys(data).length>0 && (data.$type == 'panorama'||data.type == 'panorama')){
|
||||
// data 있으면서 type이 panorama인 경우
|
||||
_openPano(fullPath);
|
||||
}else{
|
||||
_openImage(fullPath);
|
||||
}
|
||||
break;
|
||||
case 'gsim':
|
||||
// gsim viewer 주소로 변경 필요
|
||||
window.location.href = `/libs/gsimViewer/gsimViewer.html?${searchParam.toString()}`;
|
||||
break;
|
||||
case 'ifc':
|
||||
_openIfc(fullPath, thumbnail_key, resourcePath, dataId, path_name);
|
||||
break;
|
||||
case 'txt':
|
||||
case 'log':
|
||||
case 'md':
|
||||
_openText(fullPath);
|
||||
break;
|
||||
case 'url':
|
||||
_openUrl(fullPath);
|
||||
break;
|
||||
case 'zip':
|
||||
_openZip(fullPath);
|
||||
break;
|
||||
case 'glb':
|
||||
case 'gltf':
|
||||
case 'obj':
|
||||
case 'stl':
|
||||
case 'fbx':
|
||||
case '3dm':
|
||||
_open3d(fullPath, thumbnail_key, resourcePath, dataId, path_name);
|
||||
break;
|
||||
case 'html':
|
||||
_openHtml(fullPath);
|
||||
break;
|
||||
}
|
||||
if(data && Object.keys(data).length>0){
|
||||
let keys = Object.keys(data).filter(key=>key.indexOf('$') != 0);
|
||||
if(keys.length > 0){
|
||||
_drawMeta(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _openText(path){
|
||||
// ext를 매개변수로 받아도 md파일이 txt로 넘어오기때문에 경로에서 직접 ext 추출
|
||||
let ext = path.split('.').pop();
|
||||
ext = ext.split('%')[0];
|
||||
fetch(path).then(res => res.text()).then(data=>{
|
||||
let pre;
|
||||
// md 파일 확장자 일때 파싱 후 HTML 삽입
|
||||
if(ext === 'md'){
|
||||
pre = document.createElement('div');
|
||||
pre.classList.add('markdown-wrap');
|
||||
// mermaid 시퀀스 다이어 그램 변환
|
||||
const renderer = new marked.Renderer();
|
||||
const originalCode = renderer.code.bind(renderer);
|
||||
renderer.code = (code) => {
|
||||
if(code.lang === 'mermaid') return `<pre class="mermaid">${code.text}</pre>`
|
||||
return originalCode(code);
|
||||
}
|
||||
marked.setOptions({ gfm: true, breaks: true, renderer});
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('markdown-body');
|
||||
div.innerHTML = marked.parse(data);
|
||||
// mermaid 초기화 및 생성
|
||||
const mermaidEls = div.querySelectorAll('.mermaid');
|
||||
if(mermaidEls.length > 0){
|
||||
mermaid.initialize({ startOnLoad: false });
|
||||
mermaid.init(undefined, mermaidEls);
|
||||
}
|
||||
pre.appendChild(div);
|
||||
} else {
|
||||
pre = document.createElement('pre');
|
||||
pre.style.width = '100%';
|
||||
pre.style.height = '100%';
|
||||
pre.style.objectFit = 'contain';
|
||||
pre.textContent = data;
|
||||
}
|
||||
document.getElementById('popup_viewer').appendChild(pre);
|
||||
if(ext === 'md')hljs.highlightAll();
|
||||
}).catch(err=>console.error('파일 로드 실패', err));
|
||||
}
|
||||
|
||||
function _openZip(path){
|
||||
fetch(path).then(async data=>{
|
||||
let zblob = await data.blob();
|
||||
|
||||
const zip = new JSZip();
|
||||
await zip.loadAsync(zblob);
|
||||
let folderText = ``;
|
||||
let fileText = ``;
|
||||
|
||||
zip.forEach((relativePath, zipEntry) => {
|
||||
let slashIdx = relativePath.indexOf('/');
|
||||
if(slashIdx == -1 || slashIdx == relativePath.length -1){
|
||||
if(zipEntry.dir){
|
||||
folderText += `(폴더) ${zipEntry.name.split('/')[0]} \n`;
|
||||
}else{
|
||||
fileText += `(파일) ${zipEntry.name} \n`
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const pre = document.createElement('pre');
|
||||
pre.style.width = '100%';
|
||||
pre.style.height = '100%';
|
||||
pre.style.objectFit = 'contain';
|
||||
pre.textContent = `${folderText}${(folderText == ``)?'':'\n'}${fileText}`;
|
||||
document.getElementById('popup_viewer').appendChild(pre);
|
||||
})
|
||||
}
|
||||
|
||||
function _openUrl(path){
|
||||
fetch(path).then(res => res.text()).then(data=>{
|
||||
let url = data.split('URL=')[1];
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = url;
|
||||
iframe.style.width = '100%'; // 컨테이너에 맞게 너비 설정
|
||||
iframe.style.height = '100%'; // 컨테이너에 맞게 높이 설정
|
||||
iframe.style.border = 'none'; // 테두리 제거 (선택 사항)
|
||||
|
||||
document.getElementById('popup_viewer').appendChild(iframe);
|
||||
}).catch(err=>console.error('파일 로드 실패', err));
|
||||
}
|
||||
|
||||
function _openHtml(path){
|
||||
fetch(path).then(res => res.text()).then(data=>{
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.srcdoc = data;
|
||||
iframe.style.width = '100%'; // 컨테이너에 맞게 너비 설정
|
||||
iframe.style.height = '100%'; // 컨테이너에 맞게 높이 설정
|
||||
iframe.style.border = 'none'; // 테두리 제거 (선택 사항)
|
||||
|
||||
document.getElementById('popup_viewer').appendChild(iframe);
|
||||
}).catch(err=>console.error('파일 로드 실패', err));
|
||||
}
|
||||
|
||||
// function _openPdf(path,data){
|
||||
// let pdf_options = {
|
||||
// url: path,
|
||||
// initialPage: 1,
|
||||
// };
|
||||
// if(data && Object.keys(data).length > 0 && (data.$password || data.password)){
|
||||
// pdf_options.password = (data.$password)?data.$password:data.password;
|
||||
// }
|
||||
|
||||
// let iframe = document.createElement('iframe');
|
||||
// iframe.src = `/libs/pdfViewer/web/viewer.html`;
|
||||
// document.getElementById('popup_viewer').appendChild(iframe);
|
||||
// iframe.addEventListener('load', () => {
|
||||
// // pdf 실행 시 무조건 1페이지부터 보이도록 기존 pdf 히스토리 삭제
|
||||
// try {
|
||||
// let appWin = iframe.contentWindow;
|
||||
// // PDF.js의 기본 히스토리 키
|
||||
// appWin.localStorage.removeItem('pdfjs.history');
|
||||
// // 또는 여러 키 삭제
|
||||
// Object.keys(appWin.localStorage).forEach(k => {
|
||||
// if (k.startsWith('pdfjs.history') || k.startsWith('pdfjs.preferences')) {
|
||||
// appWin.localStorage.removeItem(k);
|
||||
// }
|
||||
// });
|
||||
// } catch (e) { /* ignore */ }
|
||||
|
||||
// let app = document.querySelector('#popup_viewer iframe').contentWindow.PDFViewerApplication;
|
||||
// app.pdfCursorTools._handTool.activate();
|
||||
|
||||
// app.open(pdf_options);
|
||||
|
||||
// iframe.width = '100%';
|
||||
// iframe.height = '100%';
|
||||
// });
|
||||
// }
|
||||
|
||||
function _openPdf(path, data) {
|
||||
let pdf_options = {
|
||||
url: path,
|
||||
initialPage: 1,
|
||||
};
|
||||
|
||||
if (data && Object.keys(data).length > 0 && (data.$password || data.password)) {
|
||||
pdf_options.password = (data.$password) ? data.$password : data.password;
|
||||
}
|
||||
|
||||
let iframe = document.createElement('iframe');
|
||||
iframe.src = `/libs/pdfViewer/web/viewer.html`;
|
||||
document.getElementById('popup_viewer').appendChild(iframe);
|
||||
|
||||
iframe.addEventListener('load', () => {
|
||||
try {
|
||||
let appWin = iframe.contentWindow;
|
||||
|
||||
// 히스토리 삭제
|
||||
appWin.localStorage.removeItem('pdfjs.history');
|
||||
Object.keys(appWin.localStorage).forEach(k => {
|
||||
if (k.startsWith('pdfjs.history') || k.startsWith('pdfjs.preferences')) {
|
||||
appWin.localStorage.removeItem(k);
|
||||
}
|
||||
});
|
||||
|
||||
let app = appWin.PDFViewerApplication;
|
||||
let isRendering = false;
|
||||
let lastScale = 1;
|
||||
|
||||
app.initializedPromise.then(() => {
|
||||
const PDFPageView = appWin.PDFPageView;
|
||||
|
||||
// draw 함수를 완전히 오버라이드
|
||||
const originalDraw = PDFPageView.prototype.draw;
|
||||
PDFPageView.prototype.draw = function() {
|
||||
// 고해상도 설정
|
||||
this.outputScale = {
|
||||
sx: (window.devicePixelRatio || 1) * 2,
|
||||
sy: (window.devicePixelRatio || 1) * 2
|
||||
};
|
||||
return originalDraw.call(this);
|
||||
};
|
||||
|
||||
// reset 함수도 오버라이드하여 캔버스 완전 삭제
|
||||
const originalReset = PDFPageView.prototype.reset;
|
||||
PDFPageView.prototype.reset = function() {
|
||||
// 캔버스 완전히 제거
|
||||
if (this.canvas) {
|
||||
if (this.canvas.parentNode) {
|
||||
this.canvas.parentNode.removeChild(this.canvas);
|
||||
}
|
||||
this.canvas.width = 0;
|
||||
this.canvas.height = 0;
|
||||
this.canvas = null;
|
||||
}
|
||||
|
||||
// 렌더 태스크 취소
|
||||
if (this.renderTask) {
|
||||
this.renderTask.cancel();
|
||||
this.renderTask = null;
|
||||
}
|
||||
|
||||
return originalReset.call(this);
|
||||
};
|
||||
|
||||
if (app.pdfViewer) {
|
||||
app.pdfViewer.textLayerMode = 1;
|
||||
app.pdfViewer.useOnlyCssZoom = false;
|
||||
}
|
||||
|
||||
// 확대 시 완전 재렌더링
|
||||
app.eventBus.on('scalechanging', function(evt) {
|
||||
if (isRendering) return;
|
||||
|
||||
const newScale = evt.scale;
|
||||
const scaleDiff = Math.abs(newScale - lastScale);
|
||||
|
||||
// 스케일 변화가 있을 때만 재렌더링
|
||||
if (scaleDiff > 0.01) {
|
||||
isRendering = true;
|
||||
|
||||
setTimeout(() => {
|
||||
if (app.pdfViewer && app.pdfViewer._pages) {
|
||||
app.pdfViewer._pages.forEach(pageView => {
|
||||
// 1. 렌더 태스크 강제 취소
|
||||
if (pageView.renderTask) {
|
||||
pageView.renderTask.cancel();
|
||||
pageView.renderTask = null;
|
||||
}
|
||||
|
||||
// 2. 캔버스 완전 제거
|
||||
if (pageView.canvas) {
|
||||
const parent = pageView.canvas.parentNode;
|
||||
if (parent) {
|
||||
parent.removeChild(pageView.canvas);
|
||||
}
|
||||
pageView.canvas.width = 0;
|
||||
pageView.canvas.height = 0;
|
||||
pageView.canvas = null;
|
||||
}
|
||||
|
||||
// 3. viewport 업데이트
|
||||
if (pageView.pdfPage) {
|
||||
pageView.viewport = pageView.pdfPage.getViewport({
|
||||
scale: newScale,
|
||||
rotation: pageView.rotation || 0
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 페이지 완전 리셋
|
||||
pageView.reset();
|
||||
|
||||
// 5. 새로 그리기
|
||||
pageView.draw();
|
||||
});
|
||||
}
|
||||
|
||||
lastScale = newScale;
|
||||
isRendering = false;
|
||||
}, 200); // 딜레이 증가
|
||||
}
|
||||
});
|
||||
|
||||
// 스크롤/페이지 변경 시에도 재렌더링
|
||||
app.eventBus.on('updateviewarea', function(evt) {
|
||||
if (isRendering) return;
|
||||
|
||||
// 현재 보이는 페이지만 재렌더링
|
||||
const visiblePages = app.pdfViewer._getVisiblePages();
|
||||
if (visiblePages && visiblePages.views) {
|
||||
visiblePages.views.forEach(view => {
|
||||
if (view && view.div && view.div.classList.contains('pdfPage')) {
|
||||
const pageView = app.pdfViewer._pages[view.id - 1];
|
||||
if (pageView && !pageView.renderTask) {
|
||||
pageView.draw();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.pdfCursorTools._handTool.activate();
|
||||
app.open(pdf_options);
|
||||
|
||||
} catch (e) {
|
||||
console.error('PDF 설정 오류:', e);
|
||||
}
|
||||
|
||||
iframe.width = '100%';
|
||||
iframe.height = '100%';
|
||||
});
|
||||
}
|
||||
|
||||
function _openVideo(path){
|
||||
const popupVideo = document.createElement('video');
|
||||
popupVideo.autoplay = true;
|
||||
popupVideo.muted = true;
|
||||
popupVideo.playsInline = true;
|
||||
popupVideo.controls = true;
|
||||
popupVideo.crossOrigin = 'anonymous';
|
||||
|
||||
const sourceElement = document.createElement('source');
|
||||
sourceElement.src = path;
|
||||
|
||||
popupVideo.style.width = '100%';
|
||||
popupVideo.style.height = '100%';
|
||||
popupVideo.style.objectFit = 'contain';
|
||||
|
||||
document.getElementById('popup_viewer').appendChild(popupVideo);
|
||||
popupVideo.appendChild(sourceElement);
|
||||
}
|
||||
|
||||
function _openPano(path){
|
||||
let panorama = pannellum.viewer('popup_viewer',{
|
||||
"type": "equirectangular",
|
||||
"panorama": path,
|
||||
"autoLoad": true,
|
||||
"compass":false,
|
||||
})
|
||||
}
|
||||
|
||||
function _openImage(path){
|
||||
const img = document.createElement('img');
|
||||
img.src = path;
|
||||
document.getElementById('popup_viewer').appendChild(img);
|
||||
img.style.width = '100%';
|
||||
img.style.height = '100%';
|
||||
img.style.objectFit = 'contain';
|
||||
|
||||
img.addEventListener('click',()=>{
|
||||
document.getElementById('large-img-container').style.display = 'block';
|
||||
centerImage();
|
||||
})
|
||||
|
||||
const container = document.getElementById('large-img-container');
|
||||
const image = document.getElementById('large-image');
|
||||
const zoomInfo = document.getElementById('large-img-zoomInfo');
|
||||
image.src = path;
|
||||
|
||||
let isDragging = false;
|
||||
let startX, startY;
|
||||
let translateX = 0;
|
||||
let translateY = 0;
|
||||
let scale = 1;
|
||||
|
||||
// 드래그 시작
|
||||
function dragStart(e) {
|
||||
if (e.target !== image) return;
|
||||
|
||||
isDragging = true;
|
||||
startX = e.clientX - translateX;
|
||||
startY = e.clientY - translateY;
|
||||
|
||||
// 드래그 중일 때 transition 제거
|
||||
image.style.transition = 'none';
|
||||
}
|
||||
|
||||
// 드래그 중
|
||||
function drag(e) {
|
||||
if (!isDragging) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// 현재 마우스 위치에서 시작 위치를 빼서 이동 거리 계산
|
||||
translateX = e.clientX - startX;
|
||||
translateY = e.clientY - startY;
|
||||
|
||||
updateImageTransform();
|
||||
}
|
||||
|
||||
// 드래그 종료
|
||||
function dragEnd() {
|
||||
if (!isDragging) return;
|
||||
|
||||
isDragging = false;
|
||||
// 드래그 종료 시 부드러운 transition 효과 복원
|
||||
image.style.transition = 'transform 0.1s ease-out';
|
||||
}
|
||||
|
||||
// 확대/축소
|
||||
function zoom(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// 현재 이미지의 실제 위치와 크기
|
||||
const imageRect = image.getBoundingClientRect();
|
||||
|
||||
// 마우스 포인터의 화면상 좌표
|
||||
const mouseX = e.clientX;
|
||||
const mouseY = e.clientY;
|
||||
|
||||
// 마우스 포인터의 이미지상 상대 좌표
|
||||
const mouseImageX = mouseX - imageRect.left;
|
||||
const mouseImageY = mouseY - imageRect.top;
|
||||
|
||||
// 이전 스케일 저장
|
||||
const prevScale = scale;
|
||||
|
||||
// 새로운 스케일 계산
|
||||
const delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail));
|
||||
const scaleFactor = 0.1;
|
||||
|
||||
if (delta > 0) {
|
||||
scale = Math.min(4, scale + scaleFactor);
|
||||
} else {
|
||||
scale = Math.max(0.1, scale - scaleFactor);
|
||||
}
|
||||
|
||||
// 스케일 변화율
|
||||
const scaleRatio = scale / prevScale;
|
||||
|
||||
// 새로운 translate 값 계산
|
||||
// 마우스 포인터 위치는 고정되어야 하므로, 스케일 변화에 따른 위치 조정
|
||||
translateX = mouseX - (((mouseX - translateX) * scaleRatio));
|
||||
translateY = mouseY - (((mouseY - translateY) * scaleRatio));
|
||||
|
||||
updateImageTransform();
|
||||
updateZoomInfo();
|
||||
}
|
||||
|
||||
// 이미지 transform 업데이트
|
||||
function updateImageTransform() {
|
||||
// transform의 원점을 이미지의 중심으로 설정
|
||||
const originX = image.offsetWidth / 2;
|
||||
const originY = image.offsetHeight / 2;
|
||||
image.style.transformOrigin = `${originX}px ${originY}px`;
|
||||
image.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
|
||||
}
|
||||
|
||||
// 줌 정보 업데이트
|
||||
function updateZoomInfo() {
|
||||
zoomInfo.textContent = `${Math.round(scale * 100)}%`;
|
||||
}
|
||||
|
||||
// 이미지 초기 위치 설정
|
||||
function centerImage() {
|
||||
translateX = (container.offsetWidth - image.offsetWidth) / 2;
|
||||
translateY = (container.offsetHeight - image.offsetHeight) / 2;
|
||||
updateImageTransform();
|
||||
}
|
||||
|
||||
// 이벤트 리스너 등록
|
||||
container.addEventListener('mousedown', dragStart);
|
||||
window.addEventListener('mousemove', drag);
|
||||
window.addEventListener('mouseup', dragEnd);
|
||||
container.addEventListener('wheel', zoom);
|
||||
container.addEventListener('contextmenu',(e)=>{
|
||||
e.preventDefault();
|
||||
container.style.display = 'none';
|
||||
})
|
||||
|
||||
// 더블클릭으로 원래 크기로 복원
|
||||
container.addEventListener('dblclick', () => {
|
||||
scale = 1;
|
||||
updateZoomInfo();
|
||||
});
|
||||
}
|
||||
|
||||
function _openIfc(path, thumbnail_key, resourcePath, dataId, path_name){
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.onload = () => {
|
||||
iframe.contentWindow.postMessage({ path, thumbnail_key, resourcePath, dataId, path_name }, '*'); // path 값을 iframe에 전달
|
||||
};
|
||||
iframe.src = `/libs/ifcViewer/index.html`;
|
||||
iframe.width = '100%';
|
||||
iframe.height = '100%';
|
||||
document.getElementById('popup_viewer').appendChild(iframe);
|
||||
}
|
||||
|
||||
function _open3d(path, thumbnail_key, resourcePath, dataId, path_name){
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.onload = () => {
|
||||
iframe.contentWindow.postMessage({ path, thumbnail_key, resourcePath, dataId, path_name }, '*'); // path 값을 iframe에 전달
|
||||
};
|
||||
iframe.src = `/libs/3dViewer/index.html`;
|
||||
iframe.width = '100%';
|
||||
iframe.height = '100%';
|
||||
document.getElementById('popup_viewer').appendChild(iframe);
|
||||
}
|
||||
|
||||
function _drawMeta(data){
|
||||
let container = document.getElementById('meta-data');
|
||||
container.style.display = 'block';
|
||||
Object.keys(data).forEach(item=>{
|
||||
if(!item.startsWith('$') && item != 'type' && item != 'password'){
|
||||
let line = `<span class="key">${item}</span> : <span>${data[item]}</span><br>`;
|
||||
container.innerHTML += line;
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user