Merge branch 'dev-feat-filedownload'

main
Jonguk. Lim 2 years ago
commit 36edd62c63

@ -10,12 +10,12 @@ import kr.xit.fims.biz.ec.model.CtznStmtDTO;
@Mapper @Mapper
public interface IEcCtznSttemntMapper { public interface IEcCtznSttemntMapper {
int insertEcCtznSttemnt(final CtznStmtDTO dto);
List<CtznStmtDTO> selectEcCtznSttemnts(Map<String, Object> paraMap, RowBounds rowBounds); List<CtznStmtDTO> selectEcCtznSttemnts(Map<String, Object> paraMap, RowBounds rowBounds);
CtznStmtDTO selectEcCtznSttemnt(final CtznStmtDTO.Request dto);
List<CtznStmtDTO.CtznStmtDtl> selectEcCtznSttemntDetails(final CtznStmtDTO.Request dto);
CtznStmtDTO.CtznStmtDtl selectEcCtznSttemntDetail(final CtznStmtDTO.Request dto);
int insertEcCtznSttemnt(final CtznStmtDTO dto);
int insertEcCtznSttemntDetail(final CtznStmtDTO.CtznStmtDtl dtl); int insertEcCtznSttemntDetail(final CtznStmtDTO.CtznStmtDtl dtl);
} }

@ -208,4 +208,15 @@ public class CtznStmtDTO implements Serializable { //extends ExtlEsbDataType {
private Set<Integer> indexs = new HashSet<>(); private Set<Integer> indexs = new HashSet<>();
} }
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public static class Request {
private String ctznSttemntDetailSn;
private String interfaceSeqN;
}
} }

@ -0,0 +1,56 @@
package kr.xit.fims.biz.ec.service;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import kr.xit.fims.biz.ec.mapper.IEcCtznSttemntMapper;
import kr.xit.fims.biz.ec.model.CtznStmtDTO;
import kr.xit.framework.biz.cmm.service.ICmmFileService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@Service
public class EcCtznSttemntService implements IEcCtznSttemntService {
@Value("#{prop['file.upload.root']}")
private String uploadRoot;
@Value("#{prop['file.upload.natl-newspaper.path']}")
private String uploadNewsPaperPath;
private final IEcCtznSttemntMapper mapper;
private final ICmmFileService cmmFileService;
@Override
@Transactional(readOnly = true)
public List<CtznStmtDTO> findCtznStmts(final Map<String, Object> paraMap, final RowBounds rowBounds) {
return mapper.selectEcCtznSttemnts(paraMap, rowBounds);
}
@Override
@Transactional(readOnly = true)
public CtznStmtDTO findCtznStmt(final CtznStmtDTO.Request dto) {
return mapper.selectEcCtznSttemnt(dto);
}
@Override
@Transactional(readOnly = true)
public List<CtznStmtDTO.CtznStmtDtl> findCtznStmtDtls(final CtznStmtDTO.Request dto) {
return mapper.selectEcCtznSttemntDetails(dto);
}
@Override
@Transactional(readOnly = true)
public CtznStmtDTO.CtznStmtDtl findCtznStmtDtl(final CtznStmtDTO.Request dto) {
return mapper.selectEcCtznSttemntDetail(dto);
}
}

@ -0,0 +1,25 @@
package kr.xit.fims.biz.ec.service;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.session.RowBounds;
import org.apache.poi.ss.formula.functions.T;
import kr.xit.fims.biz.ec.model.CtznStmtDTO;
import kr.xit.framework.core.utils.XitCmmnUtil;
public interface IEcCtznSttemntService {
List<CtznStmtDTO> findCtznStmts(final Map<String, Object> paraMap, final RowBounds rowBounds);
CtznStmtDTO findCtznStmt(final CtznStmtDTO.Request reqDTO);
List<CtznStmtDTO.CtznStmtDtl> findCtznStmtDtls(final CtznStmtDTO.Request reqDTO);
CtznStmtDTO.CtznStmtDtl findCtznStmtDtl(final CtznStmtDTO.Request reqDTO);
default String getUserUniqId(){
return XitCmmnUtil.getUserUniqId();
}
}

@ -0,0 +1,86 @@
package kr.xit.fims.biz.ec.web;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import kr.xit.fims.biz.FimsConst;
import kr.xit.fims.biz.ec.model.CtznStmtDTO;
import kr.xit.fims.biz.ec.service.IEcCtznSttemntService;
import kr.xit.framework.biz.cmm.model.CmmFileDTO;
import kr.xit.framework.biz.cmm.service.ICmmFileService;
import kr.xit.framework.core.model.ResultResponse;
import kr.xit.framework.support.mybatis.MybatisUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RequiredArgsConstructor
@Controller
@RequestMapping(value = "/fims/biz/ec")
public class EcCtznSttemntController {
@Value("#{prop['file.rcv.root']}")
private String fileRcvRoot;
@Value("#{prop['file.rcv.natl-newspaper.path']}")
private String rcvNatlNewspaperPath;
private final IEcCtznSttemntService service;
private final ICmmFileService fileService;
@RequestMapping(value = "/ecCtznSttemntMgtForm")
public void ecCtznSttemntMgtForm(){
}
@RequestMapping(value = "/ecCtznSttemntMgtPopup")
@ResponseBody
public ModelAndView ecCtznSttemntMgtPopup(final String interfaceSeqN){
ModelAndView mav = new ModelAndView(FimsConst.FIMS_JSP_BASE_PATH +"ec/ecCtznSttemntMgtPopup.popup");
CtznStmtDTO.Request reqDTO = CtznStmtDTO.Request.builder()
.interfaceSeqN(interfaceSeqN)
.ctznSttemntDetailSn("01").build();
mav.addObject("interfaceSeqN", interfaceSeqN);
mav.addObject("ctznStmtDTO", service.findCtznStmt(reqDTO));
mav.addObject("ctznStmtDtlDTOs", service.findCtznStmtDtls(reqDTO));
return mav;
}
@GetMapping(value = "/findCtznStmts")
public ModelAndView findCtznStmts(@RequestParam final Map<String,Object> paraMap) {
return ResultResponse.of(service.findCtznStmts(paraMap, MybatisUtils.getPagingInfo(paraMap)));
}
@RequestMapping (value = "/findCtznStmtDtls")
public ModelAndView findCtznStmtDtls(final CtznStmtDTO.Request dto) {
return ResultResponse.of(service.findCtznStmtDtls(dto));
}
@GetMapping(value = "/findCtznStmtDtl")
public ModelAndView findCtznStmtDtl(final CtznStmtDTO.Request dto) {
return ResultResponse.of(service.findCtznStmtDtl(dto));
}
@RequestMapping("/findCtznStmtAttchFiles")
public ModelAndView findCtznStmtAttchFiles(final CtznStmtDTO.Request dto) {
return ResultResponse.of(
fileService.findFilesByEsbInterfaces(
CmmFileDTO.FileMst.builder()
.jobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode())
.fileJobId(dto.getInterfaceSeqN()+dto.getCtznSttemntDetailSn())
.build()).stream()
.filter(dtl -> !Objects.equals("png", dtl.getFileExtsn()))
.collect(Collectors.toList())
);
}
}

@ -1,8 +1,11 @@
package kr.xit.fims.biz.ec.web; package kr.xit.fims.biz.ec.web;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
@ -53,10 +56,11 @@ public class EcNatlNewspaperController {
return mav; return mav;
} }
@RequestMapping(value = "/ecNatlNewspaperMgtPopup") @RequestMapping(value = "/ecNatlNewspaperPopup")
public ModelAndView ecNatlNewspaperMgtPopup(final String interfaceSeqN){ public ModelAndView ecNatlNewspaperMgtPopup(final String interfaceSeqN){
ModelAndView mav = new ModelAndView(FimsConst.FIMS_JSP_BASE_PATH +"ec/ecNatlNewspaperMgtPopup.popup"); ModelAndView mav = new ModelAndView(FimsConst.FIMS_JSP_BASE_PATH +"ec/ecNatlNewspaperPopup.popup");
mav.addObject("rcvXmlDTO", service.findEsbInterface(interfaceSeqN)); mav.addObject("rcvXmlDTO", service.findEsbInterface(interfaceSeqN));
mav.addObject("interfaceSeqN", interfaceSeqN);
mav.addObject("attchFiles", fileService.findFilesByEsbInterfaces( mav.addObject("attchFiles", fileService.findFilesByEsbInterfaces(
CmmFileDTO.FileMst.builder() CmmFileDTO.FileMst.builder()
.jobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode()) .jobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode())
@ -89,4 +93,17 @@ public class EcNatlNewspaperController {
public ModelAndView findNatlNewspaers(@RequestParam final Map<String,Object> paraMap) { public ModelAndView findNatlNewspaers(@RequestParam final Map<String,Object> paraMap) {
return ResultResponse.of(service.findEsbInterfaces(paraMap, MybatisUtils.getPagingInfo(paraMap))); return ResultResponse.of(service.findEsbInterfaces(paraMap, MybatisUtils.getPagingInfo(paraMap)));
} }
@RequestMapping("/findNatlNewspaperAttchFiles")
public ModelAndView findNatlNewspaperAttchFiles(final String interfaceSeqN) {
return ResultResponse.of(
fileService.findFilesByEsbInterfaces(
CmmFileDTO.FileMst.builder()
.jobSeCode(FimsConst.FileJobSeCode.NATL_NEWS_PAPER_RCV.getCode())
.fileJobId(interfaceSeqN)
.build()).stream()
.filter(dtl -> !Objects.equals("png", dtl.getFileExtsn()))
.collect(Collectors.toList())
);
}
} }

@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.Mapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -91,16 +92,19 @@ public class CmmFileMgtController {
download(absFile, "", response); download(absFile, "", response);
} }
@GetMapping("/download") @RequestMapping("/download")
public void download(final String scDatagb, final Long scCode, String methodName, HttpServletResponse response) { public void download(final CmmFileDTO.FileDtl fileDtlDTO, HttpServletResponse response) {
StringBuilder sb = new StringBuilder();
String absFile = ""; sb.append(uploadRoot)
.append(fileDtlDTO.getFileCours())
.append("/")
.append(fileDtlDTO.getFileId());
download(absFile, "", response); download(sb.toString(), fileDtlDTO.getOrginlFileNm(), response);
} }
private void download(String absFile, String fileName, HttpServletResponse response) { private void download(String absFile, String fileName, HttpServletResponse response) {

@ -160,12 +160,6 @@ public class ResultResponse<T> implements IResponse, Serializable {
return mav; return mav;
} }
// public static <T> ResponseEntity<? extends IResponse> of(HttpStatus httpStatus){
// ResultResponse result = new ResultResponse();
// result.message = new ResultMessage(SUCCESS, SUCCESS_MESSAGE);
// return new ResponseEntity<>(result, httpStatus);
// }
@Override @Override
public String toString() { public String toString() {
GsonBuilder builder = new GsonBuilder().serializeNulls(); // value가 null값인 경우도 생성 GsonBuilder builder = new GsonBuilder().serializeNulls(); // value가 null값인 경우도 생성

@ -6,7 +6,7 @@
<!-- ************************************************************************************************************* <!-- *************************************************************************************************************
* tb_ec_ctzn_sttemnt : 시민신고 마스터 * tb_ec_ctzn_sttemnt : 시민신고 마스터
************************************************************************************************************** --> ************************************************************************************************************** -->
<sql id="sqlEcCtznSttemnt"> <sql id="sqlEcCtznStmt">
SELECT interface_seq_n SELECT interface_seq_n
, instt_code , instt_code
, sys_code , sys_code
@ -34,7 +34,13 @@
<select id="selectEcCtznSttemnts" resultType="kr.xit.fims.biz.ec.model.CtznStmtDTO"> <select id="selectEcCtznSttemnts" resultType="kr.xit.fims.biz.ec.model.CtznStmtDTO">
/* ec-ctzn-sttemnt-mysql-mapper|selectEcCtznSttemnts-민원연계(국민신문고) 목록 조회|julim */ /* ec-ctzn-sttemnt-mysql-mapper|selectEcCtznSttemnts-민원연계(국민신문고) 목록 조회|julim */
<include refid="sqlEcCtznSttemnt"/> <include refid="sqlEcCtznStmt"/>
</select>
<select id="selectEcCtznSttemnt" resultType="kr.xit.fims.biz.ec.model.CtznStmtDTO">
/* ec-ctzn-sttemnt-mysql-mapper|selectEcCtznSttemnt-민원연계(국민신문고) 조회|julim */
<include refid="sqlEcCtznStmt"/>
WHERE interface_seq_n = #{interfaceSeqN}
</select> </select>
<insert id="insertEcCtznSttemnt"> <insert id="insertEcCtznSttemnt">
@ -91,8 +97,41 @@
<!-- ************************************************************************************************************* <!-- *************************************************************************************************************
* tb_ec_ctzn_sttemnt_detail : 시민신고 상세 * tb_ec_ctzn_sttemnt_detail : 시민신고 상세
************************************************************************************************************** --> ************************************************************************************************************** -->
<sql id="sqlEcCtznStmtDtl">
SELECT interface_seq_n
, ctzn_sttemnt_detail_sn
, vhcle_no
, reglt_id
, instt_code
, sys_code
, reglt_de_time
, reglt_place
, violt_dtls_nm
, gps_x
, gps_y
, ctzn_sttemnt_detail_process_sttus
, regist_dt
, register
, updt_dt
, updusr
FROM tb_ec_ctzn_sttemnt_detail
</sql>
<select id="selectEcCtznSttemntDetails" resultType="kr.xit.fims.biz.ec.model.CtznStmtDTO$CtznStmtDtl">
/* ec-ctzn-sttemnt-mysql-mapper|selectEcCtznSttemntDetails-민원연계(국민신문고) 상세(목록) 조회|julim */
<include refid="sqlEcCtznStmtDtl"/>
WHERE interface_seq_n = #{interfaceSeqN}
<if test='ctznSttemntDetailSn != null and ctznSttemntDetailSn != ""'>
AND ctzn_sttemnt_detail_sn = #{ctznSttemntDetailSn}
</if>
</select>
<select id="selectEcCtznSttemntDetail" resultType="kr.xit.fims.biz.ec.model.CtznStmtDTO$CtznStmtDtl">
/* ec-ctzn-sttemnt-mysql-mapper|selectEcCtznSttemntDetail-민원연계(국민신문고) 상세 조회|julim */
<include refid="sqlEcCtznStmtDtl"/>
WHERE interface_seq_n = #{interfaceSeqN}
AND ctzn_sttemnt_detail_sn = #{ctznSttemntDetailSn}
</select>
<insert id="insertEcCtznSttemntDetail"> <insert id="insertEcCtznSttemntDetail">
/* ec-ctzn-sttemnt-mysql-mapper|insertEcCtznSttemntDetail-시민신고 상세 등록|julim */ /* ec-ctzn-sttemnt-mysql-mapper|insertEcCtznSttemntDetail-시민신고 상세 등록|julim */

@ -124,15 +124,15 @@ exception.log.trace=true
# k byte # k byte
file.upload.allow.max-size: 2048 file.upload.allow.max-size: 2048
file.upload.allow.ext= file.upload.allow.ext=
#file.upload.root=/Users/minuk/data/fims/upload file.upload.root=/Users/minuk/data/fims/upload
file.upload.root=D:/data/fims/upload #file.upload.root=D:/data/fims/upload
#file.upload.root=http://211.119.124.107:280/fimsdata/upload #file.upload.root=http://211.119.124.107:280/fimsdata/upload
file.upload.natl-newspaper.path=/natl-newspaper file.upload.natl-newspaper.path=/natl-newspaper
#file.rcv.root=/Users/minuk/data/fims/rcv #file.rcv.root=/Users/minuk/data/fims/rcv
#file.rcv.natl-newspaper.path=/natl-newspaper/rcv #file.rcv.natl-newspaper.path=/natl-newspaper/rcv
#file.rcv.root=/Users/minuk/project_data/fims/01. \uC5F0\uACC4/01. \uAD6D\uBBFC\uC2E0\uBB38\uACE0 file.rcv.root=/Users/minuk/project_data/fims/01. \uC5F0\uACC4/01. \uAD6D\uBBFC\uC2E0\uBB38\uACE0
file.rcv.root=D:/project_data/fims/01. \uC5F0\uACC4/01. \uAD6D\uBBFC\uC2E0\uBB38\uACE0 #file.rcv.root=D:/project_data/fims/01. \uC5F0\uACC4/01. \uAD6D\uBBFC\uC2E0\uBB38\uACE0
#file.rcv.root=http://211.119.124.107:280/fimsdata/rcv #file.rcv.root=http://211.119.124.107:280/fimsdata/rcv
#file.rcv.natl-newspaper.path=/natl-newspaper #file.rcv.natl-newspaper.path=/natl-newspaper
file.rcv.natl-newspaper.path=/rcv file.rcv.natl-newspaper.path=/rcv

@ -0,0 +1,222 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ include file="/WEB-INF/jsp/framework/taglibs.jsp" %>
<form id="frmSearch" name="frmSearch">
<div class="search r2">
<table>
<caption>검색조건</caption>
<colgroup>
<col style="width: 400px;"/>
<col/>
<col/>
<col/>
<col/>
<col/>
<col/>
</colgroup>
<tbody>
<tr>
<td colspan="5">
<label for="sysCode">시스템 구분</label>
<code:select codeId="FIM001" defaultSelect="PVS" id="sysCode" name="sysCode" title="시스템구분" cls="selectBox" alt="selectBox tag" disabled="true"/>
</td>
<td colspan="6">
<input type="button" id="btnSearch" class="btn_search" title="검색" value="검색"/>
</td>
</tr>
</tbody>
</table>
</div>
</form>
<!-- //검색 -->
<div class="page_btn">
<span class="fll">
<div class="list clearfix" id="totCnt">전체 ㅣ <span></span></div>
</span>
<span class="flr">
<a href="#" class="btn darkgray" id="btnRegist" title="템플릿 등록">등록</a>
</span>
</div>
<!-- //버튼 및 페이지정보 -->
<!-- 데이터 출력 -->
<div id="grid"></div>
<script type="text/javaScript">
/**************************************************************************
* Global Variable
**************************************************************************/
let GRID = null;
var callbackSearch = () => fnBiz.search();
/* *******************************
* Biz function
******************************* */
const fnBiz = {
search: () => {
GRID.reloadData();
}
,pagePopup: function(flag, params){
let url;
let popTitle;
let popOption;
switch (flag) {
case "detail":
url = '<c:url value="/fims/biz/ec/ecCtznSttemntMgtPopup.do"/>';
popOption = {width: 1000, height:900};
popTitle = "주민 신고 데이타 상세";
break;
case "file":
url = '<c:url value="/fims/biz/ec/ecNatlNewspaperFileSelPopup.do"/>';
popOption = {width: 900, height:750};
popTitle = "주민 신고 데이타 선택";
break;
default:
break;
}
CmmPopup.open(url, params, popOption, popTitle);
}
,onClickGrid: function(props){
const rowData = props.grid.getRow(props.rowKey);
fnBiz.pagePopup('detail', {interfaceSeqN: rowData.interfaceSeqN});
}
// ,onSearchChange: () => {
// if($('#searchCondition').val() === 'tmplatSeCode'){
// $('select[name="searchKeyword"]')
// .css('display', 'inline-block')
// .attr('disabled', false);
// $('input[name="searchKeyword"]')
// .css('display', 'none')
// .attr('disabled', true);
// }else{
// $('select[name="searchKeyword"]')
// .css('display', 'none')
// .attr('disabled', true);
// $('input[name="searchKeyword"]')
// .css('display', 'inline-block')
// .attr('disabled', false);
// }
// }
}
/**************************************************************************
* event
**************************************************************************/
$(() => {
$('#btnSearch').on('click', () => fnBiz.search());
$('#btnRegist').on('click', () => {
fnBiz.pagePopup('file');
});
});
/* *******************************
* Grid
******************************* */
const initGrid = () => {
const gridColumns = [
{
header: '인터페이스키',
name: 'interfaceSeqN',
minWidth: 200,
sortable: true,
sortingType: 'desc',
align: 'center',
renderer: {
type: CustomButtonRenderer,
options: {
formatter: (props)=>{
return {
formatter: props.grid.getRow(props.rowKey).interfaceSeqN
,element: "text"
}
}
,eventFunction: fnBiz.onClickGrid
,eventType: "click"
}
}
},
{
header: '기관코드',
name: 'insttCode',
width: 80,
sortable: false,
align: 'center'
},
{
header: '시스템코드',
name: 'sysCode',
minWidth: 200,
sortable: false,
align: 'center'
},
{
header: '민원구분',
name: 'cvplSe',
minWidth: 200,
sortable: false,
align: 'center'
},
{
header: '민원신청번호',
name: 'cvplReqstNo',
minWidth: 200,
sortable: false,
align: 'center'
},
{
header: '민원접수번호',
name: 'cvplRceptNo',
minWidth: 200,
sortable: false,
align: 'center'
},
{
header: '시민신고처리상태',
name: 'ctznSttemntProcessSttus',
minWidth: 200,
sortable: false,
align: 'center'
},
{
header: '민원등록일시',
name: 'cpvlRegistDt',
width: 160,
sortable: false,
align: 'center',
formatter({value}){
return setDateTimeFmt(value);
}
}
];
const gridOptions = {
el: 'grid',
rowHeaders: ['rowNum'],
columns: gridColumns
};
const gridDatasource = {
initialRequest: true, // 화면 load시 조회 안함 - default
api: {
readData: {
url: '<c:url value="/fims/biz/ec/findCtznStmts.do"/>'
,serializer: (params) => fnAddPageInfo(document.frmSearch, params)
}
}
};
GRID = TuiGrid.of(gridOptions, gridDatasource, (res) => {
});
}
/**************************************************************************
* initialize
**************************************************************************/
$(document).ready(function(){
initGrid();
});
</script>

@ -0,0 +1,313 @@
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ include file="/WEB-INF/jsp/framework/taglibs.jsp" %>
<c:set var="bizName" value="주민 신고 데이타"/>
<div class="popup" style="min-width: 100px;">
<div class="popup_inner" style="max-width: 1000px;">
<p class="pop_title">
<c:out value="${bizName}"/>(<c:out value="${ctznStmtDTO.interfaceSeqN}"/>) 상세
</p>
<form name="frmStmt">
<table class="tbl03">
<caption><c:out value="${bizName}"/> 상세</caption>
<colgroup>
<col style="width: 13%;"/>
<col style="width: 20%;"/>
<col style="width: 13%;"/>
<col style="width: 20%;"/>
<col style="width: 13%;"/>
<col style="width: 20%;"/>
</colgroup>
<tbody>
<tr>
<th>기관코드</th>
<td>
<input type="text" name="insttCode" value='<c:out value="${ctznStmtDTO.insttCode}"/>' readonly>
</td>
<th>시스템코드</th>
<td>
<input type="text" name="sysCode" value='<c:out value="${ctznStmtDTO.sysCode}"/>' readonly>
</td>
<th>민원구분</th>
<td>
<input type="text" name="cvplSe" value='<c:out value="${ctznStmtDTO.cvplSe}"/>' readonly>
</td>
</tr>
<tr>
<th>민원신청번호</th>
<td>
<input type="text" name="petiAncCodeV" value='<c:out value="${ctznStmtDTO.cvplReqstNo}"/>' readonly>
</td>
<th>민원접수번호</th>
<td>
<input type="text" name="petiNoC" value='<c:out value="${ctznStmtDTO.cvplRceptNo}"/>' readonly>
</td>
<th>민원신청인명</th>
<td>
<input type="text" name="civilNoC" value='<c:out value="${ctznStmtDTO.cvplApplcntNm}"/>' readonly>
</td>
</tr>
<tr>
<th>신청인이메일</th>
<td>
<input type="text" name="peterNameV" value='<c:out value="${ctznStmtDTO.cvplApplcntEmailAdres}"/>' readonly>
</td>
<th>신청인우편번호</th>
<td>
<input type="text" name="zipCodeC" value='<c:out value="${ctznStmtDTO.cvplApplcntZip}"/>' readonly>
</td>
<th>신청인주소</th>
<td>
<input type="text" name="addressV" value='<c:out value="${ctznStmtDTO.cvplApplcntAdres}"/>' readonly>
</td>
</tr>
<tr>
<th>신청인핸드폰</th>
<td>
<input type="text" name="celNoV" value='<c:out value="${ctznStmtDTO.cvplApplcntMoblphonNo}"/>' readonly>
</td>
<th>신청인전화</th>
<td>
<input type="text" name="telNoV" value='<c:out value="${ctznStmtDTO.cvplApplcntTlphonNo}"/>' readonly>
</td>
<th>처리상태</th>
<td>
<input type="text" name="telNoV" value='<c:out value="${ctznStmtDTO.ctznSttemntProcessSttus}"/>' readonly>
</td>
</tr>
<tr>
<th>민원신청제목</th>
<td colspan="5">
<input type="text" name="petiTitleV" value='<c:out value="${ctznStmtDTO.cvplReqstSj}"/>' readonly>
</td>
</tr>
<tr>
<th>민원신청내용</th>
<td colspan="5">
<textarea name="petiReasonL" rows="10" readonly><c:out value="${ctznStmtDTO.cvplReqstCn}" /></textarea>
</td>
</tr>
<tr>
<th>민원신청일시</th>
<td>
<fmt:parseDate value="${ctznStmtDTO.cvplRceptDt}" var="cvplRceptDt" pattern="yyyyMMddHHmmss"/>
<input type="text" name="cvplRceptDt" value='<fmt:formatDate value="${cvplRceptDt}" pattern="yyyy-MM-dd HH:mm:ss"/>' readonly>
</td>
<th>민원처리기간</th>
<td>
<fmt:parseDate value="${ctznStmtDTO.cvplProcessPd}" var="cvplProcessPd" pattern="yyyyMMddHHmmss"/>
<input type="text" name="cvplProcessPd" value='<fmt:formatDate value="${cvplProcessPd}" pattern="yyyy-MM-dd HH:mm:ss"/>' readonly>
</td>
<th>민원등록일시</th>
<td>
<fmt:parseDate value="${ctznStmtDTO.cvplRegistDt}" var="cvplRegistDt" pattern="yyyyMMddHHmmss"/>
<input type="text" name="cvplRegistDt" value='<fmt:formatDate value="${cvplRegistDt}" pattern="yyyy-MM-dd HH:mm:ss"/>' readonly>
</td>
</tr>
<%-- <tr>--%>
<%-- <th>등록일시</th>--%>
<%-- <td>--%>
<%-- <fmt:parseDate value="${ctznStmtDTO.registDt}" var="registDt" pattern="yyyyMMddHHmmss"/>--%>
<%-- <input type="text" name="registDt" value="<fmt:formatDate value="${registDt}" pattern="yyyy-MM-dd HH:mm:ss"/>' readonly>--%>
<%-- </td>--%>
<%-- <th>등록인</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="sendYnC" value='<c:out value="${ctznStmtDTO.register}"/>' readonly>--%>
<%-- </td>--%>
<%-- </tr>--%>
</tbody>
</table>
</form>
<form name="frmStmtDtl">
<table class="tbl03">
<caption><c:out value="${bizName}"/> 상세</caption>
<colgroup>
<col style="width: 13%;"/>
<col style="width: 20%;"/>
<col style="width: 13%;"/>
<col style="width: 20%;"/>
<col style="width: 13%;"/>
<col style="width: 20%;"/>
</colgroup>
<tbody>
<%-- <c:forEach var="dtlDTO" items="ctznStmtDtlDTOs">--%>
<tr>
<th>차량번호</th>
<td>
<%-- <c:out value="${dtlDTO.vhcleNo}"/>--%>
<%-- <input type="text" name="vhcleNo" value="${dtlDTO.vhcleNo}" readonly>--%>
<%-- <input type="text" name="vhcleNo" value='<c:out value="${dtlDTO.vhcleNo}"/>' readonly>--%>
</td>
<th>순번</th>
<td>
<%-- <input type="text" name="ctnzSttemntDetailSn" value='<c:out value="${dtlDTO.ctnzSttemntDetailSn}"/>' readonly>--%>
<%-- <input type="text" name="ctnzSttemntDetailSn" value='jjj' readonly>--%>
</td>
<%-- <th>단속ID</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="cvplSe" value='<c:out value="${dtlDTO.regltId}"/>' readonly>--%>
<%-- </td>--%>
</tr>
<%-- </c:forEach>--%>
<%-- <tr>--%>
<%-- <th>단속일시</th>--%>
<%-- <td>--%>
<%-- <fmt:parseDate value="${dtlDTO.regltDeTime}" var="regltDeTimet" pattern="yyyyMMddHHmmss"/>--%>
<%-- <input type="text" name="regltDeTime" value="<fmt:formatDate value="${regltDeTime}" pattern="yyyy-MM-dd HH:mm:ss"/>' readonly>--%>
<%-- </td>--%>
<%-- <th>단속장소</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="petiNoC" value='<c:out value="${dtlDTO.regltPlace}"/>' readonly>--%>
<%-- </td>--%>
<%-- <th>위반내역</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="civilNoC" value='<c:out value="${dtlDTO.violtDtlsNm}"/>' readonly>--%>
<%-- </td>--%>
<%-- </tr>--%>
<%-- <tr>--%>
<%-- <th>GPS위도</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="peterNameV" value='<c:out value="${dtlDTO.gpsX}"/>' readonly>--%>
<%-- </td>--%>
<%-- <th>GPS경도</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="zipCodeC" value='<c:out value="${dtlDTO.gpsY}"/>' readonly>--%>
<%-- </td>--%>
<%-- <th>기관코드</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="addressV" value='<c:out value="${dtlDTO.insttCode}"/>' readonly>--%>
<%-- </td>--%>
<%-- </tr>--%>
<%-- <tr>--%>
<%-- <th>과태료코드</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="celNoV" value='<c:out value="${dtlDTO.sysCode}"/>' readonly>--%>
<%-- </td>--%>
<%-- <th>처리상태</th>--%>
<%-- <td>--%>
<%-- <input type="text" name="telNoV" value='<c:out value="${dtlDTO.ctznSttemntDetailProcessSttus}"/>' readonly>--%>
<%-- </td>--%>
<%-- <th>등록일시</th>--%>
<%-- <td>--%>
<%-- <fmt:parseDate value="${dtlDTO.registDt}" var="registDt" pattern="yyyyMMddHHmmss"/>--%>
<%-- <input type="text" name="registDt" value="<fmt:formatDate value="${registDt}" pattern="yyyy-MM-dd HH:mm:ss"/>' readonly>--%>
<%-- </td>--%>
<%-- </tr>--%>
</tbody>
</table>
</form>
<div id="ctznImg"></div>
<div class="popup_btn">
<span class="flr p_flr">
<a href="#" class="btn lightgray" onclick="window.close()">닫기</a>
</span>
</div>
<!-- //등록버튼 -->
</div>
</div>
<!-- //popup -->
<script type="text/javascript">
/**************************************************************************
* Global Variable
**************************************************************************/
// let orgData;
/* *******************************
* Biz function
******************************* */
const fnBiz = {
downloadImg: () => {
//ctznStmtDtlDTO
cmmAjax({
showSuccessMsg: false
,url: '<c:url value="/fims/biz/ec/findNatlNewspaperAttchFiles.do"/>'
,data: {interfaceSeqN: '${interfaceSeqN}'}
,success: (res) => {
const url = '<c:url value="/framework/biz/cmm/file/download.do"/>';
res.data?.contents.forEach((dtl, idx) => {
fetch(
url
, {
method: 'post'
, body: JSON.stringify(dtl)
}
)
.then((response) => response.blob())
.then((blob) => {
const url = URL.createObjectURL(blob);
var x = document.createElement("img");
x.setAttribute("src", url);
//x.style = 'width:150px; display: block;';
x.style = 'height:150px;';
//x.setAttribute("width", "304");
//x.setAttribute("height", "228");
x.setAttribute("alt", dtl.orginlFileNm);
document.querySelector('#ctznImg').appendChild(x);
// Revoke Blob URL after DOM updates..
//window.URL.revokeObjectURL(objectURL);
});
})
}
})
},
viewImg: () => {
cmmAjax({
showSuccessMsg: false
,url: '<c:url value="/fims/biz/ec/findCtznStmtDtls.do"/>'
,data: {interfaceSeqN: '${interfaceSeqN}'}
,success: (res) => {
res.data.contents?.forEach((dto) => {
console.log('',dto.vhcleNo)
})
}
})
}
};
/**************************************************************************
* event
**************************************************************************/
$(() => {
$('img').on('click', () => {
fnBiz.viewImg()
});
});
/**************************************************************************
* initialize
**************************************************************************/
$(document).ready(function () {
// orgData = $('form').serialize();
// fnBiz.downloadImg();
<%-- console.log('eeee');--%>
<%-- fnBiz.viewImg();--%>
<%-- //CtznStmtDTO.CtznStmtDtl--%>
<%-- <c:forEach var="dto" items="${ctznStmtDtlDTOs}">--%>
<%-- '<c:out value="${dto.vhcleNo}"/>';--%>
<%-- '<c:out value="${dto.ctnzSttemntDetailSn}"/>';--%>
<%-- <c:out value="111"/>--%>
<%-- </c:forEach>--%>
});
</script>

@ -64,8 +64,8 @@
let popOption; let popOption;
switch (flag) { switch (flag) {
case "detail": case "detail":
url = '<c:url value="/fims/biz/ec/ecNatlNewspaperMgtPopup.do"/>'; url = '<c:url value="/fims/biz/ec/ecNatlNewspaperPopup.do"/>';
popOption = {width: 1000, height:850}; popOption = {width: 1000, height:900};
popTitle = "주민 신고 데이타 상세"; popTitle = "주민 신고 데이타 상세";
break; break;
case "file": case "file":
@ -124,7 +124,7 @@
minWidth: 200, minWidth: 200,
sortable: true, sortable: true,
sortingType: 'desc', sortingType: 'desc',
//align: 'center', align: 'center',
renderer: { renderer: {
type: CustomButtonRenderer, type: CustomButtonRenderer,
options: { options: {
@ -176,12 +176,7 @@
api: { api: {
readData: { readData: {
url: '<c:url value="/fims/biz/ec/findNatlNewspaers.do"/>' url: '<c:url value="/fims/biz/ec/findNatlNewspaers.do"/>'
,serializer: (params) => { ,serializer: (params) => fnAddPageInfo(document.frmSearch, params)
const schKey = $('#searchCondition').val();
let schValue = $('input[name=searchKeyword]').val();
if(schKey === 'tmplatSeCode') schValue = $('select[name=searchKeyword]').val();
return getPageParam({[schKey]: schValue}, params);
}
} }
} }
}; };

@ -2,7 +2,7 @@
<%@ include file="/WEB-INF/jsp/framework/taglibs.jsp" %> <%@ include file="/WEB-INF/jsp/framework/taglibs.jsp" %>
<%--rcvXmlDTO--%> <%--rcvXmlDTO--%>
<%--<c:set var="isUpdate" value="${!empty userInfoVO.uniqId}"/>--%> <c:set var="files" value="${attchFiles}"/>
<c:set var="bizName" value="주민 신고 데이타"/> <c:set var="bizName" value="주민 신고 데이타"/>
<div class="popup" style="min-width: 100px;"> <div class="popup" style="min-width: 100px;">
@ -108,6 +108,7 @@
<input type="text" name="petiReasonAttachYnC" value="<c:out value="${rcvXmlDTO.petiReasonAttachYnC}"/>" readonly> <input type="text" name="petiReasonAttachYnC" value="<c:out value="${rcvXmlDTO.petiReasonAttachYnC}"/>" readonly>
</td> </td>
</tr> </tr>
<%--
<tr> <tr>
<th>첨부파일크기</th> <th>첨부파일크기</th>
<td> <td>
@ -136,6 +137,7 @@
<input type="text" name="petiFilePath5V" value="<c:out value="${rcvXmlDTO.petiFilePath5V}"/>" readonly> <input type="text" name="petiFilePath5V" value="<c:out value="${rcvXmlDTO.petiFilePath5V}"/>" readonly>
</td> </td>
</tr> </tr>
--%>
<tr> <tr>
<th>등록일시</th> <th>등록일시</th>
<td> <td>
@ -167,6 +169,7 @@
<input type="text" name="pcdGubunV" value="<c:out value="${rcvXmlDTO.pcdGubunV}"/>" readonly> <input type="text" name="pcdGubunV" value="<c:out value="${rcvXmlDTO.pcdGubunV}"/>" readonly>
</td> </td>
</tr> </tr>
<%--
<tr> <tr>
<th>처리구분2</th> <th>처리구분2</th>
<td> <td>
@ -195,9 +198,11 @@
<input type="text" name="ifid" value="<c:out value="${rcvXmlDTO.ifid}"/>" readonly> <input type="text" name="ifid" value="<c:out value="${rcvXmlDTO.ifid}"/>" readonly>
</td> </td>
</tr> </tr>
--%>
</tbody> </tbody>
</table> </table>
</form> </form>
<div id="ctznImg"></div>
<div class="popup_btn"> <div class="popup_btn">
<span class="flr p_flr"> <span class="flr p_flr">
<a href="#" class="btn lightgray" onclick="window.close()">닫기</a> <a href="#" class="btn lightgray" onclick="window.close()">닫기</a>
@ -212,19 +217,54 @@
/************************************************************************** /**************************************************************************
* Global Variable * Global Variable
**************************************************************************/ **************************************************************************/
let orgData; // let orgData;
/* ******************************* /* *******************************
* Biz function * Biz function
******************************* */ ******************************* */
const fnBiz = { const fnBiz = {
add: () => { downloadImg: () => {
cmmBizAjax('add', { cmmAjax({
url: '<c:url value="/framework/biz/mng/bbs/addBoardCreate"/>' showSuccessMsg: false
, data: $("#boardMaster").serialize() ,url: '<c:url value="/fims/biz/ec/findNatlNewspaperAttchFiles.do"/>'
,data: {interfaceSeqN: '${interfaceSeqN}'}
,success: (res) => {
const url = '<c:url value="/framework/biz/cmm/file/download.do"/>';
res.data?.contents.forEach((dtl, idx) => {
fetch(
url
, {
method: 'post'
, body: JSON.stringify(dtl)
}
)
.then((response) => response.blob())
.then((blob) => {
const url = URL.createObjectURL(blob);
var x = document.createElement("img");
x.setAttribute("src", url);
//x.style = 'width:150px; display: block;';
x.style = 'height:150px;';
//x.setAttribute("width", "304");
//x.setAttribute("height", "228");
x.setAttribute("alt", dtl.orginlFileNm);
document.querySelector('#ctznImg').appendChild(x);
// Revoke Blob URL after DOM updates..
//window.URL.revokeObjectURL(objectURL);
});
}) })
}
})
},
viewImg: () => {
} }
}; };
@ -232,13 +272,8 @@
* event * event
**************************************************************************/ **************************************************************************/
$(() => { $(() => {
$('#choiceLink').on('click', () => { $('img').on('click', () => {
CmmPopup.open( fnBiz.viewImg()
'<c:url value="/framework/biz/mng/bbs/mngBoardCreateSchPopup.do"/>'
, {}
, {width: 900, height: 870}
, '프로그램 조회'
);
}); });
}); });
@ -246,6 +281,7 @@
* initialize * initialize
**************************************************************************/ **************************************************************************/
$(document).ready(function () { $(document).ready(function () {
orgData = $('form').serialize(); // orgData = $('form').serialize();
fnBiz.downloadImg();
}); });
</script> </script>

@ -69,7 +69,7 @@
//초기화 //초기화
XitIncludeBase.init(); XitIncludeBase.init();
}); });
<%--
$.ajaxSetup({ $.ajaxSetup({
cache : false, cache : false,
beforeSend:function(jqXhr, settings) { beforeSend:function(jqXhr, settings) {
@ -112,7 +112,7 @@
alert(jqxhr.responseText); alert(jqxhr.responseText);
} }
}); });
--%>

@ -219,7 +219,11 @@ const cmmAjax = (param) => {
,data: param.data ,data: param.data
,async: nvl(param.async, true) ,async: nvl(param.async, true)
,dataType: nvl(param.dataType, "json") ,dataType: nvl(param.dataType, "json")
,contentType: nvl(param.contentType, 'application/x-www-form-urlencoded') ,contentType: nvl(param.contentType, 'application/x-www-form-urlencoded;charset=UTF-8')
,cache : false
,beforeSend: (jqXhr, settings) => {
jqXhr.setRequestHeader('AJAX',true);
}
,success: function (res) { ,success: function (res) {
if (param.showSuccessMsg === undefined || param.showSuccessMsg === true) { if (param.showSuccessMsg === undefined || param.showSuccessMsg === true) {
if(res.message) alert(res.message); if(res.message) alert(res.message);
@ -228,6 +232,42 @@ const cmmAjax = (param) => {
param.success(res) param.success(res)
} }
} }
,error: function( event, jqxhr, settings, thrownError ){
console.log('BizIncludeBase::ajaxError >>>>> ', thrownError)
if(settings.dataType==undefined){
//html 문자열을 객체로 변환 후 메시지만 추출
var doc = document.createElement("html");
doc.innerHTML = jqxhr.responseText;
var msg = $(doc).find('.content_body').eq(0).text();
if(msg == '')
msg = jqxhr.responseText;
alert(msg);
}else if(settings.dataType=='json'){
//TODO: 에러처리 화면 깨지는 경우 체크해야 함
//error 발생시 html type인 경우 반영
//try{
//const jsonObject = JSON.stringify(jqxhr.responseText);
//if(typeof jsonObject === 'object') alert(jsonObject.message)
//else return document.write(jqxhr.responseText);
if(jqxhr.responseJSON?.message){
alert(jqxhr.responseJSON?.message);
return false;
}
else {
document.write(jqxhr.responseText);
return false;
}
//}catch{
// document.write(jqxhr.responseText);
// return false;
//}
}else {
console.log('??? alert(jqxhr.responseText) 으로 처리 했는데 .... document.write(jqxhr.responseText)로 처리해야 하지 않을까????? 확인이 필요');
alert(jqxhr.responseText);
}
}
,exception: function (res) { ,exception: function (res) {
alert(res.message); alert(res.message);
if ($.type(param.exception) === 'function') { if ($.type(param.exception) === 'function') {
@ -278,6 +318,96 @@ const cmmBizAjax = (workType, param, isPopup = true) => {
} }
}; };
const cmmDownloadAjax = (param) => {
$.ajax({
url: param.url
,type: nvl(param.type, "post")
,data: param.data
,async: nvl(param.async, true)
,dataType: nvl(param.dataType, "json")
//,contentType: nvl(param.contentType, 'application/x-www-form-urlencoded;charset=UTF-8')
//,contentType: 'blob'
/*
,xhr: function () {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
//response 데이터를 바이너리로 처리한다. 세팅하지 않으면 default가 text
xhr.responseType = "blob";
};
return xhr;
}
*/
,success: function (res, msg, xhr) {
if (xhr.readyState == 4 && xhr.status == 200) {
// 성공했을때만 파일 다운로드 처리하고
let disposition = xhr.getResponseHeader('Content-Disposition');
let filename;
if (disposition && disposition.indexOf('attachment') !== -1) {
let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
let matches = filenameRegex.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
}
let blob = new Blob([res]);
let link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = filename;
link.click();
}else{
//실패했을때는 alert 메시지 출력
alertPopup("다운로드에 실패하였습니다.");
}
if (param.showSuccessMsg === undefined || param.showSuccessMsg === true) {
if(res.message) alert(res.message);
}
if ($.type(param.success) === 'function') {
param.success(res)
}
}
,exception: function (res) {
alert(res.message);
if ($.type(param.exception) === 'function') {
param.exception(res);
}
}
});
};
function dataURLtoBlob(dataurl) {
var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], {
type: mime
});
}
function downloadImg(imgSrc) {
var image = new Image();
image.crossOrigin = "anonymous";
image.src = imgSrc;
var fileName = image.src.split("/").pop();
image.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
canvas.getContext('2d').drawImage(this, 0, 0);
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(dataURLtoBlob(canvas.toDataURL()), fileName);
} else {
var link = document.createElement('a');
link.href = canvas.toDataURL();
link.download = fileName;
link.click();
}
};
}
/** /**
* <pre> * <pre>
* val 값이 null이면 ifNulVal(미지정시 '') * val 값이 null이면 ifNulVal(미지정시 '')

@ -497,3 +497,44 @@ function serialize (formData) {
return rtnData; return rtnData;
} }
/**
* [downloadImage]
* @param {[string]} img [base64encoded image data]
* @param {[string]} fileName [new file name]
* @return [image file]
*/
function downloadImage(img, fileName) {
var imgData = atob(img.src.split(',')[1]),
len = imgData.length,
buf = new ArrayBuffer(len),
view = new Uint8Array(buf),
blob,
i;
for (i = 0; i < len; i++) {
view[i] = imgData.charCodeAt(i) & 0xff; // masking
}
blob = new Blob([view], {
type: 'application/octet-stream',
});
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
//var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.style = 'display: none';
//a.href = url;
a.href = img.src;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(function () {
document.body.removeChild(a);
//URL.revokeObjectURL(url);
}, 100);
}
}

Loading…
Cancel
Save