feat: 건설현장 관리에서 건설현장 통계에 발주기관 별 건설현장 프로젝트 입력상태 및 기관별 성과 현황 구헌

main
thkim 2025-11-21 17:03:32 +09:00
parent 9e1186280e
commit bc5d1622a6
4 changed files with 327 additions and 210 deletions

View File

@ -45,4 +45,14 @@ public interface ConstructionProjectManagementMapper {
*/
public int selectSiteCountByDistrictCodes(HashMap<String, Object> params) throws SQLException;
/**
* [] (PROJECT_STATE_CODE)
*/
public List<EgovMap> selectProjectStateCountByDistrictCodes(HashMap<String, Object> params) throws SQLException;
/**
* [] (CONST_STATE_CODE - )
*/
public List<EgovMap> selectConstStateCountByDistrictCodes(HashMap<String, Object> params) throws SQLException;
}

View File

@ -28,14 +28,11 @@ public class ConstructionProjectManagementServiceImpl implements ConstructionPro
@Autowired
private DrillingInputService drillingInputService;
/**
*
*/
@Override
public Map<String, Object> selectConstructionProjectStatistics(HashMap<String, Object> params) throws Exception {
Map<String, Object> resultMap = new HashMap<>();
// 1. 사용자 권한 및 조직 코드 설정 (기존 로직)
// 1. 사용자 권한 및 조직 코드 설정
String userId = MyUtil.getStringFromObject(params.get("userId"));
if (userId != null && !userId.isEmpty()) {
HashMap<String, Object> orgCodes = drillingInputService.getOrganizationUserGlGmGsGfCodes(userId);
@ -53,99 +50,129 @@ public class ConstructionProjectManagementServiceImpl implements ConstructionPro
resultMap.put("totalCount", totalCount);
// =================================================================
// [기관별 통계 로직] v_gl='01' 필터링 및 코드순 정렬
// [기관별 통계 로직] 데이터 합산 및 정렬
// =================================================================
List<Map<String, Object>> institutionStats = new ArrayList<>();
// 중복 기관 합산을 위한 맵 (Key: 기관명)
Map<String, Map<String, Object>> uniqueStatsMap = new HashMap<>();
// 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'인 경우에만 처리
// [필터링] 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);
// [명칭 보정] 한국도로공사 코드인 경우 이름을 강제 통일 (울산광역시 -> 한국도로공사 등)
if ("01".equals(v_gl) && "02".equals(v_gm) && "002".equals(v_gs)) {
companyName = "한국도로공사";
}
int siteCount = constructionProjectManagementMapper.selectSiteCountByDistrictCodes(countParams);
HashMap<String, Object> queryParams = new HashMap<>();
queryParams.put("v_gl", v_gl);
queryParams.put("v_gm", v_gm);
queryParams.put("v_gs", v_gs);
// 1) 전체 건수
int siteCount = constructionProjectManagementMapper.selectSiteCountByDistrictCodes(queryParams);
// 그래프용 데이터 저장 (기관명, 건수, 정렬용 코드들)
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);
// 2) 입력 상태별 현황
List<EgovMap> inputStateList = constructionProjectManagementMapper.selectProjectStateCountByDistrictCodes(queryParams);
int[] inputStatusData = new int[7];
for(EgovMap m : inputStateList) {
int code = MyUtil.getIntegerFromObject(m.get("code"));
int cnt = MyUtil.getIntegerFromObject(m.get("cnt"));
if(code >= 0 && code <= 6) inputStatusData[code] = cnt;
}
// 3) 성과 현황
List<EgovMap> perfStateList = constructionProjectManagementMapper.selectConstStateCountByDistrictCodes(queryParams);
int[] performanceData = new int[6];
for(EgovMap m : perfStateList) {
int code = MyUtil.getIntegerFromObject(m.get("code"));
int cnt = MyUtil.getIntegerFromObject(m.get("cnt"));
if(code >= 1 && code <= 6) performanceData[code - 1] = cnt;
}
institutionStats.add(stat);
// [데이터 합산 로직] 이미 존재하는 기관명이면 데이터 누적
if (uniqueStatsMap.containsKey(companyName)) {
Map<String, Object> existing = uniqueStatsMap.get(companyName);
// 건수 합산
int currentCount = (Integer) existing.get("count");
existing.put("count", currentCount + siteCount);
// 입력상태 배열 합산
int[] exInput = (int[]) existing.get("inputStatusData");
for(int i=0; i<7; i++) exInput[i] += inputStatusData[i];
existing.put("inputStatusData", exInput);
// 성과현황 배열 합산
int[] exPerf = (int[]) existing.get("performanceData");
for(int i=0; i<6; i++) exPerf[i] += performanceData[i];
existing.put("performanceData", exPerf);
} else {
// 신규 추가
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);
stat.put("inputStatusData", inputStatusData);
stat.put("performanceData", performanceData);
uniqueStatsMap.put(companyName, 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);
}
});
}
// 결과 맵에 저장 (정렬된 리스트)
// Map 값을 List로 변환
List<Map<String, Object>> institutionStats = new ArrayList<>(uniqueStatsMap.values());
// [정렬] 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);
}
private int compareString(String s1, String s2) {
return (s1 == null ? "" : s1).compareTo(s2 == null ? "" : s2);
}
});
resultMap.put("institutionStats", institutionStats);
resultMap.put("busanCount", busanTotalCount);
// 3. 지역별 통계 조회
List<EgovMap> regionList = constructionProjectManagementMapper.selectRegionCount(params);
resultMap.put("regionList", regionList);
// 4. 단계별 통계 조회
// 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);
@ -169,7 +196,6 @@ public class ConstructionProjectManagementServiceImpl implements ConstructionPro
// 5. 최근 입력된 건설현장
params.put("firstIndex", 0);
params.put("recordCountPerPage", 5);
List<EgovMap> recentList = constructionProjectManagementMapper.selectRecentProjects(params);
resultMap.put("recentList", recentList);
@ -180,5 +206,4 @@ public class ConstructionProjectManagementServiceImpl implements ConstructionPro
return resultMap;
}
}

View File

@ -1,10 +1,9 @@
<?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 (기존 유지) -->
<!-- [공통] 검색 조건 Fragment -->
<sql id="searchConditions">
<if test="constName != null and constName != ''">
AND A.CONST_NAME LIKE '%' || #{constName} || '%'
@ -26,7 +25,7 @@
</if>
</sql>
<!-- 1. 전체 등록 수 조회 (기존 유지) -->
<!-- 1. 전체 등록 수 조회 -->
<select id="selectTotalCount" parameterType="hashmap" resultType="long">
SELECT COUNT(*)
FROM TEMP_CONSTRUCT_SITE_INFO A
@ -34,7 +33,7 @@
<include refid="searchConditions" />
</select>
<!-- 2. 지역별 통계 조회 (기존 유지) -->
<!-- 2. 지역별 통계 조회 -->
<select id="selectRegionCount" parameterType="hashmap" resultType="egovMap">
SELECT
SUBSTR(B.PROJECT_START_SPOT, 1, 2) AS REGION_NAME,
@ -47,7 +46,7 @@
GROUP BY SUBSTR(B.PROJECT_START_SPOT, 1, 2)
</select>
<!-- 3. 단계별 통계 조회 (기존 유지) -->
<!-- 3. 단계별 통계 조회 (전체) -->
<select id="selectStageCount" parameterType="hashmap" resultType="egovMap">
SELECT
CONST_STATE_CODE AS "constStateCode",
@ -58,7 +57,7 @@
GROUP BY CONST_STATE_CODE
</select>
<!-- 4. 최근 입력된 건설현장 목록 조회 (기존 유지) -->
<!-- 4. 최근 입력된 건설현장 목록 조회 -->
<select id="selectRecentProjects" parameterType="hashmap" resultType="egovMap">
SELECT *
FROM (
@ -83,7 +82,7 @@
WHERE RN BETWEEN #{firstIndex} + 1 AND #{firstIndex} + #{recordCountPerPage}
</select>
<!-- 5. [수정] 통계 대상 기관명 목록 조회 (사용자 요청 쿼리 적용) -->
<!-- 5. 통계 대상 기관명 목록 조회 -->
<select id="selectStatTargetCompanies" resultType="string">
SELECT tmc.COM_NAME
FROM (
@ -112,7 +111,7 @@
)}
</select>
<!-- 7. [수정] 지역코드로 건설현장 수 구하기 (사용자 요청 매핑 적용) -->
<!-- 7. 지역코드로 건설현장 수 구하기 -->
<select id="selectSiteCountByDistrictCodes" parameterType="hashmap" resultType="int">
SELECT COUNT(*)
FROM TEMP_CONSTRUCT_SITE_INFO
@ -123,4 +122,30 @@
<!-- AND CONST_TAG = 'Y' -->
</select>
<!-- [수정] 8. 기관별 입력 상태별(PROJECT_STATE_CODE) 현황 조회 -->
<!-- CONST_TAG = 'Y' 조건 제거 -->
<select id="selectProjectStateCountByDistrictCodes" parameterType="hashmap" resultType="egovMap">
SELECT
PROJECT_STATE_CODE AS "code",
COUNT(*) AS "cnt"
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}
GROUP BY PROJECT_STATE_CODE
</select>
<!-- [수정] 9. 기관별 성과 현황(CONST_STATE_CODE) 조회 -->
<!-- CONST_TAG = 'Y' 조건 제거 -->
<select id="selectConstStateCountByDistrictCodes" parameterType="hashmap" resultType="egovMap">
SELECT
CONST_STATE_CODE AS "code",
COUNT(*) AS "cnt"
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}
GROUP BY CONST_STATE_CODE
</select>
</mapper>

View File

@ -12,32 +12,56 @@
<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">
<style>
/* 그래프 컨테이너 스타일 */
.chart-wrapper {
border: 1px solid #ddd;
padding: 20px;
margin-bottom: 30px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.chart-header {
font-size: 18px;
font-weight: bold;
color: #333;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #c87202;
}
.sub-chart-title {
text-align: center;
font-weight: bold;
margin-bottom: 10px;
color: #555;
}
.sub-chart-box {
height: 300px;
position: relative;
}
</style>
<script>
var context = "${pageContext.request.contextPath}";
var myChartInstance = null; // 단계별 차트 인스턴스
var instChartInstance = null; // 기관별 차트 인스턴스 (신규)
var myChartInstance = null; // 상단 전체 단계별 차트
var instChartInstance = null; // 하단 전체 기관별 차트
// [1] 문서 로드 완료 시 초기 실행
// [1] 문서 로드 완료 시 초기 실행 (Vanilla JS)
document.addEventListener("DOMContentLoaded", function() {
// 초기 로딩 시 검색 조건 없이 전체 통계 조회
moveConstructionUserDetail();
});
// [2] 통계 조회 함수 (Vanilla JS)
function moveConstructionUserDetail() {
// 기본 검색 조건 (필요시 변경 가능)
var params = {
constTag: "Y"
};
// AJAX 요청 (XMLHttpRequest 사용)
var params = { constTag: "Y" };
var xhr = new XMLHttpRequest();
xhr.open("POST", context + "/admins/constructionProjectManagement/selectStatistics.do", true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) { // 요청 완료
if (xhr.status === 200) { // 성공
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
var res = JSON.parse(xhr.responseText);
if(res.result == "true" || res.result == true) {
@ -56,64 +80,28 @@
xhr.send(JSON.stringify(params));
}
// [3] 화면 갱신 함수
// [3] 화면 갱신 메인 함수
function renderStatistics(data) {
// (1) 전체 등록 수
var totalCountElem = document.getElementById("total-count");
if(totalCountElem) totalCountElem.textContent = (data.totalCount || 0) + " 건";
// (2) 지역별 통계
// (1) 상단 요약 수치 갱신 (Vanilla JS)
var totalElem = document.getElementById("total-count");
if (totalElem) totalElem.textContent = (data.totalCount || 0) + " 건";
var busanElem = document.getElementById("busan-count");
var daeguElem = document.getElementById("daegu-count");
var sejongElem = document.getElementById("sejong-count");
if (data.busanCount !== undefined && busanElem) {
busanElem.textContent = data.busanCount + " 건";
}
if(busanElem) busanElem.textContent = "0 건";
if(daeguElem) daeguElem.textContent = "0 건";
if(sejongElem) sejongElem.textContent = "0 건";
// 서버에서 계산된 부산 카운트가 있으면 우선 적용
if(data.busanCount !== undefined && busanElem) {
busanElem.textContent = data.busanCount + " 건";
}
// 기존 리스트 기반 지역 카운트 (필요 시 사용)
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) 단계별 건수 차트
// (2) 상단 전체 단계별 차트 갱신
updateStageChart(data.stageCounts || {});
// (3) [신규] 기관별 상세 그래프 동적 생성 (입력상태, 성과현황)
createInstitutionDetailCharts(data.institutionStats || []);
// (5) [신규] 기관별 등록 건수 차트 업데이트
// (4) 하단 전체 기관 등록 건수 차트 갱신
updateInstitutionChart(data.institutionStats || []);
}
// 단계별 차트 그리기
// [상단] 전체 단계별 차트
function updateStageChart(stageCounts) {
var chartData = [
stageCounts.feasibility || 0,
@ -123,16 +111,17 @@
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;
// 텍스트 업데이트 (Vanilla JS)
var stageElem = document.getElementById("stage-feasibility");
if (stageElem) stageElem.textContent = (stageCounts.feasibility || 0) + "건";
var canvas = document.getElementById('myChart');
if(!canvas) return;
if (myChartInstance) myChartInstance.destroy();
myChartInstance = new Chart(ctx, {
myChartInstance = new Chart(canvas, {
type: 'bar',
data: {
labels: ['타당성조사', '기본설계', '실시설계', '시공중', '준공', '유지보수'],
@ -148,38 +137,97 @@
});
}
// [신규] 기관별 차트 그리기 함수
function updateInstitutionChart(instStats) {
var ctx = document.getElementById('institutionChart');
if(!ctx) return;
if (instChartInstance) instChartInstance.destroy();
var labels = [];
var data = [];
// [중단] 기관별 상세 차트 동적 생성 함수
function createInstitutionDetailCharts(instStats) {
var container = document.getElementById("institution-detail-area");
if (!container) return;
// 데이터 분리 및 라벨 보정
instStats.forEach(function(stat) {
var name = stat.name;
// [라벨 보정] '울산광역시' -> '한국도로공사'
if (name === '울산광역시') {
name = '한국도로공사';
}
labels.push(name);
data.push(stat.count);
});
container.innerHTML = ""; // 기존 내용 초기화
instChartInstance = new Chart(ctx, {
type: 'bar', // 막대 그래프
instStats.forEach(function(stat, index) {
// 라벨 보정
var name = stat.name;
if (name === '울산광역시') name = '한국도로공사';
// Canvas ID 생성
var inputChartId = 'inputChart_' + index;
var perfChartId = 'perfChart_' + index;
// HTML 구조 생성
var html = '';
html += '<div class="col-sm-12">';
html += ' <div class="chart-wrapper">';
html += ' <div class="chart-header">' + name + ' (총 ' + stat.count + '건)</div>';
html += ' <div class="row">';
html += ' <div class="col-sm-6">';
html += ' <div class="sub-chart-title">건설현장 프로젝트 입력상태</div>';
html += ' <div class="sub-chart-box"><canvas id="' + inputChartId + '"></canvas></div>';
html += ' </div>';
html += ' <div class="col-sm-6">';
html += ' <div class="sub-chart-title">기관별 성과 현황</div>';
html += ' <div class="sub-chart-box"><canvas id="' + perfChartId + '"></canvas></div>';
html += ' </div>';
html += ' </div>';
html += ' </div>';
html += '</div>';
// DOM에 추가
var div = document.createElement("div");
div.className = "row"; // 행 단위로 추가
div.innerHTML = html;
container.appendChild(div);
// 1) 입력상태 파이 차트 그리기
drawPieChart(inputChartId, stat.inputStatusData);
// 2) 성과현황 막대 차트 그리기
drawBarChart(perfChartId, stat.performanceData);
});
}
function drawPieChart(id, dataArr) {
var canvas = document.getElementById(id);
if(!canvas) return;
new Chart(canvas, {
type: 'pie',
data: {
labels: labels,
labels: ['미입력', '입력중', '검수대기', '검수중', '수정요청', '검수완료', '등록완료'],
datasets: [{
label: '등록 건수',
data: data,
backgroundColor: 'rgba(75, 192, 192, 0.5)', // 색상 설정
borderColor: 'rgba(75, 192, 192, 1)',
data: dataArr,
backgroundColor: [
'#e0e0e0', // 미입력 (회색)
'#ffcd56', // 입력중 (노랑)
'#36a2eb', // 검수대기 (파랑)
'#ff6384', // 검수중 (빨강)
'#ff9f40', // 수정요청 (주황)
'#4bc0c0', // 검수완료 (청록)
'#9966ff' // 등록완료 (보라)
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { position: 'right' }
}
}
});
}
function drawBarChart(id, dataArr) {
var canvas = document.getElementById(id);
if(!canvas) return;
new Chart(canvas, {
type: 'bar',
data: {
labels: ['타당성조사', '기본설계', '실시설계', '시공중', '준공', '유지보수'],
datasets: [{
label: '건수',
data: dataArr,
backgroundColor: '#36a2eb',
borderWidth: 1
}]
},
@ -187,18 +235,51 @@
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: { stepSize: 1 } // 정수 단위 표시
}
y: { beginAtZero: true, ticks: { stepSize: 1 } }
},
plugins: {
legend: { display: false }, // 범례 숨김 (단일 데이터셋이므로)
title: {
display: true,
text: '기관별 건설현장 등록 현황',
font: { size: 16 }
}
legend: { display: false }
}
}
});
}
// [하단] 전체 기관별 등록 건수 차트
function updateInstitutionChart(instStats) {
var canvas = document.getElementById('institutionChart');
if(!canvas) 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(canvas, {
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: '전체 기관별 등록 현황' }
}
}
});
@ -211,48 +292,24 @@
<div class="home-trainning">
<div class="container-fluid">
<div class="row content">
<div class="col-sm-12">
<!-- [신규] 기관별 통계 그래프 영역 추가 (하단 전체 너비) -->
<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;"> <!-- 높이 지정 -->
<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 id="institution-detail-area" style="margin-top: 30px;">
<!-- 여기에 Javascript로 기관별 그래프들이 추가됨 -->
</div>
</div>