Compare commits

...

16 Commits

Author SHA1 Message Date
pjs
5e53f19f2a 2026-03-13 장비 슬라이드 변경 2026-03-14 12:25:59 +09:00
pjs
f73751497a 이벤트 예약기간 설정 2026-03-02 12:19:19 +09:00
pjs
d590a3ad48 url변경 2026-03-01 16:38:22 +09:00
pjs
8e61cfa559 고객후기 구현 2026-03-01 16:35:35 +09:00
pjs
e31ff954e3 docs: Add Walkthrough.md summarizing project setup and SEO updates 2026-02-21 10:43:04 +09:00
pjs
761092c2d7 시술/이벤트 소팅 변경 2026-02-19 22:22:20 +09:00
pjs
145e0df564 feat: Implement web event listing and detail selection functionality with dedicated CSS, JS, HTML, and SQL mapper files. 2026-02-16 01:47:26 +09:00
pjs
f4545ce91a feat: Implement reservation functionality with new UI, backend services, and data access layers. 2026-02-16 01:25:07 +09:00
pjs
3df9096eec 메타 og추가 2026-02-03 12:13:59 +09:00
pjs
b1a427b435 sitemap.xml 생성 2026-01-19 19:14:54 +09:00
pjs
4a764b8295 인스타그램 키 및 url변경 2026-01-19 18:57:38 +09:00
pjs
c0d026f896 index로 변경 2026-01-19 18:48:39 +09:00
pjs
84f062c681 진료시간변경 2025-12-22 22:48:48 +09:00
pjs
7f4aa950be 휴일설정 2025-12-17 21:44:08 +09:00
pjs
029c45a193 휴일설정 2025-12-17 21:42:55 +09:00
pjs
462d8a15de 휴일설정 2025-12-17 21:40:39 +09:00
52 changed files with 6079 additions and 7027 deletions

1
.wiki Submodule

Submodule .wiki added at 9ad4481fd7

10
Walkthrough.md Normal file
View File

@@ -0,0 +1,10 @@
# MadeU Petit Home - 프로젝트 Walkthrough
최근 개발 환경 및 시스템에 적용된 주요 변경 사항과 기능 구현 내역을 요약한 문서입니다.
## 1. 프로젝트 초기 구성
- **저장소 클론 및 환경 설정**: Git 리포지토리(`madeu_petit_home`) 초기 클론 작업 및 프론트엔드/백엔드 기본 환경 설정을 수행했습니다.
## 2. 검색 엔진 최적화(SEO) 반영
- **Sitemap 파일 구축**: 구글, 네이버 등 주요 검색 엔진의 크롤링 봇이 웹사이트 내부의 페이지들을 효과적으로 수집하고 노출할 수 있도록 프로젝트 내에 `sitemap.xml` 파일을 생성하여 라우팅 지도를 구축했습니다.
- **Open Graph (OG) 태그 적용**: 다이어트 홈 페이지 프로젝트와 동일하게 소셜 미디어 링크 공유 시 올바른 썸네일과 문구가 제공되도록 메타 태그(`og` 속성) 생성을 지원했습니다.

94
rules.md Normal file
View File

@@ -0,0 +1,94 @@
# 프로젝트 코딩 가이드라인 (Java Backend)
AI 에디터(Agent)는 다음 규칙을 항상 준수하여 코드를 작성하고 수정해야 합니다.
## 0. 기본 소통 규칙 (Communication)
- **언어**: 사용자에 대한 모든 답변과 코드 설명은 항상 **한글(Korean)**로만 작성해야 합니다.
## 1. 패키지 구성 (Package Structure)
- **베이스 패키지**: `com.madeuhome`
- **컨트롤러 (Controller)**: `ctrl`
- **서비스 (Service)**: `svc`
- **DTO (Data Transfer Object)**: `dto`
- **매퍼 (Mapper)**: `mapper`
- **공통 서비스**: `com.madeuhome.common.service` (LogHistoryService 등 시스템 공통 모듈)
## 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`)
- **XML Mapper**: `[도메인명]SqlMap.xml` (namespace는 Mapper 인터페이스의 **FQCN**과 반드시 일치)
## 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) 서비스 메소드명
- 서비스 메소드명은 **컨트롤러 메소드명과 동일**하게 명명합니다.
- 조회: `getXXXX` / 목록 조회: `getXXXXList`
- 저장: `putXXXX`
- 수정: `modXXXX`
- 삭제: `delXXXX`
### 3) Mapper 인터페이스 메소드명
- 단일조회 : `selectXXXX`
- 리스트조회 : `selectListXXXX`
- insert : `insertXXXX`
- update : `updateXXXX`
- delete : `deleteXXXX`
## 4. 데이터베이스 연동 정보 (DB Connection)
- `application-local.yml`의 설정을 기반으로 한 공통 접속 정보입니다.
- **Host**: 183.98.184.84
- **Port**: 3306
- **Database**: madeu
- **User**: madeu
- **Password**: apdlemdb12#$
## 5. 아키텍처 및 코딩 원칙 (Architecture & Coding Principles)
### 5-1. 컨트롤러 원칙 (Skinny Controller)
- 컨트롤러에는 비즈니스 로직이나 예외처리 로직을 넣지 않고, **서비스 메서드를 호출하는 1줄로만 작성**합니다.
- 컨트롤러 클래스는 `@Controller` 대신 **`@RestController`**를 사용하며, `@ResponseBody`는 생략합니다.
- 데이터 입출력 메소드의 파라미터는 **단일 DTO 하나만 `@RequestBody`**로 받습니다.
- 파일 업로드가 포함된 경우에만 `@ModelAttribute` + `@RequestParam MultipartFile`을 허용합니다.
- 화면 이동(`move~`) 메소드는 **`ModelAndView`를 리턴**합니다. (`@RestController`에서 String 리턴 시 뷰 이름이 아닌 응답 바디로 해석되므로)
- 화면 이동 메소드의 뷰 경로는 **컨트롤러에서 직접 명시**합니다. (서비스에 위임 금지)
### 5-2. 서비스 원칙 (Service Layer)
- 에러 처리(try-catch), 응답 메시지(msgCode, msgDesc) 설정은 **서비스 계층에서 전담**합니다.
- `HttpServletRequest`, `HttpSession``@Autowired`로 직접 주입받아 사용합니다. (Spring이 Request-scope 프록시로 제공)
- `session.getAttribute("loginMemberId")`를 통해 로그인 ID를 가져오고, DTO에 설정합니다.
### 5-3. DTO 원칙 (DTO Communication)
- **HashMap 사용 금지**. 컨트롤러 ↔ 서비스 ↔ 매퍼 간 모든 데이터는 **DTO 객체만 사용**합니다.
- DTO에는 `@Data` (Lombok)을 사용합니다.
- DTO 필드 구성:
- **DB 컬럼 매핑 필드**: `muProcedureReviewId`, `title`, `content`
- **조회 결과 전용 필드**: `rowNum`, `writeDate`, `writeName`
- **검색/UI 변수**: `startDate`, `endDate`, `start`, `limit`, `sort`, `dir`
- **응답 매핑 변수**: `msgCode`, `msgDesc`, `success`, `totalCount`, `rows`(Object 타입), `tId`
### 5-4. Mapper 원칙 (MyBatis Mapper)
- Mapper는 **`@Mapper` 어노테이션을 사용한 인터페이스**로 작성합니다. (`SqlSessionDaoSupport` 상속 금지)
- XML Mapper의 `namespace`는 Mapper 인터페이스의 **FQCN(Fully Qualified Class Name)**과 일치시킵니다.
- XML의 `resultType``hashmap` 대신 **DTO FQCN**을 사용합니다.
- 예외: 도메인 외부 테이블 조회(카테고리 등)는 `hashmap` 허용
- XML alias는 **DTO 필드명(camelCase)**과 정확히 일치시킵니다.
- 단건 조회는 `List` 대신 **DTO 단일 객체를 리턴**합니다.
## 6. 파일 업로드 규칙 (File Upload)
- `MultipartFile.transferTo()` 사용 시 반드시 **절대경로를 명시**합니다:
```java
File dest = new File(outDir, savedName);
file.transferTo(dest.toPath().toAbsolutePath());
```
- 상대경로 사용 시 Tomcat 임시 디렉토리 기준으로 해석되어 오류가 발생합니다.

View File

@@ -1,6 +1,5 @@
package com.madeuhome.common.ctrl;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,12 +23,13 @@ public class MenuController {
String requestURI = request.getRequestURI();
// 특정 URL 패턴에만 메뉴 추가
if (requestURI.endsWith("Intro.do") || requestURI.startsWith("/index") ) {
return menuService.getMenuHierarchy("MAIN");
}
/*
* if (requestURI.endsWith("Intro.do") || requestURI.startsWith("/index") ) {
* return menuService.getMenuHierarchy("MAIN"); }
*/
return new ArrayList<>();
return menuService.getMenuHierarchy("MAIN");
}
}

View File

@@ -0,0 +1,209 @@
package com.madeuhome.controller.web.reservation;
import java.util.HashMap;
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.servlet.ModelAndView;
import com.madeuhome.constants.Constants;
import com.madeuhome.service.web.reservation.ReservationService;
import com.madeuhome.util.HttpUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
/**
* 예약 컨트롤러
*/
@Controller
@Slf4j
public class ReservationController {
@Autowired
private ReservationService reservationService;
/**
* 예약 페이지 이동
*/
@RequestMapping(value = "/webservice/selectMakeReservation.do")
public String selectMakeReservation(HttpSession session, HttpServletRequest request, Model model) {
log.debug("ReservationController selectMakeReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
model.addAttribute("CATEGORY_DIV_CD", paramMap.get("CATEGORY_DIV_CD"));
model.addAttribute("CATEGORY_NO", paramMap.get("CATEGORY_NO"));
model.addAttribute("POST_NO", paramMap.get("POST_NO"));
model.addAttribute("PROCEDURE_ID", paramMap.get("PROCEDURE_ID"));
log.debug("ReservationController selectMakeReservation END");
return "/web/service/makeReservation";
}
/**
* 예약 시술 정보 조회
*/
@RequestMapping(value = "/webservice/selectReservation.do")
public ModelAndView selectReservation(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("ReservationController selectReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
StringBuffer errorMsg = new StringBuffer();
try {
map = reservationService.selectReservation(paramMap);
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
} finally {
if (Constants.OK == map.get("msgCode")) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
}
}
}
log.debug("ReservationController selectReservation END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 병원 휴일 조회 (달력 표출용)
*/
@RequestMapping(value = "/webservice/selectHospitalHolidayList.do")
public ModelAndView selectHospitalHolidayList(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("ReservationController selectHospitalHolidayList START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
try {
map = reservationService.selectHospitalHolidayList(paramMap);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (Constants.OK != map.get("msgCode")) {
map.put("msgCode", Constants.FAIL);
map.put("success", false);
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
}
}
}
log.debug("ReservationController selectHospitalHolidayList END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 특정 날짜 운영시간 조회
*/
@RequestMapping(value = "/webservice/selectHospitalWorkTime.do")
public ModelAndView selectHospitalWorkTime(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("ReservationController selectHospitalWorkTime START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
try {
map = reservationService.selectHospitalWorkTime(paramMap);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (Constants.OK != map.get("msgCode")) {
map.put("msgCode", Constants.FAIL);
map.put("success", false);
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
}
}
}
log.debug("ReservationController selectHospitalWorkTime END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 예약 카운트 조회
*/
@RequestMapping(value = "/webservice/selectReservationCnt.do")
public ModelAndView selectReservationCnt(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("ReservationController selectReservationCnt START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
StringBuffer errorMsg = new StringBuffer();
try {
map = reservationService.selectReservationCnt(paramMap);
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
} finally {
if (Constants.OK == map.get("msgCode")) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
}
}
}
log.debug("ReservationController selectReservationCnt END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 예약 저장
*/
@RequestMapping(value = "/webservice/insertReservation.do")
public ModelAndView insertReservation(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("ReservationController insertReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<>();
StringBuffer errorMsg = new StringBuffer();
try {
map = reservationService.insertReservation(paramMap);
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
} finally {
if (Constants.OK == map.get("msgCode")) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다.");
}
}
}
log.debug("ReservationController insertReservation END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
}

View File

@@ -18,10 +18,9 @@ import org.springframework.web.servlet.ModelAndView;
import java.util.HashMap;
@Slf4j
@Controller
public class WebEventController extends ManagerDraftAction{
public class WebEventController extends ManagerDraftAction {
@Autowired
private WebEventService webEventService;
@@ -36,8 +35,8 @@ public class WebEventController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webevent/selectListWebEventIntro.do")
public String selectListWebEventIntro(HttpSession session,HttpServletRequest request) {
@RequestMapping(value = "/webevent/selectListWebEventIntro.do")
public String selectListWebEventIntro(HttpSession session, HttpServletRequest request) {
log.debug("WebEventController selectListWebEventIntro START");
@@ -52,8 +51,9 @@ public class WebEventController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webevent/selectListWebEvent.do")
public ModelAndView selectListWebEvent(HttpSession session,HttpServletRequest request, HttpServletResponse response) {
@RequestMapping(value = "/webevent/selectListWebEvent.do")
public ModelAndView selectListWebEvent(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("WebEventController selectListWebEvent START");
@@ -61,24 +61,24 @@ public class WebEventController extends ManagerDraftAction{
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
try {
map = webEventService.selectListWebEvent(paramMap);
log.debug(map + "");
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
} finally {
if (Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
@@ -94,16 +94,16 @@ public class WebEventController extends ManagerDraftAction{
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
if (("true").equals(String.valueOf(map.get("success")))) {
insertMap.put("resultCode", "SUCCESS");
}else{
} else {
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
insertMap.put("muMemberId", paramMap.get("muMemberId"));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
@@ -111,7 +111,6 @@ public class WebEventController extends ManagerDraftAction{
log.debug("WebEventController selectListWebEvent END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
@@ -122,8 +121,8 @@ public class WebEventController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webevent/selectListEvent.do")
public ModelAndView selectListEvent(HttpSession session,HttpServletRequest request, HttpServletResponse response) {
@RequestMapping(value = "/webevent/selectListEvent.do")
public ModelAndView selectListEvent(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
log.debug("WebEventController selectListEvent START");
@@ -131,24 +130,24 @@ public class WebEventController extends ManagerDraftAction{
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
try {
map = webEventService.selectListEvent(paramMap);
log.debug(map + "");
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
} finally {
if (Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
@@ -164,15 +163,15 @@ public class WebEventController extends ManagerDraftAction{
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
if (("true").equals(String.valueOf(map.get("success")))) {
insertMap.put("resultCode", "SUCCESS");
}else{
} else {
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
@@ -180,7 +179,6 @@ public class WebEventController extends ManagerDraftAction{
log.debug("WebEventController selectListEvent END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
@@ -191,7 +189,7 @@ public class WebEventController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webevent/selectEventDetailIntro.do")
@RequestMapping(value = "/webevent/selectEventDetailIntro.do")
public String selectEventDetailIntro(HttpSession session, HttpServletRequest request, Model model) {
log.debug("WebEventController selectEventDetailIntro START");
@@ -211,8 +209,9 @@ public class WebEventController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webevent/selectEventDetail.do")
public ModelAndView selectEventDetail(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
@RequestMapping(value = "/webevent/selectEventDetail.do")
public ModelAndView selectEventDetail(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("WebEventController selectEventDetail START");
@@ -220,24 +219,24 @@ public class WebEventController extends ManagerDraftAction{
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
try {
map = webEventService.selectEventDetail(paramMap);
log.debug(map + "");
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
} finally {
if (Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
@@ -253,15 +252,15 @@ public class WebEventController extends ManagerDraftAction{
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
if (("true").equals(String.valueOf(map.get("success")))) {
insertMap.put("resultCode", "SUCCESS");
}else{
} else {
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
@@ -269,11 +268,10 @@ public class WebEventController extends ManagerDraftAction{
log.debug("WebEventController selectEventDetail END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
@RequestMapping(value="/webevent/selectMakeReservation.do")
@RequestMapping(value = "/webevent/selectMakeReservation.do")
public String selectMakeReservation(HttpSession session, HttpServletRequest request, Model model) {
log.debug("WebServiceController selectMakeReservation START");
@@ -282,214 +280,11 @@ public class WebEventController extends ManagerDraftAction{
model.addAttribute("CATEGORY_NO", paramMap.get("CATEGORY_NO"));
model.addAttribute("POST_NO", paramMap.get("POST_NO"));
model.addAttribute("PROCEDURE_ID", paramMap.get("PROCEDURE_ID"));
model.addAttribute("EVENT_START_DT", paramMap.get("EVENT_START_DT"));
model.addAttribute("EVENT_END_DT", paramMap.get("EVENT_END_DT"));
log.debug("WebServiceController selectMakeReservation END");
return "/web/webevent/makeReservation";
}
/**
* 시술 목록 상세 조회
*
* @param request
* @param response
* @return
*/
@RequestMapping(value="/webevent/selectReservation.do")
public ModelAndView selectReservation(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
log.debug("WebServiceController selectReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
map = webEventService.selectReservation(paramMap);
}catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
try {
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
HashMap<String, Object> insertMap = new HashMap<String, Object>();
insertMap.put("url", "/webevent/selectReservation.do");
insertMap.put("func", "selectListService");
insertMap.put("funcName", "예약 조회");
insertMap.put("service", "webEventService");
insertMap.put("serviceName", "예약 상세");
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
insertMap.put("resultCode", "SUCCESS");
}else{
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("WebServiceController selectReservation END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 시술 목록 상세 조회
*
* @param request
* @param response
* @return
*/
@RequestMapping(value="/webevent/selectReservationCnt.do")
public ModelAndView selectReservationCnt(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
log.debug("WebServiceController selectReservationCnt START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
map = webEventService.selectReservationCnt(paramMap);
}catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
try {
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
HashMap<String, Object> insertMap = new HashMap<String, Object>();
insertMap.put("url", "/webevent/selectReservationCnt.do");
insertMap.put("func", "selectReservationCnt");
insertMap.put("funcName", "예약 조회");
insertMap.put("service", "webEventService");
insertMap.put("serviceName", "예약 상세");
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
insertMap.put("resultCode", "SUCCESS");
}else{
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("WebServiceController selectReservationCnt END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 예약 저장
*
* @param request
* @param response
* @return
*/
@RequestMapping(value="/webevent/insertReservation.do")
public ModelAndView insertReservation(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
log.debug("WebServiceController insertReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
map = webEventService.insertReservation(paramMap);
log.debug(map + "TEST");
}catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
try {
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
HashMap<String, Object> insertMap = new HashMap<String, Object>();
insertMap.put("url", "/webevent/insertReservation.do");
insertMap.put("func", "selectReservationCnt");
insertMap.put("funcName", "예약 저장");
insertMap.put("service", "webEventService");
insertMap.put("serviceName", "예약 저장");
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
insertMap.put("resultCode", "SUCCESS");
}else{
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("WebServiceController insertReservation END");
return HttpUtil.makeHashToJsonModelAndView(map);
return "/web/service/makeReservation";
}
}

View File

@@ -5,7 +5,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.GetMapping;
@Slf4j
@@ -19,16 +19,16 @@ public class WebHomeController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/")
@GetMapping("/")
public String homeIntro(HttpSession session,HttpServletRequest request) {
log.debug("WebHomeController homeIntro START");
log.debug("WebHomeController homeIntro END");
return "/intro";
return "/index";
}
@RequestMapping(value="/index")
@GetMapping("/index")
public String homeIndex(HttpSession session,HttpServletRequest request) {
log.debug("WebHomeController homeIndex START");

View File

@@ -0,0 +1,95 @@
package com.madeuhome.controller.web.webreview;
import com.madeuhome.constants.Constants;
import com.madeuhome.init.ManagerDraftAction;
import com.madeuhome.service.web.webreview.WebReviewService;
import com.madeuhome.util.HttpUtil;
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.servlet.ModelAndView;
import java.util.HashMap;
@Slf4j
@Controller
public class WebReviewController extends ManagerDraftAction {
@Autowired
private WebReviewService webReviewService;
@RequestMapping(value = "/webreview/selectListProcedureReviewIntro.do")
public String selectListProcedureReviewIntro(HttpSession session, HttpServletRequest request) {
log.debug("WebReviewController selectListProcedureReviewIntro START");
log.debug("WebReviewController selectListProcedureReviewIntro END");
return "/web/webreview/procedureReviewSelectList";
}
@RequestMapping(value = "/webreview/selectListProcedureReview.do")
public ModelAndView selectListProcedureReview(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("WebReviewController selectListProcedureReview START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
try {
map = webReviewService.selectListProcedureReview(paramMap);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (Constants.OK == map.get("msgCode")) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다.");
}
}
}
log.debug("WebReviewController selectListProcedureReview END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
@RequestMapping(value = "/webreview/selectProcedureReviewIntro.do")
public String selectProcedureReviewIntro(HttpSession session, HttpServletRequest request, Model model) {
log.debug("WebReviewController selectProcedureReviewIntro START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
model.addAttribute("muProcedureReviewId", paramMap.get("muProcedureReviewId"));
log.debug("WebReviewController selectProcedureReviewIntro END");
return "/web/webreview/procedureReviewSelect";
}
@RequestMapping(value = "/webreview/selectProcedureReview.do")
public ModelAndView selectProcedureReview(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("WebReviewController selectProcedureReview START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
try {
map = webReviewService.selectProcedureReview(paramMap);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (Constants.OK == map.get("msgCode")) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다.");
}
}
}
log.debug("WebReviewController selectProcedureReview END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
}

View File

@@ -25,10 +25,9 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class WebServiceController extends ManagerDraftAction{
public class WebServiceController extends ManagerDraftAction {
@Autowired
private WebServiceService webServiceService;
@@ -39,8 +38,8 @@ public class WebServiceController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webservice/selectServiceIntro.do")
public String selectListServiceIntro(HttpSession session,HttpServletRequest request) {
@RequestMapping(value = "/webservice/selectServiceIntro.do")
public String selectListServiceIntro(HttpSession session, HttpServletRequest request) {
log.debug("WebServiceController selectListServiceIntro START");
@@ -56,8 +55,9 @@ public class WebServiceController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webservice/selectListCategory.do")
public ModelAndView selectListCategory(HttpSession session,HttpServletRequest request, HttpServletResponse response) {
@RequestMapping(value = "/webservice/selectListCategory.do")
public ModelAndView selectListCategory(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("WebServiceController selectListCategory START");
@@ -65,24 +65,24 @@ public class WebServiceController extends ManagerDraftAction{
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
try {
map = webServiceService.selectListWebCategory(paramMap);
log.debug(map + "");
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
} finally {
if (Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
@@ -98,16 +98,16 @@ public class WebServiceController extends ManagerDraftAction{
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
if (("true").equals(String.valueOf(map.get("success")))) {
insertMap.put("resultCode", "SUCCESS");
}else{
} else {
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
insertMap.put("muMemberId", paramMap.get("muMemberId"));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
@@ -115,7 +115,6 @@ public class WebServiceController extends ManagerDraftAction{
log.debug("WebServiceController selectListCategory END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
@@ -126,26 +125,26 @@ public class WebServiceController extends ManagerDraftAction{
* @param response
* @return
*/
@PostMapping(value="/webservice/selectListService.do")
@PostMapping(value = "/webservice/selectListService.do")
@ResponseBody
public ResponseEntity<Map<String, Object>> selectListService(@RequestParam("categoryNo") String categoryNo) {
Map<String, Object> response = new HashMap<>();
Map<String, Object> response = new HashMap<>();
try {
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("categoryNo", categoryNo);
try {
HashMap<String, Object> paramMap = new HashMap<>();
paramMap.put("categoryNo", categoryNo);
Map<String, Object> result = webServiceService.selectListWebService(paramMap);
return ResponseEntity.ok(result);
Map<String, Object> result = webServiceService.selectListWebService(paramMap);
return ResponseEntity.ok(result);
} catch (Exception e) {
log.error("서비스 조회 실패", e);
response.put("msgCode", Constants.FAIL);
response.put("msgDesc", e.getMessage());
response.put("rows", new ArrayList<>());
return ResponseEntity.ok(response);
}
} catch (Exception e) {
log.error("서비스 조회 실패", e);
response.put("msgCode", Constants.FAIL);
response.put("msgDesc", e.getMessage());
response.put("rows", new ArrayList<>());
return ResponseEntity.ok(response);
}
}
/**
@@ -155,7 +154,7 @@ public class WebServiceController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webservice/selectServiceDetailIntro.do")
@RequestMapping(value = "/webservice/selectServiceDetailIntro.do")
public String selectServiceDetailIntro(HttpSession session, HttpServletRequest request, Model model) {
log.debug("WebServiceController selectServiceDetailIntro START");
@@ -175,8 +174,9 @@ public class WebServiceController extends ManagerDraftAction{
* @param response
* @return
*/
@RequestMapping(value="/webservice/selectServiceDetail.do")
public ModelAndView selectServiceDetail(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
@RequestMapping(value = "/webservice/selectServiceDetail.do")
public ModelAndView selectServiceDetail(HttpSession session, HttpServletRequest request,
HttpServletResponse response) {
log.debug("WebServiceController selectServiceDetail START");
@@ -184,23 +184,23 @@ public class WebServiceController extends ManagerDraftAction{
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
try {
map = webServiceService.selectServiceDetail(paramMap);
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
} finally {
if (Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
} else {
if (null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
if (null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc", "정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
@@ -216,15 +216,15 @@ public class WebServiceController extends ManagerDraftAction{
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
if (("true").equals(String.valueOf(map.get("success")))) {
insertMap.put("resultCode", "SUCCESS");
}else{
} else {
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
@@ -232,227 +232,6 @@ public class WebServiceController extends ManagerDraftAction{
log.debug("WebServiceController selectServiceDetail END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
@RequestMapping(value="/webservice/selectMakeReservation.do")
public String selectMakeReservation(HttpSession session, HttpServletRequest request, Model model) {
log.debug("WebServiceController selectMakeReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
model.addAttribute("CATEGORY_DIV_CD", paramMap.get("CATEGORY_DIV_CD"));
model.addAttribute("CATEGORY_NO", paramMap.get("CATEGORY_NO"));
model.addAttribute("POST_NO", paramMap.get("POST_NO"));
model.addAttribute("PROCEDURE_ID", paramMap.get("PROCEDURE_ID"));
log.debug("WebServiceController selectMakeReservation END");
return "/web/service/makeReservation";
}
/**
* 시술 목록 상세 조회
*
* @param request
* @param response
* @return
*/
@RequestMapping(value="/webservice/selectReservation.do")
public ModelAndView selectReservation(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
log.debug("WebServiceController selectReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
map = webServiceService.selectReservation(paramMap);
}catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
try {
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
HashMap<String, Object> insertMap = new HashMap<String, Object>();
insertMap.put("url", "/webservice/selectReservation.do");
insertMap.put("func", "selectListService");
insertMap.put("funcName", "예약 조회");
insertMap.put("service", "webServiceService");
insertMap.put("serviceName", "예약 상세");
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
insertMap.put("resultCode", "SUCCESS");
}else{
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("WebServiceController selectReservation END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 시술 목록 상세 조회
*
* @param request
* @param response
* @return
*/
@RequestMapping(value="/webservice/selectReservationCnt.do")
public ModelAndView selectReservationCnt(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
log.debug("WebServiceController selectReservationCnt START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
map = webServiceService.selectReservationCnt(paramMap);
}catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
try {
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
HashMap<String, Object> insertMap = new HashMap<String, Object>();
insertMap.put("url", "/webservice/selectReservationCnt.do");
insertMap.put("func", "selectReservationCnt");
insertMap.put("funcName", "예약 조회");
insertMap.put("service", "webServiceService");
insertMap.put("serviceName", "예약 상세");
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
insertMap.put("resultCode", "SUCCESS");
}else{
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("WebServiceController selectReservationCnt END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
/**
* 예약 저장
*
* @param request
* @param response
* @return
*/
@RequestMapping(value="/webservice/insertReservation.do")
public ModelAndView insertReservation(HttpSession session, HttpServletRequest request, HttpServletResponse response) {
log.debug("WebServiceController insertReservation START");
HashMap<String, Object> paramMap = HttpUtil.getParameterMap(request);
HashMap<String, Object> map = new HashMap<String, Object>();
StringBuffer errorMsg = new StringBuffer();
try{
map = webServiceService.insertReservation(paramMap);
log.debug(map + "TEST");
}catch (Exception e) {
e.printStackTrace();
errorMsg.append(e);
return null;
}finally {
if(Constants.OK == map.get("msgCode")) {
}else{
if(null == map.get("msgCode") || ("").equals(map.get("msgCode"))) {
map.put("msgCode", Constants.FAIL);
}
map.put("success", false);
if(null == map.get("msgDesc") || ("").equals(map.get("msgDesc"))) {
map.put("msgDesc","정상적으로 수행되지 않았습니다. 관리자에게 문의하시기 바랍니다. (E0029)");
}
}
try {
HashMap<String, Object> visitLogParamMap = RequestLogUtil.getVisitLogParameterMap(request);
HashMap<String, Object> insertMap = new HashMap<String, Object>();
insertMap.put("url", "/webservice/insertReservation.do");
insertMap.put("func", "selectReservationCnt");
insertMap.put("funcName", "예약 저장");
insertMap.put("service", "webServiceService");
insertMap.put("serviceName", "예약 저장");
insertMap.put("requestValue", String.valueOf(paramMap));
insertMap.put("responseValue", String.valueOf(map));
;
if(("true").equals(String.valueOf(map.get("success")))){
insertMap.put("resultCode", "SUCCESS");
}else{
insertMap.put("resultCode", "ERROR");
}
insertMap.put("resultMsg", String.valueOf(errorMsg));
// logHistoryService.insertLogHistory(insertMap, visitLogParamMap);
} catch (Exception e) {
e.printStackTrace();
}
}
log.debug("WebServiceController insertReservation END");
return HttpUtil.makeHashToJsonModelAndView(map);
}
}

View File

@@ -0,0 +1,42 @@
package com.madeuhome.dao.web.webreview;
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 WebReviewSqlMapDAO extends SqlSessionDaoSupport {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
@PostConstruct
void init() {
setSqlSessionTemplate(sqlSessionTemplate);
}
public Map<String, Object> selectTotalProcedureReviewCount(HashMap<String, Object> paramMap)
throws DataAccessException {
return getSqlSession().selectOne("WebReview.selectTotalProcedureReviewCount", paramMap);
}
public List<Map<String, Object>> selectListProcedureReview(HashMap<String, Object> paramMap)
throws DataAccessException {
return getSqlSession().selectList("WebReview.selectListProcedureReview", paramMap);
}
public Map<String, Object> selectProcedureReview(HashMap<String, Object> paramMap) throws DataAccessException {
return getSqlSession().selectOne("WebReview.selectProcedureReview", paramMap);
}
public void updateViewCount(HashMap<String, Object> paramMap) throws DataAccessException {
getSqlSession().update("WebReview.updateViewCount", paramMap);
}
}

View File

@@ -13,7 +13,7 @@ import org.springframework.stereotype.Repository;
import jakarta.annotation.PostConstruct;
@Repository
public class WebServiceSqlMapDAO extends SqlSessionDaoSupport{
public class WebServiceSqlMapDAO extends SqlSessionDaoSupport {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
@@ -30,7 +30,8 @@ public class WebServiceSqlMapDAO extends SqlSessionDaoSupport{
* @return
* @throws DataAccessException
*/
public List<Map<String, Object>> selectListWebCategory(HashMap<String, Object> paramMap) throws DataAccessException {
public List<Map<String, Object>> selectListWebCategory(HashMap<String, Object> paramMap)
throws DataAccessException {
logger.debug("WebServiceSqlMapDAO selectListWebCategory START");
String sqlId = "WebService.selectListWebCategory";
@@ -47,7 +48,7 @@ public class WebServiceSqlMapDAO extends SqlSessionDaoSupport{
* @return
* @throws DataAccessException
*/
public List<Map<String, Object>> selectListWebService(HashMap<String, Object> paramMap) throws DataAccessException {
public List<Map<String, Object>> selectListWebService(HashMap<String, Object> paramMap) throws DataAccessException {
logger.debug("WebServiceSqlMapDAO selectListWebService START");
String sqlId = "WebService.selectListWebService";
@@ -64,7 +65,7 @@ public class WebServiceSqlMapDAO extends SqlSessionDaoSupport{
* @return
* @throws DataAccessException
*/
public Map<String, Object> selectServiceDetail(HashMap<String, Object> paramMap) throws DataAccessException {
public Map<String, Object> selectServiceDetail(HashMap<String, Object> paramMap) throws DataAccessException {
logger.debug("WebServiceSqlMapDAO selectServiceDetail START");
String sqlId = "WebService.selectServiceDetail";
@@ -81,7 +82,7 @@ public class WebServiceSqlMapDAO extends SqlSessionDaoSupport{
* @return
* @throws DataAccessException
*/
public List<Map<String, Object>> selectListService(HashMap<String, Object> paramMap) throws DataAccessException {
public List<Map<String, Object>> selectListService(HashMap<String, Object> paramMap) throws DataAccessException {
logger.debug("WebServiceSqlMapDAO selectListService START");
String sqlId = "WebService.selectListService";
@@ -90,56 +91,4 @@ public class WebServiceSqlMapDAO extends SqlSessionDaoSupport{
return getSqlSession().selectList(sqlId, paramMap);
}
/**
* 예약 상세 조회
*
* @param Map
* @return
* @throws DataAccessException
*/
public Map<String, Object> selectReservationCnt(HashMap<String, Object> paramMap) throws DataAccessException {
logger.debug("WebServiceSqlMapDAO selectReservationCnt START");
String sqlId = "WebService.selectReservationCnt";
logger.debug("WebServiceSqlMapDAO selectReservationCnt END");
return getSqlSession().selectOne(sqlId, paramMap);
}
/**
* 예약 시술 정보 조회
*
* @param Map
* @return
* @throws DataAccessException
*/
public Map<String, Object> selectReservationService(HashMap<String, Object> paramMap) throws DataAccessException {
logger.debug("WebServiceSqlMapDAO selectReservationService START");
String sqlId = "WebService.selectReservationService";
logger.debug("WebServiceSqlMapDAO selectReservationService END");
return getSqlSession().selectOne(sqlId, paramMap);
}
/**
* 예약 정보 저장
*
* @param Map
* @return
* @throws DataAccessException
*/
public Map<String, Object> insertReservation(HashMap<String, Object> paramMap) throws DataAccessException {
logger.debug("WebServiceSqlMapDAO insertReservation START");
String sqlId = "WebService.insertReservation";
logger.debug("WebServiceSqlMapDAO insertReservation END");
getSqlSession().insert(sqlId, paramMap);
return null;
}
}

View File

@@ -0,0 +1,40 @@
package com.madeuhome.dto;
import lombok.Data;
/**
* 예약 관련 DTO
*/
@Data
public class ReservationDto {
// 예약 기본 정보
private String muReserveId;
private String userName;
private String phoneNumber;
private String birthDate;
private String selectedDate;
private String time;
private String etc;
// 시술 정보
private String categoryDivCd;
private String categoryNo;
private String postNo;
private String procedureId;
private String treatmentId;
private String treatmentName;
private String treatmentProcedureId;
private String treatmentProcedureName;
private String treatmentInfos;
// 병원 운영 정보
private String muHospitalId;
private String hospitalName;
private String searchDate;
private String openYn;
private String holidayYn;
private String openStartTime;
private String openEndTime;
private String breakStartTime;
private String breakEndTime;
}

View File

@@ -0,0 +1,211 @@
package com.madeuhome.service.web.reservation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.madeuhome.constants.Constants;
import com.madeuhome.util.OkHttpService;
import lombok.extern.slf4j.Slf4j;
/**
* 예약 서비스 (구현체 포함) - 쁘띠&피부센터
*/
@Service
@Slf4j
public class ReservationService {
@Autowired
private SqlSessionTemplate sqlSession;
@Value("${url.old-crm-php}")
private String ocp;
/**
* 예약 시술 정보 조회
*/
public HashMap<String, Object> selectReservation(HashMap<String, Object> paramMap) throws Exception {
log.debug("ReservationService selectReservation START");
HashMap<String, Object> map = new HashMap<>();
try {
List<Map<String, Object>> listMap2 = new ArrayList<>();
if (paramMap.get("PROCEDURE_ID").toString().indexOf(",") != -1) {
String[] PROCEDURE_ID = paramMap.get("PROCEDURE_ID").toString().split(",");
for (int i = 0; i < PROCEDURE_ID.length; i++) {
HashMap<String, Object> map2 = new HashMap<>();
map2.put("PROCEDURE_ID", PROCEDURE_ID[i]);
listMap2.add(sqlSession.selectOne("Reservation.selectReservationService", map2));
}
} else {
listMap2.add(sqlSession.selectOne("Reservation.selectReservationService", paramMap));
}
map.put("reservation", listMap2);
map.put("msgCode", Constants.OK);
map.put("success", "true");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("ReservationService selectReservation END");
return map;
}
/**
* 병원 휴일 조회 (달력 표출용)
*/
public HashMap<String, Object> selectHospitalHolidayList(HashMap<String, Object> paramMap) throws Exception {
log.debug("ReservationService selectHospitalHolidayList START");
HashMap<String, Object> map = new HashMap<>();
try {
List<Map<String, Object>> listMap = sqlSession.selectList("Reservation.selectHospitalHolidayList",
paramMap);
map.put("rows", listMap);
map.put("msgCode", Constants.OK);
map.put("success", "true");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("ReservationService selectHospitalHolidayList END");
return map;
}
/**
* 특정 날짜 운영시간 조회
*/
public HashMap<String, Object> selectHospitalWorkTime(HashMap<String, Object> paramMap) throws Exception {
log.debug("ReservationService selectHospitalWorkTime START");
HashMap<String, Object> map = new HashMap<>();
try {
Map<String, Object> result = sqlSession.selectOne("Reservation.selectHospitalWorkTime", paramMap);
if (result != null) {
map.put("rows", result);
} else {
map.put("rows", new HashMap<>());
}
map.put("msgCode", Constants.OK);
map.put("success", "true");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("ReservationService selectHospitalWorkTime END");
return map;
}
/**
* 예약 카운트 조회
*/
public HashMap<String, Object> selectReservationCnt(HashMap<String, Object> paramMap) throws Exception {
log.debug("ReservationService selectReservationCnt START");
HashMap<String, Object> map = new HashMap<>();
try {
Map<String, Object> result = sqlSession.selectOne("Reservation.selectReservationCnt", paramMap);
map.put("rows", result);
map.put("msgCode", Constants.OK);
map.put("success", "true");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("ReservationService selectReservationCnt END");
return map;
}
/**
* 예약 저장
*/
public HashMap<String, Object> insertReservation(HashMap<String, Object> paramMap) throws Exception {
log.debug("ReservationService insertReservation START");
HashMap<String, Object> map = new HashMap<>();
try {
String muReserveId = ("R").concat(String.valueOf(System.currentTimeMillis()));
paramMap.put("muReserveId", muReserveId);
ArrayList<String> userRequest = new ArrayList<>();
// TREATMENT_INFOS 파라미터가 있으면 파싱하여 P_변수명으로 세팅 후 insertReservation 호출
if (paramMap.get("TREATMENT_INFOS") != null) {
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, Object>> treatmentList = objectMapper.readValue(
paramMap.get("TREATMENT_INFOS").toString(),
new TypeReference<List<Map<String, Object>>>() {
});
for (Map<String, Object> t : treatmentList) {
paramMap.put("P_TREATMENT_ID", t.get("MU_TREATMENT_ID"));
paramMap.put("P_TREATMENT_NM", t.get("TREATMENT_NAME"));
paramMap.put("P_PROCEDURE_ID", t.get("MU_TREATMENT_PROCEDURE_ID"));
paramMap.put("P_PROCEDURE_NM", t.get("TREATMENT_PROCEDURE_NAME"));
userRequest.add((String) t.get("TREATMENT_PROCEDURE_NAME"));
sqlSession.insert("Reservation.insertReservation", paramMap);
}
} else {
// 기존 방식대로 단일 시술 정보로 처리
paramMap.put("P_TREATMENT_ID", paramMap.get("MU_TREATMENT_ID"));
paramMap.put("P_TREATMENT_NM", paramMap.get("TREATMENT_NAME"));
paramMap.put("P_PROCEDURE_ID", paramMap.get("MU_TREATMENT_PROCEDURE_ID"));
paramMap.put("P_PROCEDURE_NM", paramMap.get("TREATMENT_PROCEDURE_NAME"));
userRequest.add((String) paramMap.get("TREATMENT_PROCEDURE_NAME"));
sqlSession.insert("Reservation.insertReservation", paramMap);
}
Map<String, String> formData = new HashMap<>();
formData.put("visitorName", (String) paramMap.get("NAME"));
formData.put("visitorPhone", (String) paramMap.get("PHONE_NUMBER"));
String strUserRequest = "[선택시술]:" + userRequest.stream().collect(Collectors.joining(", "));
String birthDate = "[생년월일]:" + paramMap.get("BIRTH_DATE");
String reqTxt = "[요청사항]:" + (String) paramMap.get("ETC");
formData.put("userRequest", strUserRequest + "\n" + birthDate + "\n" + reqTxt);
String time = (String) paramMap.get("TIME");
String date = (String) paramMap.get("SELECTED_DATE");
String dateTime = date + " " + time;
formData.put("datetime", dateTime);
Map<String, Object> detailMap = sqlSession.selectOne("Reservation.selectServiceDetail", paramMap);
formData.put("itemName", "[홈페이지] 쁘띠&피부센터");
// 구CRM 연동ID가 없을 경우 기본값 (피부 & 쁘띠센터)
String oldCrmItemId = (String) detailMap.get("oldCrmItemId") == null
? "NBI-4aJSJ67P-f0ID-4xds-aReQ-MnhfErLts0HP"
: (String) detailMap.get("oldCrmItemId");
formData.put("itemID", oldCrmItemId);
formData.put("etcText", "[진료]:" + (String) detailMap.get("CATEGORY_NM"));
// OLD CRM으로 예약정보 POST
OkHttpService.postFormData(ocp, formData);
map.put("msgCode", Constants.OK);
map.put("success", "true");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("ReservationService insertReservation END");
return map;
}
}

View File

@@ -36,7 +36,7 @@ public class WebInstagramServiceImpl implements WebInstagramService {
HashMap<String, Object> paramMap) throws Exception {
log.debug("WebInstagramServiceImpl selectListWebInstagram START");
HashMap<String, Object> map = new HashMap<String, Object>();
String apiUrl = "https://graph.instagram.com/" + clientId + "/media?fields=id,caption,media_url,permalink,media_type,thumbnail_url&access_token=" + accessToken;
String apiUrl = "https://graph.facebook.com/" + clientId + "/media?fields=id,caption,media_url,permalink,media_type,thumbnail_url&access_token=" + accessToken;
try{
RestTemplate restTemplate = new RestTemplate();
Map<String, Object> response = restTemplate.getForObject(apiUrl, Map.class);

View File

@@ -0,0 +1,9 @@
package com.madeuhome.service.web.webreview;
import java.util.HashMap;
public interface WebReviewService {
public HashMap<String, Object> selectListProcedureReview(HashMap<String, Object> paramMap) throws Exception;
public HashMap<String, Object> selectProcedureReview(HashMap<String, Object> paramMap) throws Exception;
}

View File

@@ -0,0 +1,59 @@
package com.madeuhome.service.web.webreview.impl;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.madeuhome.constants.Constants;
import com.madeuhome.dao.web.webreview.WebReviewSqlMapDAO;
import com.madeuhome.service.web.webreview.WebReviewService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service("WebReviewService")
public class WebReviewServiceImpl implements WebReviewService {
@Autowired
private WebReviewSqlMapDAO webReviewSqlMapDAO;
@Override
public HashMap<String, Object> selectListProcedureReview(HashMap<String, Object> paramMap) throws Exception {
log.debug("WebReviewServiceImpl selectListProcedureReview START");
HashMap<String, Object> map = new HashMap<String, Object>();
try {
Map<String, Object> totalMap = webReviewSqlMapDAO.selectTotalProcedureReviewCount(paramMap);
map.put("totalCount", totalMap.get("totalCount"));
List<Map<String, Object>> listMap = webReviewSqlMapDAO.selectListProcedureReview(paramMap);
map.put("rows", listMap);
map.put("msgCode", Constants.OK);
map.put("success", "true");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("WebReviewServiceImpl selectListProcedureReview END");
return map;
}
@Override
public HashMap<String, Object> selectProcedureReview(HashMap<String, Object> paramMap) throws Exception {
log.debug("WebReviewServiceImpl selectProcedureReview START");
HashMap<String, Object> map = new HashMap<String, Object>();
try {
webReviewSqlMapDAO.updateViewCount(paramMap);
Map<String, Object> detailMap = webReviewSqlMapDAO.selectProcedureReview(paramMap);
map.put("rows", detailMap);
map.put("msgCode", Constants.OK);
map.put("success", "true");
} catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("WebReviewServiceImpl selectProcedureReview END");
return map;
}
}

View File

@@ -4,9 +4,8 @@ import java.util.HashMap;
public interface WebServiceService {
public HashMap<String, Object> selectListWebCategory(HashMap<String, Object> paramMap) throws Exception;
public HashMap<String, Object> selectListWebService(HashMap<String, Object> paramMap) throws Exception;
public HashMap<String, Object> selectServiceDetail(HashMap<String, Object> paramMap) throws Exception;
public HashMap<String, Object> selectReservation(HashMap<String, Object> paramMap) throws Exception;
public HashMap<String, Object> selectReservationCnt(HashMap<String, Object> paramMap) throws Exception;
public HashMap<String, Object> insertReservation(HashMap<String, Object> paramMap) throws Exception;
}

View File

@@ -1,34 +1,24 @@
package com.madeuhome.service.web.webservice.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.madeuhome.constants.Constants;
import com.madeuhome.dao.web.webservice.WebServiceSqlMapDAO;
import com.madeuhome.service.web.webservice.WebServiceService;
import com.madeuhome.util.OkHttpService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service("WebServiceService")
public class WebServiceServiceImpl implements WebServiceService{
public class WebServiceServiceImpl implements WebServiceService {
@Autowired
private WebServiceSqlMapDAO webServiceSqlMapDAO;
@Value("${url.old-crm-php}")
private String ocp;
private WebServiceSqlMapDAO webServiceSqlMapDAO;
/**
* 시술 정보 카테고리 리스트 조회 (List)
@@ -43,16 +33,16 @@ public class WebServiceServiceImpl implements WebServiceService{
HashMap<String, Object> map = new HashMap<String, Object>();
try{
try {
List<Map<String, Object>> listMap = webServiceSqlMapDAO.selectListWebCategory(paramMap);
map.put("rows",listMap);
map.put("rows", listMap);
map.put("msgCode", Constants.OK);
map.put("success","true");
map.put("success", "true");
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
throw e;
throw e;
}
log.debug("WebServiceServiceImpl selectListWebCategory END");
@@ -72,16 +62,16 @@ public class WebServiceServiceImpl implements WebServiceService{
HashMap<String, Object> map = new HashMap<String, Object>();
try{
try {
List<Map<String, Object>> listMap = webServiceSqlMapDAO.selectListWebService(paramMap);
map.put("rows",listMap);
map.put("rows", listMap);
map.put("msgCode", Constants.OK);
map.put("success","true");
map.put("success", "true");
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
throw e;
throw e;
}
log.debug("WebServiceServiceImpl selectListWebService END");
@@ -101,195 +91,23 @@ public class WebServiceServiceImpl implements WebServiceService{
HashMap<String, Object> map = new HashMap<String, Object>();
try{
//상세 내용
try {
// 상세 내용
Map<String, Object> listMap = webServiceSqlMapDAO.selectServiceDetail(paramMap);
map.put("rows",listMap);
//시술 목록
map.put("rows", listMap);
// 시술 목록
List<Map<String, Object>> listServiceMap = webServiceSqlMapDAO.selectListService(paramMap);
map.put("price",listServiceMap);
map.put("price", listServiceMap);
map.put("msgCode", Constants.OK);
map.put("success","true");
map.put("success", "true");
}catch (Exception e) {
} catch (Exception e) {
e.printStackTrace();
throw e;
throw e;
}
log.debug("WebServiceServiceImpl selectServiceDetail END");
return map;
}
/**
* 예약 정보 조회
*
* @param paramMap
* @return
* @throws Exception
*/
@Override
public HashMap<String, Object> selectReservation(HashMap<String, Object> paramMap) throws Exception {
log.debug("WebServiceServiceImpl selectReservation START");
HashMap<String, Object> map = new HashMap<String, Object>();
try{
//예약 목록
List<Map<String, Object>> listMap2 = new ArrayList<Map<String, Object>>();
if(paramMap.get("PROCEDURE_ID").toString().indexOf(",") != -1) {
String[] PROCEDURE_ID = paramMap.get("PROCEDURE_ID").toString().split(",");
for(int i = 0; i < PROCEDURE_ID.length; i++) {
HashMap<String, Object> map2 = new HashMap<String, Object>();
map2.put("PROCEDURE_ID", PROCEDURE_ID[i]);
listMap2.add(webServiceSqlMapDAO.selectReservationService(map2));
}
}else {
listMap2.add(webServiceSqlMapDAO.selectReservationService(paramMap));
}
map.put("reservation",listMap2);
map.put("msgCode", Constants.OK);
map.put("success","true");
}catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("WebServiceServiceImpl selectReservation END");
return map;
}
/**
* 예약 정보 조회
*
* @param paramMap
* @return
* @throws Exception
*/
@Override
public HashMap<String, Object> selectReservationCnt(HashMap<String, Object> paramMap) throws Exception {
log.debug("WebServiceServiceImpl selectReservationCnt START");
HashMap<String, Object> map = new HashMap<String, Object>();
try{
//상세 내용
Map<String, Object> listMap = webServiceSqlMapDAO.selectReservationCnt(paramMap);
map.put("rows",listMap);
map.put("msgCode", Constants.OK);
map.put("success","true");
}catch (Exception e) {
e.printStackTrace();
throw e;
}
log.debug("WebServiceServiceImpl selectReservationCnt END");
return map;
}
/**
* 예약 정보 저장
*
* @param paramMap
* @return
* @throws Exception
*/
@Override
public HashMap<String, Object> insertReservation(HashMap<String, Object> paramMap) throws Exception {
log.debug("WebServiceServiceImpl insertReservation START");
HashMap<String, Object> map = new HashMap<String, Object>();
try{
String muReserveId = ("R").concat(String.valueOf(System.currentTimeMillis()));
paramMap.put("muReserveId",muReserveId);
ArrayList<String> userRequest = new ArrayList<String>();
// TREATMENT_INFOS 파라미터가 있으면 파싱하여 P_변수명으로 세팅 후 insertReservation 호출
if(paramMap.get("TREATMENT_INFOS") != null) {
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, Object>> treatmentList = objectMapper.readValue(paramMap.get("TREATMENT_INFOS").toString(), new TypeReference<List<Map<String, Object>>>(){});
for(Map<String, Object> t : treatmentList) {
paramMap.put("P_TREATMENT_ID", t.get("MU_TREATMENT_ID"));
paramMap.put("P_TREATMENT_NM", t.get("TREATMENT_NAME"));
paramMap.put("P_PROCEDURE_ID", t.get("MU_TREATMENT_PROCEDURE_ID"));
paramMap.put("P_PROCEDURE_NM", t.get("TREATMENT_PROCEDURE_NAME"));
userRequest.add((String) t.get("TREATMENT_PROCEDURE_NAME"));
webServiceSqlMapDAO.insertReservation(paramMap);
}
} else {
// 기존 방식대로 단일 시술 정보로 처리
paramMap.put("P_TREATMENT_ID", paramMap.get("MU_TREATMENT_ID"));
paramMap.put("P_TREATMENT_NM", paramMap.get("TREATMENT_NAME"));
paramMap.put("P_PROCEDURE_ID", paramMap.get("MU_TREATMENT_PROCEDURE_ID"));
paramMap.put("P_PROCEDURE_NM", paramMap.get("TREATMENT_PROCEDURE_NAME"));
userRequest.add((String) paramMap.get("TREATMENT_PROCEDURE_NAME"));
webServiceSqlMapDAO.insertReservation(paramMap);
}
Map<String, String> formData = new HashMap<>();
formData.put("visitorName", (String) paramMap.get("NAME"));
formData.put("visitorPhone", (String) paramMap.get("PHONE_NUMBER"));
String strUserRequest = "[선택시술]:" + userRequest.stream().collect(Collectors.joining(", "));
String birthDate = "[생년월일]:" + paramMap.get("BIRTH_DATE");
String reqTxt = "[요청사항]:" + (String) paramMap.get("ETC");
formData.put("userRequest", strUserRequest + "\n" + birthDate + "\n" + reqTxt);
String time = (String) paramMap.get("TIME");
String date = (String) paramMap.get("SELECTED_DATE");
String dateTime = date + " " + time;
formData.put("datetime", dateTime);
Map<String, Object> detailMap = webServiceSqlMapDAO.selectServiceDetail(paramMap);
formData.put("itemName", "[홈페이지] 쁘띠&피부센터");
// 구CRM 연동ID가 없을 경우 모두 (재방문) 다이어트 센터
String oldCrmItemId = (String) detailMap.get("oldCrmItemId") == null ? "NBI-4aJSJ67P-f0ID-4xds-aReQ-MnhfErLts0HP" : (String) detailMap.get("oldCrmItemId");
formData.put("itemID", oldCrmItemId);
formData.put("etcText", "[진료]:" + (String) detailMap.get("CATEGORY_NM"));
// OLD CRM으로 예약정보 POST
/**
* //시술정보(상품명 , 상품ID)
상품명 : 메쉬다주사(지방분해주사)
상품 ID : NBI-1M0JEq5C-b8LM-4W0V-9YxZ-Z5r9XWJpIVEZ
피부 & 쁘띠센터
NBI-4aJSJ67P-f0ID-4xds-aReQ-MnhfErLts0HP
프리미엄 레이저 리프팅
NBI-1iPCWjih-2TDk-4NG3-ci56-tK7dgxxAT6dU
(첫방문) 다이어트 센터
NBI-1Q8doiCm-qzTh-4imi-9lsK-6YDslUzIK2OO
(재방문) 다이어트 센터
NBI-2WlrXQdl-esws-5aA5-al7z-d7HwxL9tX0YF
탈모약
NBI-Nz00si0r-DkGc-5hdd-bs27-SLn0KHlHflS8
비만치료제 위고비
NBI-2nJnTFVm-ieDy-4TNQ-czDK-Ll9FyixFr7zn
*
*
**/
OkHttpService.postFormData(ocp, formData);
map.put("msgCode", Constants.OK);
map.put("success","true");
}catch (Exception e) {
e.printStackTrace();
throw e;
}
System.out.println("[insertReservation] paramMap: " + paramMap);
log.debug("WebServiceServiceImpl insertReservation END");
return map;
}
}

View File

@@ -50,4 +50,4 @@ senderPhoneNumber: 025474711
# Instagram 설정
instagram:
client-id: 17841400162629727
accesstoken: IGAAMzYDUuoLJBZAFRLMUV4V3ZA5RTdIQzNvUmdYQXd6RGpMaGgxbTFQWUJlcmpKZAGlFM29CX20tOE9iSGg4Y1hwdVVHcUVXeS1RQ3dMWVluOEdTdTY4UUh2aFVVR0tGTGhGLVMwWDdkakFtbWJtbnFhQkprMmQxcGsxQjJGZA3pUTQZDZD
accesstoken: EAAbtjekAF1EBQaZAZAmZCW8TyAA176J5yD6tFSj4MaZACgROAxGsvvT0cSvPxqLOHR4Bd8EeHSvWAqHZB8nHxurf3s3z0vtXt8sQ4zZBHpEJcnNTWSOvfch7hXv4uOQcfliBZCL6dDhMukAtgmXRHlfJasKDc5cZBcI7AuqNr1BZB0jUAwPC8uMUiIQ8dFjpRbnVyRwZDZD

View File

@@ -0,0 +1,240 @@
<?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="Reservation">
<!-- 병원 휴일 조회 (달력 표출용) -->
<select id="selectHospitalHolidayList" parameterType="hashmap" resultType="hashmap">
SELECT 'hospitalHoliday' AS "dateType"
, HOSPITAL_HOLIDAY_NAME AS "hospitalHolidayName"
, DATE_FORMAT(LOC_DATE, '%Y-%m-%d') AS "locDate"
FROM MU_HOSPITAL_HOLIDAY MH
WHERE MH.USE_YN = 'Y'
AND MH.MU_HOSPITAL_ID = (SELECT MU_HOSPITAL_ID FROM MU_HOSPITAL WHERE USE_YN='Y' AND CENTER_DIV_CD='PETIT' LIMIT 1)
AND (
(MH.REPEAT_YN = 'Y')
OR
(MH.REPEAT_YN = 'N' AND DATE_FORMAT( MH.LOC_DATE, '%Y' ) >= DATE_FORMAT( CURDATE( ), '%Y' ))
)
UNION ALL
(
SELECT 'publicHoliday' AS "dateType"
, MPH.PUBLIC_HOLIDAY_NAME AS "publicHolidayName"
, DATE_FORMAT(MPH.LOC_DATE, '%Y-%m-%d') AS "locDate"
FROM MU_PUBLIC_HOLIDAY MPH
JOIN MU_HOSPITAL MH
ON MH.MU_HOSPITAL_ID = (SELECT MU_HOSPITAL_ID FROM MU_HOSPITAL WHERE USE_YN='Y' AND CENTER_DIV_CD='PETIT' LIMIT 1)
AND MH.PUBLIC_HOLIDAY_USE_YN = 'Y'
WHERE MPH.USE_YN = 'Y'
AND DATE_FORMAT( MPH.LOC_DATE, '%Y' ) >= DATE_FORMAT( CURDATE( ), '%Y' )
)
UNION ALL
(
SELECT CASE WHEN OPEN_YN = 'Y' THEN 'hospitalScheduleOpen' ELSE 'hospitalSchedule' END AS "dateType"
, CASE WHEN OPEN_YN = 'Y' THEN '운영' ELSE '휴무' END AS "hospitalHolidayName"
, DATE_FORMAT(SCHEDULE_DATE, '%Y-%m-%d') AS "locDate"
FROM MU_HOSPITAL_SCHEDULE
WHERE MU_HOSPITAL_ID = (SELECT MU_HOSPITAL_ID FROM MU_HOSPITAL WHERE USE_YN='Y' AND CENTER_DIV_CD='PETIT' LIMIT 1)
AND USE_YN = 'Y'
AND DATE_FORMAT( SCHEDULE_DATE, '%Y' ) >= DATE_FORMAT( CURDATE( ), '%Y' )
)
</select>
<!-- 특정 날짜 운영시간 조회 -->
<select id="selectHospitalWorkTime" parameterType="hashmap" resultType="hashmap">
SELECT MH.MU_HOSPITAL_ID AS "muHospitalId"
,MH.HOSPITAL_NAME AS "hospitalName"
,CASE
WHEN MHS.MU_HOSPITAL_SCHEDULE_ID IS NOT NULL THEN MHS.OPEN_YN
ELSE
CASE DAYOFWEEK(#{searchDate})
WHEN 2 THEN MH.MON_OPEN_YN
WHEN 3 THEN MH.TUE_OPEN_YN
WHEN 4 THEN MH.WED_OPEN_YN
WHEN 5 THEN MH.THU_OPEN_YN
WHEN 6 THEN MH.FRI_OPEN_YN
WHEN 7 THEN MH.SAT_OPEN_YN
WHEN 1 THEN MH.SUN_OPEN_YN
END
END AS "openYn"
,CASE
WHEN MHS.MU_HOSPITAL_SCHEDULE_ID IS NOT NULL AND MHS.OPEN_YN = 'Y' THEN 'N'
WHEN MHS.MU_HOSPITAL_SCHEDULE_ID IS NOT NULL AND MHS.OPEN_YN = 'N' THEN 'Y'
WHEN EXISTS (
SELECT 1
FROM MU_HOSPITAL_HOLIDAY HH
WHERE HH.USE_YN = 'Y'
AND HH.MU_HOSPITAL_ID = MH.MU_HOSPITAL_ID
AND (
(HH.REPEAT_YN = 'Y' AND DATE_FORMAT(HH.LOC_DATE, '%m-%d') = DATE_FORMAT(#{searchDate}, '%m-%d'))
OR
(HH.REPEAT_YN = 'N' AND HH.LOC_DATE = #{searchDate})
)) OR
EXISTS (
SELECT 1
FROM MU_PUBLIC_HOLIDAY MPH
WHERE MPH.USE_YN = 'Y'
AND MH.PUBLIC_HOLIDAY_USE_YN='Y'
AND MPH.LOC_DATE = #{searchDate}
)
THEN 'Y'
ELSE 'N'
END AS "holidayYn"
,CASE
WHEN MHS.MU_HOSPITAL_SCHEDULE_ID IS NOT NULL THEN MHS.OPEN_START_TIME
ELSE
CASE DAYOFWEEK(#{searchDate})
WHEN 2 THEN MH.MON_OPEN_START_TIME
WHEN 3 THEN MH.TUE_OPEN_START_TIME
WHEN 4 THEN MH.WED_OPEN_START_TIME
WHEN 5 THEN MH.THU_OPEN_START_TIME
WHEN 6 THEN MH.FRI_OPEN_START_TIME
WHEN 7 THEN MH.SAT_OPEN_START_TIME
WHEN 1 THEN MH.SUN_OPEN_START_TIME
END
END AS "openStartTime"
,CASE
WHEN MHS.MU_HOSPITAL_SCHEDULE_ID IS NOT NULL THEN MHS.OPEN_END_TIME
ELSE
CASE DAYOFWEEK(#{searchDate})
WHEN 2 THEN MH.MON_OPEN_END_TIME
WHEN 3 THEN MH.TUE_OPEN_END_TIME
WHEN 4 THEN MH.WED_OPEN_END_TIME
WHEN 5 THEN MH.THU_OPEN_END_TIME
WHEN 6 THEN MH.FRI_OPEN_END_TIME
WHEN 7 THEN MH.SAT_OPEN_END_TIME
WHEN 1 THEN MH.SUN_OPEN_END_TIME
END
END AS "openEndTime"
,CASE
WHEN MHS.MU_HOSPITAL_SCHEDULE_ID IS NOT NULL THEN MHS.BREAK_START_TIME
ELSE
CASE DAYOFWEEK(#{searchDate})
WHEN 2 THEN MH.MON_BREAK_START_TIME
WHEN 3 THEN MH.TUE_BREAK_START_TIME
WHEN 4 THEN MH.WED_BREAK_START_TIME
WHEN 5 THEN MH.THU_BREAK_START_TIME
WHEN 6 THEN MH.FRI_BREAK_START_TIME
WHEN 7 THEN MH.SAT_BREAK_START_TIME
WHEN 1 THEN MH.SUN_BREAK_START_TIME
END
END AS "breakStartTime"
,CASE
WHEN MHS.MU_HOSPITAL_SCHEDULE_ID IS NOT NULL THEN MHS.BREAK_END_TIME
ELSE
CASE DAYOFWEEK(#{searchDate})
WHEN 2 THEN MH.MON_BREAK_END_TIME
WHEN 3 THEN MH.TUE_BREAK_END_TIME
WHEN 4 THEN MH.WED_BREAK_END_TIME
WHEN 5 THEN MH.THU_BREAK_END_TIME
WHEN 6 THEN MH.FRI_BREAK_END_TIME
WHEN 7 THEN MH.SAT_BREAK_END_TIME
WHEN 1 THEN MH.SUN_BREAK_END_TIME
END
END AS "breakEndTime"
,MH.PUBLIC_HOLIDAY_USE_YN AS "publicHolidayUseYn"
FROM MU_HOSPITAL AS MH
LEFT JOIN MU_HOSPITAL_SCHEDULE MHS
ON MH.MU_HOSPITAL_ID = MHS.MU_HOSPITAL_ID
AND MHS.SCHEDULE_DATE = #{searchDate}
AND MHS.USE_YN = 'Y'
WHERE MH.USE_YN = 'Y'
AND MH.CENTER_DIV_CD = 'PETIT'
LIMIT 0, 1
</select>
<!-- 예약 카운트 조회 -->
<select id="selectReservationCnt" resultType="hashmap" parameterType="hashmap">
SELECT COUNT(*) AS RES_CNT
FROM MU_RESERVE
WHERE RESERVE_DATE = #{SELECTED_DATE}
AND RESERVE_TIME = #{TIME}
</select>
<!-- 예약 시술 정보 조회 -->
<select id="selectReservationService" resultType="hashmap" parameterType="hashmap">
SELECT A.TREATMENT_PROCEDURE_NAME, (B.PRICE + B.VAT) AS PRICE, B.VAT, B.DISCOUNT_PRICE, C.MU_TREATMENT_ID, C.TREATMENT_NAME, B.MU_TREATMENT_PROCEDURE_ID
FROM MU_TREATMENT_PROCEDURE A
LEFT OUTER JOIN MU_TREATMENT_PROCEDURE_PRICE B ON A.MU_TREATMENT_PROCEDURE_ID = B.MU_TREATMENT_PROCEDURE_ID AND B.USE_YN = 'Y'
LEFT OUTER JOIN MU_TREATMENT C ON A.MU_TREATMENT_ID = C.MU_TREATMENT_ID
WHERE A.MU_TREATMENT_PROCEDURE_ID = #{PROCEDURE_ID}
</select>
<!-- 시술 상세 조회 (CRM 연동용) -->
<select id="selectServiceDetail" resultType="hashmap" parameterType="hashmap">
SELECT A.CATEGORY_DIV_CD
, A.CATEGORY_NO
, A.POST_NO
, C.CATEGORY_NM
, A.TITLE
, A.CONTENT
, A.THUMBNAIL_BOTTOM_TXT
, A.HASHTAG
, A.OLD_CRM_ITEM_ID as oldCrmItemId
, D.FILE_PATH AS THUMBNAIL_PATH
, E.FILE_PATH AS CONTENTS_PATH
, (SELECT MIN(PRICE + VAT) FROM MU_TREATMENT_PROCEDURE_PRICE C
LEFT OUTER JOIN HP_CONTENTS_BBS_PROCEDURE B ON A.POST_NO = B.POST_NO AND A.CATEGORY_DIV_CD = B.CATEGORY_DIV_CD AND A.CATEGORY_NO = B.CATEGORY_NO AND B.USE_YN = 'Y'
WHERE B.MU_TREATMENT_PROCEDURE_ID = C.MU_TREATMENT_PROCEDURE_ID AND C.USE_YN = 'Y') AS PRICE
, (SELECT MIN(DISCOUNT_PRICE) FROM MU_TREATMENT_PROCEDURE_PRICE C
LEFT OUTER JOIN HP_CONTENTS_BBS_PROCEDURE B ON A.POST_NO = B.POST_NO AND A.CATEGORY_DIV_CD = B.CATEGORY_DIV_CD AND A.CATEGORY_NO = B.CATEGORY_NO AND B.USE_YN = 'Y'
WHERE B.MU_TREATMENT_PROCEDURE_ID = C.MU_TREATMENT_PROCEDURE_ID AND C.USE_YN = 'Y') AS DISCOUNT_PRICE
FROM HP_CONTENTS_BBS A
LEFT OUTER JOIN HP_CATEGORY C ON A.CATEGORY_NO = C.CATEGORY_NO AND A.CATEGORY_DIV_CD = C.CATEGORY_DIV_CD
LEFT OUTER JOIN HP_ATTACH_FILE D ON A.THUMBNAIL_ATTACHFILE_ID = D.ATTACHFILE_ID
LEFT OUTER JOIN HP_ATTACH_FILE E ON A.CONTENTS_ATTACHFILE_ID = E.ATTACHFILE_ID
WHERE A.USE_YN = 'Y'
AND A.CATEGORY_DIV_CD = #{CATEGORY_DIV_CD}
AND A.CATEGORY_NO = #{CATEGORY_NO}
AND A.POST_NO = #{POST_NO}
</select>
<!-- 예약 저장 -->
<insert id="insertReservation" parameterType="hashmap">
<selectKey resultType="string" keyProperty="id" order="BEFORE">
SELECT CONCAT(#{muReserveId},LPAD((SELECT NEXTVAL(MU_RESERVE_SEQ)), 11, 0))
</selectKey>
INSERT INTO MU_RESERVE(
MU_RESERVE_ID
,USER_NAME
,PHONE_NUMBER
,RESERVE_DATE
,RESERVE_TIME
,MU_TREATMENT_ID
,TREATMENT_NAME
,MU_TREATMENT_PROCEDURE_ID
,TREATMENT_PROCEDURE_NAME
,ETC
,STATUS
,STATUS2
,WRITE_DATE
,WRITE_TIME
,CUD_FLAG
,USE_YN
,REG_ID
,REG_DATE
,MOD_ID
,MOD_DATE
)VALUES(
#{id}
,#{NAME}
,#{PHONE_NUMBER}
,#{SELECTED_DATE}
,#{TIME}
,#{P_TREATMENT_ID}
,#{P_TREATMENT_NM}
,#{P_PROCEDURE_ID}
,#{P_PROCEDURE_NM}
,#{ETC,jdbcType=VARCHAR}
,'T'
,'N'
,CURDATE()
,CURTIME()
,'C'
,'Y'
,'customer'
,NOW()
,'customer'
,NOW()
)
</insert>
</mapper>

View File

@@ -15,6 +15,8 @@
<!-- 카테고리 상세 목록 조회 -->
<select id="selectListEvent" parameterType="hashmap" resultType="hashmap">
SELECT A.CATEGORY_DIV_CD, A.CATEGORY_NO, A.POST_NO, TITLE, CONTENT, THUMBNAIL_BOTTOM_TXT, A.HASHTAG, D.FILE_PATH AS THUMBNAIL_PATH,
DATE_FORMAT(A.EVENT_START_DT, '%Y-%m-%d') AS EVENT_START_DT,
DATE_FORMAT(A.EVENT_END_DT, '%Y-%m-%d') AS EVENT_END_DT,
(SELECT MIN(PRICE + VAT) FROM MU_TREATMENT_PROCEDURE_PRICE C
LEFT OUTER JOIN HP_CONTENTS_BBS_PROCEDURE B ON A.POST_NO = B.POST_NO AND A.CATEGORY_DIV_CD = B.CATEGORY_DIV_CD AND A.CATEGORY_NO = B.CATEGORY_NO AND B.USE_YN = 'Y'
WHERE B.MU_TREATMENT_PROCEDURE_ID = C.MU_TREATMENT_PROCEDURE_ID AND C.USE_YN = 'Y') AS PRICE,
@@ -26,12 +28,14 @@
WHERE A.USE_YN = 'Y'
AND A.CATEGORY_DIV_CD = '04'
AND A.CATEGORY_NO = #{category_no}
ORDER BY ORD_NO
ORDER BY ORD_NO DESC
</select>
<!-- 이벤트안내 상세 조회 -->
<select id="selectEventDetail" resultType="hashmap" parameterType="hashmap">
SELECT A.CATEGORY_DIV_CD, A.CATEGORY_NO, A.POST_NO, C.CATEGORY_NM , TITLE, CONTENT, THUMBNAIL_BOTTOM_TXT, A.HASHTAG, D.FILE_PATH AS THUMBNAIL_PATH, E.FILE_PATH AS CONTENTS_PATH,
DATE_FORMAT(A.EVENT_START_DT, '%Y-%m-%d') AS EVENT_START_DT,
DATE_FORMAT(A.EVENT_END_DT, '%Y-%m-%d') AS EVENT_END_DT,
(SELECT MIN(PRICE + VAT) FROM MU_TREATMENT_PROCEDURE_PRICE C
LEFT OUTER JOIN HP_CONTENTS_BBS_PROCEDURE B ON A.POST_NO = B.POST_NO AND A.CATEGORY_DIV_CD = B.CATEGORY_DIV_CD AND A.CATEGORY_NO = B.CATEGORY_NO AND B.USE_YN = 'Y'
WHERE B.MU_TREATMENT_PROCEDURE_ID = C.MU_TREATMENT_PROCEDURE_ID AND C.USE_YN = 'Y') AS PRICE,

View File

@@ -0,0 +1,64 @@
<?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="WebReview">
<select id="selectTotalProcedureReviewCount" parameterType="hashmap" resultType="hashmap">
SELECT COUNT(*) AS "totalCount"
FROM MU_PROCEDURE_REVIEW MPR
WHERE MPR.USE_YN = 'Y'
AND MPR.CATEGORY_DIV_CD = #{categoryDivCd}
<if test="title != null and title != ''">
AND MPR.TITLE LIKE CONCAT('%', TRIM(#{title}), '%')
</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.CATEGORY_DIV_CD AS "categoryDivCd"
,MPR.TITLE AS "title"
,MPR.CONTENT AS "summary"
,MPR.HASHTAG AS "hashtag"
,MPR.VIEW_COUNT AS "viewCount"
,DATE_FORMAT(MPR.REG_DATE, '%Y-%m-%d') AS "writeDate"
FROM MU_PROCEDURE_REVIEW MPR
WHERE MPR.USE_YN = 'Y'
AND MPR.CATEGORY_DIV_CD = #{categoryDivCd}
<if test="title != null and title != ''">
AND MPR.TITLE LIKE CONCAT('%', TRIM(#{title}), '%')
</if>
ORDER BY MPR.REG_DATE DESC
LIMIT 18446744073709551615
) MPR, (SELECT @RNUM:=0) R
WHERE 1 = 1
) MPR
WHERE 1 = 1
LIMIT ${start}, ${limit}
</select>
<select id="selectProcedureReview" parameterType="hashmap" resultType="hashmap">
SELECT MPR.MU_PROCEDURE_REVIEW_ID AS "muProcedureReviewId"
,MPR.CATEGORY_DIV_CD AS "categoryDivCd"
,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"
FROM MU_PROCEDURE_REVIEW MPR
WHERE MPR.USE_YN = 'Y'
AND MPR.MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
LIMIT 0, 1
</select>
<update id="updateViewCount" parameterType="hashmap">
UPDATE MU_PROCEDURE_REVIEW
SET VIEW_COUNT = IFNULL(VIEW_COUNT, 0) + 1
WHERE USE_YN = 'Y'
AND MU_PROCEDURE_REVIEW_ID = #{muProcedureReviewId}
</update>
</mapper>

View File

@@ -25,7 +25,7 @@
WHERE A.USE_YN = 'Y'
AND A.CATEGORY_DIV_CD = '03'
AND A.CATEGORY_NO = #{categoryNo}
ORDER BY ORD_NO
ORDER BY ORD_NO DESC
</select>
<!-- 카테고리 상세 조회 -->
@@ -70,70 +70,4 @@
AND A.POST_NO = #{POST_NO}
</select>
<!-- 예약 조회 -->
<select id="selectReservationCnt" resultType="hashmap" parameterType="hashmap">
SELECT COUNT(*) AS RES_CNT
FROM MU_RESERVE
WHERE RESERVE_DATE = #{SELECTED_DATE}
AND RESERVE_TIME = #{TIME}
</select>
<!-- 시술 목록 조회 -->
<select id="selectReservationService" resultType="hashmap" parameterType="hashmap">
SELECT A.TREATMENT_PROCEDURE_NAME, (B.PRICE + B.VAT) AS PRICE, B.VAT, B.DISCOUNT_PRICE, C.MU_TREATMENT_ID, C.TREATMENT_NAME, B.MU_TREATMENT_PROCEDURE_ID
FROM MU_TREATMENT_PROCEDURE A
LEFT OUTER JOIN MU_TREATMENT_PROCEDURE_PRICE B ON A.MU_TREATMENT_PROCEDURE_ID = B.MU_TREATMENT_PROCEDURE_ID AND B.USE_YN = 'Y'
LEFT OUTER JOIN MU_TREATMENT C ON A.MU_TREATMENT_ID = C.MU_TREATMENT_ID
WHERE A.MU_TREATMENT_PROCEDURE_ID = #{PROCEDURE_ID}
</select>
<!-- 예약 저장 -->
<insert id="insertReservation" parameterType="hashmap">
<selectKey resultType="string" keyProperty="id" order="BEFORE">
SELECT CONCAT(#{muReserveId},LPAD((SELECT NEXTVAL(MU_RESERVE_SEQ)), 11, 0))
</selectKey>
INSERT INTO MU_RESERVE(
MU_RESERVE_ID
,USER_NAME
,PHONE_NUMBER
,RESERVE_DATE
,RESERVE_TIME
,MU_TREATMENT_ID
,TREATMENT_NAME
,MU_TREATMENT_PROCEDURE_ID
,TREATMENT_PROCEDURE_NAME
,ETC
,STATUS
,STATUS2
,WRITE_DATE
,WRITE_TIME
,CUD_FLAG
,USE_YN
,REG_ID
,REG_DATE
,MOD_ID
,MOD_DATE
)VALUES(
#{id}
,#{NAME}
,#{PHONE_NUMBER}
,#{SELECTED_DATE}
,#{TIME}
,#{P_TREATMENT_ID}
,#{P_TREATMENT_NM}
,#{P_PROCEDURE_ID}
,#{P_PROCEDURE_NM}
,#{ETC,jdbcType=VARCHAR}
,'T'
,'N'
,CURDATE()
,CURTIME()
,'C'
,'Y'
,'customer'
,NOW()
,'customer'
,NOW()
)
</insert>
</mapper>

File diff suppressed because it is too large Load Diff

View File

@@ -1,435 +1,471 @@
/* main_img */
.project_wrap .same main .main_img .text_box > div { width:50%; }
.project_wrap .same main .main_img .text_box .right_text_box p { font-size:52px; color:#000; }
.project_wrap .same main .main_img .text_box .right_text_box p span { font-weight:700; }
.project_wrap .same main .main_img .text_box .left_text_box p { color:#fff; }
.project_wrap .same main .main_img .text_box .left_text_box p span { color:#fff; }
/* content5 */
.project_wrap .same main .content5 { padding-bottom:0; }
/* 반응형 - 모바일 */
@media only screen and (max-width:768px){
/* main_img */
.project_wrap .same main .content5 { padding-bottom:0; }
.project_wrap .same main .main_img .text_box > div { width:100%; }
.project_wrap .same main .main_img .text_box .right_text_box { display:none; }
.project_wrap .same main .main_img .text_box .left_text_box p br { display:none; }
.project_wrap .same main .main_img .text_box .left_text_box p span { color:#000; }
* {
box-sizing: border-box;
}
.fix_area {
min-width: 1200px;
width: 1200px;
margin: 0 auto;
padding: 0 16px;
}
.btn-basic {
width:120px;
height:40px;
border: 1px solid #a73439;
border-radius:5px;
background-color: white;
color: #a73439;
}
.clear:after {
display: block;
visibility: hidden;
height: 0;
font-size: 0;
clear: both;
content: "";
}
.clear > .right {
float: right;
}
.clear > .left{
float:left;
}
.main-img-size {
position: relative;
width: 655px;
height: 368px;
overflow: hidden;
border-radius: 0.25rem;
text-align: center;
}
.img_center img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
body {
font-family: 'Noto Sans KR', sans-serif;
margin: 0;
background: #f7f7f9;
color: #222;
}
.thumbnail-bottom-txt {
padding-top: 15px;
}
.thumbnail-bottom-txt > span {
display: block;
color: #6c696a;
font-size: 13.5px;
}
.content>.wp60 {
width: 60%;
}
.content>.wp40 {
width: 40%;
}
.wp10{
width:10%;
}
.wp90{
width:90%;
}
.content>[class^=wp] {
float: left;
}
.consultation-info h2{
font-size: 4rem;
}
.consultation-info p{
margin-top: 2rem;
font-size:2rem;
}
.consultation-info .price {
display: block;
font-size: 3rem;
margin-top: 3rem;
margin-bottom: 1rem;
}
.price-area .border-line {
height: 1px;
background-color: #ddd;
}
.hashtag_list{
padding: 1rem 0 1.5rem;
}
.hashtag_list span{
font-size:1.6rem;
color:#a94442;
}
.procedure-area>.procedure_select_txt{
float: left;
width: 7rem;
line-height: 1.5;
padding-top: 0.375rem;
}
.procedure-area > .dropdown_area {
float: right;
width: calc(100% - 7rem);
}
.select_procedure_div {
position: relative;
}
.select_procedure_div .default_item {
padding-top: 0.5rem;
padding-left: 0.5rem;
padding-bottom: 0.5rem;
}
.select_procedure_div .default_item {
position: relative;
width: 100%;
padding-right: 2.5rem;
background-color: #fff;
border: 1px solid #cb9f76;
text-align: left;
border-radius: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.option_scrl_wrap {
display: none;
overflow: hidden;
position: relative;
z-index:1;
}
.select_procedure_div.active .option_scrl_wrap {
border: 1px solid #76232f;
}
.select_service_form.active .default_item:after {
border-top-color: #333;
}
.default_item:after {
content: '';
display: block;
position: absolute;
top: 50%;
right: 1.1em;
margin-top: -0.2rem;
border: 0.28571428em solid transparent;
border-top-color: #999;
}
.optipon_item {
padding: 0.4rem 0.5rem;
}
.sepr_wrap {
position: relative;
min-height: 2.5rem;
}
.item_subprice {
min-height: 42px;
}
.select_procedure_div.active .option_scrl_wrap {
display: block;
position: absolute;
right: -1px;
left: -1px;
background-color: #fff;
user-select: none;
}
.cs-checkbox > label.d-block {
display: block;
}
.cs-checkbox > label {
position: relative;
display: inline-block;
cursor: pointer;
padding-left: 30px;
margin: 0;
line-height: 20px;
transition: color .3s ease-out;
}
.oi-wrap {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
}
/* .cs-checkbox input[type="checkbox"] { */
/* visibility: hidden; */
/* display: none; */
/* } */
/* .cs-checkbox > label:before, .cs-radio > .r_visible { */
/* border: 1px solid #ccc; */
/* } */
input[type="checkbox"] {
transform: scale(1.5); /* 크기를 1.5배로 확대 */
}
.idxChk{
position:absolute;
}
.cs-checkbox > label:before {
content: "";
position: absolute;
top: 0;
left: 0;
display: inline-block;
margin-right: 0.8em;
border-radius: 0;
background-color: #fff;
}
.oi-wrap .oi-txt {
flex: 0 0 65%;
max-width: 65%;
}
.option_list {
display: block;
#service-header {
background: #fff;
max-height: 224px;
overflow-y: auto;
overflow-x: hidden;
border-bottom: 1px solid #ececec;
padding: 14px 0 14px 24px;
font-size: 0.98em;
color: #888;
}
.oi-wrap .oi-price {
flex: 0 0 35%;
max-width: 35%;
text-align: right;
.main-wrap {
max-width: 1280px;
margin: 0 auto;
background: #fff;
border-radius: 18px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.07);
margin-top: 32px;
padding: 0 0 32px 0;
}
.original_price {
color: #757575;
opacity: 0.7;
.top-section {
display: flex;
flex-wrap: wrap;
gap: 32px;
padding: 32px 32px 0 32px;
align-items: flex-start;
}
.d-block {
display: block;
.img-box {
border-radius: 18px;
align-items: center;
justify-content: center;
overflow: hidden;
}
.discount_price {
font-family: 'Campton', Sans-serif;
font-weight: 600;
color: #A73439;
}
.procedure-area:after {
display: block;
visibility: hidden;
height: 0;
font-size: 0;
clear: both;
content: "";
}
.selected-procedure{
margin-top:20px;
position:relative;
}
.selected-procedure .selt_info_wrap .info {
top: 0.7rem;
width: 9.5em;
padding-top: 0.3em;
}
.selt_info_wrap .info {
position: absolute;
top: 0.2rem;
right: 0.2rem;
width: 8.5em;
padding-right: 1rem;
padding-top: 0.2em;
text-align: right;
}
.selt_info_wrap .info button{
border:none;
background-color:#fff;
}
.real_price {
font-family: 'Campton', Sans-serif;
font-weight: 600;
color: #A73439;
}
.selected-procedure .selt_info_wrap {
padding: 0.8rem 0;
z-index:0;
}
.selt_info_wrap {
position: relative;
min-height: 2.5rem;
background-color:#fff;
border:1px solid #eee;
}
.selected-procedure .selt_info_wrap .selt {
padding-right: 9.5em;
padding-left: 1rem;
.img-box img {
width: 100%;
object-fit: cover;
border-radius: 18px;
}
.selt_info_wrap .selt {
padding-top: 0.3em;
padding-right: 8.5em;
.info-box {
flex: 1 1 300px;
min-width: 240px;
}
.total-price-area .total {
position: relative;
background-color: #e6e6e6;
padding: 25px 25px 60px;
.info-title {
font-size: 1.7em;
font-weight: 700;
margin-bottom: 6px;
color: #222;
}
.info-desc {
color: #444;
font-size: 1.1em;
margin-bottom: 18px;
}
.event-period {
font-size: 1.05em;
font-weight: 600;
color: #e74c3c;
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 8px;
padding: 10px 14px;
margin-bottom: 14px;
}
.info-price {
font-size: 1.2em;
font-weight: 700;
color: #b23c3c;
margin-bottom: 18px;
}
.select-row {
margin-bottom: 18px;
}
.select-row label {
font-weight: 500;
margin-bottom: 8px;
display: block;
}
.total-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.1em;
margin-bottom: 18px;
}
.total-price-area .total .txt_sub {
color: #222222;
font-size: 2rem;
opacity: 1;
}
.total-price-area .total .right strong {
font-size: 22px;
font-weight: 600;
.total-row .total-label {
color: #888;
}
.total-price-area .total-price-txt {
font-family: 'Campton', Sans-serif;
font-weight: 600;
color: #A73439;
.total-row .total-price {
font-weight: bold;
color: #b23c3c;
}
.total-price-area .total .bs-txt {
position: absolute;
bottom: 17px;
right: 25px;
display: block;
margin-top: 4px;
font-size: 12px;
color: #999999;
letter-spacing: -1px;
}
.main_btn {
text-align: right;
margin: 1rem 0;
}
.main_btn button {
width: 180px;
height: 50px;
}
.fastrack-btn {
position: relative;
}
.btn-primary {
border: 1px solid #a73439;
background-color: #a73439;
.reserve-btn {
width: 100%;
padding: 14px 0;
background: #b23c3c;
color: #fff;
border: none;
border-radius: 8px;
font-size: 1.1em;
font-weight: bold;
cursor: pointer;
transition: background 0.15s;
}
.img-content{
background-color: #eee;
.reserve-btn:disabled {
background: #ddd;
color: #888;
cursor: not-allowed;
}
.img-area{
padding-top: 70px;
padding-bottom: 200px;
text-align:center;
.desc-section {
margin-top: 36px;
padding: 0 32px;
}
.desc-title {
font-size: 1.25em;
font-weight: 700;
margin-bottom: 12px;
color: #222;
}
.desc-content {
color: #444;
font-size: 1.05em;
line-height: 1.7;
margin-bottom: 20px;
}
.hashtag-section {
margin-top: 30px;
padding: 20px 0;
border-top: 1px solid #eee;
}
.hashtag-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.hashtag-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hashtag {
display: inline-block;
background-color: #f8f9fa;
color: #495057;
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
text-decoration: none;
border: 1px solid #dee2e6;
transition: all 0.3s ease;
}
.hashtag:hover {
background-color: #007bff;
color: white;
border-color: #007bff;
cursor: pointer;
}
#thumbnail-bottom-txt {
padding: 8px;
margin-top: 10px;
background-color: #fff;
}
/* Choices.js 커스터마이징 - 개선된 버전 */
.choices {
border: 1px solid #ddd !important;
border-radius: 6px !important;
font-size: 1em !important;
background: #fff !important;
min-width: 300px !important;
}
.choices__inner {
background: #fff !important;
padding: 8px 12px !important;
min-height: 40px !important;
color: #222 !important;
min-width: 280px !important;
width: 100% !important;
}
/* 플레이스홀더 텍스트 잘림 방지 */
.choices__placeholder {
color: #888 !important;
opacity: 1 !important;
width: 100% !important;
min-width: 200px !important;
white-space: nowrap !important;
overflow: visible !important;
text-overflow: clip !important;
display: block !important;
}
.choices__input {
color: #222 !important;
background: transparent !important;
min-width: 200px !important;
width: 100% !important;
}
.choices__input::placeholder {
color: #888 !important;
opacity: 1 !important;
width: 100% !important;
min-width: 200px !important;
white-space: nowrap !important;
overflow: visible !important;
}
/* 다중 선택시 입력 필드 너비 확보 */
.choices[data-type*="select-multiple"] .choices__input {
min-width: 200px !important;
width: auto !important;
flex: 1 !important;
}
/* 선택된 항목들과 입력 필드 공간 분배 */
.choices__list--multiple {
flex-wrap: wrap !important;
align-items: center !important;
}
.choices__list--multiple:empty+.choices__input {
min-width: 200px !important;
width: 100% !important;
flex: 1 !important;
}
/* 선택된 항목의 가격 표시 스타일 */
.selected-item-content {
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
flex: 1 !important;
}
.selected-item-name {
font-weight: 500 !important;
margin-bottom: 2px !important;
}
.selected-item-price {
font-size: 0.85em !important;
}
.selected-price {
color: #b23c3c !important;
font-weight: bold !important;
}
.selected-price-discount {
color: #b23c3c !important;
font-weight: bold !important;
font-size: 0.9em !important;
}
.selected-price-original {
color: #aaa !important;
font-size: 0.85em !important;
text-decoration: line-through !important;
margin-left: 4px !important;
}
/* 선택된 항목들 스타일 수정 */
.choices__list--multiple .choices__item {
background-color: #f8f9fa !important;
border: 1px solid #dee2e6 !important;
border-radius: 16px !important;
padding: 8px 12px !important;
margin: 2px !important;
font-size: 0.9em !important;
color: #495057 !important;
flex-shrink: 0 !important;
display: flex !important;
align-items: center !important;
}
/* 빨간색으로 변경 */
.choices__button {
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjYjIzYzNjIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==') !important;
background-size: 14px 14px !important;
background-position: center !important;
background-repeat: no-repeat !important;
border-left: 0 !important;
margin: 0 !important;
padding: 0 !important;
width: 14px !important;
}
/* 드롭다운 컨테이너 */
.choices__list--dropdown {
background: #fff !important;
border: 1px solid #ddd !important;
border-radius: 6px !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
}
/* 드롭다운 옵션들 스타일 */
.choices__list--dropdown .choices__item {
padding: 8px 12px !important;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
color: #222 !important;
}
.choices__list--dropdown .choices__item--selectable:hover {
background-color: #f5f5f5 !important;
color: #222 !important;
}
.choices__list--dropdown .choices__item--highlighted {
background-color: #b23c3c !important;
color: #fff !important;
}
/* 포커스 상태 */
.choices.is-focused .choices__inner {
border-color: #b23c3c !important;
}
/* 가격 표시 스타일 */
.procedure-price {
color: #b23c3c !important;
font-weight: bold !important;
font-size: 0.9em !important;
}
.procedure-price-discount {
color: #b23c3c !important;
font-weight: bold !important;
font-size: 0.9em !important;
}
.procedure-price-original {
color: #aaa !important;
font-size: 0.85em !important;
text-decoration: line-through !important;
margin-left: 4px !important;
}
/* 드롭다운이 열렸을 때 하이라이트된 항목의 가격 색상 */
.choices__list--dropdown .choices__item--highlighted .procedure-price,
.choices__list--dropdown .choices__item--highlighted .procedure-price-discount {
color: #fff !important;
}
.choices__list--dropdown .choices__item--highlighted .procedure-price-original {
color: #ddd !important;
}
/* 검색 입력창 스타일 */
.choices[data-type*="select-multiple"] .choices__input {
background-color: transparent !important;
color: #222 !important;
}
/* 비활성화 상태 */
.choices.is-disabled .choices__inner {
background-color: #f8f9fa !important;
color: #6c757d !important;
cursor: not-allowed !important;
}
/* 반응형 대응 */
@media (max-width: 600px) {
.choices {
min-width: 250px !important;
}
.choices__inner {
min-width: 230px !important;
}
.choices__placeholder,
.choices__input,
.choices__input::placeholder {
min-width: 150px !important;
}
.choices__list--multiple .choices__item {
max-width: 250px !important;
padding: 6px 10px !important;
}
.selected-item-name {
font-size: 0.9em !important;
}
.selected-item-price {
font-size: 0.8em !important;
}
.choices__list--multiple .choices__item[data-deletable] .choices__button {
width: 20px !important;
height: 20px !important;
font-size: 16px !important;
text-indent: 0 !important;
}
}
@media (max-width: 900px) {
.main-wrap {
margin-top: 16px;
}
.top-section {
flex-direction: column;
gap: 18px;
padding: 20px 10px 0 10px;
}
.img-box {
width: 100%;
}
.info-box {
min-width: unset;
}
.desc-section {
padding: 0 10px;
}
}
@media (max-width: 600px) {
.main-wrap {
margin-top: 0;
border-radius: 0;
box-shadow: none;
}
.top-section {
padding: 12px 2vw 0 2vw;
}
.desc-section {
padding: 0 2vw;
}
}

View File

@@ -1,110 +1,495 @@
/* main_img */
.project_wrap .same main .main_img .text_box > div { width:50%; }
.project_wrap .same main .main_img .text_box .right_text_box p { font-size:52px; color:#000; }
.project_wrap .same main .main_img .text_box .right_text_box p span { font-weight:700; }
.project_wrap .same main .main_img .text_box .left_text_box p { color:#fff; }
.project_wrap .same main .main_img .text_box .left_text_box p span { color:#fff; }
/* content5 */
.project_wrap .same main .content5 { padding-bottom:0; }
* {
box-sizing: border-box;
}
.project_wrap .same aside {background: white; width: 30%; top: 12%;}
.project_wrap .same aside ul{position: fixed; left: 5%; width: 19%;}
.project_wrap .same aside ul li{margin-bottom: 0.5rem; margin-top: 0px;}
.project_wrap .same aside .first {padding-top: 1rem; padding-bottom: 1rem;}
.project_wrap .same aside .nonactive {border: 1px solid #d8d8d8; background-color: #f9f9fb;}
.project_wrap .same aside .active {border: 1px solid #d8d8d8; color: #a73439; background-color: rgba(118, 35, 47, 0); border-left: 2px solid #a73439;}
.project_wrap .same aside ul a{padding-left: 10px;}
.project_wrap .same main {width: 60%; padding-top: 7%;}
.project_wrap .same main ul{padding-top: 2%; width:102%;}
.event-card-list > ul { display: block; list-style-type: disc; margin-block-start: 1em; margin-block-end: 1em; padding-inline-start: 40px; unicode-bidi: isolate; }
.event-card-list > li { margin-bottom: 70px; opacity: 1;}
.event-card-list .event-card { display: block; position: relative; width: 775px; height: 196px; padding-right: 10px; margin-left: auto; overflow: hidden;}
.event-card .img_box { position: relative; width: 350px; height: 100%; background-color: black; float: left; overflow: hidden; }
.event-card .img_box img { position: absolute; min-width: 100%; min-height: 100%; width: 100%; height: 100%; margin: auto; top: 0; bottom: 0; left: 0; right: 0; opacity: 1; transform: scale(1); transition: .4s ease-out;}
.event-card .txt-box { position: relative; padding-top: 115px; width: 343px; height: 100%; float: right;}
.event-card .txt-box .tit-txt { position: absolute; left: 0; bottom: 88px;}
.txt-box .tit-txt > p { margin: 0; font-size: 22px; font-weight: 500; line-height: 1.3;}
.event-card .txt-box > .sub-txt { font-size: 12px;}
.one-ellip { position: relative;overflow: hidden; white-space: nowrap; text-overflow: ellipsis; left: 0%; width: 100%;-webkit-transition: left 3s, width 3s; -moz-transition: left 3s, width 3s; transition: left 3s, width 3s;}
.event-card .txt-box .ab_cont { padding-top: 10px;}
.ab_cont .cost { font-size: 13px; font-weight: 400; color: #7D7971;}
del { text-decoration: line-through;}
.ab_cont .discount { display: block; font-size: 1rem;}
.ab_cont .discount .txt_num { font-size: 1.6em; font-weight: 600; color: #9F2A2A;}
.event-card .txt-box .tit-txt:After { content: ''; position: absolute; top: -49px; left: 0; width: 18px; height: 35px; background: center / contain url(https://www.toxnfill2.com/imges/ico-toxnfill-g.png) no-repeat;}
.event-card-list .event-card:after { content: ''; position: absolute; bottom: 9px; right: 0; width: 58px; height: 15px; background: center / contain url(https://www.toxnfill2.com/imges/long-arrow-right-g.png) no-repeat;}
.add-ticket-btn {
font-size: 1.2rem;
font-weight: 400;
letter-spacing: -0.03em;
padding: .35rem 1rem;
border: 1px solid #ced4da;
border-radius: 50em;
transition: 0.2s;
background:white;
/* 전체 기본 스타일 */
html,
body {
height: 100vh;
margin: 0;
padding: 0;
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
background: #f8f9fa;
color: #1a1a1a;
overflow-x: hidden;
font-size: 16px;
line-height: 1.6;
}
.right { float: right;}
.left { float: left;}
.card_otxt {
position: relative;
display: block;
border-radius: 1rem;
padding: 1rem;
}
.border {
border: 1px solid #ddd;
}
.__card_otxt {
height: 95px;
}
.row>.col2 {
width: 50%;
margin-bottom: 2rem;
}
.row.row_padding>.col2, .row.row_padding>.col3, .row.row_padding>.col4, .row.row_padding>.col5 {
padding-right: 1rem;
padding-left: 1rem;
}
.row.row_padding {
margin-left: -1rem;
margin-right: -1rem;
/* 메인 컨테이너 */
.container {
max-width: 1280px;
width: 100%;
margin: 0 auto;
min-height: calc(100vh - 300px);
display: flex;
flex-wrap: wrap;
flex-direction: column;
gap: 1.5rem;
}
.border-b {
border-bottom: 1px solid #ddd;
height:5%;
/* 상단 헤더 영역 */
.header {
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
padding: 1rem 2rem;
border-bottom: 3px solid #C60B24;
flex-shrink: 0;
}
.txt_num {
font-family: 'Campton', Sans-serif;
.page-title {
font-size: clamp(1.5rem, 3vw, 1.875rem);
font-weight: 700;
color: #1a1a1a;
margin: 0;
letter-spacing: -0.025em;
}
/* 하단 콘텐츠 영역 (사이드바 + 메인) */
.content-wrapper {
flex: 1;
display: flex;
gap: 1.5rem;
min-height: 0;
}
/* 좌측 사이드바 */
.sidebar {
width: 240px;
min-width: 220px;
flex-shrink: 0;
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
height: fit-content;
max-height: 100%;
overflow-y: auto;
position: sticky;
top: 0;
}
.sidebar-header {
padding: 1.25rem 1.5rem;
font-size: 1.125rem;
font-weight: 700;
color: #C60B24;
border-bottom: 1px solid #e9ecef;
}
.category-list {
list-style: none;
padding: 1rem;
margin: 0;
}
.category-item {
margin-bottom: 0.375rem;
}
.category-link {
display: block;
width: 100%;
padding: 0.75rem 1rem;
font-size: 0.95rem;
color: #6b7280;
text-decoration: none;
background: none;
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
}
.category-link:hover {
background: #f9fafb;
color: #C60B24;
transform: translateX(2px);
}
.category-link.active {
background: rgba(198, 11, 36, 0.08);
color: #C60B24;
font-weight: 600;
color: #A73439;
border-left: 4px solid #C60B24;
}
.card_otxt .ab_cont {
/* 메인 콘텐츠 영역 */
.main-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
/* 이벤트 리스트 */
.event-list {
flex: 1;
overflow-y: auto;
padding-right: 0.5rem;
min-height: 0;
}
.event-grid {
display: flex;
flex-direction: column;
gap: 1.25rem;
padding-bottom: 2rem;
}
/* 이벤트 카드 */
.event-card {
display: flex;
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #f1f5f9;
min-height: 160px;
cursor: pointer;
position: relative;
}
.event-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border-color: #C60B24;
}
/* 지난 이벤트 스타일 */
.event-card.expired {
opacity: 0.6;
filter: grayscale(30%);
}
.event-card.expired:hover {
transform: none;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
border-color: #9ca3af;
}
.expired-badge {
position: absolute;
right: 1rem;
top: 3.4rem;
width: 8.5em;
text-align: right;
top: 12px;
right: 12px;
background: rgba(107, 114, 128, 0.9);
color: #fff;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
z-index: 2;
}
.card_otxt .fix_cont {
padding-right: 11.5rem;
.event-img {
width: 340px;
min-width: 280px;
background: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.card_otxt .fix_cont p {
.event-img img {
width: 100%;
object-fit: cover;
display: block;
}
.event-info {
flex: 1;
padding: 1.5rem;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
.event-title {
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 0.5rem;
color: #1a1a1a;
letter-spacing: -0.025em;
}
.event-desc {
color: #6b7280;
font-size: 0.9375rem;
margin-bottom: 0.75rem;
line-height: 1.5;
}
.event-meta {
font-size: 0.875rem;
color: #9ca3af;
margin-bottom: 1rem;
}
.event-date {
font-size: 0.85rem;
color: #9ca3af;
margin-bottom: 0.5rem;
}
.event-price {
font-size: 1.125rem;
color: #C60B24;
font-weight: 700;
margin-bottom: 0;
word-break: keep-all;
}
.mb3{margin-bottom: 3rem;}
.mt70{margin-top: 70%;}
/* 반응형 - 모바일 */
@media only screen and (max-width:768px){
/* main_img */
.project_wrap .same main .content5 { padding-bottom:0; }
.project_wrap .same main .main_img .text_box > div { width:100%; }
.project_wrap .same main .main_img .text_box .right_text_box { display:none; }
.project_wrap .same main .main_img .text_box .left_text_box p br { display:none; }
.project_wrap .same main .main_img .text_box .left_text_box p span { color:#000; }
.project_wrap .same main {width: 100%;}
/* 로딩 및 에러 메시지 */
.loading {
text-align: center;
padding: 2rem;
color: #6b7280;
font-size: 0.9375rem;
}
.error-message {
text-align: center;
padding: 2rem;
color: #dc2626;
font-size: 0.9375rem;
}
/* 레이어 팝업 */
.popup-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 9999;
justify-content: center;
align-items: center;
}
.popup-overlay.active {
display: flex;
}
.popup-content {
background: white;
border-radius: 16px;
padding: 2rem 2.5rem;
text-align: center;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 90%;
animation: popupIn 0.25s ease-out;
}
@keyframes popupIn {
from {
transform: scale(0.9);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.popup-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.popup-title {
font-size: 1.25rem;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 0.5rem;
}
.popup-message {
font-size: 0.95rem;
color: #6b7280;
margin-bottom: 1.5rem;
line-height: 1.5;
}
.popup-close-btn {
background: #C60B24;
color: white;
border: none;
border-radius: 8px;
padding: 0.75rem 2rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.popup-close-btn:hover {
background: #a5091e;
}
/* 스크롤바 커스터마이징 */
.event-list::-webkit-scrollbar,
.sidebar::-webkit-scrollbar {
width: 6px;
}
.event-list::-webkit-scrollbar-track,
.sidebar::-webkit-scrollbar-track {
background: #f8fafc;
border-radius: 3px;
}
.event-list::-webkit-scrollbar-thumb,
.sidebar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.event-list::-webkit-scrollbar-thumb:hover,
.sidebar::-webkit-scrollbar-thumb:hover {
background: #C60B24;
}
/* 반응형 디자인 - 태블릿 */
@media (max-width: 1024px) {
.container {
padding: 1rem;
gap: 1rem;
}
.content-wrapper {
gap: 1rem;
}
.sidebar {
width: 220px;
min-width: 200px;
}
.event-img {
width: 220px;
min-width: 220px;
}
.event-img img {
height: 140px;
}
.header {
padding: 1.25rem 1.5rem;
}
.page-title {
font-size: clamp(1.375rem, 2.8vw, 1.75rem);
}
}
/* 반응형 디자인 - 모바일 */
@media (max-width: 768px) {
.container {
flex-direction: column;
padding: 1rem;
gap: 1rem;
}
.content-wrapper {
flex-direction: column;
gap: 1rem;
}
.sidebar {
width: 100%;
position: static;
max-height: none;
order: 1;
}
.category-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
padding: 1rem;
}
.category-link {
text-align: center;
padding: 0.75rem 0.5rem;
font-size: 0.875rem;
}
.main-content {
order: 2;
}
.event-list {
overflow-y: visible;
height: auto;
}
.event-card {
flex-direction: column;
min-height: auto;
}
.event-img {
width: 100%;
min-width: auto;
}
.event-img img {
height: 200px;
}
.event-info {
padding: 1.25rem;
}
.header {
padding: 1rem 1.5rem;
border-radius: 12px;
}
.page-title {
font-size: clamp(1.25rem, 2.5vw, 1.5rem);
}
.event-title {
font-size: 1.125rem;
}
.event-price {
font-size: 1rem;
}
}
/* 작은 모바일 */
@media (max-width: 480px) {
.container {
padding: 0.75rem;
}
.header {
padding: 0.875rem 1rem;
}
.page-title {
font-size: clamp(1.125rem, 2.2vw, 1.375rem);
}
.event-info {
padding: 1rem;
}
.event-title {
font-size: 1rem;
}
.event-desc {
font-size: 0.875rem;
}
}

View File

@@ -0,0 +1,248 @@
* {
box-sizing: border-box;
}
html,
body {
height: 100vh;
margin: 0;
padding: 0;
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f8f9fa;
color: #1a1a1a;
overflow-x: hidden;
font-size: 16px;
line-height: 1.6;
}
.container {
max-width: 900px;
width: 100%;
margin: 0 auto;
min-height: calc(100vh - 300px);
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
color: #9ca3af;
padding: 0.5rem 0;
}
.breadcrumb a {
color: #6b7280;
text-decoration: none;
transition: color 0.2s;
}
.breadcrumb a:hover {
color: #C60B24;
}
.review-article {
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.review-header {
padding: 2rem 2rem 1.5rem;
border-bottom: 1px solid #f1f5f9;
}
.review-title {
font-size: clamp(1.375rem, 3vw, 1.75rem);
font-weight: 700;
color: #1a1a1a;
margin: 0 0 1rem;
letter-spacing: -0.025em;
line-height: 1.4;
}
.review-meta {
display: flex;
gap: 1.5rem;
font-size: 0.875rem;
color: #9ca3af;
margin-bottom: 0.75rem;
}
.review-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.review-tag {
display: inline-block;
padding: 0.25rem 0.75rem;
background: rgba(198, 11, 36, 0.08);
color: #C60B24;
border-radius: 20px;
font-size: 0.8125rem;
font-weight: 500;
}
.review-content {
padding: 2rem;
min-height: 300px;
}
.review-content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 0.5rem 0;
}
.review-content p {
margin: 0 0 0.75rem;
line-height: 1.8;
}
.review-content h1,
.review-content h2,
.review-content h3 {
margin: 1.5rem 0 0.75rem;
font-weight: 700;
}
.review-content blockquote {
border-left: 4px solid #C60B24;
padding: 0.75rem 1rem;
margin: 1rem 0;
background: #fafafa;
border-radius: 0 8px 8px 0;
}
.review-content ul,
.review-content ol {
padding-left: 1.5rem;
margin: 0.5rem 0;
}
.review-content li {
margin: 0.25rem 0;
list-style: inherit;
}
.ql-editor .ql-align-center {
text-align: center;
}
.ql-editor .ql-align-right {
text-align: right;
}
.ql-editor .ql-align-justify {
text-align: justify;
}
.ql-editor .ql-indent-1 {
padding-left: 3em;
}
.ql-editor .ql-indent-2 {
padding-left: 6em;
}
.ql-editor .ql-indent-3 {
padding-left: 9em;
}
.ql-editor .ql-size-small {
font-size: 0.75em;
}
.ql-editor .ql-size-large {
font-size: 1.5em;
}
.ql-editor .ql-size-huge {
font-size: 2.5em;
}
.ql-editor .ql-font-serif {
font-family: Georgia, 'Times New Roman', serif;
}
.ql-editor .ql-font-monospace {
font-family: 'Monaco', 'Courier New', monospace;
}
.btn-area {
display: flex;
justify-content: center;
padding-bottom: 2rem;
}
.btn-list {
display: inline-flex;
align-items: center;
padding: 0.75rem 2.5rem;
background: white;
color: #1a1a1a;
border: 1px solid #e5e7eb;
border-radius: 10px;
font-size: 0.9375rem;
font-weight: 600;
text-decoration: none;
transition: all 0.2s;
}
.btn-list:hover {
border-color: #C60B24;
color: #C60B24;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.loading {
text-align: center;
padding: 3rem;
color: #6b7280;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
gap: 1rem;
}
.review-header {
padding: 1.5rem;
}
.review-content {
padding: 1.5rem;
}
.review-title {
font-size: 1.25rem;
}
.review-meta {
flex-direction: column;
gap: 0.25rem;
}
}
@media (max-width: 480px) {
.container {
padding: 0.75rem;
}
.review-header {
padding: 1rem;
}
.review-content {
padding: 1rem;
}
}

View File

@@ -0,0 +1,399 @@
* {
box-sizing: border-box;
}
html,
body {
height: 100vh;
margin: 0;
padding: 0;
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f8f9fa;
color: #1a1a1a;
overflow-x: hidden;
font-size: 16px;
line-height: 1.6;
}
.container {
max-width: 1280px;
width: 100%;
margin: 0 auto;
min-height: calc(100vh - 300px);
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.header {
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
padding: 1.5rem 2rem;
border-bottom: 3px solid #C60B24;
}
.page-title {
font-size: clamp(1.5rem, 3vw, 1.875rem);
font-weight: 700;
color: #1a1a1a;
margin: 0;
letter-spacing: -0.025em;
}
.page-subtitle {
font-size: 0.95rem;
color: #6b7280;
margin: 0.25rem 0 0;
}
.search-area {
display: flex;
justify-content: flex-end;
}
.search-box {
display: flex;
gap: 0.5rem;
}
.search-box input {
width: 280px;
padding: 0.625rem 1rem;
border: 1px solid #e5e7eb;
border-radius: 10px;
font-size: 0.9375rem;
outline: none;
transition: border-color 0.2s;
}
.search-box input:focus {
border-color: #C60B24;
}
.search-btn {
padding: 0.625rem 1.25rem;
background: #C60B24;
color: white;
border: none;
border-radius: 10px;
font-size: 0.9375rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.search-btn:hover {
background: #a5091e;
}
.review-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 1.5rem;
}
.review-card {
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.06);
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #f1f5f9;
cursor: pointer;
display: flex;
flex-direction: column;
}
.review-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border-color: #C60B24;
}
/* ===== 이미지 슬라이더 ===== */
.review-slider {
position: relative;
width: 100%;
aspect-ratio: 4 / 3;
overflow: hidden;
background: #f1f5f9;
}
.review-slider-track {
display: flex;
width: 100%;
height: 100%;
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.review-slider-track img {
min-width: 100%;
max-width: 100%;
height: 100%;
object-fit: cover;
flex-shrink: 0;
}
.slider-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 32px;
height: 32px;
background: rgba(0, 0, 0, 0.45);
color: white;
border: none;
border-radius: 50%;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.25s;
z-index: 2;
line-height: 1;
}
.review-slider:hover .slider-arrow {
opacity: 1;
}
.slider-arrow.prev {
left: 8px;
}
.slider-arrow.next {
right: 8px;
}
.slider-arrow:hover {
background: rgba(0, 0, 0, 0.7);
}
.slider-dots {
position: absolute;
bottom: 8px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 5px;
z-index: 2;
}
.slider-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.5);
border: none;
cursor: pointer;
padding: 0;
transition: all 0.25s;
}
.slider-dot.active {
background: white;
transform: scale(1.3);
}
.slider-count {
position: absolute;
top: 10px;
right: 10px;
background: rgba(0, 0, 0, 0.55);
color: white;
font-size: 0.75rem;
padding: 2px 8px;
border-radius: 12px;
z-index: 2;
}
.review-no-image {
width: 100%;
aspect-ratio: 4 / 3;
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
display: flex;
align-items: center;
justify-content: center;
font-size: 2.5rem;
color: #cbd5e1;
}
.review-card-body {
padding: 1.25rem 1.5rem 1.5rem;
flex: 1;
display: flex;
flex-direction: column;
}
.review-card-title {
font-size: 1.0625rem;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 0.5rem;
letter-spacing: -0.025em;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.review-card-summary {
color: #6b7280;
font-size: 0.8125rem;
line-height: 1.6;
flex: 1;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
margin-bottom: 0.75rem;
}
.review-card-tags {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
margin-bottom: 0.75rem;
}
.review-tag {
display: inline-block;
padding: 0.2rem 0.5rem;
background: rgba(198, 11, 36, 0.08);
color: #C60B24;
border-radius: 20px;
font-size: 0.6875rem;
font-weight: 500;
}
.review-card-footer {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8125rem;
color: #9ca3af;
}
.review-card-footer .views {
display: flex;
align-items: center;
gap: 0.25rem;
}
.pagination-area {
display: flex;
justify-content: center;
gap: 0.375rem;
padding: 1rem 0 2rem;
}
.page-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #e5e7eb;
background: white;
border-radius: 8px;
font-size: 0.875rem;
color: #6b7280;
cursor: pointer;
transition: all 0.2s;
}
.page-btn:hover {
border-color: #C60B24;
color: #C60B24;
}
.page-btn.active {
background: #C60B24;
color: white;
border-color: #C60B24;
}
.page-btn.disabled {
opacity: 0.4;
cursor: default;
}
.loading {
text-align: center;
padding: 3rem;
color: #6b7280;
font-size: 0.9375rem;
grid-column: 1 / -1;
}
.empty-state {
text-align: center;
padding: 4rem 2rem;
grid-column: 1 / -1;
}
.empty-state .icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.empty-state .message {
font-size: 1.125rem;
color: #6b7280;
font-weight: 500;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
gap: 1rem;
}
.header {
padding: 1rem 1.5rem;
border-radius: 12px;
}
.page-title {
font-size: clamp(1.25rem, 2.5vw, 1.5rem);
}
.review-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.search-area {
justify-content: stretch;
}
.search-box {
width: 100%;
}
.search-box input {
flex: 1;
width: auto;
}
.slider-arrow {
opacity: 0.7;
}
}
@media (max-width: 480px) {
.container {
padding: 0.75rem;
}
.header {
padding: 0.875rem 1rem;
}
.review-card-body {
padding: 1rem;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -7,7 +7,7 @@ function fn_init() {
//인스타그램 리스트
fn_selectListWebInstagramJson();
//상단, 하단 배너
//상단, 하단 배너
fn_selectListWebMainBannerTypeAJson();
fn_selectListWebMainBannerTypeBJson();
}
@@ -46,22 +46,22 @@ function hideBackgroundMask() {
* content1 스와이퍼
************************************************/
const cont1Urls = [
'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4' // 첫 번째 슬라이드 URL
,'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=1' // 두 번째 슬라이드 URL
,'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=5' // 세 번째 슬라이드 URL
,'https://petit.madeu.co.kr/index' // 네 번째 슬라이드 URL
,'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=1' // 다섯 번째 슬라이드 URL
'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4' // 첫 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=1' // 두 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=5' // 세 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/index' // 네 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=1' // 다섯 번째 슬라이드 URL
// 필요시 추가
];
const bullet1 = ['레이저리프팅','쉬다주사(Face)','스컬트라','스킨부스터', '필러']
const bullet1 = ['레이저리프팅', '쉬다주사(Face)', '스컬트라', '스킨부스터', '필러']
const useCont1Loop = bullet1.length > 5; // slidesPerView(1.3)보다 많을 때만 loop
const cont1Swiper = new Swiper('.cont1_swiper', {
loop: useCont1Loop,
slidesPerView: 'auto',
spaceBetween: 10,
autoplay : {
autoplay: {
disableOnInteraction: false,
},
pagination: {
@@ -73,22 +73,22 @@ const cont1Swiper = new Swiper('.cont1_swiper', {
},
on: {
init: function () {
document.querySelectorAll('.cont1_swiper .swiper-slide').forEach(function(slide, idx) {
document.querySelectorAll('.cont1_swiper .swiper-slide').forEach(function (slide, idx) {
slide.style.cursor = 'pointer';
slide.setAttribute('data-url', cont1Urls[idx] || cont1Urls[0]);
slide.addEventListener('click', function() {
slide.addEventListener('click', function () {
const url = slide.getAttribute('data-url');
if(url) window.open(url, '_blank');
if (url) window.open(url, '_blank');
});
});
},
slideChange: function () {
document.querySelectorAll('.cont1_swiper .swiper-slide').forEach(function(slide, idx) {
document.querySelectorAll('.cont1_swiper .swiper-slide').forEach(function (slide, idx) {
slide.style.cursor = 'pointer';
slide.setAttribute('data-url', cont1Urls[idx] || cont1Urls[0]);
slide.onclick = function() {
slide.onclick = function () {
const url = slide.getAttribute('data-url');
if(url) window.open(url, '_blank');
if (url) window.open(url, '_blank');
};
});
}
@@ -99,21 +99,21 @@ const cont1Swiper = new Swiper('.cont1_swiper', {
* content2 스와이퍼
************************************************/
const cont2Urls = [
'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4' // 첫 번째 슬라이드 URL
,'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4' // 두 번째 슬라이드 URL
,'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4' // 세 번째 슬라이드 URL
,'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4' // 네 번째 슬라이드 URL
,'https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4' // 다섯 번째 슬라이드 URL
'https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&categoryNo=4&postNo=8' // 첫 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&categoryNo=4&postNo=2' // 두 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&categoryNo=4&postNo=10' // 세 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&categoryNo=4&postNo=3' // 네 번째 슬라이드 URL
, 'https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&categoryNo=4&postNo=4' // 다섯 번째 슬라이드 URL
];
const bullet2 = ['티타늄','써마지','울쎄라', '줄기세포', '튠페이스']
const bullet2 = ['써마지FLX', '울쎄라', '온다리프팅', '티타늄리프팅', '튠페이스']
const useCont2Loop = bullet2.length > 5; // slidesPerView(1.3)보다 많을 때만 loop
const cont2Swiper = new Swiper('.cont2_swiper', {
loop: useCont2Loop,
slidesPerView: 'auto',
spaceBetween: 10,
autoplay : {
autoplay: {
disableOnInteraction: false,
},
pagination: {
@@ -125,22 +125,22 @@ const cont2Swiper = new Swiper('.cont2_swiper', {
},
on: {
init: function () {
document.querySelectorAll('.cont2_swiper .swiper-slide').forEach(function(slide, idx) {
document.querySelectorAll('.cont2_swiper .swiper-slide').forEach(function (slide, idx) {
slide.style.cursor = 'pointer';
slide.setAttribute('data-url', cont2Urls[idx] || cont2Urls[0]);
slide.addEventListener('click', function() {
slide.addEventListener('click', function () {
const url = slide.getAttribute('data-url');
if(url) window.open(url, '_blank');
if (url) window.open(url, '_blank');
});
});
},
slideChange: function () {
document.querySelectorAll('.cont2_swiper .swiper-slide').forEach(function(slide, idx) {
document.querySelectorAll('.cont2_swiper .swiper-slide').forEach(function (slide, idx) {
slide.style.cursor = 'pointer';
slide.setAttribute('data-url', cont2Urls[idx] || cont2Urls[0]);
slide.onclick = function() {
slide.onclick = function () {
const url = slide.getAttribute('data-url');
if(url) window.open(url, '_blank');
if (url) window.open(url, '_blank');
};
});
}
@@ -150,7 +150,7 @@ const cont2Swiper = new Swiper('.cont2_swiper', {
/****************************************************************************
* 팝업 리스트 조회 (마스킹 기능 추가)
****************************************************************************/
function fn_selectListWebPopupJson(){
function fn_selectListWebPopupJson() {
let formData = new FormData();
$.ajax({
@@ -161,8 +161,8 @@ function fn_selectListWebPopupJson(){
contentType: false,
type: 'POST',
async: true,
success: function(data){
if(data.msgCode=='0'){
success: function (data) {
if (data.msgCode == '0') {
//쿠기값 확인
let cookiedata = document.cookie;
if (cookiedata.indexOf("popup=done") < 0) {
@@ -186,9 +186,9 @@ function fn_selectListWebPopupJson(){
// 탭 콘텐츠 생성
let contentHTML = '';
contentHTML += '<div role="tabpanel" class="tab-pane' + isActive + '" id="content' + (i+1) + '">';
contentHTML += '<div role="tabpanel" class="tab-pane' + isActive + '" id="content' + (i + 1) + '">';
contentHTML += '<a href="' + data.rows[i].url + '" target="_blank" rel="noopener">';
contentHTML += '<img src="' + CDN_URL + imgPath + '" alt="event_con' + (i+1) + '" />';
contentHTML += '<img src="' + CDN_URL + imgPath + '" alt="event_con' + (i + 1) + '" />';
contentHTML += '</a>';
contentHTML += '</div>';
popupContentList.append(contentHTML);
@@ -196,7 +196,7 @@ function fn_selectListWebPopupJson(){
// 탭 리스트 생성 (data-toggle 제거하고 클릭 이벤트 직접 처리)
let tabHTML = '';
tabHTML += '<li role="presentation"' + (isActive ? ' class="active"' : '') + '>';
tabHTML += '<a href="#content' + (i+1) + '" role="tab" id="contentTitle' + (i+1) + '" data-target="content' + (i+1) + '">';
tabHTML += '<a href="#content' + (i + 1) + '" role="tab" id="contentTitle' + (i + 1) + '" data-target="content' + (i + 1) + '">';
tabHTML += fn_addBrAfterFirstWord(data.rows[i].title);
tabHTML += '</a>';
tabHTML += '</li>';
@@ -207,27 +207,27 @@ function fn_selectListWebPopupJson(){
setupTabEvents();
setupPopupCloseEvents();
}
}else{
} else {
$('.popup').removeClass('open');
hideBackgroundMask(); // 추가
}
}else if('-2' == data.msgCode){
modalEvent.danger("로그인 오류", data.msgDesc, function(){
} else if ('-2' == data.msgCode) {
modalEvent.danger("로그인 오류", data.msgDesc, function () {
fn_leftFormAction('/weblogin/logout.do');
});
}else{
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
},
error : function(xhr, status, error) {
error: function (xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend:function(){
beforeSend: function () {
// 로딩열기
$(".loading-image-layer").show();
},
complete:function(){
complete: function () {
// 로딩닫기
$(".loading-image-layer").hide();
}
@@ -236,7 +236,7 @@ function fn_selectListWebPopupJson(){
// 탭 이벤트 설정 함수 추가
function setupTabEvents() {
$('#popupTabList a[role="tab"]').off('click').on('click', function(e) {
$('#popupTabList a[role="tab"]').off('click').on('click', function (e) {
e.preventDefault();
const targetId = $(this).data('target');
@@ -269,7 +269,7 @@ function setupPopupCloseEvents() {
setCookie('done', 1);
} else {
$('.popup').removeClass('open');
hideBackgroundMask();
hideBackgroundMask();
}
});
}
@@ -290,7 +290,7 @@ function fn_addBrAfterFirstWord(input) {
function setCookie(value, expiredays) {
let todayDate = new Date();
todayDate.setDate(todayDate.getDate() + expiredays);
document.cookie = "popup=" + escape( value ) + "; path=/; expires=" + todayDate.toGMTString() + ";";
document.cookie = "popup=" + escape(value) + "; path=/; expires=" + todayDate.toGMTString() + ";";
$('.popup').removeClass('open');
// *** 배경 마스킹 제거 추가 ***
@@ -300,7 +300,7 @@ function setCookie(value, expiredays) {
/****************************************************************************
* 메인 배너 리스트 조회
****************************************************************************/
function fn_selectListWebMainBannerTypeAJson(){
function fn_selectListWebMainBannerTypeAJson() {
let formData = new FormData();
formData.append('bannerType', 'PT');
@@ -312,8 +312,8 @@ function fn_selectListWebMainBannerTypeAJson(){
contentType: false,
type: 'POST',
async: true,
success: function(data){
if(data.msgCode=='0'){
success: function (data) {
if (data.msgCode == '0') {
let totalCount = data.rows.length;
if (0 < totalCount) {
let listHTML = '';
@@ -326,8 +326,8 @@ function fn_selectListWebMainBannerTypeAJson(){
// 이미지와 링크를 a 태그로 감싸기 (url 파라미터 사용)
listHTML += '<div class="swiper-slide" style="background:' + backgroundColor + ';" aria-label="' + currentAriaLabel + '">';
listHTML += ' <a href="' + data.rows[i].url + '" target="_blank" rel="noopener">';
listHTML += ' <img class="pc" src="' + CDN_URL + data.rows[i].webFilePath + '" alt="banner' + (i+1) + '" />';
listHTML += ' <img class="mb" src="' + CDN_URL + data.rows[i].mobileFilePath + '" alt="banner' + (i+1) + '" />';
listHTML += ' <img class="pc" src="' + CDN_URL + data.rows[i].webFilePath + '" alt="banner' + (i + 1) + '" />';
listHTML += ' <img class="mb" src="' + CDN_URL + data.rows[i].mobileFilePath + '" alt="banner' + (i + 1) + '" />';
listHTML += ' </a>';
listHTML += '</div>';
}
@@ -340,7 +340,7 @@ function fn_selectListWebMainBannerTypeAJson(){
const mainBannerSwiper = new Swiper('.main_banner_swiper', {
loop: useMainBannerLoop,
slidesPerView: 1,
autoplay : {
autoplay: {
disableOnInteraction: false,
},
pagination: {
@@ -361,7 +361,7 @@ function fn_selectListWebMainBannerTypeAJson(){
},
});
///////// swiper-pagination-bullet 넓이 동적 조정 /////////
///////// swiper-pagination-bullet 넓이 동적 조정 /////////
function adjustBulletWidth() {
const bullets = document.querySelectorAll('.main_banner_pagination .swiper-pagination-bullet');
const paginationWidth = 490;
@@ -373,32 +373,32 @@ function fn_selectListWebMainBannerTypeAJson(){
}
///////// 활성화된 swiper-pagination-bullet 업데이트 /////////
function updateActiveBullets(activeIndex) {
const bullets = document.querySelectorAll('.mainbannerpagination .swiper-pagination-bullet');
bullets.forEach(bullet => bullet.classList.remove('swiper-pagination-bullet-active')); // 먼저 모두 제거
if (bullets[activeIndex]) {
bullets[activeIndex].classList.add('swiper-pagination-bullet-active'); // 하나만 추가
}
}
function updateActiveBullets(activeIndex) {
const bullets = document.querySelectorAll('.mainbannerpagination .swiper-pagination-bullet');
bullets.forEach(bullet => bullet.classList.remove('swiper-pagination-bullet-active')); // 먼저 모두 제거
if (bullets[activeIndex]) {
bullets[activeIndex].classList.add('swiper-pagination-bullet-active'); // 하나만 추가
}
}
}else{$("#mainBannerList").empty();}
}else if('-2' == data.msgCode){
modalEvent.danger("로그인 오류", data.msgDesc, function(){
} else { $("#mainBannerList").empty(); }
} else if ('-2' == data.msgCode) {
modalEvent.danger("로그인 오류", data.msgDesc, function () {
fn_leftFormAction('/weblogin/logout.do');
});
}else{
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
},
error : function(xhr, status, error) {
error: function (xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend:function(){
beforeSend: function () {
// 로딩열기
$(".loading-image-layer").show();
},
complete:function(){
complete: function () {
// 로딩닫기
$(".loading-image-layer").hide();
}
@@ -408,7 +408,7 @@ function fn_selectListWebMainBannerTypeAJson(){
/****************************************************************************
* 서브 배너 이벤트 리스트 조회
****************************************************************************/
function fn_selectListWebMainBannerTypeBJson(){
function fn_selectListWebMainBannerTypeBJson() {
let formData = new FormData();
formData.append('bannerType', 'PB');
@@ -420,8 +420,8 @@ function fn_selectListWebMainBannerTypeBJson(){
contentType: false,
type: 'POST',
async: true,
success: function(data){
if(data.msgCode=='0'){
success: function (data) {
if (data.msgCode == '0') {
let totalCount = data.rows.length;
if (0 < totalCount) {
@@ -435,8 +435,8 @@ function fn_selectListWebMainBannerTypeBJson(){
// 이미지와 링크를 a 태그로 감싸기 (url 파라미터 사용)
listHTML += '<div class="swiper-slide" style="background:' + backgroundColor + ';" aria-label="' + currentAriaLabel + '">';
listHTML += ' <a href="' + data.rows[i].url + '" target="_blank" rel="noopener">';
listHTML += ' <img class="pc" src="' + CDN_URL + data.rows[i].webFilePath + '" alt="banner' + (i+1) + '" />';
listHTML += ' <img class="mb" src="' + CDN_URL + data.rows[i].mobileFilePath + '" alt="banner' + (i+1) + '" />';
listHTML += ' <img class="pc" src="' + CDN_URL + data.rows[i].webFilePath + '" alt="banner' + (i + 1) + '" />';
listHTML += ' <img class="mb" src="' + CDN_URL + data.rows[i].mobileFilePath + '" alt="banner' + (i + 1) + '" />';
listHTML += ' <button class="detail_btn">Detail view ></button>';
listHTML += ' </a>';
listHTML += '</div>';
@@ -450,7 +450,7 @@ function fn_selectListWebMainBannerTypeBJson(){
const subBannerSwiper = new Swiper('.sub_banner_swiper', {
loop: useSubBannerLoop,
slidesPerView: 1,
autoplay : {
autoplay: {
disableOnInteraction: false,
},
pagination: {
@@ -471,7 +471,7 @@ function fn_selectListWebMainBannerTypeBJson(){
},
});
///////// swiper-pagination-bullet 넓이 동적 조정 /////////
///////// swiper-pagination-bullet 넓이 동적 조정 /////////
function adjustSubBulletWidth() {
const bullets = document.querySelectorAll('.sub_banner_pagination .swiper-pagination-bullet');
const paginationWidth = 490;
@@ -495,24 +495,24 @@ function fn_selectListWebMainBannerTypeBJson(){
});
}
}else{$("#subBannerList").empty();}
}else if('-2' == data.msgCode){
modalEvent.danger("로그인 오류", data.msgDesc, function(){
} else { $("#subBannerList").empty(); }
} else if ('-2' == data.msgCode) {
modalEvent.danger("로그인 오류", data.msgDesc, function () {
fn_leftFormAction('/weblogin/logout.do');
});
}else{
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
},
error : function(xhr, status, error) {
error: function (xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend:function(){
beforeSend: function () {
// 로딩열기
$(".loading-image-layer").show();
},
complete:function(){
complete: function () {
// 로딩닫기
$(".loading-image-layer").hide();
}
@@ -522,7 +522,7 @@ function fn_selectListWebMainBannerTypeBJson(){
/****************************************************************************
* 인스타그램 피드 리스트 조회 (백엔드 API 사용)
****************************************************************************/
function fn_selectListWebInstagramJson(){
function fn_selectListWebInstagramJson() {
let formData = new FormData();
$.ajax({
url: encodeURI('/webinstagram/selectListWebInstagram.do'),
@@ -532,8 +532,8 @@ function fn_selectListWebInstagramJson(){
contentType: false,
type: 'POST',
async: true,
success: function(data){
if(data.msgCode=='0'){
success: function (data) {
if (data.msgCode == '0') {
let totalCount = data.rows.length;
if (0 < totalCount) {
let listHTML = '';
@@ -544,12 +544,12 @@ function fn_selectListWebInstagramJson(){
let permalink = data.rows[i].permalink;
let mediaType = data.rows[i].media_type || '';
let thumbnailUrl = data.rows[i].thumbnail_url || mediaUrl;
listHTML += '<div class="swiper-slide" aria-label="'+currentAriaLabel+'">';
listHTML += ' <a href="'+permalink+'" target="_blank" rel="noopener">';
listHTML += '<div class="swiper-slide" aria-label="' + currentAriaLabel + '">';
listHTML += ' <a href="' + permalink + '" target="_blank" rel="noopener">';
if (mediaType === 'VIDEO' || mediaType === 'REEL') {
listHTML += ' <img src="'+thumbnailUrl+'" alt="instagram'+(i+1)+'" />';
listHTML += ' <img src="' + thumbnailUrl + '" alt="instagram' + (i + 1) + '" />';
} else {
listHTML += ' <img src="'+mediaUrl+'" alt="instagram'+(i+1)+'" />';
listHTML += ' <img src="' + mediaUrl + '" alt="instagram' + (i + 1) + '" />';
}
listHTML += ' </a>';
listHTML += '</div>';
@@ -558,22 +558,22 @@ function fn_selectListWebInstagramJson(){
// 커스텀 내비게이션과 연동
initInstagramCustomNav(data);
}
}else if('-2' == data.msgCode){
modalEvent.danger("로그인 오류", data.msgDesc, function(){
} else if ('-2' == data.msgCode) {
modalEvent.danger("로그인 오류", data.msgDesc, function () {
fn_leftFormAction('/weblogin/logout.do');
});
}else{
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
},
error : function(xhr, status, error) {
error: function (xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend:function(){
beforeSend: function () {
// 로딩열기
$(".loading-image-layer").show();
},
complete:function(){
complete: function () {
// 로딩닫기
$(".loading-image-layer").hide();
}
@@ -601,7 +601,7 @@ function initInstagramCustomNav(data) {
const thumbnailUrl = data.rows[dataIndex].thumbnail_url || mediaUrl;
const isActive = dataIndex === currentActiveIndex ? 'active' : '';
thumbsHTML += `<div class="custom-swiper-thumb ${isActive}" data-index="${dataIndex}">
<img src="${thumbnailUrl}" alt="thumb${dataIndex+1}" />
<img src="${thumbnailUrl}" alt="thumb${dataIndex + 1}" />
</div>`;
}
document.querySelector('.custom-swiper-thumbs').innerHTML = thumbsHTML;
@@ -611,7 +611,7 @@ function initInstagramCustomNav(data) {
createThumbnails(0);
// Swiper 인스턴스
setTimeout(function() {
setTimeout(function () {
const instagramSwiper = new Swiper('.instagram_swiper', {
loop: true,
autoplay: {
@@ -628,7 +628,7 @@ function initInstagramCustomNav(data) {
},
},
on: {
slideChange: function() {
slideChange: function () {
const newIndex = this.realIndex % totalCount;
currentActiveIndex = newIndex; // 활성 인덱스 업데이트
@@ -655,15 +655,15 @@ function initInstagramCustomNav(data) {
});
// 좌우 버튼 이벤트
document.querySelector('.custom-swiper-btn.prev').onclick = function() {
document.querySelector('.custom-swiper-btn.prev').onclick = function () {
instagramSwiper.slidePrev();
};
document.querySelector('.custom-swiper-btn.next').onclick = function() {
document.querySelector('.custom-swiper-btn.next').onclick = function () {
instagramSwiper.slideNext();
};
// 썸네일 클릭 이벤트
document.querySelector('.custom-swiper-thumbs').onclick = function(e) {
document.querySelector('.custom-swiper-thumbs').onclick = function (e) {
const thumb = e.target.closest('.custom-swiper-thumb');
if (thumb) {
const targetIndex = parseInt(thumb.dataset.index);
@@ -684,7 +684,7 @@ function initInstagramCustomNav(data) {
// 활성 썸네일 표시
function updateCustomThumbActive(activeIdx) {
document.querySelectorAll('.custom-swiper-thumb').forEach(function(thumb) {
document.querySelectorAll('.custom-swiper-thumb').forEach(function (thumb) {
const thumbIndex = parseInt(thumb.dataset.index);
thumb.classList.toggle('active', thumbIndex === activeIdx);
});
@@ -699,16 +699,16 @@ function initInstagramCustomNav(data) {
fn_init();
// 기존 팝업 닫기 버튼 이벤트 (마스킹 기능 추가)
document.getElementById('btnPopupClose').addEventListener('click',function (){
document.getElementById('btnPopupClose').addEventListener('click', function () {
$('.popup').removeClass('open');
// *** 배경 마스킹 제거 추가 ***
hideBackgroundMask();
});
// 추가 이벤트 리스너들 (새로 추가)
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
// ESC 키로 팝업 닫기
document.addEventListener('keydown', function(e) {
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && document.querySelector('.popup.open')) {
$('.popup').removeClass('open');
hideBackgroundMask();
@@ -718,7 +718,7 @@ document.addEventListener('DOMContentLoaded', function() {
// 마스크 영역 클릭 시 팝업 닫기 (선택사항)
const mask = document.querySelector('.popup-background-mask');
if (mask) {
mask.addEventListener('click', function() {
mask.addEventListener('click', function () {
$('.popup').removeClass('open');
hideBackgroundMask();
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,236 +1,314 @@
/************************************************
* 초기화
************************************************/
function fn_init() {
fn_SelectDetail(category_div_cd, category_no, post_no);
// 전역 변수
let procedureChoices;
let priceList = [];
const totalEl = document.getElementById('total');
const reserveBtn = document.getElementById('reserve-btn');
$("#btn_makeReservation").on("click", function(){
fn_moveReservation(category_div_cd, category_no, post_no);
});
}
// 초기화
fn_SelectDetail(category_div_cd, category_no, post_no);
/****************************************************************************
* 카테고리 목록 가져오기
****************************************************************************/
function fn_SelectDetail(category_div_cd, category_no, post_no){
function fn_SelectDetail(category_div_cd, category_no, post_no) {
let formData = new FormData();
formData.append('CATEGORY_DIV_CD', category_div_cd);
formData.append('CATEGORY_DIV_CD', category_div_cd);
formData.append('CATEGORY_NO', category_no);
formData.append('POST_NO', post_no);
$.ajax({
url: encodeURI('/webevent/selectEventDetail.do'),
data: formData,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
async: true,
success: function(data){
if(data.msgCode=='0'){
//화면 데이터 변경
$('#category_nm').text(data.rows.CATEGORY_NM);
$.ajax({
url: encodeURI('/webevent/selectEventDetail.do'),
data: formData,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
async: true,
success: function (data) {
if (data.msgCode == '0') {
// 화면 데이터 변경
$('#title').text(data.rows.TITLE);
$('#serviceThumb').attr('src', data.rows.THUMBNAIL_PATH);
$('#main_title').text(data.rows.TITLE);
$('.thumbnail-bottom-txt').text(data.rows.THUMBNAIL_BOTTOM_TXT);
$('#serviceThumb').attr('src', CDN_URL + data.rows.THUMBNAIL_PATH);
$('#thumbnail-bottom-txt').text(data.rows.THUMBNAIL_BOTTOM_TXT);
$('#contents').text(data.rows.CONTENT);
if(data.rows.PRICE == null || data.rows.PRICE == undefined){
$('#startprice').text('0');
}else{
$('#startprice').text(data.rows.PRICE.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","));
if (data.rows.PRICE == null || data.rows.PRICE == undefined) {
$('#price').text('0');
} else {
$('#price').text(data.rows.PRICE.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","));
}
$('#hashtag').text(data.rows.HASHTAG);
$('#contents_path').text(data.rows.THUMBNAIL_BOTTOM_TXT);
$('#contents_path').attr('src', data.rows.CONTENTS_PATH);
//시술 목록 데이터 입력
let totalCount = data.price.length;
if (0 < totalCount) {
for (let i = 0; i < data.price.length; i++) {
let listHTML = '';
listHTML += '<li class="optipon_item sepr_wrap">';
listHTML += ' <div class="item_subprice">';
listHTML += ' <div class="cs-checkbox">';
listHTML += ' <input type="checkbox" class="idxChk" id="checkboxID' + i + '">';
listHTML += ' <label class="cs-checkbox-label d-block" for="checkboxID' + i + '">';
listHTML += ' <div class="oi-wrap">';
listHTML += ' <div class="oi-txt">';
listHTML += ' <span class="oi-tit-txt">' + data.price[i].TREATMENT_PROCEDURE_NAME;
listHTML += ' </span>';
listHTML += ' </div>';
listHTML += ' <div class="oi-price">';
listHTML += ' <del class="original_price d-block">';
listHTML += ' <b>';
if(data.price[i].DISCOUNT_PRICE == null || data.price[i].DISCOUNT_PRICE == undefined){
if(data.price[i].PRICE == null || data.price[i].PRICE == undefined){
listHTML += '0';
}else{
listHTML += (data.price[i].PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}else{
listHTML += (data.price[i].DISCOUNT_PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
// header 동적 변경
$('#header-category-nm').text(data.rows.CATEGORY_NM);
$('#header-title').text(data.rows.TITLE);
// 해시태그 처리
var hashtagHtml = '';
if (data.rows.HASHTAG) {
var tags = data.rows.HASHTAG.split('#');
tags.forEach(function (tag) {
var trimmed = tag.trim();
if (trimmed) {
hashtagHtml += '<span class="hashtag">#' + trimmed + '</span>';
}
listHTML += '</b>원';
listHTML += ' </del>';
listHTML += ' <span class="discount_price">';
if(data.price[i].DISCOUNT_PRICE == null || data.price[i].DISCOUNT_PRICE == undefined){
if(data.price[i].PRICE == null || data.price[i].PRICE == undefined){
listHTML += '0';
}else{
listHTML += (data.price[i].PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}else{
listHTML += (data.price[i].DISCOUNT_PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
listHTML += '</span>원';
listHTML += ' </div>';
listHTML += ' </div>';
listHTML += ' </label>';
listHTML += ' </div>';
listHTML += ' </div>';
listHTML += '</li>';
$(".option_list").append(listHTML);
$("#checkboxID" + i).change(function(){
if($("#checkboxID" + i).is(":checked")){
let listHTML2 = '';
listHTML2 += '<li class="s_item selt_info_wrap" id="liid' + i + '">';
listHTML2 += '<input type="hidden" id="procedure_id' + i + '" name="procedure_id" value="'+ data.price[i].MU_TREATMENT_PROCEDURE_ID + '">';
listHTML2 += ' <div class="txt-wrap"> ';
listHTML2 += ' <span class="selt">' + data.price[i].TREATMENT_PROCEDURE_NAME;
listHTML2 += ' </div>';
listHTML2 += ' <div class="info">';
listHTML2 += ' <span><span class="real_price">';
if(data.price[i].DISCOUNT_PRICE == null || data.price[i].DISCOUNT_PRICE == undefined){
if(data.price[i].PRICE == null || data.price[i].PRICE == undefined){
listHTML2 += '0';
}else{
listHTML2 += (data.price[i].PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}else{
listHTML2 += (data.price[i].DISCOUNT_PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
listHTML2 += '</span>원</span>';
listHTML2 += ' <button type="button" onclick="fn_nocheck(' + i + ');"><img src="/image/close-btn.png" alt="삭제" width="13px" height="13px"></button>';
listHTML2 += ' </div>';
listHTML2 += '</li>';
$("#selectEvent1").append(listHTML2);
var price = $("#price_div1").text().replace(/,/g, "");
var discount_price = '';
if(data.price[i].DISCOUNT_PRICE == null || data.price[i].DISCOUNT_PRICE == undefined){
if(data.price[i].PRICE == null || data.price[i].PRICE == undefined){
discount_price = 0;
}else{
discount_price = data.price[i].PRICE;
}
}else{
discount_price = data.price[i].DISCOUNT_PRICE;
}
price = Number(price) + Number(discount_price);
$("#price_div1").text(price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","));
}else{
fn_deleteList(i);
}
});
}
}else{
listHTML += '<li>';
listHTML += '</li>';
$(".option_list").html(listHTML);
});
}
}else{
modalEvent.danger("조회 오류", data.msgDesc);
}
$('.hashtag-list').html(hashtagHtml);
},
error : function(xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend:function(){
// 로딩열기
$(".loading-image-layer").show();
},
complete:function(){
// 로딩닫기
$(".loading-image-layer").hide();
}
});
$('#contents_path').attr('src', CDN_URL + data.rows.CONTENTS_PATH);
// 이벤트 기간 정보 저장
window.eventStartDt = data.rows.EVENT_START_DT || '';
window.eventEndDt = data.rows.EVENT_END_DT || '';
// 이벤트 기간 표시
if (window.eventStartDt || window.eventEndDt) {
const periodEl = document.getElementById('event-period');
if (periodEl) {
let periodText = '📅 이벤트 기간: ';
if (window.eventStartDt && window.eventEndDt) {
periodText += window.eventStartDt + ' ~ ' + window.eventEndDt;
} else if (window.eventEndDt) {
periodText += '~ ' + window.eventEndDt;
} else {
periodText += window.eventStartDt + ' ~';
}
periodEl.textContent = periodText;
periodEl.style.display = 'block';
}
}
// 시술 목록 데이터 처리
updateProcedureOptions(data.price || []);
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
},
error: function (xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend: function () {
$(".loading-image-layer").show();
},
complete: function () {
$(".loading-image-layer").hide();
}
});
}
function fn_nocheck(num){
$("#checkboxID" + num).prop("checked",false);
fn_deleteList(num);
/****************************************************************************
* Choices.js 초기화 및 옵션 업데이트
****************************************************************************/
function updateProcedureOptions(data) {
priceList = data;
// 기존 Choices 인스턴스 제거
if (procedureChoices) {
procedureChoices.destroy();
}
// 선택 옵션 데이터 생성
const choices = data.map(item => {
if (!item.MU_TREATMENT_PROCEDURE_ID || !item.TREATMENT_PROCEDURE_NAME) return null;
const basePrice = (item.PRICE || 0) + (item.VAT || 0);
const discountPrice = item.DISCOUNT_PRICE;
return {
value: item.MU_TREATMENT_PROCEDURE_ID,
label: item.TREATMENT_PROCEDURE_NAME,
customProperties: {
name: item.TREATMENT_PROCEDURE_NAME,
price: basePrice,
discountPrice: discountPrice,
originalData: item
}
};
}).filter(Boolean);
// Choices.js 초기화
procedureChoices = new Choices('#procedure-select', {
removeItemButton: true,
searchEnabled: true,
searchPlaceholderValue: '시술명으로 검색...',
placeholder: true,
placeholderValue: '시술을 선택하세요',
maxItemCount: -1,
choices: choices,
shouldSort: false,
searchResultLimit: 10,
searchFields: ['label', 'value'],
itemSelectText: '',
noChoicesText: '선택 가능한 시술이 없습니다',
noResultsText: '검색 결과가 없습니다',
loadingText: '시술 정보를 불러오는 중...',
// 템플릿 커스터마이징 - 선택된 항목에 가격 표시 추가
callbackOnCreateTemplates: function (template) {
return {
// 선택된 항목 템플릿 - 가격 정보 포함
item: ({ classNames }, data) => {
const customProps = data.customProperties || {};
const name = customProps.name || data.label;
const price = customProps.price || 0;
const discountPrice = customProps.discountPrice;
// 가격 표시 HTML 생성
let priceHtml = '';
if (discountPrice && discountPrice < price) {
priceHtml = `
<span class="selected-price-discount">${discountPrice.toLocaleString()}원</span>
<span class="selected-price-original">${price.toLocaleString()}원</span>
`;
} else {
priceHtml = `<span class="selected-price">${price.toLocaleString()}원</span>`;
}
return template(`
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}">
<span class="selected-item-content">
<span class="selected-item-name">${name}</span>
<span class="selected-item-price">${priceHtml}</span>
</span>
<button type="button" class="${classNames.button}" aria-label="Remove item: '${data.value}'" data-button>X</button>
</div>
`);
},
// 드롭다운 선택 옵션 템플릿
choice: ({ classNames }, data) => {
const customProps = data.customProperties || {};
const name = customProps.name || data.label;
const price = customProps.price || 0;
const discountPrice = customProps.discountPrice;
let priceHtml = '';
if (discountPrice && discountPrice < price) {
priceHtml = `
<span class="procedure-price-discount">${discountPrice.toLocaleString()}원</span>
<span class="procedure-price-original">${price.toLocaleString()}원</span>
`;
} else {
priceHtml = `<span class="procedure-price">${price.toLocaleString()}원</span>`;
}
return template(`
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} data-id="${data.id}" data-value="${data.value}">
<div style="display: flex; justify-content: space-between; width: 100%; align-items: center;">
<span>${name}</span>
${priceHtml}
</div>
</div>
`);
}
};
}
});
// 이벤트 리스너 추가
const selectElement = document.getElementById('procedure-select');
selectElement.addEventListener('change', function (event) {
console.log('Selection changed:', event.detail);
updateTotalPrice();
});
selectElement.addEventListener('addItem', function (event) {
console.log('Item added:', event.detail);
updateTotalPrice();
});
selectElement.addEventListener('removeItem', function (event) {
console.log('Item removed:', event.detail);
updateTotalPrice();
});
}
function fn_deleteList(num){
var price = $("#price_div1").text().replace(/,/g, "");
var discount_price = $("#liid" + num + " > div.info > span > span").text().replace(/,/g, "");
price = Number(price) - Number(discount_price);
$("#price_div1").text(price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","));
$("#liid" + num).remove();
}
function fn_moveReservation(category_div, category_no, post_no){
let pageMoveForm = document.createElement('form');
let obj = document.createElement('input');
obj.setAttribute('type', 'hidden');
obj.setAttribute('name', 'CATEGORY_DIV_CD');
obj.setAttribute('value', "0"+category_div);
pageMoveForm.appendChild(obj);
let obj2 = document.createElement('input');
obj2.setAttribute('type', 'hidden');
obj2.setAttribute('name', 'CATEGORY_NO');
obj2.setAttribute('value', category_no);
pageMoveForm.appendChild(obj2);
let obj3 = document.createElement('input');
obj3.setAttribute('type', 'hidden');
obj3.setAttribute('name', 'POST_NO');
obj3.setAttribute('value', post_no);
pageMoveForm.appendChild(obj3);
let obj4 = document.getElementsByName('procedure_id');
var len = obj4.length;
if(len == 0){
alert('시술이 선택되지 않았습니다.');
/****************************************************************************
* 총 금액 업데이트 - 개선된 버전
****************************************************************************/
function updateTotalPrice() {
if (!procedureChoices) {
console.log('procedureChoices not initialized');
return;
}
var value = '';
for(var i = 0; i < len; i++){
value += obj4[i].getAttribute('value') + '#';
}
let obj5 = document.createElement('input');
obj5.setAttribute('type', 'hidden');
obj5.setAttribute('name', 'PROCEDURE_ID');
obj5.setAttribute('value', value);
pageMoveForm.appendChild(obj5);
const selectedValues = procedureChoices.getValue(true);
console.log('Selected values:', selectedValues);
pageMoveForm.setAttribute('method', 'post');
pageMoveForm.setAttribute('action', '/webservice/selectMakeReservation.do');
document.body.appendChild(pageMoveForm);
let total = 0;
pageMoveForm.submit();
selectedValues.forEach(value => {
const item = priceList.find(p => p.MU_TREATMENT_PROCEDURE_ID == value);
console.log('Found item for value', value, ':', item);
if (item) {
const basePrice = (item.PRICE || 0) + (item.VAT || 0);
const discountPrice = item.DISCOUNT_PRICE;
// 할인가가 있고 더 저렴하면 할인가 사용, 아니면 기본가격 사용
const finalPrice = (discountPrice && discountPrice < basePrice) ? discountPrice : basePrice;
total += finalPrice;
console.log('Added price:', finalPrice);
}
});
console.log('Total calculated:', total);
totalEl.textContent = total.toLocaleString() + '원';
reserveBtn.disabled = selectedValues.length === 0;
}
/* 버튼 클릭했는지 안했는지 확인 class active */
var $selectProcedureDiv = $('.select_procedure_div');
$selectProcedureDiv.click(function(e){
e.stopPropagation();
$selectProcedureDiv.not(this).removeClass('active'); /*remove buttonactive from the others*/
$(this).toggleClass('active'); /*toggle current clicked element*/
/****************************************************************************
* 예약 페이지로 이동
****************************************************************************/
function fn_moveReservation(category_div, category_no, post_no) {
if (!procedureChoices) {
alert('시술 선택 기능이 초기화되지 않았습니다.');
return;
}
const selectedValues = procedureChoices.getValue(true);
if (selectedValues.length === 0) {
alert('시술을 선택해주세요.');
return;
}
const form = document.createElement('form');
form.method = 'post';
form.action = '/webevent/selectMakeReservation.do';
// 기본 파라미터 추가
const params = [
{ name: 'CATEGORY_DIV_CD', value: category_div },
{ name: 'CATEGORY_NO', value: category_no },
{ name: 'POST_NO', value: post_no },
{ name: 'EVENT_START_DT', value: window.eventStartDt || '' },
{ name: 'EVENT_END_DT', value: window.eventEndDt || '' }
];
params.forEach(param => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = param.name;
input.value = param.value;
form.appendChild(input);
});
// 선택된 시술 추가
selectedValues.forEach(value => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'PROCEDURE_ID';
input.value = value;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
// 예약 버튼 이벤트
reserveBtn.addEventListener('click', function () {
fn_moveReservation(category_div_cd, category_no, post_no);
});
$(window).on("click", function(){
$selectProcedureDiv.removeClass('active');
})
//초기화
fn_init();

View File

@@ -1,189 +1,216 @@
/************************************************
* 초기화
************************************************/
function fn_init() {
fn_SelectListCategory();
}
class EventManager {
constructor() {
this.events = [];
this.categories = [];
this.init();
}
async init() {
await this.loadCategories();
}
/****************************************************************************
* 이벤트 카테고리 목록 가져오기
****************************************************************************/
function fn_SelectListCategory(){
async apiRequest(url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: encodeURI(url),
data: data,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
success: resolve,
error: reject,
beforeSend: () => $(".loading-image-layer").show(),
complete: () => $(".loading-image-layer").hide()
});
});
}
let formData = new FormData();
formData.append('bannerType', 'A');
async loadCategories() {
try {
const formData = new FormData();
formData.append('bannerType', 'A');
const data = await this.apiRequest('/webevent/selectListWebEvent.do', formData);
$.ajax({
url: encodeURI('/webevent/selectListWebEvent.do'),
data: formData,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
async: true,
success: function(data){
if(data.msgCode=='0'){
let totalCount = data.rows.length;
if (0 < totalCount) {
let listHTML = '';
for (let i = 0; i < data.rows.length; i++) {
if(i == 0){
listHTML += '<li class="active" id="category_'+data.rows[i].CATEGORY_NO+'">';
listHTML += ' <a href="javascript:fn_SelectEventList(' + data.rows[i].CATEGORY_NO + ');">' + data.rows[i].CATEGORY_NM + '</a>';
listHTML += '</li>';
}else{
listHTML += '<li class="nonactive" id="category_'+data.rows[i].CATEGORY_NO+'">';
listHTML += ' <a href="javascript:fn_SelectEventList(' + data.rows[i].CATEGORY_NO + ');">' + data.rows[i].CATEGORY_NM + '</a>';
listHTML += '</li>';
}
}
$("#servicecategory").html(listHTML);
fn_SelectEventList(data.rows[0].CATEGORY_NO);
}
}else{
modalEvent.danger("조회 오류", data.msgDesc);
}
},
error : function(xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend:function(){
// 로딩열기
$(".loading-image-layer").show();
},
complete:function(){
// 로딩닫기
$(".loading-image-layer").hide();
}
});
}
/****************************************************************************
* 시술 목록 가져오기
****************************************************************************/
function fn_SelectEventList(category_no){
$(".active").addClass("nonactive");
$(".active").removeClass("active");
$("#category_"+category_no).removeClass("nonactive");
$("#category_"+category_no).addClass("active");
let formData = new FormData();
formData.append('category_no', category_no);
$.ajax({
url: encodeURI('/webevent/selectListEvent.do'),
data: formData,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
async: true,
success: function(data){
if(data.msgCode=='0'){
let totalCount = data.rows.length;
let listHTML = '';
if (0 < totalCount) {
for (let i = 0; i < data.rows.length; i++) {
listHTML += '<li class="fadeIn">';
listHTML += ' <a href="javascript:fn_moveDetail(' + data.rows[i].CATEGORY_DIV_CD +', ' + data.rows[i].CATEGORY_NO + ',' + data.rows[i].POST_NO + ');" class="event-card">';
listHTML += ' <div class="img_box">';
listHTML += ' <img src="https://intranet.bbgnetworks.com/uploadFiles/T00004/eventImg/20250203144112.png" alt="평일 오후1시-5시 / 피부 이벤트" class="">';
listHTML += ' </div>';
listHTML += ' <div class="txt-box">';
listHTML += ' <!-- BEST/NEW 아이콘 퍼블리싱 -->';
listHTML += ' <!-- __tit-best: best아이콘 __tit-new: new아이콘 -->';
listHTML += ' <div class="tit-txt">';
listHTML += ' <p class="multy-ellip2">' + data.rows[i].TITLE + '</p>';
listHTML += ' </div>';
listHTML += ' <span class="sub-txt max-line1 one-ellip">' + data.rows[i].THUMBNAIL_BOTTOM_TXT + '</span>';
listHTML += ' <div class="ab_cont">';
listHTML += ' <span class="cost"><del class="">';
if(data.rows[i].DISCOUNT_PRICE == null || data.rows[i].DISCOUNT_PRICE == undefined){
if(data.rows[i].PRICE == null || data.rows[i].PRICE == undefined){
listHTML += '0';
}else{
listHTML += (data.rows[i].PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}else{
listHTML += (data.rows[i].DISCOUNT_PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
listHTML += ' </del>원</span>';
listHTML += ' <span class="discount">';
listHTML += ' <strong class="txt_num">';
if(data.rows[i].DISCOUNT_PRICE == null || data.rows[i].DISCOUNT_PRICE == undefined){
if(data.rows[i].PRICE == null || data.rows[i].PRICE == undefined){
listHTML += '0';
}else{
listHTML += (data.rows[i].PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
}else{
listHTML += (data.rows[i].DISCOUNT_PRICE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
listHTML += ' </strong>원 부터 </span>';
listHTML += ' </div>';
listHTML += ' </div>';
listHTML += ' </a>';
listHTML += '</li>';
}
$("#detail_list").html(listHTML);
}else{
listHTML += '<li class="mt70">';
listHTML += '</li>';
$("#detail_list").html(listHTML);
if (data.msgCode === '0') {
this.categories = data.rows;
this.renderCategories();
if (this.categories.length > 0) {
this.loadEvents(this.categories[0].CATEGORY_NO);
}
}else{
modalEvent.danger("조회 오류", data.msgDesc);
}
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
} catch (error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다.");
}
}
},
error : function(xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend:function(){
// 로딩열기
$(".loading-image-layer").show();
},
complete:function(){
// 로딩닫기
$(".loading-image-layer").hide();
}
});
async loadEvents(categoryNo) {
try {
const formData = new FormData();
formData.append('category_no', categoryNo);
const data = await this.apiRequest('/webevent/selectListEvent.do', formData);
if (data.msgCode === '0') {
const today = new Date();
today.setHours(0, 0, 0, 0);
this.events = data.rows.map(row => {
// 지난 이벤트 판별: EVENT_END_DT가 있고 오늘보다 이전이면 expired
let isExpired = false;
if (row.EVENT_END_DT) {
const endDate = new Date(row.EVENT_END_DT);
endDate.setHours(23, 59, 59, 999);
isExpired = endDate < today;
}
return {
img: CDN_URL + row.THUMBNAIL_PATH,
title: row.TITLE,
desc: row.CONTENT,
meta: row.THUMBNAIL_BOTTOM_TXT,
price: {
before: Number(row.PRICE) || 0,
after: Number(row.DISCOUNT_PRICE) || 0
},
categoryDiv: row.CATEGORY_DIV_CD,
categoryNo: row.CATEGORY_NO,
postNo: row.POST_NO,
eventStartDt: row.EVENT_START_DT,
eventEndDt: row.EVENT_END_DT,
isExpired: isExpired
};
});
this.renderEvents();
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
} catch (error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다.");
}
}
renderCategories() {
const html = this.categories.map((cat, idx) => `
<li class="category-item">
<a href="#" class="category-link ${idx === 0 ? 'active' : ''}"
data-category="${cat.CATEGORY_NO}">${cat.CATEGORY_NM}</a>
</li>
`).join('');
document.getElementById('category-list').innerHTML = html;
document.querySelectorAll('.category-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
document.querySelectorAll('.category-link').forEach(item =>
item.classList.remove('active'));
link.classList.add('active');
this.loadEvents(link.dataset.category);
document.querySelector('.event-list').scrollTop = 0;
});
});
}
renderEvents() {
const html = this.events.map(event => {
const expiredClass = event.isExpired ? ' expired' : '';
const expiredBadge = event.isExpired ? '<span class="expired-badge">종료된 이벤트</span>' : '';
// 이벤트 기간 표시
let dateHtml = '';
if (event.eventStartDt || event.eventEndDt) {
const startStr = event.eventStartDt || '';
const endStr = event.eventEndDt || '';
if (startStr && endStr) {
dateHtml = `<div class="event-date">📅 ${startStr} ~ ${endStr}</div>`;
} else if (endStr) {
dateHtml = `<div class="event-date">📅 ~ ${endStr}</div>`;
}
}
return `
<div class="event-card${expiredClass}" data-category-div="${event.categoryDiv}"
data-category-no="${event.categoryNo}" data-post-no="${event.postNo}"
data-expired="${event.isExpired}">
${expiredBadge}
<div class="event-img">
<img src="${event.img}" alt="${event.title}">
</div>
<div class="event-info">
<div class="event-title">${event.title}</div>
${event.meta ? `<div class="event-meta">${event.meta}</div>` : ''}
${dateHtml}
<div class="event-price">
${event.price.before !== event.price.after
? `<span style="text-decoration:line-through; color:#9ca3af; font-size:0.95em; margin-right:8px;">
${event.price.before.toLocaleString()}
</span>`
: ''}
${event.price.after.toLocaleString()}원 부터
</div>
</div>
</div>
`}).join('');
document.getElementById('event-grid').innerHTML = html;
// 카드 클릭 이벤트 추가
document.querySelectorAll('.event-card').forEach(card => {
card.addEventListener('click', () => {
const isExpired = card.dataset.expired === 'true';
if (isExpired) {
// 지난 이벤트 → 팝업 표시
this.showExpiredPopup();
return;
}
const categoryDiv = card.dataset.categoryDiv;
const categoryNo = card.dataset.categoryNo;
const postNo = card.dataset.postNo;
this.goToDetail(categoryDiv, categoryNo, postNo);
});
});
}
showExpiredPopup() {
const overlay = document.getElementById('expired-popup');
if (overlay) {
overlay.classList.add('active');
}
}
hideExpiredPopup() {
const overlay = document.getElementById('expired-popup');
if (overlay) {
overlay.classList.remove('active');
}
}
goToDetail(categoryDiv, categoryNo, postNo) {
const form = document.createElement('form');
form.method = 'get';
form.action = '/webevent/selectEventDetailIntro.do';
const fields = [
{ name: 'CATEGORY_DIV_CD', value: categoryDiv },
{ name: 'CATEGORY_NO', value: categoryNo },
{ name: 'POST_NO', value: postNo }
];
fields.forEach(field => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = field.name;
input.value = field.value;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
}
function fn_moveDetail(category_div, category_no, post_no){
let pageMoveForm = document.createElement('form');
let obj = document.createElement('input');
obj.setAttribute('type', 'hidden');
obj.setAttribute('name', 'CATEGORY_DIV_CD');
obj.setAttribute('value', "0"+category_div);
pageMoveForm.appendChild(obj);
let obj2 = document.createElement('input');
obj2.setAttribute('type', 'hidden');
obj2.setAttribute('name', 'CATEGORY_NO');
obj2.setAttribute('value', category_no);
pageMoveForm.appendChild(obj2);
let obj3 = document.createElement('input');
obj3.setAttribute('type', 'hidden');
obj3.setAttribute('name', 'POST_NO');
obj3.setAttribute('value', post_no);
pageMoveForm.appendChild(obj3);
pageMoveForm.setAttribute('method', 'post');
pageMoveForm.setAttribute('action', '/webevent/selectEventDetailIntro.do');
document.body.appendChild(pageMoveForm);
pageMoveForm.submit();
}
//초기화
fn_init();
const eventManager = new EventManager();

View File

@@ -0,0 +1,89 @@
class ReviewDetailManager {
constructor() {
this.quill = null;
this.init();
}
async init() {
if (!muProcedureReviewId) { this.showError('잘못된 접근입니다.'); return; }
await this.loadReview();
}
async apiRequest(url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: encodeURI(url), data: data, dataType: 'json',
processData: false, contentType: false, type: 'POST',
success: resolve, error: reject
});
});
}
async loadReview() {
try {
const formData = new FormData();
formData.append('muProcedureReviewId', muProcedureReviewId);
const data = await this.apiRequest('/webreview/selectProcedureReview.do', formData);
if (data.msgCode === '0' && data.rows) {
this.renderReview(data.rows);
} else {
this.showError('게시글을 찾을 수 없습니다.');
}
} catch (error) {
this.showError('게시글 조회 중 오류가 발생하였습니다.');
}
}
renderReview(review) {
document.getElementById('review-title').textContent = review.title || '';
document.getElementById('breadcrumb-title').textContent = review.title || '상세보기';
document.getElementById('review-date').textContent = review.writeDate || '';
document.getElementById('review-views').textContent = review.viewCount || 0;
const tagsContainer = document.getElementById('review-tags');
if (review.hashtag) {
const tags = review.hashtag.split(',').map(t => t.trim()).filter(t => t);
tagsContainer.innerHTML = tags.map(tag => `<span class="review-tag">#${tag}</span>`).join('');
}
// Quill Delta JSON 본문 렌더링
const contentDiv = document.getElementById('review-content');
if (review.content) {
try {
const decoded = decodeURIComponent(escape(atob(review.content)));
const delta = JSON.parse(decoded);
contentDiv.innerHTML = '';
this.quill = new Quill(contentDiv, {
readOnly: true,
modules: { toolbar: false },
theme: 'snow'
});
this.quill.setContents(delta);
const toolbar = contentDiv.parentElement.querySelector('.ql-toolbar');
if (toolbar) toolbar.style.display = 'none';
const container = contentDiv.parentElement.querySelector('.ql-container');
if (container) { container.style.border = 'none'; container.style.fontSize = '16px'; }
} catch (e) {
try {
const html = decodeURIComponent(escape(atob(review.content)));
contentDiv.innerHTML = html;
} catch (e2) {
contentDiv.innerHTML = review.content;
}
}
} else {
contentDiv.innerHTML = '<p style="color:#9ca3af;text-align:center;">내용이 없습니다.</p>';
}
}
showError(msg) {
document.getElementById('review-content').innerHTML = `
<div style="text-align:center;padding:3rem;color:#6b7280;">
<div style="font-size:2rem;margin-bottom:1rem;">😔</div><div>${msg}</div>
</div>`;
}
}
const reviewDetailManager = new ReviewDetailManager();

View File

@@ -0,0 +1,205 @@
class ReviewListManager {
constructor() {
this.reviews = [];
this.currentPage = 1;
this.pageSize = 9;
this.totalCount = 0;
this.categoryDivCd = '08'; // 쁘띠센터
this.sliders = {};
this.init();
}
async init() {
this.bindEvents();
await this.loadReviews();
}
bindEvents() {
document.getElementById('btnSearch').addEventListener('click', () => {
this.currentPage = 1;
this.loadReviews();
});
document.getElementById('searchTitle').addEventListener('keypress', (e) => {
if (e.key === 'Enter') { this.currentPage = 1; this.loadReviews(); }
});
}
async apiRequest(url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: encodeURI(url), data: data, dataType: 'json',
processData: false, contentType: false, type: 'POST',
success: resolve, error: reject
});
});
}
async loadReviews() {
try {
const formData = new FormData();
formData.append('categoryDivCd', this.categoryDivCd);
formData.append('start', (this.currentPage - 1) * this.pageSize);
formData.append('limit', this.pageSize);
const searchTitle = document.getElementById('searchTitle').value.trim();
if (searchTitle) formData.append('title', searchTitle);
const data = await this.apiRequest('/webreview/selectListProcedureReview.do', formData);
if (data.msgCode === '0') {
this.reviews = data.rows || [];
this.totalCount = parseInt(data.totalCount) || 0;
this.renderReviews();
this.renderPagination();
} else {
this.showEmpty('조회 중 오류가 발생하였습니다.');
}
} catch (error) {
this.showEmpty('조회 중 오류가 발생하였습니다.');
}
}
extractImages(content) {
if (!content) return [];
try {
const decoded = decodeURIComponent(escape(atob(content)));
const delta = JSON.parse(decoded);
if (delta.ops) {
return delta.ops
.filter(op => op.insert && typeof op.insert === 'object' && op.insert.image)
.map(op => op.insert.image);
}
return [];
} catch (e) { return []; }
}
extractSummary(content) {
if (!content) return '';
try {
const decoded = decodeURIComponent(escape(atob(content)));
const delta = JSON.parse(decoded);
if (delta.ops) {
const text = delta.ops
.filter(op => typeof op.insert === 'string')
.map(op => op.insert).join('').replace(/\n/g, ' ').trim();
return text.length > 100 ? text.substring(0, 100) + '...' : text;
}
return '';
} catch (e) { return ''; }
}
buildSliderHtml(images, cardIdx) {
if (images.length === 0) return '<div class="review-no-image">📷</div>';
const imagesHtml = images.map(src => `<img src="${src}" alt="고객후기" loading="lazy" />`).join('');
const dotsHtml = images.length > 1
? `<div class="slider-dots">${images.map((_, i) =>
`<button class="slider-dot ${i === 0 ? 'active' : ''}" data-idx="${i}"></button>`).join('')}</div>` : '';
const arrowsHtml = images.length > 1
? `<button class="slider-arrow prev" data-dir="-1"></button><button class="slider-arrow next" data-dir="1"></button>` : '';
const countHtml = images.length > 1
? `<span class="slider-count">1 / ${images.length}</span>` : '';
return `<div class="review-slider" data-total="${images.length}" data-current="0">
<div class="review-slider-track">${imagesHtml}</div>${arrowsHtml}${dotsHtml}${countHtml}</div>`;
}
renderReviews() {
const grid = document.getElementById('review-grid');
if (this.reviews.length === 0) {
grid.innerHTML = `<div class="empty-state"><div class="icon">📝</div><div class="message">등록된 고객후기가 없습니다.</div></div>`;
return;
}
grid.innerHTML = this.reviews.map((review, idx) => {
const images = this.extractImages(review.summary || review.content);
const summaryText = this.extractSummary(review.summary || review.content);
const sliderHtml = this.buildSliderHtml(images, idx);
let tagsHtml = '';
if (review.hashtag) {
const tags = review.hashtag.split(',').map(t => t.trim()).filter(t => t);
tagsHtml = `<div class="review-card-tags">${tags.slice(0, 3).map(tag =>
`<span class="review-tag">#${tag}</span>`).join('')}</div>`;
}
return `<div class="review-card" data-id="${review.muProcedureReviewId}">
${sliderHtml}
<div class="review-card-body">
<div class="review-card-title">${this.escapeHtml(review.title)}</div>
<div class="review-card-summary">${summaryText}</div>
${tagsHtml}
<div class="review-card-footer">
<span class="date">${review.writeDate}</span>
<span class="views">👁 ${review.viewCount || 0}</span>
</div>
</div>
</div>`;
}).join('');
this.bindSliderEvents();
grid.querySelectorAll('.review-card').forEach(card => {
card.addEventListener('click', (e) => {
if (e.target.closest('.slider-arrow') || e.target.closest('.slider-dot')) return;
location.href = `/webreview/selectProcedureReviewIntro.do?muProcedureReviewId=${card.dataset.id}`;
});
});
}
bindSliderEvents() {
document.querySelectorAll('.review-slider').forEach(slider => {
const total = parseInt(slider.dataset.total);
if (total <= 1) return;
const track = slider.querySelector('.review-slider-track');
const dots = slider.querySelectorAll('.slider-dot');
const countEl = slider.querySelector('.slider-count');
const goTo = (idx) => {
const current = Math.max(0, Math.min(idx, total - 1));
slider.dataset.current = current;
track.style.transform = `translateX(-${current * 100}%)`;
dots.forEach((d, i) => d.classList.toggle('active', i === current));
if (countEl) countEl.textContent = `${current + 1} / ${total}`;
};
slider.querySelectorAll('.slider-arrow').forEach(arrow => {
arrow.addEventListener('click', (e) => {
e.stopPropagation();
let next = parseInt(slider.dataset.current) + parseInt(arrow.dataset.dir);
if (next < 0) next = total - 1;
if (next >= total) next = 0;
goTo(next);
});
});
dots.forEach(dot => {
dot.addEventListener('click', (e) => { e.stopPropagation(); goTo(parseInt(dot.dataset.idx)); });
});
});
}
renderPagination() {
const area = document.getElementById('pagination-area');
const totalPages = Math.ceil(this.totalCount / this.pageSize);
if (totalPages <= 1) { area.innerHTML = ''; return; }
let html = `<button class="page-btn ${this.currentPage === 1 ? 'disabled' : ''}" data-page="${this.currentPage - 1}" ${this.currentPage === 1 ? 'disabled' : ''}></button>`;
const startPage = Math.max(1, this.currentPage - 2);
const endPage = Math.min(totalPages, startPage + 4);
for (let i = startPage; i <= endPage; i++) {
html += `<button class="page-btn ${i === this.currentPage ? 'active' : ''}" data-page="${i}">${i}</button>`;
}
html += `<button class="page-btn ${this.currentPage === totalPages ? 'disabled' : ''}" data-page="${this.currentPage + 1}" ${this.currentPage === totalPages ? 'disabled' : ''}></button>`;
area.innerHTML = html;
area.querySelectorAll('.page-btn:not(.disabled)').forEach(btn => {
btn.addEventListener('click', () => {
this.currentPage = parseInt(btn.dataset.page);
this.loadReviews();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
});
}
showEmpty(msg) {
document.getElementById('review-grid').innerHTML = `<div class="empty-state"><div class="icon">⚠️</div><div class="message">${msg}</div></div>`;
document.getElementById('pagination-area').innerHTML = '';
}
escapeHtml(str) {
if (!str) return '';
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
}
}
const reviewListManager = new ReviewListManager();

View File

@@ -0,0 +1,230 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://petit.madeu.co.kr/</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>1.00</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/index</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webintroduction/selectIntroductionHospitalIntro.do</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webintroduction/selectIntroductionStaffIntro.do</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webintroduction/selectIntroductionWayIntro.do</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=4</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=4&amp;postNo=8</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=4&amp;postNo=2</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=4&amp;postNo=3</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=4&amp;postNo=4</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=4&amp;postNo=5</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=4&amp;postNo=6</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=4&amp;postNo=7</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=2</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=2&amp;postNo=3</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=2&amp;postNo=4</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=2&amp;postNo=5</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=3</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=3&amp;postNo=6</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=3&amp;postNo=10</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=3&amp;postNo=11</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=5</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=5&amp;postNo=5</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=5&amp;postNo=6</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=5&amp;postNo=7</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=5&amp;postNo=8</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=5&amp;postNo=9</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=1</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=1&amp;postNo=10</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=1&amp;postNo=3</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=1&amp;postNo=6</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=1&amp;postNo=11</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=6</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=6&amp;postNo=7</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=6&amp;postNo=8</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=6&amp;postNo=9</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=6&amp;postNo=10</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=6&amp;postNo=11</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceIntro.do?categoryNo=7</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=7&amp;postNo=8</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webservice/selectServiceDetailIntro.do?categoryDivCd=03&amp;categoryNo=7&amp;postNo=9</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webevent/selectListWebEventIntro.do</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webphoto/selectListWebPhotoIntro.do</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webaccept/acceptSite.do</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://petit.madeu.co.kr/webaccept/acceptPrivacy.do</loc>
<lastmod>2026-01-19T10:12:59+00:00</lastmod>
<priority>0.80</priority>
</url>
</urlset>

View File

@@ -1,150 +1,154 @@
<!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/layout}">
<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/layout}">
<th:block layout:fragment="layoutCss">
<link rel="stylesheet" href="/css/web/index.css">
<link rel="stylesheet" href="/css/web/instagram-swiper-custom.css?v1.0">
<link rel="stylesheet" href="/css/web/index.css">
<link rel="stylesheet" href="/css/web/instagram-swiper-custom.css?v1.0">
</th:block>
<th:block layout:fragment="layoutContent">
<div class="popup-background-mask"></div>
<main>
<section class="banner main_banner">
<div class="swiper main_banner_swiper">
<div class="swiper-wrapper" id="mainBannerList">
</div>
<div class="swiper-pagination main_banner_pagination"></div>
</div>
</section>
<section class="cont content1">
<div class="swiper-area">
<h3>
<span style="color:#fff;">MADE U</span> <span style="color:#ffcccc">시그니처</span>
</h3>
<div class="swiper cont1_swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/레이저리프팅.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/레이저리프팅.jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/쉬다주사(Face).jpg" alt="img">
<img class="mb" src="/image/signature/20251024/쉬다주사(Face).jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/스컬트라.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/스컬트라.jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/스킨부스터.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/스킨부스터.jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/필러.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/필러.jpg" alt="img">
</div>
</div>
<div class="swiper-pagination cont1_swiper_pagination"></div>
</div>
</div>
</section>
<section class="banner sub_banner">
<div class="swiper sub_banner_swiper">
<div class="swiper-wrapper" id="subBannerList">
<div class="swiper-slide" style="background:#666;">
<button class="detail_btn">Detail view ></button>
</div>
<div class="swiper-slide" style="background:#999;">
<button class="detail_btn">Detail view ></button>
</div>
<div class="swiper-slide" style="background:#ddd;">
<button class="detail_btn">Detail view ></button>
</div>
</div>
<div class="swiper-pagination sub_banner_pagination"></div>
</div>
</section>
<section class="cont content2">
<div class="swiper-area">
<h3>
<span style="color:#cc3333">MADE U</span> <span style="color:#000">프리미엄장비</span>
</h3>
<div class="swiper cont2_swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img class="pc" src="/image/equip/20251014/티타늄.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20251014/티타늄.jpg" alt="img" style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20251014/써마지.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20251014/써마지.jpg" alt="img" style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20251014/울쎄라.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20251014/울쎄라.jpg" alt="img" style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20251014/줄기세포.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20251014/줄기세포.jpg" alt="img" style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20251014/튠페이스.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20251014/튠페이스.jpg" alt="img" style="cursor:pointer">
</div>
</div>
<div class="swiper-pagination cont2_swiper_pagination"></div>
</div>
</div>
</section>
<section class="instagram">
<div class="swiper-area">
<div class="instagram_top">
<h3>
MADE U 인스타그램<br/>
<span>@madeu_gn</span>
</h3>
<button class="more_btn" onClick="window.open('https://instagram.com/madeu_gn')">View more ≫</button>
<div class="popup-background-mask"></div>
<main>
<section class="banner main_banner">
<div class="swiper main_banner_swiper">
<div class="swiper-wrapper" id="mainBannerList">
</div>
<div class="swiper-pagination main_banner_pagination"></div>
</div>
<div class="instagram_btm" id="instagramFeed">
<div class="swiper instagram_swiper">
<div class="swiper-wrapper" id="instagramList">
</div>
</div>
<!-- 커스텀 내비게이션 하단 영역 -->
<div class="instagram-swiper-footer">
<button class="custom-swiper-btn prev"></button>
<div class="custom-swiper-thumbs"></div>
<button class="custom-swiper-btn next"></button>
</div>
</section>
<section class="cont content1">
<div class="swiper-area">
<h3>
<span style="color:#fff;">MADE U</span> <span style="color:#ffcccc">시그니처</span>
</h3>
<div class="swiper cont1_swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/레이저리프팅.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/레이저리프팅.jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/쉬다주사(Face).jpg" alt="img">
<img class="mb" src="/image/signature/20251024/쉬다주사(Face).jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/스컬트라.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/스컬트라.jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/스킨부스터.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/스킨부스터.jpg" alt="img">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/signature/20251024/필러.jpg" alt="img">
<img class="mb" src="/image/signature/20251024/필러.jpg" alt="img">
</div>
</div>
<div class="swiper-pagination cont1_swiper_pagination"></div>
</div>
</div>
</section>
<section class="banner sub_banner">
<div class="swiper sub_banner_swiper">
<div class="swiper-wrapper" id="subBannerList">
<div class="swiper-slide" style="background:#666;">
<button class="detail_btn">Detail view ></button>
</div>
<div class="swiper-slide" style="background:#999;">
<button class="detail_btn">Detail view ></button>
</div>
<div class="swiper-slide" style="background:#ddd;">
<button class="detail_btn">Detail view ></button>
</div>
</div>
<div class="swiper-pagination sub_banner_pagination"></div>
</div>
</section>
<section class="cont content2">
<div class="swiper-area">
<h3>
<span style="color:#cc3333">MADE U</span> <span style="color:#000">프리미엄장비</span>
</h3>
<div class="swiper cont2_swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<img class="pc" src="/image/equip/20260313/써마지FLX.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20260313/써마지FLX.jpg" alt="img" style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20260313/울쎄라.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20260313/울쎄라.jpg" alt="img" style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20260313/온다리프팅(Face).jpg" alt="img"
style="cursor:pointer">
<img class="mb" src="/image/equip/20260313/온다리프팅(Face).jpg" alt="img"
style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20260313/티타늄리프팅(Face).jpg" alt="img"
style="cursor:pointer">
<img class="mb" src="/image/equip/20260313/티타늄리프팅(Face).jpg" alt="img"
style="cursor:pointer">
</div>
<div class="swiper-slide">
<img class="pc" src="/image/equip/20260313/튠페이스.jpg" alt="img" style="cursor:pointer">
<img class="mb" src="/image/equip/20260313/튠페이스.jpg" alt="img" style="cursor:pointer">
</div>
</div>
<div class="swiper-pagination cont2_swiper_pagination"></div>
</div>
</div>
</section>
<section class="instagram">
<div class="swiper-area">
<div class="instagram_top">
<h3>
MADE U 인스타그램<br />
<span>@madeu_gn</span>
</h3>
<button class="more_btn" onClick="window.open('https://instagram.com/madeu_gn')">View more
</button>
</div>
<div class="instagram_btm" id="instagramFeed">
<div class="swiper instagram_swiper">
<div class="swiper-wrapper" id="instagramList">
</div>
</div>
<!-- 커스텀 내비게이션 하단 영역 -->
<div class="instagram-swiper-footer">
<button class="custom-swiper-btn prev"></button>
<div class="custom-swiper-thumbs"></div>
<button class="custom-swiper-btn next"></button>
</div>
</div>
</div>
</section>
</main>
<!-- 팝업 -->
<div class="popup">
<div class="top">
<div class="tab-content" id="popupContentList"></div>
<ul class="nav nav-tabs" role="tablist" id="popupTabList"></ul>
</div>
<div class="btm">
<div class="left_box">
<input type="checkbox" id="today" />
<label for="today">오늘 하루 안보기</label>
</div>
<div class="right_box">
<button class="close_btn_btm" id="btnPopupClose"><img src="/image/web/close.png" alt="close" /></button>
</div>
</div>
</section>
</main>
<!-- 팝업 -->
<div class="popup">
<div class="top">
<div class="tab-content" id="popupContentList"></div>
<ul class="nav nav-tabs" role="tablist" id="popupTabList"></ul>
</div>
<div class="btm">
<div class="left_box">
<input type="checkbox" id="today"/>
<label for="today">오늘 하루 안보기</label>
</div>
<div class="right_box">
<button class="close_btn_btm" id="btnPopupClose"><img src="/image/web/close.png" alt="close"/></button>
</div>
</div>
</div>
</div>
</th:block>
<th:block layout:fragment="layoutContentScript">
<script>
// CDN_URL 전역 정의
const CDN_URL = "[(${@environment.getProperty('url.cdn')})]";
</script>
<script src="/js/web/index.js"></script>
</script>
<script src="/js/web/index.js"></script>
</th:block>
</html>

View File

@@ -1,8 +1,6 @@
<!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/layout}">
<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/layout}">
<th:block layout:fragment="layoutCss">
<link rel="stylesheet" href="/css/web/introduction/introductionHospitalSelect.css?v1.0">
</th:block>
@@ -10,16 +8,16 @@
<main>
<section class="main_img">
<div class="inner_wrap">
<img src="/image/web/introduction_main.png" alt="introduction"/>
<img src="/image/web/introduction_main.png" alt="introduction" />
<div class="text_box">
<p class="hashtag">#맞춤진료 #만족스러운결과</p>
<p class="title">
<span>MADE U 강남본점</span><br/>
Total Beauty<br class="mb"/> One Stop System
<span>MADE U 강남본점</span><br />
Total Beauty<br class="mb" /> One Stop System
</p>
<p class="sub_text">
한 공간에서 고객 한분 한분께 자연스러운<br class="mb"/> 아름다움과 건강한 다이어트를 위해<br/>
항상 노력하는 함께하는<br class="mb"/> 조언자가 되어 드릴 것을 약속드립니다.
한 공간에서 고객 한분 한분께 자연스러운<br class="mb" /> 아름다움과 건강한 다이어트를 위해<br />
항상 노력하는 함께하는<br class="mb" /> 조언자가 되어 드릴 것을 약속드립니다.
</p>
</div>
</div>
@@ -28,47 +26,47 @@
<div class="inner_wrap">
<div class="text_box">
<p class="hashtag">#차별화된 맞춤 플랜</p>
<p class="title">Looking Around<br/>MADE <span class="red">U</span></p>
<p class="title">Looking Around<br />MADE <span class="red">U</span></p>
<p class="sub_title">
쾌적하고 안락한 공간을 제공하는<br/>
쾌적하고 안락한 공간을 제공하는<br />
<span>메이드유 강남본점</span>
</p>
<p class="sub_text">
깨끗한 공간, 친절한 상담을 제공<span class="pc">하겠습니다.</span>
</p>
</div>
<img src="/image/web/introduction_content1.png" alt="content1"/>
<img src="/image/web/introduction_content1.png" alt="content1" />
</div>
</section>
<section class="content2">
<div class="inner_wrap">
<p class="mb">쉬운다이어트 방법 없을까?<br/><span>MADE U 시그니처 프로그램</span></p>
<p class="mb">쉬운다이어트 방법 없을까?<br /><span>MADE U 시그니처 프로그램</span></p>
<ul>
<li>
<img src="/image/web/introduction_content2-1.png" alt="content2-1"/>
<img src="/image/web/introduction_content2-1.png" alt="content2-1" />
<div class="text_box">
<p>One-On-One Customized Counseling</p>
<p class="sub_title">
MADE U 강남본점만의<br/>
MADE U 강남본점만의<br />
<span>1:1 맞춤 상담</span>
</p>
<p class="sub_text">
고객님의 체형별 그리고 고민<br class="mb"/> 부위별 맞춤진료로,<br/>
고객님의 체형별 그리고 고민<br class="mb" /> 부위별 맞춤진료로,<br />
숨겨진 아름다움을 찾아드립니다.
</p>
</div>
</li>
<li>
<img src="/image/web/introduction_content2-2.png" alt="content2-2"/>
<img src="/image/web/introduction_content2-2.png" alt="content2-2" />
<div class="text_box">
<p>Trademark Application</p>
<p class="sub_title">
MADE U 강남본점만의<br/>
MADE U 강남본점만의<br />
<span>특허<span class="red"></span>상표 출원</span>
</p>
<p class="sub_text">
메이드유 강남본점에서 자체 개발한<br class="mb"/> 메쉬다 주사<br class="pc"/>
레시피의 뛰어난<br class="mb"/> 효과를 바탕으로 다수의<br/>
메이드유 강남본점에서 자체 개발한<br class="mb" /> 메쉬다 주사<br class="pc" />
레시피의 뛰어난<br class="mb" /> 효과를 바탕으로 다수의<br />
특허와 상표를 등록했습니다.
</p>
</div>
@@ -77,151 +75,151 @@
</div>
</section>
<section class="content3">
<div class="inner_wrap">
<p>국내 고가명품 최다보유<br><span>MADE U</span> 프리미엄 장비 소개</p>
<div class="inner_wrap">
<p>국내 고가명품 최다보유<br><span>MADE U</span> 프리미엄 장비 소개</p>
<div class="equipment-grid">
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/써마지.jpg" alt="써마지">
</div>
<div class="equipment-info">
<h3 class="equipment-name">써마지</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 1,800,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-grid">
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/써마지.jpg" alt="써마지">
</div>
<div class="equipment-info">
<h3 class="equipment-name">써마지</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 1,800,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/울쎄라.jpg" alt="울쎄라">
</div>
<div class="equipment-info">
<h3 class="equipment-name">울쎄라</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 400,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/울쎄라.jpg" alt="울쎄라">
</div>
<div class="equipment-info">
<h3 class="equipment-name">울쎄라</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 400,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/티타늄.jpg" alt="티타늄리프팅">
</div>
<div class="equipment-info">
<h3 class="equipment-name">티타늄리프팅</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 600,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/티타늄.jpg" alt="티타늄리프팅">
</div>
<div class="equipment-info">
<h3 class="equipment-name">티타늄리프팅</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 600,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/온다리프팅.jpg" alt="온다리프팅">
</div>
<div class="equipment-info">
<h3 class="equipment-name">온다리프팅</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<!-- <p class="equipment-price">165,000<span>부터</span></p> -->
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/튠바디.jpg" alt="튠바디">
</div>
<div class="equipment-info">
<h3 class="equipment-name">튠바디</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 150,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/튠바디.jpg" alt="튠바디">
</div>
<div class="equipment-info">
<h3 class="equipment-name">튠바디</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 150,000<span>부터</span> --></p>
</div>
</div>
<!-- 2번째 행 -->
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/튠페이스.jpg" alt="튠페이스">
</div>
<div class="equipment-info">
<h3 class="equipment-name">튠페이스</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 400,000<span>부터</span> --></p>
</div>
</div>
<!-- 2번째 행 -->
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/튠페이스.jpg" alt="튠페이스">
</div>
<div class="equipment-info">
<h3 class="equipment-name">튠페이스</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 400,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/울핏.jpg" alt="울핏">
</div>
<div class="equipment-info">
<h3 class="equipment-name">울핏</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 50,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/울핏.jpg" alt="울핏">
</div>
<div class="equipment-info">
<h3 class="equipment-name">울핏</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 50,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/포텐자.jpg" alt="포텐자">
</div>
<div class="equipment-info">
<h3 class="equipment-name">포텐자</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 150,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/포텐자.jpg" alt="포텐자">
</div>
<div class="equipment-info">
<h3 class="equipment-name">포텐자</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 150,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/인모드.jpg" alt="인모드">
</div>
<div class="equipment-info">
<h3 class="equipment-name">인모드</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 100,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/인모드.jpg" alt="인모드">
</div>
<div class="equipment-info">
<h3 class="equipment-name">인모드</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 100,000<span>부터</span> --></p>
</div>
</div>
<!-- 3번째 행 -->
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/슈링크유니버스.jpg" alt="슈링크유니버스">
</div>
<div class="equipment-info">
<h3 class="equipment-name">슈링크유니버스</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 89,000<span>부터</span> --></p>
</div>
</div>
<!-- 3번째 행 -->
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/슈링크유니버스.jpg" alt="슈링크유니버스">
</div>
<div class="equipment-info">
<h3 class="equipment-name">슈링크유니버스</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 89,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/바디고주파테라피.jpg" alt="바디고주파테라피">
</div>
<div class="equipment-info">
<h3 class="equipment-name">바디고주파테라피</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 100,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/바디고주파테라피.jpg" alt="바디고주파테라피">
</div>
<div class="equipment-info">
<h3 class="equipment-name">바디고주파테라피</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 100,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/리포덤.jpg" alt="리포덤">
</div>
<div class="equipment-info">
<h3 class="equipment-name">리포덤</h3>
<!-- <p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p> -->
<p class="equipment-price"><!-- 50,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/리포덤.jpg" alt="리포덤">
</div>
<div class="equipment-info">
<h3 class="equipment-name">리포덤</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 50,000<span>부터</span> --></p>
</div>
</div>
<div class="equipment-card">
<div class="equipment-image">
<img src="/image/equip/라비앙.jpg" alt="라비앙">
</div>
<div class="equipment-info">
<h3 class="equipment-name">라비앙</h3>
<p class="equipment-desc">깊은 탄력과 리프팅을 동시에</p>
<p class="equipment-price"><!-- 165,000<span>부터</span> --></p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section class="content4">
<div class="inner_wrap">
<p>
한 공간에서 고객 한분 한분께 자연스러운 아름다움과<br class="mb"/> 건강한 다이어트를 위해 항상 노력하는<br/>
한 공간에서 고객 한분 한분께 자연스러운 아름다움과<br class="mb" /> 건강한 다이어트를 위해 항상 노력하는<br />
<span>Total Beauty One Stop System</span>
</p>
<ul>
@@ -229,56 +227,57 @@
<div class="text_box">
<p>
<span>
자연스러운 아름다움과<br/>
자연스러운 아름다움과<br />
건강한 다이어트
</span>
</p>
<p>
메이드유는 <span>Total Beauty One Stop System</span>으로<br class="pc"/>
한 공간에서 고객 한분 <br class="mb"/>한분께 자연스러운 아름다움과<br/>
건강한 다이어트를 위해 항상 노력하는 함께하는 조언자가<br class="pc"/>
메이드유는 <span>Total Beauty One Stop System</span>으로<br class="pc" />
한 공간에서 고객 한분 <br class="mb" />한분께 자연스러운 아름다움과<br />
건강한 다이어트를 위해 항상 노력하는 함께하는 조언자가<br class="pc" />
되어 드릴 것을 약속드립니다.
</p>
</div>
<img class="pc" src="/image/web/introduction_content4-1.jpg" alt="introduction_content4-1"/>
<img class="mb" src="/image/web/Mintroduction_content4-1.jpg" alt="introduction_content4-1"/>
<img class="pc" src="/image/web/introduction_content4-1.jpg" alt="introduction_content4-1" />
<img class="mb" src="/image/web/Mintroduction_content4-1.jpg" alt="introduction_content4-1" />
</li>
<li class="red_box">
<div class="text_box">
<p>
<span>고객을 가족으로 생각하는 마음으로<br class="pc"/>정직한 시술, <br class="mb"/>만족할 수 있는 결과,</span><br/>
더 나은 감동을 선사하기 위해<br/>
<span>고객을 가족으로 생각하는 마음으로<br class="pc" />정직한 시술, <br class="mb" />만족할 수 있는
결과,</span><br />
더 나은 감동을 선사하기 위해<br />
끊임없이 노력하겠습니다.
</p>
<p>
메이드유는 <span>전국, 해외에서 찾아오는 비만센터와<br class="pc"/> 프리미멈 명품
장비 보유 및 장비 최다 보유</span>와 엘란쎄, <br class="pc"/>스컬트라 콜라겐 볼륨
전국 3대 병원인 쁘띠 센터로 <br class="pc"/>구분되어 고객에 맞춰 운영되고
메이드유는 <span>전국, 해외에서 찾아오는 비만센터와<br class="pc" /> 프리미멈 명품
장비 보유 및 장비 최다 보유</span>와 엘란쎄, <br class="pc" />스컬트라 콜라겐 볼륨
전국 3대 병원인 쁘띠 센터로 <br class="pc" />구분되어 고객에 맞춰 운영되고
있습니다.
</p>
</div>
<img class="pc" src="/image/web/introduction_content4-2.jpg" alt="introduction_content4-2"/>
<img class="mb" src="/image/web/Mintroduction_content4-2.jpg" alt="introduction_content4-2"/>
<img class="pc" src="/image/web/introduction_content4-2.jpg" alt="introduction_content4-2" />
<img class="mb" src="/image/web/Mintroduction_content4-2.jpg" alt="introduction_content4-2" />
</li>
<li>
<div class="text_box">
<p>
No Pain 통증 없이<br/>
No Bruise 멍 없이<br/>
No Swelling 붓기 없이<br/>
No Pain 통증 없이<br />
No Bruise 멍 없이<br />
No Swelling 붓기 없이<br />
<span>3No 의료서비스를 지향</span>
</p>
<p>
모든 제품은 정품, 정량, 정품 장비 사용을 원칙으로 안전을 <br class="pc"/>
최우선으로 하고 있으며, 모든 시술은 <span>No Pain, No Bruise, <br class="pc"/>
No Swelling라는 3No 의료서비스를 지향</span>합니다. <br class="pc"/>
앞으로도 고품질의 관리와 서비스를 받을 수 있도록 노력하며 <br class="pc"/>
사소한 불편까지 읽어주는 세심한 배려, 마음까지 읽는 서비스로<br class="pc"/>
모든 제품은 정품, 정량, 정품 장비 사용을 원칙으로 안전을 <br class="pc" />
최우선으로 하고 있으며, 모든 시술은 <span>No Pain, No Bruise, <br class="pc" />
No Swelling라는 3No 의료서비스를 지향</span>합니다. <br class="pc" />
앞으로도 고품질의 관리와 서비스를 받을 수 있도록 노력하며 <br class="pc" />
사소한 불편까지 읽어주는 세심한 배려, 마음까지 읽는 서비스로<br class="pc" />
무한 감동을 드릴 것을 약속합니다.
</p>
</div>
<img class="pc" src="/image/web/introduction_content4-3.jpg" alt="introduction_content4-3"/>
<img class="mb" src="/image/web/Mintroduction_content4-3.jpg" alt="introduction_content4-3"/>
<img class="pc" src="/image/web/introduction_content4-3.jpg" alt="introduction_content4-3" />
<img class="mb" src="/image/web/Mintroduction_content4-3.jpg" alt="introduction_content4-3" />
</li>
</ul>
</div>
@@ -287,4 +286,5 @@
</th:block>
<th:block layout:fragment="layoutContentScript">
</th:block>
</html>

View File

@@ -5,8 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="keyword" content="HTML, meta, tag, element, reference">
<meta name="author" content="NTSOFT">
<meta name="description" content={props.description} data-react-helmet="true" />
<meta name="author" content="VARASOFT">
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
<meta http-equiv="Cache-Control" content="No-Cache" />
@@ -16,14 +15,13 @@
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE10">
<!-- sns미리보기 -->
<meta name="description" content="다이어트약, 강남피부과,비만클리닉, 리쥬란힐러,신논현피부과,논현피부과,스컬트라,울쎄라,강남울쎄라,써마지,강남써마지,지방분해주사,윤곽주사">
<meta property="og:type" content="website">
<!-- <meta property="og:url" content="https://ntsoft.kr/"> -->
<meta property="og:image" content="">
<meta property="og:title" content="메이드유">
<meta property="og:description" content="설명문구">
<meta property="og:title" content="메이드유의원 강남본점">
<meta name="og:description" content="다이어트약, 강남피부과,비만클리닉, 리쥬란힐러,신논현피부과,논현피부과,스컬트라,울쎄라,강남울쎄라,써마지,강남써마지,지방분해주사,윤곽주사">
<!-- 사이트등록및소유확인 -->
<meta name="naver-site-verification" content="" />
<meta name="naver-site-verification" content="8720c03a4463520a0bd0979a3a743ff8ef0d8a03" />
<title>메이드유</title>

View File

@@ -1,109 +1,117 @@
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="layoutHeader">
<th:block th:replace="/web/layout/layoutModal :: layoutModal"></th:block>
<link rel="stylesheet" href="/css/web/quick_menu/quick_menu.css">
<script>
// 디바이스 감지 함수
function isMobileDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|| window.innerWidth <= 768;
}
<th:block th:replace="/web/layout/layoutModal :: layoutModal"></th:block>
<link rel="stylesheet" href="/css/web/quick_menu/quick_menu.css">
<script>
// 디바이스 감지 함수
function isMobileDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|| window.innerWidth <= 768;
}
// 카카오톡 상담 함수 (모든 디바이스에서 동작)
function openKakaoTalk() {
window.open('https://pf.kakao.com/_puZExd', '_blank');
}
// 카카오톡 상담 함수 (모든 디바이스에서 동작)
function openKakaoTalk() {
window.open('https://pf.kakao.com/_puZExd', '_blank');
}
// 전화걸기 함수 (모바일에서만 동작)
function makePhoneCall() {
if (isMobileDevice()) {
// 모바일에서만 전화걸기
window.location.href = 'tel:02-547-4711';
} else {
// 데스크톱에서는 동작 안함 (필요시 알림 추가)
console.log('전화걸기는 모바일에서만 가능합니다.');
}
}
// 전화걸기 함수 (모바일에서만 동작)
function makePhoneCall() {
if (isMobileDevice()) {
// 모바일에서만 전화걸기
window.location.href = 'tel:02-547-4711';
} else {
// 데스크톱에서는 동작 안함 (필요시 알림 추가)
console.log('전화걸기는 모바일에서만 가능합니다.');
}
}
function moveDietCenter(){
window.location.href="https://diet.madeu.co.kr/index";
}
// 모바일 여부 확인 함수
function isMobile() {
return window.innerWidth <= 768; // 필요 시 기준 너비 조정
}
function moveDietCenter() {
window.location.href = "https://diet.madeu.co.kr/index";
}
// 모바일 여부 확인 함수
function isMobile() {
return window.innerWidth <= 768; // 필요 시 기준 너비 조정
}
// 현재 경로가 /index인지 확인
function isIndexPage() {
return window.location.pathname === '/index'; // '/index.html'인 경우 조정 필요
}
// 현재 경로가 /index인지 확인
function isIndexPage() {
return window.location.pathname === '/index'; // '/index.html'인 경우 조정 필요
}
// 퀵메뉴 표시/숨김 함수
function toggleQuickMenu() {
const quickMenu = document.querySelector('.quick-menu-simple');
if (!quickMenu) return; // 요소가 없으면 종료
// 퀵메뉴 표시/숨김 함수
function toggleQuickMenu() {
const quickMenu = document.querySelector('.quick-menu-simple');
if (!quickMenu) return; // 요소가 없으면 종료
if (isMobile()) {
// 모바일: /index일 때만 표시
quickMenu.style.display = isIndexPage() ? '' : 'none';
} else {
// 데스크톱: 항상 표시
quickMenu.style.display = '';
}
}
function moveEvent(){
window.location.href="https://petit.madeu.co.kr/webevent/selectListWebEventIntro.do";
}
// 페이지 로드와 리사이즈 이벤트 연결
window.addEventListener('load', toggleQuickMenu);
window.addEventListener('resize', toggleQuickMenu);
</script>
<header>
if (isMobile()) {
// 모바일: /index일 때만 표시
quickMenu.style.display = isIndexPage() ? '' : 'none';
} else {
// 데스크톱: 항상 표시
quickMenu.style.display = '';
}
}
function moveEvent() {
window.location.href = "https://petit.madeu.co.kr/webevent/selectListWebEventIntro.do";
}
function moveReview() {
window.location.href = "https://petit.madeu.co.kr/webreview/selectListProcedureReviewIntro.do";
}
<div class="inner_wrap">
<button class="mb mbmenu" onClick="openNav()"></button>
<a class="logo" href="/index"><img src="/image/logo_199x54.png" alt="logo"></a>
<nav>
<ul class="depth1">
<li th:each="menu : ${menuList}">
<a class="mmenu" th:href="${menu.menuUrl}" th:text="${menu.menuName}"></a>
<ul class="depth2" th:if="${#lists.size(menu.children) > 0}">
<li></li>
<li th:each="subMenu : ${menu.children}">
<a th:href="${subMenu.menuUrl}" th:text="${subMenu.menuName}"></a>
</li>
</ul>
</li>
</ul>
</nav>
// 페이지 로드와 리사이즈 이벤트 연결
window.addEventListener('load', toggleQuickMenu);
window.addEventListener('resize', toggleQuickMenu);
</script>
<header>
<div class="inner_wrap">
<button class="mb mbmenu" onClick="openNav()"></button>
<a class="logo" href="/index"><img src="/image/logo_199x54.png" alt="logo"></a>
<nav>
<ul class="depth1">
<li th:each="menu : ${menuList}">
<a class="mmenu" th:href="${menu.menuUrl}" th:text="${menu.menuName}"></a>
<ul class="depth2" th:if="${#lists.size(menu.children) > 0}">
<li></li>
<li th:each="subMenu : ${menu.children}">
<a th:href="${subMenu.menuUrl}" th:text="${subMenu.menuName}"></a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</header>
<!-- 퀵메뉴 -->
<div class="quick-menu-simple">
<div class="quick-item" onclick="moveReview()">
<img src="/image/quick_menu/review.png" alt="고객후기">
</div>
<div class="quick-item" onclick="moveEvent()">
<img src="/image/quick_menu/event.png" alt="이벤트">
</div>
<!-- 다이어트센터 -->
<div class="quick-item" onclick="moveDietCenter()">
<img src="/image/quick_menu/diet_center.png" alt="다이어트센터">
</div>
<!-- 카카오톡 상담 -->
<div class="quick-item kakao-consult" onclick="openKakaoTalk()">
<img src="/image/quick_menu/kakao_consultation.png" alt="카카오톡 상담">
</div>
<!-- 전화 상담 -->
<div class="quick-item phone-consult" onclick="makePhoneCall()">
<div class="phone-default">
<img src="/image/quick_menu/call_consultation.png" alt="전화 상담">
</div>
<div class="phone-number">
<img src="/image/quick_menu/madeu_phone_number.png" alt="전화 상담">
</div>
</div>
</div>
</header>
<!-- 퀵메뉴 -->
<div class="quick-menu-simple">
<div class="quick-item" onclick="moveEvent()">
<img src="/image/quick_menu/event.png" alt="이벤트">
</div>
<!-- 다이어트센터 -->
<div class="quick-item" onclick="moveDietCenter()">
<img src="/image/quick_menu/diet_center.png" alt="다이어트센터">
</div>
<!-- 카카오톡 상담 -->
<div class="quick-item kakao-consult" onclick="openKakaoTalk()">
<img src="/image/quick_menu/kakao_consultation.png" alt="카카오톡 상담">
</div>
<!-- 전화 상담 -->
<div class="quick-item phone-consult" onclick="makePhoneCall()">
<div class="phone-default">
<img src="/image/quick_menu/call_consultation.png" alt="전화 상담">
</div>
<div class="phone-number">
<img src="/image/quick_menu/madeu_phone_number.png" alt="전화 상담">
</div>
</div>
</div>
<script src="/js/web/layout/layoutHeader.js?ver=1"></script>
<script src="/js/web/layout/layoutHeader.js?ver=1"></script>
</th:block>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,432 +1,21 @@
<!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/layout}">
<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/layout}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<th:block layout:fragment="layoutCss">
<!-- Choices.js CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.css" />
<style>
* { box-sizing: border-box; }
body {
font-family: 'Noto Sans KR', sans-serif;
margin: 0;
background: #f7f7f9;
color: #222;
}
#service-header {
background: #fff;
border-bottom: 1px solid #ececec;
padding: 14px 0 14px 24px;
font-size: 0.98em;
color: #888;
}
.main-wrap {
max-width: 1280px;
margin: 0 auto;
background: #fff;
border-radius: 18px;
box-shadow: 0 2px 12px rgba(0,0,0,0.07);
margin-top: 32px;
padding: 0 0 32px 0;
}
.top-section {
display: flex;
flex-wrap: wrap;
gap: 32px;
padding: 32px 32px 0 32px;
align-items: flex-start;
}
.img-box {
border-radius: 18px;
align-items: center;
justify-content: center;
overflow: hidden;
}
.img-box img {
width: 100%;
object-fit: cover;
border-radius: 18px;
}
.info-box {
flex: 1 1 300px;
min-width: 240px;
}
.info-title {
font-size: 1.7em;
font-weight: 700;
margin-bottom: 6px;
color: #222;
}
.info-desc {
color: #444;
font-size: 1.1em;
margin-bottom: 18px;
}
.info-price {
font-size: 1.2em;
font-weight: 700;
color: #b23c3c;
margin-bottom: 18px;
}
.select-row {
margin-bottom: 18px;
}
.select-row label {
font-weight: 500;
margin-bottom: 8px;
display: block;
}
.total-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.1em;
margin-bottom: 18px;
}
.total-row .total-label {
color: #888;
}
.total-row .total-price {
font-weight: bold;
color: #b23c3c;
}
.reserve-btn {
width: 100%;
padding: 14px 0;
background: #b23c3c;
color: #fff;
border: none;
border-radius: 8px;
font-size: 1.1em;
font-weight: bold;
cursor: pointer;
transition: background 0.15s;
}
.reserve-btn:disabled {
background: #ddd;
color: #888;
cursor: not-allowed;
}
.desc-section {
margin-top: 36px;
padding: 0 32px;
}
.desc-title {
font-size: 1.25em;
font-weight: 700;
margin-bottom: 12px;
color: #222;
}
.desc-content {
color: #444;
font-size: 1.05em;
line-height: 1.7;
margin-bottom: 20px;
}
.hashtag-section {
margin-top: 30px;
padding: 20px 0;
border-top: 1px solid #eee;
}
.hashtag-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
.hashtag-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.hashtag {
display: inline-block;
background-color: #f8f9fa;
color: #495057;
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
text-decoration: none;
border: 1px solid #dee2e6;
transition: all 0.3s ease;
}
.hashtag:hover {
background-color: #007bff;
color: white;
border-color: #007bff;
cursor: pointer;
}
#thumbnail-bottom-txt{
padding: 8px;
margin-top: 10px;
background-color: #fff;
}
/* Choices.js 커스터마이징 - 개선된 버전 */
.choices {
border: 1px solid #ddd !important;
border-radius: 6px !important;
font-size: 1em !important;
background: #fff !important;
min-width: 300px !important;
}
.choices__inner {
background: #fff !important;
padding: 8px 12px !important;
min-height: 40px !important;
color: #222 !important;
min-width: 280px !important;
width: 100% !important;
}
/* 플레이스홀더 텍스트 잘림 방지 - 핵심 수정 */
.choices__placeholder {
color: #888 !important;
opacity: 1 !important;
width: 100% !important;
min-width: 200px !important;
white-space: nowrap !important;
overflow: visible !important;
text-overflow: clip !important;
display: block !important;
}
.choices__input {
color: #222 !important;
background: transparent !important;
min-width: 200px !important;
width: 100% !important;
}
.choices__input::placeholder {
color: #888 !important;
opacity: 1 !important;
width: 100% !important;
min-width: 200px !important;
white-space: nowrap !important;
overflow: visible !important;
}
/* 다중 선택시 입력 필드 너비 확보 */
.choices[data-type*="select-multiple"] .choices__input {
min-width: 200px !important;
width: auto !important;
flex: 1 !important;
}
/* 선택된 항목들과 입력 필드 공간 분배 */
.choices__list--multiple {
/* display: flex !important; */
flex-wrap: wrap !important;
align-items: center !important;
}
.choices__list--multiple:empty + .choices__input {
min-width: 200px !important;
width: 100% !important;
flex: 1 !important;
}
/* 선택된 항목의 가격 표시 스타일 - 새로 추가 */
.selected-item-content {
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
flex: 1 !important;
}
.selected-item-name {
font-weight: 500 !important;
margin-bottom: 2px !important;
}
.selected-item-price {
font-size: 0.85em !important;
}
.selected-price {
color: #b23c3c !important;
font-weight: bold !important;
}
.selected-price-discount {
color: #b23c3c !important;
font-weight: bold !important;
font-size: 0.9em !important;
}
.selected-price-original {
color: #aaa !important;
font-size: 0.85em !important;
text-decoration: line-through !important;
margin-left: 4px !important;
}
/* 선택된 항목들 스타일 수정 */
.choices__list--multiple .choices__item {
background-color: #f8f9fa !important;
border: 1px solid #dee2e6 !important;
border-radius: 16px !important;
padding: 8px 12px !important;
margin: 2px !important;
font-size: 0.9em !important;
color: #495057 !important;
flex-shrink: 0 !important;
display: flex !important;
align-items: center !important;
/* max-width: 300px !important; */
}
/* 빨간색으로 변경 */
.choices__button {
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjYjIzYzNjIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==') !important;
background-size: 14px 14px !important;
background-position: center !important;
background-repeat: no-repeat !important;
border-left:0 !important;
margin:0 !important;
padding:0 !important;
width:14px !important;
}
/* 드롭다운 컨테이너 */
.choices__list--dropdown {
background: #fff !important;
border: 1px solid #ddd !important;
border-radius: 6px !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
}
/* 드롭다운 옵션들 스타일 */
.choices__list--dropdown .choices__item {
padding: 8px 12px !important;
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
color: #222 !important;
}
.choices__list--dropdown .choices__item--selectable:hover {
background-color: #f5f5f5 !important;
color: #222 !important;
}
.choices__list--dropdown .choices__item--highlighted {
background-color: #b23c3c !important;
color: #fff !important;
}
/* 포커스 상태 */
.choices.is-focused .choices__inner {
border-color: #b23c3c !important;
}
/* 가격 표시 스타일 */
.procedure-price {
color: #b23c3c !important;
font-weight: bold !important;
font-size: 0.9em !important;
}
.procedure-price-discount {
color: #b23c3c !important;
font-weight: bold !important;
font-size: 0.9em !important;
}
.procedure-price-original {
color: #aaa !important;
font-size: 0.85em !important;
text-decoration: line-through !important;
margin-left: 4px !important;
}
/* 드롭다운이 열렸을 때 하이라이트된 항목의 가격 색상 */
.choices__list--dropdown .choices__item--highlighted .procedure-price,
.choices__list--dropdown .choices__item--highlighted .procedure-price-discount {
color: #fff !important;
}
.choices__list--dropdown .choices__item--highlighted .procedure-price-original {
color: #ddd !important;
}
/* 검색 입력창 스타일 */
.choices[data-type*="select-multiple"] .choices__input {
background-color: transparent !important;
color: #222 !important;
}
/* 비활성화 상태 */
.choices.is-disabled .choices__inner {
background-color: #f8f9fa !important;
color: #6c757d !important;
cursor: not-allowed !important;
}
/* 반응형 대응 */
@media (max-width: 600px) {
.choices {
min-width: 250px !important;
}
.choices__inner {
min-width: 230px !important;
}
.choices__placeholder,
.choices__input,
.choices__input::placeholder {
min-width: 150px !important;
}
.choices__list--multiple .choices__item {
max-width: 250px !important;
padding: 6px 10px !important;
}
.selected-item-name {
font-size: 0.9em !important;
}
.selected-item-price {
font-size: 0.8em !important;
}
.choices__list--multiple .choices__item[data-deletable] .choices__button {
width: 20px !important;
height: 20px !important;
font-size: 16px !important;
text-indent: 0 !important;
}
}
@media (max-width: 900px) {
.main-wrap { margin-top: 16px; }
.top-section { flex-direction: column; gap: 18px; padding: 20px 10px 0 10px; }
.img-box { width: 100%;}
.info-box { min-width: unset; }
.desc-section { padding: 0 10px; }
}
@media (max-width: 600px) {
.main-wrap { margin-top: 0; border-radius: 0; box-shadow: none; }
.top-section { padding: 12px 2vw 0 2vw; }
.desc-section { padding: 0 2vw; }
}
</style>
<!-- Choices.js CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.css" />
<link rel="stylesheet" th:href="@{/css/web/webevent/webEventSelect.css}" />
</th:block>
<th:block layout:fragment="layout_top_script">
<script th:inline="javascript">
let category_div_cd = [[${CATEGORY_DIV_CD}]];
let category_no = [[${CATEGORY_NO}]];
let post_no = [[${POST_NO}]];
const CDN_URL = [[${@environment.getProperty('url.cdn')}]];
</script>
<script th:inline="javascript">
let category_div_cd = [[${ CATEGORY_DIV_CD }]];
let category_no = [[${ CATEGORY_NO }]];
let post_no = [[${ POST_NO }]];
const CDN_URL = [[${@environment.getProperty('url.cdn') }]];
</script>
</th:block>
<th:block layout:fragment="layoutContent">
@@ -459,6 +48,8 @@ const CDN_URL = [[${@environment.getProperty('url.cdn')}]];
</div>
</div>
<div class="event-period" id="event-period" style="display:none;"></div>
<div class="info-price"><span id="price">0</span>원 부터</div>
<div class="select-row">
@@ -482,301 +73,9 @@ const CDN_URL = [[${@environment.getProperty('url.cdn')}]];
</th:block>
<th:block layout:fragment="layoutContentScript">
<!-- Choices.js JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.js"></script>
<script>
// 전역 변수
let procedureChoices;
let priceList = [];
const totalEl = document.getElementById('total');
const reserveBtn = document.getElementById('reserve-btn');
// 초기화
fn_SelectDetail(category_div_cd, category_no, post_no);
/****************************************************************************
* 카테고리 목록 가져오기
****************************************************************************/
function fn_SelectDetail(category_div_cd, category_no, post_no) {
let formData = new FormData();
formData.append('CATEGORY_DIV_CD', category_div_cd);
formData.append('CATEGORY_NO', category_no);
formData.append('POST_NO', post_no);
$.ajax({
url: encodeURI('/webevent/selectEventDetail.do'),
data: formData,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
async: true,
success: function(data) {
if(data.msgCode == '0') {
// 화면 데이터 변경
$('#title').text(data.rows.TITLE);
$('#serviceThumb').attr('src', CDN_URL + data.rows.THUMBNAIL_PATH);
$('#thumbnail-bottom-txt').text(data.rows.THUMBNAIL_BOTTOM_TXT);
$('#contents').text(data.rows.CONTENT);
if(data.rows.PRICE == null || data.rows.PRICE == undefined) {
$('#price').text('0');
} else {
$('#price').text(data.rows.PRICE.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","));
}
// header 동적 변경
$('#header-category-nm').text(data.rows.CATEGORY_NM);
$('#header-title').text(data.rows.TITLE);
// 해시태그 처리
var hashtagHtml = '';
if (data.rows.HASHTAG) {
var tags = data.rows.HASHTAG.split('#');
tags.forEach(function(tag) {
var trimmed = tag.trim();
if(trimmed) {
hashtagHtml += '<span class="hashtag">#' + trimmed + '</span>';
}
});
}
$('.hashtag-list').html(hashtagHtml);
$('#contents_path').attr('src', CDN_URL + data.rows.CONTENTS_PATH);
// 시술 목록 데이터 처리
updateProcedureOptions(data.price || []);
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
},
error: function(xhr, status, error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다. 잠시후 다시시도하십시오.");
},
beforeSend: function() {
$(".loading-image-layer").show();
},
complete: function() {
$(".loading-image-layer").hide();
}
});
}
/****************************************************************************
* Choices.js 초기화 및 옵션 업데이트
****************************************************************************/
function updateProcedureOptions(data) {
priceList = data;
// 기존 Choices 인스턴스 제거
if (procedureChoices) {
procedureChoices.destroy();
}
// 선택 옵션 데이터 생성
const choices = data.map(item => {
if (!item.MU_TREATMENT_PROCEDURE_ID || !item.TREATMENT_PROCEDURE_NAME) return null;
const basePrice = (item.PRICE || 0) + (item.VAT || 0);
const discountPrice = item.DISCOUNT_PRICE;
return {
value: item.MU_TREATMENT_PROCEDURE_ID,
label: item.TREATMENT_PROCEDURE_NAME,
customProperties: {
name: item.TREATMENT_PROCEDURE_NAME,
price: basePrice,
discountPrice: discountPrice,
originalData: item
}
};
}).filter(Boolean);
// Choices.js 초기화
procedureChoices = new Choices('#procedure-select', {
removeItemButton: true,
searchEnabled: true,
searchPlaceholderValue: '시술명으로 검색...',
placeholder: true,
placeholderValue: '시술을 선택하세요',
maxItemCount: -1,
choices: choices,
shouldSort: false,
searchResultLimit: 10,
searchFields: ['label', 'value'],
itemSelectText: '',
noChoicesText: '선택 가능한 시술이 없습니다',
noResultsText: '검색 결과가 없습니다',
loadingText: '시술 정보를 불러오는 중...',
// 템플릿 커스터마이징 - 선택된 항목에 가격 표시 추가
callbackOnCreateTemplates: function(template) {
return {
// 선택된 항목 템플릿 - 가격 정보 포함
item: ({ classNames }, data) => {
const customProps = data.customProperties || {};
const name = customProps.name || data.label;
const price = customProps.price || 0;
const discountPrice = customProps.discountPrice;
// 가격 표시 HTML 생성
let priceHtml = '';
if (discountPrice && discountPrice < price) {
priceHtml = `
<span class="selected-price-discount">${discountPrice.toLocaleString()}원</span>
<span class="selected-price-original">${price.toLocaleString()}원</span>
`;
} else {
priceHtml = `<span class="selected-price">${price.toLocaleString()}원</span>`;
}
return template(`
<div class="${classNames.item} ${data.highlighted ? classNames.highlightedState : classNames.itemSelectable}" data-item data-id="${data.id}" data-value="${data.value}">
<span class="selected-item-content">
<span class="selected-item-name">${name}</span>
<span class="selected-item-price">${priceHtml}</span>
</span>
<button type="button" class="${classNames.button}" aria-label="Remove item: '${data.value}'" data-button>X</button>
</div>
`);
},
// 드롭다운 선택 옵션 템플릿
choice: ({ classNames }, data) => {
const customProps = data.customProperties || {};
const name = customProps.name || data.label;
const price = customProps.price || 0;
const discountPrice = customProps.discountPrice;
let priceHtml = '';
if (discountPrice && discountPrice < price) {
priceHtml = `
<span class="procedure-price-discount">${discountPrice.toLocaleString()}원</span>
<span class="procedure-price-original">${price.toLocaleString()}원</span>
`;
} else {
priceHtml = `<span class="procedure-price">${price.toLocaleString()}원</span>`;
}
return template(`
<div class="${classNames.item} ${classNames.itemChoice} ${data.disabled ? classNames.itemDisabled : classNames.itemSelectable}" data-select-text="" data-choice ${data.disabled ? 'data-choice-disabled aria-disabled="true"' : 'data-choice-selectable'} data-id="${data.id}" data-value="${data.value}">
<div style="display: flex; justify-content: space-between; width: 100%; align-items: center;">
<span>${name}</span>
${priceHtml}
</div>
</div>
`);
}
};
}
});
// 이벤트 리스너 추가
const selectElement = document.getElementById('procedure-select');
selectElement.addEventListener('change', function(event) {
console.log('Selection changed:', event.detail);
updateTotalPrice();
});
selectElement.addEventListener('addItem', function(event) {
console.log('Item added:', event.detail);
updateTotalPrice();
});
selectElement.addEventListener('removeItem', function(event) {
console.log('Item removed:', event.detail);
updateTotalPrice();
});
}
/****************************************************************************
* 총 금액 업데이트 - 개선된 버전
****************************************************************************/
function updateTotalPrice() {
if (!procedureChoices) {
console.log('procedureChoices not initialized');
return;
}
const selectedValues = procedureChoices.getValue(true);
console.log('Selected values:', selectedValues);
let total = 0;
selectedValues.forEach(value => {
const item = priceList.find(p => p.MU_TREATMENT_PROCEDURE_ID == value);
console.log('Found item for value', value, ':', item);
if (item) {
const basePrice = (item.PRICE || 0) + (item.VAT || 0);
const discountPrice = item.DISCOUNT_PRICE;
// 할인가가 있고 더 저렴하면 할인가 사용, 아니면 기본가격 사용
const finalPrice = (discountPrice && discountPrice < basePrice) ? discountPrice : basePrice;
total += finalPrice;
console.log('Added price:', finalPrice);
}
});
console.log('Total calculated:', total);
totalEl.textContent = total.toLocaleString() + '원';
reserveBtn.disabled = selectedValues.length === 0;
}
/****************************************************************************
* 예약 페이지로 이동
****************************************************************************/
function fn_moveReservation(category_div, category_no, post_no) {
if (!procedureChoices) {
alert('시술 선택 기능이 초기화되지 않았습니다.');
return;
}
const selectedValues = procedureChoices.getValue(true);
if (selectedValues.length === 0) {
alert('시술을 선택해주세요.');
return;
}
const form = document.createElement('form');
form.method = 'post';
form.action = '/webevent/selectMakeReservation.do';
// 기본 파라미터 추가
const params = [
{ name: 'CATEGORY_DIV_CD', value: category_div },
{ name: 'CATEGORY_NO', value: category_no },
{ name: 'POST_NO', value: post_no }
];
params.forEach(param => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = param.name;
input.value = param.value;
form.appendChild(input);
});
// 선택된 시술 추가
selectedValues.forEach(value => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'PROCEDURE_ID';
input.value = value;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
// 예약 버튼 이벤트
reserveBtn.addEventListener('click', function() {
fn_moveReservation(category_div_cd, category_no, post_no);
});
</script>
<!-- Choices.js JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/choices.js/10.2.0/choices.min.js"></script>
<script th:src="@{/js/web/webevent/webEventSelect.js}"></script>
</th:block>
</html>

View File

@@ -1,595 +1,56 @@
<!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/layout}">
<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/layout}">
<th:block layout:fragment="layoutCss">
<style>
* {
box-sizing: border-box;
}
/* 전체 기본 스타일 */
html, body {
height: 100vh;
margin: 0;
padding: 0;
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
background: #f8f9fa;
color: #1a1a1a;
overflow-x: hidden;
font-size: 16px;
line-height: 1.6;
}
/* 메인 컨테이너 */
.container {
max-width: 1280px;
width: 100%;
margin: 0 auto;
min-height: calc(100vh - 300px);
display: flex;
flex-direction: column;
gap: 1.5rem;
}
/* 상단 헤더 영역 */
.header {
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0,0,0,0.06);
padding: 1rem 2rem;
border-bottom: 3px solid #C60B24;
flex-shrink: 0;
}
.page-title {
font-size: clamp(1.5rem, 3vw, 1.875rem); /* 24px ~ 30px */
font-weight: 700;
color: #1a1a1a;
margin: 0;
letter-spacing: -0.025em;
}
/* 하단 콘텐츠 영역 (사이드바 + 메인) */
.content-wrapper {
flex: 1;
display: flex;
gap: 1.5rem;
min-height: 0;
}
/* 좌측 사이드바 */
.sidebar {
width: 240px;
min-width: 220px;
flex-shrink: 0;
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0,0,0,0.06);
height: fit-content;
max-height: 100%;
overflow-y: auto;
position: sticky;
top: 0;
}
.sidebar-header {
padding: 1.25rem 1.5rem;
font-size: 1.125rem; /* 18px */
font-weight: 700;
color: #C60B24;
border-bottom: 1px solid #e9ecef;
}
.category-list {
list-style: none;
padding: 1rem;
margin: 0;
}
.category-item {
margin-bottom: 0.375rem;
}
.category-link {
display: block;
width: 100%;
padding: 0.75rem 1rem;
font-size: 0.95rem; /* 15.2px */
color: #6b7280;
text-decoration: none;
background: none;
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
}
.category-link:hover {
background: #f9fafb;
color: #C60B24;
transform: translateX(2px);
}
.category-link.active {
background: rgba(198, 11, 36, 0.08);
color: #C60B24;
font-weight: 600;
border-left: 4px solid #C60B24;
}
/* 메인 콘텐츠 영역 */
.main-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
}
/* 이벤트 리스트 */
.event-list {
flex: 1;
overflow-y: auto;
padding-right: 0.5rem;
min-height: 0;
}
.event-grid {
display: flex;
flex-direction: column;
gap: 1.25rem;
padding-bottom: 2rem;
}
/* 이벤트 카드 */
.event-card {
display: flex;
background: white;
border-radius: 16px;
box-shadow: 0 2px 16px rgba(0,0,0,0.06);
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #f1f5f9;
min-height: 160px;
cursor: pointer;
}
.event-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 32px rgba(0,0,0,0.12);
border-color: #C60B24;
}
.event-img {
width: 340px;
min-width: 280px;
background: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.event-img img {
width: 100%;
object-fit: cover;
display: block;
}
.event-info {
flex: 1;
padding: 1.5rem;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
}
.event-title {
font-size: 1.25rem; /* 20px */
font-weight: 700;
margin-bottom: 0.5rem;
color: #1a1a1a;
letter-spacing: -0.025em;
}
.event-desc {
color: #6b7280;
font-size: 0.9375rem; /* 15px */
margin-bottom: 0.75rem;
line-height: 1.5;
}
.event-meta {
font-size: 0.875rem; /* 14px */
color: #9ca3af;
margin-bottom: 1rem;
}
.event-price {
font-size: 1.125rem; /* 18px */
color: #C60B24;
font-weight: 700;
margin-bottom: 0;
}
/* 로딩 및 에러 메시지 */
.loading {
text-align: center;
padding: 2rem;
color: #6b7280;
font-size: 0.9375rem; /* 15px */
}
.error-message {
text-align: center;
padding: 2rem;
color: #dc2626;
font-size: 0.9375rem; /* 15px */
}
/* 스크롤바 커스터마이징 */
.event-list::-webkit-scrollbar,
.sidebar::-webkit-scrollbar {
width: 6px;
}
.event-list::-webkit-scrollbar-track,
.sidebar::-webkit-scrollbar-track {
background: #f8fafc;
border-radius: 3px;
}
.event-list::-webkit-scrollbar-thumb,
.sidebar::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.event-list::-webkit-scrollbar-thumb:hover,
.sidebar::-webkit-scrollbar-thumb:hover {
background: #C60B24;
}
/* 반응형 디자인 - 태블릿 */
@media (max-width: 1024px) {
.container {
padding: 1rem;
gap: 1rem;
}
.content-wrapper {
gap: 1rem;
}
.sidebar {
width: 220px;
min-width: 200px;
}
.event-img {
width: 220px;
min-width: 220px;
}
.event-img img {
height: 140px;
}
.header {
padding: 1.25rem 1.5rem;
}
.page-title {
font-size: clamp(1.375rem, 2.8vw, 1.75rem); /* 22px ~ 28px */
}
}
/* 반응형 디자인 - 모바일 */
@media (max-width: 768px) {
.container {
flex-direction: column;
padding: 1rem;
gap: 1rem;
}
.content-wrapper {
flex-direction: column;
gap: 1rem;
}
.sidebar {
width: 100%;
position: static;
max-height: none;
order: 1;
}
.category-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 0.5rem;
padding: 1rem;
}
.category-link {
text-align: center;
padding: 0.75rem 0.5rem;
font-size: 0.875rem; /* 14px */
}
.main-content {
order: 2;
}
.event-list {
overflow-y: visible;
height: auto;
}
.event-card {
flex-direction: column;
min-height: auto;
}
.event-img {
width: 100%;
min-width: auto;
}
.event-img img {
height: 200px;
}
.event-info {
padding: 1.25rem;
}
.header {
padding: 1rem 1.5rem;
border-radius: 12px;
}
.page-title {
font-size: clamp(1.25rem, 2.5vw, 1.5rem); /* 20px ~ 24px */
}
.event-title {
font-size: 1.125rem; /* 18px */
}
.event-price {
font-size: 1rem; /* 16px */
}
}
/* 작은 모바일 */
@media (max-width: 480px) {
.container {
padding: 0.75rem;
}
.header {
padding: 0.875rem 1rem;
}
.page-title {
font-size: clamp(1.125rem, 2.2vw, 1.375rem); /* 18px ~ 22px */
}
.event-info {
padding: 1rem;
}
.event-title {
font-size: 1rem; /* 16px */
}
.event-desc {
font-size: 0.875rem; /* 14px */
}
}
</style>
<link rel="stylesheet" th:href="@{/css/web/webevent/webEventSelectList.css}" />
</th:block>
<th:block layout:fragment="layout_top_script">
<script th:inline="javascript">
const CDN_URL = [[${@environment.getProperty('url.cdn')}]];
</script>
<script th:inline="javascript">
const CDN_URL = [[${@environment.getProperty('url.cdn') }]];
</script>
</th:block>
<th:block layout:fragment="layoutContent">
<div class="container">
<!-- 상단 헤더 영역 -->
<div class="header">
<h1 class="page-title">이벤트 안내</h1>
</div>
<div class="container">
<!-- 상단 헤더 영역 -->
<div class="header">
<h1 class="page-title">이벤트 안내</h1>
</div>
<!-- 하단 콘텐츠 영역 (사이드바 + 메인) -->
<div class="content-wrapper">
<!-- 좌측 사이드바 -->
<aside class="sidebar">
<!--<div class="sidebar-header">이벤트</div>-->
<ul class="category-list" id="category-list">
<!-- 카테고리 JS로 렌더링 -->
</ul>
</aside>
<!-- 하단 콘텐츠 영역 (사이드바 + 메인) -->
<div class="content-wrapper">
<!-- 좌측 사이드바 -->
<aside class="sidebar">
<ul class="category-list" id="category-list">
<!-- 카테고리 JS로 렌더링 -->
</ul>
</aside>
<!-- 메인 콘텐츠 -->
<main class="main-content">
<div class="event-list">
<div class="event-grid" id="event-grid">
<!-- 이벤트 카드 JS로 렌더링 -->
<!-- 메인 콘텐츠 -->
<main class="main-content">
<div class="event-list">
<div class="event-grid" id="event-grid">
<!-- 이벤트 카드 JS로 렌더링 -->
</div>
</div>
</div>
</main>
</main>
</div>
</div>
<!-- 지난 이벤트 레이어 팝업 -->
<div class="popup-overlay" id="expired-popup">
<div class="popup-content">
<div class="popup-icon"></div>
<div class="popup-title">지난 이벤트</div>
<div class="popup-message">이 이벤트는 종료되었습니다.<br>다른 진행 중인 이벤트를 확인해 주세요.</div>
<button class="popup-close-btn" onclick="eventManager.hideExpiredPopup()">확인</button>
</div>
</div>
</div>
</th:block>
<th:block layout:fragment="layoutContentScript">
<script>
class EventManager {
constructor() {
this.events = [];
this.categories = [];
this.init();
}
async init() {
await this.loadCategories();
}
async apiRequest(url, data) {
return new Promise((resolve, reject) => {
$.ajax({
url: encodeURI(url),
data: data,
dataType: 'json',
processData: false,
contentType: false,
type: 'POST',
success: resolve,
error: reject,
beforeSend: () => $(".loading-image-layer").show(),
complete: () => $(".loading-image-layer").hide()
});
});
}
async loadCategories() {
try {
const formData = new FormData();
formData.append('bannerType', 'A');
const data = await this.apiRequest('/webevent/selectListWebEvent.do', formData);
if (data.msgCode === '0') {
this.categories = data.rows;
this.renderCategories();
if (this.categories.length > 0) {
this.loadEvents(this.categories[0].CATEGORY_NO);
}
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
} catch (error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다.");
}
}
async loadEvents(categoryNo) {
try {
const formData = new FormData();
formData.append('category_no', categoryNo);
const data = await this.apiRequest('/webevent/selectListEvent.do', formData);
if (data.msgCode === '0') {
this.events = data.rows.map(row => ({
img: CDN_URL + row.THUMBNAIL_PATH,
title: row.TITLE,
desc: row.CONTENT,
meta: row.THUMBNAIL_BOTTOM_TXT,
price: {
before: Number(row.PRICE) || 0,
after: Number(row.DISCOUNT_PRICE) || 0
},
categoryDiv: row.CATEGORY_DIV_CD,
categoryNo: row.CATEGORY_NO,
postNo: row.POST_NO
}));
this.renderEvents();
} else {
modalEvent.danger("조회 오류", data.msgDesc);
}
} catch (error) {
modalEvent.danger("조회 오류", "조회 중 오류가 발생하였습니다.");
}
}
renderCategories() {
const html = this.categories.map((cat, idx) => `
<li class="category-item">
<a href="#" class="category-link ${idx === 0 ? 'active' : ''}"
data-category="${cat.CATEGORY_NO}">${cat.CATEGORY_NM}</a>
</li>
`).join('');
document.getElementById('category-list').innerHTML = html;
document.querySelectorAll('.category-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
document.querySelectorAll('.category-link').forEach(item =>
item.classList.remove('active'));
link.classList.add('active');
this.loadEvents(link.dataset.category);
document.querySelector('.event-list').scrollTop = 0;
});
});
}
renderEvents() {
const html = this.events.map(event => `
<div class="event-card" data-category-div="${event.categoryDiv}"
data-category-no="${event.categoryNo}" data-post-no="${event.postNo}">
<div class="event-img">
<img src="${event.img}" alt="${event.title}">
</div>
<div class="event-info">
<div class="event-title">${event.title}</div>
${event.meta ? `<div class="event-meta">${event.meta}</div>` : ''}
<div class="event-price">
${event.price.before !== event.price.after
? `<span style="text-decoration:line-through; color:#9ca3af; font-size:0.95em; margin-right:8px;">
${event.price.before.toLocaleString()}
</span>`
: ''}
${event.price.after.toLocaleString()}원 부터
</div>
</div>
</div>
`).join('');
document.getElementById('event-grid').innerHTML = html;
// 카드 클릭 이벤트 추가
document.querySelectorAll('.event-card').forEach(card => {
card.addEventListener('click', () => {
const categoryDiv = card.dataset.categoryDiv;
const categoryNo = card.dataset.categoryNo;
const postNo = card.dataset.postNo;
this.goToDetail(categoryDiv, categoryNo, postNo);
});
});
}
goToDetail(categoryDiv, categoryNo, postNo) {
const form = document.createElement('form');
form.method = 'get';
form.action = '/webevent/selectEventDetailIntro.do';
const fields = [
{ name: 'CATEGORY_DIV_CD', value: categoryDiv },
{ name: 'CATEGORY_NO', value: categoryNo },
{ name: 'POST_NO', value: postNo }
];
fields.forEach(field => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = field.name;
input.value = field.value;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
}
const eventManager = new EventManager();
</script>
<script th:src="@{/js/web/webevent/webEventSelectList.js}"></script>
</th:block>
</html>

View File

@@ -0,0 +1,44 @@
<!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/layout}">
<th:block layout:fragment="layoutCss">
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css" rel="stylesheet" />
<link rel="stylesheet" th:href="@{/css/web/webreview/procedureReviewSelect.css}" />
</th:block>
<th:block layout:fragment="layout_top_script">
<script th:inline="javascript">
const muProcedureReviewId = [[${ muProcedureReviewId }]];
const CDN_URL = [[${@environment.getProperty('url.cdn') }]];
</script>
</th:block>
<th:block layout:fragment="layoutContent">
<div class="container">
<div class="breadcrumb">
<a href="/webreview/selectListProcedureReviewIntro.do">고객후기</a>
<span>&gt;</span>
<span id="breadcrumb-title">상세보기</span>
</div>
<article class="review-article">
<div class="review-header">
<h1 class="review-title" id="review-title"></h1>
<div class="review-meta">
<span class="review-date" id="review-date"></span>
<span class="review-views">조회 <span id="review-views">0</span></span>
</div>
<div class="review-tags" id="review-tags"></div>
</div>
<div class="review-content" id="review-content">
<div class="loading">게시글을 불러오는 중...</div>
</div>
</article>
<div class="btn-area">
<a href="/webreview/selectListProcedureReviewIntro.do" class="btn-list">목록으로</a>
</div>
</div>
</th:block>
<th:block layout:fragment="layoutContentScript">
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js"></script>
<script th:src="@{/js/web/webreview/procedureReviewSelect.js}"></script>
</th:block>
</html>

View File

@@ -0,0 +1,34 @@
<!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/layout}">
<th:block layout:fragment="layoutCss">
<link rel="stylesheet" th:href="@{/css/web/webreview/procedureReviewSelectList.css}" />
</th:block>
<th:block layout:fragment="layout_top_script">
<script th:inline="javascript">
const CDN_URL = [[${@environment.getProperty('url.cdn') }]];
</script>
</th:block>
<th:block layout:fragment="layoutContent">
<div class="container">
<div class="header">
<h1 class="page-title">고객후기</h1>
<p class="page-subtitle">실제 고객님들의 생생한 후기를 확인하세요</p>
</div>
<div class="search-area">
<div class="search-box">
<input type="text" id="searchTitle" placeholder="제목으로 검색" />
<button id="btnSearch" class="search-btn">검색</button>
</div>
</div>
<div class="review-grid" id="review-grid">
<div class="loading">고객후기를 불러오는 중...</div>
</div>
<div class="pagination-area" id="pagination-area"></div>
</div>
</th:block>
<th:block layout:fragment="layoutContentScript">
<script th:src="@{/js/web/webreview/procedureReviewSelectList.js}"></script>
</th:block>
</html>