그리드 컨트롤 수정

This commit is contained in:
2026-02-23 14:17:21 +09:00
parent 0db26cec5d
commit ac43b2757f
3 changed files with 212 additions and 47 deletions

View File

@@ -1,6 +1,8 @@
package com.madeu.crm.settings.medicalcategory.ctrl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
@@ -182,4 +184,29 @@ public class MedicalCategoryController extends ManagerDraftAction {
log.debug("MedicalCategoryController delMedicalCategory END");
return map;
}
/**
* 진료유형 다건 삭제
*/
@PostMapping("/delMultiMedicalCategory.do")
public HashMap<String, Object> delMultiMedicalCategory(@RequestBody Map<String, List<String>> body) {
log.debug("MedicalCategoryController delMultiMedicalCategory START");
HashMap<String, Object> map = new HashMap<>();
try {
List<String> pidList = body.get("pidList");
if (pidList == null || pidList.isEmpty()) {
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "삭제할 항목이 선택되지 않았습니다.");
return map;
}
map = medicalCategoryService.deleteMultiMedicalCategory(pidList);
} catch (Exception e) {
log.error("delMultiMedicalCategory : ", e);
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "서버 오류가 발생했습니다.");
}
log.debug("MedicalCategoryController delMultiMedicalCategory END");
return map;
}
}

View File

@@ -212,4 +212,74 @@ public class MedicalCategoryService {
return map;
}
/**
* 카테고리 다건 삭제 (논리 삭제 처리)
* 하위 항목이 있는 카테고리는 건너뛰고, 나머지만 삭제 처리
*
* @param pidList 삭제 대상 pid 목록
* @return
* @throws Exception
*/
public HashMap<String, Object> deleteMultiMedicalCategory(List<String> pidList) throws Exception {
HashMap<String, Object> map = new HashMap<>();
int successCount = 0;
int skipCount = 0;
List<String> skippedNames = new ArrayList<>();
try {
for (String pidStr : pidList) {
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("pid", pidStr);
// 하위 카테고리 존재 여부 확인
int childCount = medicalCategoryMapper.getChildCategoryCount(paramMap);
if (childCount > 0) {
// 하위 항목이 있으면 건너뜀
Map<String, Object> info = medicalCategoryMapper.getMedicalCategory(paramMap);
if (info != null && info.get("divi_name") != null) {
skippedNames.add(String.valueOf(info.get("divi_name")));
}
skipCount++;
continue;
}
// 삭제 처리
int result = medicalCategoryMapper.deleteMedicalCategory(paramMap);
if (result > 0) {
successCount++;
}
}
if (successCount > 0) {
map.put("msgCode", Constants.OK);
map.put("success", true);
} else {
map.put("msgCode", Constants.FAIL);
}
// 결과 메시지 구성
StringBuilder msg = new StringBuilder();
if (successCount > 0) {
msg.append(successCount + "건 삭제되었습니다.");
}
if (skipCount > 0) {
if (msg.length() > 0)
msg.append("\n");
msg.append(skipCount + "건은 하위 항목이 존재하여 건너뛰었습니다.");
if (!skippedNames.isEmpty()) {
msg.append("\n(" + String.join(", ", skippedNames) + ")");
}
}
if (successCount == 0 && skipCount == 0) {
msg.append("삭제할 항목이 없습니다.");
}
map.put("msgDesc", msg.toString());
} catch (Exception e) {
log.error("deleteMultiMedicalCategory Error: ", e);
throw e;
}
return map;
}
}

View File

@@ -30,47 +30,77 @@ $(document).ready(function () {
}
});
// 메뉴 [삭제] 클릭 이벤트
// 메뉴 [삭제] 클릭 이벤트 - 멀티셀렉트 지원
$(document).on('click', '#customContextMenu .delete', function () {
var menu = $('#customContextMenu');
var pid = menu.data('pid');
var name = menu.data('name');
menu.hide();
if (!pid) return;
if (!confirm("'" + name + "' 항목을 삭제하시겠습니까?\n하위 항목이 있을 경우 삭제되지 않습니다.")) return;
if (!window.currentContextRow) return;
$.ajax({
url: '/settings/medicalCategory/delMedicalCategory.do',
type: 'POST',
data: { pid: pid },
success: function (res) {
alert(res.msgDesc);
if (res.msgCode === '0' && window.currentContextRow) {
var tableId = window.currentContextRow.getTable().element.id;
var isSelected = window.currentContextRow.isSelected();
var contextTable = window.currentContextRow.getTable();
var contextData = window.currentContextRow.getData();
var selectedRows = contextTable.getSelectedRows();
// 1. 그리드에서 행 삭제
window.currentContextRow.delete();
// 2. 만약 선택된 행이었다면, 우측 하위 패널 초기화
if (isSelected) {
if (tableId === 'gridDepth2') {
table3.setData([]);
table4.setData([]);
$("#btnArea3").empty();
$("#btnArea4").empty();
} else if (tableId === 'gridDepth3') {
table4.setData([]);
$("#btnArea4").empty();
}
}
}
},
error: function () {
alert("삭제 중 오류가 발생했습니다.");
}
// 우클릭한 행이 선택된 행 목록에 포함되어 있는지 확인
var isContextRowSelected = selectedRows.some(function (r) {
return r.getData().pid === contextData.pid;
});
var targetRows;
if (isContextRowSelected && selectedRows.length > 1) {
// 멀티 선택 상태에서 선택된 행 중 하나를 우클릭한 경우 → 전체 선택 삭제
targetRows = selectedRows;
} else {
// 단일 행 또는 선택되지 않은 행을 우클릭한 경우 → 해당 행만 삭제
targetRows = [window.currentContextRow];
}
var pidList = targetRows.map(function (r) { return r.getData().pid; });
var nameList = targetRows.map(function (r) { return r.getData().divi_name; });
var confirmMsg;
if (pidList.length === 1) {
confirmMsg = "'" + nameList[0] + "' 항목을 삭제하시겠습니까?\n하위 항목이 있을 경우 삭제되지 않습니다.";
} else {
confirmMsg = pidList.length + "개 항목을 삭제하시겠습니까?\n(" + nameList.join(', ') + ")\n하위 항목이 있는 경우 해당 항목은 건너뜁니다.";
}
if (!confirm(confirmMsg)) return;
if (pidList.length === 1) {
// 단건 삭제 - 기존 API 사용
$.ajax({
url: '/settings/medicalCategory/delMedicalCategory.do',
type: 'POST',
data: { pid: pidList[0] },
success: function (res) {
alert(res.msgDesc);
if (res.msgCode === '0') {
loadData(true);
}
},
error: function () {
alert("삭제 중 오류가 발생했습니다.");
}
});
} else {
// 멀티 삭제
$.ajax({
url: '/settings/medicalCategory/delMultiMedicalCategory.do',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({ pidList: pidList }),
success: function (res) {
alert(res.msgDesc);
if (res.msgCode === '0') {
loadData(true);
}
},
error: function () {
alert("삭제 중 오류가 발생했습니다.");
}
});
}
});
}
@@ -94,7 +124,7 @@ $(document).ready(function () {
index: "pid",
layout: "fitColumns",
placeholder: "상위 항목을 선택하세요.",
selectable: 1,
selectable: true,
height: "100%",
columnDefaults: {
headerTooltip: true,
@@ -102,18 +132,17 @@ $(document).ready(function () {
}
};
// Depth 2 Grid - 순번, 명칭, 순서, 관리, 하위
// Depth 2 Grid - 순번, 명칭, 순서, 하위
table2 = new Tabulator("#gridDepth2", Object.assign({}, commonOpts, {
columns: [
{ title: "순번", formatter: "rownum", width: 38, hozAlign: "center", headerSort: false, cssClass: "col-sm" },
{ title: "명칭", field: "divi_name", formatter: categoryNameFormatter, tooltip: true },
{ title: "순서", field: "divi_sort", width: 38, hozAlign: "center", cssClass: "col-sm" },
{ title: "관리", width: 50, hozAlign: "center", headerSort: false, formatter: editFormatter, cellClick: function (e, cell) { e.stopPropagation(); editCategory(cell.getRow().getData().pid); } },
{ title: "하위", width: 50, hozAlign: "center", headerSort: false, formatter: function (c) { return addDescendantFormatter(c, 2); }, cellClick: function (e, cell) { e.stopPropagation(); var d = cell.getRow().getData(); addChildCategory(d.pid, 3, d.divi_name); } }
]
}));
// Depth 3 Grid - 순번, 명칭, 거래처, 담당자(연락처), 단가, 순서, 관리, 하위
// Depth 3 Grid - 순번, 명칭, 거래처, 담당자(연락처), 단가, 순서, 하위
table3 = new Tabulator("#gridDepth3", Object.assign({}, commonOpts, {
columns: [
{ title: "순번", formatter: "rownum", width: 38, hozAlign: "center", headerSort: false, cssClass: "col-sm" },
@@ -122,24 +151,65 @@ $(document).ready(function () {
{ title: "담당자(연락처)", field: "cust_contact", width: 110, hozAlign: "center", tooltip: true },
{ title: "단가", field: "kind_cost", width: 80, hozAlign: "right", formatter: costFormatter },
{ title: "순서", field: "divi_sort", width: 38, hozAlign: "center", cssClass: "col-sm" },
{ title: "관리", width: 50, hozAlign: "center", headerSort: false, formatter: editFormatter, cellClick: function (e, cell) { e.stopPropagation(); editCategory(cell.getRow().getData().pid); } },
{ title: "하위", width: 50, hozAlign: "center", headerSort: false, formatter: function (c) { return addDescendantFormatter(c, 3); }, cellClick: function (e, cell) { e.stopPropagation(); var d = cell.getRow().getData(); addChildCategory(d.pid, 4, d.divi_name); } }
]
}));
// Depth 4 Grid - 순번, 명칭, 순서, 관리 (하위 없음)
// Depth 4 Grid - 순번, 명칭, 순서 (하위 없음)
table4 = new Tabulator("#gridDepth4", Object.assign({}, commonOpts, {
columns: [
{ title: "순번", formatter: "rownum", width: 38, hozAlign: "center", headerSort: false, cssClass: "col-sm" },
{ title: "명칭", field: "divi_name", formatter: categoryNameFormatter, tooltip: true },
{ title: "순서", field: "divi_sort", width: 38, hozAlign: "center", cssClass: "col-sm" },
{ title: "관리", width: 50, hozAlign: "center", headerSort: false, formatter: editFormatter, cellClick: function (e, cell) { e.stopPropagation(); editCategory(cell.getRow().getData().pid); } }
{ title: "순서", field: "divi_sort", width: 38, hozAlign: "center", cssClass: "col-sm" }
]
}));
table2.on("rowClick", function (e, row) { clickRow(row, 2); });
table3.on("rowClick", function (e, row) { clickRow(row, 3); });
table4.on("rowClick", function (e, row) { clickRow(row, 4); });
// Shift+Click 범위 선택을 위한 마지막 클릭 행 추적
var lastClickedRow = {};
function handleRowClick(e, row, table, depth) {
var tableId = table.element.id;
if (e.shiftKey && lastClickedRow[tableId]) {
// Shift+Click: 범위 선택
var allRows = table.getRows();
var startIdx = allRows.indexOf(lastClickedRow[tableId]);
var endIdx = allRows.indexOf(row);
if (startIdx === -1) startIdx = 0;
var from = Math.min(startIdx, endIdx);
var to = Math.max(startIdx, endIdx);
table.deselectRow();
for (var i = from; i <= to; i++) {
allRows[i].select();
}
} else if (e.ctrlKey || e.metaKey) {
// Ctrl+Click: 토글 선택
row.toggleSelect();
} else {
// 일반 클릭: 단일 선택
table.deselectRow();
row.select();
}
lastClickedRow[tableId] = row;
clickRow(row, depth);
}
table2.on("rowClick", function (e, row) { handleRowClick(e, row, table2, 2); });
table3.on("rowClick", function (e, row) { handleRowClick(e, row, table3, 3); });
table4.on("rowClick", function (e, row) { handleRowClick(e, row, table4, 4); });
function onCellDblClick(e, cell) {
if (cell.getField() === "divi_name") {
editCategory(cell.getRow().getData().pid);
}
}
table2.on("cellDblClick", onCellDblClick);
table3.on("cellDblClick", onCellDblClick);
table4.on("cellDblClick", onCellDblClick);
table2.on("rowContext", onRowContextMenu);
table3.on("rowContext", onRowContextMenu);
@@ -195,8 +265,6 @@ $(document).ready(function () {
var data = row.getData();
var children = data._children || [];
row.getTable().deselectRow();
row.select();
if (depth === 2) {
table3.setData(children).then(function () {