diff --git a/src/main/resources/mappers/crm/categoryManagement/CategoryManagementSql.xml b/src/main/resources/mappers/crm/categoryManagement/CategoryManagementSql.xml index 1651168..f457c3b 100644 --- a/src/main/resources/mappers/crm/categoryManagement/CategoryManagementSql.xml +++ b/src/main/resources/mappers/crm/categoryManagement/CategoryManagementSql.xml @@ -34,6 +34,7 @@ SELECT CATEGORY_NO , CATEGORY_DIV_CD , CATEGORY_NM + , ORDER_NO , DATE_FORMAT(REG_DATE, '%Y-%m-%d %H:%i') AS REG_DATE FROM madeu.HP_CATEGORY @@ -64,14 +65,18 @@ - - SELECT NVL(MAX(CATEGORY_NO),0) + 1 FROM HP_CATEGORY WHERE CATEGORY_DIV_CD = #{categoryDivCd} + + SELECT IFNULL(MAX(CATEGORY_NO),0) + 1 as categoryNo + , CASE WHEN #{orderNo} IS NULL OR #{orderNo} = '' OR #{orderNo} = 0 THEN IFNULL(MAX(ORDER_NO),0) + 1 ELSE #{orderNo} END as orderNo + FROM HP_CATEGORY + WHERE CATEGORY_DIV_CD = #{categoryDivCd} /** CategoryManagementSql.putCategoryManagement **/ INSERT INTO HP_CATEGORY( CATEGORY_NO , CATEGORY_DIV_CD , CATEGORY_NM + , ORDER_NO , USE_YN , REG_ID , REG_DATE @@ -81,6 +86,7 @@ #{categoryNo}, #{categoryDivCd}, #{categoryNm}, + #{orderNo}, 'Y', #{regId}, NOW(), @@ -93,6 +99,7 @@ /** CategoryManagementSql.modCategoryManagement **/ UPDATE HP_CATEGORY SET CATEGORY_NM = #{categoryNm} + ,ORDER_NO = #{orderNo} ,MOD_ID = #{modId} ,MOD_DATE = NOW() WHERE CATEGORY_NO = #{categoryNo} diff --git a/src/main/resources/static/css/web/categoryTree.css b/src/main/resources/static/css/web/categoryTree.css new file mode 100644 index 0000000..61ad169 --- /dev/null +++ b/src/main/resources/static/css/web/categoryTree.css @@ -0,0 +1,399 @@ +/* =================================================================== + 카테고리 트리 레이아웃 스타일 + =================================================================== */ + +/* 트리 + 상세 패널 레이아웃 */ +.category-tree-layout { + display: flex; + width: 100%; + height: calc(100% - 140px); + gap: 12px; +} + +/* ---- 좌측 트리 패널 ---- */ +.category-tree-panel { + width: 380px; + min-width: 300px; + background: #fff; + border: 1px solid #E9ECF0; + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.tree-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid #E9ECF0; + background: #fff; + /* 밝게 변경 */ +} + +.tree-panel-title { + font-size: 14px; + font-weight: 700; + color: #333; +} + +.tree-panel-actions { + display: flex; + gap: 6px; +} + +.tree-sub-header { + display: flex; + gap: 6px; + padding: 8px 12px; + border-bottom: 1px solid #E9ECF0; + justify-content: flex-start; + /* Changed to Left Align */ + background: #fff; + align-items: center; +} + +.tree-action-btn { + padding: 2px 8px; + font-size: 12px; + color: #666; + background: #fff; + border: 1px solid #ddd; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; +} + +/* Button styles in Tree Header */ +.tree-panel-actions, +.tree-sub-header { + display: flex; + gap: 4px; + align-items: center; +} + +.tree-panel-actions .tree-action-btn, +.tree-panel-actions .put_btn, +.tree-panel-actions .delete_btn, +.tree-sub-header .tree-action-btn { + float: none !important; + margin: 0 !important; + padding: 2px 8px; + font-size: 12px; + height: 26px; + /* Unified height */ + line-height: normal; + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; +} + +.tree-panel-actions .put_btn img, +.tree-panel-actions .delete_btn img { + width: 12px; + height: 12px; + margin: 0; + position: static !important; +} + +/* Specific colors */ +.tree-panel-actions .put_btn { + background: #3985EA; + color: #fff; + border: 1px solid #3985EA; +} + +.tree-panel-actions .delete_btn { + background: #FF2222; + color: #fff; + border: 1px solid #FF2222; +} + +.tree-panel-actions .tree-action-btn { + background: #fff; + color: #666; + border: 1px solid #ddd; +} + + +.tree-action-btn:hover { + background: #3985EA; + /* Primary Blue */ + color: #fff; + border-color: #3985EA; +} + +.category-tree-container { + flex: 1; + overflow: hidden; + /* Changed from auto to hidden to prevent double scrollbars with Tabulator */ + padding: 0; + /* Padding 제거 (Tabulator가 꽉 차게) */ +} + +#categoryTreeContainer { + height: 100%; + width: 100%; +} + +/* Active Row Highlight */ +.tabulator-row.row-active { + background-color: #e6f7ff !important; + /* Light Blue */ + font-weight: bold; + color: #1890ff; +} + + +/* ---- 우측 상세 패널 ---- */ +.category-detail-panel { + flex: 1; + background: #fff; + border: 1px solid #E9ECF0; + border-radius: 8px; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.detail-panel-header { + display: flex; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid #E9ECF0; + background: #fff; + /* 밝게 변경 */ +} + +.detail-panel-title { + font-size: 14px; + font-weight: 700; + color: #333; +} + +.detail-panel-content { + flex: 1; + padding: 20px; + overflow: auto; +} + +.detail-empty-state { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: #999; + font-size: 14px; +} + +/* 폼 스타일 */ +.detail-form-group { + margin-bottom: 16px; +} + +.detail-form-group label { + display: block; + margin-bottom: 6px; + font-size: 13px; + font-weight: 600; + color: #555; +} + +.detail-form-input, +.detail-form-select { + width: 100%; + height: 38px; + padding: 0 12px; + border: 1px solid #E9ECF0; + border-radius: 5px; + font-size: 14px; + color: #333; + background: #fff; + transition: border-color 0.2s; +} + +.detail-form-input:focus, +.detail-form-select:focus { + border-color: #3985EA; + outline: none; + box-shadow: 0 0 0 3px rgba(57, 133, 234, 0.1); +} + +.detail-form-input[readonly] { + background: #f5f5f5; + color: #888; +} + +.detail-form-select { + appearance: none; + background: #fff url('/image/web/select_arrow.svg') no-repeat right 10px center; + cursor: pointer; +} + +.detail-form-actions { + display: flex; + gap: 8px; + margin-top: 24px; +} + +.detail-save-btn { + padding: 8px 24px; + background: #3985EA; + color: #fff; + border: none; + border-radius: 5px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: background 0.2s; +} + +.detail-save-btn:hover { + background: #2c6fd1; +} + +.detail-cancel-btn { + padding: 8px 24px; + background: #fff; + color: #666; + border: 1px solid #ddd; + border-radius: 5px; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; +} + +.detail-cancel-btn:hover { + background: #f5f5f5; + border-color: #ccc; +} + +/* 상세정보 표시 */ +.detail-info-group { + margin-bottom: 14px; +} + +.detail-info-label { + font-size: 12px; + font-weight: 600; + color: #999; + margin-bottom: 4px; +} + +.detail-info-value { + font-size: 14px; + color: #333; + padding: 8px 12px; + background: #f8f9fb; + border-radius: 4px; +} + + +/* =================================================================== + Tabulator Custom Styling (Light Theme) + =================================================================== */ +.tabulator { + border: none; + background-color: #fff; +} + +/* Header */ +.tabulator .tabulator-header { + background-color: #fff; + border-bottom: 1px solid #E9ECF0; + font-weight: 600; + color: #494E53; +} + +.tabulator .tabulator-header .tabulator-col { + background-color: #fff; + border-right: 1px solid #E9ECF0; +} + +.tabulator .tabulator-header .tabulator-col-content { + padding: 10px; +} + +/* Rows */ +.tabulator .tabulator-row { + background-color: #fff; + border-bottom: 1px solid #E9ECF0; + color: #333; +} + +.tabulator .tabulator-row:nth-child(even) { + background-color: #fff; + /* Zebra striping 제거 혹은 연하게 */ +} + +.tabulator .tabulator-row:hover { + background-color: rgba(57, 133, 234, 0.05) !important; +} + +.tabulator .tabulator-row.tabulator-selected { + background-color: rgba(57, 133, 234, 0.1) !important; + border-color: #3985EA; +} + +.tabulator .tabulator-cell { + padding: 8px 10px; + border-right: 1px solid #E9ECF0; + font-size: 14px; +} + +/* Group Headers */ +.tabulator .tabulator-group { + background-color: #f8f9fb !important; + border-bottom: 1px solid #E9ECF0; + border-top: 1px solid #E9ECF0; + color: #2c3e50; + font-weight: 700; + padding: 8px 10px; +} + +.tabulator .tabulator-group:hover { + background-color: #f0f2f5 !important; + cursor: pointer; +} + +/* Checkbox (Row Selection) */ +.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title input[type=checkbox], +.tabulator .tabulator-cell input[type=checkbox] { + accent-color: #3985EA; + width: 16px; + height: 16px; + cursor: pointer; +} + + +/* ---- 반응형 ---- */ +@media only screen and (max-width:1280px) { + .category-tree-panel { + width: 280px; + min-width: 240px; + } + + .category-tree-layout { + height: calc(100% - 120px); + } +} + +@media only screen and (max-width:1080px) { + .category-tree-layout { + flex-direction: column; + height: auto; + } + + .category-tree-panel { + width: 100%; + height: 300px; + } + + .category-detail-panel { + height: 300px; + } +} \ No newline at end of file diff --git a/src/main/resources/static/js/web/categoryManagement/CategoryManagement.js b/src/main/resources/static/js/web/categoryManagement/CategoryManagement.js index ba49f0c..abe1b91 100644 --- a/src/main/resources/static/js/web/categoryManagement/CategoryManagement.js +++ b/src/main/resources/static/js/web/categoryManagement/CategoryManagement.js @@ -1,34 +1,88 @@ -/* 페이징 관련 변수 */ -let webCategoryTotalCount = 0; -let webCategoryTotalPages = 0; +/** + * CategoryManagement.js + * Tabulator library based implementation + */ -/*aggird*/ -let webCategoryAgGridData = []; +// Global Table Instance +let categoryTable = null; +let categoryList = []; // Full flat list from server -/* 삭제할 항목들 */ -let delList = []; +// Category division map for labels +const categoryDivMap = { + "01": "다이어트 시술", + "02": "다이어트 이벤트", + "03": "쁘띠 시술", + "04": "쁘띠 이벤트", + "05": "다이어트 전후사진", + "06": "쁘띠 전후사진" +}; -/* 등록 버튼 이중 클릭 방지 플래그 */ -let isInsertBtnDisabled = false; +// Initialize +$(function () { + fn_init(); + fn_initEvent(); +}); -/**************************************************************************** - * 카테고리 정보 리스트 조회 - ****************************************************************************/ -function fn_selectListwebCategoryJson(){ +function fn_init() { + // Initial load + fn_searchCategoryList(); +} + + +function fn_initEvent() { + // Expand/Collapse All (Groups) + $("#btnExpandAll").click(function () { + if (categoryTable) { + console.log("Expanding all groups..."); + categoryTable.blockRedraw(); + categoryTable.getGroups().forEach(group => group.show()); + categoryTable.restoreRedraw(); + } + }); + + $("#btnCollapseAll").click(function () { + if (categoryTable) { + console.log("Collapsing all groups..."); + categoryTable.blockRedraw(); + categoryTable.getGroups().forEach(group => group.hide()); + categoryTable.restoreRedraw(); + } + }); + + // CRUD Buttons (Top) + $("#btnInsertWebCategory").click(function () { + fn_showInsertForm(); + }); + + $("#btnDeleteWebCategory").click(function () { + fn_deleteCategory(); + }); + + // Form Buttons + $("#btnSaveCategory").click(fn_insertCategory); + $("#btnCancelInsert").click(fn_hideDetailPanel); + + $("#btnUpdateCategory").click(fn_updateCategory); + $("#btnCancelEdit").click(fn_hideDetailPanel); +} + +/** + * Fetch Data + */ +function fn_searchCategoryList() { let formData = new FormData(); - formData.append("menuClass", menuClass); - // gridSort에 categoryNm가 들어오면 CATEGORY_NM로 변환 - let sortValue = webCategorySort; - if (sortValue && sortValue.indexOf('categoryNm') !== -1) { - sortValue = sortValue.replace(/categoryNm/g, 'CATEGORY_NM'); - } - formData.append("gridSort", sortValue); - formData.append("gridLimitStart", webCategoryStart || 0); - formData.append("gridLimitEnd", webCategoryLimit || 10); - formData.append("categoryDivCd", categoryDivCd); - formData.append("categoryNm", categoryNm); - formData.append("searchCategoryDivCd", $("#searchCategoryDivCd").val()); - formData.append("searchCategoryNm", $("#searchCategoryNm").val()); + formData.append("menuClass", menuClass); + + // Server expects gridLimitStart/End for pagination, sending large range for All data + formData.append("gridLimitStart", 0); + formData.append("gridLimitEnd", 100000); + + // Search filters (Removed) + formData.append("searchCategoryDivCd", ""); + formData.append("searchCategoryNm", ""); + + // Sort + formData.append("gridSort", "CATEGORY_DIV_CD ASC, CATEGORY_NM ASC"); $.ajax({ url: encodeURI('/categoryManagement/getCategoryManagementList.do'), @@ -37,513 +91,277 @@ function fn_selectListwebCategoryJson(){ processData: false, contentType: false, type: 'POST', - async: true, - success: function(data){ - if('0'==data.msgCode){ - // 페이징 처리 - webCategoryTotalCount = data.totalCount; - //$("#txt_noticeTotalCount").text(noticeTotalCount); - - webCategoryTotalPages = Math.ceil(webCategoryTotalCount/webCategoryLimit); - - // 리스트 조회 - webCategoryAgGridData = data.rows; - webCategoryGridOptions.api.setRowData(webCategoryAgGridData); - - if(0', - next : 'next', - first : '', - last : '', - onPageClick: function (Category, page) { - fn_webCategoryPagination(page); - } - }).on('page', function (Category, page) { - //console.info(page + ' (from Category listening)'); - }); - } - else{ - - } - } - else{ - modalEvent.danger("조회 오류", data.msgDesc); + success: function (data) { + if (data.msgCode === '0' || data.success === 'true') { + categoryList = data.rows || []; + fn_renderTable(categoryList); + fn_hideDetailPanel(); + } else { + alert(data.msgDesc || "조회 중 오류가 발생했습니다."); } }, - error : function(xhr, status, error) { - modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오."); - }, - beforeSend:function(){ - // 로딩열기 - webCategoryGridOptions.api.showLoadingOverlay(); - }, - complete:function(){ - + error: function () { + alert("서버 통신 오류가 발생했습니다."); } }); } -/**************************************************************************** - * 검색하기 - ****************************************************************************/ -function fn_webCategorySearch(param){ - if("A"!=param && "Y"!=selectUseYn){ - modalEvent.warning("", "조회 권한이 없습니다."); - return false; - } +// Render Tabulator +function fn_renderTable(list) { + // Pre-process list to strip HTML tags for display if needed, + // or we can use a formatter. Let's use a simple formatter. - fn_webCategoryPaginReset(); - - categoryDivCd = $("#searchCategoryDivCd").val(); - categoryNm = $("#searchCategoryNm").val(); - - fn_selectListwebCategoryJson(); -} - - -/**************************************************************************** - * 초기화하기 - ****************************************************************************/ -function fn_webCategoryReset(){ - $("#searchCategoryDivCd").val(""); - $("#searchCategoryNm").val(""); - fn_webCategorySearch(); -} - -/**************************************************************************** - * 페이징 처리 - ****************************************************************************/ -function fn_webCategoryPagination(param){ - webCategoryStart = (parseInt(param)-1)*webCategoryLimit; - - fn_selectListwebCategoryJson(); -} - -/**************************************************************************** - * 페이징 리셋 - ****************************************************************************/ -function fn_webCategoryPaginReset(){ - branchOfficeCd = ''; - mbName = ''; - mbHp = ''; - opinionClassificationCd = ''; - - webCategoryStart = 0; - webCategoryLimit = 100; - webCategoryTotalCount = 0; - webCategoryTotalPages = 0; - - //페이징 초기화 - if($("#webCategoryPagination").data("twbs-pagination")){ - $("#webCategoryPagination").twbsPagination("destroy"); - } -} - -/**************************************************************************** - * 검색 엔터 카테고리 - ****************************************************************************/ -function fn_webCategoryEnter(e){ - if(e.which){ - // 파이어폭스 - if(13 == e.which) { - //로그인 액션 스크립트 - fn_webCategorySearch(); - } - } - else{ - // 윈도우, 사파리, 크롬 - if(13 == Category.keyCode) { - //로그인 액션 스크립트 - fn_webCategorySearch(); - } - } -} - -/**************************************************************************** - * 완료 - ****************************************************************************/ -function fn_webCategoryOk(){ - isInsertBtnDisabled = false; - $("#btnInsertWebCategory").prop('disabled', false); - fn_webCategoryReset(); -} -// JavaScript -// Category division options -let categoryDivOptions = [ - { value: "01", label: "다이어트 시술" }, - { value: "02", label: "다이어트 이벤트" }, - { value: "03", label: "쁘띠 시술" }, - { value: "04", label: "쁘띠 이벤트" }, - { value: "05", label: "다이어트 전후사진" }, - { value: "06", label: "쁘띠 전후사진" } -]; - - -let webCategoryColumnDefs = [ - {field: "checkbox", headerName:"", minWidth:55, maxWidth:55, headerCheckboxSelection: true, checkboxSelection: true}, - { - field: "categoryDivCd", - headerName: "카테고리구분", - minWidth: 150, - maxWidth: 200, - cellStyle: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center' + categoryTable = new Tabulator("#categoryTreeContainer", { + data: list, + layout: "fitColumns", + selectableRows: false, // Updated: Only select via checkbox + groupBy: "categoryDivCd", + groupStartOpen: true, // Default to Expanded + groupHeader: function (value, count, data, group) { + // Custom Header: "Division Name (Count)" + let label = categoryDivMap[value] || value; + return label + " (" + count + ")"; }, - cellRenderer: function(params) { - let found = categoryDivOptions.find(opt => opt.value === params.value); - return found ? found.label : params.value; - }, - // Make editable only for new rows (regDate is empty) - editable: function(params) { - // 신규행(regDate 없음)이고 등록중 플래그가 있거나, 둘 중 하나라도 값이 없으면 false - if (!params.data.regDate) { - if (params.data._isRegistering) return false; - // 둘 중 하나라도 값이 없으면(입력 중)만 true, 둘 다 값이 있으면 false - if (!params.data.categoryDivCd || !params.data.categoryNm) return true; - return false; - } - // 다른 행: 그리드에 신규행이 있고, 그 신규행이 입력 중이거나 등록중이면 수정 불가 - let rowData = []; - params.api.forEachNode(function(node) { - rowData.push(node.data); - }); - let hasNewRowEditing = rowData.some(row => !row.regDate && (!row.categoryDivCd || !row.categoryNm || row._isRegistering)); - if (hasNewRowEditing) return false; - return true; - }, - cellEditor: 'agSelectCellEditor', - cellEditorParams: { - values: categoryDivOptions.map(opt => opt.label) - }, - valueSetter: function(params) { - let found = categoryDivOptions.find(opt => opt.label === params.newValue); - if (found) { - params.data.categoryDivCd = found.value; - return true; - } - return false; - } - }, - { - field: "categoryNm", - headerName: "카테고리명", - minWidth: 200, - maxWidth: 300, - cellStyle: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center' - }, - // 신규행이 등록중이거나 입력이 덜 된 경우 수정 불가 - editable: function(params) { - // 신규행: 입력(값이 없을 때)만 가능, 입력이 끝나면(둘 다 값이 있으면) 수정 불가, 등록중에도 수정 불가 - if (!params.data.regDate) { - if (params.data._isRegistering) return false; - // 둘 중 하나라도 값이 없으면 true(입력 가능), 둘 다 값이 있으면 false(수정 불가) - if (!params.data.categoryDivCd || !params.data.categoryNm) return true; - return false; - } - // 신규행이 등록중이거나 입력 중이면 다른 행은 수정 불가 - let rowData = []; - params.api.forEachNode(function(node) { - rowData.push(node.data); - }); - let hasNewRowEditing = rowData.some(row => !row.regDate && (!row.categoryDivCd || !row.categoryNm || row._isRegistering)); - if (hasNewRowEditing) return false; - return true; - } - }, - { field: "regDate", headerName:"등록일", minWidth: 130, maxWidth: 150, editable: false, - cellStyle: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center' - }, - } -]; - -// let the grid know which columns and what data to use -let webCategoryGridOptions = { - suppressRowTransform: true, - columnDefs: webCategoryColumnDefs, - defaultColDef: { // 리스트 기본 설정 - flex: 1, - sortable: true, //정렬 여부 - resizable: true, //리사이즈 - cellStyle:{ - textAlign:'left', - fontSize:'14px', - lineHeight:'16px', - whiteSpace:'normal', - padding:'0' - }, - //suppressSizeToFit:true, //자동 맞춤 - //enableRowGroup: true, // 그룹 묶음 - enablePivot: true, - enableValue : true, - wrapText: true, // 텍스트 줄바꿈 - autoHeight: true - }, - //suppressMultiSort:true, //단일솔트 true가 단일, false가 다중 shift + sort 시 - headerHeight : 41, // header 높이 - //rowHeight : 41, // row 높이 - rowData : webCategoryAgGridData, - suppressRowClickSelection : true, // 로우 클릭시 체크박스 선택 true no, false yes - localeText : { - noRowsToShow : '조회 결과가 없습니다.' - }, //데이터 없을 시 나오는 문구 - rowSelection : 'multiple', // row 다중 선택 - debug : false, - onSelectionChanged: function(Category){ - delList = Category.api.getSelectedRows(); - }, - onSortChanged: function(Category){ - //정렬 - webCategorySort = ''; //기존 정렬 초기화 - let columnArr = Category.columnApi.getColumnState(); - if(0]*>?/gm, '') : ''; }, - error: function() { - modalEvent.danger('등록 오류', '등록 중 오류가 발생하였습니다.'); - event.data._isRegistering = false; - event.api.refreshCells({ force: true }); // 색상 즉시 반영 - } - }); - } else { - // 둘 중 하나라도 없으면 등록/수정 불가, 아무 동작 안함 - return; - } - } else { - // 기존 row 수정: 둘 다 값이 있을 때만 수정 - if (event.data.categoryDivCd && event.data.categoryNm) { - let updatedData = { ...event.oldData, ...event.data }; - updatedData.menuClass = menuClass; - $.ajax({ - url: '/categoryManagement/modCategoryManagement.do', - type: 'POST', - data: JSON.stringify(updatedData), - contentType: 'application/json', - success: function(res) { - response = JSON.parse(res); - if (response.msgCode === '0') { - modalEvent.success('수정 완료', '수정이 성공적으로 반영되었습니다.'); - fn_selectListwebCategoryJson(); - } else { - modalEvent.danger('수정 오류', response.msgDesc); + cellClick: function (e, cell) { + // Handle Cell Click -> Edit + const row = cell.getRow(); + const item = row.getData(); + + // Highlight Row + if (categoryTable) { + const rows = categoryTable.getRows(); + rows.forEach(r => r.getElement().classList.remove("row-active")); + row.getElement().classList.add("row-active"); } - }, - error: function() { - modalEvent.danger('수정 오류', '수정 중 오류가 발생하였습니다.'); + + console.log("Name Cell Clicked:", item); + fn_showEditForm(item); } - }); - } else { - // 둘 중 하나라도 없으면 수정 불가, 아무 동작 안함 - return; - } - } - }, - getRowStyle: function(params) { - return null; // 색상은 CSS에서 처리 - }, - getRowClass: function(params) { - if (!params.data) return ''; - if (!params.data.regDate && params.data._isRegistering) { - return 'registering-row'; - } - if (!params.data.regDate) { - return 'new-row'; - } - return ''; - } -}; - -// lookup the container we want the Grid to use -let webCategoryGridDiv = document.querySelector('#webCategoryGrid'); - -// create the grid passing in the div to use together with the columns & data we want to use -new agGrid.Grid(webCategoryGridDiv, webCategoryGridOptions); - - -/**************************************************************************** - * 페이지 init - ****************************************************************************/ -function fn_pageInit(){ - // 초기 페이징 처리 - $("#searchCategoryDivCd").val(categoryDivCd); - $("#searchCategoryNm").val(categoryNm); - fn_webCategorySearch("A"); -} - -/**************************************************************************** - * 페이지 Category - ****************************************************************************/ -function fn_pageCategory(){ - $(document).on('keypress', '#searchCategoryDivCd', function(e) { - fn_webCategoryEnter(e); - }); - $(document).on('keypress', '#searchCategoryNm', function(e) { - fn_webCategoryEnter(e); - }); - - $("#btnSearchWebCategory").click(function () { - fn_webCategorySearch(); - }); - - $("#btnInsertWebCategory").click(function () { - if (isInsertBtnDisabled) return; - isInsertBtnDisabled = true; - $(this).prop('disabled', true); - fn_insertwebCategoryIntro(); - // 등록 완료 또는 실패 시 다시 활성화 (fn_webCategoryOk에서 처리) - }); - $("#btnDeleteWebCategory").click(function () { - fn_deleteWebCategory(); + } + ], + // rowClick was causing issues with selection/edit overlap. + // Moved to specific cellClick handlers. }); } -// 카테고리 등록 모달 열기 -function fn_insertwebCategoryIntro() { - if("Y"==insertUseYn){ - // 그리드에 한 줄 추가 - let newRow = { - categoryDivCd: '', - categoryNm: '', - regDate: '' - }; - webCategoryGridOptions.api.applyTransaction({ add: [newRow], addIndex: 0 }); - // 첫 번째 row, 카테고리구분 셀에 포커스 - setTimeout(function() { - webCategoryGridOptions.api.startEditingCell({ - rowIndex: 0, - colKey: 'categoryDivCd' - }); - }, 100); - } else { - modalEvent.warning('', '등록 권한이 없습니다.'); - return false; - } + +/** + * Detail Panel: Hide All + */ +function fn_hideDetailPanel() { + $("#categoryInsertForm").hide(); + $("#categoryEditForm").hide(); + $("#categoryDetailContent").show(); // Default empty state + // Clear selection if desired + // if(categoryTable) categoryTable.deselectRow(); // Optional: keep selection } -// 카테고리 삭제 -function fn_deleteWebCategory() { - // 신규행이 등록중이거나 입력 중이면 삭제 불가 - let rowData = []; - webCategoryGridOptions.api.forEachNode(function(node) { - rowData.push(node.data); - }); - let hasNewRowEditing = rowData.some(row => !row.regDate && (!row.categoryDivCd || !row.categoryNm || row._isRegistering)); - if (hasNewRowEditing) { - modalEvent.warning('', '신규 카테고리 등록이 완료되기 전에는 삭제할 수 없습니다.'); +/** + * Detail Panel: Show Insert + */ +function fn_showInsertForm(preSelectDivCd) { + if ("Y" !== insertUseYn) { + alert("등록 권한이 없습니다."); return; } - if(!delList || delList.length === 0) { - modalEvent.warning('', '삭제할 카테고리를 선택하세요.'); + + $("#categoryDetailContent").hide(); + $("#categoryEditForm").hide(); + $("#categoryInsertForm").show(); + + // Reset fields + $("#insertCategoryDivCd").val(preSelectDivCd || ""); + $("#insertCategoryNm").val(""); + // Order No is auto-generated (MAX+1) on server + + // Update Header + $(".detail-panel-title").text("카테고리 등록"); +} + +/** + * Detail Panel: Show Edit + */ +function fn_showEditForm(item) { + if (!item) return; + console.log("Show Edit Form for:", item); + + try { + $("#categoryDetailContent").hide(); + $("#categoryInsertForm").hide(); + $("#categoryEditForm").show(); + + // Fill fields + $("#editCategoryNo").val(item.categoryNo); // PK + $("#editCategoryDivCd").val(item.categoryDivCd); // PK part + + $("#editCategoryDivNm").val(categoryDivMap[item.categoryDivCd] || item.categoryDivCd); + + // Strip HTML for input value as well? usually we want the raw value if it is editable. + // Assuming edit is for clean text. + let cleanName = item.categoryNm ? String(item.categoryNm).replace(/<[^>]*>?/gm, '') : ''; + $("#editCategoryNm").val(cleanName); + $("#editOrderNo").val(item.orderNo || "0"); // Set Order No + + $("#editRegDate").val(item.regDate); + + // Update Header + $(".detail-panel-title").text("카테고리 상세/수정"); + } catch (err) { + console.error("Error displaying edit form:", err); + } +} + +/** + * Action: Insert + */ +function fn_insertCategory() { + const divCd = $("#insertCategoryDivCd").val(); + const nm = $("#insertCategoryNm").val(); + // Order No handled by backend (MAX+1) + + if (!divCd) { alert("카테고리 구분을 선택해주세요."); return; } + if (!nm) { alert("카테고리명을 입력해주세요."); return; } + + const data = { + categoryDivCd: divCd, + categoryNm: nm, + orderNo: 0, // Default 0 to trigger backend logic + menuClass: menuClass + }; + + $.ajax({ + url: '/categoryManagement/putCategoryManagement.do', + type: 'POST', + data: JSON.stringify(data), + contentType: 'application/json', + success: function (res) { + let response = (typeof res === 'string') ? JSON.parse(res) : res; + if (response.msgCode === '0') { + alert("등록되었습니다."); + fn_searchCategoryList(); // reload + } else { + alert(response.msgDesc); + } + }, + error: function () { + alert("등록 중 오류가 발생했습니다."); + } + }); +} + +/** + * Action: Update + */ +function fn_updateCategory() { + if ("Y" !== updateUseYn) { + alert("수정 권한이 없습니다."); return; } - modalEvent.info('삭제', '선택한 카테고리를 삭제하시겠습니까?', function(){ - let data = { - menuClass: menuClass, - delList: delList + + const no = $("#editCategoryNo").val(); + const divCd = $("#editCategoryDivCd").val(); + const nm = $("#editCategoryNm").val(); + const orderNo = $("#editOrderNo").val() || 0; + + if (!nm) { alert("카테고리명을 입력해주세요."); return; } + + const data = { + categoryNo: no, + categoryDivCd: divCd, + categoryNm: nm, + orderNo: orderNo, + menuClass: menuClass + }; + + $.ajax({ + url: '/categoryManagement/modCategoryManagement.do', + type: 'POST', + data: JSON.stringify(data), + contentType: 'application/json', + success: function (res) { + let response = (typeof res === 'string') ? JSON.parse(res) : res; + if (response.msgCode === '0') { + alert("수정되었습니다."); + fn_searchCategoryList(); + } else { + alert(response.msgDesc); + } + }, + error: function () { + alert("수정 중 오류가 발생했습니다."); + } + }); +} + +/** + * Action: Delete + */ +function fn_deleteCategory() { + if ("Y" !== deleteUseYn) { + alert("삭제 권한이 없습니다."); + return; + } + + if (!categoryTable) return; + + // Get Selected Data + const selectedData = categoryTable.getSelectedData(); + + if (selectedData.length === 0) { + alert("삭제할 카테고리를 선택해주세요."); + return; + } + + // Map to required format + const itemsToDelete = selectedData.map(item => ({ + categoryNo: item.categoryNo, + categoryDivCd: item.categoryDivCd + })); + + if (confirm(itemsToDelete.length + "건의 카테고리를 삭제하시겠습니까?")) { + const data = { + delList: itemsToDelete, + menuClass: menuClass }; + $.ajax({ url: '/categoryManagement/delCategoryManagement.do', data: JSON.stringify(data), - dataType: 'json', - contentType: 'application/json; charset=utf-8', + contentType: 'application/json', type: 'POST', - async: true, - success: function(res){ - if(res.msgCode === '0'){ - modalEvent.success('삭제 완료', '카테고리가 삭제되었습니다.', function(){ - fn_webCategoryOk(); - }); + success: function (res) { + let response = (typeof res === 'string') ? JSON.parse(res) : res; + if (response.msgCode === '0') { + alert("삭제되었습니다."); + fn_searchCategoryList(); } else { - modalEvent.danger('삭제 오류', res.msgDesc); + alert(response.msgDesc); } }, - error: function(xhr, status, error) { - modalEvent.danger('삭제 오류', '삭제 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.'); + error: function () { + alert("삭제 중 오류가 발생했습니다."); } }); - }); -} - -$(function(){ - // 페이지 init - fn_pageInit(); - - // 페이지 Category - fn_pageCategory(); -}); \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/main/resources/templates/web/categoryManagement/CategoryManagementList.html b/src/main/resources/templates/web/categoryManagement/CategoryManagementList.html index c842470..bdd4386 100644 --- a/src/main/resources/templates/web/categoryManagement/CategoryManagementList.html +++ b/src/main/resources/templates/web/categoryManagement/CategoryManagementList.html @@ -1,36 +1,27 @@ - + - + + + @@ -39,53 +30,108 @@

카테고리 관리

-
+
- -
- - - -
- -
- - -
+ + + +
-
+ +
+ +
+
+ 카테고리 목록 +
- -
- + + +
+
+ +
+ + +
+
+
+ + +
+
+ 카테고리 정보 +
+
+
+

좌측 목록에서 카테고리를 선택하세요.

+
+
+ + + + + + +
-
- - + + + \ No newline at end of file