원무>진료유형설정 구현

This commit is contained in:
pjs
2026-02-22 23:21:46 +09:00
parent 6c78f1a897
commit 49994443d0
11 changed files with 2712 additions and 86 deletions

View File

@@ -0,0 +1,185 @@
package com.madeu.crm.settings.medicalcategory.ctrl;
import java.util.HashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.madeu.constants.Constants;
import com.madeu.crm.settings.medicalcategory.dto.MedicalCategoryDTO;
import com.madeu.crm.settings.medicalcategory.service.MedicalCategoryService;
import com.madeu.init.ManagerDraftAction;
import com.madeu.util.HttpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequestMapping("/settings/medicalCategory")
public class MedicalCategoryController extends ManagerDraftAction {
@Autowired
private MedicalCategoryService medicalCategoryService;
// ==================== 뷰 반환 메서드 ====================
/**
* 통합 진료유형 관리 메인 화면 이동
*/
@RequestMapping("/list.do")
public ModelAndView selectList(HttpServletRequest request, HttpServletResponse response) {
log.debug("MedicalCategoryController selectList START");
log.debug("MedicalCategoryController selectList END");
// 프론트 설계 4번 항목 경로
return new ModelAndView("/web/settings/medicalcategory/medicalCategoryList");
}
/**
* 카테고리 상세/등록 팝업 화면 이동
*/
@RequestMapping("/infoPop.do")
public ModelAndView infoPop(HttpServletRequest request, HttpServletResponse response) {
log.debug("MedicalCategoryController infoPop START");
log.debug("MedicalCategoryController infoPop END");
return new ModelAndView("/web/settings/medicalcategory/popup/medicalCategoryInfoPop");
}
// ==================== API 메서드 (JSON 반환) ====================
/**
* 진료유형 트리 리스트 조회
*/
@PostMapping("/getMedicalCategoryTreeList.do")
public HashMap<String, Object> getMedicalCategoryTreeList(HttpServletRequest request,
HttpServletResponse response) {
log.debug("MedicalCategoryController getMedicalCategoryTreeList START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
try {
map = medicalCategoryService.getMedicalCategoryTreeList(paramMap);
} catch (Exception e) {
log.error("getMedicalCategoryTreeList : ", e);
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "서버 오류가 발생했습니다.");
} finally {
if (Constants.OK != map.get("msgCode")) {
map.put("msgCode", Constants.FAIL);
map.put("success", false);
if (map.get("msgDesc") == null || "".equals(map.get("msgDesc"))) {
map.put("msgDesc", "정보를 불러오는데 실패했습니다. 관리자에게 문의하세요.");
}
}
}
log.debug("MedicalCategoryController getMedicalCategoryTreeList END");
return map;
}
/**
* 카테고리 단건 상세 정보 가져오기
*/
@PostMapping("/getMedicalCategory.do")
public HashMap<String, Object> getMedicalCategory(HttpServletRequest request, HttpServletResponse response) {
log.debug("MedicalCategoryController getMedicalCategory START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
try {
map = medicalCategoryService.getMedicalCategory(paramMap);
} catch (Exception e) {
log.error("getMedicalCategory : ", e);
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "서버 오류가 발생했습니다.");
}
log.debug("MedicalCategoryController getMedicalCategory END");
return map;
}
/**
* 거래처 목록 조회
*/
@PostMapping("/getCustList.do")
public HashMap<String, Object> getCustList(HttpServletRequest request, HttpServletResponse response) {
log.debug("MedicalCategoryController getCustList START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
try {
map = medicalCategoryService.getCustList(paramMap);
} catch (Exception e) {
log.error("getCustList : ", e);
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "서버 오류가 발생했습니다.");
}
log.debug("MedicalCategoryController getCustList END");
return map;
}
/**
* 진료유형 등록
*/
@PostMapping("/putMedicalCategory.do")
public HashMap<String, Object> putMedicalCategory(@RequestBody MedicalCategoryDTO dto, HttpServletRequest request) {
log.debug("MedicalCategoryController putMedicalCategory START");
HashMap<String, Object> map = new HashMap<>();
try {
map = medicalCategoryService.insertMedicalCategory(dto);
} catch (Exception e) {
log.error("putMedicalCategory : ", e);
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "서버 오류가 발생했습니다.");
}
log.debug("MedicalCategoryController putMedicalCategory END");
return map;
}
/**
* 진료유형 수정
*/
@PostMapping("/modMedicalCategory.do")
public HashMap<String, Object> modMedicalCategory(@RequestBody MedicalCategoryDTO dto, HttpServletRequest request) {
log.debug("MedicalCategoryController modMedicalCategory START");
HashMap<String, Object> map = new HashMap<>();
try {
map = medicalCategoryService.updateMedicalCategory(dto);
} catch (Exception e) {
log.error("modMedicalCategory : ", e);
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "서버 오류가 발생했습니다.");
}
log.debug("MedicalCategoryController modMedicalCategory END");
return map;
}
/**
* 진료유형 삭제
*/
@PostMapping("/delMedicalCategory.do")
public HashMap<String, Object> delMedicalCategory(HttpServletRequest request, HttpServletResponse response) {
log.debug("MedicalCategoryController delMedicalCategory START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
try {
map = medicalCategoryService.deleteMedicalCategory(paramMap);
} catch (Exception e) {
log.error("delMedicalCategory : ", e);
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "서버 오류가 발생했습니다.");
}
log.debug("MedicalCategoryController delMedicalCategory END");
return map;
}
}

View File

@@ -0,0 +1,95 @@
package com.madeu.crm.settings.medicalcategory.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@ToString
public class MedicalCategoryDTO {
// 카테고리 고유 번호
private Integer pid;
// 진료유형 명칭
private String diviName;
// 현재 항목의 깊이 (예: '1', '2', '3', '4')
private String diviDept;
// 부모 카테고리의 고유 번호(pid)
private Integer diviParent;
// 구분(진료) 'y'/'n'
private String clinicUse;
// 구분(비용) 'y'/'n'
private String expenseUse;
// 구분(면세) 'y'/'n'
private String taxFree;
// 색상 코드
private String diviColor;
// 정렬 순서
private Integer diviSort;
// 거래처 pid
private Integer custListPid;
// 단가
private Double kindCost;
// 제품설명
private String kindMsg1;
// 기타설명
private String kindMsg2;
// 단위
private String kindUnit;
// 단위당 용량
private Float kindUnitVol;
// 단위당 용량(단위)
private String kindUnit2;
// 비율
private String ccRate;
// 재고관리품목 'y'/'n'
private String stockUse;
// 손익계산서 서브보기 'y'/'n'
private String incomeUse;
// 비과세 'y'/'n'
private String noTax;
// 비세금 'y'/'n'
private String noTax1;
// 기타수입 사용 'y'/'n'
private String etcincomeUse;
// 기타손실 사용 'y'/'n'
private String etclossUse;
// 데이터 사용/삭제 노출 여부 'y'/'n'
private String listUse;
// 등록일시
private String regDate;
// 수정일시
private String upDate;
// 지점(스토어) 고유값
private String storePid;
// 미수금관리 보이기 'y'/'n'
private String npayUse;
}

View File

@@ -0,0 +1,68 @@
package com.madeu.crm.settings.medicalcategory.mapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import com.madeu.crm.settings.medicalcategory.dto.MedicalCategoryDTO;
@Mapper
public interface MedicalCategoryMapper {
/**
* 진료유형 리스트 전체 또는 조건 조회
*
* @param paramMap (store_pid, list_use 등 검색조건)
* @return 카테고리 계층 리스트
*/
List<Map<String, Object>> getMedicalCategoryList(HashMap<String, Object> paramMap);
/**
* 특정 카테고리 상세 정보 조회
*
* @param paramMap (pid)
* @return 카테고리 상세
*/
Map<String, Object> getMedicalCategory(HashMap<String, Object> paramMap);
/**
* 카테고리 신규 등록
*
* @param dto
* @return
*/
int insertMedicalCategory(MedicalCategoryDTO dto);
/**
* 카테고리 정보 수정
*
* @param dto
* @return
*/
int updateMedicalCategory(MedicalCategoryDTO dto);
/**
* 카테고리 삭제 처리 (실제 삭제 or 논리적 삭제(list_use='n'))
*
* @param paramMap (pid)
* @return
*/
int deleteMedicalCategory(HashMap<String, Object> paramMap);
/**
* 하위 카테고리 목록 존재 여부/수량 확인 (삭제 시 방어 로직 등에 사용)
*
* @param paramMap (divi_parent)
* @return
*/
int getChildCategoryCount(HashMap<String, Object> paramMap);
/**
* 거래처 목록 조회 (Depth 3 매핑용)
*
* @return 거래처 리스트
*/
List<Map<String, Object>> getCustList(HashMap<String, Object> paramMap);
}

View File

@@ -0,0 +1,215 @@
package com.madeu.crm.settings.medicalcategory.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.madeu.constants.Constants;
import com.madeu.crm.settings.medicalcategory.dto.MedicalCategoryDTO;
import com.madeu.crm.settings.medicalcategory.mapper.MedicalCategoryMapper;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class MedicalCategoryService {
@Autowired
private MedicalCategoryMapper medicalCategoryMapper;
/**
* 카테고리 트리 리스트 조회
*/
public HashMap<String, Object> getMedicalCategoryTreeList(HashMap<String, Object> paramMap) throws Exception {
HashMap<String, Object> map = new HashMap<>();
List<Map<String, Object>> resultList = new ArrayList<>();
try {
// DB에서 전체 목록 조회 (list_use='y') - 정렬순은 divi_dept, divi_sort 기준
List<Map<String, Object>> listMap = medicalCategoryMapper.getMedicalCategoryList(paramMap);
if (listMap != null && !listMap.isEmpty()) {
// Tabulator 의 dataTree 모드를 사용하기 위해, 평면 구조(Flat)를 받아서 프론트엔드가 자체적으로 Tree로 엮거나
// 직접 백엔드에서 `_children` 프로퍼티를 만들어서 계층형으로 보내주는 두 가지 방식이 있습니다.
// 여기서는 Tabulator 가 dataTreeChildField: "_children" 속성으로 자동 파싱하기 쉽도록
// 백엔드에서 부모-자식 트리 구조로 조립해서 보냅니다.
resultList = buildTree(listMap);
}
map.put("msgCode", Constants.OK);
map.put("success", true);
map.put("rows", resultList);
} catch (Exception e) {
log.error("getMedicalCategoryTreeList Error: ", e);
throw e;
}
return map;
}
/**
* 평면형(Flat) List를 Tabulator Tree(계층형) 구조로 변환하는 헬퍼 메서드
*/
private List<Map<String, Object>> buildTree(List<Map<String, Object>> flatList) {
// pid를 키로 하는 Map 구조 생성 (빠른 접근 위함)
Map<Integer, Map<String, Object>> nodeMap = new HashMap<>();
List<Map<String, Object>> roots = new ArrayList<>();
for (Map<String, Object> flatNode : flatList) {
// 자식 목록을 담을 공간 초기화
flatNode.put("_children", new ArrayList<Map<String, Object>>());
// pid
Integer pid = Integer.parseInt(String.valueOf(flatNode.get("pid")));
nodeMap.put(pid, flatNode);
}
for (Map<String, Object> flatNode : flatList) {
Integer diviParent = Integer.parseInt(String.valueOf(flatNode.get("divi_parent")));
if (diviParent == 0) {
// 부모가 0이면 최상위(Depth 1) 노드
roots.add(flatNode);
} else {
// 부모가 있으면 부모 노드를 찾아서 _children 리스트에 추가
Map<String, Object> parentNode = nodeMap.get(diviParent);
if (parentNode != null) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> children = (List<Map<String, Object>>) parentNode.get("_children");
children.add(flatNode);
} else {
// 고아 노드일 경우 (부모가 삭제되었거나 매칭오류) 어떻게 처리할지 정책
// roots.add(flatNode);
}
}
}
return roots;
}
/**
* 특정 카테고리 상세 정보 조회
*/
public HashMap<String, Object> getMedicalCategory(HashMap<String, Object> paramMap) throws Exception {
HashMap<String, Object> map = new HashMap<>();
try {
Map<String, Object> categoryMap = medicalCategoryMapper.getMedicalCategory(paramMap);
map.put("msgCode", Constants.OK);
map.put("success", true);
map.put("data", categoryMap);
} catch (Exception e) {
log.error("getMedicalCategory Error: ", e);
throw e;
}
return map;
}
/**
* 거래처 목록 조회
*/
public HashMap<String, Object> getCustList(HashMap<String, Object> paramMap) throws Exception {
HashMap<String, Object> map = new HashMap<>();
try {
List<Map<String, Object>> list = medicalCategoryMapper.getCustList(paramMap);
map.put("msgCode", Constants.OK);
map.put("success", true);
map.put("data", list);
} catch (Exception e) {
log.error("getCustList Error: ", e);
throw e;
}
return map;
}
/**
* 카테고리 등록
*
* @param dto
* @return
* @throws Exception
*/
public HashMap<String, Object> insertMedicalCategory(MedicalCategoryDTO dto) throws Exception {
HashMap<String, Object> map = new HashMap<>();
try {
int result = medicalCategoryMapper.insertMedicalCategory(dto);
if (result > 0) {
map.put("msgCode", Constants.OK);
map.put("success", true);
map.put("msgDesc", "등록되었습니다.");
} else {
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "등록에 실패했습니다.");
}
} catch (Exception e) {
log.error("insertMedicalCategory Error: ", e);
throw e;
}
return map;
}
/**
* 카테고리 수정
*
* @param dto
* @return
* @throws Exception
*/
public HashMap<String, Object> updateMedicalCategory(MedicalCategoryDTO dto) throws Exception {
HashMap<String, Object> map = new HashMap<>();
try {
int result = medicalCategoryMapper.updateMedicalCategory(dto);
if (result > 0) {
map.put("msgCode", Constants.OK);
map.put("success", true);
map.put("msgDesc", "수정되었습니다.");
} else {
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "존재하지 않는 데이터이거나 수정 실패했습니다.");
}
} catch (Exception e) {
log.error("updateMedicalCategory Error: ", e);
throw e;
}
return map;
}
/**
* 카테고리 삭제 (논리 삭제 처리)
*
* @param paramMap
* @return
* @throws Exception
*/
public HashMap<String, Object> deleteMedicalCategory(HashMap<String, Object> paramMap) throws Exception {
HashMap<String, Object> map = new HashMap<>();
try {
// 1. 하위 카테고리가 존재하는지 여부 확인
int childCount = medicalCategoryMapper.getChildCategoryCount(paramMap);
if (childCount > 0) {
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "하위 항목이 존재하여 삭제할 수 없습니다. 먼저 하위 항목을 삭제하세요.");
return map;
}
// 2. 삭제 처리
int result = medicalCategoryMapper.deleteMedicalCategory(paramMap);
if (result > 0) {
map.put("msgCode", Constants.OK);
map.put("success", true);
map.put("msgDesc", "삭제되었습니다.");
} else {
map.put("msgCode", Constants.FAIL);
map.put("msgDesc", "삭제 실패했습니다.");
}
} catch (Exception e) {
log.error("deleteMedicalCategory Error: ", e);
throw e;
}
return map;
}
}

View File

@@ -0,0 +1,260 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.madeu.crm.settings.medicalcategory.mapper.MedicalCategoryMapper">
<!-- 1. 진료유형 리스트 전체 조회 (store_pid 기준) -->
<select id="getMedicalCategoryList" parameterType="java.util.HashMap" resultType="java.util.HashMap">
SELECT a.pid,
a.divi_name,
a.divi_dept,
a.divi_parent,
a.clinic_use,
a.expense_use,
a.tax_free,
a.divi_color,
a.divi_sort,
a.cust_list_pid,
a.kind_cost,
a.kind_msg1,
a.kind_msg2,
a.kind_unit,
a.kind_unit_vol,
a.kind_unit2,
a.cc_rate,
a.stock_use,
a.income_use,
a.no_tax,
a.no_tax1,
a.etcIncome_use,
a.etcLoss_use,
a.list_use,
DATE_FORMAT(a.reg_date, '%Y-%m-%d %H:%i:%s') AS reg_date,
DATE_FORMAT(a.up_date, '%Y-%m-%d %H:%i:%s') AS up_date,
a.store_pid,
a.npay_use
FROM medical_divi_list a
WHERE a.list_use = 'y'
<if test='storePid != null and storePid != ""'>
AND a.store_pid = #{storePid}
</if>
<if test='diviDept != null and diviDept != ""'>
AND a.divi_dept = #{diviDept}
</if>
<if test='diviParent != null and diviParent != ""'>
AND a.divi_parent = #{diviParent}
</if>
ORDER BY a.divi_dept ASC, a.divi_sort ASC
</select>
<!-- 2. 특정 카테고리 하나만 조회 -->
<select id="getMedicalCategory" parameterType="java.util.HashMap" resultType="java.util.HashMap">
SELECT a.pid,
a.divi_name,
a.divi_dept,
a.divi_parent,
a.clinic_use,
a.expense_use,
a.tax_free,
a.divi_color,
a.divi_sort,
a.cust_list_pid,
a.kind_cost,
a.kind_msg1,
a.kind_msg2,
a.kind_unit,
a.kind_unit_vol,
a.kind_unit2,
a.cc_rate,
a.stock_use,
a.income_use,
a.no_tax,
a.no_tax1,
a.etcIncome_use,
a.etcLoss_use,
a.list_use,
DATE_FORMAT(a.reg_date, '%Y-%m-%d %H:%i:%s') AS reg_date,
DATE_FORMAT(a.up_date, '%Y-%m-%d %H:%i:%s') AS up_date,
a.store_pid,
a.npay_use
FROM medical_divi_list a
WHERE a.pid = #{pid}
</select>
<!-- 3. 하위 카테고리 갯수 파악 (삭제 유효성 검사 등) -->
<select id="getChildCategoryCount" parameterType="java.util.HashMap" resultType="int">
SELECT COUNT(pid)
FROM medical_divi_list
WHERE divi_parent = #{pid}
AND list_use = 'y'
</select>
<!-- 4. 신규 등록 Insert -->
<insert id="insertMedicalCategory" parameterType="com.madeu.crm.settings.medicalcategory.dto.MedicalCategoryDTO" useGeneratedKeys="true" keyProperty="pid">
INSERT INTO medical_divi_list (
divi_name,
divi_dept,
divi_parent,
clinic_use,
expense_use,
tax_free,
divi_color,
divi_sort,
cust_list_pid,
kind_cost,
kind_msg1,
kind_msg2,
kind_unit,
kind_unit_vol,
kind_unit2,
cc_rate,
stock_use,
income_use,
no_tax,
no_tax1,
etcIncome_use,
etcLoss_use,
list_use,
reg_date,
up_date,
store_pid,
npay_use
) VALUES (
#{diviName},
#{diviDept},
#{diviParent},
IFNULL(#{clinicUse}, 'y'),
IFNULL(#{expenseUse}, 'n'),
IFNULL(#{taxFree}, 'n'),
IFNULL(#{diviColor}, '#000000'),
IFNULL(#{diviSort}, 0),
IFNULL(#{custListPid}, 0),
IFNULL(#{kindCost}, 0),
IFNULL(#{kindMsg1}, ''),
IFNULL(#{kindMsg2}, ''),
IFNULL(#{kindUnit}, ''),
IFNULL(#{kindUnitVol}, 0),
IFNULL(#{kindUnit2}, ''),
IFNULL(#{ccRate}, ''),
IFNULL(#{stockUse}, 'y'),
IFNULL(#{incomeUse}, 'n'),
IFNULL(#{noTax}, 'n'),
IFNULL(#{noTax1}, 'n'),
IFNULL(#{etcincomeUse}, 'n'),
IFNULL(#{etclossUse}, 'n'),
'y',
NOW(),
NOW(),
#{storePid},
IFNULL(#{npayUse}, 'n')
)
</insert>
<!-- 5. 정보 수정 Update -->
<update id="updateMedicalCategory" parameterType="com.madeu.crm.settings.medicalcategory.dto.MedicalCategoryDTO">
UPDATE medical_divi_list
<set>
<if test='diviName != null and diviName != ""'>
divi_name = #{diviName},
</if>
<if test='diviDept != null and diviDept != ""'>
divi_dept = #{diviDept},
</if>
<if test='diviParent != null'>
divi_parent = #{diviParent},
</if>
<if test='clinicUse != null and clinicUse != ""'>
clinic_use = #{clinicUse},
</if>
<if test='expenseUse != null and expenseUse != ""'>
expense_use = #{expenseUse},
</if>
<if test='taxFree != null and taxFree != ""'>
tax_free = #{taxFree},
</if>
<if test='diviColor != null and diviColor != ""'>
divi_color = #{diviColor},
</if>
<if test='diviSort != null'>
divi_sort = #{diviSort},
</if>
<if test='custListPid != null'>
cust_list_pid = #{custListPid},
</if>
<if test='kindCost != null'>
kind_cost = #{kindCost},
</if>
<if test='kindMsg1 != null'>
kind_msg1 = #{kindMsg1},
</if>
<if test='kindMsg2 != null'>
kind_msg2 = #{kindMsg2},
</if>
<if test='kindUnit != null'>
kind_unit = #{kindUnit},
</if>
<if test='kindUnitVol != null'>
kind_unit_vol = #{kindUnitVol},
</if>
<if test='kindUnit2 != null'>
kind_unit2 = #{kindUnit2},
</if>
<if test='ccRate != null'>
cc_rate = #{ccRate},
</if>
<if test='stockUse != null and stockUse != ""'>
stock_use = #{stockUse},
</if>
<if test='incomeUse != null and incomeUse != ""'>
income_use = #{incomeUse},
</if>
<if test='noTax != null and noTax != ""'>
no_tax = #{noTax},
</if>
<if test='noTax1 != null and noTax1 != ""'>
no_tax1 = #{noTax1},
</if>
<if test='etcincomeUse != null and etcincomeUse != ""'>
etcIncome_use = #{etcincomeUse},
</if>
<if test='etclossUse != null and etclossUse != ""'>
etcLoss_use = #{etclossUse},
</if>
<if test='listUse != null and listUse != ""'>
list_use = #{listUse},
</if>
<if test='storePid != null and storePid != ""'>
store_pid = #{storePid},
</if>
<if test='npayUse != null and npayUse != ""'>
npay_use = #{npayUse},
</if>
up_date = NOW()
</set>
WHERE pid = #{pid}
</update>
<!-- 6. 삭제 Update (논리 삭제) list_use = 'n' -->
<update id="deleteMedicalCategory" parameterType="java.util.HashMap">
UPDATE medical_divi_list
SET list_use = 'n', up_date = NOW()
WHERE pid = #{pid}
</update>
<!-- 7. 거래처 목록 조회 (Depth 3 전용) -->
<select id="getCustList" parameterType="java.util.HashMap" resultType="java.util.HashMap">
SELECT pid,
cust_name,
co_no,
co_ceo,
co_staff,
co_hp,
co_kind,
divi_sort
FROM crm_cust_list
WHERE list_use = 'y'
AND list_del = 'n'
ORDER BY divi_sort ASC
</select>
</mapper>

View File

@@ -2,6 +2,17 @@
카테고리 트리 레이아웃 스타일
=================================================================== */
/* 추가 버튼을 셀렉트 바로 옆에 배치 */
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box {
display: flex !important;
align-items: center;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box {
float: none !important;
margin-left: 10px;
}
/* 트리 + 상세 패널 레이아웃 */
.category-tree-layout {
display: flex;
@@ -26,10 +37,11 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
height: 50px;
padding: 0 16px;
border-bottom: 1px solid #E9ECF0;
background: #fff;
/* 밝게 변경 */
box-sizing: border-box;
}
.tree-panel-title {
@@ -396,4 +408,98 @@
.category-detail-panel {
height: 300px;
}
}
/* ===================================================================
Grid Link Buttons (수정/등록)
=================================================================== */
.grid-link-btn {
display: inline-block;
font-size: 12px;
cursor: pointer;
letter-spacing: -0.3px;
transition: all 0.15s;
user-select: none;
}
.grid-link-edit {
color: #888;
}
.grid-link-edit:hover {
color: #333;
text-decoration: underline;
}
.grid-link-add {
color: #3985EA;
font-weight: 600;
}
.grid-link-add:hover {
color: #2c6fd1;
text-decoration: underline;
}
/* Compact row height for category grids */
.category-tree-container .tabulator .tabulator-cell {
padding: 4px 6px;
}
.category-tree-container .tabulator .tabulator-header .tabulator-col-content {
padding: 6px 6px;
}
/* 작은 컬럼 (순번, 순서) */
.tabulator .tabulator-cell.col-sm {
font-size: 11px;
padding: 4px 2px;
}
.tabulator .tabulator-header .tabulator-col.col-sm .tabulator-col-content {
font-size: 11px;
padding: 6px 2px;
}
/* ======= 커스텀 컨텍스트 메뉴 ======= */
.custom-context-menu {
position: absolute;
z-index: 10000;
background-color: #ffffff;
border: 1px solid #ddd;
box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.15);
border-radius: 4px;
padding: 5px 0;
min-width: 120px;
display: none;
}
.custom-context-menu ul {
list-style: none;
margin: 0;
padding: 0;
}
.custom-context-menu li {
padding: 8px 15px;
cursor: pointer;
font-size: 13px;
color: #333;
transition: background 0.2s;
}
.custom-context-menu li:hover {
background-color: #f5f5f5;
}
.custom-context-menu li.delete {
color: #FF5252;
}
.custom-context-menu li img {
width: 14px;
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}

View File

@@ -1,69 +1,418 @@
.project_wrap {width:100%; min-width:1080px; margin:0 auto;}
.project_wrap {
width: 100%;
min-width: 1080px;
margin: 0 auto;
}
/*오른쪽영역*/
.project_wrap .content_section {margin-top:50px; width:100%; min-width:1080px; display:table;}
.project_wrap .content_section .hospital_wrap {width: calc(100% - 72px); min-width:calc(1080px - 72px); height:calc(100vh - 50px); float:left; position:relative;}
.project_wrap .content_section {
margin-top: 50px;
width: 100%;
min-width: 1080px;
display: table;
}
.project_wrap .content_section .hospital_wrap {
width: calc(100% - 72px);
min-width: calc(1080px - 72px);
height: calc(100vh - 50px);
float: left;
position: relative;
}
/* 왼쪽_메뉴 영역 */
.project_wrap .content_section .hospital_wrap .left_box {position:absolute; width:240px; height:calc(100vh - 50px); overflow:auto; padding:10px 20px;}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list {width:100%;}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list .title_menu {width:100%; height:auto; margin:20px 0 10px 0; padding:0; font-size:14px; font-weight:700;}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list .title_menu.first {margin-top:0;}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list a {width:100%; height:100%; display:block; margin-bottom:8px; padding:8px; font-size:14px; text-align:left; border-radius:5px;}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list a.on {background:#3985EA; border:none;}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li {width:100%; height:36px; margin-bottom:8px;}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li a.on {color:#fff;}
.project_wrap .content_section .hospital_wrap .left_box {
position: absolute;
width: 240px;
height: calc(100vh - 50px);
overflow: auto;
padding: 10px 20px;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list {
width: 100%;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list .title_menu {
width: 100%;
height: auto;
margin: 20px 0 10px 0;
padding: 0;
font-size: 14px;
font-weight: 700;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list .title_menu.first {
margin-top: 0;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list a {
width: 100%;
height: 100%;
display: block;
margin-bottom: 8px;
padding: 8px;
font-size: 14px;
text-align: left;
border-radius: 5px;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list a.on {
background: #3985EA;
border: none;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li {
width: 100%;
height: 36px;
margin-bottom: 8px;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li a.on {
color: #fff;
}
/* 센터쪽 */
.project_wrap .content_section .hospital_wrap .center_box {width:calc(100% - 240px); height:calc(100vh - 50px); position:absolute; left:240px; padding:10px 10px 10px 0;}
.project_wrap .content_section .hospital_wrap .center_box .page_title {min-width:90px; padding-left:10px; font-size:18px; font-weight:700; line-height:50px; float:left;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box {display:table; width:100%; padding:20px 0;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box {width:140px; height:36px; margin-left:10px; border:1px solid #E9ECF0; border-radius:5px; background:url(/image/web/select_arrow.svg) no-repeat 95% 55%/20px auto #fff;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box.first { width:100px; margin-left:0; float:left; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .slash {width:6px; font-size:14px; font-weight:400; color:#000; line-height:36px; margin:0 12px; display:block; float:left;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list_box { margin-left:10px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box.active {z-index:10;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .label {width:100%; height:100%; padding:0 10px; outline:none; font-size:14px; font-weight:400; text-align:left; color: #494E53; cursor:pointer; background:none;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list {min-width:100%; border-radius:5px; transition:.4s ease-in; border:Solid 1px #E9ECF0; padding:10px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list .option_list_item {width:100%; line-height:30px; transition:.1s; position:relative; display:table; font-size:14px; color:#494E53;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list .option_list_item label {width:100%; line-height:30px; margin-bottom:0px; clear:both; font-weight:400;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box {border-radius:8px; float:left; position:relative;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box {position:relative; width:140px; height:36px; float:left; margin-left:10px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box.last {margin-left:0;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box img {position:absolute; top:50%; transform:translateY(-50%); left:10px; z-index:1; width:22px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box .date_picker {line-height:34px; display:block; margin-right:20px; width:100%; font-size:14px; padding:0 12px; padding-left:40px; outline:none; border:1px solid #E9ECF0; border-radius:5px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box input[type="date"]::-webkit-calendar-picker-indicator {display:none;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box .date_picker {-webkit-appearance:none; -moz-appearance:none; appearance:none; position:absolute; cursor:pointer;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .slash {color:#000; font-size:14px; font-weight:400; line-height:36px; margin:0 5px; width:6px; display:block; float:left;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list {float:left; position:relative; margin-left:10px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box {width:180px; float:left; height:36px; position:relative;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box img {position:absolute; top:50%; transform:translateY(-50%); left:5px; z-index:1;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box input {width:100%; height:36px; border:1px solid #E9ECF0; border-radius:5px; background:none; position:absolute; left:0; padding:0 10px 0 30px; font-size:14px; background:#fff;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box input::placeholder {color:#B5BDC4;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box .search_list {position:absolute; top:40px; left:0; width:150px; background:#fff; color:#fff; border-radius:5px; transition:.4s ease-in; z-index:1; border:solid 1px #E9ECF0; display:none; margin:0; padding:10px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_btn {background:#3985EA; border-radius:5px; color:#fff; margin-left:5px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box {float:right;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .put_btn {background:#3985EA; color:#fff; border-radius:5px; float:left;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .put_btn img {position:relative; top:-2px; width:20px; margin-right:5px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .delete_btn {background:#FF2222; color:#fff; border-radius:5px; margin-left:10px;}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .delete_btn img {position:relative; top:-2px; width:20px; margin-right:5px;}
.project_wrap .content_section .hospital_wrap .center_box {
width: calc(100% - 240px);
height: calc(100vh - 50px);
position: absolute;
left: 240px;
padding: 10px 10px 10px 0;
}
.project_wrap .content_section .hospital_wrap .center_box .page_title {
min-width: 90px;
padding-left: 10px;
font-size: 18px;
font-weight: 700;
line-height: 50px;
float: left;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box {
display: table;
width: 100%;
padding: 20px 0;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box {
width: 140px;
height: 36px;
margin-left: 10px;
border: 1px solid #E9ECF0;
border-radius: 5px;
background: url(/image/web/select_arrow.svg) no-repeat 95% 55%/20px auto #fff;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box.first {
width: 100px;
margin-left: 0;
float: left;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .slash {
width: 6px;
font-size: 14px;
font-weight: 400;
color: #000;
line-height: 36px;
margin: 0 12px;
display: block;
float: left;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list_box {
margin-left: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box.active {
z-index: 10;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .label {
width: 100%;
height: 100%;
padding: 0 10px;
outline: none;
font-size: 14px;
font-weight: 400;
text-align: left;
color: #494E53;
cursor: pointer;
background: none;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list {
min-width: 100%;
max-height: 300px;
overflow-y: auto;
border-radius: 5px;
transition: .4s ease-in;
border: Solid 1px #E9ECF0;
padding: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list .option_list_item {
width: 100%;
line-height: 30px;
transition: .1s;
position: relative;
display: table;
font-size: 14px;
color: #494E53;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list .option_list_item label {
width: 100%;
line-height: 30px;
margin-bottom: 0px;
clear: both;
font-weight: 400;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box {
border-radius: 8px;
float: left;
position: relative;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box {
position: relative;
width: 140px;
height: 36px;
float: left;
margin-left: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box.last {
margin-left: 0;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box img {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 10px;
z-index: 1;
width: 22px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box .date_picker {
line-height: 34px;
display: block;
margin-right: 20px;
width: 100%;
font-size: 14px;
padding: 0 12px;
padding-left: 40px;
outline: none;
border: 1px solid #E9ECF0;
border-radius: 5px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box input[type="date"]::-webkit-calendar-picker-indicator {
display: none;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box .date_picker {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
position: absolute;
cursor: pointer;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .slash {
color: #000;
font-size: 14px;
font-weight: 400;
line-height: 36px;
margin: 0 5px;
width: 6px;
display: block;
float: left;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list {
float: left;
position: relative;
margin-left: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box {
width: 180px;
float: left;
height: 36px;
position: relative;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box img {
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 5px;
z-index: 1;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box input {
width: 100%;
height: 36px;
border: 1px solid #E9ECF0;
border-radius: 5px;
background: none;
position: absolute;
left: 0;
padding: 0 10px 0 30px;
font-size: 14px;
background: #fff;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box input::placeholder {
color: #B5BDC4;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box .search_list {
position: absolute;
top: 40px;
left: 0;
width: 150px;
background: #fff;
color: #fff;
border-radius: 5px;
transition: .4s ease-in;
z-index: 1;
border: solid 1px #E9ECF0;
display: none;
margin: 0;
padding: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_btn {
background: #3985EA;
border-radius: 5px;
color: #fff;
margin-left: 5px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box {
float: right;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .put_btn {
background: #3985EA;
color: #fff;
border-radius: 5px;
float: left;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .put_btn img {
position: relative;
top: -2px;
width: 20px;
margin-right: 5px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .delete_btn {
background: #FF2222;
color: #fff;
border-radius: 5px;
margin-left: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .right_btn_box .delete_btn img {
position: relative;
top: -2px;
width: 20px;
margin-right: 5px;
}
/* table_box */
.project_wrap .content_section .hospital_wrap .center_box .table_box {width:100%; height:calc(100% - 180px); overflow:auto; background:#fff; border:solid 1px #E9ECF0; border-radius:5px;}
.project_wrap .content_section .hospital_wrap .center_box .table_box {
width: 100%;
height: calc(100% - 180px);
overflow: auto;
background: #fff;
border: solid 1px #E9ECF0;
border-radius: 5px;
}
/* 페이지게이션 */
.project_wrap .content_section .hospital_wrap .center_box .page_box {position:absolute; bottom:20px; width:100%; height:24px;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation {height:24px;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination {margin:0 auto; display:table;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li {display:inline-block; padding:0}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a, .project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a {position:relative; width:24px; height:24px; background:none;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a:hover, .project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a:hover {background:none;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a img {position:absolute; top:50%; left:50%; transform:translate3d(-50%, -50%, 0); width:10px;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a img {position:absolute; top:50%; left:50%; transform:translate3d(-50%, -50%, 0) rotate(180deg); width:10px;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li a {width:24px; height:24px; padding:0; border:none; text-align:center; line-height:22px; font-size:14px; font-weight:500; background:#FFF; font-size:14px;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li a:hover, .project_wrap .content_section .hospital_wrap .center_box .right_note .page_box .navigation .pagination li a:focus {background:#3985EA; color:#fff; font-weight:700;}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li.active a {background:#3985EA; color:#fff;}
.project_wrap .content_section .hospital_wrap .center_box .page_box {
position: absolute;
bottom: 20px;
width: 100%;
height: 24px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation {
height: 24px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination {
margin: 0 auto;
display: table;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li {
display: inline-block;
padding: 0
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a,
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a {
position: relative;
width: 24px;
height: 24px;
background: none;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a:hover,
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a:hover {
background: none;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a img {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
width: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a img {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0) rotate(180deg);
width: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li a {
width: 24px;
height: 24px;
padding: 0;
border: none;
text-align: center;
line-height: 22px;
font-size: 14px;
font-weight: 500;
background: #FFF;
font-size: 14px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li a:hover,
.project_wrap .content_section .hospital_wrap .center_box .right_note .page_box .navigation .pagination li a:focus {
background: #3985EA;
color: #fff;
font-weight: 700;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li.active a {
background: #3985EA;
color: #fff;
}
/* Style for select boxes in the search area */
.search_box select {
@@ -89,43 +438,152 @@
color: #333;
font-size: 14px;
}
/*반응형 View*/
@media only screen and (max-width:1500px) {
.project_wrap .content_section .hospital_wrap .left_box { width:160px; padding:10px 15px; }
.project_wrap .content_section .hospital_wrap .center_box { width:calc(100% - 160px); left:160px; }
.project_wrap .content_section .hospital_wrap .left_box {
width: 160px;
padding: 10px 15px;
}
.project_wrap .content_section .hospital_wrap .center_box {
width: calc(100% - 160px);
left: 160px;
}
}
@media only screen and (max-width:1280px) {
.project_wrap .content_section .hospital_wrap { width:calc(100% - 60px); }
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list .title_menu { font-size:12px; }
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li { height:32px; margin-bottom:5px; }
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li a { font-size:12px; }
.project_wrap .content_section .hospital_wrap .center_box .page_title { min-width:80px; height:40px; font-size:16px; line-height:40px; }
.project_wrap .content_section .hospital_wrap .center_box .total { font-size:12px; line-height:40px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box { padding:15px 0; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box { width:120px; height:32px; background-size:18px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .label { padding:0 10px; font-size:12px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list .option_list_item { font-size:12px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box { width:120px; height:32px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box img { width:20px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box .date_picker { height:32px; padding-left:35px; font-size:12px; line-height:32px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .slash { line-height:32px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box { width:120px; height:32px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box img { width:22px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box input { height:32px; padding-left:35px; font-size:12px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .right_btn_box .download_btn { margin-left:5px; padding-left:10px; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .right_btn_box .download_btn p { display:none; }
.project_wrap .content_section .hospital_wrap .center_box .filter_box .right_btn_box .download_btn img { width:12px; margin-top:-3px; position:static; transform:none; }
.project_wrap .content_section .hospital_wrap .center_box .page_box { height:23px; }
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation { height:23px; }
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li a { width:23px; height:23px; font-size:12px; }
.project_wrap .content_section .hospital_wrap {
width: calc(100% - 60px);
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list .title_menu {
font-size: 12px;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li {
height: 32px;
margin-bottom: 5px;
}
.project_wrap .content_section .hospital_wrap .left_box .sub_menu_list li a {
font-size: 12px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_title {
min-width: 80px;
height: 40px;
font-size: 16px;
line-height: 40px;
}
.project_wrap .content_section .hospital_wrap .center_box .total {
font-size: 12px;
line-height: 40px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box {
padding: 15px 0;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box {
width: 120px;
height: 32px;
background-size: 18px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .label {
padding: 0 10px;
font-size: 12px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box .select_option_list .option_list_item {
font-size: 12px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box {
width: 120px;
height: 32px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box img {
width: 20px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .date_box .date_picker {
height: 32px;
padding-left: 35px;
font-size: 12px;
line-height: 32px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .calendar_box .slash {
line-height: 32px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box {
width: 120px;
height: 32px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box img {
width: 22px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .search_list .search_box input {
height: 32px;
padding-left: 35px;
font-size: 12px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .right_btn_box .download_btn {
margin-left: 5px;
padding-left: 10px;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .right_btn_box .download_btn p {
display: none;
}
.project_wrap .content_section .hospital_wrap .center_box .filter_box .right_btn_box .download_btn img {
width: 12px;
margin-top: -3px;
position: static;
transform: none;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box {
height: 23px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation {
height: 23px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li a {
width: 23px;
height: 23px;
font-size: 12px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a,
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a { width:23px; height:23px; }
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a {
width: 23px;
height: 23px;
}
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:first-child a img,
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a img { width:9px; }
.project_wrap .content_section .hospital_wrap .center_box .table_box { height:calc(100% - 155px); }
.project_wrap .content_section .hospital_wrap .center_box .page_box .navigation .pagination li:last-child a img {
width: 9px;
}
.project_wrap .content_section .hospital_wrap .center_box .table_box {
height: calc(100% - 155px);
}
}
@media only screen and (max-width:1080px) {
.project_wrap .content_section .hospital_wrap { width:calc(100% - 50px); }
.project_wrap .content_section .hospital_wrap {
width: calc(100% - 50px);
}
}

View File

@@ -0,0 +1,544 @@
/**
* 통합 진료유형 관리 (medicalCategoryList.js)
* - medical_divi_list 테이블 기반
* - Depth 1(진료과목 select) → Depth 2/3/4 (Tabulator 그리드)
*/
$(document).ready(function () {
let globalTreeData = [];
let nodeMap = {};
let table2 = null;
let table3 = null;
let table4 = null;
let tableOverview = null;
let overviewInitialized = false;
function initGrid() {
// 커스텀 컨텍스트 메뉴 요소 생성 및 이벤트 바인딩
if ($('#customContextMenu').length === 0) {
$('body').append(
'<div id="customContextMenu" class="custom-context-menu">' +
' <ul>' +
' <li class="delete">삭제</li>' +
' </ul>' +
'</div>'
);
// 메뉴 밖 클릭 시 커스텀 메뉴 닫기
$(document).on('click', function (e) {
if (!$(e.target).closest('#customContextMenu').length) {
$('#customContextMenu').hide();
}
});
// 메뉴 [삭제] 클릭 이벤트
$(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;
$.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();
// 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("삭제 중 오류가 발생했습니다.");
}
});
});
}
// Tabulator rowContext 이벤트(우클릭) 공통 핸들러
function onRowContextMenu(e, row) {
e.preventDefault();
var data = row.getData();
window.currentContextRow = row; // 행 삭제를 위해 캐싱
$('#customContextMenu')
.data('pid', data.pid)
.data('name', data.divi_name)
.css({
top: e.pageY + 'px',
left: e.pageX + 'px'
})
.show();
}
let commonOpts = {
index: "pid",
layout: "fitColumns",
placeholder: "상위 항목을 선택하세요.",
selectable: 1,
height: "100%",
columnDefaults: {
headerTooltip: true,
headerHozAlign: "center"
}
};
// 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 - 순번, 명칭, 거래처, 담당자(연락처), 단가, 순서, 관리, 하위
table3 = new Tabulator("#gridDepth3", 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: "cust_name", width: 90, hozAlign: "center", tooltip: true },
{ 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 - 순번, 명칭, 순서, 관리 (하위 없음)
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); } }
]
}));
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); });
table2.on("rowContext", onRowContextMenu);
table3.on("rowContext", onRowContextMenu);
table4.on("rowContext", onRowContextMenu);
window.loadData();
}
/* ====== 포맷터 영역 ====== */
function categoryNameFormatter(cell) {
var data = cell.getRow().getData();
var nameStyle = data.list_use === 'y' ? 'font-weight:600;' : 'text-decoration:line-through; color:#aaa;';
var color = data.divi_color || '#333';
if (data.list_use !== 'y') color = '#aaa';
var html = '<span style="' + nameStyle + ' color:' + color + '">' + cell.getValue() + '</span>';
// 면세 표시
if (data.tax_free === 'y') {
html += ' <span style="color:#ff0000; font-size:11px;">[면세]</span>';
}
return html;
}
function costFormatter(cell) {
var val = cell.getValue();
if (val && Number(val) > 0) {
return Number(val).toLocaleString();
}
return '';
}
function ynFormatter(cell) {
var val = cell.getValue();
if (val === 'y') {
return '<span style="color:#3985EA; font-weight:600;">Y</span>';
}
return '<span style="color:#ccc;">N</span>';
}
function editFormatter(cell) {
return '<span class="grid-link-btn grid-link-edit">수정</span>';
}
function addDescendantFormatter(cell, depth) {
return '<span class="grid-link-btn grid-link-add">등록</span>';
}
/* ====== 행 클릭 ====== */
function clickRow(row, depth, currentState) {
var data = row.getData();
var children = data._children || [];
row.getTable().deselectRow();
row.select();
if (depth === 2) {
table3.setData(children).then(function () {
if (currentState && currentState.depth3) {
table3.selectRow(currentState.depth3);
let selectedRows = table3.getSelectedRows();
if (selectedRows && selectedRows.length > 0) {
clickRow(selectedRows[0], 3, currentState);
}
}
});
table4.setData([]);
$("#btnArea3").html('<button class="put_btn" onclick="addChildCategory(\'' + data.pid + '\', \'3\', \'' + (data.divi_name || '').replace(/'/g, "\\'") + '\')"><img src="/image/web/notice_btn_icon.svg" alt="추가" style="width:12px; height:12px; margin:0; position:static !important;">추가</button>');
$("#btnArea4").empty();
} else if (depth === 3) {
table4.setData(children).then(function () {
if (currentState && currentState.depth4) {
table4.selectRow(currentState.depth4);
}
});
$("#btnArea4").html('<button class="put_btn" onclick="addChildCategory(\'' + data.pid + '\', \'4\', \'' + (data.divi_name || '').replace(/'/g, "\\'") + '\')"><img src="/image/web/notice_btn_icon.svg" alt="추가" style="width:12px; height:12px; margin:0; position:static !important;">추가</button>');
}
}
/* ====== 트리 구성 ====== */
function buildNodeMap(nodes) {
nodes.forEach(node => {
nodeMap[node.pid] = node;
if (node._children && node._children.length > 0) {
buildNodeMap(node._children);
} else if (node._children && node._children.length === 0) {
delete node._children; // 하위 뎁스가 없어 최하위 node일 경우 트리 확정버튼(+/-)을 숨기기 위해 빈 배열 삭제
}
});
}
/* ====== 데이터 로드 ====== */
window.loadData = function (preserveState) {
let currentState = {};
if (preserveState) {
currentState.depth1 = $("#depth1Select").val();
currentState.depth1Overview = $("#depth1SelectOverview").val();
let selected2 = table2.getSelectedData()[0];
let selected3 = table3.getSelectedData()[0];
let selected4 = table4.getSelectedData()[0];
if (selected2) currentState.depth2 = selected2.pid;
if (selected3) currentState.depth3 = selected3.pid;
if (selected4) currentState.depth4 = selected4.pid;
}
$.ajax({
url: "/settings/medicalCategory/getMedicalCategoryTreeList.do",
type: "POST",
data: { storePid: '1' },
success: function (res) {
if (res.msgCode === '0') {
globalTreeData = res.rows || [];
nodeMap = {};
buildNodeMap(globalTreeData);
renderDepth1Select(globalTreeData, currentState);
} else {
alert(res.msgDesc);
}
},
error: function () {
alert("데이터 목록을 불러오는데 실패했습니다.");
}
});
};
function renderDepth1Select(nodes, currentState) {
let filteredNodes = [];
if (nodes && nodes.length > 0) {
const seenNames = new Set();
for (const node of nodes) {
const name = node.divi_name ? node.divi_name.trim() : '';
if (!seenNames.has(name)) {
seenNames.add(name);
filteredNodes.push(node);
}
}
}
const optionList = $("#depth1OptionList");
const labelBtn = $("#depth1SelectBox .label");
const hiddenInput = $("#depth1Select");
optionList.empty();
const optListOverview = $("#depth1OptionListOverview");
const lblBtnOverview = $("#depth1SelectBoxOverview .label");
const hdInputOverview = $("#depth1SelectOverview");
optListOverview.empty();
if (filteredNodes.length === 0) {
labelBtn.text('항목 없음');
hiddenInput.val('');
lblBtnOverview.text('항목 없음');
hdInputOverview.val('');
table2.setData([]);
table3.setData([]);
table4.setData([]);
$("#btnArea2").empty();
$("#btnArea3").empty();
$("#btnArea4").empty();
if (tableOverview) tableOverview.setData([]);
return;
}
filteredNodes.forEach(function (node) {
var li = $('<li class="option_list_item"></li>');
var label = $('<label></label>').text(node.divi_name);
li.append(label);
li.data('value', node.pid);
li.on('click', function () {
hiddenInput.val(node.pid);
labelBtn.text(node.divi_name);
window.onChangeDepth1();
});
optionList.append(li);
var liOver = $('<li class="option_list_item"></li>');
var labelOver = $('<label></label>').text(node.divi_name);
liOver.append(labelOver);
liOver.data('value', node.pid);
liOver.on('click', function () {
hdInputOverview.val(node.pid);
lblBtnOverview.text(node.divi_name);
window.onChangeDepth1Overview();
});
optListOverview.append(liOver);
});
// 선택 상태 복원
let targetPid = filteredNodes[0].pid;
let targetName = filteredNodes[0].divi_name;
let targetPidOverview = filteredNodes[0].pid;
let targetNameOverview = filteredNodes[0].divi_name;
if (currentState && currentState.depth1) {
let matched = filteredNodes.find(n => n.pid == currentState.depth1);
if (matched) {
targetPid = matched.pid;
targetName = matched.divi_name;
}
}
if (currentState && currentState.depth1Overview) {
let matched = filteredNodes.find(n => n.pid == currentState.depth1Overview);
if (matched) {
targetPidOverview = matched.pid;
targetNameOverview = matched.divi_name;
}
}
hiddenInput.val(targetPid);
labelBtn.text(targetName);
window.onChangeDepth1(currentState);
hdInputOverview.val(targetPidOverview);
lblBtnOverview.text(targetNameOverview);
if (overviewInitialized) {
window.onChangeDepth1Overview();
}
}
window.onChangeDepth1Overview = function () {
const selectedPid = $("#depth1SelectOverview").val();
if (!selectedPid) {
if (tableOverview) tableOverview.setData([]);
return;
}
const pid = parseInt(selectedPid);
const node = nodeMap[pid];
if (tableOverview && node) {
tableOverview.setData([node]);
} else if (tableOverview) {
tableOverview.setData([]);
}
};
window.onChangeDepth1 = function (currentState) {
const selectedPid = $("#depth1Select").val();
if (!selectedPid) {
table2.setData([]);
table3.setData([]);
table4.setData([]);
$("#btnArea2").empty();
$("#btnArea3").empty();
$("#btnArea4").empty();
return;
}
const pid = parseInt(selectedPid);
const node = nodeMap[pid];
table3.setData([]);
table4.setData([]);
$("#btnArea3").empty();
$("#btnArea4").empty();
if (node && node._children) {
table2.setData(node._children).then(function () {
if (currentState && currentState.depth2) {
table2.selectRow(currentState.depth2);
let selectedRows = table2.getSelectedRows();
if (selectedRows && selectedRows.length > 0) {
clickRow(selectedRows[0], 2, currentState);
}
}
});
$("#btnArea2").html('<button class="put_btn" style="padding:2px 8px; font-size:12px; height:26px;" onclick="addChildCategory(\'' + node.pid + '\', \'2\', \'' + (node.divi_name || '').replace(/'/g, "\\'") + '\')"><img src="/image/web/notice_btn_icon.svg" alt="추가" style="width:12px; height:12px; margin:0; position:static !important;">추가</button>');
} else {
table2.setData([]);
$("#btnArea2").empty();
}
};
/* ====== 팝업 관련 ====== */
window.editCategory = function (pid) {
openInfoPopup('edit', pid);
};
window.addChildCategory = function (diviParent, diviDept, parentName) {
openInfoPopup('add', '', diviDept, diviParent, (parentName || '').replace(/'/g, "\\'"));
};
window.delCategory = function (pid) {
if (!confirm("해당 카테고리를 삭제하시겠습니까?\n하위 항목이 있을 경우 삭제되지 않습니다.")) return;
$.ajax({
url: "/settings/medicalCategory/delMedicalCategory.do",
type: "POST",
data: { pid: pid },
success: function (res) {
alert(res.msgDesc);
if (res.msgCode === '0') {
loadData(true);
}
},
error: function () {
alert("삭제 통신 중 오류가 발생했습니다.");
}
});
};
function openInfoPopup(mode, pid, diviDept, diviParent, parentName) {
pid = pid || '';
diviDept = diviDept || '';
diviParent = diviParent || '';
parentName = parentName || '';
const url = '/settings/medicalCategory/infoPop.do?mode=' + mode + '&pid=' + pid + '&diviDept=' + diviDept + '&diviParent=' + diviParent + '&parentName=' + encodeURIComponent(parentName);
let width = 650;
let height = 750;
let left = (screen.width - width) / 2;
let top = (screen.height - height) / 2;
window.open(url, "MedicalCategoryInfoPopup", "width=" + width + ",height=" + height + ",left=" + left + ",top=" + top + ",scrollbars=yes,resizable=yes");
}
initGrid();
/* ====== 전체보기 탭 ====== */
// 탭 전환 이벤트
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var target = $(e.target).attr('href');
if (target === '#tabOverview') {
if (!overviewInitialized) {
initOverviewGrid(function () {
window.onChangeDepth1Overview();
});
overviewInitialized = true;
} else {
window.onChangeDepth1Overview();
}
}
});
function initOverviewGrid(callback) {
tableOverview = new Tabulator("#gridOverview", {
dataTree: true,
dataTreeStartExpanded: true,
layout: "fitColumns",
placeholder: "데이터를 불러오는 중...",
height: "100%",
columnDefaults: {
headerTooltip: true,
headerHozAlign: "center",
tooltip: true
},
columns: [
{ title: "1depth", field: "divi_name", width: "15%", hozAlign: "left", formatter: overviewDepthFormatter(1) },
{ title: "2depth", field: "divi_name2", width: "12%", hozAlign: "left", formatter: overviewDepthFormatter(2) },
{ title: "3depth", field: "divi_name3", width: "18%", hozAlign: "left", formatter: overviewDepthFormatter(3) },
{ title: "4depth", field: "divi_name4", width: "12%", hozAlign: "left", formatter: overviewDepthFormatter(4) },
{ title: "단가", field: "kind_cost", width: "10%", formatter: costFormatter, hozAlign: "right" },
{ title: "단위", field: "kind_unit", width: "10%", hozAlign: "center" },
{ title: "사용여부", field: "list_use", width: "10%", formatter: ynFormatter, hozAlign: "center" },
{ title: "면세여부", field: "tax_free", width: "8%", formatter: ynFormatter, hozAlign: "center" }
]
});
tableOverview.on("tableBuilt", function () {
if (typeof callback === 'function') callback();
});
}
function overviewDepthFormatter(targetDepth) {
return function (cell) {
var data = cell.getRow().getData();
if (parseInt(data.divi_dept, 10) !== targetDepth) return '';
var nameStyle = data.list_use === 'y' ? 'font-weight:600;' : 'text-decoration:line-through; color:#aaa;';
var color = data.divi_color || '#333';
if (data.list_use !== 'y') color = '#aaa';
var html = '<span style="' + nameStyle + ' color:' + color + '">' + (data.divi_name || '') + '</span>';
if (data.tax_free === 'y') {
html += ' <span style="color:#ff0000; font-size:11px; margin-left: 5px;">[면세]</span>';
}
return html;
};
}
function loadOverviewData() {
if (globalTreeData.length === 0) {
window.loadData();
} else {
window.onChangeDepth1Overview();
}
}
});

View File

@@ -0,0 +1,265 @@
/**
* 진료유형 상세/추가 팝업 스크립트
* - medical_divi_list DDL 전체 컬럼 반영
*/
$(document).ready(function () {
// 1. URL 파라미터 파싱
const params = new URLSearchParams(window.location.search);
const mode = params.get('mode'); // 'add' or 'edit'
const pid = params.get('pid');
const diviDept = params.get('diviDept');
const diviParent = params.get('diviParent');
const parentName = params.get('parentName');
bindEvents();
// 뎁스3이면 거래처 목록을 로드한 뒤 폼 초기화를 진행
if (diviDept === '3' || mode === 'edit') {
loadCustList().then(function () {
initForm();
applyDepthUI();
});
} else {
initForm();
applyDepthUI();
}
function applyDepthUI() {
const currentDept = $("#diviDept").val() || diviDept;
if (currentDept == '1') {
$("#trNoTax").show();
$("#trNoTax1").show();
} else {
$("#trNoTax").hide();
$("#trNoTax1").hide();
}
if (currentDept == '3') {
$("#productInfoTitle").show();
$("#productInfoTable").show();
} else {
$("#productInfoTitle").hide();
$("#productInfoTable").hide();
}
}
// 거래처 셀렉트 박스 세팅
function loadCustList() {
return new Promise(function (resolve) {
$.ajax({
url: '/settings/medicalCategory/getCustList.do',
type: 'POST',
success: function (res) {
if (res.msgCode === '0' && res.data) {
const $select = $("#custListPid");
$select.empty().append('<option value="">선택</option>');
res.data.forEach(function (cust) {
$select.append(`<option value="${cust.pid}">${cust.cust_name}</option>`);
});
}
resolve();
},
error: function () {
console.error("거래처 목록 로드 실패");
resolve();
}
});
});
}
function initForm() {
if (mode === 'add') {
$("#popTitle").text((diviDept == '1' ? '최상위 ' : '') + '진료유형 신규 등록');
$("#pid").val('');
$("#diviDept").val(diviDept);
$("#diviParent").val(diviParent);
$("#btn_delete").hide();
if (diviParent !== '0' && parentName) {
$("#parentNameRow").show();
$("#parentNameTxt").text(parentName);
}
} else if (mode === 'edit') {
$("#popTitle").text('진료유형 정보 수정');
$("#pid").val(pid);
$("#btn_delete").show();
loadDetail(pid);
}
}
function loadDetail(id) {
$.ajax({
url: '/settings/medicalCategory/getMedicalCategory.do',
type: 'POST',
data: { pid: id },
success: function (res) {
if (res.msgCode === '0') {
const data = res.data;
$("#diviDept").val(data.divi_dept);
$("#diviParent").val(data.divi_parent);
$("#diviName").val(data.divi_name);
$("#diviSort").val(data.divi_sort);
$("#diviColor").val(data.divi_color || '#000000');
// 단가/제품 정보
if (data.cust_list_pid && data.cust_list_pid !== 0) {
$("#custListPid").val(data.cust_list_pid);
}
$("#kindCost").val(data.kind_cost || 0);
$("#kindUnit").val(data.kind_unit || '');
$("#kindUnitVol").val(data.kind_unit_vol || 0);
$("#kindUnit2").val(data.kind_unit2 || '');
$("#kindMsg1").val(data.kind_msg1 || '');
$("#kindMsg2").val(data.kind_msg2 || '');
$("#ccRate").val(data.cc_rate || '');
// 설정 버튼들
setRadio('listUse', data.list_use);
setCheckbox('clinicUse', data.clinic_use);
setCheckbox('expenseUse', data.expense_use);
setCheckbox('taxFree', data.tax_free);
setCheckbox('etcincomeUse', data.etcincome_use);
setCheckbox('etclossUse', data.etcloss_use);
setCheckbox('stockUse', data.stock_use);
setCheckbox('noTax', data.no_tax);
setCheckbox('noTax1', data.no_tax1);
setCheckbox('npayUse', data.npay_use);
applyDepthUI();
} else {
alert("정보를 불러올 수 없습니다.");
window.close();
}
},
error: function () {
alert("상세 정보 조회 통신 오류");
window.close();
}
});
}
function setRadio(name, value) {
if (value) {
$("input:radio[name='" + name + "'][value='" + value + "']").prop('checked', true);
}
}
function getRadio(name) {
return $("input:radio[name='" + name + "']:checked").val();
}
function setCheckbox(name, value) {
if (value === 'y') {
$("input:checkbox[name='" + name + "']").prop('checked', true);
} else {
$("input:checkbox[name='" + name + "']").prop('checked', false);
}
}
function getCheckbox(name) {
return $("input:checkbox[name='" + name + "']").is(":checked") ? 'y' : 'n';
}
function bindEvents() {
$("#btn_close").on("click", function () {
window.close();
});
$("#btn_save").on("click", function () {
saveCategory();
});
$("#btn_delete").on("click", function () {
deleteCategory();
});
}
function saveCategory() {
const dName = $("#diviName").val().trim();
if (!dName) {
alert("진료유형 명칭을 입력하세요.");
$("#diviName").focus();
return;
}
const submitObj = {
pid: $("#pid").val() || null,
storePid: $("#storePid").val(),
diviName: dName,
diviDept: $("#diviDept").val(),
diviParent: $("#diviParent").val(),
diviSort: parseInt($("#diviSort").val() || "0", 10),
diviColor: $("#diviColor").val(),
// 구분 설정
listUse: getRadio('listUse') || 'n',
clinicUse: getCheckbox('clinicUse'),
expenseUse: getCheckbox('expenseUse'),
taxFree: getCheckbox('taxFree'),
etcincomeUse: getCheckbox('etcincomeUse'),
etclossUse: getCheckbox('etclossUse'),
stockUse: getCheckbox('stockUse'),
noTax: getCheckbox('noTax'),
noTax1: getCheckbox('noTax1'),
npayUse: getCheckbox('npayUse'),
// 단가/제품 정보
custListPid: parseInt($("#custListPid").val() || "0", 10),
kindCost: parseFloat($("#kindCost").val() || "0"),
kindUnit: $("#kindUnit").val(),
kindUnitVol: parseFloat($("#kindUnitVol").val() || "0"),
kindUnit2: $("#kindUnit2").val(),
kindMsg1: $("#kindMsg1").val(),
kindMsg2: $("#kindMsg2").val(),
ccRate: $("#ccRate").val()
};
const targetUrl = mode === 'edit' ? '/settings/medicalCategory/modMedicalCategory.do' : '/settings/medicalCategory/putMedicalCategory.do';
$.ajax({
url: targetUrl,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(submitObj),
success: function (res) {
alert(res.msgDesc);
if (res.msgCode === '0') {
if (window.opener && typeof window.opener.loadData === 'function') {
window.opener.loadData(true);
}
window.close();
}
},
error: function () {
alert("저장 중 시스템 오류가 발생했습니다.");
}
});
}
function deleteCategory() {
const pidVal = $("#pid").val();
if (!pidVal) return;
if (!confirm("해당 카테고리를 삭제하시겠습니까?\n하위 항목이 있을 경우 삭제되지 않습니다.")) return;
$.ajax({
url: '/settings/medicalCategory/delMedicalCategory.do',
type: 'POST',
data: { pid: pidVal },
success: function (res) {
alert(res.msgDesc);
if (res.msgCode === '0') {
if (window.opener && typeof window.opener.loadData === 'function') {
window.opener.loadData(true);
}
window.close();
}
},
error: function () {
alert("삭제 중 시스템 오류가 발생했습니다.");
}
});
}
});

View File

@@ -0,0 +1,148 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{/web/layout/homeLayout}">
<head>
<title>통합 진료유형 관리</title>
</head>
<th:block layout:fragment="layout_css">
<link rel="stylesheet" href="/css/web/webCategorySelectList.css?v2.1">
<link rel="stylesheet" href="/css/web/hospital_work.css?v1.1">
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
<link rel="stylesheet" href="https://unpkg.com/tabulator-tables@5.6.1/dist/css/tabulator.min.css">
<link rel="stylesheet" href="/css/web/categoryTree.css?v1.2">
<style>
.project_wrap .content_section .hospital_wrap .center_box .tab_panel {
height: 100%;
overflow: hidden;
}
.project_wrap .content_section .hospital_wrap .center_box .tab_panel .tab-content {
height: calc(100% - 52px);
overflow: hidden;
}
.project_wrap .content_section .hospital_wrap .center_box .tab_panel .tab-content .tab-pane {
height: 100%;
overflow: hidden;
}
/* 현재 화면 전용 여백 제거 */
.project_wrap .content_section .hospital_wrap .center_box .filter_box .form_box .select_box {
margin-left: 0 !important;
}
</style>
</th:block>
<th:block layout:fragment="layout_top_script">
<script>
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
</script>
</th:block>
<th:block layout:fragment="layout_content">
<div class="center_box">
<div class="tab_panel" role="tabpanel">
<!-- Nav tabs -->
<div class="nav_box">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a href="#tabManage" aria-controls="tabManage" role="tab" data-toggle="tab">
<p>진료유형 관리</p>
</a>
</li>
<li role="presentation">
<a href="#tabOverview" aria-controls="tabOverview" role="tab" data-toggle="tab">
<p>전체보기</p>
</a>
</li>
</ul>
</div>
<!-- Tab panes -->
<div class="tab-content">
<!-- ===== 탭1: 진료유형 관리 ===== -->
<div role="tabpanel" class="tab-pane active" id="tabManage">
<div class="filter_box">
<div class="form_box">
<div class="select_list first">
<div class="select_box dropdown" id="depth1SelectBox">
<button type="button" class="label dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">진료과목 전체</button>
<input type="hidden" id="depth1Select">
<ul class="select_option_list dropdown-menu" id="depth1OptionList"></ul>
</div>
</div>
<div class="right_btn_box">
<button class="put_btn" onclick="addChildCategory('0', '1', '최상위 항목')">
<img src="/image/web/notice_btn_icon.svg" alt="추가">추가
</button>
</div>
</div>
</div>
<!-- 2depth | 3depth | 4depth 가로 배치 -->
<div class="category-tree-layout">
<!-- Depth 2 -->
<div class="category-tree-panel" style="flex:25; width:auto; min-width:0;">
<div class="tree-panel-header">
<span class="tree-panel-title">진료유형</span>
<div class="tree-panel-actions" id="btnArea2"></div>
</div>
<div id="gridDepth2" class="category-tree-container"></div>
</div>
<!-- Depth 3 -->
<div class="category-tree-panel" style="flex:55; width:auto; min-width:0;">
<div class="tree-panel-header">
<span class="tree-panel-title">제품/시술</span>
<div class="tree-panel-actions" id="btnArea3"></div>
</div>
<div id="gridDepth3" class="category-tree-container"></div>
</div>
<!-- Depth 4 -->
<div class="category-tree-panel" style="flex:20; width:auto; min-width:0;">
<div class="tree-panel-header">
<span class="tree-panel-title">부위</span>
<div class="tree-panel-actions" id="btnArea4"></div>
</div>
<div id="gridDepth4" class="category-tree-container"></div>
</div>
</div>
</div>
<!-- ===== 탭2: 전체보기 ===== -->
<div role="tabpanel" class="tab-pane" id="tabOverview">
<div class="filter_box">
<div class="form_box">
<div class="select_list first">
<div class="select_box dropdown" id="depth1SelectBoxOverview">
<button type="button" class="label dropdown-toggle" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false">진료과목 전체</button>
<input type="hidden" id="depth1SelectOverview">
<ul class="select_option_list dropdown-menu" id="depth1OptionListOverview"></ul>
</div>
</div>
</div>
</div>
<div id="gridOverview" style="width:100%; height:calc(100vh - 270px);"></div>
</div>
</div>
</div>
</div>
</th:block>
<th:block layout:fragment="layout_popup">
</th:block>
<th:block layout:fragment="layout_script">
<script src="https://unpkg.com/tabulator-tables@5.6.1/dist/js/tabulator.min.js"></script>
<script th:src="@{/js/web/settings/medicalcategory/medicalCategoryList.js(v=202602223)}"></script>
</th:block>
</html>

View File

@@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
<title>진료유형 정보</title>
<link rel="stylesheet" th:href="@{/css/common.css}">
<script th:src="@{/js/web/jquery.min.js}"></script>
<style>
.pop_wrap {
max-width: 600px;
margin: 0 auto;
}
.pop_wrap h3 {
font-size: 16px;
margin-bottom: 16px;
padding-bottom: 10px;
border-bottom: 2px solid #3985EA;
color: #333;
}
.board_write {
width: 100%;
border-collapse: collapse;
}
.board_write th {
background: #f8f9fb;
padding: 10px 12px;
text-align: left;
font-size: 13px;
font-weight: 600;
color: #555;
border: 1px solid #e9ecf0;
white-space: nowrap;
}
.board_write td {
padding: 8px 12px;
border: 1px solid #e9ecf0;
}
.board_write input[type="text"],
.board_write input[type="number"] {
height: 30px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 0 8px;
font-size: 13px;
}
.board_write select {
height: 30px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 0 8px;
font-size: 13px;
}
.w100p {
width: 100%;
box-sizing: border-box;
}
.w80 {
width: 80px;
}
.w120 {
width: 120px;
}
.ess {
color: #ff0000;
}
.txt_info {
font-size: 11px;
color: #999;
}
.pop_btn_area {
text-align: center;
margin-top: 20px;
}
.btn_blue {
padding: 8px 24px;
background: #3985EA;
color: #fff;
border: none;
border-radius: 5px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.btn_blue:hover {
background: #2c6fd1;
}
.btn_gray {
padding: 8px 24px;
background: #fff;
color: #666;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
cursor: pointer;
margin-left: 6px;
}
.btn_gray:hover {
background: #f5f5f5;
}
.btn_red {
padding: 8px 24px;
background: #ff4444;
color: #fff;
border: none;
border-radius: 5px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
margin-left: 6px;
}
.btn_red:hover {
background: #cc0000;
}
.section-title {
font-size: 13px;
font-weight: 700;
color: #3985EA;
padding: 8px 0 4px 0;
border-bottom: 1px solid #e9ecf0;
margin-top: 12px;
}
</style>
</head>
<body style="padding: 20px;">
<div class="pop_wrap">
<h3 id="popTitle">진료유형 등록</h3>
<input type="hidden" id="pid" value="">
<input type="hidden" id="diviDept" value="">
<input type="hidden" id="diviParent" value="">
<!-- 현재 하드코딩된 스토어 ID(지점), 추후엔 로그인세션 등에서 가져옴 -->
<input type="hidden" id="storePid" value="1">
<!-- 기본 정보 -->
<table class="board_write">
<colgroup>
<col width="120px">
<col width="*">
</colgroup>
<tbody>
<tr id="parentNameRow" style="display:none;">
<th>상위 카테고리</th>
<td><span id="parentNameTxt"></span></td>
</tr>
<tr>
<th><span class="ess">*</span> 항목 명칭</th>
<td><input type="text" id="diviName" class="w100p" placeholder="예: 피부, 보톡스, 제모 등"></td>
</tr>
<tr>
<th>정렬 순서</th>
<td><input type="number" id="diviSort" class="w80" value="0"> <span class="txt_info">숫자가 낮을수록 먼저
표시됩니다.</span></td>
</tr>
<tr>
<th>색상 코드</th>
<td><input type="color" id="diviColor" value="#000000"></td>
</tr>
</tbody>
</table>
<!-- 구분 설정 -->
<div class="section-title">구분 설정</div>
<table class="board_write">
<colgroup>
<col width="120px">
<col width="*">
</colgroup>
<tbody>
<tr>
<th>노출 여부</th>
<td>
<label><input type="radio" name="listUse" value="y" checked> Y</label>
<label><input type="radio" name="listUse" value="n"> N</label>
</td>
</tr>
<tr>
<th>구분</th>
<td>
<label><input type="checkbox" name="clinicUse" value="y" checked> 진료</label>&nbsp;
<label><input type="checkbox" name="expenseUse" value="y"> 비용(판관비)</label>&nbsp;
<label><input type="checkbox" name="taxFree" value="y"> 면세</label>&nbsp;
<label><input type="checkbox" name="etcincomeUse" value="y"> 영업외 수익</label>&nbsp;
<label><input type="checkbox" name="etclossUse" value="y"> 영업외 손실</label>
</td>
</tr>
<tr>
<th>재고관리품목</th>
<td>
<label><input type="checkbox" name="stockUse" value="y" checked></label>&nbsp;&nbsp;&nbsp;&nbsp;
<label>재고관리 없이 미수금만 관리하실경우 체크하세요 <input type="checkbox" name="npayUse" value="y"></label>
</td>
</tr>
<tr id="trNoTax">
<th>비과세</th>
<td>
<label><input type="checkbox" name="noTax" value="y"></label>
</td>
</tr>
<tr id="trNoTax1">
<th>비세금</th>
<td>
<label><input type="checkbox" name="noTax1" value="y"></label>
</td>
</tr>
</tbody>
</table>
<!-- 단가/제품 정보 -->
<div class="section-title" id="productInfoTitle">단가/제품 정보</div>
<table class="board_write" id="productInfoTable">
<colgroup>
<col width="120px">
<col width="*">
</colgroup>
<tbody>
<tr>
<th>거래처</th>
<td>
<select id="custListPid" class="w100p"
style="height:30px; border:1px solid #ddd; border-radius:4px;">
<option value="">선택</option>
</select>
</td>
</tr>
<tr>
<th>단가</th>
<td><input type="number" id="kindCost" class="w120" value="0"></td>
</tr>
<tr>
<th>단위</th>
<td>
<input type="text" id="kindUnit" class="w120" placeholder="예: cc, ml">
<span class="txt_info" style="margin-left:8px;">용량:</span>
<input type="number" id="kindUnitVol" class="w80" value="0" step="0.1">
<input type="text" id="kindUnit2" style="width:60px;" placeholder="단위">
</td>
</tr>
<tr>
<th>제품설명</th>
<td><input type="text" id="kindMsg1" class="w100p" placeholder="제품설명을 입력하세요"></td>
</tr>
<tr>
<th>기타설명</th>
<td><input type="text" id="kindMsg2" class="w100p" placeholder="기타설명을 입력하세요"></td>
</tr>
<tr>
<th>비율</th>
<td><input type="text" id="ccRate" class="w120" placeholder="비율"></td>
</tr>
</tbody>
</table>
<div class="pop_btn_area">
<button type="button" class="btn_blue" id="btn_save">저장</button>
<button type="button" class="btn_red" id="btn_delete" style="display:none;">삭제</button>
<button type="button" class="btn_gray" id="btn_close">닫기</button>
</div>
</div>
<script th:src="@{/js/web/settings/medicalcategory/popup/medicalCategoryInfoPop.js}"></script>
</body>
</html>