Merge remote-tracking branch 'origin/master' into master

Resolve conflict in PortalNoticeManService.java import block:
union both sides — keep Arrays (HEAD) and LocalDateTime/ArrayList/Collections (origin/master),
all of which are used in the merged body.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Rinjae-gf63
2026-06-16 16:41:57 +09:00
145 changed files with 5797 additions and 2318 deletions
@@ -38,12 +38,22 @@
var loginUser = '<%= loginVo.getUserId() %>';
var url = '<c:url value="/onl/apim/approval/portalApprovalMan.json" />';
var url_view = '<c:url value="/onl/apim/approval/portalApprovalMan.view" />';
function dateFormat(cellvalue) {
if ( cellvalue == null || cellvalue.length < 8 )
return '';
return cellvalue.substr(0 ,4) + '-' +
cellvalue.substr(4, 2) + '-' +
cellvalue.substr(6, 2) ;
}
$(document).ready(function () {
var key = "${param.id}";
if (key) {
detail(key);
}
$("#expectEndDate").datepicker().inputmask("yyyy-mm-dd", {'autoUnmask': true});
$("#btn_app_approve").click(function () {
if (confirm("승인하시겠습니까?")) {
@@ -112,6 +122,28 @@
});
}
});
$("#btn_modify").click(function () {
if (confirm("수정하시겠습니까?")) {
$.ajax({
type: "POST",
url: url,
data: {cmd: "UPDATE", id: key, expectEndDate: $("#expectEndDate").val().replaceAll('-','')},
success: function (args) {
alert("수정되었습니다.");
if (window.dialogArguments && window.dialogArguments.self) {
window.dialogArguments.self.location.reload();
} else if (window.opener) {
window.opener.location.reload();
}
window.close();
},
error: function (e) {
alert(e.responseText);
}
});
}
});
$("#btn_close").click(function () {
window.close();
@@ -126,12 +158,25 @@
data: {cmd: 'DETAIL', id: key},
success: function (json) {
var data = json;
if (data.approvalStatus.code == 'END') {
$('#btn_modify').hide();
$('#expectEndDate').hide();
$('#spExpectEndDate').show();
$('#spExpectEndDate').text(dateFormat(data.expectEndDate));
} else {
$('#btn_modify').show();
$('#expectEndDate').show();
$('#spExpectEndDate').hide();
}
$("#approvalType").text(data.approvalType === 'USER' ? "법인사용자" : "API 사용");
$("#approvalStatus").text(data.approvalStatus.description);
$("#requesterName").text(data.requester.userName);
$("#approvalSubject").text(data.approvalSubject);
$("#approvalDetail").html(data.approvalDetail);
$("#createdDate").text(data.createdDate).inputmask("9999-99-99 99:99:99.999", {'autoUnmask': true});
$("#expectEndDate").val(data.expectEndDate);
if (data.approvers.some(approver =>
approver.approverStatus === 'CURRENT' &&
@@ -144,7 +189,7 @@
document.getElementById("btn_app_reject").style.display = "none";
}
if (data.approvalStatus.description === '배포실패') {
if (data.approvalStatus.code === 'FAILED') {
document.getElementById("btn_app_redeploy").style.display = "";
}
@@ -242,9 +287,14 @@
</tr>
<tr>
<th>상세 내용</th>
<td colspan="3">
<td>
<div id="approvalDetail" style="white-space: pre-wrap;"></div>
</td>
<th>예상완료일</th>
<td>
<input type="text" id="expectEndDate" style="width: 120px; text-align:center"/>
<span id="spExpectEndDate"></span>
</td>
</tr>
</table>
@@ -282,6 +332,9 @@
</table>
</div>
<div class="popup_button">
<button type="button" class="cssbtn" id="btn_modify" level="W"><i
class="material-icons">save</i>수정
</button>
<button type="button" class="cssbtn" style="display: none;" id="btn_app_approve" level="W"><i
class="material-icons">check</i>승인
</button>
@@ -17,10 +17,59 @@
<script language="javascript" >
var url = '<c:url value="/onl/apim/approval/portalApprovalMan.json" />';
var url_view = '<c:url value="/onl/apim/approval/portalApprovalMan.view" />';
var hardDeleteEnabled = ${hardDeleteEnabled};
const APPROVAL_TYPE_DISPLAY = {
'USER': '법인사용자',
'APP': 'API 사용'
};
function dateFormat(cellvalue, options, rowObject) {
if ( cellvalue == null || cellvalue.length < 8 )
return '';
return cellvalue.substr(0 ,4) + '-' +
cellvalue.substr(4, 2) + '-' +
cellvalue.substr(6, 2) ;
}
function deleteSelectedApprovals() {
var selectedIds = $("#grid").jqGrid('getGridParam', 'selarrrow');
if (selectedIds.length === 0) {
showAlert("삭제할 승인 요청을 선택해주세요.", {type: 'warning'});
return;
}
if (!hardDeleteEnabled) {
showAlert("완전삭제 기능이 비활성화되어 있습니다.", {type: 'error'});
return;
}
var confirmMsg = "선택된 " + selectedIds.length + "건을 완전삭제(DB 영구 삭제)하시겠습니까?";
showConfirm(confirmMsg, {
type: 'error',
onConfirm: function() {
showConfirm("최종 확인: 완전삭제는 되돌릴 수 없습니다. 정말 실행하시겠습니까?", {
type: 'error',
title: '최종 확인',
onConfirm: function() {
$.ajax({
type: "POST", url: url, dataType: "json",
data: { cmd: 'HARD_DELETE_MULTIPLE', ids: selectedIds.join(',') },
success: function(data) {
showAlert(data.message, {
type: data.status === 'success' ? 'success' : 'error',
onClose: function() { $("#grid").trigger("reloadGrid"); }
});
},
error: function(e) {
showAlert("완전삭제 오류: " + e.responseText, {type: 'error'});
}
});
}
});
}
});
}
$(document).ready(function() {
$('#grid').jqGrid({
@@ -33,7 +82,7 @@
searchApprovalStatus: $('select[name=searchApprovalStatus]').val(),
searchApprovalSubject: $('input[name=searchApprovalSubject]').val()
},
colNames: ['No.', 'id', '제목', '승인 유형', '승인 유형', '승인 상태','요청자', '생성일'],
colNames: ['No.', 'id', '제목', '승인 유형', '승인 유형', '승인 상태','요청자', '생성일', '예상완료일'],
colModel: [
{ name: 'rowNum', align: 'center', width: '30', sortable: false},
{ name: 'id', align: 'center', key: true, hidden: true },
@@ -42,7 +91,8 @@
{ name: 'approvalType', align: 'center', width: 80, hidden: true},
{ name: 'approvalStatus.description', align: 'center', width: 80 },
{ name: 'requester.userName', align: 'center', width: 100 },
{ name: 'createdDate', align: 'center', width: 120, formatter: 'date', formatoptions: { srcformat: 'ISO8601Long', newformat: 'Y-m-d H:i:s' } }
{ name: 'createdDate', align: 'center', width: 120, formatter: 'date', formatoptions: { srcformat: 'ISO8601Long', newformat: 'Y-m-d H:i:s' } },
{ name: 'expectEndDate', align: 'center', width: 100, formatter: dateFormat},
],
jsonReader: {
repeatitems: false
@@ -53,6 +103,8 @@
viewrecords: true,
autowidth: true,
height: 'auto',
multiselect: true,
multiboxonly: true,
ondblClickRow: function(rowId) {
var rowData = $(this).getRowData(rowId);
var id = rowData['id'];
@@ -93,6 +145,10 @@
$("#grid").setGridParam({ url: url, postData: postData, page: 1 }).trigger("reloadGrid");
});
$("#btn_delete_selected").click(function() {
deleteSelectedApprovals();
});
$("select[name^=search], input[name^=search]").keydown(function(key) {
if (key.keyCode == 13) {
$("#btn_search").click();
@@ -112,9 +168,17 @@
</div><!-- end content_top -->
<div class="content_middle" id="content_middle">
<div class="search_wrap">
<c:if test="${hardDeleteEnabled}">
<button type="button" class="cssbtn" id="btn_delete_selected" level="W" style="background-color: #dc3545; border-color: #dc3545; color: white;"><i class="material-icons">delete_forever</i> 선택 삭제</button>
</c:if>
<button type="button" class="cssbtn" id="btn_search" level="R"><i class="material-icons">search</i> <%= localeMessage.getString("button.search") %></button>
</div>
<div class="title">승인 요청 목록<span class="tooltip">승인 요청을 관리합니다.</span></div>
<c:if test="${hardDeleteEnabled}">
<div style="background:#fff3cd; border:1px solid #ffc107; padding:8px 15px; margin:5px 0; border-radius:4px; color:#856404;">
<strong>[주의]</strong> 선택 삭제 시 승인 요청과 승인자 이력이 DB에서 영구 삭제됩니다.
</div>
</c:if>
<form id="ajaxForm" onsubmit="return false;">
<table class="search_condition" cellspacing="0">
<tbody>
@@ -134,8 +198,8 @@
<div class="select-style">
<select name="searchApprovalStatus">
<option value="">전체</option>
<option value="CREATED">생성됨</option>
<option value="REQUESTED">요청됨</option>
<option value="PROCESSING">진행중</option>
<option value="DENIED">반려됨</option>
<option value="END">승인됨</option>
</select>
@@ -20,15 +20,22 @@
var select_orgId = "";
function formatInquiryStatus(cellvalue, options, rowObject) {
return cellvalue === 'PENDING'
? '<span style="color: red;">문의중</span>'
: '<span>답변완료</span>';
if (cellvalue == 'PENDING') {
return '<span style="color: red;">문의중</span>';
} else if (cellvalue == 'RESPONDED') {
return '<span >답변완료</span>'
} else if (cellvalue == 'CLOSED') {
return '<span >답변종료</span>'
} else {
return cellvalue;
}
}
function formatFile(cellvalue, options, rowObject) {
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
var icon = rowObject.hasAttachment ? '<img src="' + iconPath + '" alt="File Attached" style="vertical-align: middle; margin-left: 5px; width: 16px; height: 16px;">' : '';
return cellvalue + icon;
var commentCount = rowObject.commentCount > 0 ? ' <span style="color: #337ab7;">[' + rowObject.commentCount + ']</span>' : '';
return cellvalue + icon + commentCount;
}
function deleteSelectedRows() {
@@ -36,6 +36,13 @@
padding-bottom: 10px;
border-bottom: 2px solid #e0e0e0; /* 제목 아래 줄 긋기 */
}
.comment-section {
margin-bottom: 30px;
border: 1px solid #e0e0e0; /* 동일한 박스 스타일 */
padding: 20px;
border-radius: 5px;
}
.info-row {
margin-bottom: 10px;
@@ -57,6 +64,15 @@
width: 100%;
min-height: 200px;
}
#commentDetail {
min-height: 100px;
border: 1px solid #00000032;
border-radius: 4px;
padding: 10px;
flex: 1;
font-family: '맑은고딕'
}
.required-mark {
color: red;
@@ -68,6 +84,7 @@
var url = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.json" />';
var url_view = '<c:url value="/onl/apim/portalinquiry/portalInquiryMan.view" />';
var file_download = '<c:url value="/file/download.do" />';
var inquiryStatus = 'PENDING';
function decodeHTMLEntities(text) {
var textArea = document.createElement('textarea');
@@ -82,10 +99,21 @@
dataType: "json",
data: {cmd: 'DETAIL', id: key},
success: function (data) {
$("#id").val(key);
$("#inquirySubject").text(data.inquirySubject);
$("#inquiryDetail").html(data.inquiryDetail);
$("#inquiryStatus").text(data.inquiryStatus === 'PENDING' ? '문의중' : '답변완료');
inquiryStatus = data.inquiryStatus;
if (data.inquiryStatus == 'PENDING') {
$("#inquiryStatus").text('문의중');
} else if (data.inquiryStatus == 'RESPONDED') {
$("#inquiryStatus").text('답변완료');
} else if (data.inquiryStatus == 'CLOSED') {
$("#inquiryStatus").text('답변종료');
}
$("#inquirer").text(data.inquirer.userName);
$("#createdDate").text(timeStampFormat(data.createdDate));
$("#responder").text(data.responderName || '');
@@ -94,6 +122,8 @@
var decodedContent = decodeHTMLEntities(data.responseDetail);
$("#responseDetail").summernote('code', decodedContent || '');
// 댓글 표시
listComment(key);
// 첨부 파일 표시
displayAttachFiles(data.fileInfo);
@@ -104,6 +134,59 @@
}
});
}
function listComment(inquiryId) {
$.ajax({
type: "POST",
url: url,
dataType: "json",
data: {cmd: 'LIST_COMMENT', id: inquiryId},
success: function (data) {
var $commentList = $('#commentList');
$commentList.empty();
if (!data || data.length === 0) return;
$.each(data, function (i, comment) {
var date = comment.createdDate ? timeStampFormat(comment.createdDate) : '';
var row = $('<div>').addClass('info-row');
$('<div>').addClass('info-label').css('color', '#555').text(comment.writerName || '').appendTo(row);
var content = $('<div>').addClass('info-content');
$('<div>').css('white-space', 'pre-wrap').text(comment.commentDetail).appendTo(content);
var meta = $('<div>').css({'font-size': '0.9em', 'color': '#999', 'margin-top': '2px'});
$('<span>').text(date).appendTo(meta);
$('<button>').attr('type', 'button').addClass('cssbtn smallBtn2').css({'margin-left': '8px', 'font-size': '0.85em', 'padding': '1px 6px'})
.text('delete').on('click', (function(id, inquiryId) {
return function() { deleteComment(id, inquiryId); };
})(comment.id, comment.inquiryId))
.appendTo(meta);
meta.appendTo(content);
content.appendTo(row);
row.appendTo($commentList);
});
},
error: function (e) {
alert(e.responseText);
}
});
}
function deleteComment(commentId, inquiryId) {
if (inquiryStatus == 'CLOSED') {
alert('답변이 종료되어 삭제할 수 없습니다');
return;
}
if (!confirm("댓글을 삭제하시겠습니까?")) return;
$.ajax({
type: "POST",
url: url,
data: {cmd: 'DELETE_COMMENT', id: commentId},
success: function () {
listComment(inquiryId);
},
error: function (e) {
alert(e.responseText);
}
});
}
$(document).ready(function () {
var returnUrl = getReturnUrlForReturn();
@@ -114,12 +197,17 @@
placeholder: '여기에 답변을 입력하세요.',
height: 200
});
if (key) {
detail(key);
}
$("#btn_modify").click(function () {
if (inquiryStatus == 'CLOSED') {
alert('답변이 종료되어 수정할 수 없습니다');
return;
}
if (!checkRequired("ajaxForm")) return;
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
@@ -139,6 +227,32 @@
}
});
});
$("#btn_comment").click(function () {
if (inquiryStatus == 'CLOSED') {
alert('답변이 종료되어 댓글을 등록할 수 없습니다');
return;
}
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
$.ajax({
type: "POST",
url: url,
data: {
cmd: "INSERT_COMMENT",
inquiryId: $('#id').val(),
commentDetail: $('#commentDetail').val(),
},
success: function (json) {
alert("<%= localeMessage.getString("common.saveMsg") %>");
$('#commentDetail').val('');
listComment(key);
},
error: function (e) {
alert(e.responseText);
}
});
});
$("#btn_previous").click(function () {
goNav(returnUrl);
@@ -206,6 +320,10 @@
<!-- 답변 작성 부분 -->
<div class="response-section">
<div class="info-row">
<div class="info-label">상태</div>
<div id="inquiryStatus" class="info-content"></div>
</div>
<div class="info-row">
<div class="info-label">답변자</div>
<div id="responder" class="info-content"></div>
@@ -221,6 +339,19 @@
</div>
</div>
</div>
<!-- 댓글 작성 부분 -->
<div class="comment-section">
<div id="commentList"></div>
<div class="info-row">
<div class="info-label">${sessionScope.login.userName}</div>
<div style="flex:1; display: flex; gap: 8px; align-items: stretch;">
<textarea id="commentDetail" name="commentDetail" data-required data-warning="댓글 내용을 입력하여 주십시오."></textarea>
<button type="button" class="cssbtn" id="btn_comment" level="W" style="width:100px; height: 100px; text-align:center; ">댓글등록</button>
</div>
</div>
</div>
<input type="hidden" name="id" id="id">
</form>
</div>
@@ -19,10 +19,23 @@
<script language="javascript" >
var url = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.json" />';
var url_view = '<c:url value="/onl/apim/portalnotice/portalNoticeMan.view" />';
var apiPopupUrl = '<c:url value="/onl/apim/apigroup/apiGroupMan.view" />';
var file_download = '<c:url value="/file/download.do" />';
var isDetail = false;
var fileInfo = null;
// 코드 상수 — PortalNoticeUI 와 동기화
var NOTICE_TYPE_NORMAL = '1';
var NOTICE_TYPE_MAINTENANCE = '2';
var NOTICE_TYPE_INCIDENT = '3';
// 영향 API 목록 (UI 상태)
var affectedApis = [];
function isIncidentType(t) {
return t === NOTICE_TYPE_INCIDENT || t === NOTICE_TYPE_MAINTENANCE;
}
function init() {
var key = "${param.id}";
isDetail = key != "" && key != "null";
@@ -35,8 +48,7 @@
$("#btn_modify").html('<i class="material-icons">save</i> <%= localeMessage.getString("button.register") %>');
buttonControl(false);
}
// 초기화 로직
$.ajax({
type : "POST",
url:url,
@@ -45,7 +57,8 @@
success:function(json){
combo = json;
new makeOptions("CODE","NAME").setObj($("select[name=noticeType]")).setNoValueInclude(false).setData(json.noticeTypeList).rendering();
toggleIncidentFields();
if (key) {
detail(key);
}
@@ -56,6 +69,22 @@
});
}
function toggleIncidentFields() {
var t = $('#noticeType').val();
if (isIncidentType(t)) {
$('.incident-row').show();
// INCIDENT 만 상태 영역 표시
if (t === NOTICE_TYPE_INCIDENT) {
$('.incident-state-row').show();
} else {
$('.incident-state-row').hide();
}
} else {
$('.incident-row').hide();
$('.incident-state-row').hide();
}
}
function decodeHTMLEntities(text) {
var textArea = document.createElement('textarea');
textArea.innerHTML = text;
@@ -73,57 +102,100 @@
function displayAttachFile(file) {
var $attachFiles = $('#attachFiles');
$attachFiles.empty();
if (file) {
var iconPath = '${pageContext.request.contextPath}/images/icon_file.png';
var fileItem = $('<div>').addClass('file-item');
$('<img>').attr('src', iconPath).css({'marginLeft': '15px','marginRight': '5px' ,'height': '1.2em'}).appendTo(fileItem);
var fileNameSpan = $('<span>')
.addClass('fileName')
.text(file.originalFileName + '.' + file.fileExtension + ' (' + formatFileSize(file.size) + ')')
.css('cursor', 'pointer')
.on('click', function() {
if (file.fileId) {
downloadFile(file.fileId);
}
});
var fileNameSpan = $('<span>').addClass('fileName')
.text(file.originalFileName + '.' + file.fileExtension + ' (' + formatFileSize(file.size) + ')')
.css('cursor', 'pointer')
.on('click', function() { if (file.fileId) downloadFile(file.fileId); });
fileNameSpan.appendTo(fileItem);
$('<button>').text('X').addClass('delete-file').css('marginLeft', '15px')
.on('click', function() {
fileInfo = null;
displayAttachFile(null);
// 폼에 삭제 플래그 추가
$('<input>').attr({
type: 'hidden',
name: 'fileDeleted',
value: 'true'
}).appendTo('#ajaxForm');
})
.appendTo(fileItem);
.on('click', function() {
fileInfo = null;
displayAttachFile(null);
$('<input>').attr({type: 'hidden', name: 'fileDeleted', value: 'true'}).appendTo('#ajaxForm');
}).appendTo(fileItem);
fileItem.appendTo($attachFiles);
}
}
function downloadFile(fileId) {
if (fileId) {
var fileSn = 1; // 단일 파일이므로 시리얼 넘버를 1로 고정
window.location.href = file_download + '?fileId=' + fileId + '&fileSn=' + fileSn;
} else {
console.log("File not yet uploaded or doesn't have an ID");
window.location.href = file_download + '?fileId=' + fileId + '&fileSn=1';
}
}
function renderAffectedApis() {
var $tb = $('#affectedApiTbody');
$tb.empty();
if (!affectedApis.length) {
$tb.append('<tr><td colspan="3" style="text-align:center;color:#999;">선택된 영향 API가 없습니다.</td></tr>');
return;
}
$.each(affectedApis, function(idx, api) {
var $tr = $('<tr>');
$tr.append('<td style="text-align:center;width:40px;"><input type="checkbox" class="affected-api-check" data-idx="' + idx + '"></td>');
$tr.append('<td>' + (api.apiId || '') + '</td>');
$tr.append('<td>' + (api.apiName || '') + '</td>');
$tb.append($tr);
});
}
function openApiPopup() {
// showModal 헬퍼는 urlAddServiceType + &menuId=getMenuId() + &pop=true 를 자동 부여 →
// AuthorizeInterceptor 가 정상적으로 권한 인식. window.open 직접 호출 시 권한 부족 발생.
// 본 페이지는 summernote 본문 에디터 등 다른 iframe 이 함께 존재할 수 있어
// frames[0] 가정은 위험 → DOM 에서 .ui-dialog 내 iframe 중 src=apiGroupMan 인 것을 식별.
var selectedIds = affectedApis.map(function(a) { return a.apiId; }).join(',');
var url2 = apiPopupUrl + '?cmd=POPUP&selectedApiIds=' + encodeURIComponent(selectedIds);
var args = {};
showModal(url2, args, 1280, 860, function () {
var doc = args.self && args.self.document;
if (!doc) return;
var popupFrame = null;
var iframes = doc.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
var src = iframes[i].getAttribute('src') || '';
if (src.indexOf('apiGroupMan') >= 0) {
popupFrame = iframes[i].contentWindow;
break;
}
}
if (!popupFrame) return;
var rv = popupFrame.returnValue;
if (!rv) return;
var rows = Array.isArray(rv) ? rv : [rv];
$.each(rows, function(_, item) {
if (!item || !item.apiId) return;
var exists = affectedApis.some(function(a){ return a.apiId === item.apiId; });
if (!exists) {
affectedApis.push({apiId: item.apiId, apiName: item.apiDesc || item.apiName || ''});
}
});
renderAffectedApis();
});
}
function removeSelectedAffectedApis() {
var idxes = [];
$('.affected-api-check:checked').each(function() {
idxes.push(parseInt($(this).data('idx'), 10));
});
if (!idxes.length) {
alert('삭제할 항목을 선택해주세요.');
return;
}
// 역순 삭제
idxes.sort(function(a,b){return b-a;}).forEach(function(i){ affectedApis.splice(i, 1); });
renderAffectedApis();
}
function detail(key) {
if (!isDetail) return;
$.ajax({
type: "POST",
url: url,
dataType: "json",
type: "POST", url: url, dataType: "json",
data: {cmd: 'DETAIL', id: key},
success: function (data) {
$("#id").val(key);
@@ -135,7 +207,18 @@
var decodedContent = decodeHTMLEntities(data.noticeDetail);
$('#contents').summernote('code', decodedContent);
// 파일 정보 표시
// 장애/점검 필드
$('#summary').val(data.summary || '');
$('#startedAt').val(data.startedAt || '');
$('#endAt').val(data.endAt || '');
$('#state').val(data.state || 'INVESTIGATING');
$('#previousState').text(data.previousState || '없음');
affectedApis = (data.affectedApis || []).map(function(a){
return {apiId: a.apiId, apiName: a.apiName || ''};
});
renderAffectedApis();
toggleIncidentFields();
if (data.fileInfo) {
fileInfo = {
originalFileName: data.fileInfo.fileDetails[0].originalFileName,
@@ -146,24 +229,21 @@
displayAttachFile(fileInfo);
}
},
error: function (e) {
alert(e.responseText);
}
error: function (e) { alert(e.responseText); }
});
}
$(document).ready(function () {
var returnUrl = getReturnUrlForReturn();
init();
// Summernote 에디터 초기화 (이미지 붙여넣기 data-uri 방식, 리사이징 지원)
initSummernote('#contents', {
placeholder: '여기에 내용을 입력하세요.',
height: 300
});
initSummernote('#contents', { placeholder: '여기에 내용을 입력하세요.', height: 300 });
$('#noticeType').on('change', toggleIncidentFields);
$('#btn_addAffectedApi').click(openApiPopup);
$('#btn_removeAffectedApi').click(removeSelectedAffectedApis);
$('#fileInput').change(function(e) {
var file = e.target.files[0];
@@ -171,52 +251,66 @@
fileInfo = {
originalFileName: file.name.split('.').slice(0, -1).join('.'),
fileExtension: file.name.split('.').pop(),
file: file,
size: file.size
file: file, size: file.size
};
displayAttachFile(fileInfo);
}
this.value = ''; // 입력 필드 초기화
});
$('#addFile').click(function() {
$('#fileInput').click();
this.value = '';
});
$('#addFile').click(function() { $('#fileInput').click(); });
$("#btn_modify").click(function () {
if (!checkRequired("ajaxForm")) return;
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true)
return;
var noticeType = $('#noticeType').val();
if (isIncidentType(noticeType)) {
if (!$('#startedAt').val()) {
alert('시작 시각을 입력해 주십시오.');
return;
}
if (noticeType === NOTICE_TYPE_MAINTENANCE && !$('#endAt').val()) {
alert('점검 종료 시각을 입력해 주십시오.');
return;
}
if (affectedApis.length === 0) {
alert('영향 API를 1건 이상 선택해 주십시오.');
return;
}
}
if (confirm("<%= localeMessage.getString("common.checkSave")%>") != true) return;
var formData = new FormData($("#ajaxForm")[0]);
formData.set("useYn", $("#useYn").is(":checked") ? "Y" : "N");
formData.set("fixYn", $("#fixYn").is(":checked") ? "Y" : "N");
formData.set("noticeDetail", $('#contents').summernote('code'));
// 시작/종료 시각이 비어 있으면 전송하지 않음 (서버 파싱 오류 방지)
if (!$('#startedAt').val()) formData.delete('startedAt');
if (!$('#endAt').val()) formData.delete('endAt');
// 영향 API → affectedApis[i].apiId/apiName 으로 전송
$.each(affectedApis, function(i, api) {
formData.set('affectedApis[' + i + '].apiId', api.apiId);
formData.set('affectedApis[' + i + '].apiName', api.apiName || '');
});
formData.append("cmd", isDetail ? "UPDATE" : "INSERT");
// 파일 처리
if (fileInfo && fileInfo.file) {
formData.set("files", fileInfo.file);
formData.set("fileName", fileInfo.originalFileName + '.' + fileInfo.fileExtension);
}
$.ajax({
type: "POST",
url: url,
data: formData,
processData: false,
contentType: false,
success: function (json) {
type: "POST", url: url, data: formData,
processData: false, contentType: false,
success: function () {
alert("<%= localeMessage.getString("common.saveMsg") %>");
goNav(returnUrl);
},
error: function (e) {
alert(e.responseText);
}
error: function (e) { alert(e.responseText); }
});
});
@@ -224,25 +318,18 @@
if (confirm("<%= localeMessage.getString("common.confirmMsg")%>")) {
var postData = $('#ajaxForm').serializeArray();
postData.push({name: "cmd", value: "DELETE"});
$.ajax({
type: "POST",
url: url,
data: postData,
success: function (args) {
type: "POST", url: url, data: postData,
success: function () {
alert("<%= localeMessage.getString("common.deleteMsg") %>");
goNav(returnUrl);
},
error: function (e) {
alert(e.responseText);
}
error: function (e) { alert(e.responseText); }
});
}
});
$("#btn_previous").click(function () {
goNav(returnUrl);
});
$("#btn_previous").click(function () { goNav(returnUrl); });
});
</script>
</head>
@@ -252,7 +339,7 @@
<ul class="path">
<li><a href="#">${rmsMenuPath}</a></li>
</ul>
</div><!-- end content_top -->
</div>
<div class="content_middle">
<div class="search_wrap">
<button type="button" class="cssbtn" id="btn_modify" level="W" status="DETAIL,NEW"><i class="material-icons">save</i> <%= localeMessage.getString("button.modify") %></button>
@@ -270,9 +357,9 @@
<col style="width: 40%"/>
</colgroup>
<tr>
<th>게시유형</th>
<td colspan="3">
<div class="select-style" >
<th>게시유형 <font color="red">*</font></th>
<td colspan="3">
<div class="select-style">
<select name="noticeType" id="noticeType"></select>
</div>
</td>
@@ -286,7 +373,7 @@
<td>
<input type="checkbox" name="useYn" id="useYn" value="Y" ${portalNoticeUI.useYn eq 'Y' ? 'checked' : ''}> 사용
</td>
<th>고정여부</th>
<th>상단고정</th>
<td>
<input type="checkbox" name="fixYn" id="fixYn" value="Y" ${portalNoticeUI.fixYn eq 'Y' ? 'checked' : ''}> 고정
</td>
@@ -297,14 +384,53 @@
<textarea id="contents" name="noticeDetail" style="width:100%;height:300px" data-required data-warning="본문을 입력하여 주십시오."></textarea>
</td>
</tr>
<tr>
<th>첨부파일</th>
<td colspan="3">
<div style="margin: 5px 0px; display: inline-block;">
<input type="file" id="fileInput" name="files" style="display:none;" />
<button type="button" id="addFile" class="cssbtn smallBtn">파일 선택</button>
<!-- ───── 장애/점검 전용 영역 ───── -->
<tr class="incident-row" style="display:none;">
<th>시작 <font color="red">*</font></th>
<td>
<input type="datetime-local" id="startedAt" name="startedAt" style="width:90%">
</td>
<th>종료</th>
<td>
<input type="datetime-local" id="endAt" name="endAt" style="width:90%" placeholder="해소시 자동 채움">
</td>
</tr>
<tr class="incident-row incident-state-row" style="display:none;">
<th>상태</th>
<td>
<div class="select-style">
<select id="state" name="state">
<option value="INVESTIGATING">INVESTIGATING - 조사중</option>
<option value="IDENTIFIED">IDENTIFIED - 원인 파악</option>
<option value="MONITORING">MONITORING - 모니터링</option>
<option value="RESOLVED">RESOLVED - 해소</option>
<option value="CANCELED">CANCELED - 취소</option>
</select>
</div>
<div id="attachFiles" style="margin: 5px 0px; display: inline-block;"></div>
</td>
<th>이전 상태</th>
<td><span id="previousState">없음</span></td>
</tr>
<tr class="incident-row" style="display:none;">
<th>영향 API 선택</th>
<td colspan="3">
<div style="margin-bottom:5px;">
<button type="button" class="cssbtn smallBtn" id="btn_removeAffectedApi"><i class="material-icons">delete</i> 삭제</button>
<button type="button" class="cssbtn smallBtn" id="btn_addAffectedApi"><i class="material-icons">add</i> 추가</button>
</div>
<table class="table_row" cellspacing="0" style="width:100%;">
<thead>
<tr>
<th style="width:40px;text-align:center;"><input type="checkbox" id="affectedApiAll" onclick="$('.affected-api-check').prop('checked', this.checked);"></th>
<th>API ID</th>
<th>API 명</th>
</tr>
</thead>
<tbody id="affectedApiTbody">
<tr><td colspan="3" style="text-align:center;color:#999;">선택된 영향 API가 없습니다.</td></tr>
</tbody>
</table>
</td>
</tr>
</table>
@@ -312,4 +438,4 @@
</div>
</div>
</body>
</html>
</html>