- 공개범위 설정 기능 개발(ALL/ORG/PRIVATE) - 게시물/댓글 정교화 - 요청 및 DB 처리 확장 - UI, DTO, Service 계층 업데이트 - 감사 로그 및 조회수 처리 로직 추가
This commit is contained in:
@@ -16,3 +16,4 @@ PortalPartnershipManController_피드백/개선요청관리_APIGW_UNMASK,DELETE
|
||||
PortalPropertyController_포탈 프로퍼티 관리_APIGW_UPDATE
|
||||
PortalUserTermsManController_약관동의 이력_APIGW_UNMASK
|
||||
MessageRequestManController_메세지발송내역_APIGW_UPDATE_STATUS
|
||||
PortalInquiryManController_Q&A문의관리_APIGW_VISIBILITY
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
targetNamespace="http://xmlns.oracle.com/weblogic/weblogic-web-app"
|
||||
xmlns="http://xmlns.oracle.com/weblogic/weblogic-web-app"
|
||||
elementFormDefault="qualified">
|
||||
|
||||
<xs:element name="weblogic-web-app">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="context-root" type="xs:string" minOccurs="0"/>
|
||||
<xs:element name="session-descriptor" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="timeout-secs" type="xs:integer" minOccurs="0"/>
|
||||
<xs:element name="persistent-store-type" type="xs:string" minOccurs="0"/>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="container-descriptor" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="prefer-application-packages" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="package-name" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="prefer-application-resources" minOccurs="0">
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="resource-name" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
|
||||
</xs:schema>
|
||||
@@ -33,6 +33,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function formatVisibility(cellvalue, options, rowObject) {
|
||||
if (cellvalue == 'PRIVATE') {
|
||||
return '<span style="color:#c0392b; font-weight:bold;">비공개</span>';
|
||||
} else if (cellvalue == 'ALL') {
|
||||
return '전체공개';
|
||||
} else if (cellvalue == 'ORG') {
|
||||
return '법인공개';
|
||||
}
|
||||
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;">' : '';
|
||||
@@ -101,13 +112,14 @@
|
||||
mtype: 'POST',
|
||||
url: url,
|
||||
postData : { cmd : 'LIST' },
|
||||
colNames:['No.', 'id', '제목', '법인명', '상태', '작성자', '등록일', '답변자','답변일'],
|
||||
colNames:['No.', 'id', '제목', '법인명', '상태', '공개범위', '작성자', '등록일', '답변자','답변일'],
|
||||
colModel:[
|
||||
{ name : 'rowNum' , align:'center', width:50, sortable:false },
|
||||
{ name : 'id' , align:'center', key:true, hidden:true},
|
||||
{ name : 'inquirySubject' , align:'left' , width:200 , formatter: formatFile},
|
||||
{ name : 'orgName' , align:'center' , width:100 , },
|
||||
{ name : 'inquiryStatus' , align:'center', width:70, formatter: formatInquiryStatus },
|
||||
{ name : 'visibility' , align:'center', width:70, formatter: formatVisibility },
|
||||
{ name : 'inquirer.userName', align:'center', width:70 },
|
||||
{ name : 'createdDate' , align:'center', width:100, formatter: timeStampFormat },
|
||||
{ name : 'responderName' , align:'center', width:70 },
|
||||
|
||||
@@ -79,12 +79,46 @@
|
||||
margin-left: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.reason-modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 1000;
|
||||
}
|
||||
.reason-modal-box {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
width: 400px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
.reason-modal-box h3 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.reason-modal-box textarea {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.reason-modal-actions {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<script language="javascript">
|
||||
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';
|
||||
var originalVisibility = 'ORG';
|
||||
var reasonModalCallback = null;
|
||||
|
||||
function decodeHTMLEntities(text) {
|
||||
var textArea = document.createElement('textarea');
|
||||
@@ -124,6 +158,24 @@
|
||||
var decodedContent = decodeHTMLEntities(data.responseDetail);
|
||||
$("#responseDetail").summernote('code', decodedContent || '');
|
||||
|
||||
// 공개범위 / 조회수
|
||||
$('#visibilitySelect').val(data.visibility || 'ORG');
|
||||
originalVisibility = data.visibility || 'ORG';
|
||||
$('#viewCount').text(data.viewCount || 0);
|
||||
|
||||
// 조회수 증가 (sessionStorage dedup — 새로고침 시 중복 증가 억제)
|
||||
var viewedKey = 'inquiry_viewed_' + key;
|
||||
if (!sessionStorage.getItem(viewedKey)) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: 'INCREMENT_VIEW', id: key},
|
||||
success: function () {
|
||||
sessionStorage.setItem(viewedKey, 'true');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 댓글 표시
|
||||
listComment(key);
|
||||
|
||||
@@ -149,8 +201,17 @@
|
||||
if (!data || data.length === 0) return;
|
||||
$.each(data, function (i, comment) {
|
||||
var date = comment.createdDate ? timeStampFormat(comment.createdDate) : '';
|
||||
var isPrivate = comment.visibility === 'PRIVATE';
|
||||
var row = $('<div>').addClass('info-row');
|
||||
$('<div>').addClass('info-label').css('color', '#555').text(comment.writerName || '').appendTo(row);
|
||||
if (isPrivate) {
|
||||
// 비공개 댓글은 색상으로 구별
|
||||
row.css({'background-color': '#FFF5F5', 'border-radius': '4px', 'padding': '4px'});
|
||||
}
|
||||
var labelDiv = $('<div>').addClass('info-label').css('color', '#555').text(comment.writerName || '');
|
||||
if (isPrivate) {
|
||||
$('<span>').text(' 비공개').css({'color': '#c0392b', 'font-size': '0.85em', 'font-weight': 'bold'}).appendTo(labelDiv);
|
||||
}
|
||||
labelDiv.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'});
|
||||
@@ -161,6 +222,19 @@
|
||||
})(comment.id, comment.inquiryId))
|
||||
.appendTo(meta);
|
||||
meta.appendTo(content);
|
||||
|
||||
// 댓글 공개범위 변경 컨트롤 (사유 필수)
|
||||
var visCtrl = $('<div>').css({'margin-top': '4px', 'font-size': '0.85em'});
|
||||
var sel = $('<select>').append('<option value="ALL">공개</option>').append('<option value="PRIVATE">비공개</option>');
|
||||
sel.val(comment.visibility || 'ALL');
|
||||
var reasonInput = $('<input>').attr({'type': 'text', 'placeholder': '변경 사유'}).css({'margin-left': '4px'});
|
||||
var applyBtn = $('<button>').attr('type', 'button').addClass('cssbtn smallBtn2').css({'margin-left': '4px', 'font-size': '0.85em', 'padding': '1px 6px'})
|
||||
.text('공개범위변경').on('click', (function(id, inquiryId, selEl, reasonEl) {
|
||||
return function() { changeCommentVisibility(id, inquiryId, selEl.val(), reasonEl.val()); };
|
||||
})(comment.id, comment.inquiryId, sel, reasonInput));
|
||||
visCtrl.append(sel).append(reasonInput).append(applyBtn);
|
||||
visCtrl.appendTo(content);
|
||||
|
||||
content.appendTo(row);
|
||||
row.appendTo($commentList);
|
||||
});
|
||||
@@ -171,6 +245,84 @@
|
||||
});
|
||||
}
|
||||
|
||||
function openReasonModal(callback) {
|
||||
reasonModalCallback = callback;
|
||||
$('#reasonModalInput').val('');
|
||||
$('#reasonModal').show();
|
||||
$('#reasonModalInput').focus();
|
||||
}
|
||||
|
||||
function closeReasonModal() {
|
||||
$('#reasonModal').hide();
|
||||
reasonModalCallback = null;
|
||||
}
|
||||
|
||||
// 답변(summernote) 내용이 실제로 입력됐는지 확인
|
||||
function hasAnswerContent() {
|
||||
var v = $('#responseDetail').summernote('code');
|
||||
var text = $('<div>').html(v || '').text().replace(/ /g, ' ').trim();
|
||||
return text.length > 0;
|
||||
}
|
||||
|
||||
function saveInquiryAnswer(returnUrl) {
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
postData.push({name: "cmd", value: "INSERT"});
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: postData,
|
||||
success: function (json) {
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 게시물 공개범위 변경(사유 필수). 성공 후 답변 내용이 있으면 이어서 저장.
|
||||
function changeInquiryVisibility(newVis, reason, returnUrl, thenSaveAnswer) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: 'VISIBILITY', serviceType: 'APIGW', targetType: 'INQUIRY', id: $('#id').val(),
|
||||
visibility: newVis, auditReason: reason},
|
||||
success: function () {
|
||||
originalVisibility = newVis;
|
||||
if (thenSaveAnswer) {
|
||||
saveInquiryAnswer(returnUrl);
|
||||
} else {
|
||||
alert('변경되었습니다.');
|
||||
goNav(returnUrl);
|
||||
}
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function changeCommentVisibility(commentId, inquiryId, visibility, reason) {
|
||||
if (!reason || !reason.trim()) {
|
||||
alert('공개범위 변경 사유를 입력하세요.');
|
||||
return;
|
||||
}
|
||||
if (!confirm('댓글 공개범위를 변경하시겠습니까?')) return;
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: {cmd: 'VISIBILITY', serviceType: 'APIGW', targetType: 'COMMENT', id: commentId, visibility: visibility, auditReason: reason},
|
||||
success: function () {
|
||||
alert('변경되었습니다.');
|
||||
listComment(inquiryId);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteComment(commentId, inquiryId) {
|
||||
if (inquiryStatus == 'CLOSED') {
|
||||
alert('답변이 종료되어 삭제할 수 없습니다');
|
||||
@@ -210,24 +362,38 @@
|
||||
alert('답변이 종료되어 수정할 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
var newVis = $('#visibilitySelect').val();
|
||||
var visChanged = (newVis !== originalVisibility);
|
||||
|
||||
if (visChanged) {
|
||||
// 공개범위 변경 감지 → 사유 입력 모달 (답변 내용이 있으면 함께 저장)
|
||||
var saveAnswerToo = hasAnswerContent();
|
||||
openReasonModal(function (reason) {
|
||||
changeInquiryVisibility(newVis, reason, returnUrl, saveAnswerToo);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 공개범위 변경 없음 → 기존 답변 저장
|
||||
if (!checkRequired("ajaxForm")) return;
|
||||
if (!confirm("<%= localeMessage.getString("common.checkSave")%>")) return;
|
||||
|
||||
var postData = $('#ajaxForm').serializeArray();
|
||||
postData.push({name: "cmd", value: "INSERT"});
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: postData,
|
||||
success: function (json) {
|
||||
alert("<%= localeMessage.getString("common.saveMsg") %>");
|
||||
goNav(returnUrl);
|
||||
},
|
||||
error: function (e) {
|
||||
alert(e.responseText);
|
||||
}
|
||||
saveInquiryAnswer(returnUrl);
|
||||
});
|
||||
|
||||
$("#reasonModalOk").click(function () {
|
||||
var reason = ($('#reasonModalInput').val() || '').trim();
|
||||
if (!reason) {
|
||||
alert('변경 사유를 입력하세요.');
|
||||
return;
|
||||
}
|
||||
var cb = reasonModalCallback;
|
||||
closeReasonModal();
|
||||
if (cb) cb(reason);
|
||||
});
|
||||
|
||||
$("#reasonModalCancel").click(function () {
|
||||
closeReasonModal();
|
||||
});
|
||||
|
||||
$("#btn_comment").click(function () {
|
||||
@@ -310,6 +476,21 @@
|
||||
<div class="info-label">작성일</div>
|
||||
<div id="createdDate" class="info-content"></div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">조회수</div>
|
||||
<div id="viewCount" class="info-content">0</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">공개범위</div>
|
||||
<div class="info-content" style="display:flex; gap:8px; align-items:center;">
|
||||
<select id="visibilitySelect">
|
||||
<option value="ALL">전체공개</option>
|
||||
<option value="ORG">법인공개</option>
|
||||
<option value="PRIVATE">비공개</option>
|
||||
</select>
|
||||
<span style="font-size:0.85em; color:#999;">변경 후 상단 [수정] 버튼을 누르면 사유 입력창이 표시됩니다.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row" id="attachFileRow" style="display: none;">
|
||||
<div class="info-label">첨부파일</div>
|
||||
<div id="attachFiles" class="info-content"></div>
|
||||
@@ -358,5 +539,17 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 공개범위 변경 사유 입력 모달 -->
|
||||
<div id="reasonModal" class="reason-modal-overlay">
|
||||
<div class="reason-modal-box">
|
||||
<h3>공개범위 변경 사유</h3>
|
||||
<textarea id="reasonModalInput" placeholder="변경 사유를 입력하세요."></textarea>
|
||||
<div class="reason-modal-actions">
|
||||
<button type="button" class="cssbtn" id="reasonModalOk">확인</button>
|
||||
<button type="button" class="cssbtn" id="reasonModalCancel">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -37,4 +37,7 @@ public class PortalInquiryCommentUI {
|
||||
@DateTimeFormat(pattern = "yyyyMMddHHmmss")
|
||||
private LocalDateTime lastModifiedDate;
|
||||
|
||||
/** 공개범위(ALL=공개/PRIVATE=비공개). */
|
||||
private String visibility;
|
||||
|
||||
}
|
||||
|
||||
+13
@@ -92,4 +92,17 @@ public class PortalInquiryManController extends BaseAnnotationController {
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
// 공개범위 변경 — 감사 로그는 AuditLogInterceptor 가 자동 기록(auditpoints.dat 등록, auditReason 파라미터)
|
||||
@PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=VISIBILITY")
|
||||
public ResponseEntity<String> updateVisibility(String targetType, String id, String visibility, String auditReason) {
|
||||
portalInquiryManService.updateVisibility(targetType, id, visibility, auditReason);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@PostMapping(value = "/onl/apim/portalinquiry/portalInquiryMan.json", params = "cmd=INCREMENT_VIEW")
|
||||
public ResponseEntity<Void> incrementView(String id) {
|
||||
portalInquiryManService.incrementViewCount(id);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.eactive.apim.portal.portaluser.entity.PortalUserEnums;
|
||||
import com.eactive.apim.portal.portaluser.repository.PortalUserRepository;
|
||||
import com.eactive.apim.portal.qna.entity.Inquiry;
|
||||
import com.eactive.apim.portal.qna.entity.InquiryComment;
|
||||
import com.eactive.apim.portal.qna.entity.VisibilityScope;
|
||||
import com.eactive.apim.portal.user.entity.UserInfo;
|
||||
import com.eactive.eai.rms.common.base.BaseService;
|
||||
import com.eactive.eai.rms.common.exception.IntegrationException;
|
||||
@@ -44,6 +45,7 @@ public class PortalInquiryManService extends BaseService {
|
||||
public static final String REVIEWING = "REVIEWING";
|
||||
public static final String RESPONDED = "RESPONDED";
|
||||
public static final String CLOSED = "CLOSED";
|
||||
public static final String TARGET_COMMENT = "COMMENT";
|
||||
|
||||
|
||||
public PortalInquiryManService(PortalInquiryService portalInquiryService,
|
||||
@@ -235,4 +237,41 @@ public class PortalInquiryManService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
/** 조회수 증가 (JSP sessionStorage dedup 통과 시 호출). 관리자는 공개범위 무관 전체 접근. */
|
||||
public void incrementViewCount(String id) {
|
||||
Inquiry inquiry = portalInquiryService.getById(id);
|
||||
inquiry.increaseViewCount();
|
||||
portalInquiryService.save(inquiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시물/댓글 공개범위 변경. 사유(reason) 필수.
|
||||
* 변경 이력은 eapim-admin 공통 감사 로그(AuditLogInterceptor)가 자동 기록한다
|
||||
* (auditpoints.dat 의 PortalInquiryManController_..._VISIBILITY, serviceType=APIGW, auditReason 파라미터).
|
||||
*
|
||||
* @param targetType INQUIRY 또는 COMMENT
|
||||
* @param id 대상 게시물/댓글 ID
|
||||
* @param newVisibility 변경할 공개범위(ALL/ORG/PRIVATE)
|
||||
* @param reason 변경 사유(필수)
|
||||
*/
|
||||
public void updateVisibility(String targetType, String id, String newVisibility, String reason) {
|
||||
if (StringUtils.isBlank(reason)) {
|
||||
throw new IntegrationException("공개범위 변경 사유를 입력해주세요.");
|
||||
}
|
||||
VisibilityScope newScope = VisibilityScope.fromString(newVisibility, null);
|
||||
if (newScope == null) {
|
||||
throw new IntegrationException("유효하지 않은 공개범위입니다.");
|
||||
}
|
||||
|
||||
if (TARGET_COMMENT.equalsIgnoreCase(targetType)) {
|
||||
InquiryComment comment = portalInquiryCommentService.getById(id);
|
||||
comment.setVisibility(newScope);
|
||||
portalInquiryCommentService.save(comment);
|
||||
} else {
|
||||
Inquiry inquiry = portalInquiryService.getById(id);
|
||||
inquiry.setVisibility(newScope);
|
||||
portalInquiryService.save(inquiry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,4 +57,10 @@ public class PortalInquiryUI {
|
||||
private String orgName;
|
||||
|
||||
private int commentCount;
|
||||
|
||||
/** 공개범위(ALL/ORG/PRIVATE). */
|
||||
private String visibility;
|
||||
|
||||
/** 조회수. */
|
||||
private long viewCount;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user