시술후기 구현
This commit is contained in:
42
docs/작업내역_20260225.md
Normal file
42
docs/작업내역_20260225.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 메이드유 CRM 작업내역 — 2026년 2월 25일
|
||||||
|
|
||||||
|
### 1. 코드 관리 기능 추가
|
||||||
|
- CRM에서 사용하는 **공통코드**(단위, 분류 등)를 조회·등록·수정·삭제할 수 있는 **코드 관리 화면**이 새로 추가되었습니다.
|
||||||
|
|
||||||
|
### 2. 진료유형 설정 화면 수정
|
||||||
|
- 진료유형 설정의 각 단계(1~5뎁스) 팝업 화면이 수정되었습니다.
|
||||||
|
- 진료유형 목록 화면의 기능이 개선되었습니다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 직접 작업한 내역
|
||||||
|
|
||||||
|
### 1. 사용약품 검색 및 등록 기능
|
||||||
|
- 용량/출력(4뎁스) 카테고리에서 **사용 약품을 검색하여 등록**할 수 있는 기능을 새로 만들었습니다.
|
||||||
|
- "약품 추가(검색)" 버튼을 누르면 **검색 팝업**이 열리고, 제품명으로 검색할 수 있습니다.
|
||||||
|
- 검색 결과에서 **제품명을 클릭**하면 해당 약품이 바로 등록됩니다.
|
||||||
|
|
||||||
|
### 2. 사용약품 관리 표(그리드) 구성
|
||||||
|
- 등록된 약품들이 아래 항목으로 구성된 표에 표시됩니다.
|
||||||
|
|
||||||
|
| 항목 | 설명 |
|
||||||
|
|------|------|
|
||||||
|
| 거래처 | 약품을 공급하는 거래처 이름 |
|
||||||
|
| 약품명 | 등록된 약품 이름 |
|
||||||
|
| 재고 | 현재 남아있는 재고 수량 |
|
||||||
|
| 재고단위 | 재고의 단위 (ml, cc 등) |
|
||||||
|
| **사용량** | 1회 시술 시 사용하는 양 (클릭하여 수정 가능) |
|
||||||
|
| **사용단위** | 사용량의 단위 (클릭하여 선택 가능) |
|
||||||
|
| 삭제 | 약품을 목록에서 제거 |
|
||||||
|
|
||||||
|
- **사용량**과 **사용단위**는 표에서 바로 클릭하여 수정할 수 있으며, 수정 즉시 자동 저장됩니다.
|
||||||
|
- 사용단위는 드롭다운 목록에서 선택하는 방식이며, 기본값은 "선택"입니다.
|
||||||
|
- 수정 가능한 칸은 연한 파란색 배경으로 표시되어 있습니다.
|
||||||
|
|
||||||
|
### 3. 신규 등록 화면에서도 약품관리 표시
|
||||||
|
- 기존에는 수정할 때만 약품관리가 보였으나, 이제 **새로 등록하는 화면에서도** 약품관리 영역이 보입니다.
|
||||||
|
- 단, 카테고리를 먼저 저장한 뒤에 약품을 추가할 수 있으며, 저장 전에는 안내 문구가 표시됩니다.
|
||||||
|
|
||||||
|
### 4. 약품 데이터 저장 구조 신규 생성
|
||||||
|
- 시술별 사용 약품 정보를 저장하기 위한 **데이터베이스 테이블을 새로 생성**하였습니다.
|
||||||
|
- 약품명, 거래처, 사용량, 단위, 단가 등의 정보가 저장됩니다.
|
||||||
36
rules.md
Normal file
36
rules.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 프로젝트 코딩 가이드라인 (Java Backend)
|
||||||
|
|
||||||
|
AI 에디터(Agent)는 다음 규칙을 항상 준수하여 코드를 작성하고 수정해야 합니다.
|
||||||
|
|
||||||
|
## 0. 기본 소통 규칙 (Communication)
|
||||||
|
- **언어**: 사용자에 대한 모든 답변과 코드 설명은 항상 **한글(Korean)**로만 작성해야 합니다.
|
||||||
|
|
||||||
|
## 1. 패키지 구성 (Package Structure)
|
||||||
|
- **컨트롤러 (Controller)**: `ctrl`
|
||||||
|
- **서비스 (Service)**: `svc`
|
||||||
|
- **DTO (Data Transfer Object)**: `dto`
|
||||||
|
- **매퍼 (Mapper)**: `mapper`
|
||||||
|
|
||||||
|
## 2. 파일 명명 규칙 및 구성 (File Naming Conventions)
|
||||||
|
- **컨트롤러 (Controller)**: `[도메인명]Controller.java` (예: `ABCDController.java`)
|
||||||
|
- **서비스 (Service)**: `[도메인명]Service.java` (인터페이스와 구현체(impl)를 분리하지 않고 Service 클래스 파일 하나로만 구현, 예: `ABCDService.java`)
|
||||||
|
- **DTO**: `[도메인명]DTO.java` (예: `ABCDDTO.java`)
|
||||||
|
- **매퍼 (Mapper)**: `[도메인명]Mapper.java` (예: `ABCDMapper.java`)
|
||||||
|
|
||||||
|
## 3. URL 및 메소드 명명 규칙 (RequestMapping & Method Naming)
|
||||||
|
|
||||||
|
### 1) RequestMapping (URL) 및 컨트롤러 메소드명
|
||||||
|
- 페이지 이동하는 url : `moveXXXX.do`
|
||||||
|
- 팝업 오픈하는 url : `openXXXX.do`
|
||||||
|
- 조회 url : `getXXXX.do`
|
||||||
|
- 저장 url : `putXXXX.do`
|
||||||
|
- 수정 url : `modXXXX.do`
|
||||||
|
- 삭제 url : `delXXXX.do`
|
||||||
|
- **단, 컨트롤러 메소드명은 위 url에서 `.do`를 제외한 이름과 동일하게 명명합니다.**
|
||||||
|
|
||||||
|
### 2) 서비스 메소드명
|
||||||
|
- 단일조회 : `selectXXXX`
|
||||||
|
- 리스트조회 : `selectListXXXX`
|
||||||
|
- insert : `insertXXXX`
|
||||||
|
- update : `updateXXXX`
|
||||||
|
- delete : `deleteXXXX`
|
||||||
38
sql/create_mu_procedure_review.sql
Normal file
38
sql/create_mu_procedure_review.sql
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- MU_PROCEDURE_REVIEW : 시술후기 전용 테이블
|
||||||
|
-- 기존 HP_BEFORE_AFTER_PHOTO_BBS 대신 사용
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
CREATE TABLE `MU_PROCEDURE_REVIEW` (
|
||||||
|
`MU_PROCEDURE_REVIEW_ID` varchar(25) NOT NULL COMMENT '시술후기 식별자',
|
||||||
|
`MU_MEMBER_ID` varchar(25) NOT NULL COMMENT '작성자 식별자',
|
||||||
|
`CATEGORY_NO` int(11) DEFAULT NULL COMMENT '카테고리 번호',
|
||||||
|
`TITLE` varchar(200) NOT NULL COMMENT '제목',
|
||||||
|
`CONTENT` text NOT NULL COMMENT '내용 (HTML - Quill 에디터)',
|
||||||
|
`HASHTAG` varchar(500) DEFAULT NULL COMMENT '해시태그',
|
||||||
|
`BEFORE_PHOTO_ATTACHFILE_ID` varchar(30) DEFAULT NULL COMMENT 'Before 사진 첨부파일 식별자',
|
||||||
|
`AFTER_PHOTO_ATTACHFILE_ID` varchar(30) DEFAULT NULL COMMENT 'After 사진 첨부파일 식별자',
|
||||||
|
`VIEW_COUNT` int(11) NOT NULL DEFAULT 0 COMMENT '조회수',
|
||||||
|
`USE_YN` char(1) NOT NULL DEFAULT 'Y' COMMENT '사용여부 (Y:사용, N:삭제)',
|
||||||
|
`REG_ID` varchar(25) NOT NULL COMMENT '등록자',
|
||||||
|
`REG_DATE` timestamp NOT NULL DEFAULT current_timestamp() COMMENT '등록일자',
|
||||||
|
`MOD_ID` varchar(25) NOT NULL COMMENT '수정자',
|
||||||
|
`MOD_DATE` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp() COMMENT '수정일자',
|
||||||
|
PRIMARY KEY (`MU_PROCEDURE_REVIEW_ID`),
|
||||||
|
KEY `idx_mu_procedure_review_category` (`CATEGORY_NO`),
|
||||||
|
KEY `idx_mu_procedure_review_reg_date` (`REG_DATE`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='시술후기 정보';
|
||||||
|
|
||||||
|
-- 시퀀스 테이블
|
||||||
|
CREATE TABLE `MU_PROCEDURE_REVIEW_SEQ` (
|
||||||
|
`next_not_cached_value` bigint(21) NOT NULL,
|
||||||
|
`minimum_value` bigint(21) NOT NULL,
|
||||||
|
`maximum_value` bigint(21) NOT NULL,
|
||||||
|
`start_value` bigint(21) NOT NULL COMMENT 'start value when sequences is created or value if RESTART is used',
|
||||||
|
`increment` bigint(21) NOT NULL COMMENT 'increment value',
|
||||||
|
`cache_size` bigint(21) unsigned NOT NULL,
|
||||||
|
`cycle_option` tinyint(1) unsigned NOT NULL COMMENT '0 if no cycles are allowed, 1 if the sequence should begin a new cycle when maximum_value is passed',
|
||||||
|
`cycle_count` bigint(21) NOT NULL COMMENT 'How many cycles have been done'
|
||||||
|
) ENGINE=InnoDB SEQUENCE=1;
|
||||||
|
|
||||||
|
INSERT INTO `MU_PROCEDURE_REVIEW_SEQ` VALUES (1,1,99999999999,1,1,0,1,0);
|
||||||
@@ -0,0 +1,473 @@
|
|||||||
|
package com.madeu.crm.procedureReview.ctrl;
|
||||||
|
|
||||||
|
import com.madeu.constants.Constants;
|
||||||
|
import com.madeu.init.ManagerDraftAction;
|
||||||
|
import com.madeu.crm.procedureReview.svc.ProcedureReviewService;
|
||||||
|
import com.madeu.service.web.webloghistory.WebLogHistoryService;
|
||||||
|
import com.madeu.util.HttpUtil;
|
||||||
|
import com.madeu.util.RequestLogUtil;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.springframework.web.servlet.ModelAndView;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Controller
|
||||||
|
public class ProcedureReviewController extends ManagerDraftAction {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProcedureReviewService procedureReviewService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebLogHistoryService webLogHistoryService;
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/moveProcedureReviewList.do")
|
||||||
|
public String moveProcedureReviewList(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response, Model model) {
|
||||||
|
log.debug("ProcedureReviewController moveProcedureReviewList START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return "/web/login/logout";
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.selectProcedureReviewListIntro(paramMap);
|
||||||
|
|
||||||
|
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||||
|
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||||
|
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||||
|
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||||
|
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "/web/login/logout";
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController moveProcedureReviewList END");
|
||||||
|
return "/crm/procedureReview/procedureReviewSelectList";
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/getProcedureReviewList.do")
|
||||||
|
public ModelAndView getProcedureReviewList(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
log.debug("ProcedureReviewController getProcedureReviewList START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.selectListProcedureReview(paramMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (Constants.OK == map.get("msgCode")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("success", false);
|
||||||
|
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||||
|
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController getProcedureReviewList END");
|
||||||
|
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/getProcedureReview.do")
|
||||||
|
public ModelAndView getProcedureReview(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
log.debug("ProcedureReviewController getProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.selectProcedureReview(paramMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (Constants.OK == map.get("msgCode")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("success", false);
|
||||||
|
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||||
|
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController getProcedureReview END");
|
||||||
|
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/getCategoryList.do")
|
||||||
|
public ModelAndView getCategoryList(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
map = procedureReviewService.selectListCategory(paramMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (Constants.OK == map.get("msgCode")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("success", false);
|
||||||
|
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||||
|
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController getCategoryList END");
|
||||||
|
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/putProcedureReviewFile.do")
|
||||||
|
public ModelAndView putProcedureReviewFile(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
@RequestParam(value = "file", required = false) MultipartFile file) {
|
||||||
|
log.debug("ProcedureReviewController putProcedureReviewFile START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
StringBuffer errorMsg = new StringBuffer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.insertProcedureReviewFile(paramMap, file);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
errorMsg.append(e);
|
||||||
|
} finally {
|
||||||
|
if (Constants.OK == map.get("msgCode")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("success", false);
|
||||||
|
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||||
|
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||||
|
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
insertMap.put("url", "/procedureReview/putProcedureReviewFile.do");
|
||||||
|
insertMap.put("func", "putProcedureReviewFile");
|
||||||
|
insertMap.put("funcName", "홈페이지 시술후기 상세 이미지파일 저장");
|
||||||
|
insertMap.put("service", "procedureReviewService");
|
||||||
|
insertMap.put("serviceName", "홈페이지 시술후기 등록");
|
||||||
|
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||||
|
insertMap.put("responseValue", String.valueOf(map));
|
||||||
|
insertMap.put("tId", map.get("tId"));
|
||||||
|
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||||
|
|| String.valueOf(errorMsg).length() == 0) {
|
||||||
|
insertMap.put("resultCode", "SUCCESS");
|
||||||
|
} else {
|
||||||
|
insertMap.put("resultCode", "ERROR");
|
||||||
|
}
|
||||||
|
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||||
|
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||||
|
|
||||||
|
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController putProcedureReviewFile END");
|
||||||
|
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/moveProcedureReviewInsert.do")
|
||||||
|
public String moveProcedureReviewInsert(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Model model) {
|
||||||
|
log.debug("ProcedureReviewController moveProcedureReviewInsert START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return "/web/login/logout";
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.selectProcedureReviewInsertIntro(paramMap);
|
||||||
|
|
||||||
|
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||||
|
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||||
|
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||||
|
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||||
|
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||||
|
model.addAttribute("categorylist", map.get("categorylist"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "/web/login/logout";
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController moveProcedureReviewInsert END");
|
||||||
|
return "/crm/procedureReview/procedureReviewInsert";
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/putProcedureReview.do")
|
||||||
|
public ModelAndView putProcedureReview(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
log.debug("ProcedureReviewController putProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
StringBuffer errorMsg = new StringBuffer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.insertProcedureReview(paramMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
errorMsg.append(e);
|
||||||
|
} finally {
|
||||||
|
if (Constants.OK == map.get("msgCode")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("success", false);
|
||||||
|
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||||
|
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||||
|
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
insertMap.put("url", "/procedureReview/putProcedureReview.do");
|
||||||
|
insertMap.put("func", "putProcedureReview");
|
||||||
|
insertMap.put("funcName", "홈페이지 시술후기 등록");
|
||||||
|
insertMap.put("service", "procedureReviewService");
|
||||||
|
insertMap.put("serviceName", "홈페이지 시술후기 등록");
|
||||||
|
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||||
|
insertMap.put("responseValue", String.valueOf(map));
|
||||||
|
insertMap.put("tId", map.get("tId"));
|
||||||
|
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||||
|
|| String.valueOf(errorMsg).length() == 0) {
|
||||||
|
insertMap.put("resultCode", "SUCCESS");
|
||||||
|
} else {
|
||||||
|
insertMap.put("resultCode", "ERROR");
|
||||||
|
}
|
||||||
|
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||||
|
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||||
|
|
||||||
|
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController putProcedureReview END");
|
||||||
|
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/moveProcedureReviewUpdate.do")
|
||||||
|
public String moveProcedureReviewUpdate(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Model model) {
|
||||||
|
log.debug("ProcedureReviewController moveProcedureReviewUpdate START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return "/web/login/logout";
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.selectProcedureReviewUpdateIntro(paramMap);
|
||||||
|
|
||||||
|
model.addAttribute("selectUseYn", map.get("selectUseYn"));
|
||||||
|
model.addAttribute("insertUseYn", map.get("insertUseYn"));
|
||||||
|
model.addAttribute("updateUseYn", map.get("updateUseYn"));
|
||||||
|
model.addAttribute("deleteUseYn", map.get("deleteUseYn"));
|
||||||
|
model.addAttribute("downloadUseYn", map.get("downloadUseYn"));
|
||||||
|
model.addAttribute("categorylist", map.get("categorylist"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return "/web/login/logout";
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController moveProcedureReviewUpdate END");
|
||||||
|
return "/crm/procedureReview/procedureReviewUpdate";
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/modProcedureReview.do")
|
||||||
|
public ModelAndView modProcedureReview(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
log.debug("ProcedureReviewController modProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
StringBuffer errorMsg = new StringBuffer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.updateProcedureReview(paramMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
errorMsg.append(e);
|
||||||
|
} finally {
|
||||||
|
if (Constants.OK == map.get("msgCode")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("success", false);
|
||||||
|
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||||
|
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||||
|
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
insertMap.put("url", "/procedureReview/modProcedureReview.do");
|
||||||
|
insertMap.put("func", "modProcedureReview");
|
||||||
|
insertMap.put("funcName", "홈페이지 시술후기 수정");
|
||||||
|
insertMap.put("service", "procedureReviewService");
|
||||||
|
insertMap.put("serviceName", "홈페이지 시술후기 수정");
|
||||||
|
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||||
|
insertMap.put("responseValue", String.valueOf(map));
|
||||||
|
insertMap.put("tId", map.get("tId"));
|
||||||
|
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||||
|
|| String.valueOf(errorMsg).length() == 0) {
|
||||||
|
insertMap.put("resultCode", "SUCCESS");
|
||||||
|
} else {
|
||||||
|
insertMap.put("resultCode", "ERROR");
|
||||||
|
}
|
||||||
|
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||||
|
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||||
|
|
||||||
|
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController modProcedureReview END");
|
||||||
|
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/procedureReview/delProcedureReview.do")
|
||||||
|
public ModelAndView delProcedureReview(HttpSession session, HttpServletRequest request,
|
||||||
|
HttpServletResponse response) {
|
||||||
|
log.debug("ProcedureReviewController delProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
StringBuffer errorMsg = new StringBuffer();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!webCheckLogin(session)) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
paramMap.put("loginMemberId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("regId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
paramMap.put("modId", String.valueOf(session.getAttribute("loginMemberId")));
|
||||||
|
map = procedureReviewService.deleteProcedureReview(paramMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
errorMsg.append(e);
|
||||||
|
} finally {
|
||||||
|
if (Constants.OK == map.get("msgCode")) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("success", false);
|
||||||
|
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
|
||||||
|
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
|
||||||
|
HashMap<String, Object> insertMap = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
insertMap.put("url", "/procedureReview/delProcedureReview.do");
|
||||||
|
insertMap.put("func", "delProcedureReview");
|
||||||
|
insertMap.put("funcName", "홈페이지 시술후기 삭제");
|
||||||
|
insertMap.put("service", "procedureReviewService");
|
||||||
|
insertMap.put("serviceName", "홈페이지 시술후기 삭제");
|
||||||
|
insertMap.put("requestValue", String.valueOf(paramMap));
|
||||||
|
insertMap.put("responseValue", String.valueOf(map));
|
||||||
|
insertMap.put("tId", map.get("tId"));
|
||||||
|
if ((String.valueOf(errorMsg)).equals("") || (String.valueOf(errorMsg) == null)
|
||||||
|
|| String.valueOf(errorMsg).length() == 0) {
|
||||||
|
insertMap.put("resultCode", "SUCCESS");
|
||||||
|
} else {
|
||||||
|
insertMap.put("resultCode", "ERROR");
|
||||||
|
}
|
||||||
|
insertMap.put("resultMsg", String.valueOf(errorMsg));
|
||||||
|
insertMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||||
|
|
||||||
|
webLogHistoryService.insertLogHistory(insertMap, visitLogParamMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewController delProcedureReview END");
|
||||||
|
return HttpUtil.makeHashToJsonModelAndView(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.madeu.crm.procedureReview.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ProcedureReviewDTO {
|
||||||
|
// WebPhotoDiet uses HashMap for data transfer, creating this DTO as placeholder
|
||||||
|
// to follow project conventions.
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.madeu.crm.procedureReview.mapper;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import org.mybatis.spring.SqlSessionTemplate;
|
||||||
|
import org.mybatis.spring.support.SqlSessionDaoSupport;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.dao.DataAccessException;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public class ProcedureReviewMapper extends SqlSessionDaoSupport {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SqlSessionTemplate sqlSessionTemplate;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
void init() {
|
||||||
|
setSqlSessionTemplate(sqlSessionTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> selectTotalProcedureReviewCount(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper selectTotalProcedureReviewCount START");
|
||||||
|
String sqlId = "ProcedureReview.selectTotalProcedureReviewCount";
|
||||||
|
logger.debug("ProcedureReviewMapper selectTotalProcedureReviewCount END");
|
||||||
|
return getSqlSession().selectList(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> selectListProcedureReview(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper selectListProcedureReview START");
|
||||||
|
String sqlId = "ProcedureReview.selectListProcedureReview";
|
||||||
|
logger.debug("ProcedureReviewMapper selectListProcedureReview END");
|
||||||
|
return getSqlSession().selectList(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> selectProcedureReview(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper selectProcedureReview START");
|
||||||
|
String sqlId = "ProcedureReview.selectProcedureReview";
|
||||||
|
logger.debug("ProcedureReviewMapper selectProcedureReview END");
|
||||||
|
return getSqlSession().selectList(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int insertProcedureReview(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper insertProcedureReview START");
|
||||||
|
String sqlId = "ProcedureReview.insertProcedureReview";
|
||||||
|
logger.debug("ProcedureReviewMapper insertProcedureReview END");
|
||||||
|
return getSqlSession().insert(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int updateProcedureReview(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper updateProcedureReview START");
|
||||||
|
String sqlId = "ProcedureReview.updateProcedureReview";
|
||||||
|
logger.debug("ProcedureReviewMapper updateProcedureReview END");
|
||||||
|
return getSqlSession().update(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int deleteProcedureReview(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper deleteProcedureReview START");
|
||||||
|
String sqlId = "ProcedureReview.deleteProcedureReview";
|
||||||
|
logger.debug("ProcedureReviewMapper deleteProcedureReview END");
|
||||||
|
return getSqlSession().update(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Map<String, Object>> selectListPhotoCategory(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper selectListPhotoCategory START");
|
||||||
|
String sqlId = "ProcedureReview.selectListPhotoCategory";
|
||||||
|
logger.debug("ProcedureReviewMapper selectListPhotoCategory END");
|
||||||
|
return getSqlSession().selectList(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int insertProcedureReviewBeforeFile(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper insertProcedureReviewBeforeFile START");
|
||||||
|
String sqlId = "ProcedureReview.insertProcedureReviewBeforeFile";
|
||||||
|
logger.debug("ProcedureReviewMapper insertProcedureReviewBeforeFile END");
|
||||||
|
return getSqlSession().insert(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int insertProcedureReviewAfterFile(HashMap<String, Object> paramMap)
|
||||||
|
throws DataAccessException {
|
||||||
|
logger.debug("ProcedureReviewMapper insertProcedureReviewAfterFile START");
|
||||||
|
String sqlId = "ProcedureReview.insertProcedureReviewAfterFile";
|
||||||
|
logger.debug("ProcedureReviewMapper insertProcedureReviewAfterFile END");
|
||||||
|
return getSqlSession().insert(sqlId, paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,728 @@
|
|||||||
|
package com.madeu.crm.procedureReview.svc;
|
||||||
|
|
||||||
|
import com.madeu.constants.Constants;
|
||||||
|
import com.madeu.crm.procedureReview.mapper.ProcedureReviewMapper;
|
||||||
|
import com.madeu.dao.web.webauthmenurelation.WebAuthMenuRelationSqlMapDAO;
|
||||||
|
import com.madeu.dao.web.webmember.WebMemberSqlMapDAO;
|
||||||
|
import com.madeu.common.service.AttachFileService;
|
||||||
|
import com.madeu.util.ValidationCheckUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service("ProcedureReviewService")
|
||||||
|
public class ProcedureReviewService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ProcedureReviewMapper procedureReviewMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebMemberSqlMapDAO webMemberSqlMapDAO;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebAuthMenuRelationSqlMapDAO webAuthMenuRelationSqlMapDAO;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AttachFileService afs;
|
||||||
|
|
||||||
|
@Value("${url.cdn}")
|
||||||
|
String CDN_URL;
|
||||||
|
|
||||||
|
public HashMap<String, Object> selectProcedureReviewListIntro(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReviewListIntro START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
|
||||||
|
String menuClass = String.valueOf(paramMap.get("menuClass"));
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(menuClass)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "메뉴 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMapSize) {
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("success", "true");
|
||||||
|
|
||||||
|
map.put("selectUseYn", authMenuRelationlistMap.get(0).get("selectUseYn"));
|
||||||
|
map.put("insertUseYn", authMenuRelationlistMap.get(0).get("insertUseYn"));
|
||||||
|
map.put("updateUseYn", authMenuRelationlistMap.get(0).get("updateUseYn"));
|
||||||
|
map.put("deleteUseYn", authMenuRelationlistMap.get(0).get("deleteUseYn"));
|
||||||
|
map.put("downloadUseYn", authMenuRelationlistMap.get(0).get("downloadUseYn"));
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReviewListIntro END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, Object> selectListProcedureReview(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService selectListProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
List<Map<String, Object>> listMap = new ArrayList<Map<String, Object>>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMap.size()) {
|
||||||
|
|
||||||
|
if (("Y").equals(authMenuRelationlistMap.get(0).get("selectUseYn"))) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "조회 권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paramMap.put("categoryDivCd", "07");
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
if (null == paramMap.get("procedureReviewDir") || ("").equals(paramMap.get("procedureReviewDir"))) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
String dir = String.valueOf(paramMap.get("procedureReviewDir"));
|
||||||
|
if (("A").equals(dir)) {
|
||||||
|
paramMap.put("procedureReviewDir", "DESC");
|
||||||
|
} else if (("B").equals(dir)) {
|
||||||
|
paramMap.put("procedureReviewDir", "ASC");
|
||||||
|
} else {
|
||||||
|
paramMap.put("procedureReviewDir", "DESC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paramMap.put("useYn", "Y");
|
||||||
|
|
||||||
|
List<Map<String, Object>> totalCountListMap = procedureReviewMapper
|
||||||
|
.selectTotalProcedureReviewCount(paramMap);
|
||||||
|
int totalCount = Integer.parseInt(String.valueOf(totalCountListMap.get(0).get("totalCount")));
|
||||||
|
|
||||||
|
if (0 < totalCount) {
|
||||||
|
listMap = procedureReviewMapper.selectListProcedureReview(paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("success", "true");
|
||||||
|
map.put("totalCount", totalCount);
|
||||||
|
map.put("rows", listMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService selectListProcedureReview END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, Object> selectProcedureReview(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
String muProcedureReviewId = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(muProcedureReviewId)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "시술후기 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMap.size()) {
|
||||||
|
|
||||||
|
if (("Y").equals(authMenuRelationlistMap.get(0).get("selectUseYn"))) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "조회 권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> listMap = procedureReviewMapper.selectProcedureReview(paramMap);
|
||||||
|
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("success", "true");
|
||||||
|
map.put("rows", listMap);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReview END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public HashMap<String, Object> insertProcedureReviewFile(
|
||||||
|
HashMap<String, Object> paramMap, MultipartFile file) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService insertProcedureReviewFile START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
String tId = String.valueOf(System.currentTimeMillis());
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
Calendar c1 = Calendar.getInstance();
|
||||||
|
String tDate = sdf.format(c1.getTime());
|
||||||
|
|
||||||
|
if (null == file || file.isEmpty()) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "이미지 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMapSize) {
|
||||||
|
|
||||||
|
if (("Y").equals(authMenuRelationlistMap.get(0).get("insertUseYn"))) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "등록 권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
if (null != file && !file.isEmpty()) {
|
||||||
|
Map<String, Object> fileMap = afs.saveAttachFile("img", file);
|
||||||
|
String filePath = String.valueOf(fileMap.get("filePath"));
|
||||||
|
String fileName = String.valueOf(fileMap.get("attachfileNm"));
|
||||||
|
String chgFileName = String.valueOf(fileMap.get("orgAttachfileNm"));
|
||||||
|
|
||||||
|
String formattedCdnUrl = CDN_URL.endsWith("/") ? CDN_URL : CDN_URL + "/";
|
||||||
|
paramMap.put("filePath", formattedCdnUrl + filePath);
|
||||||
|
paramMap.put("fileName", fileName);
|
||||||
|
paramMap.put("originalFileName", chgFileName);
|
||||||
|
paramMap.put("attachfileId", fileMap.get("attachfileId"));
|
||||||
|
}
|
||||||
|
paramMap.put("tDate", tDate);
|
||||||
|
paramMap.put("tId", tId);
|
||||||
|
|
||||||
|
map.put("rows", paramMap);
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("msgDesc", "등록되었습니다.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService insertProcedureReviewFile END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, Object> selectListCategory(HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
paramMap.put("categoryDivCd", "07");
|
||||||
|
List<Map<String, Object>> listMap = procedureReviewMapper.selectListPhotoCategory(paramMap);
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("rows", listMap);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, Object> selectProcedureReviewInsertIntro(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReviewInsertIntro START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
|
||||||
|
String menuClass = String.valueOf(paramMap.get("menuClass"));
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(menuClass)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "메뉴 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMapSize) {
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("success", "true");
|
||||||
|
|
||||||
|
map.put("selectUseYn", authMenuRelationlistMap.get(0).get("selectUseYn"));
|
||||||
|
map.put("insertUseYn", authMenuRelationlistMap.get(0).get("insertUseYn"));
|
||||||
|
map.put("updateUseYn", authMenuRelationlistMap.get(0).get("updateUseYn"));
|
||||||
|
map.put("deleteUseYn", authMenuRelationlistMap.get(0).get("deleteUseYn"));
|
||||||
|
map.put("downloadUseYn", authMenuRelationlistMap.get(0).get("downloadUseYn"));
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paramMap.put("categoryDivCd", "07");
|
||||||
|
List<Map<String, Object>> listMap = procedureReviewMapper.selectListPhotoCategory(paramMap);
|
||||||
|
map.put("categorylist", listMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReviewInsertIntro END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public HashMap<String, Object> insertProcedureReview(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService insertProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
String tId = String.valueOf(System.currentTimeMillis());
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
Calendar c1 = Calendar.getInstance();
|
||||||
|
String tDate = sdf.format(c1.getTime());
|
||||||
|
|
||||||
|
String title = String.valueOf(paramMap.get("title"));
|
||||||
|
String content = String.valueOf(paramMap.get("content"));
|
||||||
|
String isEncoded = String.valueOf(paramMap.get("isEncoded"));
|
||||||
|
if ("Y".equals(isEncoded) && content != null) {
|
||||||
|
try {
|
||||||
|
byte[] decodedBytes = java.util.Base64.getDecoder().decode(content);
|
||||||
|
content = java.net.URLDecoder.decode(new String(decodedBytes, "UTF-8"), "UTF-8");
|
||||||
|
paramMap.put("content", content);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(title)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "제목 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(content)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "시술후기 상세 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMapSize) {
|
||||||
|
if (("Y").equals(authMenuRelationlistMap.get(0).get("insertUseYn"))) {
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "등록 권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
paramMap.put("tDate", tDate);
|
||||||
|
paramMap.put("tId", tId);
|
||||||
|
paramMap.put("muMemberId", paramMap.get("loginMemberId"));
|
||||||
|
procedureReviewMapper.insertProcedureReview(paramMap);
|
||||||
|
paramMap.put("muProcedureReviewId", paramMap.get("id"));
|
||||||
|
map.put("muProcedureReviewId", paramMap.get("muProcedureReviewId"));
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("msgDesc", "등록되었습니다.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService insertProcedureReview END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, Object> selectProcedureReviewUpdateIntro(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReviewUpdateIntro START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
|
||||||
|
String menuClass = String.valueOf(paramMap.get("menuClass"));
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(menuClass)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "메뉴 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMapSize) {
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("success", "true");
|
||||||
|
|
||||||
|
map.put("selectUseYn", authMenuRelationlistMap.get(0).get("selectUseYn"));
|
||||||
|
map.put("insertUseYn", authMenuRelationlistMap.get(0).get("insertUseYn"));
|
||||||
|
map.put("updateUseYn", authMenuRelationlistMap.get(0).get("updateUseYn"));
|
||||||
|
map.put("deleteUseYn", authMenuRelationlistMap.get(0).get("deleteUseYn"));
|
||||||
|
map.put("downloadUseYn", authMenuRelationlistMap.get(0).get("downloadUseYn"));
|
||||||
|
} else {
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paramMap.put("categoryDivCd", "07");
|
||||||
|
List<Map<String, Object>> listMap = procedureReviewMapper.selectListPhotoCategory(paramMap);
|
||||||
|
map.put("categorylist", listMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService selectProcedureReviewUpdateIntro END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(rollbackFor = { Exception.class }, propagation = Propagation.REQUIRES_NEW)
|
||||||
|
public HashMap<String, Object> updateProcedureReview(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService updateProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
|
||||||
|
String muProcedureReviewId = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||||
|
String title = String.valueOf(paramMap.get("title"));
|
||||||
|
String content = String.valueOf(paramMap.get("content"));
|
||||||
|
String isEncoded = String.valueOf(paramMap.get("isEncoded"));
|
||||||
|
if ("Y".equals(isEncoded) && content != null) {
|
||||||
|
try {
|
||||||
|
byte[] decodedBytes = java.util.Base64.getDecoder().decode(content);
|
||||||
|
content = java.net.URLDecoder.decode(new String(decodedBytes, "UTF-8"), "UTF-8");
|
||||||
|
paramMap.put("content", content);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(muProcedureReviewId)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "시술후기 식별자 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(title)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "제목 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(content)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "시술후기 상세 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMapSize) {
|
||||||
|
|
||||||
|
if (("Y").equals(authMenuRelationlistMap.get(0).get("updateUseYn"))) {
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "수정 권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
procedureReviewMapper.updateProcedureReview(paramMap);
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("msgDesc", "수정되었습니다.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService updateProcedureReview END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, Object> deleteProcedureReview(
|
||||||
|
HashMap<String, Object> paramMap) throws Exception {
|
||||||
|
log.debug("ProcedureReviewService deleteProcedureReview START");
|
||||||
|
|
||||||
|
HashMap<String, Object> map = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean check = true;
|
||||||
|
|
||||||
|
String muProcedureReviewId = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||||
|
|
||||||
|
if (true != ValidationCheckUtil.emptyCheck(muProcedureReviewId)) {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "시술후기 식별자 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
List<Map<String, Object>> userListMap = webMemberSqlMapDAO.checkMember(paramMap);
|
||||||
|
int userListMapSize = userListMap.size();
|
||||||
|
|
||||||
|
if (1 == userListMapSize) {
|
||||||
|
paramMap.put("menuClassAuthId", userListMap.get(0).get("muAuthId"));
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "사용자 정보가 올바르지 않습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true == check) {
|
||||||
|
HashMap<String, Object> authCheckParamMap = new HashMap<String, Object>();
|
||||||
|
authCheckParamMap.put("menuClass", paramMap.get("menuClass"));
|
||||||
|
authCheckParamMap.put("muAuthId", paramMap.get("menuClassAuthId"));
|
||||||
|
List<Map<String, Object>> authMenuRelationlistMap = webAuthMenuRelationSqlMapDAO
|
||||||
|
.selectAuthMenuRelation(authCheckParamMap);
|
||||||
|
int authMenuRelationlistMapSize = authMenuRelationlistMap.size();
|
||||||
|
|
||||||
|
if (1 == authMenuRelationlistMapSize) {
|
||||||
|
|
||||||
|
if (("Y").equals(authMenuRelationlistMap.get(0).get("deleteUseYn"))) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "삭제 권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
check = false;
|
||||||
|
map.put("msgCode", Constants.FAIL);
|
||||||
|
map.put("msgDesc", "권한 정보가 없습니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String muProcedureReviewIdStr = String.valueOf(paramMap.get("muProcedureReviewId"));
|
||||||
|
if (true == check) {
|
||||||
|
String[] idArray = muProcedureReviewIdStr.split(",");
|
||||||
|
for (String id : idArray) {
|
||||||
|
paramMap.put("muProcedureReviewId", id.trim());
|
||||||
|
procedureReviewMapper.deleteProcedureReview(paramMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
map.put("msgCode", Constants.OK);
|
||||||
|
map.put("msgDesc", "삭제되었습니다.");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
log.debug("ProcedureReviewService deleteProcedureReview END");
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
server:
|
server:
|
||||||
|
tomcat:
|
||||||
|
max-swallow-size: -1
|
||||||
|
max-http-form-post-size: -1
|
||||||
port: 8080
|
port: 8080
|
||||||
servlet:
|
servlet:
|
||||||
session:
|
session:
|
||||||
timeout: -1
|
timeout: -1
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
|
servlet:
|
||||||
|
multipart:
|
||||||
|
max-file-size: 500MB
|
||||||
|
max-request-size: 500MB
|
||||||
datasource:
|
datasource:
|
||||||
hikari:
|
hikari:
|
||||||
driver-class-name: org.mariadb.jdbc.Driver
|
driver-class-name: org.mariadb.jdbc.Driver
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ spring:
|
|||||||
|
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
maxFileSize: 500MB
|
max-file-size: 500MB
|
||||||
maxRequestSize: 500MB
|
max-request-size: 500MB
|
||||||
|
|
||||||
aop:
|
aop:
|
||||||
proxy-target-class: false
|
proxy-target-class: false
|
||||||
@@ -30,6 +30,8 @@ spring:
|
|||||||
|
|
||||||
|
|
||||||
server:
|
server:
|
||||||
|
tomcat:
|
||||||
|
max-swallow-size: -1
|
||||||
encoding:
|
encoding:
|
||||||
charset: UTF-8
|
charset: UTF-8
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -56,6 +58,7 @@ logging:
|
|||||||
org.hibernate.SQL: DEBUG
|
org.hibernate.SQL: DEBUG
|
||||||
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
|
||||||
p6spy: debug
|
p6spy: debug
|
||||||
|
org.springframework.aop.framework.CglibAopProxy: ERROR
|
||||||
|
|
||||||
decorator:
|
decorator:
|
||||||
datasource:
|
datasource:
|
||||||
|
|||||||
209
src/main/resources/mappers/ProcedureReviewSqlMap.xml
Normal file
209
src/main/resources/mappers/ProcedureReviewSqlMap.xml
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<?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="ProcedureReview">
|
||||||
|
|
||||||
|
<select id="selectTotalProcedureReviewCount" parameterType="hashmap" resultType="hashmap">
|
||||||
|
SELECT COUNT(*) AS "totalCount"
|
||||||
|
FROM MU_PROCEDURE_REVIEW MPR
|
||||||
|
WHERE MPR.USE_YN = 'Y'
|
||||||
|
<if test="procedureReviewSearchKeywordParam1 != null and procedureReviewSearchKeywordParam1 != ''">
|
||||||
|
AND MPR.TITLE LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam1}), '%')
|
||||||
|
</if>
|
||||||
|
<if test="procedureReviewSearchKeywordParam2 != null and procedureReviewSearchKeywordParam2 != ''">
|
||||||
|
AND IFNULL((SELECT MM.NAME FROM MU_MEMBER AS MM
|
||||||
|
WHERE MM.USE_YN = 'Y'
|
||||||
|
AND MM.MU_MEMBER_ID = MPR.REG_ID
|
||||||
|
LIMIT 0, 1
|
||||||
|
),'') LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam2}), '%')
|
||||||
|
</if>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectListProcedureReview" parameterType="hashmap" resultType="hashmap">
|
||||||
|
SELECT MPR.*
|
||||||
|
FROM (
|
||||||
|
SELECT MPR.*
|
||||||
|
,CAST(@RNUM:=@RNUM + 1 AS CHAR) AS "rowNum"
|
||||||
|
FROM (
|
||||||
|
SELECT MPR.MU_PROCEDURE_REVIEW_ID AS "muProcedureReviewId"
|
||||||
|
,MPR.TITLE AS "title"
|
||||||
|
,MPR.CONTENT AS "content"
|
||||||
|
,MPR.HASHTAG AS "hashtag"
|
||||||
|
,MPR.VIEW_COUNT AS "viewCount"
|
||||||
|
,DATE_FORMAT(MPR.REG_DATE, '%Y-%m-%d') AS "writeDate"
|
||||||
|
,IFNULL((SELECT MM.NAME
|
||||||
|
FROM MU_MEMBER AS MM
|
||||||
|
WHERE MM.USE_YN = 'Y'
|
||||||
|
AND MM.MU_MEMBER_ID = MPR.REG_ID
|
||||||
|
LIMIT 0, 1
|
||||||
|
),'') AS "writeName"
|
||||||
|
FROM MU_PROCEDURE_REVIEW MPR
|
||||||
|
WHERE MPR.USE_YN = 'Y'
|
||||||
|
<if test="procedureReviewSearchKeywordParam1 != null and procedureReviewSearchKeywordParam1 != ''">
|
||||||
|
AND MPR.TITLE LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam1}), '%')
|
||||||
|
</if>
|
||||||
|
<if test="procedureReviewSearchKeywordParam2 != null and procedureReviewSearchKeywordParam2 != ''">
|
||||||
|
AND IFNULL((SELECT MM.NAME FROM MU_MEMBER AS MM
|
||||||
|
WHERE MM.USE_YN = 'Y'
|
||||||
|
AND MM.MU_MEMBER_ID = MPR.REG_ID
|
||||||
|
LIMIT 0, 1
|
||||||
|
),'') LIKE CONCAT('%', TRIM(#{procedureReviewSearchKeywordParam2}), '%')
|
||||||
|
</if>
|
||||||
|
<choose>
|
||||||
|
<when test="procedureReviewSort != null and procedureReviewSort != ''">
|
||||||
|
ORDER BY ${procedureReviewSort}
|
||||||
|
</when>
|
||||||
|
<otherwise>
|
||||||
|
ORDER BY MPR.REG_DATE DESC
|
||||||
|
</otherwise>
|
||||||
|
</choose>
|
||||||
|
LIMIT 18446744073709551615
|
||||||
|
) MPR, (SELECT @RNUM:=0) R
|
||||||
|
WHERE 1 = 1
|
||||||
|
) MPR
|
||||||
|
WHERE 1 = 1
|
||||||
|
LIMIT ${procedureReviewStart}, ${procedureReviewLimit}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="selectProcedureReview" parameterType="hashmap" resultType="hashmap">
|
||||||
|
SELECT MPR.MU_PROCEDURE_REVIEW_ID AS "muProcedureReviewId"
|
||||||
|
,MPR.CATEGORY_NO AS "categoryno"
|
||||||
|
,MPR.TITLE AS "title"
|
||||||
|
,MPR.CONTENT AS "content"
|
||||||
|
,MPR.HASHTAG AS "hashtag"
|
||||||
|
,HAF.FILE_PATH AS "beforefile"
|
||||||
|
,HAF2.FILE_PATH AS "afterfile"
|
||||||
|
FROM MU_PROCEDURE_REVIEW MPR
|
||||||
|
LEFT OUTER JOIN HP_ATTACH_FILE HAF ON HAF.ATTACHFILE_ID = MPR.BEFORE_PHOTO_ATTACHFILE_ID
|
||||||
|
LEFT OUTER JOIN HP_ATTACH_FILE HAF2 ON HAF2.ATTACHFILE_ID = MPR.AFTER_PHOTO_ATTACHFILE_ID
|
||||||
|
WHERE MPR.USE_YN = 'Y'
|
||||||
|
AND MPR.MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
|
||||||
|
LIMIT 0, 1
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<insert id="insertProcedureReview" parameterType="hashmap">
|
||||||
|
<selectKey resultType="string" keyProperty="id" order="BEFORE">
|
||||||
|
SELECT CONCAT('PR-', LPAD(NEXTVAL(MU_PROCEDURE_REVIEW_SEQ), 21, '0'))
|
||||||
|
</selectKey>
|
||||||
|
INSERT INTO MU_PROCEDURE_REVIEW (
|
||||||
|
MU_PROCEDURE_REVIEW_ID
|
||||||
|
,MU_MEMBER_ID
|
||||||
|
,CATEGORY_NO
|
||||||
|
,TITLE
|
||||||
|
,CONTENT
|
||||||
|
,HASHTAG
|
||||||
|
,BEFORE_PHOTO_ATTACHFILE_ID
|
||||||
|
,AFTER_PHOTO_ATTACHFILE_ID
|
||||||
|
,VIEW_COUNT
|
||||||
|
,USE_YN
|
||||||
|
,REG_ID
|
||||||
|
,REG_DATE
|
||||||
|
,MOD_ID
|
||||||
|
,MOD_DATE
|
||||||
|
) VALUES (
|
||||||
|
#{id}
|
||||||
|
,#{muMemberId}
|
||||||
|
,#{categoryno, jdbcType=VARCHAR}
|
||||||
|
,#{title}
|
||||||
|
,#{content}
|
||||||
|
,#{hashtag}
|
||||||
|
,#{beforeId, jdbcType=VARCHAR}
|
||||||
|
,#{afterId, jdbcType=VARCHAR}
|
||||||
|
,0
|
||||||
|
,'Y'
|
||||||
|
,#{regId}
|
||||||
|
,NOW()
|
||||||
|
,#{modId}
|
||||||
|
,NOW()
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<insert id="insertProcedureReviewBeforeFile" parameterType="hashmap">
|
||||||
|
<selectKey resultType="string" keyProperty="beforeId" order="BEFORE">
|
||||||
|
SELECT CONCAT('ATF-', LPAD(NEXTVAL(seq_attachfile_id), 21, '0'))
|
||||||
|
</selectKey>
|
||||||
|
INSERT INTO HP_ATTACH_FILE (
|
||||||
|
ATTACHFILE_ID
|
||||||
|
,ATTACHFILE_NM
|
||||||
|
,FILE_PATH
|
||||||
|
,FILE_SIZE
|
||||||
|
,FILE_TYPE
|
||||||
|
,REG_ID
|
||||||
|
,REG_DATE
|
||||||
|
,MOD_ID
|
||||||
|
,MOD_DATE
|
||||||
|
) VALUES (
|
||||||
|
#{beforeId}
|
||||||
|
,#{beforeFileName}
|
||||||
|
,#{beforeFilePath}
|
||||||
|
,'0'
|
||||||
|
,'img'
|
||||||
|
,#{regId}
|
||||||
|
,NOW()
|
||||||
|
,#{modId}
|
||||||
|
,NOW()
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<insert id="insertProcedureReviewAfterFile" parameterType="hashmap">
|
||||||
|
<selectKey resultType="string" keyProperty="afterId" order="BEFORE">
|
||||||
|
SELECT CONCAT('ATF-', LPAD(NEXTVAL(seq_attachfile_id), 21, '0'))
|
||||||
|
</selectKey>
|
||||||
|
INSERT INTO HP_ATTACH_FILE (
|
||||||
|
ATTACHFILE_ID
|
||||||
|
,ATTACHFILE_NM
|
||||||
|
,FILE_PATH
|
||||||
|
,FILE_SIZE
|
||||||
|
,FILE_TYPE
|
||||||
|
,REG_ID
|
||||||
|
,REG_DATE
|
||||||
|
,MOD_ID
|
||||||
|
,MOD_DATE
|
||||||
|
) VALUES (
|
||||||
|
#{afterId}
|
||||||
|
,#{afterFileName}
|
||||||
|
,#{afterFilePath}
|
||||||
|
,'0'
|
||||||
|
,'img'
|
||||||
|
,#{regId}
|
||||||
|
,NOW()
|
||||||
|
,#{modId}
|
||||||
|
,NOW()
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateProcedureReview" parameterType="hashmap">
|
||||||
|
UPDATE MU_PROCEDURE_REVIEW
|
||||||
|
SET TITLE = #{title}
|
||||||
|
,CONTENT = #{content}
|
||||||
|
,HASHTAG = #{hashtag}
|
||||||
|
<if test="beforeId != null and beforeId != ''">
|
||||||
|
,BEFORE_PHOTO_ATTACHFILE_ID = #{beforeId}
|
||||||
|
</if>
|
||||||
|
<if test="afterId != null and afterId != ''">
|
||||||
|
,AFTER_PHOTO_ATTACHFILE_ID = #{afterId}
|
||||||
|
</if>
|
||||||
|
,MOD_ID = #{modId}
|
||||||
|
,MOD_DATE = NOW()
|
||||||
|
WHERE USE_YN = 'Y'
|
||||||
|
AND MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="deleteProcedureReview" parameterType="hashmap">
|
||||||
|
UPDATE MU_PROCEDURE_REVIEW
|
||||||
|
SET MOD_ID = #{modId}
|
||||||
|
,MOD_DATE = NOW()
|
||||||
|
,USE_YN = 'N'
|
||||||
|
WHERE USE_YN = 'Y'
|
||||||
|
AND MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<select id="selectListPhotoCategory" parameterType="hashmap" resultType="hashmap">
|
||||||
|
SELECT ROW_NUMBER() OVER (ORDER BY HC.REG_DATE DESC) AS "rowNum"
|
||||||
|
,HC.CATEGORY_NO AS "categoryNo"
|
||||||
|
,HC.CATEGORY_NM AS "categoryNm"
|
||||||
|
FROM HP_CATEGORY AS HC
|
||||||
|
WHERE HC.USE_YN = 'Y'
|
||||||
|
AND HC.CATEGORY_DIV_CD = #{categoryDivCd}
|
||||||
|
ORDER BY HC.CATEGORY_NO ASC
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
321
src/main/resources/static/css/web/procedureReview.css
Normal file
321
src/main/resources/static/css/web/procedureReview.css
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
/* ================================================
|
||||||
|
시술후기 등록/수정 전용 스타일
|
||||||
|
================================================ */
|
||||||
|
|
||||||
|
/* ── 전체 컨테이너 ── */
|
||||||
|
.procedure-review-form {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 좌측 패널 (폼 영역) ── */
|
||||||
|
.procedure-review-form .form-panel {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 우측 패널 (이미지 영역) ── */
|
||||||
|
.procedure-review-form .image-panel {
|
||||||
|
flex: 0 0 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 폼 그룹 ── */
|
||||||
|
.procedure-review-form .pr-form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-form-row .pr-form-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-form-row .pr-form-group.pr-category {
|
||||||
|
flex: 0 0 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 라벨 ── */
|
||||||
|
.procedure-review-form .pr-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-label .required {
|
||||||
|
color: #EF4444;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Input / Select 공통 ── */
|
||||||
|
.procedure-review-form input[type="text"],
|
||||||
|
.procedure-review-form select {
|
||||||
|
width: 100%;
|
||||||
|
height: 38px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid #D1D5DB;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #1F2937;
|
||||||
|
background: #fff;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form input[type="text"]:focus,
|
||||||
|
.procedure-review-form select:focus {
|
||||||
|
border-color: #3985EA;
|
||||||
|
box-shadow: 0 0 0 3px rgba(57, 133, 234, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form input[type="text"]::placeholder {
|
||||||
|
color: #9CA3AF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Quill 에디터 ── */
|
||||||
|
.procedure-review-form .editor-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .editor-container .ql-toolbar.ql-snow {
|
||||||
|
border: 1px solid #D1D5DB;
|
||||||
|
border-radius: 6px 6px 0 0;
|
||||||
|
background: #F9FAFB;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .editor-container .ql-container.ql-snow {
|
||||||
|
border: 1px solid #D1D5DB;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 6px 6px;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: 'Pretendard', 'Noto Sans KR', sans-serif;
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .editor-container .ql-editor {
|
||||||
|
min-height: 240px;
|
||||||
|
line-height: 1.7;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .editor-container .ql-editor.ql-blank::before {
|
||||||
|
color: #9CA3AF;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 이미지 카드 ── */
|
||||||
|
.procedure-review-form .image-card {
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 14px;
|
||||||
|
background: #F3F4F6;
|
||||||
|
border-bottom: 1px solid #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header span {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header .image-card-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header .image-card-actions label {
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header .image-card-actions label:hover {
|
||||||
|
background: #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header .image-card-actions label img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header .image-card-actions .img-delete-btn {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9CA3AF;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-header .image-card-actions .img-delete-btn:hover {
|
||||||
|
background: #FEE2E2;
|
||||||
|
color: #EF4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-body {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #FAFAFA;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-body .placeholder-text {
|
||||||
|
color: #D1D5DB;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .image-card .image-card-body img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 가이드 박스 ── */
|
||||||
|
.procedure-review-form .photo-guide {
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: #F0F5FF;
|
||||||
|
border: 1px solid #DBEAFE;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4B5563;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .photo-guide p {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #3985EA;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .photo-guide ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .photo-guide ul li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 14px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .photo-guide ul li::before {
|
||||||
|
content: '•';
|
||||||
|
position: absolute;
|
||||||
|
left: 2px;
|
||||||
|
color: #3985EA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 액션 버튼 영역 ── */
|
||||||
|
.procedure-review-form .pr-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-actions .pr-btn {
|
||||||
|
width: 90px;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-actions .pr-btn-primary {
|
||||||
|
background: #3985EA;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-actions .pr-btn-primary:hover {
|
||||||
|
background: #2563EB;
|
||||||
|
box-shadow: 0 2px 8px rgba(37, 99, 235, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-actions .pr-btn-cancel {
|
||||||
|
background: #F3F4F6;
|
||||||
|
color: #6B7280;
|
||||||
|
border: 1px solid #D1D5DB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.procedure-review-form .pr-actions .pr-btn-cancel:hover {
|
||||||
|
background: #E5E7EB;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── 반응형 ── */
|
||||||
|
@media only screen and (max-width: 1400px) {
|
||||||
|
.procedure-review-form .image-panel {
|
||||||
|
flex: 0 0 340px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 1200px) {
|
||||||
|
.procedure-review-form {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.procedure-review-form .image-panel {
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
.procedure-review-form .image-panel .images-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.procedure-review-form .image-panel .images-row .image-card {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,380 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* Quill 에디터 인스턴스
|
||||||
|
****************************************************************************/
|
||||||
|
let quill;
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Quill Image blot - data: URL (base64) 허용
|
||||||
|
****************************************************************************/
|
||||||
|
const QuillImage = Quill.import('formats/image');
|
||||||
|
const originalSanitize = QuillImage.sanitize;
|
||||||
|
QuillImage.sanitize = function (url) {
|
||||||
|
if (url && url.startsWith('data:')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return originalSanitize.call(this, url);
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Quill 에디터 초기화
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_initQuillEditor() {
|
||||||
|
quill = new Quill('#quillEditor', {
|
||||||
|
theme: 'snow',
|
||||||
|
placeholder: '내용을 입력해주세요.',
|
||||||
|
modules: {
|
||||||
|
toolbar: {
|
||||||
|
container: [
|
||||||
|
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
||||||
|
[{ 'font': [] }],
|
||||||
|
[{ 'size': ['small', false, 'large', 'huge'] }],
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
[{ 'color': [] }, { 'background': [] }],
|
||||||
|
[{ 'script': 'sub' }, { 'script': 'super' }],
|
||||||
|
[{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'list': 'check' }],
|
||||||
|
[{ 'indent': '-1' }, { 'indent': '+1' }],
|
||||||
|
[{ 'direction': 'rtl' }],
|
||||||
|
[{ 'align': [] }],
|
||||||
|
['blockquote', 'code-block'],
|
||||||
|
['link', 'image', 'video', 'formula'],
|
||||||
|
['clean']
|
||||||
|
],
|
||||||
|
handlers: {
|
||||||
|
image: imageHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quill.root.addEventListener('paste', function (e) {
|
||||||
|
try {
|
||||||
|
console.log("[Paste Event] Triggered");
|
||||||
|
let clipboardData = e.clipboardData || window.clipboardData || (e.originalEvent && e.originalEvent.clipboardData);
|
||||||
|
console.log("[Paste Event] clipboardData:", clipboardData);
|
||||||
|
if (!clipboardData || !clipboardData.items) {
|
||||||
|
console.warn("[Paste Event] No clipboardData or items found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = clipboardData.items;
|
||||||
|
console.log("[Paste Event] items length:", items.length);
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let item = items[i];
|
||||||
|
console.log(`[Paste Event] item[${i}] kind: ${item.kind}, type: ${item.type}`);
|
||||||
|
if (item.kind === 'file' && item.type.match('^image/')) {
|
||||||
|
console.log(`[Paste Event] Image file detected. Prevent default.`);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
let file = item.getAsFile();
|
||||||
|
console.log(`[Paste Event] getAsFile() result:`, file);
|
||||||
|
if (file) {
|
||||||
|
if (!file.name || !file.name.includes('.')) {
|
||||||
|
let ext = 'png';
|
||||||
|
if (file.type === 'image/jpeg') ext = 'jpg';
|
||||||
|
else if (file.type === 'image/gif') ext = 'gif';
|
||||||
|
file = new File([file], "clipboard_image." + ext, { type: file.type });
|
||||||
|
console.log(`[Paste Event] Renamed missing file info:`, file.name);
|
||||||
|
}
|
||||||
|
console.log(`[Paste Event] Call fn_uploadImageAndInsertToQuill() for:`, file.name);
|
||||||
|
fn_uploadImageAndInsertToQuill(file);
|
||||||
|
} else {
|
||||||
|
console.error("[Paste Event] getAsFile() returned null.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Paste Event] Error parsing paste logic:", err);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
quill.root.addEventListener('drop', function (e) {
|
||||||
|
try {
|
||||||
|
console.log("[Drop Event] Triggered");
|
||||||
|
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||||
|
let hasImage = false;
|
||||||
|
console.log("[Drop Event] files length:", e.dataTransfer.files.length);
|
||||||
|
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||||
|
let file = e.dataTransfer.files[i];
|
||||||
|
console.log(`[Drop Event] file[${i}] type: ${file.type}`);
|
||||||
|
if (file.type.match('^image/')) {
|
||||||
|
console.log(`[Drop Event] Image file detected. Prevent default.`);
|
||||||
|
hasImage = true;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
fn_uploadImageAndInsertToQuill(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[Drop Event] No files found in drop event");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Drop Event] Error parsing drop logic:", err);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
function imageHandler() {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.setAttribute('type', 'file');
|
||||||
|
input.setAttribute('accept', 'image/*');
|
||||||
|
input.click();
|
||||||
|
|
||||||
|
input.onchange = async () => {
|
||||||
|
const file = input.files[0];
|
||||||
|
fn_uploadImageAndInsertToQuill(file);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fn_uploadImageAndInsertToQuill(file) {
|
||||||
|
if (!file) {
|
||||||
|
console.error("[Upload] file is undefined or null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("[Upload] fn_uploadImageAndInsertToQuill Start. File name:", file.name, "size:", file.size);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append("menuClass", menuClass);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/procedureReview/putProcedureReviewFile.do',
|
||||||
|
type: 'POST',
|
||||||
|
data: formData,
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (res) {
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
try { res = JSON.parse(res); } catch (e) { }
|
||||||
|
}
|
||||||
|
console.log("[Upload] API Success response:", res);
|
||||||
|
if (res.msgCode == 0 || res.msgCode === '0') {
|
||||||
|
let range = quill.getSelection(true);
|
||||||
|
let index = range ? range.index : quill.getLength();
|
||||||
|
console.log(`[Upload] Inserting image at index = ${index}, path = ${res.rows.filePath}`);
|
||||||
|
quill.insertEmbed(index, 'image', res.rows.filePath);
|
||||||
|
} else {
|
||||||
|
console.error("[Upload] API logic error msgDesc:", res.msgDesc);
|
||||||
|
modalEvent.danger("업로드 오류", res.msgDesc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error("[Upload] AJAX HTTP error. status:", status, "error:", error, "responseText:", xhr.responseText);
|
||||||
|
modalEvent.danger("업로드 오류", "이미지 업로드 중 오류가 발생했습니다.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Quill 콘텐츠를 Delta JSON 문자열로 반환 (저장용)
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_getQuillContentForSave() {
|
||||||
|
return JSON.stringify(quill.getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 저장된 콘텐츠를 Quill에 로드 (Delta JSON / 레거시 HTML 호환)
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_loadQuillContent(content) {
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
if (typeof content === 'object') {
|
||||||
|
if (Array.isArray(content.ops)) quill.setContents(content.ops);
|
||||||
|
else if (Array.isArray(content)) quill.setContents(content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
// base64인지 판별해 디코딩 시도 (기존 데이터와 새 데이터 호환을 위해)
|
||||||
|
const isBase64 = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(content);
|
||||||
|
if (isBase64 && content.length > 0 && !content.trim().startsWith('{') && !content.trim().startsWith('[')) {
|
||||||
|
try {
|
||||||
|
content = decodeURIComponent(escape(atob(content)));
|
||||||
|
} catch (e) {
|
||||||
|
// base64 디코딩 실패면 그냥 넘어감
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
|
||||||
|
let parsed = JSON.parse(content);
|
||||||
|
if (typeof parsed === 'string') {
|
||||||
|
parsed = JSON.parse(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof parsed === 'object' && parsed !== null && Array.isArray(parsed.ops)) {
|
||||||
|
quill.setContents(parsed.ops);
|
||||||
|
return;
|
||||||
|
} else if (Array.isArray(parsed)) {
|
||||||
|
quill.setContents(parsed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Quill Delta Parsing Failed - Fallback to HTML : ", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
quill.clipboard.dangerouslyPasteHTML(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 시술후기 등록
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_insertProcedureReview() {
|
||||||
|
if ("Y" != insertUseYn) {
|
||||||
|
modalEvent.warning("", "등록 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = $("#title").val();
|
||||||
|
let content = fn_getQuillContentForSave();
|
||||||
|
let hashtag = $("#hashtag").val();
|
||||||
|
|
||||||
|
// Quill 에디터 빈 값 체크
|
||||||
|
let quillText = quill.getText().trim();
|
||||||
|
|
||||||
|
if (true != fn_emptyCheck(title)) {
|
||||||
|
modalEvent.warning("등록", "제목을 입력하세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!quillText || quillText.length === 0) {
|
||||||
|
modalEvent.warning("등록", "내용을 입력하세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (true != fn_emptyCheck(hashtag)) {
|
||||||
|
modalEvent.warning("등록", "해시태그를 입력하세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalEvent.info("등록", "시술후기 정보를 등록하시겠습니까?", function () {
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("menuClass", menuClass);
|
||||||
|
formData.append("title", title);
|
||||||
|
|
||||||
|
let encodedContent = btoa(unescape(encodeURIComponent(content)));
|
||||||
|
formData.append("content", encodedContent);
|
||||||
|
formData.append("isEncoded", "Y"); // base64 인코딩 플래그 추가
|
||||||
|
formData.append("hashtag", hashtag);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: encodeURI('/procedureReview/putProcedureReview.do'),
|
||||||
|
data: formData,
|
||||||
|
dataType: "json",
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
type: 'POST',
|
||||||
|
async: true,
|
||||||
|
success: function (data) {
|
||||||
|
if ('0' == data.msgCode) {
|
||||||
|
modalEvent.success("등록 성공", data.msgDesc, function () {
|
||||||
|
fn_selectListProcedureReviewIntro();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modalEvent.danger("등록 오류", data.msgDesc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
modalEvent.danger("등록 오류", "등록 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 리스트 화면으로 이동.
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_selectListProcedureReviewIntro() {
|
||||||
|
if ("Y" == selectUseYn) {
|
||||||
|
let pagingParam = "?menuClass=" + menuClass;
|
||||||
|
fn_leftFormAction("/procedureReview/moveProcedureReviewList.do" + pagingParam);
|
||||||
|
} else {
|
||||||
|
modalEvent.warning("", "조회 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 도움말 모달
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_initHelpModal() {
|
||||||
|
$('#btnHelp').on('click', function () {
|
||||||
|
$('#helpModal').addClass('active');
|
||||||
|
});
|
||||||
|
$('#helpModalClose').on('click', function () {
|
||||||
|
$('#helpModal').removeClass('active');
|
||||||
|
});
|
||||||
|
$('#helpModal').on('click', function (e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
$(this).removeClass('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 미리보기 모달
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_initPreviewModal() {
|
||||||
|
$('#previewModalClose').on('click', function () {
|
||||||
|
$('#previewModal').removeClass('active');
|
||||||
|
});
|
||||||
|
$('#previewModal').on('click', function (e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
$(this).removeClass('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fn_showPreview() {
|
||||||
|
let title = $("#title").val();
|
||||||
|
let content = quill.root.innerHTML; // 미리보기는 렌더링된 HTML 사용
|
||||||
|
let hashtag = $("#hashtag").val();
|
||||||
|
|
||||||
|
// 미리보기 데이터 설정
|
||||||
|
$('#previewTitle').text(title || '(제목 없음)');
|
||||||
|
$('#previewContent').html(content);
|
||||||
|
|
||||||
|
if (hashtag) {
|
||||||
|
let tags = hashtag.split(/\s+/).map(function (tag) {
|
||||||
|
tag = tag.trim();
|
||||||
|
if (tag && !tag.startsWith('#')) tag = '#' + tag;
|
||||||
|
return tag;
|
||||||
|
}).filter(function (tag) { return tag; });
|
||||||
|
$('#previewHashtag').html(tags.join(' '));
|
||||||
|
$('#previewHashtagArea').show();
|
||||||
|
} else {
|
||||||
|
$('#previewHashtagArea').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#previewModal').addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이지 init
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_pageInit() {
|
||||||
|
fn_initQuillEditor();
|
||||||
|
fn_initHelpModal();
|
||||||
|
fn_initPreviewModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이지 event
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_pageProcedureReview() {
|
||||||
|
$('.btnCancle').on("click", function () {
|
||||||
|
fn_selectListProcedureReviewIntro();
|
||||||
|
});
|
||||||
|
$('.btnSave').on("click", function () {
|
||||||
|
fn_insertProcedureReview();
|
||||||
|
});
|
||||||
|
$('.btnPreview').on("click", function () {
|
||||||
|
fn_showPreview();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
fn_pageInit();
|
||||||
|
fn_pageProcedureReview();
|
||||||
|
});
|
||||||
@@ -0,0 +1,413 @@
|
|||||||
|
/* 페이징 관련 변수 */
|
||||||
|
let procedureReviewTotalCount = 0;
|
||||||
|
let procedureReviewTotalPages = 0;
|
||||||
|
|
||||||
|
/*aggird*/
|
||||||
|
let procedureReviewAgGridData = [];
|
||||||
|
|
||||||
|
let procedureReviewSelectId = "";
|
||||||
|
let procedureReviewSelectCategoryNo = "";
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 시술후기 정보 리스트 조회
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_selectListProcedureReviewJson() {
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("menuClass", menuClass);
|
||||||
|
formData.append("procedureReviewSearchKeywordParam0", procedureReviewSearchKeywordParam0);
|
||||||
|
formData.append("procedureReviewSearchKeywordParam1", procedureReviewSearchKeywordParam1);
|
||||||
|
formData.append("procedureReviewSearchKeywordParam2", procedureReviewSearchKeywordParam2);
|
||||||
|
formData.append("procedureReviewSearchKeywordParam3", procedureReviewSearchKeywordParam3);
|
||||||
|
formData.append("procedureReviewSort", procedureReviewSort);
|
||||||
|
formData.append("procedureReviewDir", procedureReviewDir);
|
||||||
|
formData.append("procedureReviewStart", procedureReviewStart);
|
||||||
|
formData.append("procedureReviewLimit", procedureReviewLimit);
|
||||||
|
formData.append("procedureReviewSearchStartDate", procedureReviewSearchStartDate);
|
||||||
|
formData.append("procedureReviewSearchEndDate", procedureReviewSearchEndDate);
|
||||||
|
formData.append("procedureReviewSearchDateType", procedureReviewSearchDateType);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: encodeURI('/procedureReview/getProcedureReviewList.do'),
|
||||||
|
data: formData,
|
||||||
|
dataType: "json",
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
type: 'POST',
|
||||||
|
async: true,
|
||||||
|
success: function (data) {
|
||||||
|
if ('0' == data.msgCode) {
|
||||||
|
// 페이징 처리
|
||||||
|
procedureReviewTotalCount = data.totalCount;
|
||||||
|
|
||||||
|
procedureReviewTotalPages = Math.ceil(procedureReviewTotalCount / procedureReviewLimit);
|
||||||
|
|
||||||
|
// 리스트 조회
|
||||||
|
procedureReviewAgGridData = data.rows;
|
||||||
|
procedureReviewGridOptions.api.setRowData(procedureReviewAgGridData);
|
||||||
|
|
||||||
|
if (0 < data.rows.length) {
|
||||||
|
//페이징 처리
|
||||||
|
window.pagObj = $('#procedureReviewPagination').twbsPagination({
|
||||||
|
startPage: ((procedureReviewStart / procedureReviewLimit) + 1),
|
||||||
|
totalPages: (procedureReviewTotalPages == 0) ? 1 : procedureReviewTotalPages,
|
||||||
|
visiblePages: 10,
|
||||||
|
initiateStartPageClick: false,
|
||||||
|
prev: '<img src="/image/web/page_navigation_arrow.svg" alt="prev"/>',
|
||||||
|
next: '<img src="/image/web/page_navigation_arrow.svg" alt="next"/>',
|
||||||
|
first: '',
|
||||||
|
last: '',
|
||||||
|
onPageClick: function (event, page) {
|
||||||
|
fn_procedureReviewPagination(page);
|
||||||
|
}
|
||||||
|
}).on('page', function (event, page) {
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modalEvent.danger("조회 오류", data.msgDesc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||||
|
},
|
||||||
|
beforeSend: function () {
|
||||||
|
// 로딩열기
|
||||||
|
procedureReviewGridOptions.api.showLoadingOverlay();
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 검색하기
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_procedureReviewSearch(param) {
|
||||||
|
if ("A" != param && "Y" != selectUseYn) {
|
||||||
|
modalEvent.warning("", "조회 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn_procedureReviewPaginReset();
|
||||||
|
|
||||||
|
procedureReviewSearchKeywordParam0 = $("#procedureReviewSearchKeyword0").val();
|
||||||
|
procedureReviewSearchKeywordParam1 = $("#procedureReviewSearchKeyword1").val();
|
||||||
|
procedureReviewSearchKeywordParam2 = ""; // 작성자 검색 제거
|
||||||
|
procedureReviewSearchKeywordParam3 = $("#procedureReviewSearchKeywordParam3").val();
|
||||||
|
|
||||||
|
procedureReviewSearchDateType = $("#procedureReviewSearchDateType").val();
|
||||||
|
procedureReviewSearchStartDate = $("#procedureReviewSearchStartDate").val();
|
||||||
|
procedureReviewSearchEndDate = $("#procedureReviewSearchEndDate").val();
|
||||||
|
|
||||||
|
fn_selectListProcedureReviewJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 초기화하기
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_procedureReviewReset() {
|
||||||
|
$("#procedureReviewSearchKeyword0").val("");
|
||||||
|
$("#procedureReviewSearchKeyword1").val("");
|
||||||
|
$("#procedureReviewSearchKeyword2").val("");
|
||||||
|
$("#procedureReviewSearchKeyword3").val("");
|
||||||
|
|
||||||
|
fn_procedureReviewSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이징 처리
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_procedureReviewPagination(param) {
|
||||||
|
procedureReviewStart = (parseInt(param) - 1) * procedureReviewLimit;
|
||||||
|
|
||||||
|
fn_selectListProcedureReviewJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이징 리셋
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_procedureReviewPaginReset() {
|
||||||
|
procedureReviewSearchKeywordParam0 = '';
|
||||||
|
procedureReviewSearchKeywordParam1 = '';
|
||||||
|
procedureReviewSearchKeywordParam2 = '';
|
||||||
|
procedureReviewSearchKeywordParam3 = '';
|
||||||
|
|
||||||
|
procedureReviewStart = 0;
|
||||||
|
procedureReviewLimit = 100;
|
||||||
|
procedureReviewTotalCount = 0;
|
||||||
|
procedureReviewTotalPages = 0;
|
||||||
|
|
||||||
|
//페이징 초기화
|
||||||
|
if ($("#procedureReviewPagination").data("twbs-pagination")) {
|
||||||
|
$("#procedureReviewPagination").twbsPagination("destroy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 시술후기 삭제
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_deleteProcedureReview() {
|
||||||
|
if ("Y" != deleteUseYn) {
|
||||||
|
modalEvent.warning("", "삭제 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!procedureReviewSelectId) {
|
||||||
|
modalEvent.warning("", "삭제할 대상을 선택하세요.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalEvent.info("삭제", "선택한 시술후기 정보를 삭제하시겠습니까?", function () {
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("menuClass", menuClass);
|
||||||
|
formData.append("muProcedureReviewId", procedureReviewSelectId);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: encodeURI('/procedureReview/delProcedureReview.do'),
|
||||||
|
data: formData,
|
||||||
|
dataType: "json",
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
type: 'POST',
|
||||||
|
async: true,
|
||||||
|
success: function (data) {
|
||||||
|
if ('0' == data.msgCode) {
|
||||||
|
modalEvent.success("삭제 성공", data.msgDesc, function () {
|
||||||
|
fn_procedureReviewOk();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modalEvent.danger("삭제 오류", data.msgDesc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
modalEvent.danger("삭제 오류", "삭제 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||||
|
},
|
||||||
|
beforeSend: function () {
|
||||||
|
|
||||||
|
},
|
||||||
|
complete: function () {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 검색 엔터 시술후기
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_procedureReviewEnter(e) {
|
||||||
|
if (e.which) {
|
||||||
|
if (13 == e.which) {
|
||||||
|
fn_procedureReviewSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (13 == e.keyCode) {
|
||||||
|
fn_procedureReviewSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 완료
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_procedureReviewOk() {
|
||||||
|
fn_procedureReviewReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 등록 화면으로 이동.
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_insertProcedureReviewIntro() {
|
||||||
|
if ("Y" == insertUseYn) {
|
||||||
|
let pagingParam = "?menuClass=" + menuClass;
|
||||||
|
fn_leftFormAction("/procedureReview/moveProcedureReviewInsert.do" + pagingParam);
|
||||||
|
} else {
|
||||||
|
modalEvent.warning("", "등록 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 수정 화면으로 이동.
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_updateProcedureReviewIntro(muProcedureReviewId) {
|
||||||
|
if ("Y" == updateUseYn) {
|
||||||
|
let pagingParam = "?menuClass=" + menuClass;
|
||||||
|
pagingParam += "&muProcedureReviewId=" + muProcedureReviewId;
|
||||||
|
fn_leftFormAction("/procedureReview/moveProcedureReviewUpdate.do" + pagingParam);
|
||||||
|
} else {
|
||||||
|
modalEvent.warning("", "수정 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let procedureReviewColumnDefs = [
|
||||||
|
{ field: "checkbox", headerName: "", minWidth: 55, maxWidth: 55, headerCheckboxSelection: true, checkboxSelection: true },
|
||||||
|
{ field: "rowNum", headerName: "번호", minWidth: 60, maxWidth: 60, sortable: false, cellStyle: { textAlign: 'center' } },
|
||||||
|
{ field: "title", headerName: "제목", minWidth: 150, cellStyle: { cursor: 'pointer', color: '#3985EA' } },
|
||||||
|
{
|
||||||
|
field: "content",
|
||||||
|
headerName: "내용요약",
|
||||||
|
minWidth: 150,
|
||||||
|
valueFormatter: function (params) {
|
||||||
|
let val = params.value;
|
||||||
|
if (!val) return "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
let delta = JSON.parse(val);
|
||||||
|
if (typeof delta === 'string') {
|
||||||
|
delta = JSON.parse(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta && delta.ops) {
|
||||||
|
let text = "";
|
||||||
|
delta.ops.forEach(function (op) {
|
||||||
|
if (op.insert) {
|
||||||
|
if (typeof op.insert === 'string') text += op.insert;
|
||||||
|
else if (op.insert.image) text += "[이미지] ";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return text.replace(/\n/g, ' ').trim();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Fallback to strip HTML
|
||||||
|
}
|
||||||
|
return String(val).replace(/<[^>]*>?/gm, '').replace(/\n/g, ' ').trim();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ field: "hashtag", headerName: "해시태그", minWidth: 150 },
|
||||||
|
{ field: "writeDate", headerName: "등록일", minWidth: 100, maxWidth: 150 },
|
||||||
|
{ field: "writeName", headerName: "작성자", minWidth: 100, maxWidth: 150 },
|
||||||
|
];
|
||||||
|
|
||||||
|
let procedureReviewGridOptions = {
|
||||||
|
suppressRowTransform: true,
|
||||||
|
columnDefs: procedureReviewColumnDefs,
|
||||||
|
defaultColDef: {
|
||||||
|
flex: 1,
|
||||||
|
sortable: true,
|
||||||
|
resizable: true,
|
||||||
|
editable: true,
|
||||||
|
cellStyle: { textAlign: 'left', fontSize: '14px', padding: '0' },
|
||||||
|
enablePivot: true,
|
||||||
|
enableValue: true
|
||||||
|
},
|
||||||
|
headerHeight: 41,
|
||||||
|
rowHeight: 41,
|
||||||
|
rowData: procedureReviewAgGridData,
|
||||||
|
suppressRowClickSelection: true,
|
||||||
|
localeText: {
|
||||||
|
noRowsToShow: '조회 결과가 없습니다.'
|
||||||
|
},
|
||||||
|
rowSelection: 'multiple',
|
||||||
|
debug: false,
|
||||||
|
onCellClicked: function (event) {
|
||||||
|
if ('title' == event.column.colId) {
|
||||||
|
fn_updateProcedureReviewIntro(event.data.muProcedureReviewId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSelectionChanged: function (event) {
|
||||||
|
let selectRows = [];
|
||||||
|
selectRows = event.api.getSelectedRows();
|
||||||
|
|
||||||
|
procedureReviewSelectId = '';
|
||||||
|
for (let i = 0; i < selectRows.length; i++) {
|
||||||
|
procedureReviewSelectId += selectRows[i].muProcedureReviewId + ",";
|
||||||
|
}
|
||||||
|
procedureReviewSelectId = procedureReviewSelectId.substring(0, procedureReviewSelectId.lastIndexOf(','));
|
||||||
|
},
|
||||||
|
onSortChanged: function (event) {
|
||||||
|
procedureReviewSort = '';
|
||||||
|
let columnArr = event.columnApi.getColumnState();
|
||||||
|
if (0 < columnArr.length) {
|
||||||
|
columnArr.sort(function (a, b) {
|
||||||
|
return a.sortIndex - b.sortIndex;
|
||||||
|
});
|
||||||
|
|
||||||
|
let nullCnt = 0;
|
||||||
|
for (let i = 0; i < columnArr.length; i++) {
|
||||||
|
let gridSortModel = columnArr[i].colId;
|
||||||
|
let gridSort = columnArr[i].sort;
|
||||||
|
|
||||||
|
if (gridSort != null) {
|
||||||
|
procedureReviewStart = 0;
|
||||||
|
procedureReviewSort += gridSortModel + ' ' + gridSort + ',';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nullCnt++;
|
||||||
|
if (nullCnt == columnArr.length) {
|
||||||
|
procedureReviewSort = '';
|
||||||
|
procedureReviewDir = '';
|
||||||
|
procedureReviewStart = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
procedureReviewSort = procedureReviewSort.substring(0, procedureReviewSort.lastIndexOf(","));
|
||||||
|
|
||||||
|
fn_procedureReviewSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let procedureReviewGridDiv = document.querySelector('#procedureReviewGrid');
|
||||||
|
new agGrid.Grid(procedureReviewGridDiv, procedureReviewGridOptions);
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이지 init
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_pageInit() {
|
||||||
|
if (!procedureReviewSearchStartDate && !procedureReviewSearchEndDate) {
|
||||||
|
} else {
|
||||||
|
$("#procedureReviewSearchStartDate").val(procedureReviewSearchStartDate).trigger("change");
|
||||||
|
$("#procedureReviewSearchEndDate").val(procedureReviewSearchEndDate).trigger("change");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#procedureReviewSearchKeyword0").val(procedureReviewSearchKeywordParam0);
|
||||||
|
$("#procedureReviewSearchKeyword1").val(procedureReviewSearchKeywordParam1);
|
||||||
|
$("#procedureReviewSearchKeyword2").val(procedureReviewSearchKeywordParam2);
|
||||||
|
$("#procedureReviewSearchKeyword3").val(procedureReviewSearchKeywordParam3);
|
||||||
|
|
||||||
|
fn_procedureReviewSearch("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이지 event
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_pageProcedureReview() {
|
||||||
|
|
||||||
|
$(document).on('keypress', '#procedureReviewSearchKeyword1', function (e) {
|
||||||
|
fn_procedureReviewEnter(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('keypress', '#procedureReviewSearchKeyword3', function (e) {
|
||||||
|
fn_procedureReviewEnter(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btnSearchProcedureReview").click(function () {
|
||||||
|
fn_procedureReviewSearch();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btnInsertProcedureReview").click(function () {
|
||||||
|
fn_insertProcedureReviewIntro();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btnDeleteProcedureReview").click(function () {
|
||||||
|
fn_deleteProcedureReview();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
fn_pageInit();
|
||||||
|
fn_pageProcedureReview();
|
||||||
|
});
|
||||||
@@ -0,0 +1,421 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* Quill 에디터 인스턴스
|
||||||
|
****************************************************************************/
|
||||||
|
let quill;
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Quill Image blot - data: URL (base64) 허용
|
||||||
|
****************************************************************************/
|
||||||
|
const QuillImage = Quill.import('formats/image');
|
||||||
|
const originalSanitize = QuillImage.sanitize;
|
||||||
|
QuillImage.sanitize = function (url) {
|
||||||
|
if (url && url.startsWith('data:')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return originalSanitize.call(this, url);
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Quill 에디터 초기화
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_initQuillEditor() {
|
||||||
|
quill = new Quill('#quillEditor', {
|
||||||
|
theme: 'snow',
|
||||||
|
placeholder: '내용을 입력해주세요.',
|
||||||
|
modules: {
|
||||||
|
toolbar: {
|
||||||
|
container: [
|
||||||
|
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
|
||||||
|
[{ 'font': [] }],
|
||||||
|
[{ 'size': ['small', false, 'large', 'huge'] }],
|
||||||
|
['bold', 'italic', 'underline', 'strike'],
|
||||||
|
[{ 'color': [] }, { 'background': [] }],
|
||||||
|
[{ 'script': 'sub' }, { 'script': 'super' }],
|
||||||
|
[{ 'list': 'ordered' }, { 'list': 'bullet' }, { 'list': 'check' }],
|
||||||
|
[{ 'indent': '-1' }, { 'indent': '+1' }],
|
||||||
|
[{ 'direction': 'rtl' }],
|
||||||
|
[{ 'align': [] }],
|
||||||
|
['blockquote', 'code-block'],
|
||||||
|
['link', 'image', 'video', 'formula'],
|
||||||
|
['clean']
|
||||||
|
],
|
||||||
|
handlers: {
|
||||||
|
image: imageHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
quill.root.addEventListener('paste', function (e) {
|
||||||
|
try {
|
||||||
|
console.log("[Paste Event] Triggered");
|
||||||
|
let clipboardData = e.clipboardData || window.clipboardData || (e.originalEvent && e.originalEvent.clipboardData);
|
||||||
|
console.log("[Paste Event] clipboardData:", clipboardData);
|
||||||
|
if (!clipboardData || !clipboardData.items) {
|
||||||
|
console.warn("[Paste Event] No clipboardData or items found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = clipboardData.items;
|
||||||
|
console.log("[Paste Event] items length:", items.length);
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let item = items[i];
|
||||||
|
console.log(`[Paste Event] item[${i}] kind: ${item.kind}, type: ${item.type}`);
|
||||||
|
if (item.kind === 'file' && item.type.match('^image/')) {
|
||||||
|
console.log(`[Paste Event] Image file detected. Prevent default.`);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
let file = item.getAsFile();
|
||||||
|
console.log(`[Paste Event] getAsFile() result:`, file);
|
||||||
|
if (file) {
|
||||||
|
if (!file.name || !file.name.includes('.')) {
|
||||||
|
let ext = 'png';
|
||||||
|
if (file.type === 'image/jpeg') ext = 'jpg';
|
||||||
|
else if (file.type === 'image/gif') ext = 'gif';
|
||||||
|
file = new File([file], "clipboard_image." + ext, { type: file.type });
|
||||||
|
console.log(`[Paste Event] Renamed missing file info:`, file.name);
|
||||||
|
}
|
||||||
|
console.log(`[Paste Event] Call fn_uploadImageAndInsertToQuill() for:`, file.name);
|
||||||
|
fn_uploadImageAndInsertToQuill(file);
|
||||||
|
} else {
|
||||||
|
console.error("[Paste Event] getAsFile() returned null.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Paste Event] Error parsing paste logic:", err);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
quill.root.addEventListener('drop', function (e) {
|
||||||
|
try {
|
||||||
|
console.log("[Drop Event] Triggered");
|
||||||
|
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length) {
|
||||||
|
let hasImage = false;
|
||||||
|
console.log("[Drop Event] files length:", e.dataTransfer.files.length);
|
||||||
|
for (let i = 0; i < e.dataTransfer.files.length; i++) {
|
||||||
|
let file = e.dataTransfer.files[i];
|
||||||
|
console.log(`[Drop Event] file[${i}] type: ${file.type}`);
|
||||||
|
if (file.type.match('^image/')) {
|
||||||
|
console.log(`[Drop Event] Image file detected. Prevent default.`);
|
||||||
|
hasImage = true;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
fn_uploadImageAndInsertToQuill(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("[Drop Event] No files found in drop event");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("[Drop Event] Error parsing drop logic:", err);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
function imageHandler() {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.setAttribute('type', 'file');
|
||||||
|
input.setAttribute('accept', 'image/*');
|
||||||
|
input.click();
|
||||||
|
|
||||||
|
input.onchange = async () => {
|
||||||
|
const file = input.files[0];
|
||||||
|
fn_uploadImageAndInsertToQuill(file);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fn_uploadImageAndInsertToQuill(file) {
|
||||||
|
if (!file) {
|
||||||
|
console.error("[Upload] file is undefined or null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("[Upload] fn_uploadImageAndInsertToQuill Start. File name:", file.name, "size:", file.size);
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
formData.append("menuClass", menuClass);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/procedureReview/putProcedureReviewFile.do',
|
||||||
|
type: 'POST',
|
||||||
|
data: formData,
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
dataType: 'json',
|
||||||
|
success: function (res) {
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
try { res = JSON.parse(res); } catch (e) { }
|
||||||
|
}
|
||||||
|
console.log("[Upload] API Success response:", res);
|
||||||
|
if (res.msgCode == 0 || res.msgCode === '0') {
|
||||||
|
let range = quill.getSelection(true);
|
||||||
|
let index = range ? range.index : quill.getLength();
|
||||||
|
console.log(`[Upload] Inserting image at index = ${index}, path = ${res.rows.filePath}`);
|
||||||
|
quill.insertEmbed(index, 'image', res.rows.filePath);
|
||||||
|
} else {
|
||||||
|
console.error("[Upload] API logic error msgDesc:", res.msgDesc);
|
||||||
|
modalEvent.danger("업로드 오류", res.msgDesc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
console.error("[Upload] AJAX HTTP error. status:", status, "error:", error, "responseText:", xhr.responseText);
|
||||||
|
modalEvent.danger("업로드 오류", "이미지 업로드 중 오류가 발생했습니다.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* Quill 콘텐츠를 Delta JSON 문자열로 반환 (저장용)
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_getQuillContentForSave() {
|
||||||
|
return JSON.stringify(quill.getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 저장된 콘텐츠를 Quill에 로드 (Delta JSON / 레거시 HTML 호환)
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_loadQuillContent(content) {
|
||||||
|
if (!content) return;
|
||||||
|
|
||||||
|
if (typeof content === 'object') {
|
||||||
|
if (Array.isArray(content.ops)) quill.setContents(content.ops);
|
||||||
|
else if (Array.isArray(content)) quill.setContents(content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
// base64인지 판별해 디코딩 시도 (기존 데이터와 새 데이터 호환을 위해)
|
||||||
|
const isBase64 = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?$/.test(content);
|
||||||
|
if (isBase64 && content.length > 0 && !content.trim().startsWith('{') && !content.trim().startsWith('[')) {
|
||||||
|
try {
|
||||||
|
content = decodeURIComponent(escape(atob(content)));
|
||||||
|
} catch (e) {
|
||||||
|
// base64 디코딩 실패면 그냥 넘어감
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.trim().startsWith('{') || content.trim().startsWith('[')) {
|
||||||
|
let parsed = JSON.parse(content);
|
||||||
|
if (typeof parsed === 'string') {
|
||||||
|
parsed = JSON.parse(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof parsed === 'object' && parsed !== null && Array.isArray(parsed.ops)) {
|
||||||
|
quill.setContents(parsed.ops);
|
||||||
|
return;
|
||||||
|
} else if (Array.isArray(parsed)) {
|
||||||
|
quill.setContents(parsed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("Quill Delta Parsing Failed - Fallback to HTML : ", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
quill.clipboard.dangerouslyPasteHTML(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 시술후기 상세 조회
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_selectProcedureReview() {
|
||||||
|
if (true != fn_emptyCheck(muProcedureReviewId)) {
|
||||||
|
modalEvent.warning("수정", "시술후기 정보가 없습니다.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("menuClass", menuClass);
|
||||||
|
formData.append("muProcedureReviewId", muProcedureReviewId);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: encodeURI('/procedureReview/getProcedureReview.do'),
|
||||||
|
data: formData,
|
||||||
|
dataType: "json",
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
type: 'POST',
|
||||||
|
async: true,
|
||||||
|
success: function (data) {
|
||||||
|
if ('0' == data.msgCode) {
|
||||||
|
if (data.rows && data.rows.length > 0) {
|
||||||
|
let photoData = data.rows[0];
|
||||||
|
$("#title").val(photoData.title);
|
||||||
|
fn_loadQuillContent(photoData.content);
|
||||||
|
$("#hashtag").val(photoData.hashtag);
|
||||||
|
} else {
|
||||||
|
modalEvent.warning("수정", "조회된 시술후기 데이터가 없습니다.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
alert("수정 오류: " + data.msgDesc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
alert("수정 오류: 수정 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 시술후기 수정
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_updateProcedureReview() {
|
||||||
|
if ("Y" != updateUseYn) {
|
||||||
|
modalEvent.warning("", "수정 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let title = $("#title").val();
|
||||||
|
let content = fn_getQuillContentForSave();
|
||||||
|
let hashtag = $("#hashtag").val();
|
||||||
|
|
||||||
|
// Quill 에디터 빈 값 체크
|
||||||
|
let quillText = quill.getText().trim();
|
||||||
|
|
||||||
|
if (true != fn_emptyCheck(title)) {
|
||||||
|
modalEvent.warning("수정", "제목을 입력하세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!quillText || quillText.length === 0) {
|
||||||
|
modalEvent.warning("수정", "내용을 입력하세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (true != fn_emptyCheck(hashtag)) {
|
||||||
|
modalEvent.warning("수정", "해시태그를 입력하세요.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
modalEvent.info("수정", "시술후기 정보를 수정하시겠습니까?", function () {
|
||||||
|
let formData = new FormData();
|
||||||
|
formData.append("menuClass", menuClass);
|
||||||
|
formData.append("title", title);
|
||||||
|
|
||||||
|
let encodedContent = btoa(unescape(encodeURIComponent(content)));
|
||||||
|
formData.append("content", encodedContent);
|
||||||
|
formData.append("isEncoded", "Y"); // base64 인코딩 플래그 추가
|
||||||
|
formData.append("hashtag", hashtag);
|
||||||
|
formData.append("muProcedureReviewId", muProcedureReviewId);
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: encodeURI('/procedureReview/modProcedureReview.do'),
|
||||||
|
data: formData,
|
||||||
|
dataType: "json",
|
||||||
|
processData: false,
|
||||||
|
contentType: false,
|
||||||
|
type: 'POST',
|
||||||
|
async: true,
|
||||||
|
success: function (data) {
|
||||||
|
if ('0' == data.msgCode) {
|
||||||
|
modalEvent.success("수정 성공", data.msgDesc, function () {
|
||||||
|
fn_selectListProcedureReviewIntro();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
modalEvent.danger("수정 오류", data.msgDesc);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
modalEvent.danger("수정 오류", "수정 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 리스트 화면으로 이동
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_selectListProcedureReviewIntro() {
|
||||||
|
if ("Y" == selectUseYn) {
|
||||||
|
let pagingParam = "?menuClass=" + menuClass;
|
||||||
|
fn_leftFormAction("/procedureReview/moveProcedureReviewList.do" + pagingParam);
|
||||||
|
} else {
|
||||||
|
modalEvent.warning("", "조회 권한이 없습니다.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 도움말 모달
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_initHelpModal() {
|
||||||
|
$('#btnHelp').on('click', function () {
|
||||||
|
$('#helpModal').addClass('active');
|
||||||
|
});
|
||||||
|
$('#helpModalClose').on('click', function () {
|
||||||
|
$('#helpModal').removeClass('active');
|
||||||
|
});
|
||||||
|
$('#helpModal').on('click', function (e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
$(this).removeClass('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 미리보기 모달
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_initPreviewModal() {
|
||||||
|
$('#previewModalClose').on('click', function () {
|
||||||
|
$('#previewModal').removeClass('active');
|
||||||
|
});
|
||||||
|
$('#previewModal').on('click', function (e) {
|
||||||
|
if (e.target === this) {
|
||||||
|
$(this).removeClass('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function fn_showPreview() {
|
||||||
|
let title = $("#title").val();
|
||||||
|
let content = quill.root.innerHTML;
|
||||||
|
let hashtag = $("#hashtag").val();
|
||||||
|
|
||||||
|
// 미리보기 데이터 설정
|
||||||
|
$('#previewTitle').text(title || '(제목 없음)');
|
||||||
|
$('#previewContent').html(content);
|
||||||
|
|
||||||
|
if (hashtag) {
|
||||||
|
let tags = hashtag.split(/\s+/).map(function (tag) {
|
||||||
|
tag = tag.trim();
|
||||||
|
if (tag && !tag.startsWith('#')) tag = '#' + tag;
|
||||||
|
return tag;
|
||||||
|
}).filter(function (tag) { return tag; });
|
||||||
|
$('#previewHashtag').html(tags.join(' '));
|
||||||
|
$('#previewHashtagArea').show();
|
||||||
|
} else {
|
||||||
|
$('#previewHashtagArea').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#previewModal').addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이지 init
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_pageInit() {
|
||||||
|
fn_initQuillEditor();
|
||||||
|
fn_initHelpModal();
|
||||||
|
fn_initPreviewModal();
|
||||||
|
fn_selectProcedureReview();
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
* 페이지 event
|
||||||
|
****************************************************************************/
|
||||||
|
function fn_pageEvent() {
|
||||||
|
$('.btnCancle').on("click", function () {
|
||||||
|
fn_selectListProcedureReviewIntro();
|
||||||
|
});
|
||||||
|
$('.btnSave').on("click", function () {
|
||||||
|
fn_updateProcedureReview();
|
||||||
|
});
|
||||||
|
$('.btnPreview').on("click", function () {
|
||||||
|
fn_showPreview();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
fn_pageInit();
|
||||||
|
fn_pageEvent();
|
||||||
|
});
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/web/layout/homeLayout}">
|
||||||
|
<th:block layout:fragment="layout_css">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/web/ContentsBbsUpd.css">
|
||||||
|
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||||
|
<style>
|
||||||
|
/* Quill 에디터 스타일 보정 */
|
||||||
|
.ql-toolbar.ql-snow {
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
background: #F9FAFB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-container.ql-snow {
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
min-height: 350px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor {
|
||||||
|
min-height: 340px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor.ql-blank::before {
|
||||||
|
color: #B5BDC4;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 페이지 스크롤 활성화 오버라이드 */
|
||||||
|
.center_box {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project_wrap .content_section .hospital_wrap .center_box .content_box {
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 단일 폼 레이아웃 */
|
||||||
|
.pr-single-form {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-grid-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group input[type="text"] {
|
||||||
|
height: 38px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group input[type="text"]:focus {
|
||||||
|
border-color: #3985EA;
|
||||||
|
box-shadow: 0 0 0 2px rgba(57, 133, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 내용 헤더 (라벨 + 도움말 버튼) */
|
||||||
|
.content-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-help {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #3985EA;
|
||||||
|
background: #EFF6FF;
|
||||||
|
border: 1px solid #BFDBFE;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-help:hover {
|
||||||
|
background: #DBEAFE;
|
||||||
|
border-color: #93C5FD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 액션 버튼 */
|
||||||
|
.pr-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quill 에디터 및 미리보기 이미지 크기 제한 */
|
||||||
|
.ql-editor img,
|
||||||
|
#previewContent img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 도움말 모달 */
|
||||||
|
.help-modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 9999;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-overlay.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 640px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-header h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1F2937;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-close {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
background: #F3F4F6;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #6B7280;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-close:hover {
|
||||||
|
background: #E5E7EB;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body {
|
||||||
|
padding: 24px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #4B5563;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1F2937;
|
||||||
|
margin: 20px 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body h4:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 8px 0 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body table th {
|
||||||
|
background: #F9FAFB;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body .tip-box {
|
||||||
|
background: #F0F9FF;
|
||||||
|
border: 1px solid #BAE6FD;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body .tip-box strong {
|
||||||
|
color: #0284C7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_top_script">
|
||||||
|
<script>
|
||||||
|
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||||
|
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||||
|
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_content">
|
||||||
|
<div class="center_box">
|
||||||
|
<p class="page_title" style="float:none; display:block; width:100%;">시술후기 등록</p>
|
||||||
|
|
||||||
|
<div class="pr-single-form">
|
||||||
|
<!-- 제목 -->
|
||||||
|
<div class="form-grid-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>제목</label>
|
||||||
|
<input type="text" id="title" placeholder="제목을 입력해주세요." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 내용 (Quill 에디터) -->
|
||||||
|
<div class="form-group" style="margin-bottom: 16px;">
|
||||||
|
<div class="content-header">
|
||||||
|
<label>내용</label>
|
||||||
|
<button type="button" class="btn-help" id="btnHelp">? 도움말</button>
|
||||||
|
</div>
|
||||||
|
<div id="quillEditor"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 해시태그 -->
|
||||||
|
<div class="form-grid-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>해시태그</label>
|
||||||
|
<input type="text" id="hashtag" placeholder="#해시태그를 입력해주세요." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 버튼 -->
|
||||||
|
<div class="pr-actions">
|
||||||
|
<button class="preview_btn btnPreview"
|
||||||
|
style="width: 90px; height: 36px; border-radius: 4px; background: #fff; color: #3985EA; border: 1px solid #3985EA; font-weight: 600; cursor: pointer;">미리보기</button>
|
||||||
|
<button class="registration_btn btnSave"
|
||||||
|
style="width: 80px; height: 36px; border-radius: 4px;">등록</button>
|
||||||
|
<button class="cancel_btn btnCancle"
|
||||||
|
style="width: 80px; height: 36px; border-radius: 4px; margin-left: 10px;">취소</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 미리보기 모달 -->
|
||||||
|
<div class="help-modal-overlay" id="previewModal">
|
||||||
|
<div class="help-modal" style="width: 800px; max-height: 90vh;">
|
||||||
|
<div class="help-modal-header">
|
||||||
|
<h3>👁 미리보기</h3>
|
||||||
|
<button class="help-modal-close" id="previewModalClose">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="help-modal-body" style="padding: 0;">
|
||||||
|
<!-- 제목 영역 -->
|
||||||
|
<div style="padding: 20px 24px; border-bottom: 1px solid #E5E7EB;">
|
||||||
|
<p style="font-size: 11px; color: #9CA3AF; margin-bottom: 4px;">제목</p>
|
||||||
|
<h2 id="previewTitle" style="font-size: 20px; font-weight: 700; color: #1F2937; margin: 0;"></h2>
|
||||||
|
</div>
|
||||||
|
<!-- 내용 영역 -->
|
||||||
|
<div style="padding: 24px;">
|
||||||
|
<div id="previewContent" class="ql-editor"
|
||||||
|
style="min-height: 200px; max-height: 500px; overflow-y: auto; padding: 0;"></div>
|
||||||
|
</div>
|
||||||
|
<!-- 해시태그 영역 -->
|
||||||
|
<div id="previewHashtagArea" style="padding: 12px 24px 20px; border-top: 1px solid #E5E7EB;">
|
||||||
|
<span id="previewHashtag"
|
||||||
|
style="display: inline-block; padding: 4px 12px; background: #EFF6FF; color: #3985EA; border-radius: 20px; font-size: 13px; font-weight: 500;"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 도움말 모달 -->
|
||||||
|
<div class="help-modal-overlay" id="helpModal">
|
||||||
|
<div class="help-modal">
|
||||||
|
<div class="help-modal-header">
|
||||||
|
<h3>📝 에디터 사용 가이드</h3>
|
||||||
|
<button class="help-modal-close" id="helpModalClose">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="help-modal-body">
|
||||||
|
<h4>📌 텍스트 서식</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
<th>단축키</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>B</strong> 굵게</td>
|
||||||
|
<td>선택한 텍스트를 굵게 표시</td>
|
||||||
|
<td>Ctrl+B</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><em>I</em> 기울임</td>
|
||||||
|
<td>선택한 텍스트를 기울임꼴로 표시</td>
|
||||||
|
<td>Ctrl+I</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><u>U</u> 밑줄</td>
|
||||||
|
<td>선택한 텍스트에 밑줄 적용</td>
|
||||||
|
<td>Ctrl+U</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><s>S</s> 취소선</td>
|
||||||
|
<td>선택한 텍스트에 취소선 적용</td>
|
||||||
|
<td>-</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>🎨 글자 스타일</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>헤더 (H1~H6)</td>
|
||||||
|
<td>제목 크기를 설정합니다. 숫자가 작을수록 큰 제목</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>폰트</td>
|
||||||
|
<td>글꼴을 변경합니다</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>글자 크기</td>
|
||||||
|
<td>Small, Normal, Large, Huge 중 선택</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>글자색 / 배경색</td>
|
||||||
|
<td>텍스트 색상 또는 배경 하이라이트 색상 선택</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>📋 문단 서식</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>순서 목록</td>
|
||||||
|
<td>1. 2. 3. 형태의 번호 목록</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>비순서 목록</td>
|
||||||
|
<td>• 형태의 글머리 기호 목록</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>체크 목록</td>
|
||||||
|
<td>☐ 형태의 체크박스 목록</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>들여쓰기</td>
|
||||||
|
<td>텍스트를 안쪽/바깥쪽으로 이동</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>정렬</td>
|
||||||
|
<td>좌측, 중앙, 우측, 양쪽 정렬</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>인용문</td>
|
||||||
|
<td>인용 블록으로 표시</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>코드 블록</td>
|
||||||
|
<td>코드 형태로 표시</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>🔗 멀티미디어</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>링크</td>
|
||||||
|
<td>선택한 텍스트에 URL 링크 삽입</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>이미지</td>
|
||||||
|
<td>이미지 파일을 선택하여 삽입 (서버에 자동 업로드)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>동영상</td>
|
||||||
|
<td>YouTube 등 동영상 URL을 삽입</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>🧹 기타</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>서식 지우기</td>
|
||||||
|
<td>선택한 텍스트의 모든 서식을 초기화</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>위첨자 / 아래첨자</td>
|
||||||
|
<td>X² 또는 H₂O 같은 수식 표현</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="tip-box">
|
||||||
|
<strong>💡 TIP</strong><br>
|
||||||
|
• 텍스트를 드래그한 후 툴바 버튼을 클릭하면 선택 영역에만 서식이 적용됩니다.<br>
|
||||||
|
• 이미지는 클립보드에서 Ctrl+V로 붙여넣기도 가능합니다.<br>
|
||||||
|
• 실행 취소는 Ctrl+Z, 다시 실행은 Ctrl+Y 입니다.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_popup">
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_script">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
|
||||||
|
<script src="/js/crm/procedureReview/procedureReviewInsert.js"></script>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/web/layout/homeLayout}">
|
||||||
|
<th:block layout:fragment="layout_css">
|
||||||
|
<link rel="stylesheet" href="/css/web/webPhotoDietSelectList.css">
|
||||||
|
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_top_script">
|
||||||
|
<script src="/js/web/jquery.twbsPagination.js" type="text/javascript"></script>
|
||||||
|
<script>
|
||||||
|
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||||
|
let categoryDivCd = "07";
|
||||||
|
|
||||||
|
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||||
|
let insertUseYn = "[[${insertUseYn}]]" == "" ? "N" : "[[${insertUseYn}]]";
|
||||||
|
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
|
||||||
|
let deleteUseYn = "[[${deleteUseYn}]]" == "" ? "N" : "[[${deleteUseYn}]]";
|
||||||
|
let downloadUseYn = "[[${downloadUseYn}]]" == "" ? "N" : "[[${downloadUseYn}]]";
|
||||||
|
|
||||||
|
/* 검색 관련 변수 */
|
||||||
|
let procedureReviewSearchKeywordParam0 = "[[${param.procedureReviewSearchKeywordParam0}]]";
|
||||||
|
let procedureReviewSearchKeywordParam1 = "[[${param.procedureReviewSearchKeywordParam1}]]";
|
||||||
|
let procedureReviewSearchKeywordParam2 = "";
|
||||||
|
let procedureReviewSearchKeywordParam3 = "";
|
||||||
|
|
||||||
|
let procedureReviewSearchDateType = "";
|
||||||
|
let procedureReviewSearchStartDate = "";
|
||||||
|
let procedureReviewSearchEndDate = "";
|
||||||
|
|
||||||
|
let procedureReviewSort = "[[${param.procedureReviewSort}]]";
|
||||||
|
let procedureReviewDir = "[[${param.procedureReviewDir}]]";
|
||||||
|
let procedureReviewStart = "[[${param.procedureReviewStart}]]" == "" ? 0 : "[[${param.procedureReviewStart}]]";
|
||||||
|
let procedureReviewLimit = "[[${param.procedureReviewLimit}]]" == "" ? 500 : "[[${param.procedureReviewLimit}]]";
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_content">
|
||||||
|
<!-- 센터쪽 -->
|
||||||
|
<div class="center_box">
|
||||||
|
<p class="page_title">시술후기</p>
|
||||||
|
|
||||||
|
<div class="filter_box">
|
||||||
|
<div class="form_box">
|
||||||
|
|
||||||
|
<!-- 제목 검색 input -->
|
||||||
|
<div class="search_list">
|
||||||
|
<div class="search_box">
|
||||||
|
<img src="/image/web/search_G.svg" alt="search" />
|
||||||
|
<input type="text" id="procedureReviewSearchKeyword1" required placeholder="제목">
|
||||||
|
|
||||||
|
<div class="search_list"></div><!-- 검색내역 나오는곳 -->
|
||||||
|
</div>
|
||||||
|
<button id="btnSearchProcedureReview" class="search_btn" data-toggle="modal"
|
||||||
|
data-target=".work_closed_modal" style="transition: all 0.2s ease-in-out 0s;">조회</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right_btn_box">
|
||||||
|
<button id="btnInsertProcedureReview" class="treatmentdiet_btn">
|
||||||
|
<img src="/image/web/notice_btn_icon.svg" alt="등록">등록
|
||||||
|
</button>
|
||||||
|
<button id="btnDeleteProcedureReview" class="delete_btn">
|
||||||
|
<img src="/image/web/delete_btn_icon.svg" alt="삭제">삭제
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="procedureReviewGrid" class="table_box ag-theme-balham"></div>
|
||||||
|
|
||||||
|
<!-- 페이지게이션 -->
|
||||||
|
<div class="page_box">
|
||||||
|
<nav aria-label="Page navigation" class="navigation">
|
||||||
|
<ul class="pagination" id="procedureReviewPagination"></ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="procedureReviewSelectListForm" method="POST" target="_blank"></form>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_popup">
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_script">
|
||||||
|
<script src="/js/web/ag-grid-community-29.3.5.min.js"></script>
|
||||||
|
<script src="/js/crm/procedureReview/procedureReviewSelectList.js"></script>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,479 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
|
||||||
|
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{/web/layout/homeLayout}">
|
||||||
|
<th:block layout:fragment="layout_css">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="/css/web/ContentsBbsUpd.css">
|
||||||
|
<link rel="stylesheet" href="/css/web/grid.css?v1.1">
|
||||||
|
<style>
|
||||||
|
/* Quill 에디터 스타일 보정 */
|
||||||
|
.ql-toolbar.ql-snow {
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-radius: 5px 5px 0 0;
|
||||||
|
background: #F9FAFB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-container.ql-snow {
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 5px 5px;
|
||||||
|
min-height: 350px;
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor {
|
||||||
|
min-height: 340px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ql-editor.ql-blank::before {
|
||||||
|
color: #B5BDC4;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 페이지 스크롤 활성화 오버라이드 */
|
||||||
|
.center_box {
|
||||||
|
overflow-y: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project_wrap .content_section .hospital_wrap .center_box .content_box {
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 단일 폼 레이아웃 */
|
||||||
|
.pr-single-form {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-grid-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group input[type="text"] {
|
||||||
|
height: 38px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid #E9ECF0;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pr-single-form .form-group input[type="text"]:focus {
|
||||||
|
border-color: #3985EA;
|
||||||
|
box-shadow: 0 0 0 2px rgba(57, 133, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 내용 헤더 (라벨 + 도움말 버튼) */
|
||||||
|
.content-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-header label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-help {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #3985EA;
|
||||||
|
background: #EFF6FF;
|
||||||
|
border: 1px solid #BFDBFE;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-help:hover {
|
||||||
|
background: #DBEAFE;
|
||||||
|
border-color: #93C5FD;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 액션 버튼 */
|
||||||
|
.pr-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quill 에디터 및 미리보기 이미지 크기 제한 */
|
||||||
|
.ql-editor img,
|
||||||
|
#previewContent img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 도움말 모달 */
|
||||||
|
.help-modal-overlay {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 9999;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-overlay.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 640px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid #E5E7EB;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-header h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1F2937;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-close {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: none;
|
||||||
|
background: #F3F4F6;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #6B7280;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-close:hover {
|
||||||
|
background: #E5E7EB;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body {
|
||||||
|
padding: 24px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #4B5563;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1F2937;
|
||||||
|
margin: 20px 0 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body h4:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 8px 0 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body table th {
|
||||||
|
background: #F9FAFB;
|
||||||
|
padding: 8px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body table td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body .tip-box {
|
||||||
|
background: #F0F9FF;
|
||||||
|
border: 1px solid #BAE6FD;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-modal-body .tip-box strong {
|
||||||
|
color: #0284C7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_top_script">
|
||||||
|
<script>
|
||||||
|
let menuClass = "[[${param.menuClass}]]" == "" ? "" : "[[${param.menuClass}]]";
|
||||||
|
let selectUseYn = "[[${selectUseYn}]]" == "" ? "N" : "[[${selectUseYn}]]";
|
||||||
|
let updateUseYn = "[[${updateUseYn}]]" == "" ? "N" : "[[${updateUseYn}]]";
|
||||||
|
let muProcedureReviewId = "[[${param.muProcedureReviewId}]]";
|
||||||
|
const CDN_URL = "[[${@environment.getProperty('url.cdn')}]]";
|
||||||
|
</script>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_content">
|
||||||
|
<div class="center_box">
|
||||||
|
<p class="page_title" style="float:none; display:block; width:100%;">시술후기 수정</p>
|
||||||
|
|
||||||
|
<div class="pr-single-form">
|
||||||
|
<!-- 제목 -->
|
||||||
|
<div class="form-grid-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>제목</label>
|
||||||
|
<input type="text" id="title" placeholder="제목을 입력해주세요." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 내용 (Quill 에디터) -->
|
||||||
|
<div class="form-group" style="margin-bottom: 16px;">
|
||||||
|
<div class="content-header">
|
||||||
|
<label>내용</label>
|
||||||
|
<button type="button" class="btn-help" id="btnHelp">? 도움말</button>
|
||||||
|
</div>
|
||||||
|
<div id="quillEditor"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 해시태그 -->
|
||||||
|
<div class="form-grid-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>해시태그</label>
|
||||||
|
<input type="text" id="hashtag" placeholder="#해시태그를 입력해주세요." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 버튼 -->
|
||||||
|
<div class="pr-actions">
|
||||||
|
<button class="preview_btn btnPreview"
|
||||||
|
style="width: 90px; height: 36px; border-radius: 4px; background: #fff; color: #3985EA; border: 1px solid #3985EA; font-weight: 600; cursor: pointer;">미리보기</button>
|
||||||
|
<button class="registration_btn btnSave"
|
||||||
|
style="width: 80px; height: 36px; border-radius: 4px;">수정</button>
|
||||||
|
<button class="cancel_btn btnCancle"
|
||||||
|
style="width: 80px; height: 36px; border-radius: 4px; margin-left: 10px;">취소</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 미리보기 모달 -->
|
||||||
|
<div class="help-modal-overlay" id="previewModal">
|
||||||
|
<div class="help-modal" style="width: 800px; max-height: 90vh;">
|
||||||
|
<div class="help-modal-header">
|
||||||
|
<h3>👁 미리보기</h3>
|
||||||
|
<button class="help-modal-close" id="previewModalClose">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="help-modal-body" style="padding: 0;">
|
||||||
|
<!-- 제목 영역 -->
|
||||||
|
<div style="padding: 20px 24px; border-bottom: 1px solid #E5E7EB;">
|
||||||
|
<p style="font-size: 11px; color: #9CA3AF; margin-bottom: 4px;">제목</p>
|
||||||
|
<h2 id="previewTitle" style="font-size: 20px; font-weight: 700; color: #1F2937; margin: 0;"></h2>
|
||||||
|
</div>
|
||||||
|
<!-- 내용 영역 -->
|
||||||
|
<div style="padding: 24px;">
|
||||||
|
<div id="previewContent" class="ql-editor"
|
||||||
|
style="min-height: 200px; max-height: 500px; overflow-y: auto; padding: 0;"></div>
|
||||||
|
</div>
|
||||||
|
<!-- 해시태그 영역 -->
|
||||||
|
<div id="previewHashtagArea" style="padding: 12px 24px 20px; border-top: 1px solid #E5E7EB;">
|
||||||
|
<span id="previewHashtag"
|
||||||
|
style="display: inline-block; padding: 4px 12px; background: #EFF6FF; color: #3985EA; border-radius: 20px; font-size: 13px; font-weight: 500;"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 도움말 모달 -->
|
||||||
|
<div class="help-modal-overlay" id="helpModal">
|
||||||
|
<div class="help-modal">
|
||||||
|
<div class="help-modal-header">
|
||||||
|
<h3>📝 에디터 사용 가이드</h3>
|
||||||
|
<button class="help-modal-close" id="helpModalClose">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="help-modal-body">
|
||||||
|
<h4>📌 텍스트 서식</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
<th>단축키</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>B</strong> 굵게</td>
|
||||||
|
<td>선택한 텍스트를 굵게 표시</td>
|
||||||
|
<td>Ctrl+B</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><em>I</em> 기울임</td>
|
||||||
|
<td>선택한 텍스트를 기울임꼴로 표시</td>
|
||||||
|
<td>Ctrl+I</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><u>U</u> 밑줄</td>
|
||||||
|
<td>선택한 텍스트에 밑줄 적용</td>
|
||||||
|
<td>Ctrl+U</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><s>S</s> 취소선</td>
|
||||||
|
<td>선택한 텍스트에 취소선 적용</td>
|
||||||
|
<td>-</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>🎨 글자 스타일</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>헤더 (H1~H6)</td>
|
||||||
|
<td>제목 크기를 설정합니다. 숫자가 작을수록 큰 제목</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>폰트</td>
|
||||||
|
<td>글꼴을 변경합니다</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>글자 크기</td>
|
||||||
|
<td>Small, Normal, Large, Huge 중 선택</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>글자색 / 배경색</td>
|
||||||
|
<td>텍스트 색상 또는 배경 하이라이트 색상 선택</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>📋 문단 서식</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>순서 목록</td>
|
||||||
|
<td>1. 2. 3. 형태의 번호 목록</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>비순서 목록</td>
|
||||||
|
<td>• 형태의 글머리 기호 목록</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>체크 목록</td>
|
||||||
|
<td>☐ 형태의 체크박스 목록</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>들여쓰기</td>
|
||||||
|
<td>텍스트를 안쪽/바깥쪽으로 이동</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>정렬</td>
|
||||||
|
<td>좌측, 중앙, 우측, 양쪽 정렬</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>인용문</td>
|
||||||
|
<td>인용 블록으로 표시</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>코드 블록</td>
|
||||||
|
<td>코드 형태로 표시</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>🔗 멀티미디어</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>링크</td>
|
||||||
|
<td>선택한 텍스트에 URL 링크 삽입</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>이미지</td>
|
||||||
|
<td>이미지 파일을 선택하여 삽입 (서버에 자동 업로드)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>동영상</td>
|
||||||
|
<td>YouTube 등 동영상 URL을 삽입</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h4>🧹 기타</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>기능</th>
|
||||||
|
<th>설명</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>서식 지우기</td>
|
||||||
|
<td>선택한 텍스트의 모든 서식을 초기화</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>위첨자 / 아래첨자</td>
|
||||||
|
<td>X² 또는 H₂O 같은 수식 표현</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="tip-box">
|
||||||
|
<strong>💡 TIP</strong><br>
|
||||||
|
• 텍스트를 드래그한 후 툴바 버튼을 클릭하면 선택 영역에만 서식이 적용됩니다.<br>
|
||||||
|
• 이미지는 클립보드에서 Ctrl+V로 붙여넣기도 가능합니다.<br>
|
||||||
|
• 실행 취소는 Ctrl+Z, 다시 실행은 Ctrl+Y 입니다.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_popup">
|
||||||
|
</th:block>
|
||||||
|
<th:block layout:fragment="layout_script">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
|
||||||
|
<script src="/js/crm/procedureReview/procedureReviewUpdate.js"></script>
|
||||||
|
</th:block>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user