단속 > 단속 등록&열람: 구조지수 VO 추가, 검색/전체 조회 기능 구현 및 등록 팝업에 구조지수 드롭다운 적용

dev
박성영 4 months ago
parent f19bbe41dd
commit 1de56b1839

@ -3,10 +3,10 @@ package go.kr.project.crdn.crndRegistAndView.crdnActInfo.controller;
import egovframework.constant.MessageConstants;
import egovframework.constant.TilesConstants;
import egovframework.util.ApiResponseUtil;
import egovframework.util.SessionUtil;
import go.kr.project.common.model.CmmnCodeSearchVO;
import go.kr.project.common.service.CommonCodeService;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnActInfoVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnStrctIdxVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.service.CrdnActInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -116,6 +116,41 @@ public class CrdnActInfoController {
return mav;
}
/**
* (AJAX)
* @param searchTerm
* @return
*/
@Operation(summary = "구조지수 검색", description = "구조지수 목록을 검색합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@PostMapping("/searchStrctIdx.ajax")
@ResponseBody
public ResponseEntity<?> searchStrctIdxAjax(@RequestParam("searchTerm") String searchTerm) {
log.debug("구조지수 검색 요청: {}", searchTerm);
List<CrdnStrctIdxVO> list = service.searchStrctIdx(searchTerm);
return ApiResponseUtil.success(list);
}
/**
* (AJAX)
* @return
*/
@Operation(summary = "전체 구조지수 조회", description = "전체 구조지수 목록을 조회합니다.")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "500", description = "서버 오류")
})
@PostMapping("/getAllStrctIdx.ajax")
@ResponseBody
public ResponseEntity<?> getAllStrctIdxAjax() {
log.debug("전체 구조지수 조회 요청");
List<CrdnStrctIdxVO> list = service.getAllStrctIdx();
return ApiResponseUtil.success(list);
}
/**
* (AJAX)
* @param vo

@ -1,6 +1,7 @@
package go.kr.project.crdn.crndRegistAndView.crdnActInfo.mapper;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnActInfoVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnStrctIdxVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@ -53,4 +54,18 @@ public interface CrdnActInfoMapper {
* @return
*/
int deleteActInfo(CrdnActInfoVO vo);
/**
*
* @param searchTerm
* @return
*/
List<CrdnStrctIdxVO> searchStrctIdx(String searchTerm);
/**
*
* @return
*/
List<CrdnStrctIdxVO> getAllStrctIdx();
}

@ -0,0 +1,65 @@
package go.kr.project.crdn.crndRegistAndView.crdnActInfo.model;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* VO
*/
@Data
public class CrdnStrctIdxVO {
/**
*
*/
private String strctIdxCd;
/**
*
*/
private String strctNm;
/**
*
*/
private BigDecimal strctIdx;
/**
*
*/
private LocalDateTime regDt;
/**
*
*/
private String rgtr;
/**
*
*/
private LocalDateTime mdfcnDt;
/**
*
*/
private String mdfr;
/**
*
*/
private String delYn;
/**
*
*/
private LocalDateTime delDt;
/**
*
*/
private String dltr;
/**
*
*/
private BigDecimal rdvlrtCnYrCnt;
/**
*
*/
private BigDecimal lastYrRdvlrt;
/**
*
*/
private BigDecimal dprt;
}

@ -1,6 +1,7 @@
package go.kr.project.crdn.crndRegistAndView.crdnActInfo.service;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnActInfoVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnStrctIdxVO;
import java.util.List;
@ -58,4 +59,18 @@ public interface CrdnActInfoService {
* @return
*/
int deleteActInfos(List<String> actInfoIds);
/**
*
* @param searchTerm
* @return
*/
List<CrdnStrctIdxVO> searchStrctIdx(String searchTerm);
/**
*
* @return
*/
List<CrdnStrctIdxVO> getAllStrctIdx();
}

@ -4,6 +4,7 @@ import egovframework.exception.MessageException;
import egovframework.util.SessionUtil;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.mapper.CrdnActInfoMapper;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnActInfoVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.model.CrdnStrctIdxVO;
import go.kr.project.crdn.crndRegistAndView.crdnActInfo.service.CrdnActInfoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -92,6 +93,18 @@ public class CrdnActInfoServiceImpl extends EgovAbstractServiceImpl implements C
return mapper.deleteActInfo(vo);
}
@Override
public List<CrdnStrctIdxVO> searchStrctIdx(String searchTerm) {
log.debug("구조지수 검색: {}", searchTerm);
return mapper.searchStrctIdx(searchTerm);
}
@Override
public List<CrdnStrctIdxVO> getAllStrctIdx() {
log.debug("전체 구조지수 조회");
return mapper.getAllStrctIdx();
}
/**
* ()
* : .

@ -211,4 +211,36 @@
AND DEL_YN = 'N'
</update>
<!-- 구조지수 검색 -->
<select id="searchStrctIdx" parameterType="String" resultType="CrdnStrctIdxVO">
/* ActInfoMapper.searchStrctIdx : 구조지수 검색 */
SELECT
STRCT_IDX_CD,
STRCT_NM,
STRCT_IDX,
RDVLRT_CN_YR_CNT,
LAST_YR_RDVLRT,
DPRT
FROM tb_strct_idx
WHERE STRCT_NM LIKE CONCAT('%', #{searchTerm}, '%')
AND DEL_YN = 'N'
ORDER BY STRCT_NM
</select>
<!-- 전체 구조지수 목록 조회 -->
<select id="getAllStrctIdx" resultType="CrdnStrctIdxVO">
/* ActInfoMapper.getAllStrctIdx : 전체 구조지수 목록 조회 */
SELECT
STRCT_IDX_CD,
STRCT_NM,
STRCT_IDX,
RDVLRT_CN_YR_CNT,
LAST_YR_RDVLRT,
DPRT
FROM tb_strct_idx
WHERE DEL_YN = 'N'
ORDER BY STRCT_NM
</select>
</mapper>

@ -70,12 +70,13 @@
<tr>
<th class="th">구조지수</th>
<td>
<select id="strctIdxCd" name="strctIdxCd" class="input" style="width: 180px;">
<option value="">선택하세요</option>
<c:forEach var="code" items="${strctIdxCdList}">
<option value="${code.cdId}" <c:if test="${data.strctIdxCd eq code.cdId}">selected</c:if>>${code.cdNm}</option>
</c:forEach>
</select>
<div class="strct-idx-dropdown-container">
<input type="text" id="strctIdxNm" name="strctIdxNm" class="input" style="width: 200px;" placeholder="구조명을 입력하거나 선택하세요" autocomplete="off" value="${data.strctIdxCdNm}"/>
<input type="hidden" id="strctIdxCd" name="strctIdxCd" value="${data.strctIdxCd}"/>
<div id="strctIdxDropdown" class="strct-idx-dropdown">
<!-- 드롭다운 목록이 여기에 동적으로 생성됩니다 -->
</div>
</div>
</td>
<th class="th">용도지수</th>
<td>
@ -124,6 +125,7 @@
</div>
</div>
<script type="text/javascript">
/**
* 불법행위정보 등록/수정 팝업 JavaScript
@ -150,9 +152,222 @@
}
});
// 구조지수 드롭다운 초기화
initStrctIdxDropdown();
console.log('불법행위정보 팝업이 초기화되었습니다. 모드:', mode);
});
// 구조지수 드롭다운 관련 변수
var strctIdxData = []; // 전체 구조지수 데이터
var filteredStrctIdxData = []; // 필터링된 구조지수 데이터
var selectedIndex = -1; // 현재 선택된 항목 인덱스
var isDropdownOpen = false; // 드롭다운 열림 상태
/**
* 구조지수 드롭다운 초기화
* 중요한 로직 주석: 전체 구조지수 데이터를 로드하고 이벤트를 바인딩한다.
*/
function initStrctIdxDropdown() {
// 전체 구조지수 데이터 로드
$.ajax({
url: '<c:url value="/crdn/crndRegistAndView/crdnActInfo/getAllStrctIdx.ajax"/>',
type: 'POST',
success: function(response) {
if (response.success) {
strctIdxData = response.data;
filteredStrctIdxData = strctIdxData;
} else {
console.error('구조지수 데이터를 가져오는데 실패했습니다.');
}
},
error: function() {
console.error('구조지수 데이터 로드 중 오류가 발생했습니다.');
}
});
// 이벤트 바인딩
bindStrctIdxEvents();
}
/**
* 구조지수 드롭다운 이벤트 바인딩
* 중요한 로직 주석: input 필드의 클릭, 키보드 이벤트 등을 처리한다.
*/
function bindStrctIdxEvents() {
var $input = $('#strctIdxNm');
var $dropdown = $('#strctIdxDropdown');
// input 클릭 시 드롭다운 열기
$input.on('click', function() {
showStrctIdxDropdown();
});
// input 포커스 시 드롭다운 열기
$input.on('focus', function() {
showStrctIdxDropdown();
});
// input 키보드 입력 시 실시간 필터링
$input.on('input', function() {
filterStrctIdxData($(this).val());
showStrctIdxDropdown();
selectedIndex = -1;
});
// 키보드 네비게이션
$input.on('keydown', function(e) {
if (!isDropdownOpen) return;
switch (e.keyCode) {
case 38: // 위 화살표
e.preventDefault();
navigateDropdown(-1);
break;
case 40: // 아래 화살표
e.preventDefault();
navigateDropdown(1);
break;
case 13: // 엔터
e.preventDefault();
selectCurrentItem();
break;
case 27: // ESC
hideStrctIdxDropdown();
break;
}
});
// 문서 클릭 시 드롭다운 닫기
$(document).on('click', function(e) {
if (!$(e.target).closest('.strct-idx-dropdown-container').length) {
hideStrctIdxDropdown();
}
});
}
/**
* 구조지수 데이터 필터링
* 중요한 로직 주석: 입력된 텍스트를 기준으로 구조명을 필터링한다.
*/
function filterStrctIdxData(searchTerm) {
var term = searchTerm.trim().toLowerCase();
if (term === '') {
filteredStrctIdxData = strctIdxData;
} else {
filteredStrctIdxData = strctIdxData.filter(function(item) {
return item.strctNm && item.strctNm.toLowerCase().indexOf(term) > -1;
});
}
}
/**
* 구조지수 드롭다운 표시
* 중요한 로직 주석: 필터링된 데이터를 기반으로 드롭다운 리스트를 생성하고 표시한다.
*/
function showStrctIdxDropdown() {
var $dropdown = $('#strctIdxDropdown');
var html = '';
if (filteredStrctIdxData.length === 0) {
html = '<div class="strct-idx-item no-results">검색 결과가 없습니다.</div>';
} else {
filteredStrctIdxData.forEach(function(item, index) {
var strctIdx = item.strctIdx ? parseFloat(item.strctIdx).toFixed(2) : '-';
var rdvlrtCnYrCnt = item.rdvlrtCnYrCnt ? parseFloat(item.rdvlrtCnYrCnt).toFixed(0) : '-';
var lastYrRdvlrt = item.lastYrRdvlrt ? parseFloat(item.lastYrRdvlrt).toFixed(2) : '-';
var dprt = item.dprt ? parseFloat(item.dprt).toFixed(2) : '-';
html += '<div class="strct-idx-item" data-index="' + index + '" data-code="' + item.strctIdxCd + '" data-name="' + item.strctNm + '">';
html += ' <div class="strct-idx-main">' + item.strctNm + '</div>';
html += ' <div class="strct-idx-details">';
html += ' <span class="strct-idx-value">구조지수: ' + strctIdx + '</span>';
html += ' <span class="strct-idx-value">잔가율연수: ' + rdvlrtCnYrCnt + '</span>';
html += ' <span class="strct-idx-value">최종연도잔가율: ' + lastYrRdvlrt + '</span>';
html += ' <span class="strct-idx-value">감가상각률: ' + dprt + '</span>';
html += ' </div>';
html += '</div>';
});
}
$dropdown.html(html);
$dropdown.show();
isDropdownOpen = true;
// 마우스 클릭 이벤트 바인딩
$dropdown.find('.strct-idx-item').on('click', function() {
if (!$(this).hasClass('no-results')) {
var index = parseInt($(this).attr('data-index'));
selectDropdownItem(index);
}
});
}
/**
* 구조지수 드롭다운 숨기기
*/
function hideStrctIdxDropdown() {
$('#strctIdxDropdown').hide();
isDropdownOpen = false;
selectedIndex = -1;
}
/**
* 드롭다운 네비게이션
* 중요한 로직 주석: 화살표 키로 드롭다운 항목들을 네비게이션한다.
*/
function navigateDropdown(direction) {
var $items = $('#strctIdxDropdown .strct-idx-item:not(.no-results)');
if ($items.length === 0) return;
// 이전 선택 항목 하이라이트 제거
$items.removeClass('selected');
// 새로운 인덱스 계산
selectedIndex += direction;
if (selectedIndex < 0) selectedIndex = $items.length - 1;
if (selectedIndex >= $items.length) selectedIndex = 0;
// 새로운 항목 하이라이트
$items.eq(selectedIndex).addClass('selected');
// 스크롤 조정
var $dropdown = $('#strctIdxDropdown');
var $selectedItem = $items.eq(selectedIndex);
var dropdownHeight = $dropdown.height();
var itemHeight = $selectedItem.outerHeight();
var scrollTop = $dropdown.scrollTop();
var itemTop = $selectedItem.position().top + scrollTop;
if (itemTop < scrollTop) {
$dropdown.scrollTop(itemTop);
} else if (itemTop + itemHeight > scrollTop + dropdownHeight) {
$dropdown.scrollTop(itemTop + itemHeight - dropdownHeight);
}
}
/**
* 현재 선택된 항목 선택
*/
function selectCurrentItem() {
if (selectedIndex >= 0 && selectedIndex < filteredStrctIdxData.length) {
selectDropdownItem(selectedIndex);
}
}
/**
* 드롭다운 항목 선택
* 중요한 로직 주석: 선택된 구조지수를 input 필드에 설정하고 드롭다운을 닫는다.
*/
function selectDropdownItem(index) {
var selectedItem = filteredStrctIdxData[index];
if (selectedItem) {
$('#strctIdxNm').val(selectedItem.strctNm);
$('#strctIdxCd').val(selectedItem.strctIdxCd);
hideStrctIdxDropdown();
}
}
/**
* 불법행위정보 저장 함수
* 중요한 로직 주석: validation 체크 후 mode에 따라 등록/수정 API를 호출한다.

@ -1616,3 +1616,144 @@ select[data-auto-color="true"] option[data-color] {
font-size: 14px;
color: #333;
}
/* Autocomplete styles */
.autocomplete-container {
position: relative;
}
.autocomplete-results {
position: absolute;
top: 100%;
left: 0;
right: 0;
border: 1px solid #ddd;
border-top: none;
max-height: 200px;
overflow-y: auto;
background-color: #fff;
z-index: 1000;
}
.autocomplete-item {
padding: 8px 12px;
cursor: pointer;
}
.autocomplete-item:hover {
background-color: #f0f0f0;
}
/* ===== 구조지수 드롭다운 스타일 ===== */
.strct-idx-dropdown-container {
position: relative;
display: inline-block;
}
.strct-idx-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ccc;
border-top: none;
border-radius: 0 0 4px 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
display: none;
max-height: 300px;
overflow-y: auto;
}
.strct-idx-item {
padding: 10px 12px;
cursor: pointer;
border-bottom: 1px solid #f5f5f5;
transition: background-color 0.2s;
}
.strct-idx-item:last-child {
border-bottom: none;
}
.strct-idx-item:hover {
background-color: #f8f9fa;
}
.strct-idx-item.selected {
background-color: #e3f2fd;
color: #1976d2;
}
.strct-idx-item.no-results {
color: #666;
font-style: italic;
text-align: center;
cursor: default;
}
.strct-idx-item.no-results:hover {
background-color: transparent;
}
.strct-idx-main {
font-size: 14px;
font-weight: 600;
color: #333;
margin-bottom: 4px;
}
.strct-idx-details {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 12px;
}
.strct-idx-value {
color: #666;
background-color: #f8f9fa;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
}
.strct-idx-item:hover .strct-idx-value {
background-color: #e9ecef;
}
.strct-idx-item.selected .strct-idx-value {
background-color: #bbdefb;
color: #1565c0;
}
/* 드롭다운 스크롤바 스타일 */
.strct-idx-dropdown::-webkit-scrollbar {
width: 8px;
}
.strct-idx-dropdown::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.strct-idx-dropdown::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 4px;
}
.strct-idx-dropdown::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* input 필드 포커스 시 드롭다운과 연결된 스타일 */
.strct-idx-dropdown-container .input:focus {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.strct-idx-dropdown-container .input:focus + input + .strct-idx-dropdown {
border-top: 1px solid #007bff;
}

Loading…
Cancel
Save