Merge branch 'refs/heads/dev' into xithoon

pull/24/head
xhoon 2 weeks ago
commit 6d7b1a3d01

@ -56,13 +56,11 @@ public class DayAnswerController {
@PostMapping("/minwon/dayanswer/dayanswer-select.ajax")
public ResponseEntity<?> list(@ModelAttribute DayAnswerDto.Request.Search dto) {
dto.setTotalCount(0);
dto.setPagingYn("Y");
List<DayAnswerDto.Response.cpMain> result;
dto.setTotalCount(0);
dto.setPagingYn("N");
result = dayAnswerService.selectAllDayAnswer(dto);
List<DayAnswerDto.Response.cpMain> result = dayAnswerService.selectAllDayAnswer(dto);
return ApiResponseUtil.successWithGrid(result, dto);
}
@ -131,57 +129,7 @@ public class DayAnswerController {
HttpServletResponse response
) throws Exception {
// 검색 조건을 이용해 전체 리스트 조회
List<DayAnswerDto.Response.cpMain> list = dayAnswerService.selectAllDayAnswer(dto);
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet("일별답변결과");
int rowNo = 0;
Row header = sheet.createRow(rowNo++);
header.createCell(0).setCellValue("메인코드");
header.createCell(1).setCellValue("시군구코드");
header.createCell(2).setCellValue("구분");
header.createCell(3).setCellValue("접수일자");
header.createCell(4).setCellValue("접수번호");
header.createCell(5).setCellValue("처리자");
header.createCell(6).setCellValue("전화번호");
header.createCell(7).setCellValue("휴대폰");
header.createCell(8).setCellValue("이메일");
header.createCell(9).setCellValue("처리상태");
header.createCell(10).setCellValue("답변내용");
for (DayAnswerDto.Response.cpMain rowData : list) {
Row row = sheet.createRow(rowNo++);
row.createCell(0).setCellValue(nvl(rowData.getAsMmcode()));
row.createCell(1).setCellValue(nvl(rowData.getAsSggcode()));
row.createCell(2).setCellValue(nvl(rowData.getAsIngb()));
row.createCell(3).setCellValue(nvl(rowData.getAsJsdate()));
row.createCell(4).setCellValue(nvl(rowData.getAsJsno()));
row.createCell(5).setCellValue(nvl(rowData.getAsUser()));
row.createCell(6).setCellValue(nvl(rowData.getAsTel()));
row.createCell(7).setCellValue(nvl(rowData.getAsCell()));
row.createCell(8).setCellValue(nvl(rowData.getAsEmail()));
row.createCell(9).setCellValue(nvl(rowData.getAsState()));
row.createCell(10).setCellValue(nvl(rowData.getAsText()));
}
for (int i = 0; i <= 10; i++) {
sheet.autoSizeColumn(i);
}
String fileName = URLEncoder.encode("일별답변결과.xlsx", StandardCharsets.UTF_8.name())
.replaceAll("\\+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
wb.write(response.getOutputStream());
wb.close();
}
private String nvl(String s) {
return s == null ? "" : s;
dayAnswerService.downloadExcel(dto, response);
}
@GetMapping("/minwon/dayanswer/history.ajax")
@ -194,4 +142,30 @@ public class DayAnswerController {
System.out.println(" 결과건수 = " + (list != null ? list.size() : 0));
return list;
}
@PostMapping("/minwon/dayanswer/update-all-state.ajax")
@ResponseBody
public Map<String, Object> updateAllState(
@ModelAttribute DayAnswerDto.Request.Search dto,
@RequestParam("newState") String newState) {
int updated = dayAnswerService.updateAllState(dto, newState);
Map<String, Object> result = new HashMap<>();
result.put("updated", updated);
result.put("result", "success");
return result;
}
@PostMapping("/minwon/dayanswer/update-one-state.ajax")
@ResponseBody
public Map<String, Object> updateOneState(
@RequestParam("asMmcode") String asMmcode,
@RequestParam("newState") String newState) {
int updated = dayAnswerService.updateOneState(asMmcode, newState);
Map<String, Object> result = new HashMap<>();
result.put("updated", updated);
result.put("result", "success");
return result;
}
}

@ -4,6 +4,7 @@ import go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface DayAnswerMapper {
@ -19,4 +20,14 @@ public interface DayAnswerMapper {
List<DayAnswerDto.Response.History> selectHistoryasMmcode(String asMmcode);
int insertAnswerHistory(DayAnswerDto.Request.Update dto);
int updateAllState(@Param("search") DayAnswerDto.Request.Search dto,
@Param("newState") String newState);
int updateOneState(@Param("asMmcode") String asMmcode,
@Param("newState") String newState);
int selectAllDayAnswerCount(DayAnswerDto.Request.Search dto);
List<DayAnswerDto.Response.cpMain> selectAllDayAnswerForExcel(DayAnswerDto.Request.Search dto);
}

@ -17,11 +17,13 @@ public class DayAnswerDto {
public static class Search extends PagingVO {
private String searchCondition;
private String searchKeyword;
private String searchStartDt;
private String searchEndDt;
private String worker;
private String tabState;
}
@Data
@ -70,14 +72,18 @@ public class DayAnswerDto {
private String asSysGubunC;
private String asPetiAncCodeV;
private String asPetiNoC;
private String ccCause;
private String mmCarno;
private String mmDate;
private String mmCode;
}
@Data
public static class History {
private String asMmcode;
private String asText;
private String asUser;
private String asState;
private String updateDt;
private String asMmcode;
private String asText;
private String asUser;
private String asState;
private String updateDt;
}
}

@ -18,5 +18,11 @@ public interface DayAnswerService {
void updateDayAnswer(DayAnswerDto.Request.Update dto);
int updateAllState(DayAnswerDto.Request.Search dto, String newState);
int updateOneState(String asMmcode, String newState);
void downloadExcel(DayAnswerDto.Request.Search dto,
javax.servlet.http.HttpServletResponse response) throws Exception;
}

@ -4,26 +4,79 @@ import go.kr.project.biz.minwon.dayanswer.mapper.DayAnswerMapper;
import go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto;
import go.kr.project.biz.minwon.dayanswer.service.DayAnswerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
@Service
@RequiredArgsConstructor
@Slf4j
public class DayAnswerServiceImpl extends EgovAbstractServiceImpl implements DayAnswerService {
private final DayAnswerMapper dayAnswerMapper;
@Override
public List<DayAnswerDto.Response.cpMain> selectAllDayAnswer(DayAnswerDto.Request.Search dto) {
@Override
public List<DayAnswerDto.Response.cpMain> selectAllDayAnswer(DayAnswerDto.Request.Search dto) {
//log.info("검색 DTO = page={}, perPage={}, startIndex={}, tabState={}, keyword={}",
// dto.getPage(), dto.getPerPage(), dto.getStartIndex(),
// dto.getTabState(), dto.getSearchKeyword());
//log.info("totalCount = {}", dto.getTotalCount());
//
// // 1. page / perPage 기본값 처리
// Integer page = dto.getPage();
// Integer perPage = dto.getPerPage();
//
// if (page == null || page <= 0) {
// page = 1;
// }
// if (perPage == null || perPage <= 0) {
// perPage = 30; // PagingVO 기본값과 맞추기
// }
//
// dto.setPage(page);
// dto.setPerPage(perPage);
//
// // 2. 전체 건수 조회
// int totalCount = dayAnswerMapper.selectAllDayAnswerCount(dto);
// dto.setTotalCount(totalCount);
//
// // 3. startIndex / endIndex 계산
// int startIndex = (page - 1) * perPage;
// dto.setStartIndex(startIndex);
// dto.setEndIndex(startIndex + perPage);
//
// // 4. 전체 페이지 수 계산
// int totalPages = totalCount > 0
// ? (int) Math.ceil((double) totalCount / perPage)
// : 0;
// dto.setTotalPages(totalPages);
//
//log.info("검색 DTO = page={}, perPage={}, startIndex={}, tabState={}, keyword={}",
// dto.getPage(), dto.getPerPage(), dto.getStartIndex(),
// dto.getTabState(), dto.getSearchKeyword());
//log.info("totalCount = {}", dto.getTotalCount());
// 5. 실제 목록 조회
List<DayAnswerDto.Response.cpMain> list = dayAnswerMapper.selectAllDayAnswer(dto);
List<DayAnswerDto.Response.cpMain> result = dayAnswerMapper.selectAllDayAnswer(dto);
log.info("selectAllDayAnswer 결과 건수 = {}", list.size());
return list;
return result;
}
@Override
public DayAnswerDto.Response.cpMain selectOneByasMmcode (String asMmcode){
return dayAnswerMapper.selectOneByasMmcode(asMmcode);
@ -46,6 +99,76 @@ public class DayAnswerServiceImpl extends EgovAbstractServiceImpl implements Day
dayAnswerMapper.updateDayAnswer(dto);
dayAnswerMapper.insertAnswerHistory(dto);
}
@Override
public int updateAllState(DayAnswerDto.Request.Search dto, String newState) {
return dayAnswerMapper.updateAllState(dto, newState);
}
@Override
public int updateOneState(String asMmcode, String newState) {
return dayAnswerMapper.updateOneState(asMmcode, newState);
}
@Override
public void downloadExcel(DayAnswerDto.Request.Search dto,
HttpServletResponse response) throws Exception {
// 페이징 없이 전체 조회 (엑셀용 쿼리)
List<DayAnswerDto.Response.cpMain> list =
dayAnswerMapper.selectAllDayAnswerForExcel(dto);
Workbook wb = new XSSFWorkbook();
Sheet sheet = wb.createSheet("일별답변결과");
int rowNo = 0;
// 헤더
Row header = sheet.createRow(rowNo++);
header.createCell(0).setCellValue("메인코드");
header.createCell(1).setCellValue("시군구코드");
header.createCell(2).setCellValue("구분");
header.createCell(3).setCellValue("접수일자");
header.createCell(4).setCellValue("접수번호");
header.createCell(5).setCellValue("처리자");
header.createCell(6).setCellValue("전화번호");
header.createCell(7).setCellValue("휴대폰");
header.createCell(8).setCellValue("이메일");
header.createCell(9).setCellValue("처리상태");
header.createCell(10).setCellValue("답변내용");
// 데이터
for (DayAnswerDto.Response.cpMain rowData : list) {
Row row = sheet.createRow(rowNo++);
row.createCell(0).setCellValue(nvl(rowData.getAsMmcode()));
row.createCell(1).setCellValue(nvl(rowData.getAsSggcode()));
row.createCell(2).setCellValue(nvl(rowData.getAsIngb()));
row.createCell(3).setCellValue(nvl(rowData.getAsJsdate()));
row.createCell(4).setCellValue(nvl(rowData.getAsJsno()));
row.createCell(5).setCellValue(nvl(rowData.getAsUser()));
row.createCell(6).setCellValue(nvl(rowData.getAsTel()));
row.createCell(7).setCellValue(nvl(rowData.getAsCell()));
row.createCell(8).setCellValue(nvl(rowData.getAsEmail()));
row.createCell(9).setCellValue(nvl(rowData.getAsState()));
row.createCell(10).setCellValue(nvl(rowData.getAsText()));
}
for (int i = 0; i <= 10; i++) {
sheet.autoSizeColumn(i);
}
String fileName = URLEncoder.encode("일별답변결과.xlsx", StandardCharsets.UTF_8.name())
.replaceAll("\\+", "%20");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + fileName + "\"");
wb.write(response.getOutputStream());
wb.close();
}
private String nvl(String s) {
return s == null ? "" : s;
}
}

@ -34,6 +34,18 @@ public class MinwonInitController {
return "biz/minwon/init/init" + TilesConstants.BASE;
}
/**
*
* @return
*/
@GetMapping("/minwon/init/init_popup.do")
public String initPopupView() {
return "biz/minwon/init/init_popup" + TilesConstants.POPUP;
}
@PostMapping("/minwon/init/list.ajax")
public ResponseEntity<?> getMinwonInitListAjax(@ModelAttribute MinwonInitDto.Request.SearchMinwonInitList dto) {

@ -0,0 +1,50 @@
package go.kr.project.biz.search.controller;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto;
import go.kr.project.biz.search.model.SearchDto;
import go.kr.project.biz.search.service.SearchService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Controller
@Slf4j
@RequiredArgsConstructor
public class SearchController {
private final SearchService searchService;
@GetMapping("/search/search.do")
public String searchPageReturn () {
return "biz/search/search" + TilesConstants.BASE;
}
@PostMapping("/search/search-select.ajax")
public ResponseEntity<?> list(@ModelAttribute SearchDto.Request.Search dto) {
dto.setTotalCount(0);
dto.setPagingYn("N");
List<SearchDto.Response.cpMain> result = searchService.selectAllSearch(dto);
return ApiResponseUtil.successWithGrid(result, dto);
}
}

@ -0,0 +1,14 @@
package go.kr.project.biz.search.mapper;
import go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto;
import go.kr.project.biz.search.model.SearchDto;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SearchMapper {
List<SearchDto.Response.cpMain> selectAllSearch(SearchDto.Request.Search dto);
}

@ -0,0 +1,41 @@
package go.kr.project.biz.search.model;
import go.kr.project.system.common.model.PagingVO;
import lombok.Getter;
import lombok.Setter;
public class SearchDto {
public static class Request {
@Getter
@Setter
public static class Search extends PagingVO {
private String searchCondition;
private String searchKeyword;
private String searchStartDt;
private String searchEndDt;
private String worker;
}
}
public static class Response {
@Getter
@Setter
public static class cpMain {
private String mmIngb;
private String mmDate;
private String mmCarno;
private String omJno;
private String omName;
private String mmKeum2;
private String mmImageGb;
private String mmImageCnt;
private String mmCarcheck;
private String mmCode;
}
}
}

@ -0,0 +1,9 @@
package go.kr.project.biz.search.service;
import go.kr.project.biz.search.model.SearchDto;
import java.util.List;
public interface SearchService {
List<SearchDto.Response.cpMain> selectAllSearch(SearchDto.Request.Search dto);
}

@ -0,0 +1,28 @@
package go.kr.project.biz.search.service.impl;
import go.kr.project.biz.search.mapper.SearchMapper;
import go.kr.project.biz.search.model.SearchDto;
import go.kr.project.biz.search.service.SearchService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.egovframe.rte.fdl.cmmn.EgovAbstractServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
@Slf4j
public class SearchServiceImpl extends EgovAbstractServiceImpl implements SearchService {
private final SearchMapper searchMapper;
@Override
public List<SearchDto.Response.cpMain> selectAllSearch(SearchDto.Request.Search dto){
List<SearchDto.Response.cpMain> list = searchMapper.selectAllSearch(dto);
return list;
}
}

@ -10,62 +10,299 @@
resultType은 쿼리 결과를 반납하는 객체를 넣어준다.
아래 보면 이너클래스는 $형태로 들어간다.
-->
<select id="selectAllDayAnswer"
parameterType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Request$Search"
resultType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Response$cpMain">
select *
from cp_answer
<!-- left outer join cp_answer CA on(cm.mm_code = ca.as_mmcode)-->
<!-- 목록 조회 -->
<select id="selectAllDayAnswer"
parameterType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Request$Search"
resultType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Response$cpMain">
SELECT
AS_MMCODE,
AS_SGGCODE,
AS_INGB,
DATE_FORMAT(AS_JSDATE,'%Y-%m-%d') AS asJsdate,
AS_JSNO,
AS_JSNO_M,
AS_BBS_NO,
DATE_FORMAT(AS_LIMIT_DT,'%Y-%m-%d') AS asLimitDt,
AS_USER,
AS_TEL,
AS_CELL,
AS_EMAIL,
AS_STATE,
AS_POST_CD,
AS_POST_DT,
DATE_FORMAT(AS_STATE_DT, '%Y-%m-%d %H:%i:%s') AS asStateDt,
AS_TEXT,
AS_REUSER,
AS_INLINE,
AS_SYS_GUBUN_C,
AS_PETI_ANC_CODE_V,
AS_PETI_NO_C,
DATE_FORMAT(
STR_TO_DATE(CONCAT(M.MM_DATE, M.MM_TIME), '%Y%m%d%H%i%s'),
'%Y-%m-%d %H:%i:%s' ) AS mmDate,
MM_CARNO,
CC_CAUSE,
MM_CODE
FROM CP_ANSWER A
JOIN CP_MAIN M ON A.AS_MMCODE = M.MM_CODE
JOIN CP_OWNER O ON M.MM_OMCODE = O.OM_CODE
LEFT JOIN CP_CANCEL C ON M.MM_CODE = C.CC_MMCODE
<where>
<if test="searchStartDt != null and searchStartDt != ''">
AND as_jsdate &gt;= REPLACE(#{searchStartDt}, '-', '')
</if>
<if test="searchEndDt != null and searchEndDt != ''">
AND as_jsdate &lt;= REPLACE(#{searchEndDt}, '-', '')
</if>
<!-- 탭 상태 필터 -->
<if test="tabState != null and tabState != ''">
AND
<choose>
<when test="tabState == 'FIN_BU'">
AS_STATE = '6'
</when>
<when test="tabState == 'FIN_GE'">
AS_STATE = '7'
</when>
<when test="tabState == 'FIN_ME'">
AS_STATE IN ('8')
</when>
<when test="tabState == 'FIN_ADD'">
AS_STATE IN ('9')
</when>
<when test="tabState == 'FAIL'">
AS_STATE = '5'
</when>
<when test="tabState == 'INCOMP'">
AS_STATE IN ('A','B')
</when>
</choose>
</if>
<!-- 날짜 -->
<if test="searchStartDt != null and searchStartDt != ''">
AND as_jsdate &gt;= REPLACE(#{searchStartDt}, '-', '')
</if>
<if test="searchEndDt != null and searchEndDt != ''">
AND as_jsdate &lt;= REPLACE(#{searchEndDt}, '-', '')
</if>
<!-- 검색구분 + 검색어 -->
<choose>
<!-- 접수번호 -->
<when test="searchCondition == 'title' and searchKeyword != null and searchKeyword != ''">
AND as_jsno LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<!-- 처리자명 -->
<when test="searchCondition == 'contents' and searchKeyword != null and searchKeyword != ''">
AND as_user LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<!-- 전화번호 -->
<when test="searchCondition == 'writer' and searchKeyword != null and searchKeyword != ''">
AND REPLACE(as_tel, '-', '') LIKE CONCAT('%', REPLACE(#{searchKeyword}, '-', ''), '%')
</when>
<!-- 검색구분 없이 검색어만 있는 경우 -->
<otherwise>
<if test="searchKeyword != null and searchKeyword != ''">
AND (
as_jsno LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_user LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_tel LIKE CONCAT('%', #{searchKeyword}, '%')
)
</if>
</otherwise>
</choose>
</where>
ORDER BY AS_JSDATE DESC, AS_JSNO DESC
<!-- &lt;!&ndash; startIndex, perPage 둘 다 있을 때만 LIMIT &ndash;&gt;-->
<!-- <if test="startIndex != null and perPage != null">-->
<!-- LIMIT #{startIndex}, #{perPage}-->
<!-- </if>-->
</select>
<!-- 전체 건수 조회 -->
<select id="selectAllDayAnswerCount"
parameterType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Request$Search"
resultType="int">
SELECT
AS_MMCODE,
AS_SGGCODE,
AS_INGB,
DATE_FORMAT(AS_JSDATE,'%Y-%m-%d') AS asJsdate,
AS_JSNO,
AS_JSNO_M,
AS_BBS_NO,
DATE_FORMAT(AS_LIMIT_DT,'%Y-%m-%d') AS asLimitDt,
AS_USER,
AS_TEL,
AS_CELL,
AS_EMAIL,
AS_STATE,
AS_POST_CD,
AS_POST_DT,
DATE_FORMAT(AS_STATE_DT, '%Y-%m-%d %H:%i:%s') AS asStateDt,
AS_TEXT,
AS_REUSER,
AS_INLINE,
AS_SYS_GUBUN_C,
AS_PETI_ANC_CODE_V,
AS_PETI_NO_C,
DATE_FORMAT(
STR_TO_DATE(CONCAT(M.MM_DATE, M.MM_TIME), '%Y%m%d%H%i%s'),
'%Y-%m-%d %H:%i:%s' ) AS mmDate,
MM_CARNO,
CC_CAUSE,
MM_CODE
FROM CP_ANSWER A
JOIN CP_MAIN M ON A.AS_MMCODE = M.MM_CODE
JOIN CP_OWNER O ON M.MM_OMCODE = O.OM_CODE
LEFT JOIN CP_CANCEL C ON M.MM_CODE = C.CC_MMCODE
<where>
<if test="tabState != null and tabState != ''">
AND
<choose>
<when test="tabState == 'FIN_BU'">
AS_STATE = '6'
</when>
<when test="tabState == 'FIN_GE'">
AS_STATE = '7'
</when>
<when test="tabState == 'FIN_ME'">
AS_STATE IN ('8')
</when>
<when test="tabState == 'FIN_ADD'">
AS_STATE IN ('9')
</when>
<when test="tabState == 'FAIL'">
AS_STATE = '5'
</when>
<when test="tabState == 'INCOMP'">
AS_STATE IN ('A','B')
</when>
</choose>
</if>
<if test="searchStartDt != null and searchStartDt != ''">
AND as_jsdate &gt;= REPLACE(#{searchStartDt}, '-', '')
</if>
<if test="searchEndDt != null and searchEndDt != ''">
AND as_jsdate &lt;= REPLACE(#{searchEndDt}, '-', '')
</if>
<choose>
<!-- 접수번호 -->
<when test="searchCondition == 'title' and searchKeyword != null and searchKeyword != ''">
AND as_Jsno LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<choose>
<when test="searchCondition == 'title' and searchKeyword != null and searchKeyword != ''">
AND as_jsno LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<when test="searchCondition == 'contents' and searchKeyword != null and searchKeyword != ''">
AND as_user LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<when test="searchCondition == 'writer' and searchKeyword != null and searchKeyword != ''">
AND REPLACE(as_tel, '-', '') LIKE CONCAT('%', REPLACE(#{searchKeyword}, '-', ''), '%')
</when>
<otherwise>
<if test="searchKeyword != null and searchKeyword != ''">
AND (
as_jsno LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_user LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_tel LIKE CONCAT('%', #{searchKeyword}, '%')
)
</if>
</otherwise>
</choose>
</where>
</select>
<select id="selectAllDayAnswerForExcel"
parameterType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Request$Search"
resultType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Response$cpMain">
SELECT
AS_MMCODE,
AS_SGGCODE,
AS_INGB,
AS_JSDATE,
AS_JSNO,
AS_JSNO_M,
AS_BBS_NO,
AS_LIMIT_DT,
AS_USER,
AS_TEL,
AS_CELL,
AS_EMAIL,
AS_STATE,
AS_POST_CD,
AS_POST_DT,
DATE_FORMAT(AS_STATE_DT, '%Y-%m-%d %H:%i:%s') AS AS_STATE_DT,
AS_TEXT,
AS_REUSER,
AS_INLINE,
AS_SYS_GUBUN_C,
AS_PETI_ANC_CODE_V,
AS_PETI_NO_C
FROM cp_answer
<where>
<!-- 처리자명 -->
<when test="searchCondition == 'contents' and searchKeyword != null and searchKeyword != ''">
AND as_User LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<if test="tabState != null and tabState != ''">
AND
<choose>
<when test="tabState == 'FIN_BU'">
AS_STATE = '6'
</when>
<when test="tabState == 'FIN_GE'">
AS_STATE = '7'
</when>
<when test="tabState == 'FIN_ME'">
AS_STATE IN ('8')
</when>
<when test="tabState == 'FIN_ADD'">
AS_STATE IN ('9')
</when>
<when test="tabState == 'FAIL'">
AS_STATE = '5'
</when>
<when test="tabState == 'INCOMP'">
AS_STATE IN ('A','B')
</when>
</choose>
</if>
<!-- 전화번호: 컬럼/입력 하이픈 제거 후 비교 -->
<when test="searchCondition == 'writer' and searchKeyword != null and searchKeyword != ''">
AND REPLACE(as_Tel, '-', '') LIKE CONCAT('%', REPLACE(#{searchKeyword}, '-', ''), '%')
</when>
<!-- 검색구분 비어 있고 키워드만 있는 경우 -->
<otherwise>
<if test="searchKeyword != null and searchKeyword != ''">
AND (
as_jsno LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_user LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_tel LIKE CONCAT('%', #{searchKeyword}, '%')
)
<if test="searchStartDt != null and searchStartDt != ''">
AND as_jsdate &gt;= REPLACE(#{searchStartDt}, '-', '')
</if>
<if test="searchEndDt != null and searchEndDt != ''">
AND as_jsdate &lt;= REPLACE(#{searchEndDt}, '-', '')
</if>
</otherwise>
</choose>
</where>
<choose>
<when test="searchCondition == 'title' and searchKeyword != null and searchKeyword != ''">
AND as_jsno LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<when test="searchCondition == 'contents' and searchKeyword != null and searchKeyword != ''">
AND as_user LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<when test="searchCondition == 'writer' and searchKeyword != null and searchKeyword != ''">
AND REPLACE(as_tel, '-', '') LIKE CONCAT('%', REPLACE(#{searchKeyword}, '-', ''), '%')
</when>
<otherwise>
<if test="searchKeyword != null and searchKeyword != ''">
AND (
as_jsno LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_user LIKE CONCAT('%', #{searchKeyword}, '%')
OR as_tel LIKE CONCAT('%', #{searchKeyword}, '%')
)
</if>
</otherwise>
</choose>
</where>
</select>
<select id="selectOneByasMmcode" parameterType="string" resultType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Response$cpMain">
select
AS_MMCODE asMmcode,
AS_SGGCODE asSggcode,
AS_INGB asIngb,
AS_JSDATE asJsdate,
AS_JSNO asJsno,
AS_BBS_NO asBbsNo,
AS_LIMIT_DT asLimitDt,
AS_USER asUser,
AS_TEL asTel,
AS_CELL asCell,
AS_EMAIL asEmail,
AS_TEXT asText
AS_MMCODE ,
AS_SGGCODE ,
AS_INGB ,
AS_JSDATE ,
AS_JSNO ,
AS_BBS_NO ,
AS_LIMIT_DT ,
AS_USER ,
AS_TEL ,
AS_CELL ,
AS_EMAIL ,
AS_TEXT
from cp_answer
<!-- left outer join cp_answer CA on(cm.mm_code = ca.as_mmcode) -->
WHERE AS_MMCODE = #{asMmcode}
@ -101,13 +338,41 @@
resultType="go.kr.project.biz.minwon.dayanswer.model.DayAnswerDto$Response$History">
SELECT
AS_MMCODE asMmcode,
AS_TEXT asText,
AS_USER asUser,
AS_STATE asState,
AS_MMCODE ,
AS_TEXT ,
AS_USER ,
AS_STATE ,
DATE_FORMAT(UPDATE_DT, '%Y-%m-%d %H:%i:%s') as updateDt
FROM cp_answer_history
WHERE AS_MMCODE = #{asMmcode}
ORDER BY UPDATE_DT DESC
</select>
<update id="updateAllState">
UPDATE cp_answer
SET AS_STATE = #{newState}
WHERE 1 = 1
<if test="vo.searchCondition != null and vo.searchKeyword != null and vo.searchKeyword != ''">
AND
<choose>
<when test="vo.searchCondition == 'title'">
AS_JSNO LIKE CONCAT('%', #{vo.searchKeyword}, '%')
</when>
<when test="vo.searchCondition == 'contents'">
AS_REUSER LIKE CONCAT('%', #{vo.searchKeyword}, '%')
</when>
</choose>
</if>
<if test="vo.searchStartDt != null and vo.searchStartDt != ''">
AND AS_JSDATE &gt;= #{vo.searchStartDt}
</if>
<if test="vo.searchEndDt != null and vo.searchEndDt != ''">
AND AS_JSDATE &lt;= #{vo.searchEndDt}
</if>
</update>
<update id="updateOneState">
UPDATE cp_answer
SET AS_STATE = #{newState}
WHERE AS_MMCODE = #{asMmcode}
</update>
</mapper>

@ -0,0 +1,69 @@
<?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="go.kr.project.biz.search.mapper.SearchMapper">
<select id="selectAllSearch"
parameterType="go.kr.project.biz.search.model.SearchDto$Request$Search"
resultType="go.kr.project.biz.search.model.SearchDto$Response$cpMain">
SELECT M.MM_INGB,
DATE_FORMAT(
STR_TO_DATE(CONCAT(M.MM_DATE, M.MM_TIME), '%Y%m%d%H%i%s'),
'%Y-%m-%d %H:%i:%s' ) AS mmDate,
M.MM_CARNO,
O.OM_JNO,
O.OM_NAME,
M.MM_KEUM2,
M.MM_IMAGEGB,
M.MM_IMAGECNT,
M.MM_CARCHECK,
M.MM_STATE,
M.MM_CODE
FROM CP_MAIN M
LEFT OUTER JOIN CP_ANSWER A ON M.MM_CODE = AS_MMCODE
LEFT OUTER JOIN CP_CANCEL C ON M.MM_CODE=CC_MMCODE
JOIN CP_OWNER O ON M.MM_OMCODE=O.OM_CODE
<where>
<!-- 날짜 -->
<if test="searchStartDt != null and searchStartDt != ''">
AND as_jsdate &gt;= REPLACE(#{searchStartDt}, '-', '')
</if>
<if test="searchEndDt != null and searchEndDt != ''">
AND as_jsdate &lt;= REPLACE(#{searchEndDt}, '-', '')
</if>
<!-- 검색구분 + 검색어 -->
<choose>
<!-- 차량번호 -->
<when test="searchCondition == 'title' and searchKeyword != null and searchKeyword != ''">
AND MM_CARNO LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<!-- 소유자 -->
<when test="searchCondition == 'contents' and searchKeyword != null and searchKeyword != ''">
AND OM_NAME LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<!-- 주민번호 -->
<when test="searchCondition == 'writer' and searchKeyword != null and searchKeyword != ''">
AND OM_JNO LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<!-- 코드 -->
<when test="searchCondition == 'code' and searchKeyword != null and searchKeyword != ''">
AND MM_CODE LIKE CONCAT('%', #{searchKeyword}, '%')
</when>
<!-- 검색구분 없이 검색어만 있는 경우 -->
<otherwise>
<if test="searchKeyword != null and searchKeyword != ''">
AND (
MM_CARNO LIKE CONCAT('%', #{searchKeyword}, '%')
OR OM_NAME LIKE CONCAT('%', #{searchKeyword}, '%')
OR OM_JNO LIKE CONCAT('%', #{searchKeyword}, '%')
OR MM_CODE LIKE CONCAT('%', #{searchKeyword}, '%')
)
</if>
</otherwise>
</choose>
</where>
ORDER BY AS_JSDATE DESC, AS_JSNO DESC
</select>
</mapper>

@ -11,7 +11,6 @@
</section>
</div>
</section>
<input type="text" id="bbs_no">
<div class="contants_body">
<div class="gs_b_top">
<ul class="lef">
@ -22,6 +21,7 @@
<option value="title" <c:if test="${paramVO.searchCondition eq 'title'}">selected</c:if>>접수번호</option>
<option value="contents" <c:if test="${paramVO.searchCondition eq 'contents'}">selected</c:if>>처리자명</option>
<option value="writer" <c:if test="${paramVO.searchCondition eq 'writer'}">selected</c:if>>전화번호</option>
<input type="hidden" id="tabState" name="tabState" value=""/>
</select>
</li>
<li class="th">검색어</li>
@ -33,7 +33,6 @@
</li>
<li>
<button type="button" id="search_btn" class="newbtnss bg1">검색</button>
<button type="button" id="btnAdd" class="newbtnss bg1">자료등록</button>
<button type="button" id="btnExcel" class="newbtnss bg1">엑셀다운로드</button>
</li>
@ -42,13 +41,78 @@
<li>
<select id="perPageSelect" class="input">
<option value="10" <c:if test="${param.perPage eq '10'}">selected</c:if>>페이지당 10</option>
<option value="20" <c:if test="${param.perPage eq '20'}">selected</c:if>>페이지당 20</option>
<option value="30" <c:if test="${param.perPage eq '30'}">selected</c:if>>페이지당 30</option>
<option value="30" <c:if test="${empty param.perPage or param.perPage eq '30'}">selected</c:if>>페이지당 30</option>
<option value="100" <c:if test="${param.perPage eq '100'}">selected</c:if>>페이지당 100</option>
</select>
<span class="page_number"><span id="currentPage"></span><span class="bar">/</span><sapn id="totalPages"></sapn> Pages</span>
</li>
</ul>
</div>
<div class="state-tab-wrap">
<ul class="state-tabs">
<li class="on" data-state="">전체</li>
<li data-state="FIN_BU" value="6">답변완료(부과)</li>
<li data-state="FIN_GE" value="7">답변완료(계도)</li>
<li data-state="FIN_ME" value="8">답변완료(미부과)</li>
<li data-state="FIN_ADD" value="9">답변완료(수기)</li>
<li data-state="FAIL" value="5">답변실패</li>
<li data-state="INCOMP" value="fail">답변미대상</li>
</ul>
</div>
<style>
.state-tab-wrap {
margin: 10px 0;
}
.state-tabs {
list-style: none;
padding: 0;
margin: 0;
display: flex;
}
.state-tabs li {
padding: 6px 14px;
border: 1px solid #ccc;
border-bottom: none;
background: #f5f5f5;
font-size: 12px;
cursor: pointer;
margin-right: 4px;
}
.state-tabs li.on {
background: #ffffff;
font-weight: bold;
border-bottom: 1px solid #ffffff;
}
.state-area {
margin-top: 10px;
padding: 10px 0;
display: flex;
align-items: center;
gap: 10px;
}
.state-label {
font-weight: bold;
margin-right: 5px;
white-space: nowrap;
}
.state-input {
width: 120px;
height: 28px;
padding: 4px;
border: 1px solid #ccc;
}
.state-area button {
height: 32px;
padding: 0 12px;
}
</style>
<div class="gs_booking">
<div class="row">
<div class="col-sm-12">
@ -60,429 +124,324 @@
</div>
</div>
</div>
<div class="gs_booking">
</div>
<div class="state-area">
<label class="state-label">새처리상태값</label>
<input type="text" id="newStateValue" class="state-input">
<button type="button" id="btnAllChange" class="newbtnss bg1">전체 답변상태변경</button>
<button type="button" id="btnOneChange" class="newbtnss bg1">1개 답변상태변경</button>
<button type="button" id="btncdmake" class="newbtnss bg1">표지정보조회 CD 만들기</button>
</div>
<div class="status-right">
<span id="statusSummary">
초기 0, 대기 1, 대상(부과) 2, 대상(계도) 3, 대상(서손) 4, 실패 5,
완료(부과) 6, 완료(계도) 7, 완료(서손) 8, 완료(수기) 9, 미대상 A, 미대상(서손) B
</span>
</div>
</div>
</div>
</div>
<!-- /Main body -->
<script type="text/javascript">
/**
* 게시판 목록 관리 모듈
* 게시판 목록을 조회하고 관리하는 기능을 제공합니다.
*/
(function(window, $) {
'use strict';
var SEARCH_COND = {};
// 검색정보 셋팅
var setSearchCond = function() {
var searchCondition = $.trim(nvl($("#searchCondition").val(), ""));
var searchKeyword = $.trim(nvl($("#searchKeyword").val(), ""));
var searchUseYn = $.trim(nvl($("#searchUseYn").val(), ""));
var searchStartDt = $.trim(nvl($("#searchStartDt").val(), ""));
var searchEndDt = $.trim(nvl($("#searchEndDt").val(), ""));
SEARCH_COND.searchCondition = searchCondition;
SEARCH_COND.searchKeyword = searchKeyword;
SEARCH_COND.searchUseYn = searchUseYn;
SEARCH_COND.searchStartDt = searchStartDt;
SEARCH_COND.searchEndDt = searchEndDt;
};
/**
* 게시판 목록 관리 네임스페이스
*/
var NoticeList = {
/**
* 그리드 관련 객체
*/
grid: {
/**
* 그리드 인스턴스
*/
instance: null,
/**
* 그리드 설정 초기화
* @returns {Object} 그리드 설정 객체
*/
initConfig: function() {
// 데이터 소스 설정
var dataSource = this.createDataSource();
// 현재 선택된 perPage 값 가져오기
var perPage = parseInt($('#perPageSelect').val() || 10, 10);
// 그리드 설정 객체 생성
var gridConfig = new XitTuiGridConfig();
// 기본 설정
gridConfig.setOptDataSource(dataSource); // 데이터소스 연결
gridConfig.setOptGridId('grid'); // 그리드를 출력할 Element ID
gridConfig.setOptGridHeight(390); // 그리드 높이(단위: px)
gridConfig.setOptRowHeight(30); // 그리드 행 높이(단위: px)
gridConfig.setOptRowHeaderType('checkbox'); // 행 첫번째 셀 타입(rowNum: 순번, checkbox: 체크박스, '': 출력 안함)
// 페이징 옵션 설정
gridConfig.setOptPageOptions({
useClient: false, // 클라이언트 페이징 여부(false: 서버 페이징)
perPage: perPage // 페이지당 표시 건수
});
gridConfig.setOptUseClientSort(false); // 서버사이드 정렬 false
// 컬럼 정보 설정
gridConfig.setOptColumns([
{
header: 'asMmcode',
name: 'asMmcode',
width: 50,
align: 'center'
},
{
header: 'asSggcode',
name: 'asSggcode',
width: 70,
align: 'center'
},
{
header: 'asIngb',
name: 'asIngb',
width: 100,
align: 'center'
},
{
header: 'asJsdate',
name: 'asJsdate',
width: 100,
align: 'center'
},
{
header: 'asJsno',
name: 'asJsno',
width: 150,
align: 'center'
},
{
header: 'asJsnoM',
name: 'asJsnoM',
width: 70,
align: 'center'
},
{
header: 'asBbsNo',
name: 'asBbsNo',
width: 70,
align: 'center'
},
{
header: 'asLimitDt',
name: 'asLimitDt',
width: 150,
align: 'center'
},
{
header: 'asUser',
name: 'asUser',
width: 150,
align: 'center'
},
{
header: 'asTel',
name: 'asTel',
width: 50,
align: 'center'
},
{
header: 'asCell',
name: 'asCell',
width: 250,
align: 'center'
},
{
header: 'asEmail',
name: 'asEmail',
width: 150,
align: 'center'
},
{
header: 'asState',
name: 'asState',
width: 150,
align: 'center'
},
{
header: 'asPostCd',
name: 'asPostCd',
width: 150,
align: 'center'
},
{
header: 'asPostDt',
name: 'asPostDt',
width: 150,
align: 'center'
},
{
header: 'asStateDt',
name: 'asStateDt',
width: 150,
align: 'center'
},
{
header: 'asText',
name: 'asText',
width: 150,
align: 'center'
},
{
header: 'asReuser',
name: 'asReuser',
width: 150,
align: 'center'
},
{
header: 'asInline',
name: 'asInline',
width: 150,
align: 'center'
},
{
header: 'asSysGubunC',
name: 'asSysGubunC',
width: 150,
align: 'center'
},
{
header: 'asPetiAncCodeV',
name: 'asPetiAncCodeV',
width: 150,
align: 'center'
},
{
header: 'asPetiNoC',
name: 'asPetiNoC',
width: 150,
align: 'center'
}
]);
return gridConfig;
},
/**
* 데이터 소스 생성
* @returns {Object} 데이터 소스 객체
*/
createDataSource: function() {
return {
api: {
readData: {
url: '<c:url value="/minwon/dayanswer/dayanswer-select.ajax"/>',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
processData: true
}
},
initialRequest: false,
serializer: function (params) {
setSearchCond();
SEARCH_COND.perPage = params.perPage;
SEARCH_COND.page = params.page;
return $.param(SEARCH_COND);
}
};
},
/**
* 그리드 인스턴스 생성
*/
create: function() {
var gridConfig = this.initConfig();
var Grid = tui.Grid;
this.instance = gridConfig.instance(Grid);
// 그리드 테마 설정
Grid.applyTheme('striped');
// 그리드 이벤트 설정
this.gridBindEvents();
},
/**
* 그리드 이벤트 바인딩
*/
gridBindEvents: function() {
var self = this;
// 요청 성공 시 총 건수 표시
this.instance.on('successResponse', function(ev) {
var responseObj = JSON.parse(ev.xhr.response);
//$('.totCnt').text(responseObj.data.pagination.totalCount);
$("#currentPage").text(responseObj.data.pagination.page);
$("#totalPages").text(responseObj.data.pagination.totalPages);
});
this.instance.on('dblclick', function (ev) {
if (ev.rowKey === undefined) {
return;
}
var rowData = self.instance.getRow(ev.rowKey);
if (!rowData) {
return;
}
var asMmcode = rowData.asMmcode;
console.log("더블클릭 asMmcode =", asMmcode);
if (!asMmcode) {
alert("선택한 행에 asMmcode 값이 없습니다.");
return;
}
var popUrl = '<c:url value="/minwon/dayanswer/dayanswertotalPop.do"/>'
+ '?asMmcode=' + encodeURIComponent(asMmcode);
console.log("팝업 URL =", popUrl);
var popTitle = "상세정보";
var popOption = "width=1100,height=700,resizable=yes,scrollbars=yes,location=no,top=100,left=100";
window.open(popUrl, popTitle, popOption);
});
$('#btnAdd').on('click', function() {
const popUrl = '${pageContext.request.contextPath}/minwon/dayanswer/dayanswerregister.do';
const popTitle = '자료 등록';
const popOption = 'width=700,height=600,resizable=yes,scrollbars=yes';
window.open(popUrl, popTitle, popOption);
});
let INIT_POPUP = null;
let SEARCH_COND = {};
let GRID = null;
// 검색조건 세팅
let setSearchCond = function () {
let searchCondition = $.trim(nvl($("#searchCondition").val(), ""));
let searchKeyword = $.trim(nvl($("#searchKeyword").val(), ""));
let searchUseYn = $.trim(nvl($("#searchUseYn").val(), ""));
let searchStartDt = $.trim(nvl($("#searchStartDt").val(), ""));
let searchEndDt = $.trim(nvl($("#searchEndDt").val(), ""));
let tabState = $.trim(nvl($("#tabState").val(), ""));
SEARCH_COND.searchCondition = searchCondition;
SEARCH_COND.searchKeyword = searchKeyword;
SEARCH_COND.searchUseYn = searchUseYn;
SEARCH_COND.searchStartDt = searchStartDt;
SEARCH_COND.searchEndDt = searchEndDt;
SEARCH_COND.tabState = tabState;
};
const fnBiz = {
init: () => {
initGrid();
},
eventListener: () => {
// 엑셀 다운로드
$('#btnExcel').on('click', function () {
const params = $.param({
searchCondition: $('#searchCondition').val(),
searchKeyword: $('#searchKeyword').val(),
searchStartDt: $('#searchStartDt').val(),
searchEndDt: $('#searchEndDt').val()
});
const url = '${pageContext.request.contextPath}/minwon/dayanswer/dayanswer-excel.do?' + params;
window.location.href = url;
});
// 전체 상태 변경
$('#btnAllChange').on('click', function () {
const newState = $.trim($('#newStateValue').val());
if (!GRID) {
alert('그리드가 아직 초기화되지 않았습니다.');
return;
}
},
/**
* 이벤트 핸들러 설정
*/
eventBindEvents: function(e) {
const checkedRowKeys = GRID.getCheckedRowKeys();
if (checkedRowKeys.length > 0) {
alert('체크된 행이 있습니다.\n전체 답변상태변경은 체크를 모두 해제한 상태에서만 가능합니다.\n' +
'한 개만 수정하려면 [1개 답변상태변경] 버튼을 사용해주세요.');
return;
}
if (!newState) {
alert('새 처리상태값을 입력하세요.');
return;
}
var self = this;
if (!confirm('현재 조회된 모든 건의 처리상태를 [' + newState + '] 로 변경하시겠습니까?')) {
return;
}
$('#btnExcel').on('click', function() {
// 지금 화면의 검색조건 그대로 가져오기
const params = $.param({
$.ajax({
url: '<c:url value="/minwon/dayanswer/update-all-state.ajax"/>',
type: 'POST',
data: {
newState: newState,
searchCondition: $('#searchCondition').val(),
searchKeyword: $('#searchKeyword').val(),
searchStartDt: $('#searchStartDt').val(),
searchEndDt: $('#searchEndDt').val()
});
searchKeyword: $('#searchKeyword').val(),
searchStartDt: $('#searchStartDt').val(),
searchEndDt: $('#searchEndDt').val()
},
success: function () {
alert('전체 답변상태가 변경되었습니다.');
const currentPage = GRID.getPagination()._currentPage || 1;
GRID.readData(currentPage);
},
error: function () {
alert('상태 변경 중 오류가 발생했습니다.');
}
});
});
const url = '${pageContext.request.contextPath}/minwon/dayanswer/dayanswer-excel.do?' + params;
console.log("엑셀 다운로드 URL:", url);
// 한 건 상태 변경
$('#btnOneChange').on('click', function () {
const newState = $.trim($('#newStateValue').val());
window.location.href = url;
});
// 검색 버튼 클릭 이벤트
$('#search_btn').on('click', function() {
if (!newState) {
alert('새 처리상태값을 입력하세요.');
return;
}
if (!GRID) {
alert('그리드가 아직 초기화되지 않았습니다.');
return;
}
SEARCH_COND = {};
// 등록일 from~to 유효성 검사
var startDate = $("#searchStartDt").val().trim();
var endDate = $("#searchEndDt").val().trim();
const checkedRowKeys = GRID.getCheckedRowKeys();
// 시작일과 종료일 중 하나만 입력된 경우 체크
if ((startDate && !endDate) || (!startDate && endDate)) {
alert("등록일 검색 시 시작일과 종료일을 모두 입력해주세요.");
return;
}
if (checkedRowKeys.length === 0) {
alert('상태를 변경할 행을 1개 선택하세요.');
return;
}
if (checkedRowKeys.length > 1) {
alert('1개 답변상태변경은 한 행만 선택할 수 있습니다.');
return;
}
const rowData = GRID.getRow(checkedRowKeys[0]);
if (!rowData) {
alert('선택된 행 정보를 가져올 수 없습니다.');
return;
}
const asMmcode = rowData.asMmcode;
// 시작일과 종료일이 모두 입력된 경우 유효성 검사
if (startDate && endDate) {
if (!isDate(startDate) || !isDate(endDate)) {
alert("유효한 날짜 형식이 아닙니다. (YYYY-MM-DD)");
return;
}
// 시작일이 종료일보다 늦은 경우 체크
var startDateObj = new Date(startDate);
var endDateObj = new Date(endDate);
if (startDateObj > endDateObj) {
alert("시작일은 종료일보다 이후일 수 없습니다.");
return;
}
if (!confirm('선택한 건의 상태를 [' + newState + '] 로 변경하시겠습니까?')) {
return;
}
$.ajax({
url: '<c:url value="/minwon/dayanswer/update-one-state.ajax"/>',
type: 'POST',
data: {
asMmcode: asMmcode,
newState: newState
},
success: function () {
alert('선택한 답변 상태가 변경되었습니다.');
const currentPage = GRID.getPagination()._currentPage || 1;
GRID.readData(currentPage);
},
error: function () {
alert('상태 변경 중 오류가 발생했습니다.');
}
// 페이지를 1로 리셋
$("#page").val(1);
// 그리드 데이터 리로드
self.grid.instance.readData(1);
});
});
// 검색어 입력 필드에서 엔터키 이벤트 처리
$('#searchKeyword').on('keypress', function(e) {
if (e.which === 13) { // 엔터키 코드는 13
e.preventDefault(); // 기본 이벤트 방지
$('#search_btn').trigger('click'); // 검색 버튼 클릭 이벤트 트리거
// 검색 버튼
$('#search_btn').on('click', function () {
let startDate = $("#searchStartDt").val();
let endDate = $("#searchEndDt").val();
if ((startDate && !endDate) || (!startDate && endDate)) {
alert("등록일 검색 시 시작일과 종료일을 모두 입력해주세요.");
return;
}
if (startDate && endDate) {
if (!isDate(startDate) || !isDate(endDate)) {
alert("유효한 날짜 형식이 아닙니다. (YYYY-MM-DD)");
return;
}
});
let startDateObj = new Date(startDate);
let endDateObj = new Date(endDate);
if (startDateObj > endDateObj) {
alert("시작일은 종료일보다 이후일 수 없습니다.");
return;
}
}
// perPage 변경 이벤트 추가
$('#perPageSelect').on('change', function() {
var perPage = parseInt($(this).val(), 10);
// Grid의 perPage 설정 변경 및 데이터 리로드
self.grid.instance.setPerPage(perPage);
});
$("#page").val(1);
GRID.readData(1);
});
// 검색어 엔터
$('#searchKeyword').on('keypress', function (e) {
if (e.which === 13) {
e.preventDefault();
$('#search_btn').trigger('click');
}
});
// perPage 변경 이벤트 추가
$('#perPageSelect').on('change', () => {
const pagination = TuiGrid.instance.getPagination();
if (!pagination) return;
pagination.setItemsPerPage($('#perPageSelect').val());
pagination.reset(TuiGrid.instance.getRowCount());
pagination.movePageTo(1);
});
// 상태 탭 클릭
$('.state-tabs li').on('click', function (e) {
$('.state-tabs li').removeClass('on');
$(this).addClass('on');
const state = $(this).data('state') || '';
$('#tabState').val(state);
SEARCH_COND = {};
GRID.readData(1);
});
}
};
/** tui-grid Set */
const initGrid = () => {
const gridColumns = [
{ header: '답변일시', name: 'asPostDt', width: 150, align: 'center' },
{ header: '번호', name: 'asBbsNo', width: 150, align: 'center' },
{ header: '신청자', name: 'asUser', width: 150, align: 'center' },
{ header: '접수일자', name: 'asJsdate', width: 150, align: 'center' },
{ header: '처리기한', name: 'asLimitDt',width: 150, align: 'center' },
{ header: '위반일자', name: 'mmDate', width: 150, align: 'center' },
{ header: '접수번호', name: 'asJsno', width: 150, align: 'center' },
{ header: '차량번호', name: 'mmCarno', width: 150, align: 'center' },
{ header: '미부과사유',name: 'ccCause', width: 150, align: 'center' },
{ header: '처리상태', name: 'asState', width: 150, align: 'center' },
{ header: 'mmCode', name: 'mmCode', sortable: true, width: 150, align: 'center', hidden: true}
];
const gridDatasource = {
api: {
readData: {
url: '<c:url value="/minwon/dayanswer/dayanswer-select.ajax"/>',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
processData: true
}
},
/**
* 모듈 초기화
*/
init: function() {
initialRequest: false, // 직접 readData(1) 호출할 거라 false
serializer: function (params) {
setSearchCond();
SEARCH_COND.perPage = params.perPage;
SEARCH_COND.page = params.page;
// 그리드 생성
this.grid.create();
return $.param(SEARCH_COND);
}
};
// 이벤트 핸들러 설정
this.eventBindEvents();
const perPage = parseInt($('#perPageSelect').val() || 10, 10);
this.grid.instance.readData(${param.page eq null or param.page eq 0 ? 1 : param.page});
const gridOptions = {
el: 'grid',
rowHeaders: ['checkbox'],
columns: gridColumns,
noData: "처리 할 초기자료가 없습니다.",
pageOptions: {
useClient: true,
perPage: perPage
}
};
// 페이지 로드 시 초기화
$(function() {
NoticeList.init();
// $.ajax({
// url: "http://localhost:8080/postman/test",
// type: 'POST',
// contentType: 'application/json',
// data: JSON.stringify({
// aaa: "111",
// bbb: "222",
// ccc: "333"
// }), // 비어 있어도 {}
// success: function(response) {
// console.log(response);
// $("#bbs_no").val(response.asBbsNo);
// console.log($("#bbs_no").val());
//
//
// },
// error: function(xhr, status, error) {
// // 에러 처리는 xit-common.js의 ajaxError에서 처리됨
// }
// });
// 실제 GRID 생성
GRID = TuiGrid.of(gridOptions, gridDatasource, (res) => {
// 서버 응답 후 페이지 정보 세팅
const data = res.data || {};
if (data.pagination) {
$("#currentPage").text(data.pagination.page || '');
$("#totalPages").text(data.pagination.totalPages || '');
}
// 더블 클릭 이벤트
GRID.on("dblclick", (e) => {
var popUrl = '/minwon/init/init_popup.do';
var popTitle = "initPopup";
var popOption = "width=1400px, height=900px, resizable=yes, scrollbars=yes, location=no, top=100px, left=100px";
// 1) localStorage에 저장
console.log(e)
let cursor = e.instance.getValue(e.rowKey, 'mmCode');
let mmCodes = e.instance.getData().map(row => row.mmCode);
console.log(cursor);
const state = { cursor, mmCodes, savedAt: Date.now() };
localStorage.setItem('TOTAL_INFO_STATE', JSON.stringify(state));
// 2) 팝업이 없거나 닫혀 있으면 새로 열기
if (!INIT_POPUP || INIT_POPUP.closed) {
INIT_POPUP = window.open(popUrl, popTitle, popOption);
} else {
// 이미 떠 있으면 새로 안 만들고, 그 창에 포커스만 줌
INIT_POPUP.focus();
INIT_POPUP.INIT_POP_API.search();
}
});
});
// 전역 네임스페이스에 모듈 노출
window.NoticeList = NoticeList;
// 최초 1페이지 조회
GRID.readData(1);
};
// 레디펑션
$(function () {
fnBiz.init();
fnBiz.eventListener();
});
})(window, jQuery);
</script>

@ -37,7 +37,7 @@
</li>
<li class="th">접수일자 지정</li>
<li>
<input type="text" id="searchStartDt" name="searchStartDt" class="input calender datepicker" value="${param.searchStartDt}" /> ~
<input type="text" id="searchStartDt" name="searchStartDt" class="input calender datepicker bottom" value="${param.searchStartDt}" /> ~
<input type="text" id="searchEndDt" name="searchEndDt" class="input calender datepicker" value="${param.searchEndDt}" />
</li>
<li>
@ -71,366 +71,178 @@
<!-- /Main body -->
<script type="text/javascript">
/**
* 게시판 목록 관리 모듈
* 게시판 목록을 조회하고 관리하는 기능을 제공합니다.
*/
(function(window, $) {
'use strict';
let TOTAL_INFO_POPUP = null;
var SEARCH_COND = {};
// 검색정보 셋팅
var setSearchCond = function() {
var searchCondition = $.trim(nvl($("#searchCondition").val(), ""));
var searchKeyword = $.trim(nvl($("#searchKeyword").val(), ""));
var searchUseYn = $.trim(nvl($("#searchUseYn").val(), ""));
var searchStartDt = $.trim(nvl($("#searchStartDt").val(), ""));
var searchEndDt = $.trim(nvl($("#searchEndDt").val(), ""));
SEARCH_COND.searchCondition = searchCondition;
SEARCH_COND.searchKeyword = searchKeyword;
SEARCH_COND.searchUseYn = searchUseYn;
SEARCH_COND.searchStartDt = searchStartDt;
SEARCH_COND.searchEndDt = searchEndDt;
};
/**
* 게시판 목록 관리 네임스페이스
*/
var NoticeList = {
/**
* 그리드 관련 객체
*/
grid: {
/**
* 그리드 인스턴스
*/
instance: null,
/**
* 그리드 설정 초기화
* @returns {Object} 그리드 설정 객체
*/
initConfig: function() {
// 데이터 소스 설정
var dataSource = this.createDataSource();
// 현재 선택된 perPage 값 가져오기
var perPage = parseInt($('#perPageSelect').val() || 10, 10);
// 그리드 설정 객체 생성
var gridConfig = new XitTuiGridConfig();
// 기본 설정w
gridConfig.setOptDataSource(dataSource); // 데이터소스 연결
gridConfig.setOptGridId('grid'); // 그리드를 출력할 Element ID
gridConfig.setOptGridHeight(390); // 그리드 높이(단위: px)
gridConfig.setOptRowHeight(30); // 그리드 행 높이(단위: px)
gridConfig.setOptRowHeaderType('checkbox'); // 행 첫번째 셀 타입(rowNum: 순번, checkbox: 체크박스, '': 출력 안함)
// 페이징 옵션 설정
gridConfig.setOptPageOptions({
useClient: true, // 클라이언트 페이징 여부(false: 서버 페이징)
perPage: perPage // 페이지당 표시 건수
});
gridConfig.setOptUseClientSort(true); // 서버사이드 정렬 false
// 컬럼 정보 설정
gridConfig.setOptColumns([
{
header: '등록구분',
name: 'mmDlgb',
sortable: true,
width: 50,
align: 'center'
},
{
header: '목록번호',
name: 'asBbsNo',
sortable: true,
width: 70,
align: 'center'
},
{
header: '신고자',
name: 'mmSgnm',
sortable: true,
width: 100,
align: 'center'
},
{
header: '담당자',
name: 'mmSgtel',
sortable: true,
width: 100,
align: 'center'
},
{
header: '전화번호',
name: 'asTel',
width: 150,
align: 'center'
},
{
header: '접수일자',
name: 'asJsdate',
sortable: true,
width: 70,
align: 'center'
},
{
header: '처리기한',
name: 'asLimitDt',
sortable: true,
width: 70,
align: 'center'
},
{
header: '위반일자',
name: 'mmDate',
sortable: true,
width: 150,
align: 'center'
},
{
header: '첨부',
name: 'mmImagegb',
width: 150,
align: 'center'
},
{
header: '사진갯수',
name: 'mmImagecnt',
width: 50,
align: 'center'
},
{
header: '위반내용',
name: 'mmSgcont',
width: 250,
align: 'center'
},
{
header: '접수번호',
name: 'asJsno',
sortable: true,
width: 150,
align: 'center'
},
{
header: '차량번호',
name: 'mmCarno',
sortable: true,
width: 150,
align: 'center'
},
{
header: 'mmCode',
name: 'mmCode',
sortable: true,
width: 150,
align: 'center',
hidden: false
}
]);
return gridConfig;
},
/**
* 데이터 소스 생성
* @returns {Object} 데이터 소스 객체
*/
createDataSource: function() {
return {
api: {
readData: {
url: '<c:url value="/minwon/init/list.ajax"/>',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
processData: true
}
},
initialRequest: false,
serializer: function (params) {
setSearchCond();
SEARCH_COND.perPage = params.perPage;
SEARCH_COND.page = params.page;
return $.param(SEARCH_COND);
}
};
},
/**
* 그리드 인스턴스 생성
*/
create: function() {
var gridConfig = this.initConfig();
var Grid = tui.Grid;
this.instance = gridConfig.instance(Grid);
// 그리드 테마 설정
Grid.applyTheme('striped');
// 그리드 이벤트 설정
this.gridBindEvents();
},
/**
* 그리드 이벤트 바인딩
*/
gridBindEvents: function() {
var self = this;
// 요청 성공 시 총 건수 표시
this.instance.on('successResponse', function(ev) {
var responseObj = JSON.parse(ev.xhr.response);
//$('.totCnt').text(responseObj.data.pagination.totalCount);
// $("#currentPage").text(responseObj.data.pagination.page);
// $("#totalPages").text(responseObj.data.pagination.totalPages);
});
// 행 더블클릭 이벤트 - 게시물 상세 페이지로 이동
this.instance.on('dblclick', (ev) => {
var popUrl = '${pageContext.request.contextPath}/total/info.do';
var popTitle = "totalInfoPopup";
var popOption = "width=1400px, height=900px, resizable=yes, scrollbars=yes, location=no, top=100px, left=100px";
// 1) localStorage에 저장
let cursor = this.instance.getValue(ev.rowKey, 'mmCode');
let mmCodes = this.instance.getData().map(row => row.mmCode);
const state = { cursor, mmCodes, savedAt: Date.now() };
localStorage.setItem('TOTAL_INFO_STATE', JSON.stringify(state));
// 2) 팝업이 없거나 닫혀 있으면 새로 열기
if (!TOTAL_INFO_POPUP || TOTAL_INFO_POPUP.closed) {
TOTAL_INFO_POPUP = window.open(popUrl, popTitle, popOption);
} else {
// 이미 떠 있으면 새로 안 만들고, 그 창에 포커스만 줌
TOTAL_INFO_POPUP.focus();
TOTAL_INFO_POPUP.TOTAL_INFO_POPUP_API.search();
// 필요하면 URL도 다시 세팅해서 강제 새로고침
// totalInfoPopup.location.href = popUrl;
}
// 팝업 띄우기
// const $openPop = window.open(popUrl, popTitle, popOption);
// $($openPop).prop('cursor', cursor);
// $($openPop).prop('mmCodes', mmCodes);
});
let GRID = null;
let INIT_POPUP = null;
let SEARCH_COND = {};
let perPage = parseInt($('#perPageSelect').val() || 10, 10);
// 검색정보 셋팅
let setSearchCond = function() {
let searchCondition = $.trim(nvl($("#searchCondition").val(), ""));
let searchKeyword = $.trim(nvl($("#searchKeyword").val(), ""));
let searchUseYn = $.trim(nvl($("#searchUseYn").val(), ""));
let searchStartDt = $.trim(nvl($("#searchStartDt").val(), ""));
let searchEndDt = $.trim(nvl($("#searchEndDt").val(), ""));
SEARCH_COND.searchCondition = searchCondition;
SEARCH_COND.searchKeyword = searchKeyword;
SEARCH_COND.searchUseYn = searchUseYn;
SEARCH_COND.searchStartDt = searchStartDt;
SEARCH_COND.searchEndDt = searchEndDt;
};
const fnBiz = {
init: () => {
initGrid();
},
eventListener: () => {
// 검색 버튼 클릭 이벤트
$('#search_btn').on('click', function() {
// 등록일 from~to 유효성 검사
let startDate = $("#searchStartDt").val();
let endDate = $("#searchEndDt").val();
// 시작일과 종료일 중 하나만 입력된 경우 체크
if ((startDate && !endDate) || (!startDate && endDate)) {
alert("등록일 검색 시 시작일과 종료일을 모두 입력해주세요.");
return;
}
},
/**
* 이벤트 핸들러 설정
*/
eventBindEvents: function() {
var self = this;
// 검색 버튼 클릭 이벤트
$('#search_btn').on('click', function() {
// 등록일 from~to 유효성 검사
var startDate = $("#searchStartDt").val();
var endDate = $("#searchEndDt").val();
// 시작일과 종료일 중 하나만 입력된 경우 체크
if ((startDate && !endDate) || (!startDate && endDate)) {
alert("등록일 검색 시 시작일과 종료일을 모두 입력해주세요.");
// 시작일과 종료일이 모두 입력된 경우 유효성 검사
if (startDate && endDate) {
if (!isDate(startDate) || !isDate(endDate)) {
alert("유효한 날짜 형식이 아닙니다. (YYYY-MM-DD)");
return;
}
// 시작일과 종료일이 모두 입력된 경우 유효성 검사
if (startDate && endDate) {
if (!isDate(startDate) || !isDate(endDate)) {
alert("유효한 날짜 형식이 아닙니다. (YYYY-MM-DD)");
return;
}
// 시작일이 종료일보다 늦은 경우 체크
var startDateObj = new Date(startDate);
var endDateObj = new Date(endDate);
if (startDateObj > endDateObj) {
alert("시작일은 종료일보다 이후일 수 없습니다.");
return;
}
}
// 시작일이 종료일보다 늦은 경우 체크
let startDateObj = new Date(startDate);
let endDateObj = new Date(endDate);
// 페이지를 1로 리셋
$("#page").val(1);
// 그리드 데이터 리로드
self.grid.instance.readData(1);
});
// 검색어 입력 필드에서 엔터키 이벤트 처리
$('#searchKeyword').on('keypress', function(e) {
if (e.which === 13) { // 엔터키 코드는 13
e.preventDefault(); // 기본 이벤트 방지
$('#search_btn').trigger('click'); // 검색 버튼 클릭 이벤트 트리거
if (startDateObj > endDateObj) {
alert("시작일은 종료일보다 이후일 수 없습니다.");
return;
}
});
// perPage 변경 이벤트 추가
$('#perPageSelect').on('change', function() {
var perPage = parseInt($(this).val(), 10);
// Grid의 perPage 설정 변경 및 데이터 리로드
self.grid.instance.setPerPage(perPage);
});
},
}
/**
* 모듈 초기화
*/
init: function() {
// 페이지를 1로 리셋
$("#page").val(1);
// 그리드 데이터 리로드
TuiGrid.instance.readData(1);
});
// 검색어 입력 필드에서 엔터키 이벤트 처리
$('#searchKeyword').on('keypress', function(e) {
if (e.which === 13) { // 엔터키 코드는 13
e.preventDefault(); // 기본 이벤트 방지
$('#search_btn').trigger('click'); // 검색 버튼 클릭 이벤트 트리거
}
});
// perPage 변경 이벤트 추가
$('#perPageSelect').on('change', () => {
const pagination = TuiGrid.instance.getPagination();
if (!pagination) return;
pagination.setItemsPerPage($('#perPageSelect').val());
pagination.reset(TuiGrid.instance.getRowCount());
pagination.movePageTo(1);
});
}
}
/** tui-grid Set */
const initGrid = () => {
const gridColumns = [
{header: '등록구분', name: 'mmDlgb', sortable: true, width: 50,},
{header: '목록번호', name: 'asBbsNo', sortable: true, width: 70,},
{header: '신고자', name: 'mmSgnm', sortable: true, width: 100,},
{header: '담당자', name: 'mmSgtel', sortable: true, width: 100,},
{header: '전화번호', name: 'asTel', width: 150,},
{header: '접수일자', name: 'asJsdate', sortable: true, width: 70,},
{header: '처리기한', name: 'asLimitDt', sortable: true, width: 70,},
{header: '위반일자', name: 'mmDate', sortable: true, width: 150,},
{header: '첨부', name: 'mmImagegb', width: 150,},
{header: '사진갯수', name: 'mmImagecnt', width: 50,},
{header: '위반내용', name: 'mmSgcont', width: 250,},
{header: '접수번호', name: 'asJsno', sortable: true, width: 150,},
{header: '차량번호', name: 'mmCarno', sortable: true, width: 150,},
{header: 'mmCode', name: 'mmCode', sortable: true, width: 150, align: 'center', hidden: true}
];
const gridDatasource = {
api: {
readData: {
url: '<c:url value="/minwon/init/list.ajax"/>',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
processData: true
}
},
initialRequest: true, //초기화시 조회
serializer: function (params) {
setSearchCond();
SEARCH_COND.perPage = params.perPage;
SEARCH_COND.page = params.page;
return $.param(SEARCH_COND);
}
}
const gridOptions = {
el: 'grid',
rowHeaders: ['checkbox'],
columns: gridColumns,
noData: "처리 할 초기자료가 없습니다.",
pageOptions: {
useClient: true, // 클라이언트 페이징 여부(false: 서버 페이징)
perPage: perPage,
},
};
// 그리드 생성
this.grid.create();
GRID = TuiGrid.of(gridOptions, gridDatasource, (res) => {
// 이벤트 핸들러 설정
this.eventBindEvents();
GRID.on("dblclick", (e) => {
var popUrl = '/minwon/init/init_popup.do';
var popTitle = "initPopup";
var popOption = "width=1400px, height=900px, resizable=yes, scrollbars=yes, location=no, top=100px, left=100px";
this.grid.instance.readData(${param.page eq null or param.page eq 0 ? 1 : param.page});
}
};
// 1) localStorage에 저장
console.log(e)
let cursor = e.instance.getValue(e.rowKey, 'mmCode');
let mmCodes = e.instance.getData().map(row => row.mmCode);
// 페이지 로드 시 초기화
$(function() {
NoticeList.init();
// $.ajax({
// url: "http://localhost:8080/postman/test",
// type: 'POST',
// contentType: 'application/json',
// data: JSON.stringify({
// aaa: "111",
// bbb: "222",
// ccc: "333"
// }), // 비어 있어도 {}
// success: function(response) {
// console.log(response);
// $("#bbs_no").val(response.asBbsNo);
// console.log($("#bbs_no").val());
//
//
// },
// error: function(xhr, status, error) {
// // 에러 처리는 xit-common.js의 ajaxError에서 처리됨
// }
// });
const state = { cursor, mmCodes, savedAt: Date.now() };
localStorage.setItem('TOTAL_INFO_STATE', JSON.stringify(state));
// 2) 팝업이 없거나 닫혀 있으면 새로 열기
if (!INIT_POPUP || INIT_POPUP.closed) {
INIT_POPUP = window.open(popUrl, popTitle, popOption);
} else {
// 이미 떠 있으면 새로 안 만들고, 그 창에 포커스만 줌
INIT_POPUP.focus();
INIT_POPUP.INIT_POP_API.search();
}
});
});
};
// 레디펑션
$(function () {
fnBiz.init();
fnBiz.eventListener();
// 전역 네임스페이스에 모듈 노출
window.NoticeList = NoticeList;
});
})(window, jQuery);
</script>

@ -0,0 +1,597 @@
<%--
Created by IntelliJ IDEA.
User: kurt
Date: 2025. 7. 31.
Time: 오후 5:34
To change this template use File | Settings | File Templates.
--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="main_body">
<div class="detail-card">
<div class="card-header">
<div class="title">민원 접수 초기자료 편집</div>
<div class="actions">
<span class="pill">Double Click 민원원본보기</span>
<button type="button" class="close-btn">닫기 Esc</button>
</div>
</div>
<!-- 상단 네비/페이지 인디케이터 -->
<div class="card-toolbar">
<%-- 부모창에서 받아오는 리스트 배열 --%>
<input type="hidden" id="mmCodes" />
<%-- 부모창에서 받아오는 리스크 커서 --%>
<input type="hidden" id="cursor" />
<%-- 개별총정보 상태값 --%>
<input type="hidden" id="infoState" value="init" />
<div class="page-indicator">
<span id="cursorCnt">0</span>of <span id="total">0</span>
</div>
<div class="nav-group">
<button type="button" class="nav-btn" name="navigate" data-act="first" id="first">◀◀</button>
<button type="button" class="nav-btn" name="navigate" data-act="prev">◀</button>
<button type="button" class="nav-btn" name="navigate" data-act="next">▶</button>
<button type="button" class="nav-btn" name="navigate" data-act="last">▶▶</button>
</div>
</div>
<div class="detail-body">
<!-- 좌측 정보 -->
<div class="left">
<div class="section-title">위반 정보</div>
<div class="subnote">등록구분/위반일시/위반내역 등</div>
<div class="form-grid">
<!-- 1줄: 등록구분 / 목록번호 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">등록구분</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmDlgb" id="mmDlgb" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">목록번호</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmIngb" id="mmIngb" value="" readonly>
</div>
</div>
</div>
<!-- 2줄: 위반일자 / 위반시간 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">위반일자</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmDate" id="mmDate" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">위반시간</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmLawgb" id="mmLawgb" value="" readonly>
</div>
</div>
</div>
<!-- 3줄: 위반내용 / 영상매체 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">위반내용</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmSgnm" id="mmSgnm" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">영상매체</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asCell" id="asCell" value="" readonly>
</div>
</div>
</div>
<!-- 4줄: 신고자 / 연락처 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">신고자</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmSgnm" id="mmSgnm" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">연락처</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asCell" id="asCell" value="" readonly>
</div>
</div>
</div>
<!-- 5줄: 담당자 / (빈칸) -->
<div class="form-row">
<div class="field-group">
<div class="lbl">담당자</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmSgtel" id="mmSgtel" value="" readonly>
</div>
</div>
<div class="field-group empty"></div>
</div>
<!-- 6줄: 신고내용(textarea, 한 줄 전체) -->
<div class="form-row block">
<div class="field-group full">
<div class="lbl">신고내용</div>
<div class="fld">
<textarea name="cpMain" data-field="mmSgcont" id="mmSgcont" value="" readonly></textarea>
</div>
</div>
</div>
<!-- 7줄: 위반장소 (한 줄 전체) -->
<div class="form-row block">
<div class="field-group full">
<div class="lbl">위반장소</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
</div>
</div>
</div>
<!-- 8줄: 시군구 / 법정동 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">시군구</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asJsno" id="asJsno" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">법정동</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asJsno" id="asJsno" value="" readonly>
</div>
</div>
</div>
<!-- 9줄: 접수일 / 답변기한 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">접수일</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asJsno" id="asJsno" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">답변기한</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asJsno" id="asJsno" value="" readonly>
</div>
</div>
</div>
<!-- 구분선 & 차량 정보 타이틀 -->
<div class="form-divider"></div>
<div class="section-title block">차량 정보</div>
<!-- 10줄: 차량번호 / (빈칸) -->
<div class="form-row">
<div class="field-group">
<div class="lbl">차량번호</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="" id="" value="" readonly>
</div>
</div>
<div class="field-group empty"></div>
</div>
<!-- 11줄: 특기사항 (textarea, 한 줄 전체) -->
<div class="form-row block">
<div class="field-group full">
<div class="lbl">특기사항</div>
<div class="fld">
<textarea name="cpMain" data-field="mmSgcont" id="mmSgcont" value="" readonly></textarea>
</div>
</div>
</div>
<!-- 12줄: 차량명 / 차량색상 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">차량명</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">차량색상</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<!-- 13줄: 연료구분 / (빈칸) -->
<div class="form-row">
<div class="field-group">
<div class="lbl">연료구분</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group empty"></div>
</div>
<!-- 14줄: 소유주 / 등록구분 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">소유주</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">등록구분</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<!-- 15줄: 주민번호 / 우편번호 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">주민번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">우편번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<!-- 16줄: 주소 (한 줄 전체) -->
<div class="form-row block">
<div class="field-group full">
<div class="lbl">주소</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<!-- 17줄: 번지 / 차대번호 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">번지</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">차대번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<!-- 18줄: 도로코드 / (빈칸) -->
<div class="form-row">
<div class="field-group">
<div class="lbl">도로코드</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group empty"></div>
</div>
</div>
</div>
<!-- 우측 사진/지도/프리뷰 -->
<div class="right">
<div class="section-title">사진</div>
<div class="thumbs" id="photoThumbs">
<%-- IMG area --%>
</div>
<div class="mapbox">
<img src="" alt="지도">
</div>
<div class="preview" id="photoPreviewBox">
<img id="photoPreview" src="${pageContext.request.contextPath}/static/img/sample-1.jpg" alt="미리보기">
</div>
</div>
<div id="btn-area">
<button type="button" class="btn btn-light" name="changeSt" id="non-target" value="81">미부과처리</button>
<button type="button" class="btn btn-light" name="changeSt" id="non-target" value="">계도처리</button>
<button type="button" class="btn btn-light" name="changeSt" id="non-target" value="">회수처리</button>
<button type="button" class="btn btn-light" name="changeSt" id="non-target" value="">자료복사</button>
<button type="button" class="btn btn-light" id="delay">처리보류</button>
<button type="button" class="btn btn-light" id="delay">원본보기</button>
<button type="button" class="btn btn-light" id="infoReadReturn">되돌리기</button>
<button type="button" class="btn btn-light" id="stateChange" hidden>처리상태 변경</button>
</div>
</div>
<div class="statusbar">
<div class="status-left" id="mmCode"></div>
<div class="status-right">
<span><span class="count-dot" id="photoCount">3</span> 사진</span>
<label><input type="checkbox" id="hidePhoto"> 사진 안보이기</label>
</div>
</div>
</div>
<div id="photoEditSection" style="display:none;">
<jsp:include page="/WEB-INF/views/biz/totalInfo/totalInfo_photo_dialog.jsp" />
</div>
</div>
<!-- /Main body -->
<%--<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>--%>
<%--<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>--%>
<script type="text/javascript">
window.INIT_POP_API = {
search: () => {
console.log("Total Info Search!!!!")
const { cursor, mmCodes } = JSON.parse(localStorage.getItem("TOTAL_INFO_STATE"));
$.ajax({
// PathVariable 형태로 url를 동적으로 쓰는방식이다.
// 해당 방식 이외에 그냥 쿼리스트링으로 넘기는 방법도 있다.
url: "/total/info/" + cursor + "/info.ajax",
type: "GET",
dataType: 'json',
success: function(response) {
console.log(response.data)
$("#mmIngb").val(response.data.cpMain.mmIngb);
$("#mmDlgb").val(response.data.cpMain.mmDlgb);
$("#mmLawgb").val(response.data.cpMain.mmLawgb);
$("#mmKeum1").val(response.data.cpMain.mmKeum1);
$("#mmKeum2").val(response.data.cpMain.mmKeum2);
$("#mmSgcont").val(response.data.cpMain.mmSgcont);
$("#mmSgnm").val(response.data.cpMain.mmSgnm);
$("#mmSgtel").val(response.data.cpMain.mmSgtel);
$("#mmDate").val(response.data.cpMain.mmDate);
$("#asBbsNo").val(response.data.cpAnswer.asBbsNo);
$("#asState").val(response.data.cpAnswer.asState);
$("#asStateDt").val(response.data.cpAnswer.asStateDt);
$("#asCell").val(response.data.cpAnswer.asCell);
$("#asJsno").val(response.data.cpAnswer.asJsno);
$("#asJsdate").val(response.data.cpAnswer.asJsdate);
$("#mmCode").text(response.data.cpMain.mmCode.substring(5).replace(/^(\d{4})(.*)$/, '$1-$2'))
// 이미지 태그 동적 추가
const mmCode = response.data.cpMain.mmCode;
const imgPath = "${sessionScope.sessionVO.imgPath}";
console.log(imgPath)
const thumbContainer = $("#photoThumbs");
const imgList = ['A','B','C']; // 필요한 개수만큼
thumbContainer.empty(); // 기존 내용 제거
imgList.forEach(num => {
const img = `
<div class="thumb">
<img src="/images/\${mmCode}\${num}.jpg" alt="사진 ${num}" onerror="this.style.display='none'">
</div>`
;
thumbContainer.append(img);
});
//네비게이터 커서 카운트
$("#cursorCnt").text(mmCodes.indexOf(cursor) + 1);
$("#total").text(mmCodes.length);
// total info 초기상태
$("#infoState").val("init").trigger("change");
},
error: function(xhr, status, error) {
$("#result").text("조회 실패");
}
});
},
}
let fnBiz = {
init: () => {
// fnBiz.search();
},
infoSave: () => {
const { cursor, mmCodes } = JSON.parse(localStorage.getItem("TOTAL_INFO_STATE"));
const payload = fnBiz.collectByDataField();
console.log("data-field payload:", payload);
$.ajax({
url: "/total/info/" + cursor + "/info/edit.ajax",
type: "POST",
data: JSON.stringify(payload),
contentType: "application/json",
success: function(response) {
alert("수정완료.")
},
error: function(xhr, status, error) {
$("#result").text("수정 실패");
}
});
},
changeState: (e) => {
let state = e.target.value;
const { cursor, mmCodes } = JSON.parse(localStorage.getItem("TOTAL_INFO_STATE"));
$.ajax({
url: `/total/info/\${cursor}/\${state}/state.ajax`,
type: "POST",
contentType: "application/json",
success: function(response) {
alert("완료.")
},
error: function(xhr, status, error) {
$("#result").text("수정 실패");
}
});
},
btnSet: (infoState) => {
switch (infoState) {
case "init":
// $("#btn-area > button").prop("hidden", true);
// $("#infoEdit").prop("hidden", false);
// $("#infoDel").prop("hidden", false);
// $("#delay").prop("hidden", false);
// $("#buillPrint").prop("hidden", false);
// $("#stateChange").prop("hidden", false);
// $("#non-target").prop("hidden", false);
break;
case "edit":
// $("#btn-area > button").prop("hidden", true);
// $("#infoSave").prop("hidden", false);
// $("#infoReadReturn").prop("hidden", false);
break;
}
switch ($("#asState")) {
case "":
$("#destructionDocReReg").prop("hidden", false);
}
},
collectByDataField: () => {
const payload = {};
$("[data-field]").each(function () {
const key = $(this).data("field");
const val = $(this).val();
payload[key] = val;
});
return payload;
},
eventListener: () => {
/** 네비게이션 */
$("button[name=navigate]").on("click", (e) => {
let flag = $(e.currentTarget).data("act");
let {cursor, mmCodes} = JSON.parse(localStorage.getItem("TOTAL_INFO_STATE"));
if (!cursor || !mmCodes) return;
const idx = mmCodes.indexOf(cursor);
let nextCursor = cursor;
switch (flag) {
case "first":
nextCursor = mmCodes[0];
break;
case "prev":
nextCursor = mmCodes[Math.max(0, idx - 1)];
break;
case "next":
nextCursor = mmCodes[Math.min(mmCodes.length - 1, idx + 1)];
break;
case "last":
nextCursor = mmCodes[mmCodes.length - 1];
break;
}
// 스토리지 업데이트
localStorage.setItem("TOTAL_INFO_STATE", JSON.stringify({ cursor: nextCursor, mmCodes: mmCodes }))
//커서로 조회
INIT_POP_API.search();
});
/** 개별총정보 상태변경 트리거 */
$("#infoState").on("change", () => {
fnBiz.btnSet($("#infoState").val());
})
/** 개별총정보 수정모드 진입*/
$("#infoEdit").on("click", () => {
$("input").prop("readonly", false);
$("#infoState").val("edit").trigger("change");
})
/** 개별총정보 읽기모드 진입 */
$("#infoReadReturn").on("click", () => {
$("input").prop("readonly", true);
INIT_POP_API.search();
$("#infoState").val("init").trigger("change");
})
/** 정보수정 */
$("#infoSave").on("click", () => {
fnBiz.infoSave();
$("input").prop("readonly", true);
INIT_POP_API.search();
})
/** 상태값 변경 */
$("button[name='changeSt']").on("click", (e) => {
let flag = e.target.value;
fnBiz.changeState(e, flag);
})
/** 사진 더블클릭 → 원본 다이얼로그 */
$("#photoThumbs").on("dblclick", "img", function () {
const src = $(this).attr("src"); // 썸네일 경로
$("#photoDialogImg").attr("src", src); // 같은 경로를 원본으로 사용 (원본/썸네일 분리돼 있으면 여기서 가공)
$("#photoDialog").dialog("open");
});
}
}
$(function () {
$("#tabs").tabs();
INIT_POP_API.search();
fnBiz.eventListener();
});
</script>

@ -0,0 +1,264 @@
<%--
Created by IntelliJ IDEA.
User: moong
Date: 2025-11-20
Time: 오전 10:07
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="main_body">
<section id="section8" class="main_bars">
<div class="bgs-main">
<section id="section5">
<div class="sub_title"></div>
<button type="button" onclick="location.href='<c:url value='/template/noticeSample/register.do'/>'" class="newbtn bg1">등록</button>
</section>
</div>
</section>
<div class="contants_body">
<div class="gs_b_top">
<ul class="lef">
<li class="th">검색구분</li>
<li>
<select id="searchCondition" name="searchCondition" class="input">
<option value="">검색구분</option>
<option value="title" <c:if test="${paramVO.searchCondition eq 'title'}">selected</c:if>>차량번호</option>
<option value="contents" <c:if test="${paramVO.searchCondition eq 'contents'}">selected</c:if>>소유자</option>
<option value="writer" <c:if test="${paramVO.searchCondition eq 'writer'}">selected</c:if>>주민번호</option>
<option value="code" <c:if test="${paramVO.searchCondition eq 'code'}">selected</c:if>>코드</option>
</select>
</li>
<li class="th">검색어</li>
<li><input type="text" id="searchKeyword" name="searchKeyword" class="input" value="${param.searchKeyword}"/></li>
<li class="th">접수일자 지정</li>
<li>
<input type="text" id="searchStartDt" name="searchStartDt" class="input calender datepicker" value="${param.searchStartDt}" /> ~
<input type="text" id="searchEndDt" name="searchEndDt" class="input calender datepicker" value="${param.searchEndDt}" />
</li>
<li>
<button type="button" id="search_btn" class="newbtnss bg1">검색</button>
</li>
</ul>
<ul class="rig2">
<li>
<select id="perPageSelect" class="input">
<option value="10" <c:if test="${param.perPage eq '10'}">selected</c:if>>페이지당 10</option>
<option value="30" <c:if test="${empty param.perPage or param.perPage eq '30'}">selected</c:if>>페이지당 30</option>
<option value="100" <c:if test="${param.perPage eq '100'}">selected</c:if>>페이지당 100</option>
</select>
<span class="page_number"><span id="currentPage"></span><span class="bar">/</span><sapn id="totalPages"></sapn> Pages</span>
</li>
</ul>
</div>
<div class="gs_booking">
<div class="row">
<div class="col-sm-12">
<div class="box_column">
<div class="containers">
<div id="grid"></div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
let INIT_POPUP = null;
let SEARCH_COND = {};
let GRID = null;
// 검색조건 세팅
let setSearchCond = function () {
let searchCondition = $.trim(nvl($("#searchCondition").val(), ""));
let searchKeyword = $.trim(nvl($("#searchKeyword").val(), ""));
let searchUseYn = $.trim(nvl($("#searchUseYn").val(), ""));
let searchStartDt = $.trim(nvl($("#searchStartDt").val(), ""));
let searchEndDt = $.trim(nvl($("#searchEndDt").val(), ""));
SEARCH_COND.searchCondition = searchCondition;
SEARCH_COND.searchKeyword = searchKeyword;
SEARCH_COND.searchUseYn = searchUseYn;
SEARCH_COND.searchStartDt = searchStartDt;
SEARCH_COND.searchEndDt = searchEndDt;
};
const fnBiz = {
init: () => {
initGrid();
},
eventListener: () => {
// 엑셀 다운로드
$('#btnExcel').on('click', function () {
const params = $.param({
searchCondition: $('#searchCondition').val(),
searchKeyword: $('#searchKeyword').val(),
searchStartDt: $('#searchStartDt').val(),
searchEndDt: $('#searchEndDt').val()
});
const url = '${pageContext.request.contextPath}/minwon/dayanswer/dayanswer-excel.do?' + params;
window.location.href = url;
});
// 검색 버튼
$('#search_btn').on('click', function () {
let startDate = $("#searchStartDt").val();
let endDate = $("#searchEndDt").val();
if ((startDate && !endDate) || (!startDate && endDate)) {
alert("등록일 검색 시 시작일과 종료일을 모두 입력해주세요.");
return;
}
if (startDate && endDate) {
if (!isDate(startDate) || !isDate(endDate)) {
alert("유효한 날짜 형식이 아닙니다. (YYYY-MM-DD)");
return;
}
let startDateObj = new Date(startDate);
let endDateObj = new Date(endDate);
if (startDateObj > endDateObj) {
alert("시작일은 종료일보다 이후일 수 없습니다.");
return;
}
}
$("#page").val(1);
GRID.readData(1);
});
// 검색어 엔터
$('#searchKeyword').on('keypress', function (e) {
if (e.which === 13) {
e.preventDefault();
$('#search_btn').trigger('click');
}
});
// perPage 변경 이벤트 추가
$('#perPageSelect').on('change', () => {
const pagination = TuiGrid.instance.getPagination();
if (!pagination) return;
pagination.setItemsPerPage($('#perPageSelect').val());
pagination.reset(TuiGrid.instance.getRowCount());
pagination.movePageTo(1);
});
// 상태 탭 클릭
$('.state-tabs li').on('click', function () {
$('.state-tabs li').removeClass('on');
$(this).addClass('on');
const state = $(this).data('state') || '';
$('#tabState').val(state);
SEARCH_COND = {};
GRID.readData(1);
});
}
};
/** tui-grid Set */
const initGrid = () => {
const gridColumns = [
{ header: '자료출처', name: 'mmIngb', width: 150, align: 'center' },
{ header: '접수(위반)일시', name: 'mmDate', width: 150, align: 'center' },
{ header: '차량번호', name: 'mmCarno', width: 150, align: 'center' },
{ header: '주민번호', name: 'omJno', width: 150, align: 'center' },
{ header: '소유자', name: 'omName', width: 150, align: 'center' },
{ header: '잔액', name: 'mmKeum2', width: 150, align: 'center' },
{ header: '영상매체', name: 'mmImageGb', width: 150, align: 'center' },
{ header: '사진', name: 'mmImageCnt', width: 150, align: 'center' },
{ header: '차량확인', name: 'mmCarcheck', width: 150, align: 'center' },
{ header: '처리상태', name: 'mmState', width: 150, align: 'center' },
{ header: 'mmCode', name: 'mmCode', sortable: true, width: 150, align: 'center', hidden: true}
];
const gridDatasource = {
api: {
readData: {
url: '<c:url value="/search/search-select.ajax"/>',
method: 'POST',
contentType: 'application/x-www-form-urlencoded',
processData: true
}
},
initialRequest: false, // 직접 readData(1) 호출할 거라 false
serializer: function (params) {
setSearchCond();
SEARCH_COND.perPage = params.perPage;
SEARCH_COND.page = params.page;
return $.param(SEARCH_COND);
}
};
const perPage = parseInt($('#perPageSelect').val() || 10, 10);
const gridOptions = {
el: 'grid',
rowHeaders: ['checkbox'],
columns: gridColumns,
noData: "처리 할 초기자료가 없습니다.",
pageOptions: {
useClient: false,
perPage: perPage
}
};
// 실제 GRID 생성
GRID = TuiGrid.of(gridOptions, gridDatasource, (res) => {
// 서버 응답 후 페이지 정보 세팅
const data = res.data || {};
if (data.pagination) {
$("#currentPage").text(data.pagination.page || '');
$("#totalPages").text(data.pagination.totalPages || '');
}
// 더블 클릭 이벤트
GRID.on("dblclick", (e) => {
var popUrl = '/minwon/init/init_popup.do';
var popTitle = "initPopup";
var popOption = "width=1400px, height=900px, resizable=yes, scrollbars=yes, location=no, top=100px, left=100px";
// 1) localStorage에 저장
console.log(e)
let cursor = e.instance.getValue(e.rowKey, 'mmCode');
let mmCodes = e.instance.getData().map(row => row.mmCode);
console.log(cursor);
const state = { cursor, mmCodes, savedAt: Date.now() };
localStorage.setItem('TOTAL_INFO_STATE', JSON.stringify(state));
// 2) 팝업이 없거나 닫혀 있으면 새로 열기
if (!INIT_POPUP || INIT_POPUP.closed) {
INIT_POPUP = window.open(popUrl, popTitle, popOption);
} else {
// 이미 떠 있으면 새로 안 만들고, 그 창에 포커스만 줌
INIT_POPUP.focus();
INIT_POPUP.INIT_POP_API.search();
}
});
});
// 최초 1페이지 조회
GRID.readData(1);
};
// 레디펑션
$(function () {
fnBiz.init();
fnBiz.eventListener();
});
</script>

@ -22,51 +22,51 @@
</section>
</div>
</section>
<div class="contants_body">
<div class="gs_booking">
<div class="row">
<div class="col-sm-12">
<div class="box_column">
<!-- 이미지 에디터 컨테이너 -->
<div class="tui-image-editor-warapper" style="width: 100%; height: 700px;">
<div id="tui-image-editor"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<%-- <!-- 🔹 보기 모드 -->--%>
<%-- <div id="photoViewMode">--%>
<%-- <img id="photoDialogImg"--%>
<%-- src=""--%>
<%-- alt="사진 보기"--%>
<%-- style="max-width:100%; height:auto; display:block; margin:0 auto;">--%>
<%-- <div style="text-align:right; margin-top:8px;">--%>
<%-- <button type="button" id="btnPhotoEdit" class="btn btn-light">사진 편집</button>--%>
<%-- <button type="button" id="btnPhotoClose" class="btn btn-light">닫기</button>--%>
<%-- <div class="contants_body">--%>
<%-- <div class="gs_booking">--%>
<%-- <div class="row">--%>
<%-- <div class="col-sm-12">--%>
<%-- <div class="box_column">--%>
<%-- <!-- 이미지 에디터 컨테이너 -->--%>
<%-- <div class="tui-image-editor-warapper" style="width: 100%; height: 700px;">--%>
<%-- <div id="tui-image-editor"></div>--%>
<%-- </div>--%>
<%-- </div>--%>
<%-- </div>--%>
<%-- </div>--%>
<%-- </div>--%>
<%-- </div>--%>
<%-- <!-- 🔹 편집 모드 -->--%>
<%-- <div id="photoEditMode" style="display:none;">--%>
<%-- <div class="edit-canvas-wrap">--%>
<%-- <!-- 나중에 cropperjs 붙일 이미지 or canvas -->--%>
<%-- <img id="photoEditImg"--%>
<%-- src=""--%>
<%-- alt="편집 대상"--%>
<%-- style="max-width:100%; height:auto; display:block; margin:0 auto;">--%>
<%-- </div>--%>
<!-- 🔹 보기 모드 -->
<div id="photoViewMode">
<img id="photoDialogImg"
src=""
alt="사진 보기"
style="max-width:100%; height:auto; display:block; margin:0 auto;">
<%-- <div class="edit-toolbar" style="text-align:right; margin-top:8px;">--%>
<%-- <!-- 여기부터는 예시 버튼들. 나중에 기능 붙이면 됨 -->--%>
<%-- <button type="button" id="btnEditRotate" class="btn btn-light">회전</button>--%>
<%-- <button type="button" id="btnEditApply" class="btn btn-light">적용</button>--%>
<%-- <button type="button" id="btnEditCancel" class="btn btn-light">취소</button>--%>
<%-- </div>--%>
<%-- </div>--%>
<div style="text-align:right; margin-top:8px;">
<button type="button" id="btnPhotoEdit" class="btn btn-light">사진 편집</button>
<button type="button" id="btnPhotoClose" class="btn btn-light">닫기</button>
</div>
</div>
<!-- 🔹 편집 모드 -->
<div id="photoEditMode" style="display:none;">
<div class="edit-canvas-wrap">
<!-- 나중에 cropperjs 붙일 이미지 or canvas -->
<img id="photoEditImg"
src=""
alt="편집 대상"
style="max-width:100%; height:auto; display:block; margin:0 auto;">
</div>
<div class="edit-toolbar" style="text-align:right; margin-top:8px;">
<!-- 여기부터는 예시 버튼들. 나중에 기능 붙이면 됨 -->
<button type="button" id="btnEditRotate" class="btn btn-light">회전</button>
<button type="button" id="btnEditApply" class="btn btn-light">적용</button>
<button type="button" id="btnEditCancel" class="btn btn-light">취소</button>
</div>
</div>
</div>
<script type="text/javascript">

@ -38,9 +38,9 @@
<%-- 개별총정보 상태값 --%>
<input type="hidden" id="infoState" value="init" />
<div class="page-indicator">
<span id="cursorCnt">0</span>of <span id="total">0</span></div>
<span id="cursorCnt">0</span>of <span id="total">0</span>
</div>
<div class="nav-group">
<button type="button" class="nav-btn" name="navigate" data-act="first" id="first">◀◀</button>
<button type="button" class="nav-btn" name="navigate" data-act="prev">◀</button>
@ -56,157 +56,293 @@
<div class="subnote">등록구분/위반일시/위반내역 등</div>
<div class="form-grid">
<div class="lbl">등록구분</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmDlgb" id="mmDlgb" value="" readonly>
</div>
<div class="lbl">자료출처</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmIngb" id="mmIngb" value="" readonly>
</div>
<div class="lbl">위반일시</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmDate" id="mmDate" value="" readonly>
</div>
<div class="lbl">위반내용</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmLawgb" id="mmLawgb" value="" readonly>
<!-- 1줄: 등록구분 / 자료출처 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">등록구분</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmDlgb" id="mmDlgb" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">자료출처</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmIngb" id="mmIngb" value="" readonly>
</div>
</div>
</div>
<div class="lbl">신고자</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmSgnm" id="mmSgnm" value="" readonly>
</div>
<div class="lbl">연락처</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asCell" id="asCell" value="" readonly>
<!-- 2줄: 위반일시 / 위반내용 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">위반일시</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmDate" id="mmDate" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">위반내용</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmLawgb" id="mmLawgb" value="" readonly>
</div>
</div>
</div>
<div class="lbl">담당자</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmSgtel" id="mmSgtel" value="" readonly>
</div>
<div class="lbl">공개여부</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
<!-- 3줄: 신고자 / 연락처 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">신고자</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmSgnm" id="mmSgnm" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">연락처</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asCell" id="asCell" value="" readonly>
</div>
</div>
</div>
<div class="lbl">신고내용</div>
<div class="fld block">
<textarea name="cpMain" data-field="mmSgcont" id="mmSgcont" value="" readonly></textarea>
<!-- 4줄: 담당자 / 공개여부 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">담당자</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmSgtel" id="mmSgtel" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">공개여부</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
</div>
</div>
</div>
<div class="lbl">위반장소</div>
<div class="fld"><input type="text" data-field="" id="" value="" readonly></div>
<div class="lbl">접수번호</div>
<div class="fld"><input type="text" name="cpAnswer" data-field="asJsno" id="asJsno" value="" readonly></div>
<div class="lbl">접수일자</div>
<div class="fld"><input type="text" name="cpAnswer" data-field="asJsdate" id="asJsdate" value="" readonly></div>
<div class="lbl">목록번호</div>
<div class="fld"><input type="text" name="cpAnswer" data-field="asBbsNo" id="asBbsNo" value="" readonly></div>
<div class="lbl">법정동</div>
<div class="fld"><input type="text" data-field="" id="" value="" readonly></div>
<div></div><div></div>
<div class="lbl">사진등록금액</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmKeum1" id="mmKeum1" value="" readonly>
</div>
<div class="lbl">부과금액</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmKeum2" id="mmKeum2" value="" readonly>
<!-- 5줄: 신고내용 (textarea, 한 줄 전체) -->
<div class="form-row block">
<div class="field-group full">
<div class="lbl">신고내용</div>
<div class="fld">
<textarea name="cpMain" data-field="mmSgcont" id="mmSgcont" value="" readonly></textarea>
</div>
</div>
</div>
<div class="lbl">감액금액</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
</div>
<div class="lbl">총수납액</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
<!-- 6줄: 위반장소 / 접수번호 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">위반장소</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">접수번호</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asJsno" id="asJsno" value="" readonly>
</div>
</div>
</div>
<div class="lbl">잔액</div>
<div class="fld">
<input class="hl" data-field="" type="text" value="" readonly>
</div>
<div class="lbl">특기사항</div>
<div class="fld">
<button type="button" class="btn btn-light" id="btnSpecial">특기사항 보기</button>
<!-- 7줄: 접수일자 / 목록번호 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">접수일자</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asJsdate" id="asJsdate" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">목록번호</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asBbsNo" id="asBbsNo" value="" readonly>
</div>
</div>
</div>
<div class="block bar"></div>
<div class="lbl">처리상태일시</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asStateDt" id="asStateDt" value="" readonly>
</div>
<div class="lbl">처리상태</div>
<div class="fld">
<input type="text" class="hl" name="cpAnswer" data-field="asState" id="asState" value="" readonly>
<!-- 8줄: 법정동 / (빈칸) -->
<div class="form-row">
<div class="field-group">
<div class="lbl">법정동</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
</div>
</div>
<div class="field-group empty"></div>
</div>
<div class="lbl">차량명</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
<div class="lbl">차량색상</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 9줄: 사진등록금액 / 부과금액 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">사진등록금액</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmKeum1" id="mmKeum1" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">부과금액</div>
<div class="fld">
<input type="text" name="cpMain" data-field="mmKeum2" id="mmKeum2" value="" readonly>
</div>
</div>
</div>
<div class="lbl">연료구분</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 10줄: 감액금액 / 총수납액 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">감액금액</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">총수납액</div>
<div class="fld">
<input type="text" data-field="" id="" value="" readonly>
</div>
</div>
</div>
<div></div><div></div>
<div class="block bar"></div>
<div class="section-title block">소유주 정보</div>
<!-- 11줄: 잔액 / 특기사항 버튼 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">잔액</div>
<div class="fld">
<input class="hl" data-field="" type="text" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">특기사항</div>
<div class="fld">
<button type="button" class="btn btn-light" id="btnSpecial">특기사항 보기</button>
</div>
</div>
</div>
<div class="lbl">소유주</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 구분선 -->
<div class="bar"></div>
<!-- 12줄: 처리상태일시 / 처리상태 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">처리상태일시</div>
<div class="fld">
<input type="text" name="cpAnswer" data-field="asStateDt" id="asStateDt" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">처리상태</div>
<div class="fld">
<input type="text" class="hl" name="cpAnswer" data-field="asState" id="asState" value="" readonly>
</div>
</div>
</div>
<div class="lbl">등록구분</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 13줄: 차량명 / 차량색상 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">차량명</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">차량색상</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<div class="lbl">주민번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 14줄: 연료구분 / (빈칸) -->
<div class="form-row">
<div class="field-group">
<div class="lbl">연료구분</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group empty"></div>
</div>
<div class="lbl">우편번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 구분선 -->
<div class="bar"></div>
<!-- 소유주 정보 타이틀 -->
<div class="section-title">소유주 정보</div>
<!-- 15줄: 소유주 / 등록구분 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">소유주</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">등록구분</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<div class="lbl">주소</div>
<div class="fld block">
<input type="text" data-field="" value="" readonly>
<!-- 16줄: 주민번호 / 우편번호 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">주민번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">우편번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<div class="lbl">번지</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 17줄: 주소 (한 줄 전체) -->
<div class="form-row block">
<div class="field-group full">
<div class="lbl">주소</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<div class="lbl">차대번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 18줄: 번지 / 차대번호 -->
<div class="form-row">
<div class="field-group">
<div class="lbl">번지</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group">
<div class="lbl">차대번호</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
</div>
<div class="lbl">도로코드</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
<!-- 19줄: 도로코드 / (빈칸) -->
<div class="form-row">
<div class="field-group">
<div class="lbl">도로코드</div>
<div class="fld">
<input type="text" data-field="" value="" readonly>
</div>
</div>
<div class="field-group empty"></div>
</div>
<div></div><div></div>
</div>
</div>
@ -218,7 +354,7 @@
</div>
<div class="mapbox">
<img src="${pageContext.request.contextPath}/static/img/sample-map.jpg" alt="지도">
<img src="" alt="지도">
</div>
<div class="preview" id="photoPreviewBox">
@ -254,7 +390,6 @@
<jsp:include page="/WEB-INF/views/biz/totalInfo/totalInfo_photo_dialog.jsp" />
</div>
<div id="tabs-b">
의견진술
</div>
@ -269,6 +404,7 @@
</div>
</div>
</div>
<!-- /Main body -->
<%--<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>--%>
<%--<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.min.js"></script>--%>

@ -75,6 +75,11 @@
<script type="text/javascript" src="<c:url value='/xit/common_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/datatables_util.js' />"></script>
<script type="text/javascript" src="<c:url value='/xit/moment.js' />"></script>
<%-- tui grid new --%>
<link rel="stylesheet" type="text/css" href="<c:url value='/xit/tuiGridCustom/xit-tui-grid.css' />">
<script type="text/javascript" src="<c:url value='/xit/tuiGridCustom/xit-tui-grid.js' />"></script>
<!-- 파일 업로드 스타일 -->
<link rel="stylesheet" href="<c:url value="/css/xit-multi-fileupload.css"/>" />

@ -11,93 +11,139 @@
<%-- 활성화된 메뉴 정보 저장할 변수 --%>
<c:set var="activeMenuId" value="" scope="request" />
<c:set var="activeSubMenuId" value="" scope="request" />
<%-- 콘솔 디버깅 용도 (개발 환경에서만 사용) --%>
<!-- Current URL: ${currentUrl} -->
<c:set var="activeThirdMenuId" value="" scope="request" />
<!-- Sidebar body -->
<div class="sidebar-body">
<ul class="nav treeview mb-4" data-accordion>
<c:if test="${not empty sessionScope.sessionVO.menus}">
<c:forEach var="menu" items="${sessionScope.sessionVO.menus}">
<c:if test="${menu.useYn eq 'Y' and menu.viewYn eq 'Y'}">
<%-- 메뉴 상태 변수 초기화 (unescape 처리) --%>
<c:set var="menuUrl" value="${not empty menu.menuUrl ? xssUtil.unescape(menu.menuUrl) : '#'}" />
<c:set var="menuUrlPattern" value="${not empty menu.urlPattern ? xssUtil.unescape(menu.urlPattern) : ''}" />
<c:set var="isMenuActive" value="false" />
<c:set var="hasActiveChild" value="false" />
<%-- 메인 메뉴 활성화 상태 확인 (디버깅 정보 추가) --%>
<c:if test="${not empty menuUrlPattern && menuUrl != '#'}">
<c:set var="patterns" value="${fn:split(menuUrlPattern, ',')}" />
<!-- Menu ID: ${menu.menuId}, Patterns: ${menuUrlPattern} -->
<c:forEach var="pattern" items="${patterns}">
<c:set var="trimmedPattern" value="${fn:trim(pattern)}" />
<!-- Checking pattern: "${trimmedPattern}" against URL: "${currentUrl}" -->
<c:if test="${path:match(trimmedPattern, currentUrl)}">
<c:set var="isMenuActive" value="true" />
<c:set var="activeMenuId" value="${menu.menuId}" scope="request" />
<!-- Pattern matched! Menu ${menu.menuId} is active -->
</c:if>
</c:forEach>
</c:if>
<c:if test="${not empty sessionScope.sessionVO.menus}">
<c:forEach var="menu" items="${sessionScope.sessionVO.menus}">
<c:if test="${menu.useYn eq 'Y' and menu.viewYn eq 'Y'}">
<%-- 메뉴 상태 변수 초기화 (unescape 처리) --%>
<c:set var="menuUrl" value="${not empty menu.menuUrl ? xssUtil.unescape(menu.menuUrl) : '#'}" />
<c:set var="menuUrlPattern" value="${not empty menu.urlPattern ? xssUtil.unescape(menu.urlPattern) : ''}" />
<c:set var="isMenuActive" value="false" />
<c:set var="hasActiveChild" value="false" />
<%-- 서브메뉴 처리 및 활성화 상태 확인 --%>
<c:if test="${not empty menu.children}">
<c:forEach var="subMenu" items="${menu.children}">
<c:if test="${subMenu.useYn eq 'Y' and subMenu.viewYn eq 'Y'}">
<%-- 서브메뉴 URL unescape 처리 --%>
<c:set var="subMenuUrl" value="${not empty subMenu.menuUrl ? xssUtil.unescape(subMenu.menuUrl) : '#'}" />
<c:set var="subMenuUrlPattern" value="${not empty subMenu.urlPattern ? xssUtil.unescape(subMenu.urlPattern) : ''}" />
<c:set var="isSubMenuActive" value="false" />
<%-- 서브메뉴 활성화 상태 확인 (디버깅 정보 추가) --%>
<c:if test="${not empty subMenuUrlPattern && subMenuUrl != '#'}">
<c:set var="subPatterns" value="${fn:split(subMenuUrlPattern, ',')}" />
<!-- SubMenu ID: ${subMenu.menuId}, Patterns: ${subMenuUrlPattern} -->
<c:forEach var="subPattern" items="${subPatterns}">
<c:set var="trimmedSubPattern" value="${fn:trim(subPattern)}" />
<!-- Checking subpattern: "${trimmedSubPattern}" against URL: "${currentUrl}" -->
<c:if test="${path:match(trimmedSubPattern, currentUrl)}">
<c:set var="isSubMenuActive" value="true" />
<c:set var="activeSubMenuId" value="${subMenu.menuId}" scope="request" />
<c:set var="hasActiveChild" value="true" />
<!-- SubPattern matched! SubMenu ${subMenu.menuId} is active -->
</c:if>
</c:forEach>
<%-- 1뎁스 메뉴 활성화 상태 확인 --%>
<c:if test="${not empty menuUrlPattern && menuUrl != '#'}">
<c:set var="patterns" value="${fn:split(menuUrlPattern, ',')}" />
<c:forEach var="pattern" items="${patterns}">
<c:set var="trimmedPattern" value="${fn:trim(pattern)}" />
<c:if test="${path:match(trimmedPattern, currentUrl)}">
<c:set var="isMenuActive" value="true" />
<c:set var="activeMenuId" value="${menu.menuId}" scope="request" />
</c:if>
</c:if>
</c:forEach>
</c:if>
<%-- 메뉴 렌더링 --%>
<li class="nav-item" data-menu-id="${menu.menuId}" data-url-pattern="${menuUrlPattern}">
<a href="<c:url value="${empty menuUrl ? '#' : menuUrl}" />" class="nav-link has-icon ${not empty menu.children ? 'treeview-toggle' : ''} ${isMenuActive || hasActiveChild ? 'active show' : ''}">
<c:if test="${not empty menu.menuIcon}"><i data-feather="${menu.menuIcon}"></i></c:if>${menu.menuNm}
</a>
</c:forEach>
</c:if>
<%-- 서브메뉴 렌더링 --%>
<%-- 2뎁스 & 3뎁스 활성화 상태 확인 --%>
<c:if test="${not empty menu.children}">
<ul class="nav" style="${isMenuActive || hasActiveChild ? '' : ''}">
<c:forEach var="subMenu" items="${menu.children}">
<c:if test="${subMenu.useYn eq 'Y' and subMenu.viewYn eq 'Y'}">
<%-- 서브메뉴 URL unescape 처리 --%>
<c:set var="subMenuUrl" value="${not empty subMenu.menuUrl ? xssUtil.unescape(subMenu.menuUrl) : '#'}" />
<c:set var="subMenuUrlPattern" value="${not empty subMenu.urlPattern ? xssUtil.unescape(subMenu.urlPattern) : ''}" />
<c:set var="isSubMenuActive" value="${activeSubMenuId eq subMenu.menuId}" />
<c:forEach var="subMenu" items="${menu.children}">
<c:if test="${subMenu.useYn eq 'Y' and subMenu.viewYn eq 'Y'}">
<%-- 2뎁스 URL --%>
<c:set var="subMenuUrl" value="${not empty subMenu.menuUrl ? xssUtil.unescape(subMenu.menuUrl) : '#'}" />
<c:set var="subMenuUrlPattern" value="${not empty subMenu.urlPattern ? xssUtil.unescape(subMenu.urlPattern) : ''}" />
<c:set var="isSubMenuActive" value="false" />
<%-- 2뎁스 활성화 확인 --%>
<c:if test="${not empty subMenuUrlPattern && subMenuUrl != '#'}">
<c:set var="subPatterns" value="${fn:split(subMenuUrlPattern, ',')}" />
<c:forEach var="subPattern" items="${subPatterns}">
<c:set var="trimmedSubPattern" value="${fn:trim(subPattern)}" />
<c:if test="${path:match(trimmedSubPattern, currentUrl)}">
<c:set var="isSubMenuActive" value="true" />
<c:set var="activeSubMenuId" value="${subMenu.menuId}" scope="request" />
<c:set var="hasActiveChild" value="true" />
</c:if>
</c:forEach>
</c:if>
<%-- 3뎁스(손자 메뉴) 활성화 확인 --%>
<c:if test="${not empty subMenu.children}">
<c:forEach var="thirdMenu" items="${subMenu.children}">
<c:if test="${thirdMenu.useYn eq 'Y' and thirdMenu.viewYn eq 'Y'}">
<c:set var="thirdMenuUrl" value="${not empty thirdMenu.menuUrl ? xssUtil.unescape(thirdMenu.menuUrl) : '#'}" />
<c:set var="thirdMenuUrlPattern" value="${not empty thirdMenu.urlPattern ? xssUtil.unescape(thirdMenu.urlPattern) : ''}" />
<li class="nav-item ${isSubMenuActive ? 'show' : ''}" data-menu-id="${subMenu.menuId}" data-url-pattern="${subMenuUrlPattern}">
<a href="<c:url value='${subMenuUrl}' />" class="nav-link">${subMenu.menuNm}</a>
</li>
<c:if test="${not empty thirdMenuUrlPattern && thirdMenuUrl != '#'}">
<c:set var="thirdPatterns" value="${fn:split(thirdMenuUrlPattern, ',')}" />
<c:forEach var="thirdPattern" items="${thirdPatterns}">
<c:set var="trimmedThirdPattern" value="${fn:trim(thirdPattern)}" />
<c:if test="${path:match(trimmedThirdPattern, currentUrl)}">
<%-- 3뎁스가 active면 부모(2뎁스, 1뎁스)도 전부 펼쳐줘야 함 --%>
<c:set var="hasActiveChild" value="true" />
<c:set var="activeSubMenuId" value="${subMenu.menuId}" scope="request" />
<c:set var="activeThirdMenuId" value="${thirdMenu.menuId}" scope="request" />
</c:if>
</c:forEach>
</c:if>
</c:if>
</c:forEach>
</c:if>
</c:forEach>
</ul>
</c:if>
</c:forEach>
</c:if>
</li>
</c:if>
</c:forEach>
</c:if>
</ul>
<%-- 1뎁스 메뉴 렌더링 --%>
<li class="nav-item" data-menu-id="${menu.menuId}" data-url-pattern="${menuUrlPattern}">
<a href="<c:url value='${empty menuUrl ? "#" : menuUrl}' />"
class="nav-link has-icon ${not empty menu.children ? 'treeview-toggle' : ''} ${isMenuActive || hasActiveChild ? 'active show' : ''}">
<c:if test="${not empty menu.menuIcon}">
<i data-feather="${menu.menuIcon}"></i>
</c:if>
${menu.menuNm}
</a>
<%-- 2뎁스 & 3뎁스 메뉴 렌더링 --%>
<c:if test="${not empty menu.children}">
<ul class="nav" style="${isMenuActive || hasActiveChild ? '' : ''}">
<c:forEach var="subMenu" items="${menu.children}">
<c:if test="${subMenu.useYn eq 'Y' and subMenu.viewYn eq 'Y'}">
<%-- 2뎁스 URL --%>
<c:set var="subMenuUrl" value="${not empty subMenu.menuUrl ? xssUtil.unescape(subMenu.menuUrl) : '#'}" />
<c:set var="subMenuUrlPattern" value="${not empty subMenu.urlPattern ? xssUtil.unescape(subMenu.urlPattern) : ''}" />
<c:set var="isSubMenuActive" value="${activeSubMenuId eq subMenu.menuId}" />
<li class="nav-item ${isSubMenuActive ? 'show' : ''}"
data-menu-id="${subMenu.menuId}"
data-url-pattern="${subMenuUrlPattern}">
<a href="<c:url value='${subMenuUrl}' />"
class="nav-link ${not empty subMenu.children ? 'treeview-toggle' : ''} ${isSubMenuActive ? 'active' : ''}">
${subMenu.menuNm}
</a>
<%-- 3뎁스 메뉴 렌더링 --%>
<c:if test="${not empty subMenu.children}">
<ul class="nav nav-3depth" style="${isSubMenuActive ? '' : ''}">
<c:forEach var="thirdMenu" items="${subMenu.children}">
<c:if test="${thirdMenu.useYn eq 'Y' and thirdMenu.viewYn eq 'Y'}">
<c:set var="thirdMenuUrl" value="${not empty thirdMenu.menuUrl ? xssUtil.unescape(thirdMenu.menuUrl) : '#'}" />
<c:set var="thirdMenuUrlPattern" value="${not empty thirdMenu.urlPattern ? xssUtil.unescape(thirdMenu.urlPattern) : ''}" />
<c:set var="isThirdMenuActive" value="${activeThirdMenuId eq thirdMenu.menuId}" />
<li class="nav-item ${isThirdMenuActive ? 'show active' : ''}"
data-menu-id="${thirdMenu.menuId}"
data-url-pattern="${thirdMenuUrlPattern}">
<a href="<c:url value='${thirdMenuUrl}' />" class="nav-link">
${thirdMenu.menuNm}
</a>
</li>
</c:if>
</c:forEach>
</ul>
</c:if>
<%-- /3뎁스 --%>
</li>
</c:if>
</c:forEach>
</ul>
</c:if>
</li>
</c:if>
</c:forEach>
</c:if>
</ul>
</div>
<!-- /Sidebar body -->
<!-- /Sidebar body -->

@ -256,7 +256,7 @@
/* 카드/헤더 */
.detail-card{border:1px solid var(--border); border-radius:10px; overflow:hidden; background:#fff;}
.detail-card .card-header{display:flex; justify-content:space-between; align-items:center; background:var(--green); color:#fff; padding:10px 12px;}
.detail-card .card-header{display:flex; justify-content:space-between; align-items:center; background:#202342; color:#fff; padding:10px 12px;}
.card-header .title{font-weight:700}
.card-header .actions{display:flex; gap:6px; align-items:center}
.pill{font-size:12px; background:rgba(255,255,255,.25); padding:3px 8px; border-radius:999px}
@ -270,14 +270,30 @@
.subnote{font-size:12px; color:#888; text-align:right; margin-top:-4px; margin-bottom:8px}
/* 폼 그리드 */
.form-grid{display:grid; grid-template-columns: 110px 1fr 110px 1fr; gap:8px 10px}
.lbl{align-self:center; color:#444; font-size:13px}
.fld input,.fld textarea,.fld select{width:100%; padding:6px 8px; border:1px solid var(--border); border-radius:6px; font-size:13px; background:#fff}
.fld input[readonly],.fld textarea[readonly]{background:#fafafa}
.fld textarea{height:80px; resize:vertical}
.badge{display:inline-block; background:#eef2ff; color:#1d4ed8; border:1px solid #c7d2fe; padding:3px 8px; border-radius:999px; font-size:12px}
.hl{background:var(--warn)}
.block{grid-column: 1 / -1}
/*.form-grid{display:grid; grid-template-columns: 110px 1fr 110px 1fr; gap:8px 10px}*/
/*.lbl{align-self:center; color:#444; font-size:13px}*/
/*.fld input,.fld textarea,.fld select{width:100%; padding:6px 8px; border:1px solid var(--border); border-radius:6px; font-size:13px; background:#fff}*/
/*.fld input[readonly],.fld textarea[readonly]{background:#fafafa}*/
/*.fld textarea{height:80px; resize:vertical}*/
/*.badge{display:inline-block; background:#eef2ff; color:#1d4ed8; border:1px solid #c7d2fe; padding:3px 8px; border-radius:999px; font-size:12px}*/
/*.hl{background:var(--warn)}*/
/*.block{grid-column: 1 / -1}*/
/** 플렉스 폼 start */
.form-grid {display: flex;flex-direction: column; row-gap: 8px;}
.form-row {display: flex;column-gap: 10px;}
.field-group {display: flex;align-items: center;flex: 1 1 0;min-width: 0;gap: 6px;}
.field-group .lbl {flex: 0 0 110px;color: #444;font-size: 13px;align-self: center;white-space: nowrap;font-weight: 400;}
.field-group .fld {flex: 1 1 0;}
.field-group .fld input, .field-group .fld textarea, .field-group .fld select {width: 100%;padding: 6px 8px;border: 1px solid var(--border);border-radius: 6px;font-size: 13px;background: #fff;box-sizing: border-box;}
.field-group .fld input[readonly], .field-group .fld textarea[readonly] {background: #fafafa;} .field-group .fld textarea {height: 80px;resize: vertical;} .form-row.block .field-group.full {flex: 1 1 100%;display: flex;align-items: flex-start;}
.form-row.block .field-group.full .lbl {margin-top: 4px;}
.field-group.empty {flex: 1 1 0;}
.badge {display: inline-block;background: #eef2ff;color: #1d4ed8;border: 1px solid #c7d2fe;padding: 3px 8px;border-radius: 999px;font-size: 12px;}
.hl {background: var(--warn);}
@media (max-width: 1200px) { .form-row {flex-direction: column;} }
/* 플렉스 폼 end */
.bar{height:1px; background:var(--border); margin:8px 0}
@ -356,4 +372,5 @@
/** totalInfo End */

@ -0,0 +1,71 @@
@charset "utf-8";
tui-grid-content-area {border-radius: 10px;}
/* selected row */
table.tui-grid-table tr.tui-grid-cell-current-row > td{
background-color: rgba(199, 235, 235, 1);
}
table.tui-grid-table tr.tui-grid-cell-current-row > td:focus{
outline: none;
}
/* font size */
table.tui-grid-table th{
border-color: #fff;
font-size: 14px;
font-family: 'notokr-bold';
}
table.tui-grid-table td div{
font-size: 0.80rem;
}
/* header color */
table.tui-grid-table th.tui-grid-cell-header, table.tui-grid-table th.tui-grid-cell-row-header{
background-color: #f2f4ff;
border-right: 1px solid #fff;
/*border-top: 2px solid #d0d0d0;*/
font-size: 14px;
font-weight: bold;
}
.tui-grid-scrollbar-right-top {background-color: #f2f4ff;}
.tui-grid-body-area {margin-right: 3px;}
.tui-grid-scrollbar-y-outer-border {background-color: unset !important;}
.tui-grid-scrollbar-right-bottom { border-bottom-right-radius: 10px;}
/* grid cell */
/* table.tui-grid-table td.tui-grid-cell div.tui-grid-cell-content{ */
/* overflow: auto; */
/* } */
table.tui-grid-table td.tui-grid-cell-editable > div.tui-grid-cell-content{
/* border: 1px outset; */
cursor: pointer;
}
/* summary영역 수정 */
.tui-grid-summary-area .tui-grid-cell {
text-align: right;
font-size: 11px;
padding-right: 5px;
background-color: rgba(238, 238, 238, 1);
border: 1px solid white;
}
.tui-grid-table tr {
border-bottom: 1px solid #eee;
}
/* "CustomButtonRenderer" print cell .. */
table.tui-grid-table tr > td > span > p {
color: #0000ee;
cursor: pointer;
padding-bottom: 1px;
border-bottom: solid 1px;
padding-left: 10px;
}

@ -0,0 +1,698 @@
const Grid = tui.Grid;
const customTheme = {
selection: {
background: '#4daaf9',
border: '#004082'
},
scrollbar: {
background: '#f5f5f5',
thumb: '#d9d9d9',
active: '#c1c1c1'
},
row: {
even: {
// background: '#f2f4ff'
background: '#fff'
},
hover: {
background: '#ccc'
}
},
cell: {
normal: {
background: '#fbfbfb',
border: '#e0e0e0',
showVerticalBorder: false
},
header: {
background: '#f2f4ff',
// border: '#ccc',
text:'#09097a',
showVerticalBorder: false
},
rowHeader: {
// border: '#ccc',
showVerticalBorder: false
},
editable: {
background: '#fbfbfb'
},
selectedHeader: {
background: '#d8d8d8'
},
focused: {
border: '#418ed4'
},
disabled: {
text: '#b0b0b0'
}
}
}
class CustomRowNumberRenderer {
constructor(props) {
const el = document.createElement('span');
this.el = el;
this.el.innerHTML = this.getRowNum(props);
}
getRowNum(props) {
// paging 처리시 : scroll인 경우 제외
const currentPage = props.grid.getPagination()?._currentPage;
// You can change the number `5` as your perPage option.
return Number(props.formattedValue) + (currentPage - 1) * props.grid.getPagination()?._options?.itemsPerPage;
}
getElement() {
return this.el;
}
render(props) {
this.el.innerHTML = this.getRowNum(props);
}
}
/*****************************************
* Tui-Grid 공식문서
* https://nhn.github.io/tui.grid/latest/
******************************************/
const TuiGrid = {
instance: null,
defaultOptions: {
el: $('#grid'),
//[선택]DataSource 정보(readData|createData|updateData|modifyData|deleteData 등)
data: {
headers: {
AJAX: true
},
withCredentials: false,
initialRequest: false,
api: {
readData: {
contentType: 'application/json',
dataType: 'json',
method: 'get',
initParams: {}
}
, createData: {url: '', method: 'POST'}
, updateData: {url: '', method: 'PUT'}
, modifyData: {url: '', method: 'PUT'}
, deleteData: {url: '', method: 'DELETE'}
}
},
header: {
align:'left', //헤더 좌측정렬
columns: [{name: "_checked", align: "center"}] //헤더 채크박스 가운데정렬
}, //[선택]헤더정보(헤더 명칭 및 매핑 field)
columns: [], //[필수]컬럼정보(헤더 명칭 및 매핑 field)
rowHeaders: [], //[선택]ROW 헤더 타입(rowNum: 순번, checkbox: 체크박스)
bodyHeight: 467, //[선택]Grid 높이 (number(단위: px)|'auto'|'fitToParent')
minBodyHeight: 350, //[선택]Grid 최소 높이 (단위: px)
rowHeight: 50, //[선택]Grid row 높이 (number(단위: px)|'auto' )
minRowHeight: 50, //[선택]Grid row 최소 높이 (단위: px)
pageOptions: {
useClient: false,
page: 1,
perPage: 10
}, //[선택]한 페이지에 출력할 건수
columnOptions: { //[선택]고정 컬럼
//frozenCount: 0 //고정컬럼 갯수
frozenBorderWidth: 2 //고정컬럼 보더(border) 두께
, resizable: true
, minWidth: 100 //최소 사이즈
},
summary: [], //[선택]하단합계
treeColumnOptions: {}, //[선택]tree 구조 grid
},
of: function (options, dataSource, successCallback) {
this.instance = null;
// rowNum fix
if (options.pageOptions?.type !== 'scroll') {
options.rowHeaders.filter((r, idx) => {
if (r === 'rowNum') {
options.rowHeaders[idx] = {
type: 'rowNum',
renderer: {
type: CustomRowNumberRenderer
}
}
}
})
}
const newOptions = $.extend(true, {}, this.defaultOptions, options, {data: dataSource});
newOptions.el = document.getElementById(options.el);
//this.elId = options.el;
// language
tui.Grid.setLanguage('en', {
display: {
noData: options?.noData
}
});
// theme
//tui.Grid.applyTheme('custom', eval(customTheme));
//Grid.applyTheme('custom', customTheme);
Grid.applyTheme('striped', customTheme);
// console.log("newOpt", newOptions);
this.instance = new tui.Grid(newOptions);
this.instance.on('successResponse', function (ev) {
// console.log(`successResponse >>>>>>>>>>>>>>>>>> `,ev);
var msg = JSON.parse(ev.xhr.response).message; //tui-grid 기본 format 메시지
//if($('#totCnt span')){
if (document.getElementById('totCnt')) {
const res = JSON.parse(ev.xhr.response);
// paging
if (res.data.pagination) {
$('#totCnt span').text(res.data.pagination.totalCount);
// no paging
} else {
$('#totCnt span').text(res.count)
}
// console.log('totCnt >>>>> ',$('#totCnt span').text())
}
if (successCallback) successCallback(JSON.parse(ev.xhr.response));
//if(successCallback) successCallback(ev);
});
// 결과가 false인 경우 발생한 경우
this.instance.on('failResponse', function (ev) {
// console.log(`failResponse >>>>>>>>>>>>>>>>>> `,ev);
try {
//tui-grid 기본 format 메시지
const res = JSON.parse(ev.xhr.response);
let msg = '';
if (!(res.message == null || res.message == undefined || res.message == '')) {
alert(res.message);
} else {
alert(res);
}
} catch (e) {
alert("오류가 발생하였습니다.");
// console.error('TuiGrid::failResponse parsing error', e)
}
});
// 오류가 발생한 경우
this.instance.on('errorResponse', function (ev) {
// console.log(`errorResponse >>>>>>>>>>>>>>>>>> `,ev);
try {
//tui-grid 기본 format 메시지
const res = JSON.parse(ev.xhr.response);
if (!(res.message == null || res.message == undefined || res.message == '')) {
alert(res.message);
} else {
alert(res);
}
} catch (e) {
alert("오류가 발생하였습니다.");
// console.error('TuiGrid::errorResponse parsing error', e)
}
});
/**
* grid check box event start
* */
// 체크박스 이벤트
this.instance.on("check", () => {
$("#selectOption").addClass("active");
$("#remove").attr("disabled", false);
$("#sendMessageBtn").removeClass("disabled");
$("#sendMessageBtn").attr("disabled", false);
$("#checkedCount").text(GRID.getCheckedRows().length);
$("#assigns-btn").removeClass("disabled");
$("#assigns-btn").attr("disabled", false);
$("#suspend-btn").attr("disabled", false);
$("#returnBtn").attr("disabled", false);
});
// 체크박스 해제 이벤트
this.instance.on("uncheck", () => {
const checkedCount = GRID.getCheckedRows().length;
if (checkedCount === 0) {
$("#selectOption").removeClass("active");
$("#remove").attr("disabled", true);
$("#sendMessageBtn").addClass("disabled");
$("#sendMessageBtn").attr("disabled", true);
$("#assigns-btn").addClass("disabled");
$("#assigns-btn").attr("disabled", true);
$("#suspend-btn").attr("disabled", true);
$("#returnBtn").attr("disabled", true);
}
$("#checkedCount").text(checkedCount);
});
// 전체 체크박스 이벤트
this.instance.on("checkAll", () => {
$("#selectOption").addClass("active");
$("#remove").attr("disabled", false);
$("#sendMessageBtn").removeClass("disabled");
$("#sendMessageBtn").attr("disabled", false);
$("#checkedCount").text(GRID.getCheckedRows().length);
$("#assigns-btn").removeClass("disabled");
$("#assigns-btn").attr("disabled", false);
$("#suspend-btn").attr("disabled", false);
$("#returnBtn").attr("disabled", false);
});
// 전체 체크박스 해제 이벤트
this.instance.on("uncheckAll", () => {
$("#selectOption").removeClass("active");
$("#remove").attr("disabled", true);
$("#sendMessageBtn").addClass("disabled");
$("#sendMessageBtn").attr("disabled", true);
$("#checkedCount").text(GRID.getCheckedRows().length);
$("#assigns-btn").addClass("disabled");
$("#assigns-btn").attr("disabled", true);
$("#suspend-btn").attr("disabled", true);
$("#returnBtn").attr("disabled", true);
});
/**
* grid check box event end
* */
return this.instance;
},
/** Excel Export */
exportExcel: function (_instance, fileName, rowHeader) {
/* 필수값 설정 */
var _gridId = _instance.el.id;
var _frstColTyp = rowHeader;
var _arrHeader = [];
var _arrName = [];
var _mCustomRenderer = {};
/* =================
* 2021.04.30. 박민규
* 컬럼 취득 방법 변경
* AsIs: GridConfig 설정한 Columns 정보
* ToBe: instance 설정된 Columns 정보
================= */
//2021.04.30. 주석처리
// this.getOptColumns().forEach(function(opt, idx){
_instance.getColumns().forEach(function (opt, idx) {
_arrHeader.push(opt.header);
_arrName.push(opt.name);
//2021.04.30. 주석처리
// if(!fnIsEmpty(opt.renderer)){
if (!(fnIsEmpty(opt.renderer) || 'DefaultRenderer' == opt.renderer.type.name || fnIsEmpty(opt.renderer.type.name))) { //브라우저별 opt.renderer.type.name 값의 차이 => Chrome: "DefaultRenderer", IE: undefined
_mCustomRenderer[opt.name] = opt.renderer;
}
});
/* grid head Setting */
var elementTHEAD = document.createElement('table');
var $sltHeader = $('#' + _gridId + ' .tui-grid-header-area > table.tui-grid-table').clone();
//head 처리(table 고정셀 이용 시)
if ($sltHeader.length > 1) {
//좌측 table의 head Selector
var firstTableTh = $sltHeader.find('tr:last-child th');
//우측 table row의 헤드(th)를 좌측 table row에 병합
for (var i = 1; i < $sltHeader.length; i++) {
$sltHeader.eq(i).find('tr').each(function (idx, row) {
var firstTableRow = $sltHeader.eq(0).find('tr:eq(' + idx + ')');
if (firstTableRow.length == 0) {
//<tr> 신규생성
var tr = document.createElement('tr');
tr.innerHTML = row.innerHTML;
$sltHeader.eq(0).append(tr);
//firstTable <th>의 rowspan 변경
for (var j = 0; j < firstTableTh.length; j++) {
firstTableTh.eq(j).attr('rowspan', Number(firstTableTh.eq(j).attr('rowspan')) + 1);
}
} else {
firstTableRow.append(row.innerHTML);
}
});
}
}
elementTHEAD.innerHTML = $sltHeader.eq(0).find('tbody').html();
/* grid body Setting */
var elementTBODY = document.createElement('tbody');
_instance.getData().forEach(function (row, idxRow) {
var elementTR = document.createElement('tr');
//타입별 <td> element 생성( 첫번째 컬럼타입 설정 시 )
if (!fnIsEmpty(_frstColTyp))
elementTR.appendChild(fnCreateTdByFrstCol(_frstColTyp, row));
//<td> element 생성
_arrName.forEach(function (columnName, idxColumn) {
var columnVal = '';
var customRenderer = _mCustomRenderer[columnName];
if (fnIsEmpty(customRenderer)) {
//column Value 설정
columnVal = fnNvl(row[columnName]);
} else {
//prop 객체 생성
var columnInfo = {};
columnInfo['renderer'] = customRenderer;
columnInfo['name'] = columnName;
var props = {};
props['columnInfo'] = columnInfo;
props['rowKey'] = row.rowKey;
props['grid'] = _instance;
//렌더러 호출
customRenderer.type(props);
//column Value 설정
columnVal = fnNvl(row[columnName]);
if (row.rowKey < 15) {
// // console.log('[export]rowKey->'+row.rowKey+'/ column->'+columnName+' / value->'+row[columnName]);
// // console.log(_instance.getRow(row.rowKey));
}
}
var elementTD = document.createElement('td');
elementTD.innerHTML = columnVal;
elementTR.appendChild(elementTD);
});
elementTBODY.appendChild(elementTR);
});
/* grid table Setting */
var elementTABLE = document.createElement('table');
elementTABLE.appendChild(elementTHEAD);
elementTABLE.appendChild(elementTBODY);
/* export Excel */
var fileUtil = new luluFileExportUtil(fileName, 'table', elementTABLE);
fileUtil.exportExcel();
/* member Function Declare */
//isEmpty Function
function fnIsEmpty(val) {
if (val == undefined || val == null || val == '' || val == {} || val == [])
return true;
return false;
}
//nvl Function
function fnNvl(val, replaceVal) {
replaceVal = fnIsEmpty(replaceVal) ? '' : replaceVal;
val = fnIsEmpty(val) ? replaceVal : val;
return val;
}
//<td> element 생성 Function
function fnCreateTdByFrstCol(colType, row) {
var returnVal = '';
/* column 타입별 value 설정 */
switch (colType) {
case 'rowNum':
returnVal = row._attributes.rowNum;
break;
case 'checkbox':
if (row._attributes.checked)
returnVal = '☑';
else
returnVal = '□';
break;
default:
return returnVal;
break;
}
/* <td> element 생성 */
var elementFrstTD = document.createElement('td');
elementFrstTD.innerHTML = returnVal;
return elementFrstTD;
}
}
}
/**
* Button 렌더러
* -설명: Grid의 cell에 Button을 생성 한다.
* 버튼명칭에 format 사용이 가능하며 사용방법은 아래와 같다.
* ex) value: 'A is {0}. B is {1}. {0}!={1}',
* listColumns: ['컬럼1', '컬럼2']
* @param value 버튼에 출력 명칭
* @param listColumns format에 매칭할 컬럼 목록
* @param callbackFnc 버튼 클릭 호출 함수명
* @author 박민규
* @date 2020.05.28.
*/
var LuluButtonRenderer = function (props) {
// // console.log('XitButtonRenderer Called!!-> '+props.columnInfo.name);
//options get
var opt = props.columnInfo.renderer.options;
var value = opt.value;
var id = opt.id;
var btnClass = opt.btnClass;
var callbackFnc = opt.callbackFnc;
var args = opt.listColumns;
//"value" Formatting
if (args != undefined) {
var row = props.grid.getRow(props.rowKey);
for (var i = 0; i < args.length; i++) {
var regEx = new RegExp('\\{' + i + '\\}', 'gi');
value = value.replace(regEx, row[args[i]]);
}
}
//Element Setting
//23.07.24 최정민 추
//iuput button에서 button으로 수정 (아이콘을 넣기위함)
//el id추가
//버튼에 아이콘을 쓰기위하여 아이콘 클레스명 인자로 받아갈것.
var el = document.createElement('button');
var icon = document.createElement('i');
icon.className = btnClass; // 원하는 아이콘 클래스를 설정하세요.
el.className = btnClass;
el.type = 'button';
el.value = value;
el.id = id;
//EventListener Setting
el.addEventListener('click', function () {
var callback = callbackFnc + '(props)';
eval(callback);
});
//Element render
this.el = el;
// this.render(props);
if (props.rowKey < 15) {
// // console.log('[Renderer]rowKey->'+props.rowKey+' column->'+props.columnInfo.name+' / el.value->'+el.value);
}
//DataSet Injection ( Dataset에 추가해야 "복사(ctrl+c)" 기능 사용 가능 )
props.grid.setValue(props.rowKey, props.columnInfo.name, el.value, false);
}
LuluButtonRenderer.prototype.getElement = function () {
return this.el;
}
LuluButtonRenderer.prototype.render = function (props) {
this.el.value = props.value;
}
/**
* column 병합 렌더러
* -설명: 다수의 Column을 하나의 Column으로 병합하여 cell에 출력 한다.
* 필요 format 사용이 가능하며 사용방법은 아래와 같다. (포맷 사용 "구분자(separator)" 적용되지 않는다)
* [단순 컬럼 병합]
* ex) listColumns: ['컬럼1', '컬럼2'],
* separator: '/'
* [포맷사용 컬럼 병합]
* ex) listColumns: ['컬럼1', '컬럼2'],
* format: 'A is {0}. B is {1}. {0}!={1}'
* @param listColumns 병합할 컬럼 name 목록
* @param separator 컬럼 연결 구분자(default: 공백(' '))
* @param format 출력 포맷
* @author 박민규
* @date 2020.05.28.
*/
var XitColumnMergeRenderer = function (props) {
// // console.log('XitColumnMergeRenderer Called!!-> '+props.columnInfo.name);
//options get
var opt = props.columnInfo.renderer.options;
var args = opt.listColumns;
var separator = opt.separator;
if (fnIsEmpty(separator))
separator = ' ';
var value = opt.format;
//Columns merge
if (fnIsEmpty(value)) {
var arrStr = [];
var row = props.grid.getRow(props.rowKey);
args.forEach(function (column) {
arrStr.push(row[column]);
});
value = arrStr.join(separator);
} else {
var row = props.grid.getRow(props.rowKey);
for (var i = 0; i < args.length; i++) {
var regEx = new RegExp('\\{' + i + '\\}', 'gi');
value = value.replace(regEx, fnNvl(row[args[i]]));
}
}
function fnNvl(val) {
if (val == undefined || val == null)
return '';
return val;
}
function fnIsEmpty(val) {
if (val == undefined || val == null || val == '' || val == [] || val == {})
return true;
return false;
}
//Element Setting
var el = document.createElement('div');
el.setAttribute('class', 'tui-grid-cell-content');
el.innerHTML = value;
//Element render
this.el = el;
// this.render(props);
if (props.rowKey < 15) {
// // console.log('[Renderer]rowKey->'+props.rowKey+' column->'+props.columnInfo.name+' / el.value->'+el.innerHTML);
}
//DataSet Injection ( Dataset에 추가해야 "복사(ctrl+c)" 기능 사용 가능 )
props.grid.setValue(props.rowKey, props.columnInfo.name, el.innerHTML, false);
}
XitColumnMergeRenderer.prototype.getElement = function () {
return this.el;
}
XitColumnMergeRenderer.prototype.render = function (props) {
this.el.value = props.value;
}
/**
* Button 렌더러
* @author 최유수
* @date 2020.07.15.
* formatter : 안에 입력될 문자열.
,eventType : 셀에 이벤트.
,eventFunction : 이벤트에 부여될 함수.
element 속성
element : 생성 엘리먼트 속성
type : 생성 엘리먼트의 타입
value : 들어갈 텍스트
*/
/**
* 요구사항
* 속성을 부여할때
*
* 1.셀에 개발자가 원하는 Element를 생성 있음
* 2.1 속성에 개발자가 원하는 이벤트를 부여 있음
* 3. 2 이벤트에 개발자가 원하는 함수를 부여 있음
* 4. 개발자가 원하는만큼 유형을 추가 있음.(유형이 여러개일수도 아닐수도 있음)
*/
var CustomButtonRenderer = function (props) {
//options get
var opt = props.columnInfo.renderer.options;
//formatter를 구성할 인자값(해당 인자값은 함수형일때 배열일 수 있음.)
var formatter = opt.formatter;
//이벤트로 사용할 함수
var eventFunction = opt.eventFunction;
//이벤트 속성
var eventType = opt.eventType;
//객체 유형
var element = opt.element;
//객체 속성
var type = opt.type;
var formattObject = formatter;
if (typeof formatter == "function") {
//formatter가 함수에 의해 구현이됨, Return값은 Arr<Object>, Objcect임;
//Object는 formatter : Text 와 Element로 구성됨.
formattObject = formatter(props);
} else {
//formatter가 단순 텍스트임.
formattObject = opt;
}
var ele = cellMaker(formattObject, eventFunction, eventType, element, type, props);
this.el = ele;
}
CustomButtonRenderer.prototype.getElement = function () {
return this.el;
}
//셀을 만듦.
var cellMaker = function (formattObject, eventFunction, eventType, element, type, props) {
var spanEle = document.createElement("span");
var ele;
//셀에 1개 이상의 데이터가 들어가는 경우
if (formattObject instanceof Array && formattObject.length != 0) {
formattObject.forEach(function (elementObject, index, arrays) {
elementObject["eventFunction"] = eventFunction;
elementObject["eventType"] = eventType;
ele = elMaker(elementObject, props);
$(spanEle).append(ele);
//객체가 다수이면 띄어쓰기 함
if (index < formattObject.length - 1) {
$(spanEle).append(document.createTextNode('\u00a0 // \u00a0'));
}
});
}
//셀에 데이터가 1개이지만 함수로 Obj를 가져와서 만듦
if (formattObject instanceof Object) {
formattObject["eventFunction"] = eventFunction;
formattObject["eventType"] = eventType;
ele = elMaker(formattObject, props);
spanEle.appendChild(ele);
}
return spanEle;
}
var elMaker = function (elementObject, props) {
var text = elementObject.formatter
var eventType = elementObject.eventType
var element = elementObject.element
var eventFunction = elementObject.eventFunction
var type = elementObject.type
var ele;
if (element == "text") {
ele = document.createElement("p");
ele.style.whiteSpace = 'nowrap';
ele.style.display = 'inline-block';
ele.style.fontSize = '11px';
ele.style.fontFamily = 'Nanum Barun Gothic';
ele.appendChild(document.createTextNode(text));
} else {
ele = document.createElement(element);
ele.type = type;
ele.value = text;
}
//이벤트가 함수이면 이벤트 부여함.
if (typeof eventFunction == "function") {
ele.addEventListener(eventType, function (event) {
eventFunction(props, event)
});
}
return ele;
}
Loading…
Cancel
Save