feat: 건설현장 통계에 기관별 등록 건수 추가

main
thkim 2025-11-21 16:10:31 +09:00
parent 225a32decf
commit eaab9989f3
9 changed files with 670 additions and 205 deletions

View File

@ -1,6 +1,7 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/java/geoinfo/admins/chLog/DownloadAppController.java=UTF-8
encoding//src/main/webapp/WEB-INF/views/admins/constructionProjectManagement/construction-project-statistics-index.jsp=UTF-8
encoding//src/main/webapp/body/map/web3d/web3d.jsp=UTF-8
encoding//src/main/webapp/popups/pop_201705_01.jsp=UTF-8
encoding//src/test/java=UTF-8

View File

@ -60,7 +60,7 @@ if /i "!workspace_path:~1,2!" == "\:" (
rem # 최종 target_prefix를 설정합니다.
set "target_prefix=!workspace_path!\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\geoinfo_admin\"
explorer "!target_prefix!"
#explorer "!target_prefix!"
echo target_prefix: "!target_prefix!"
@ -117,7 +117,7 @@ if "!line:~-4!" == ".xml" (
set "target_file=%target_prefix%!relative_path!"
)
rem --- [수정된 부분] 파일 복사 실행 및 결과 출력 ---
rem --- 파일 복사 실행 및 결과 출력 ---
if defined source_file (
if exist "!source_file!" (
echo [COPY]

View File

@ -1,3 +1,3 @@
#게시판 - 자료실에 파일 업로드 안 되는 문제 수정 건
src\main\webapp\WEB-INF\views\admins\constructionProjectManagement\construction-project-statistics-index.jsp
src\main\resources\geoinfo\sqlmap\mappers\admins\user\DrillingInquiryMapper.xml
src\main\resources\geoinfo\sqlmap\mappers\admins\constructionProjectManagement\ConstructionProjectManagementMapper.xml

View File

@ -6,6 +6,7 @@ import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@ -25,6 +26,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
import egovframework.rte.psl.dataaccess.util.EgovMap;
import egovframework.rte.ptl.mvc.tags.ui.pagination.PaginationInfo;
import geoinfo.admins.constructionProjectManagement.service.ConstructionProjectManagementService;
import geoinfo.admins.user.service.DrillingInquiryService;
import geoinfo.admins.user.service.GeneralUserMngService;
import geoinfo.admins.user.service.HomeTrainingService;
@ -32,8 +34,6 @@ import geoinfo.comm.util.strUtil;
import geoinfo.session.UserInfo;
import geoinfo.util.MyUtil;
@Controller
public class ConstructionProjectManagementController {
@Resource(name = "generalUserMngService")
@ -43,11 +43,16 @@ public class ConstructionProjectManagementController {
private HomeTrainingService homeTrainingService;
@Resource(name = "drillingInquiryService")
private DrillingInquiryService drillingInquiryService;
// [변경] 새로 만든 서비스 주입
@Resource(name = "constructionProjectManagementService")
private ConstructionProjectManagementService constructionProjectManagementService;
// [추가] 기존 검색/조회 기능을 위해 필요 (변수 선언 추가)
@Resource(name = "drillingInquiryService")
private DrillingInquiryService drillingInquiryService;
/**
*
*
* @param params
* @param model
* @param response
@ -66,6 +71,41 @@ public class ConstructionProjectManagementController {
return "admins/constructionProjectManagement/construction-project-statistics-index";
}
/**
* (AJAX)
*/
@RequestMapping(value = "admins/constructionProjectManagement/selectStatistics.do", method = RequestMethod.POST)
@ResponseBody
public JSONObject selectStatistics(HttpServletRequest request, @RequestBody String strJSON, HttpServletResponse response) {
JSONObject jsonResponse = new JSONObject();
try {
// 1. 파라미터 파싱
JSONParser jsonParser = new JSONParser();
JSONObject jsonObject = (JSONObject) jsonParser.parse(strJSON);
HashMap<String, Object> params = new HashMap<>();
if (jsonObject != null) {
for(Object key : jsonObject.keySet()){
params.put((String)key, jsonObject.get(key));
}
}
// 2. 서비스 호출 (새로 만든 서비스 사용)
Map<String, Object> stats = constructionProjectManagementService.selectConstructionProjectStatistics(params);
jsonResponse.put("result", "true");
jsonResponse.put("data", stats);
} catch (Exception e) {
jsonResponse.put("result", "false");
jsonResponse.put("message", e.getMessage());
e.printStackTrace();
}
return jsonResponse;
}
/**
*
* @param request
@ -612,42 +652,6 @@ public class ConstructionProjectManagementController {
return "admins/constructionProjectManagement/construction-user-detail";
}
// 발주기관 프로젝트목록 가져오기
// @ResponseBody
// @RequestMapping(value = "/drilling-project-list", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
// public String getDrillingProjectList(HttpServletRequest request, HttpServletResponse response, @RequestParam HashMap<String,Object> params) throws Exception {
// if (!UserInfo.isValidSession(request, response, "admin")) {
// return "";
// }
// JSONObject jsonObject = new JSONObject();
// strUtil sUtil = new strUtil();
//
// String projectName = sUtil.checkNull((String)params.get("projectName"));
//
// JSONArray jsonListObject = new JSONArray();
//
//// if( projectName == ""){
//// jsonObject.put("resultMessage", "OK");
//// jsonObject.put("resultCode", 200);
//// jsonObject.put("result", new JSONObject().put("list", jsonListObject));
//// } else {
// JSONObject result = new JSONObject();
// result.put("list", drillingInquiryService.drillingInquiryAutocompleteList(request, params));
//
// jsonObject.put("resultMessage", "OK");
// jsonObject.put("resultCode", 200);
// jsonObject.put("result", result);
//// }
//
// response.setContentType("application/json; charset=UTF-8"); // 응답 헤더 설정
// response.setCharacterEncoding("UTF-8"); // 응답 데이터 인코딩 설정 (중요)
//
// try (OutputStream os = response.getOutputStream()) { // OutputStream 사용
// os.write(jsonObject.toString().getBytes("UTF-8")); // UTF-8 인코딩하여 출력
// }
//
// return null; // @ResponseBody이므로 반환 값은 필요 없습니다.
// }
@RequestMapping(value = "/drilling/inquiry/list.do", method = RequestMethod.GET, produces = { "application/json; charset=utf-8" })
@ResponseBody
public ResponseEntity<JSONObject> drillingInquiryList (

View File

@ -0,0 +1,48 @@
package geoinfo.admins.constructionProjectManagement.service;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import egovframework.rte.psl.dataaccess.mapper.Mapper;
import egovframework.rte.psl.dataaccess.util.EgovMap;
@Mapper("constructionProjectManagementMapper")
public interface ConstructionProjectManagementMapper {
/**
*
*/
public Long selectTotalCount(HashMap<String, Object> params) throws SQLException;
/**
*
*/
public List<EgovMap> selectRegionCount(HashMap<String, Object> params) throws SQLException;
/**
*
*/
public List<EgovMap> selectStageCount(HashMap<String, Object> params) throws SQLException;
/**
* ( N)
*/
public List<EgovMap> selectRecentProjects(HashMap<String, Object> params) throws SQLException;
/**
*
*/
public List<String> selectStatTargetCompanies() throws SQLException;
/**
* (GL, GM, GS) (Stored Procedure )
*/
public void spGetMasterCompanyDistrict(HashMap<String, Object> params) throws SQLException;
/**
*
*/
public int selectSiteCountByDistrictCodes(HashMap<String, Object> params) throws SQLException;
}

View File

@ -0,0 +1,16 @@
package geoinfo.admins.constructionProjectManagement.service;
import java.util.HashMap;
import java.util.Map;
public interface ConstructionProjectManagementService {
/**
*
* @param params
* @return Map (totalCount, regionList, stageList, recentList )
* @throws Exception
*/
public Map<String, Object> selectConstructionProjectStatistics(HashMap<String, Object> params) throws Exception;
}

View File

@ -0,0 +1,184 @@
package geoinfo.admins.constructionProjectManagement.service.impl;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import egovframework.rte.psl.dataaccess.util.EgovMap;
import geoinfo.admins.constructionProjectManagement.service.ConstructionProjectManagementMapper;
import geoinfo.admins.constructionProjectManagement.service.ConstructionProjectManagementService;
import geoinfo.admins.user.service.DrillingInputService;
import geoinfo.util.MyUtil;
@Service("constructionProjectManagementService")
public class ConstructionProjectManagementServiceImpl implements ConstructionProjectManagementService {
@Resource(name = "constructionProjectManagementMapper")
private ConstructionProjectManagementMapper constructionProjectManagementMapper;
@Autowired
private DrillingInputService drillingInputService;
/**
*
*/
@Override
public Map<String, Object> selectConstructionProjectStatistics(HashMap<String, Object> params) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
// 1. 사용자 권한 및 조직 코드 설정 (기존 로직)
String userId = MyUtil.getStringFromObject(params.get("userId"));
if (userId != null && !userId.isEmpty()) {
HashMap<String, Object> orgCodes = drillingInputService.getOrganizationUserGlGmGsGfCodes(userId);
if (orgCodes != null) {
params.put("masterCompanyOCode", orgCodes.get("v_gl"));
params.put("masterCompanyTwCode", orgCodes.get("v_gm"));
params.put("masterCompanyThCode", orgCodes.get("v_gs"));
params.put("masterCompanyName", orgCodes.get("v_gf"));
}
}
try {
// 2. 전체 등록 수 조회
Long totalCount = constructionProjectManagementMapper.selectTotalCount(params);
resultMap.put("totalCount", totalCount);
// =================================================================
// [기관별 통계 로직] v_gl='01' 필터링 및 코드순 정렬
// =================================================================
List<Map<String, Object>> institutionStats = new ArrayList<>();
// A. 통계 대상 기관명 목록 조회
List<String> companyList = constructionProjectManagementMapper.selectStatTargetCompanies();
// 부산 지역(v_gl='01') 합계용 변수
int busanTotalCount = 0;
if (companyList != null) {
for (String companyName : companyList) {
if (companyName == null || companyName.trim().isEmpty()) continue;
// B. SP 호출하여 지역코드(GL, GM, GS) 획득
HashMap<String, Object> spParams = new HashMap<>();
spParams.put("projectMasterCompanyName", companyName);
// SP 실행 (OUT 변수는 spParams에 담김: v_gl, v_gm, v_gs)
constructionProjectManagementMapper.spGetMasterCompanyDistrict(spParams);
String v_gl = (String) spParams.get("v_gl");
String v_gm = (String) spParams.get("v_gm");
String v_gs = (String) spParams.get("v_gs");
// [필터링 조건] v_gl이 '01'인 경우에만 처리
if ("01".equals(v_gl)) {
// C. 해당 지역코드로 건설현장 수 카운트
HashMap<String, Object> countParams = new HashMap<>();
countParams.put("v_gl", v_gl);
countParams.put("v_gm", v_gm);
countParams.put("v_gs", v_gs);
int siteCount = constructionProjectManagementMapper.selectSiteCountByDistrictCodes(countParams);
// 그래프용 데이터 저장 (기관명, 건수, 정렬용 코드들)
Map<String, Object> stat = new HashMap<>();
stat.put("name", companyName);
stat.put("count", siteCount);
stat.put("gl", v_gl);
stat.put("gm", v_gm);
stat.put("gs", v_gs);
institutionStats.add(stat);
// 부산 지역 합계 누적
busanTotalCount += siteCount;
}
}
// [정렬] GL_CODE ASC, GM_CODE ASC, GS_CODE ASC 순서로 정렬
Collections.sort(institutionStats, new Comparator<Map<String, Object>>() {
@Override
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
String gl1 = (String) o1.get("gl");
String gl2 = (String) o2.get("gl");
int result = compareString(gl1, gl2);
if (result != 0) return result;
String gm1 = (String) o1.get("gm");
String gm2 = (String) o2.get("gm");
result = compareString(gm1, gm2);
if (result != 0) return result;
String gs1 = (String) o1.get("gs");
String gs2 = (String) o2.get("gs");
return compareString(gs1, gs2);
}
// null-safe string comparison
private int compareString(String s1, String s2) {
if (s1 == null) s1 = "";
if (s2 == null) s2 = "";
return s1.compareTo(s2);
}
});
}
// 결과 맵에 저장 (정렬된 리스트)
resultMap.put("institutionStats", institutionStats);
resultMap.put("busanCount", busanTotalCount);
// 3. 지역별 통계 조회
List<EgovMap> regionList = constructionProjectManagementMapper.selectRegionCount(params);
resultMap.put("regionList", regionList);
// 4. 단계별 통계 조회
List<EgovMap> stageList = constructionProjectManagementMapper.selectStageCount(params);
Map<String, Integer> stageCounts = new HashMap<>();
// 초기화
stageCounts.put("feasibility", 0);
stageCounts.put("basicDesign", 0);
stageCounts.put("detailDesign", 0);
stageCounts.put("construction", 0);
stageCounts.put("completion", 0);
stageCounts.put("maintenance", 0);
for (EgovMap map : stageList) {
String stateCode = String.valueOf(map.get("constStateCode"));
Integer cnt = Integer.parseInt(String.valueOf(map.get("cnt")));
if ("1".equals(stateCode)) stageCounts.put("feasibility", cnt);
else if ("2".equals(stateCode)) stageCounts.put("basicDesign", cnt);
else if ("3".equals(stateCode)) stageCounts.put("detailDesign", cnt);
else if ("4".equals(stateCode)) stageCounts.put("construction", cnt);
else if ("5".equals(stateCode)) stageCounts.put("completion", cnt);
else if ("6".equals(stateCode)) stageCounts.put("maintenance", cnt);
}
resultMap.put("stageCounts", stageCounts);
// 5. 최근 입력된 건설현장
params.put("firstIndex", 0);
params.put("recordCountPerPage", 5);
List<EgovMap> recentList = constructionProjectManagementMapper.selectRecentProjects(params);
resultMap.put("recentList", recentList);
} catch (SQLException e) {
e.printStackTrace();
throw new Exception("통계 데이터 조회 중 오류가 발생했습니다.");
}
return resultMap;
}
}

View File

@ -0,0 +1,126 @@
<?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">
<!-- namespace 확인: Java Interface 경로와 일치 -->
<mapper namespace="geoinfo.admins.constructionProjectManagement.service.ConstructionProjectManagementMapper">
<!-- [공통] 검색 조건 Fragment (기존 유지) -->
<sql id="searchConditions">
<if test="constName != null and constName != ''">
AND A.CONST_NAME LIKE '%' || #{constName} || '%'
</if>
<if test="masterCompanyName != null and masterCompanyName != ''">
AND A.MASTER_COMPANY_NAME LIKE '%' || #{masterCompanyName} || '%'
</if>
<if test="masterCompanyAdmin != null and masterCompanyAdmin != ''">
AND A.MASTER_COMPANY_ADMIN LIKE '%' || #{masterCompanyAdmin} || '%'
</if>
<if test="masterCompanyTel != null and masterCompanyTel != ''">
AND A.MASTER_COMPANY_TEL LIKE '%' || #{masterCompanyTel} || '%'
</if>
<if test="constTag != null and constTag != ''">
AND A.CONST_TAG = #{constTag}
</if>
<if test="masterCompanyOCode != null and masterCompanyOCode != ''">
AND A.MASTER_COMPANY_O_CODE = #{masterCompanyOCode}
</if>
</sql>
<!-- 1. 전체 등록 수 조회 (기존 유지) -->
<select id="selectTotalCount" parameterType="hashmap" resultType="long">
SELECT COUNT(*)
FROM TEMP_CONSTRUCT_SITE_INFO A
WHERE 1=1
<include refid="searchConditions" />
</select>
<!-- 2. 지역별 통계 조회 (기존 유지) -->
<select id="selectRegionCount" parameterType="hashmap" resultType="egovMap">
SELECT
SUBSTR(B.PROJECT_START_SPOT, 1, 2) AS REGION_NAME,
COUNT(*) AS CNT
FROM TEMP_CONSTRUCT_SITE_INFO A
LEFT JOIN TBL_PROJECT_INFO B ON A.PROJECT_CODE = B.PROJECT_CODE
WHERE 1=1
AND B.PROJECT_START_SPOT IS NOT NULL
<include refid="searchConditions" />
GROUP BY SUBSTR(B.PROJECT_START_SPOT, 1, 2)
</select>
<!-- 3. 단계별 통계 조회 (기존 유지) -->
<select id="selectStageCount" parameterType="hashmap" resultType="egovMap">
SELECT
CONST_STATE_CODE AS "constStateCode",
COUNT(*) AS "cnt"
FROM TEMP_CONSTRUCT_SITE_INFO A
WHERE 1=1
<include refid="searchConditions" />
GROUP BY CONST_STATE_CODE
</select>
<!-- 4. 최근 입력된 건설현장 목록 조회 (기존 유지) -->
<select id="selectRecentProjects" parameterType="hashmap" resultType="egovMap">
SELECT *
FROM (
SELECT ROWNUM RN, TB.*
FROM (
SELECT
A.CID,
A.CONST_NAME,
B.PROJECT_START_SPOT,
A.CONST_STATE_CODE,
A.CONST_COMPANY_CODE,
A.PROJECT_STATE_CODE,
A.CRT_DT,
A.MOD_DT
FROM TEMP_CONSTRUCT_SITE_INFO A
LEFT JOIN TBL_PROJECT_INFO B ON A.PROJECT_CODE = B.PROJECT_CODE
WHERE 1=1
<include refid="searchConditions" />
ORDER BY A.CRT_DT DESC
) TB
)
WHERE RN BETWEEN #{firstIndex} + 1 AND #{firstIndex} + #{recordCountPerPage}
</select>
<!-- 5. [수정] 통계 대상 기관명 목록 조회 (사용자 요청 쿼리 적용) -->
<select id="selectStatTargetCompanies" resultType="string">
SELECT tmc.COM_NAME
FROM (
SELECT
wmi.*,
ROW_NUMBER() OVER (PARTITION BY wmi.PROJECT_MASTER_COMPANY_CODE ORDER BY wmi.DATETIME DESC) as rn
FROM
web_member_in wmi
WHERE
wmi.cls = 2
) wmi
LEFT JOIN TBL_MASTER_COMPANY tmc ON
TRIM(wmi.PROJECT_MASTER_COMPANY_CODE) = tmc.COM_CODE
WHERE rn = 1
AND tmc.COM_NAME IS NOT NULL
</select>
<!-- 6. 기관명을 이용해 지역코드 구하기 (SP 호출) -->
<select id="spGetMasterCompanyDistrict" statementType="CALLABLE" parameterType="hashmap">
{ call SP_GET_MASTER_COMPANY_DISTRICT (
#{projectMasterCompanyName, mode=IN, jdbcType=VARCHAR},
#{v_gl, mode=OUT, jdbcType=VARCHAR},
#{v_gm, mode=OUT, jdbcType=VARCHAR},
#{v_gs, mode=OUT, jdbcType=VARCHAR},
#{v_gf, mode=OUT, jdbcType=VARCHAR}
)}
</select>
<!-- 7. [수정] 지역코드로 건설현장 수 구하기 (사용자 요청 매핑 적용) -->
<select id="selectSiteCountByDistrictCodes" parameterType="hashmap" resultType="int">
SELECT COUNT(*)
FROM TEMP_CONSTRUCT_SITE_INFO
WHERE MASTER_COMPANY_O_CODE = #{v_gl}
AND MASTER_COMPANY_TW_CODE = #{v_gm}
AND MASTER_COMPANY_TH_CODE = #{v_gs}
<!-- 유효한 현장만 카운트할 경우 아래 주석 해제 -->
<!-- AND CONST_TAG = 'Y' -->
</select>
</mapper>

View File

@ -3,175 +3,261 @@
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<!-- <script src="${pageContext.request.contextPath}/js/admins/chart.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Chart.js 라이브러리 -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script src="${pageContext.request.contextPath}/js/admins/common.js"></script>
<link rel="stylesheet" HREF="${pageContext.request.contextPath}/css/admins/style.css" type="text/css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link rel="stylesheet" HREF="${pageContext.request.contextPath}/css/admins/style.css" type="text/css">
<script>
var context = "${pageContext.request.contextPath}";
<script>
var context = "${pageContext.request.contextPath}";
var myChartInstance = null; // 단계별 차트 인스턴스
var instChartInstance = null; // 기관별 차트 인스턴스 (신규)
let xhr;
if(window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
// IE5, IE6 일때
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
// [1] 문서 로드 완료 시 초기 실행
document.addEventListener("DOMContentLoaded", function() {
// 초기 로딩 시 검색 조건 없이 전체 통계 조회
moveConstructionUserDetail();
});
const ctx = document.getElementById('myChart');
// [2] 통계 조회 함수 (Vanilla JS)
function moveConstructionUserDetail() {
// 기본 검색 조건 (필요시 변경 가능)
var params = {
constTag: "Y"
};
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
datasets: [{
label: '# of Votes',
data: [12, 19, 3, 5, 2, 3],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
</head>
// AJAX 요청 (XMLHttpRequest 사용)
var xhr = new XMLHttpRequest();
xhr.open("POST", context + "/admins/constructionProjectManagement/selectStatistics.do", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
<body>
<h1>건설현장 통계</h1>
<div class="home-trainning">
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) { // 요청 완료
if (xhr.status === 200) { // 성공
try {
var res = JSON.parse(xhr.responseText);
if(res.result == "true" || res.result == true) {
renderStatistics(res.data);
} else {
alert("통계 데이터를 불러오는 중 오류가 발생했습니다: " + (res.message || ""));
}
} catch (e) {
console.error("JSON 파싱 오류:", e);
}
} else {
console.error("AJAX Error:", xhr.statusText);
}
}
};
xhr.send(JSON.stringify(params));
}
// [3] 화면 갱신 함수
function renderStatistics(data) {
// (1) 전체 등록 수
var totalCountElem = document.getElementById("total-count");
if(totalCountElem) totalCountElem.textContent = (data.totalCount || 0) + " 건";
<div class="container-fluid">
<div class="row content">
<div class="col-sm-12">
<h4>검색</h4>
<div class="well">
<div class="container mt-5">
<form class="form-inline">
<div class="form-group mb-2 mr-2">
<label for="project-name" class="mr-2">프로젝트명:</label>
<input type="text" class="form-control" id="project-name" placeholder="프로젝트명 입력">
</div>
<div class="form-group mb-2 mr-2">
<label for="client-name" class="mr-2">발주기관 명:</label>
<input type="text" class="form-control" id="client-name" placeholder="발주기관 명 입력">
</div>
<div class="form-group mb-2 mr-2 w-50">
<label for="manager-name" class="mr-2">담당자:</label>
<input type="text" class="form-control" id="manager-name" placeholder="담당자 이름 입력">
</div>
<div class="form-group mb-2 mr-2">
<label for="contact-number" class="mr-2">연락처:</label>
<input type="text" class="form-control" id="contact-number" placeholder="연락처 입력">
</div>
</form>
</div>
<div class="text-right mt-3">
<button type="submit" class="btn btn-primary mb-2">검색</button>
</div>
</div>
// (2) 지역별 통계
var busanElem = document.getElementById("busan-count");
var daeguElem = document.getElementById("daegu-count");
var sejongElem = document.getElementById("sejong-count");
<h4>건설현장 등록 건수</h4>
<div class="row">
<div class="col-sm-3">
<div class="well">
<h4>전체 등록 수</h4>
<p>12 건</p>
</div>
</div>
<div class="col-sm-3">
<div class="well">
<h4>부산광역시</h4>
<p>12 건</p>
</div>
</div>
<div class="col-sm-3">
<div class="well">
<h4>대구광역시</h4>
<p>0 건</p>
</div>
</div>
<div class="col-sm-3">
<div class="well">
<h4>세종특별자치시</h4>
<p>0 건</p>
</div>
</div>
</div>
if(busanElem) busanElem.textContent = "0 건";
if(daeguElem) daeguElem.textContent = "0 건";
if(sejongElem) sejongElem.textContent = "0 건";
<div class="row">
<div class="col-sm-4">
<div class="well">
<p class="fw-bold" style="font-size: 18px; color:#c87202;">최근 입력된 건설현장</p>
<p>부산 북항 재개발 사업 - 부산광역시</p>
<p>가덕도 신공항 건설공사 - 부산광역시</p>
<p>부산 에코델타시티 조성사업 - 부산광역시</p>
<p>동해남부선 복선전철화 사업 - 부산광역시</p>
<p>부산 도시철도 1호선 연장 공사 - 부산광역시</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">+ 더 보기</button>
</div>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<p class="fw-bold" style="font-size: 18px; color:#c87202;">단계별 건수</p>
<p><b>타당성조사 및 계획검토:</b> 12건</p>
<p><b>기본설계:</b> 0건</p>
<p><b>실시설계:</b> 0건</p>
<p><b>시공중:</b> 0건</p>
<p><b>준공:</b> 0건</p>
<p><b>유지보수:</b> 0건</p>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<p class="fw-bold" style="font-size: 18px; color:#c87202;">프로젝트 연결률</p>
<p><b>부산광역시:</b> 8.33%</p>
<p><b>대전광역시:</b> 0%</p>
<p><b>세종특별자치시:</b> 0%</p>
<p><b>서울특별시:</b> 0%</p>
<p><b>대구광역시:</b> 0%</p>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group">
<button type="button" class="btn btn-sm btn-outline-secondary">+ 더 보기</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-sm-8">
<div class="well">
<p>Text</p>
</div>
</div>
<div class="col-sm-4">
<div class="well">
<p>Text</p>
</div>
</div>
</div>
</div>
</  
div>
</div>
// 서버에서 계산된 부산 카운트가 있으면 우선 적용
if(data.busanCount !== undefined && busanElem) {
busanElem.textContent = data.busanCount + " 건";
}
</div>
</body>
// 기존 리스트 기반 지역 카운트 (필요 시 사용)
if(data.regionList) {
data.regionList.forEach(function(item) {
var region = item.regionName || item.REGION_NAME || "";
var count = item.cnt || item.CNT || 0;
// 부산은 위에서 처리했으므로 제외하거나 중복 처리 가능
if(region.indexOf("대구") >= 0 && daeguElem) daeguElem.textContent = count + " 건";
else if(region.indexOf("세종") >= 0 && sejongElem) sejongElem.textContent = count + " 건";
});
}
// (3) 최근 입력된 건설현장
var recentArea = document.getElementById("recent-project-area");
var recentHtml = '<p class="fw-bold" style="font-size: 18px; color:#c87202;">최근 입력된 건설현장</p>';
if(data.recentList && data.recentList.length > 0) {
data.recentList.forEach(function(project) {
var name = project.constName || project.CONST_NAME || "";
var spot = project.projectStartSpot || project.PROJECT_START_SPOT || "";
var spotShort = spot.split(" ")[0];
recentHtml += '<p>' + name + ' - ' + spotShort + '</p>';
});
} else {
recentHtml += '<p>최근 등록된 데이터가 없습니다.</p>';
}
recentHtml += '<div class="d-flex justify-content-between align-items-center">' +
'<div class="btn-group"><button type="button" class="btn btn-sm btn-outline-secondary">+ 더 보기</button></div></div>';
if(recentArea) recentArea.innerHTML = recentHtml;
// (4) 단계별 건수 차트
updateStageChart(data.stageCounts || {});
// (5) [신규] 기관별 등록 건수 차트 업데이트
updateInstitutionChart(data.institutionStats || []);
}
// 단계별 차트 그리기
function updateStageChart(stageCounts) {
var chartData = [
stageCounts.feasibility || 0,
stageCounts.basicDesign || 0,
stageCounts.detailDesign || 0,
stageCounts.construction || 0,
stageCounts.completion || 0,
stageCounts.maintenance || 0
];
var stageFeasElem = document.getElementById("stage-feasibility");
if(stageFeasElem) stageFeasElem.textContent = (stageCounts.feasibility || 0) + "건";
var ctx = document.getElementById('myChart');
if(!ctx) return;
if (myChartInstance) myChartInstance.destroy();
myChartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: ['타당성조사', '기본설계', '실시설계', '시공중', '준공', '유지보수'],
datasets: [{
label: '건설현장 단계별 건수',
data: chartData,
backgroundColor: 'rgba(54, 162, 235, 0.2)',
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: { scales: { y: { beginAtZero: true } } }
});
}
// [신규] 기관별 차트 그리기 함수
function updateInstitutionChart(instStats) {
var ctx = document.getElementById('institutionChart');
if(!ctx) return;
if (instChartInstance) instChartInstance.destroy();
var labels = [];
var data = [];
// 데이터 분리 및 라벨 보정
instStats.forEach(function(stat) {
var name = stat.name;
// [라벨 보정] '울산광역시' -> '한국도로공사'
if (name === '울산광역시') {
name = '한국도로공사';
}
labels.push(name);
data.push(stat.count);
});
instChartInstance = new Chart(ctx, {
type: 'bar', // 막대 그래프
data: {
labels: labels,
datasets: [{
label: '등록 건수',
data: data,
backgroundColor: 'rgba(75, 192, 192, 0.5)', // 색상 설정
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: { stepSize: 1 } // 정수 단위 표시
}
},
plugins: {
legend: { display: false }, // 범례 숨김 (단일 데이터셋이므로)
title: {
display: true,
text: '기관별 건설현장 등록 현황',
font: { size: 16 }
}
}
}
});
}
</script>
</head>
<body>
<h1>건설현장 통계</h1>
<div class="home-trainning">
<div class="container-fluid">
<div class="row content">
<div class="col-sm-12">
<!-- [신규] 기관별 통계 그래프 영역 추가 (하단 전체 너비) -->
<div class="row" style="margin-top: 30px;">
<div class="col-sm-12">
<div class="well" style="background-color: #fff;">
<p class="fw-bold" style="font-size: 18px; color:#c87202; margin-bottom: 15px;">기관별 등록 건수</p>
<div style="height: 400px;"> <!-- 높이 지정 -->
<canvas id="institutionChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row">
<!-- 최근 입력된 건설현장 -->
<div class="col-sm-4">
<div class="well" id="recent-project-area">
<p class="fw-bold" style="font-size: 18px; color:#c87202;">최근 입력된 건설현장</p>
<p>로딩 중...</p>
</div>
</div>
<!-- 단계별 건수 텍스트 -->
<div class="col-sm-4">
<div class="well">
<p class="fw-bold" style="font-size: 18px; color:#c87202;">단계별 건수</p>
<p><b>타당성조사 및 계획검토:</b> <span id="stage-feasibility">0건</span></p>
<p><b>기본설계:</b> 0건</p>
<p><b>실시설계:</b> 0건</p>
<p><b>시공중:</b> 0건</p>
<p><b>준공:</b> 0건</p>
<p><b>유지보수:</b> 0건</p>
</div>
</div>
<!-- 단계별 건수 그래프 -->
<div class="col-sm-4">
<div class="well">
<canvas id="myChart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>