1541 lines
102 KiB
HTML
1541 lines
102 KiB
HTML
<!doctype html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>시공 코드 조회</title>
|
||
<style>
|
||
:root {
|
||
--bg-top: #f3efe4;
|
||
--bg-bottom: #f8fbff;
|
||
--panel: rgba(255, 255, 255, 0.88);
|
||
--line: rgba(38, 56, 88, 0.14);
|
||
--text: #1b2a3a;
|
||
--muted: #607086;
|
||
--accent: #0f766e;
|
||
--accent-strong: #115e59;
|
||
--shadow: 0 24px 60px rgba(26, 43, 66, 0.12);
|
||
--mono: "JetBrains Mono", "Fira Code", monospace;
|
||
--sans: "Pretendard", "Noto Sans KR", sans-serif;
|
||
}
|
||
* { box-sizing: border-box; }
|
||
body {
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
font-family: var(--sans);
|
||
color: var(--text);
|
||
background:
|
||
radial-gradient(circle at top left, rgba(15, 118, 110, 0.16), transparent 28%),
|
||
radial-gradient(circle at top right, rgba(59, 130, 246, 0.12), transparent 22%),
|
||
linear-gradient(180deg, var(--bg-top), var(--bg-bottom));
|
||
}
|
||
.shell {
|
||
max-width: 2040px;
|
||
margin: 0 auto;
|
||
padding: 18px 18px 48px;
|
||
}
|
||
.table-card {
|
||
background: var(--panel);
|
||
border: 1px solid var(--line);
|
||
border-radius: 24px;
|
||
box-shadow: var(--shadow);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
.meta-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-top: 18px;
|
||
}
|
||
.shared-note {
|
||
margin: 0 0 14px;
|
||
padding: 12px 14px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 14px;
|
||
background: rgba(238, 248, 251, 0.92);
|
||
color: #355468;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
}
|
||
.meta-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-height: 38px;
|
||
padding: 8px 12px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--line);
|
||
background: rgba(255, 255, 255, 0.76);
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
}
|
||
.table-card {
|
||
padding: 18px;
|
||
}
|
||
.workspace {
|
||
display: grid;
|
||
grid-template-columns: 290px minmax(0, 1fr);
|
||
gap: 18px;
|
||
align-items: start;
|
||
}
|
||
.sidebar-card,
|
||
.content-card {
|
||
background: rgba(255, 255, 255, 0.92);
|
||
border: 1px solid var(--line);
|
||
border-radius: 18px;
|
||
padding: 18px;
|
||
}
|
||
.sidebar-card {
|
||
position: sticky;
|
||
top: 18px;
|
||
}
|
||
.sidebar-title,
|
||
.content-title {
|
||
margin: 0 0 8px;
|
||
font-size: 18px;
|
||
}
|
||
.sidebar-sub {
|
||
margin: 0 0 14px;
|
||
color: var(--muted);
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
.sidebar-tools {
|
||
display: grid;
|
||
gap: 10px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.sidebar-tools input,
|
||
.sidebar-tools select,
|
||
.sidebar-tools button {
|
||
width: 100%;
|
||
height: 42px;
|
||
border-radius: 12px;
|
||
border: 1px solid var(--line);
|
||
padding: 0 12px;
|
||
font-size: 13px;
|
||
font-family: var(--sans);
|
||
}
|
||
.sidebar-tools input {
|
||
background: rgba(255, 255, 255, 0.96);
|
||
}
|
||
.filter-field {
|
||
display: grid;
|
||
gap: 6px;
|
||
}
|
||
.filter-label {
|
||
padding: 0 2px;
|
||
color: #4d6276;
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
}
|
||
.sidebar-tools select {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
-moz-appearance: none;
|
||
height: 46px;
|
||
padding: 0 42px 0 14px;
|
||
border-radius: 14px;
|
||
background:
|
||
linear-gradient(180deg, rgba(255,255,255,0.98), rgba(242,248,248,0.96)),
|
||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%230f766e' stroke-width='2.3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
||
background-repeat: no-repeat, no-repeat;
|
||
background-position: 0 0, calc(100% - 14px) 50%;
|
||
background-size: auto, 16px;
|
||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.7), 0 8px 20px rgba(15, 118, 110, 0.06);
|
||
color: #173247;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
|
||
}
|
||
.sidebar-tools select:hover,
|
||
.sidebar-tools input:hover {
|
||
border-color: rgba(15, 118, 110, 0.28);
|
||
}
|
||
.sidebar-tools select:focus,
|
||
.sidebar-tools input:focus {
|
||
outline: none;
|
||
border-color: rgba(15, 118, 110, 0.58);
|
||
box-shadow: 0 0 0 4px rgba(15, 118, 110, 0.12);
|
||
}
|
||
.sidebar-tools button {
|
||
background: linear-gradient(135deg, var(--accent), var(--accent-strong));
|
||
color: #fff;
|
||
font-weight: 700;
|
||
border: 0;
|
||
cursor: pointer;
|
||
}
|
||
.status {
|
||
margin: 0 0 14px;
|
||
color: var(--muted);
|
||
font-size: 14px;
|
||
}
|
||
.table-wrap {
|
||
overflow: auto;
|
||
border: 1px solid var(--line);
|
||
border-radius: 18px;
|
||
background: rgba(255, 255, 255, 0.86);
|
||
max-height: 72vh;
|
||
}
|
||
table {
|
||
width: 100%;
|
||
min-width: 640px;
|
||
border-collapse: collapse;
|
||
}
|
||
thead th {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 1;
|
||
background: #eef6f6;
|
||
color: #274254;
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.12em;
|
||
}
|
||
th, td {
|
||
padding: 14px 16px;
|
||
border-bottom: 1px solid rgba(38, 56, 88, 0.08);
|
||
text-align: left;
|
||
vertical-align: top;
|
||
font-size: 14px;
|
||
}
|
||
tbody tr:hover {
|
||
background: rgba(15, 118, 110, 0.05);
|
||
}
|
||
tbody tr.project-row {
|
||
cursor: pointer;
|
||
}
|
||
tbody tr.project-row.selected {
|
||
background: rgba(15, 118, 110, 0.12);
|
||
}
|
||
.code {
|
||
font-family: var(--mono);
|
||
font-weight: 700;
|
||
color: #17496f;
|
||
white-space: nowrap;
|
||
}
|
||
.empty {
|
||
padding: 42px 18px;
|
||
text-align: center;
|
||
color: var(--muted);
|
||
font-size: 14px;
|
||
}
|
||
.detail-grid {
|
||
display: grid;
|
||
gap: 18px;
|
||
grid-template-columns: minmax(0, 1fr);
|
||
margin-top: 18px;
|
||
}
|
||
.detail-card {
|
||
background: rgba(255, 255, 255, 0.92);
|
||
border: 1px solid var(--line);
|
||
border-radius: 18px;
|
||
padding: 18px;
|
||
}
|
||
.detail-head {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex-wrap: wrap;
|
||
margin-bottom: 14px;
|
||
}
|
||
.detail-title {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
}
|
||
.detail-sub {
|
||
margin: 6px 0 0;
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
}
|
||
.detail-actions button {
|
||
height: 40px;
|
||
border-radius: 12px;
|
||
border: 1px solid var(--line);
|
||
padding: 0 14px;
|
||
background: #f3faf9;
|
||
color: var(--accent-strong);
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
}
|
||
.kv-table {
|
||
width: 100%;
|
||
min-width: 0;
|
||
border-collapse: collapse;
|
||
}
|
||
.kv-table th,
|
||
.kv-table td {
|
||
padding: 12px 14px;
|
||
border-bottom: 1px solid rgba(38, 56, 88, 0.08);
|
||
text-align: left;
|
||
font-size: 14px;
|
||
}
|
||
.kv-table th {
|
||
width: 180px;
|
||
background: #f5f8fb;
|
||
color: #425466;
|
||
}
|
||
.merge-table {
|
||
width: 100%;
|
||
min-width: 1500px;
|
||
border-collapse: collapse;
|
||
table-layout: fixed;
|
||
}
|
||
.merge-table th,
|
||
.merge-table td {
|
||
padding: 12px 14px;
|
||
border-bottom: 1px solid rgba(38, 56, 88, 0.08);
|
||
text-align: center;
|
||
vertical-align: top;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
.merge-table th {
|
||
background: #eef6f6;
|
||
color: #274254;
|
||
font-size: 12px;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
white-space: nowrap;
|
||
}
|
||
.merge-table thead tr:first-child th {
|
||
text-align: center;
|
||
border-bottom: 1px solid rgba(38, 56, 88, 0.08);
|
||
}
|
||
.merge-table thead tr:nth-child(2) th {
|
||
text-align: center;
|
||
font-size: 11px;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.merge-table td {
|
||
white-space: normal;
|
||
overflow: visible;
|
||
text-overflow: clip;
|
||
word-break: keep-all;
|
||
}
|
||
.bridge-summary {
|
||
white-space: normal;
|
||
word-break: keep-all;
|
||
overflow: visible;
|
||
text-overflow: clip;
|
||
}
|
||
.cell-num {
|
||
white-space: normal;
|
||
text-align: center;
|
||
word-break: break-word;
|
||
line-height: 1.35;
|
||
}
|
||
.cell-text {
|
||
text-align: center;
|
||
}
|
||
.cell-wide {
|
||
text-align: left;
|
||
}
|
||
.remark-button {
|
||
width: 32px;
|
||
height: 32px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 999px;
|
||
background: #f3faf9;
|
||
color: var(--accent-strong);
|
||
font-size: 16px;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
}
|
||
.remark-button:hover {
|
||
filter: brightness(0.97);
|
||
}
|
||
.remark-button:disabled {
|
||
opacity: 0.45;
|
||
cursor: default;
|
||
}
|
||
.map-button {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 48px;
|
||
height: 28px;
|
||
margin-top: 6px;
|
||
padding: 0 10px;
|
||
border: 1px solid rgba(15, 118, 110, 0.24);
|
||
border-radius: 999px;
|
||
background: #e9f8f5;
|
||
color: var(--accent-strong);
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
.map-button:hover {
|
||
filter: brightness(0.96);
|
||
}
|
||
.map-location {
|
||
display: grid;
|
||
gap: 4px;
|
||
justify-items: start;
|
||
}
|
||
.modal-backdrop {
|
||
position: fixed;
|
||
inset: 0;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 20px;
|
||
background: rgba(20, 30, 40, 0.42);
|
||
z-index: 50;
|
||
}
|
||
.modal-backdrop.open {
|
||
display: flex;
|
||
}
|
||
.modal-card {
|
||
width: min(560px, 100%);
|
||
background: rgba(255, 255, 255, 0.98);
|
||
border: 1px solid var(--line);
|
||
border-radius: 20px;
|
||
box-shadow: var(--shadow);
|
||
padding: 20px;
|
||
}
|
||
.modal-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
.modal-title {
|
||
margin: 0;
|
||
font-size: 18px;
|
||
}
|
||
.modal-close {
|
||
width: 36px;
|
||
height: 36px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 999px;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
}
|
||
.modal-body {
|
||
color: var(--text);
|
||
font-size: 14px;
|
||
line-height: 1.7;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
.plan-button {
|
||
height: 32px;
|
||
padding: 0 10px;
|
||
border: 1px solid var(--line);
|
||
border-radius: 999px;
|
||
background: #eef6f6;
|
||
color: #17496f;
|
||
font-size: 12px;
|
||
font-weight: 800;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
.plan-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 12px;
|
||
}
|
||
.plan-table th,
|
||
.plan-table td {
|
||
padding: 8px 10px;
|
||
border: 1px solid rgba(38, 56, 88, 0.08);
|
||
font-size: 13px;
|
||
text-align: left;
|
||
vertical-align: top;
|
||
}
|
||
.plan-table th {
|
||
background: #f4f8fb;
|
||
color: #425466;
|
||
width: 140px;
|
||
}
|
||
.plan-section-title {
|
||
margin: 18px 0 8px;
|
||
font-size: 14px;
|
||
font-weight: 800;
|
||
color: #17496f;
|
||
}
|
||
.inline-plan-wrap {
|
||
padding: 10px 0;
|
||
}
|
||
.inline-plan-wrap .plan-table {
|
||
margin-top: 0;
|
||
}
|
||
.inline-plan-wrap .plan-table th,
|
||
.inline-plan-wrap .plan-table td {
|
||
text-align: center;
|
||
}
|
||
.inline-plan-wrap .plan-table td.plan-left,
|
||
.inline-plan-wrap .plan-table th.plan-left {
|
||
text-align: left;
|
||
}
|
||
@media (max-width: 720px) {
|
||
.shell { padding: 18px 12px 32px; }
|
||
.table-card { border-radius: 20px; }
|
||
.workspace { grid-template-columns: 1fr; }
|
||
.sidebar-card { position: static; }
|
||
.table-wrap { max-height: none; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<main class="shell">
|
||
<div class="shared-note">`8092` 페이지입니다. 시공코드 목록, 계약정보, 공사개요, 교량 매칭 정보를 조회하는 전용 화면입니다.</div>
|
||
<section class="table-card">
|
||
<div class="meta-row" style="margin: 0 0 14px;">
|
||
<div class="meta-chip" id="countChip">데이터 대기 중</div>
|
||
<div class="meta-chip" id="sourceChip">DB 캐시 확인 중</div>
|
||
</div>
|
||
<div class="workspace">
|
||
<aside class="sidebar-card">
|
||
<h2 class="sidebar-title">프로젝트 목록</h2>
|
||
<p class="sidebar-sub">왼쪽 목록에서 시공코드나 약칭을 누르면 오른쪽에 계약정보 표가 표시됩니다.</p>
|
||
<div class="sidebar-tools">
|
||
<input id="searchInput" type="search" placeholder="시공코드 또는 약칭 검색">
|
||
<div class="filter-field">
|
||
<label class="filter-label" for="contractTypeFilter">계약종류</label>
|
||
<select id="contractTypeFilter">
|
||
<option value="">전체 보기</option>
|
||
</select>
|
||
</div>
|
||
<div class="filter-field">
|
||
<label class="filter-label" for="applicationTypeFilter">적용형식</label>
|
||
<select id="applicationTypeFilter">
|
||
<option value="">전체 보기</option>
|
||
</select>
|
||
</div>
|
||
<button id="reloadButton" type="button">시공코드 목록 새로 가져오기</button>
|
||
</div>
|
||
<p class="status" id="statusText">프로젝트 목록을 불러오는 중입니다.</p>
|
||
<div class="table-wrap">
|
||
<table style="min-width: 0;">
|
||
<thead>
|
||
<tr>
|
||
<th style="width: 150px;">시공코드</th>
|
||
<th>약칭</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="resultBody">
|
||
<tr><td colspan="2" class="empty">불러오는 중...</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</aside>
|
||
|
||
<div class="content-card">
|
||
<div class="detail-grid">
|
||
<div class="detail-card" style="padding:0; border:none; background:transparent;">
|
||
<div class="detail-head">
|
||
<div>
|
||
<h2 class="content-title" id="detailTitle">계약정보 표</h2>
|
||
<p class="detail-sub" id="detailMeta">시공코드나 약칭을 클릭하면 DB에 저장된 계약정보를 먼저 보여줍니다.</p>
|
||
</div>
|
||
<div class="detail-actions">
|
||
<button id="detailSyncButton" type="button">새 정보 다시 가져오기</button>
|
||
</div>
|
||
</div>
|
||
<table class="kv-table">
|
||
<tbody id="detailBody">
|
||
<tr><td colspan="2" class="empty">선택된 시공코드가 없습니다.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="detail-card">
|
||
<div class="detail-head">
|
||
<div>
|
||
<h2 class="content-title">교량 매칭 정보</h2>
|
||
<p class="detail-sub" id="bridgeMeta">공사규모와 공사개요를 교량명 기준으로 매칭해서 보여줍니다.</p>
|
||
</div>
|
||
</div>
|
||
<div class="table-wrap" style="max-height:none;">
|
||
<table class="merge-table">
|
||
<colgroup>
|
||
<col style="width: 110px;">
|
||
<col style="width: 110px;">
|
||
<col style="width: 96px;">
|
||
<col style="width: 120px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 84px;">
|
||
<col style="width: 84px;">
|
||
<col style="width: 64px;">
|
||
<col style="width: 64px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 58px;">
|
||
<col style="width: 240px;">
|
||
<col style="width: 64px;">
|
||
</colgroup>
|
||
<thead>
|
||
<tr>
|
||
<th rowspan="2">교량명</th>
|
||
<th rowspan="2">적용형식</th>
|
||
<th rowspan="2">시공상태</th>
|
||
<th rowspan="2">공사기간</th>
|
||
<th colspan="2">연장</th>
|
||
<th colspan="2">폭원</th>
|
||
<th colspan="2">형고</th>
|
||
<th colspan="2">경간구성</th>
|
||
<th rowspan="2">GIRDER</th>
|
||
<th rowspan="2">가로보</th>
|
||
<th rowspan="2">패널</th>
|
||
<th rowspan="2">철근</th>
|
||
<th rowspan="2">교량위치</th>
|
||
<th rowspan="2">특이사항</th>
|
||
</tr>
|
||
<tr>
|
||
<th>하행</th>
|
||
<th>상행</th>
|
||
<th>하행</th>
|
||
<th>상행</th>
|
||
<th>지점부</th>
|
||
<th>중앙부</th>
|
||
<th>하행</th>
|
||
<th>상행</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="bridgeBody">
|
||
<tr><td colspan="19" class="empty">선택된 시공코드가 없습니다.</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
<div class="modal-backdrop" id="remarkModal" aria-hidden="true">
|
||
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="remarkModalTitle">
|
||
<div class="modal-head">
|
||
<h3 class="modal-title" id="remarkModalTitle">특이사항</h3>
|
||
<button class="modal-close" id="remarkModalClose" type="button" aria-label="팝업 닫기">×</button>
|
||
</div>
|
||
<div class="modal-body" id="remarkModalBody">내용이 없습니다.</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-backdrop" id="planModal" aria-hidden="true">
|
||
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="planModalTitle">
|
||
<div class="modal-head">
|
||
<h3 class="modal-title" id="planModalTitle">공사시행계획서</h3>
|
||
<button class="modal-close" id="planModalClose" type="button" aria-label="팝업 닫기">×</button>
|
||
</div>
|
||
<div class="modal-body" id="planModalBody">내용이 없습니다.</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const LINKED_CODE_BY_BUSINESS = {"26-002":{"salesCode":"26-교영-02","designCode":"26-설계-02","constCode":""},"26-004":{"salesCode":"26-교영-04","designCode":"26-설계-03","constCode":"26-시공-03"},"25-003":{"salesCode":"25-교영-02","designCode":"25-설계-01","constCode":"25-시공-01"},"25-005":{"salesCode":"25-교영-04","designCode":"25-설계-02","constCode":"25-시공-37"},"25-008":{"salesCode":"25-교영-07","designCode":"25-설계-04","constCode":"25-시공-08"},"25-009":{"salesCode":"25-교영-09","designCode":"25-설계-05","constCode":"26-시공-02"},"25-004":{"salesCode":"25-교영-03","designCode":"25-설계-06","constCode":""},"25-032":{"salesCode":"25-교영-32","designCode":"26-설계-01","constCode":""},"25-028":{"salesCode":"25-교영-28","designCode":"26-설계-04","constCode":""},"24-035":{"salesCode":"24-교영-35","designCode":"24-설계-01","constCode":""},"24-034":{"salesCode":"24-교영-34","designCode":"24-설계-03","constCode":""},"24-026":{"salesCode":"24-교영-26","designCode":"24-설계-05","constCode":""},"24-039":{"salesCode":"24-교영-39","designCode":"24-설계-09","constCode":"25-시공-13"},"24-044":{"salesCode":"24-교영-44","designCode":"24-설계-14","constCode":"25-시공-30"},"24-056":{"salesCode":"24-교영-85","designCode":"24-설계-31","constCode":"24-시공-24"},"24-061":{"salesCode":"24-교영-63","designCode":"24-설계-20","constCode":"24-시공-25"},"24-068":{"salesCode":"24-교영-69","designCode":"24-설계-21","constCode":"24-시공-27"},"24-072":{"salesCode":"24-교영-75","designCode":"24-설계-23","constCode":""},"24-074":{"salesCode":"24-교영-77","designCode":"24-설계-24","constCode":""},"24-078":{"salesCode":"24-교영-81","designCode":"24-설계-25","constCode":"26-시공-01"},"24-079":{"salesCode":"24-교영-82","designCode":"24-설계-26","constCode":"24-시공-33"},"24-080":{"salesCode":"24-교영-83","designCode":"24-설계-27","constCode":""},"24-081":{"salesCode":"24-교영-86","designCode":"24-설계-30","constCode":""},"24-064":{"salesCode":"24-교영-65","designCode":"24-설계-32","constCode":""},"24-066":{"salesCode":"24-교영-67","designCode":"24-설계-33","constCode":""},"24-067":{"salesCode":"24-교영-68","designCode":"24-설계-34","constCode":""},"24-017":{"salesCode":"24-교영-17","designCode":"24-설계-35","constCode":""},"24-020":{"salesCode":"24-교영-20","designCode":"24-설계-36","constCode":""},"24-084":{"salesCode":"24-교영-89","designCode":"24-설계-37","constCode":""},"24-063":{"salesCode":"24-교영-64","designCode":"25-설계-03","constCode":""},"24-073":{"salesCode":"24-교영-76","designCode":"25-설계-07","constCode":"25-시공-15"},"24-024":{"salesCode":"24-교영-24","designCode":"25-설계-09","constCode":""},"24-019":{"salesCode":"24-교영-19","designCode":"25-설계-11","constCode":"25-시공-24"},"23-042":{"salesCode":"23-교영-38","designCode":"24-설계-28","constCode":""},"23-004":{"salesCode":"23-교영-04","designCode":"23-설계-07","constCode":""},"23-008":{"salesCode":"23-교영-08","designCode":"23-설계-08","constCode":"25-시공-32"},"23-003":{"salesCode":"23-교영-03","designCode":"23-설계-10","constCode":""},"23-019":{"salesCode":"23-교영-19","designCode":"23-설계-12","constCode":""},"23-020":{"salesCode":"23-교영-20","designCode":"23-설계-13","constCode":""},"23-035":{"salesCode":"23-교영-35","designCode":"23-설계-14","constCode":"24-시공-21"},"23-030":{"salesCode":"23-교영-30","designCode":"24-설계-02","constCode":""},"23-037":{"salesCode":"23-교영-37","designCode":"24-설계-18","constCode":""},"24-048":{"salesCode":"23-교영-37","designCode":"24-설계-18","constCode":""},"23-041":{"salesCode":"23-교영-39","designCode":"24-설계-29","constCode":""},"22-046":{"salesCode":"22-교영-46","designCode":"22-설계-01","constCode":""},"22-016":{"salesCode":"22-교영-16","designCode":"22-설계-02","constCode":"23-시공-27"},"22-045":{"salesCode":"22-교영-45","designCode":"22-설계-03","constCode":""},"22-017":{"salesCode":"22-교영-17","designCode":"22-설계-04","constCode":""},"22-014":{"salesCode":"22-교영-14","designCode":"22-설계-05","constCode":""},"22-044":{"salesCode":"22-교영-44","designCode":"22-설계-06","constCode":""},"22-043":{"salesCode":"22-교영-43","designCode":"22-설계-07","constCode":""},"22-011":{"salesCode":"22-교영-11","designCode":"22-설계-08","constCode":""},"22-041":{"salesCode":"22-교영-41","designCode":"22-설계-10","constCode":""},"22-033":{"salesCode":"22-교영-33","designCode":"23-설계-01","constCode":"25-시공-33"},"22-027":{"salesCode":"22-교영-27","designCode":"23-설계-02","constCode":""},"22-012":{"salesCode":"22-교영-12","designCode":"23-설계-03","constCode":""},"22-039":{"salesCode":"22-교영-39","designCode":"23-설계-04","constCode":"25-시공-31"},"22-038":{"salesCode":"22-교영-38","designCode":"23-설계-11","constCode":""},"22-036":{"salesCode":"22-교영-36","designCode":"25-설계-10","constCode":""},"21-011":{"salesCode":"21-교영-11","designCode":"21-설계-02","constCode":"25-시공-28"},"21-006":{"salesCode":"21-교영-06","designCode":"21-설계-03","constCode":""},"21-004":{"salesCode":"21-교영-04","designCode":"21-설계-04","constCode":""},"21-015":{"salesCode":"21-교영-15","designCode":"21-설계-05","constCode":""},"21-005":{"salesCode":"21-교영-05","designCode":"21-설계-06","constCode":""},"21-008":{"salesCode":"21-교영-08","designCode":"21-설계-07","constCode":""},"21-027":{"salesCode":"21-교영-27","designCode":"21-설계-08","constCode":"24-시공-09"},"21-003":{"salesCode":"21-교영-03","designCode":"21-설계-09","constCode":""},"21-007":{"salesCode":"21-교영-07","designCode":"21-설계-10","constCode":""},"21-034":{"salesCode":"21-교영-34","designCode":"21-설계-11","constCode":"25-시공-29"},"21-016":{"salesCode":"21-교영-16","designCode":"21-설계-12","constCode":"25-시공-19"},"21-029":{"salesCode":"21-교영-29","designCode":"23-설계-09","constCode":""},"20-008":{"salesCode":"20-교영-08","designCode":"20-설계-01","constCode":""},"20-010":{"salesCode":"20-교영-07","designCode":"20-설계-02","constCode":""},"20-015":{"salesCode":"20-교영-15","designCode":"20-설계-03","constCode":""},"20-017":{"salesCode":"20-교영-17","designCode":"20-설계-04","constCode":""},"20-030":{"salesCode":"20-교영-30","designCode":"20-설계-05","constCode":""},"20-029":{"salesCode":"20-교영-29","designCode":"21-설계-01","constCode":""},"20-002":{"salesCode":"20-교영-02","designCode":"26-설계-05","constCode":"26-시공-04"},"19-002":{"salesCode":"19-교영-02","designCode":"19-설계-02","constCode":"24-시공-18"},"19-004":{"salesCode":"19-교영-04","designCode":"19-설계-06","constCode":""},"19-005":{"salesCode":"19-교영-05","designCode":"19-설계-07","constCode":"21-시공-04"},"19-019":{"salesCode":"19-교영-19","designCode":"19-설계-08","constCode":""},"19-006":{"salesCode":"19-교영-06","designCode":"19-설계-09","constCode":"24-시공-15"},"19-018":{"salesCode":"24-교영-70","designCode":"24-설계-22","constCode":"24-시공-29"},"19-022":{"salesCode":"19-교영-22","designCode":"19-설계-14","constCode":""},"18-037":{"salesCode":"18-교영-37","designCode":"18-설계-17","constCode":"23-시공-17"},"18-040":{"salesCode":"18-교영-40","designCode":"18-설계-04","constCode":""},"18-039":{"salesCode":"18-교영-39","designCode":"18-설계-07","constCode":""},"18-002":{"salesCode":"18-교영-02","designCode":"18-설계-09","constCode":"23-시공-28"},"18-012":{"salesCode":"18-교영-12","designCode":"18-설계-11","constCode":"22-시공-27"},"18-005":{"salesCode":"18-교영-05","designCode":"18-설계-12","constCode":"25-시공-22"},"18-038":{"salesCode":"18-교영-38","designCode":"18-설계-13","constCode":"19-시공-05"},"18-014":{"salesCode":"18-교영-14","designCode":"18-설계-14","constCode":""},"18-008":{"salesCode":"18-교영-08","designCode":"18-설계-15","constCode":"21-시공-14"},"18-021":{"salesCode":"18-교영-21","designCode":"18-설계-16","constCode":"22-시공-18"},"18-036":{"salesCode":"18-교영-36","designCode":"18-설계-18","constCode":""},"18-006":{"salesCode":"18-교영-06","designCode":"18-설계-19","constCode":"21-시공-27"},"18-017":{"salesCode":"18-교영-17","designCode":"19-설계-01","constCode":"25-시공-07"},"18-011":{"salesCode":"18-교영-11","designCode":"19-설계-03","constCode":"25-시공-16"},"18-032":{"salesCode":"18-교영-32","designCode":"19-설계-04","constCode":""},"18-018":{"salesCode":"18-교영-18","designCode":"19-설계-05","constCode":""},"18-025":{"salesCode":"18-교영-25","designCode":"19-설계-11","constCode":""},"18-026":{"salesCode":"18-교영-26","designCode":"19-설계-12","constCode":"23-시공-34"},"18-033":{"salesCode":"18-교영-33","designCode":"18-설계-20","constCode":""},"17-039":{"salesCode":"17-교영-39","designCode":"17-설계-06","constCode":"18-시공-15"},"17-040":{"salesCode":"17-교영-40","designCode":"17-설계-07","constCode":"21-시공-20"},"17-008":{"salesCode":"17-교영-08","designCode":"17-설계-08","constCode":"20-시공-22"},"17-004":{"salesCode":"17-교영-04","designCode":"17-설계-09","constCode":"20-시공-30"},"17-012":{"salesCode":"17-교영-12","designCode":"17-설계-10","constCode":"17-시공-26"},"17-021":{"salesCode":"17-교영-21","designCode":"17-설계-14","constCode":""},"17-035":{"salesCode":"17-교영-35","designCode":"17-설계-15","constCode":""},"17-036":{"salesCode":"17-교영-36","designCode":"17-설계-16","constCode":"19-시공-24"},"17-030":{"salesCode":"17-교영-30","designCode":"18-설계-02","constCode":""},"17-033":{"salesCode":"17-교영-33","designCode":"18-설계-05","constCode":""},"17-027":{"salesCode":"17-교영-27","designCode":"18-설계-06","constCode":""},"17-031":{"salesCode":"17-교영-31","designCode":"18-설계-08","constCode":"24-시공-17"},"17-034":{"salesCode":"17-교영-34","designCode":"18-설계-10","constCode":""},"17-037":{"salesCode":"17-교영-37","designCode":"17-설계-17","constCode":""},"17-038":{"salesCode":"17-교영-38","designCode":"17-설계-18","constCode":""},"17-026":{"salesCode":"17-교영-26","designCode":"19-설계-10","constCode":"23-시공-31"},"16-062":{"salesCode":"16-교영-62","designCode":"16-설계-01","constCode":"20-시공-01"},"16-057":{"salesCode":"16-교영-57","designCode":"16-설계-03","constCode":"16-시공-39"},"16-058":{"salesCode":"16-교영-58","designCode":"16-설계-04","constCode":"16-시공-36"},"16-016":{"salesCode":"16-교영-16","designCode":"16-설계-05","constCode":"17-시공-16"},"16-059":{"salesCode":"16-교영-59","designCode":"16-설계-06","constCode":""},"16-012":{"salesCode":"16-교영-12","designCode":"16-설계-07","constCode":"16-시공-40"},"16-004":{"salesCode":"16-교영-04","designCode":"16-설계-09","constCode":"21-시공-11"},"16-060":{"salesCode":"16-교영-60","designCode":"16-설계-10","constCode":""},"16-013":{"salesCode":"16-교영-13","designCode":"16-설계-11","constCode":"20-시공-07"},"16-027":{"salesCode":"16-교영-27","designCode":"16-설계-12","constCode":""},"16-006":{"salesCode":"16-교영-06","designCode":"16-설계-13","constCode":"20-시공-18"},"16-061":{"salesCode":"16-교영-61","designCode":"16-설계-15","constCode":""},"16-041":{"salesCode":"16-교영-41","designCode":"16-설계-16","constCode":"17-시공-04"},"16-022":{"salesCode":"16-교영-22","designCode":"16-설계-17","constCode":"20-시공-21"},"16-014":{"salesCode":"16-교영-14","designCode":"16-설계-18","constCode":"21-시공-07"},"16-024":{"salesCode":"16-교영-24","designCode":"16-설계-19","constCode":"18-시공-02"},"16-046":{"salesCode":"16-교영-46","designCode":"16-설계-20","constCode":"17-시공-06"},"16-025":{"salesCode":"16-교영-25","designCode":"16-설계-21","constCode":"24-시공-30"},"16-003":{"salesCode":"16-교영-03","designCode":"17-설계-01","constCode":"22-시공-30"},"16-048":{"salesCode":"16-교영-48","designCode":"17-설계-02","constCode":"20-시공-38"},"16-047":{"salesCode":"16-교영-47","designCode":"17-설계-03","constCode":"23-시공-37"},"16-039":{"salesCode":"16-교영-39","designCode":"17-설계-04","constCode":"20-시공-15"},"16-049":{"salesCode":"16-교영-49","designCode":"17-설계-05","constCode":"21-시공-12"},"16-033":{"salesCode":"16-교영-33","designCode":"17-설계-13","constCode":"21-시공-10"},"16-056":{"salesCode":"16-교영-56","designCode":"18-설계-03","constCode":""},"16-040":{"salesCode":"16-교영-40","designCode":"25-설계-08","constCode":"25-시공-18"},"15-030":{"salesCode":"15-교영-30","designCode":"15-설계-01","constCode":"19-시공-18"},"15-031":{"salesCode":"15-교영-31","designCode":"15-설계-03","constCode":"17-시공-12"},"15-002":{"salesCode":"15-교영-02","designCode":"15-설계-04","constCode":""},"15-001":{"salesCode":"15-교영-01","designCode":"15-설계-05","constCode":""},"15-032":{"salesCode":"15-교영-32","designCode":"15-설계-06","constCode":""},"15-033":{"salesCode":"15-교영-33","designCode":"15-설계-07","constCode":"24-시공-19"},"15-034":{"salesCode":"15-교영-34","designCode":"15-설계-08","constCode":"17-시공-20"},"15-035":{"salesCode":"15-교영-35","designCode":"15-설계-09","constCode":"19-시공-03"},"15-036":{"salesCode":"15-교영-36","designCode":"15-설계-10","constCode":"16-시공-41"},"15-003":{"salesCode":"15-교영-03","designCode":"15-설계-13","constCode":"20-시공-31"},"15-037":{"salesCode":"15-교영-37","designCode":"15-설계-14","constCode":""},"15-024":{"salesCode":"15-교영-24","designCode":"15-설계-15","constCode":"17-시공-18"},"15-038":{"salesCode":"15-교영-38","designCode":"15-설계-16","constCode":""},"15-039":{"salesCode":"15-교영-39","designCode":"15-설계-17","constCode":"16-시공-09"},"15-040":{"salesCode":"15-교영-40","designCode":"15-설계-18","constCode":"17-시공-01"},"15-041":{"salesCode":"15-교영-41","designCode":"15-설계-19","constCode":""},"15-042":{"salesCode":"15-교영-42","designCode":"15-설계-20","constCode":""},"15-004":{"salesCode":"15-교영-04","designCode":"15-설계-21","constCode":"20-시공-36"},"15-043":{"salesCode":"15-교영-43","designCode":"15-설계-22","constCode":""},"15-027":{"salesCode":"15-교영-27","designCode":"15-설계-23","constCode":""},"15-044":{"salesCode":"15-교영-44","designCode":"15-설계-24","constCode":""},"15-025":{"salesCode":"15-교영-25","designCode":"15-설계-25","constCode":""},"15-045":{"salesCode":"15-교영-45","designCode":"15-설계-26","constCode":""},"15-046":{"salesCode":"15-교영-46","designCode":"15-설계-27","constCode":"19-시공-07"},"15-047":{"salesCode":"15-교영-47","designCode":"15-설계-28","constCode":"16-시공-32"},"15-048":{"salesCode":"15-교영-48","designCode":"15-설계-29","constCode":""},"15-049":{"salesCode":"15-교영-49","designCode":"15-설계-30","constCode":""},"15-050":{"salesCode":"15-교영-50","designCode":"15-설계-31","constCode":"18-시공-12"},"15-051":{"salesCode":"15-교영-51","designCode":"15-설계-32","constCode":"17-시공-17"},"15-052":{"salesCode":"15-교영-52","designCode":"15-설계-33","constCode":"20-시공-12"},"15-011":{"salesCode":"15-교영-11","designCode":"17-설계-11","constCode":""},"15-029":{"salesCode":"15-교영-29","designCode":"17-설계-12","constCode":"21-시공-31"},"14-029":{"salesCode":"14-교영-29","designCode":"14-설계-04","constCode":"15-시공-40"},"14-030":{"salesCode":"14-교영-30","designCode":"14-설계-06","constCode":""},"14-031":{"salesCode":"14-교영-31","designCode":"14-설계-07","constCode":"17-시공-13"},"14-032":{"salesCode":"14-교영-32","designCode":"14-설계-08","constCode":""},"14-033":{"salesCode":"14-교영-33","designCode":"14-설계-09","constCode":"14-시공-36"},"14-034":{"salesCode":"14-교영-34","designCode":"14-설계-10","constCode":""},"14-035":{"salesCode":"14-교영-35","designCode":"14-설계-11","constCode":""},"14-036":{"salesCode":"14-교영-36","designCode":"14-설계-12","constCode":"14-시공-31"},"14-037":{"salesCode":"14-교영-37","designCode":"14-설계-13","constCode":"15-시공-03"},"14-038":{"salesCode":"14-교영-38","designCode":"14-설계-14","constCode":"15-시공-23"},"14-039":{"salesCode":"14-교영-39","designCode":"14-설계-15","constCode":"15-시공-33"},"14-040":{"salesCode":"14-교영-40","designCode":"14-설계-16","constCode":"16-시공-27"},"14-041":{"salesCode":"14-교영-41","designCode":"14-설계-17","constCode":"15-시공-16"},"14-042":{"salesCode":"14-교영-42","designCode":"14-설계-18","constCode":"17-시공-02"},"14-043":{"salesCode":"14-교영-43","designCode":"14-설계-19","constCode":""},"14-044":{"salesCode":"14-교영-44","designCode":"14-설계-20","constCode":"17-시공-11"},"14-045":{"salesCode":"14-교영-45","designCode":"14-설계-21","constCode":"16-시공-01"},"14-013":{"salesCode":"14-교영-13","designCode":"14-설계-22","constCode":""},"14-005":{"salesCode":"14-교영-05","designCode":"14-설계-23","constCode":"17-시공-10"},"14-046":{"salesCode":"14-교영-46","designCode":"14-설계-24","constCode":"17-시공-22"},"14-016":{"salesCode":"14-교영-16","designCode":"14-설계-25","constCode":""},"14-047":{"salesCode":"14-교영-47","designCode":"14-설계-26","constCode":"20-시공-19"},"14-048":{"salesCode":"14-교영-48","designCode":"14-설계-27","constCode":"16-시공-28"},"14-049":{"salesCode":"14-교영-49","designCode":"14-설계-28","constCode":"16-시공-18"},"14-050":{"salesCode":"14-교영-50","designCode":"14-설계-29","constCode":"14-시공-32"},"14-051":{"salesCode":"14-교영-51","designCode":"14-설계-30","constCode":"16-시공-25"},"14-053":{"salesCode":"14-교영-53","designCode":"14-설계-32","constCode":"19-시공-22"},"14-052":{"salesCode":"14-교영-52","designCode":"14-설계-31","constCode":"16-시공-24"},"14-062":{"salesCode":"14-교영-62","designCode":"14-설계-33","constCode":"19-시공-25"},"14-054":{"salesCode":"14-교영-54","designCode":"14-설계-34","constCode":"16-시공-37"},"14-055":{"salesCode":"14-교영-55","designCode":"14-설계-35","constCode":""},"14-056":{"salesCode":"14-교영-56","designCode":"14-설계-37","constCode":"16-시공-15"},"14-024":{"salesCode":"14-교영-24","designCode":"14-설계-38","constCode":"19-시공-15"},"14-057":{"salesCode":"14-교영-57","designCode":"14-설계-39","constCode":"15-시공-20"},"14-060":{"salesCode":"14-교영-60","designCode":"14-설계-42","constCode":"16-시공-34"},"14-058":{"salesCode":"14-교영-58","designCode":"14-설계-40","constCode":""},"14-059":{"salesCode":"14-교영-59","designCode":"14-설계-41","constCode":""},"14-061":{"salesCode":"14-교영-61","designCode":"14-설계-43","constCode":""},"14-022":{"salesCode":"14-교영-22","designCode":"15-설계-02","constCode":"16-시공-03"},"14-023":{"salesCode":"14-교영-23","designCode":"16-설계-08","constCode":"21-시공-13"},"13-045":{"salesCode":"13-교영-45","designCode":"08-설계-24","constCode":"13-시공-18"},"13-009":{"salesCode":"13-교영-09","designCode":"11-설계-21","constCode":"13-시공-01"},"13-044":{"salesCode":"13-교영-44","designCode":"12-설계-21","constCode":"16-시공-12"},"13-039":{"salesCode":"13-교영-39","designCode":"12-설계-51","constCode":"14-시공-23"},"13-030":{"salesCode":"13-교영-30","designCode":"12-설계-53","constCode":""},"13-047":{"salesCode":"13-교영-47","designCode":"13-설계-01","constCode":"23-시공-30"},"13-011":{"salesCode":"13-교영-11","designCode":"13-설계-02","constCode":""},"13-055":{"salesCode":"13-교영-55","designCode":"13-설계-03","constCode":"14-시공-22"},"13-056":{"salesCode":"13-교영-56","designCode":"13-설계-04","constCode":""},"13-022":{"salesCode":"13-교영-22","designCode":"13-설계-05","constCode":""},"13-020":{"salesCode":"13-교영-20","designCode":"13-설계-06","constCode":"15-시공-26"},"13-057":{"salesCode":"13-교영-57","designCode":"13-설계-07","constCode":"13-시공-26"},"13-058":{"salesCode":"13-교영-58","designCode":"13-설계-08","constCode":"14-시공-01"},"13-059":{"salesCode":"13-교영-59","designCode":"13-설계-09","constCode":""},"13-060":{"salesCode":"13-교영-60","designCode":"13-설계-10","constCode":"15-시공-24"},"13-061":{"salesCode":"13-교영-61","designCode":"13-설계-11","constCode":"15-시공-38"},"13-062":{"salesCode":"13-교영-62","designCode":"13-설계-13","constCode":""},"13-063":{"salesCode":"13-교영-63","designCode":"13-설계-14","constCode":"15-시공-37"},"13-064":{"salesCode":"13-교영-64","designCode":"13-설계-15","constCode":"14-시공-25"},"13-021":{"salesCode":"13-교영-21","designCode":"13-설계-16","constCode":""},"13-050":{"salesCode":"13-교영-50","designCode":"13-설계-17","constCode":""},"13-019":{"salesCode":"13-교영-19","designCode":"13-설계-18","constCode":""},"13-035":{"salesCode":"13-교영-35","designCode":"13-설계-19","constCode":"15-시공-21"},"13-065":{"salesCode":"13-교영-65","designCode":"13-설계-20","constCode":"14-시공-05"},"13-066":{"salesCode":"13-교영-66","designCode":"13-설계-21","constCode":""},"13-067":{"salesCode":"13-교영-67","designCode":"13-설계-22","constCode":"16-시공-38"},"13-052":{"salesCode":"13-교영-52","designCode":"13-설계-23","constCode":""},"13-068":{"salesCode":"13-교영-68","designCode":"13-설계-24","constCode":"16-시공-29"},"13-012":{"salesCode":"13-교영-12","designCode":"13-설계-25","constCode":"16-시공-22"},"13-069":{"salesCode":"13-교영-69","designCode":"13-설계-27","constCode":""},"13-014":{"salesCode":"13-교영-14","designCode":"13-설계-28","constCode":"16-시공-26"},"13-070":{"salesCode":"13-교영-70","designCode":"13-설계-29","constCode":""},"13-071":{"salesCode":"13-교영-71","designCode":"13-설계-30","constCode":"17-시공-08"},"13-072":{"salesCode":"13-교영-72","designCode":"13-설계-31","constCode":"15-시공-27"},"13-073":{"salesCode":"13-교영-73","designCode":"13-설계-32","constCode":"14-시공-14"},"13-074":{"salesCode":"13-교영-74","designCode":"13-설계-33","constCode":"19-시공-13"},"13-076":{"salesCode":"13-교영-76","designCode":"13-설계-35","constCode":""},"13-075":{"salesCode":"13-교영-75","designCode":"13-설계-34","constCode":"14-시공-06"},"13-077":{"salesCode":"13-교영-77","designCode":"13-설계-36","constCode":""},"13-078":{"salesCode":"13-교영-78","designCode":"13-설계-37","constCode":""},"13-079":{"salesCode":"13-교영-79","designCode":"13-설계-38","constCode":""},"13-036":{"salesCode":"13-교영-36","designCode":"13-설계-39","constCode":"22-시공-25"},"13-013":{"salesCode":"13-교영-13","designCode":"13-설계-40","constCode":"21-시공-16"},"13-080":{"salesCode":"13-교영-80","designCode":"13-설계-41","constCode":""},"13-040":{"salesCode":"13-교영-40","designCode":"13-설계-42","constCode":""},"13-081":{"salesCode":"13-교영-81","designCode":"13-설계-43","constCode":"17-시공-02"},"13-082":{"salesCode":"13-교영-82","designCode":"13-설계-44","constCode":""},"13-025":{"salesCode":"13-교영-25","designCode":"14-설계-01","constCode":"17-시공-13"},"13-024":{"salesCode":"13-교영-24","designCode":"14-설계-02","constCode":"17-시공-13"},"13-018":{"salesCode":"13-교영-18","designCode":"14-설계-03","constCode":"15-시공-32"},"13-041":{"salesCode":"13-교영-41","designCode":"14-설계-05","constCode":"16-시공-33"},"13-043":{"salesCode":"13-교영-43","designCode":"14-설계-36","constCode":"17-시공-21"},"13-083":{"salesCode":"13-교영-83","designCode":"13-설계-45","constCode":""},"13-048":{"salesCode":"13-교영-48","designCode":"16-설계-02","constCode":"18-시공-03"},"13-028":{"salesCode":"13-교영-28","designCode":"16-설계-14","constCode":"20-시공-16"},"12-003":{"salesCode":"12-교영-03","designCode":"12-설계-03","constCode":""},"12-004":{"salesCode":"12-교영-04","designCode":"12-설계-04","constCode":""},"12-002":{"salesCode":"12-교영-02","designCode":"12-설계-01","constCode":"12-시공-15"},"12-005":{"salesCode":"12-교영-05","designCode":"12-설계-05","constCode":""},"12-006":{"salesCode":"12-교영-06","designCode":"12-설계-07","constCode":""},"12-025":{"salesCode":"12-교영-25","designCode":"12-설계-38","constCode":"17-시공-25"},"12-007":{"salesCode":"12-교영-07","designCode":"12-설계-09","constCode":""},"12-008":{"salesCode":"12-교영-08","designCode":"12-설계-15","constCode":"13-시공-16"},"12-009":{"salesCode":"12-교영-09","designCode":"12-설계-16","constCode":"12-시공-34"},"12-010":{"salesCode":"12-교영-10","designCode":"12-설계-17","constCode":"14-시공-17"},"12-011":{"salesCode":"12-교영-11","designCode":"12-설계-19","constCode":"13-시공-24"},"12-012":{"salesCode":"12-교영-12","designCode":"12-설계-20","constCode":"13-시공-14"},"12-013":{"salesCode":"12-교영-13","designCode":"12-설계-22","constCode":"12-시공-23"},"12-014":{"salesCode":"12-교영-14","designCode":"12-설계-23","constCode":""},"12-015":{"salesCode":"12-교영-15","designCode":"12-설계-25","constCode":""},"12-016":{"salesCode":"12-교영-16","designCode":"12-설계-26","constCode":"12-시공-22"},"12-017":{"salesCode":"12-교영-17","designCode":"12-설계-27","constCode":"14-시공-24"},"12-018":{"salesCode":"12-교영-18","designCode":"12-설계-28","constCode":"16-시공-10"},"12-019":{"salesCode":"12-교영-19","designCode":"12-설계-30","constCode":""},"12-020":{"salesCode":"12-교영-20","designCode":"12-설계-31","constCode":"16-시공-16"},"12-021":{"salesCode":"12-교영-21","designCode":"12-설계-32","constCode":""},"12-022":{"salesCode":"12-교영-22","designCode":"12-설계-33","constCode":"15-시공-31"},"12-023":{"salesCode":"12-교영-23","designCode":"12-설계-36","constCode":"17-시공-23"},"12-024":{"salesCode":"12-교영-24","designCode":"12-설계-37","constCode":"13-시공-21"},"12-026":{"salesCode":"12-교영-26","designCode":"12-설계-39","constCode":"13-시공-22"},"12-027":{"salesCode":"12-교영-27","designCode":"12-설계-40","constCode":"12-시공-09"},"12-028":{"salesCode":"12-교영-28","designCode":"12-설계-41","constCode":""},"12-029":{"salesCode":"12-교영-29","designCode":"12-설계-43","constCode":"13-시공-30"},"12-030":{"salesCode":"12-교영-30","designCode":"12-설계-44","constCode":"15-시공-06"},"12-031":{"salesCode":"12-교영-31","designCode":"12-설계-45","constCode":"20-시공-18"},"12-032":{"salesCode":"12-교영-32","designCode":"12-설계-46","constCode":"12-시공-33"},"12-033":{"salesCode":"12-교영-33","designCode":"12-설계-47","constCode":"13-시공-04"},"12-034":{"salesCode":"12-교영-34","designCode":"12-설계-48","constCode":"17-시공-14"},"12-035":{"salesCode":"12-교영-35","designCode":"12-설계-49","constCode":"16-시공-06"},"12-036":{"salesCode":"12-교영-36","designCode":"12-설계-50","constCode":""},"12-037":{"salesCode":"12-교영-37","designCode":"12-설계-56","constCode":"17-시공-15"},"12-038":{"salesCode":"12-교영-38","designCode":"12-설계-57","constCode":""},"12-039":{"salesCode":"12-교영-39","designCode":"12-설계-58","constCode":""},"12-040":{"salesCode":"12-교영-40","designCode":"12-설계-59","constCode":""},"12-041":{"salesCode":"12-교영-41","designCode":"12-설계-62","constCode":""},"12-042":{"salesCode":"12-교영-42","designCode":"12-설계-64","constCode":"17-시공-03"},"12-043":{"salesCode":"12-교영-43","designCode":"12-설계-66","constCode":"16-시공-11"},"12-044":{"salesCode":"12-교영-44","designCode":"12-설계-67","constCode":""},"12-045":{"salesCode":"12-교영-45","designCode":"12-설계-68","constCode":"15-시공-09"},"12-046":{"salesCode":"12-교영-46","designCode":"12-설계-69","constCode":"14-시공-35"},"12-047":{"salesCode":"12-교영-47","designCode":"12-설계-70","constCode":"13-시공-06"},"12-048":{"salesCode":"12-교영-48","designCode":"12-설계-71","constCode":""},"12-049":{"salesCode":"12-교영-49","designCode":"12-설계-72","constCode":""},"12-050":{"salesCode":"12-교영-50","designCode":"12-설계-73","constCode":"14-시공-21"},"11-027":{"salesCode":"11-교영-27","designCode":"11-설계-01","constCode":"11-시공-05"},"11-028":{"salesCode":"11-교영-28","designCode":"11-설계-03","constCode":""},"11-029":{"salesCode":"11-교영-29","designCode":"11-설계-04","constCode":"13-시공-29"},"11-030":{"salesCode":"11-교영-30","designCode":"11-설계-05","constCode":""},"11-031":{"salesCode":"11-교영-31","designCode":"11-설계-06","constCode":""},"11-032":{"salesCode":"11-교영-32","designCode":"11-설계-07","constCode":""},"11-033":{"salesCode":"11-교영-33","designCode":"11-설계-08","constCode":""},"11-034":{"salesCode":"11-교영-34","designCode":"11-설계-09","constCode":"12-시공-37"},"11-035":{"salesCode":"11-교영-35","designCode":"11-설계-10","constCode":""},"11-036":{"salesCode":"11-교영-36","designCode":"11-설계-11","constCode":"12-시공-02"},"11-037":{"salesCode":"11-교영-37","designCode":"11-설계-13","constCode":""},"11-038":{"salesCode":"11-교영-38","designCode":"11-설계-15","constCode":""},"11-039":{"salesCode":"11-교영-39","designCode":"11-설계-17","constCode":"11-시공-19"},"11-040":{"salesCode":"11-교영-40","designCode":"11-설계-19","constCode":"12-시공-40"},"11-041":{"salesCode":"11-교영-41","designCode":"11-설계-20","constCode":""},"11-042":{"salesCode":"11-교영-42","designCode":"11-설계-22","constCode":"13-시공-27"},"11-043":{"salesCode":"11-교영-43","designCode":"11-설계-23","constCode":"15-시공-41"},"11-044":{"salesCode":"11-교영-44","designCode":"11-설계-24","constCode":"15-시공-15"},"11-045":{"salesCode":"11-교영-45","designCode":"11-설계-26","constCode":"13-시공-17"},"11-046":{"salesCode":"11-교영-46","designCode":"11-설계-27","constCode":"14-시공-13"},"11-047":{"salesCode":"11-교영-47","designCode":"11-설계-28","constCode":""},"11-048":{"salesCode":"11-교영-48","designCode":"11-설계-29","constCode":"12-시공-28"},"11-049":{"salesCode":"11-교영-49","designCode":"11-설계-30","constCode":""},"11-050":{"salesCode":"11-교영-50","designCode":"11-설계-31","constCode":""},"11-051":{"salesCode":"11-교영-51","designCode":"11-설계-32","constCode":""},"11-052":{"salesCode":"11-교영-52","designCode":"11-설계-33","constCode":"14-시공-04"},"11-053":{"salesCode":"11-교영-53","designCode":"11-설계-34","constCode":""},"11-054":{"salesCode":"11-교영-54","designCode":"11-설계-35","constCode":""},"11-055":{"salesCode":"11-교영-55","designCode":"11-설계-37","constCode":""},"11-056":{"salesCode":"11-교영-56","designCode":"11-설계-38","constCode":"12-시공-24"},"11-057":{"salesCode":"11-교영-57","designCode":"11-설계-40","constCode":""},"11-058":{"salesCode":"11-교영-58","designCode":"11-설계-42","constCode":"12-시공-01"},"11-059":{"salesCode":"11-교영-59","designCode":"11-설계-43","constCode":"13-시공-03"},"11-060":{"salesCode":"11-교영-60","designCode":"11-설계-45","constCode":"16-시공-02"},"11-061":{"salesCode":"11-교영-61","designCode":"11-설계-46","constCode":""},"11-062":{"salesCode":"11-교영-62","designCode":"11-설계-48","constCode":"15-시공-18"},"11-063":{"salesCode":"11-교영-63","designCode":"11-설계-49","constCode":""},"11-064":{"salesCode":"11-교영-64","designCode":"11-설계-50","constCode":"16-시공-13"},"11-065":{"salesCode":"11-교영-65","designCode":"11-설계-51","constCode":""},"11-066":{"salesCode":"11-교영-66","designCode":"11-설계-52","constCode":""},"11-067":{"salesCode":"11-교영-67","designCode":"11-설계-53","constCode":"12-시공-08"},"11-068":{"salesCode":"11-교영-68","designCode":"11-설계-54","constCode":"13-시공-33"},"11-069":{"salesCode":"11-교영-69","designCode":"11-설계-55","constCode":"12-시공-03"},"11-070":{"salesCode":"11-교영-70","designCode":"11-설계-58","constCode":"15-시공-29"},"11-071":{"salesCode":"11-교영-71","designCode":"11-설계-59","constCode":"16-시공-21"},"11-072":{"salesCode":"11-교영-72","designCode":"11-설계-60","constCode":""},"11-073":{"salesCode":"11-교영-73","designCode":"11-설계-62","constCode":"12-시공-11"},"11-074":{"salesCode":"11-교영-74","designCode":"11-설계-63","constCode":"15-시공-36"},"10-002":{"salesCode":"10-교영-02","designCode":"10-설계-01","constCode":"10-시공-12"},"10-003":{"salesCode":"10-교영-03","designCode":"10-설계-02","constCode":"10-시공-13"},"10-004":{"salesCode":"10-교영-04","designCode":"10-설계-03","constCode":"11-시공-02"},"10-005":{"salesCode":"10-교영-05","designCode":"10-설계-04","constCode":"11-시공-04"},"10-006":{"salesCode":"10-교영-06","designCode":"10-설계-05","constCode":"11-시공-07"},"10-007":{"salesCode":"10-교영-07","designCode":"10-설계-06","constCode":"11-시공-14"},"10-008":{"salesCode":"10-교영-08","designCode":"10-설계-07","constCode":""},"10-009":{"salesCode":"10-교영-09","designCode":"10-설계-08","constCode":"11-시공-06"},"10-010":{"salesCode":"10-교영-10","designCode":"10-설계-10","constCode":"16-시공-08"},"10-011":{"salesCode":"10-교영-11","designCode":"10-설계-11","constCode":"11-시공-13"},"10-012":{"salesCode":"10-교영-12","designCode":"10-설계-13","constCode":"14-시공-02"},"10-013":{"salesCode":"10-교영-13","designCode":"10-설계-15","constCode":"13-시공-07"},"10-014":{"salesCode":"10-교영-14","designCode":"10-설계-16","constCode":"12-시공-04"},"10-015":{"salesCode":"10-교영-15","designCode":"10-설계-17","constCode":"12-시공-35"},"10-016":{"salesCode":"10-교영-16","designCode":"10-설계-18","constCode":""},"10-017":{"salesCode":"10-교영-17","designCode":"10-설계-20","constCode":""},"10-018":{"salesCode":"10-교영-18","designCode":"10-설계-21","constCode":"12-시공-32"},"10-019":{"salesCode":"10-교영-19","designCode":"10-설계-23","constCode":"18-시공-01"},"10-020":{"salesCode":"10-교영-20","designCode":"10-설계-27","constCode":""},"10-021":{"salesCode":"10-교영-21","designCode":"10-설계-29","constCode":"17-시공-07"},"10-022":{"salesCode":"10-교영-22","designCode":"10-설계-30","constCode":""},"10-023":{"salesCode":"10-교영-23","designCode":"10-설계-31","constCode":"16-시공-35"},"10-024":{"salesCode":"10-교영-24","designCode":"10-설계-34","constCode":"12-시공-06"},"10-025":{"salesCode":"10-교영-25","designCode":"10-설계-35","constCode":""},"10-026":{"salesCode":"10-교영-26","designCode":"10-설계-36","constCode":"14-시공-18"},"10-027":{"salesCode":"10-교영-27","designCode":"10-설계-37","constCode":""},"10-028":{"salesCode":"10-교영-28","designCode":"10-설계-39","constCode":""},"10-029":{"salesCode":"10-교영-29","designCode":"10-설계-40","constCode":"13-시공-11"},"10-030":{"salesCode":"10-교영-30","designCode":"10-설계-41","constCode":""},"10-031":{"salesCode":"10-교영-31","designCode":"10-설계-42","constCode":""},"10-032":{"salesCode":"10-교영-32","designCode":"10-설계-43","constCode":""},"10-033":{"salesCode":"10-교영-33","designCode":"10-설계-45","constCode":""},"10-034":{"salesCode":"10-교영-34","designCode":"10-설계-46","constCode":""},"10-035":{"salesCode":"10-교영-35","designCode":"10-설계-47","constCode":""},"09-002":{"salesCode":"09-교영-02","designCode":"09-설계-01","constCode":"10-시공-01"},"09-003":{"salesCode":"09-교영-03","designCode":"09-설계-02","constCode":"09-시공-14"},"09-004":{"salesCode":"09-교영-04","designCode":"09-설계-05","constCode":"10-시공-03"},"09-005":{"salesCode":"09-교영-05","designCode":"09-설계-06","constCode":"10-시공-08"},"09-006":{"salesCode":"09-교영-06","designCode":"09-설계-09","constCode":"09-시공-15"},"09-007":{"salesCode":"09-교영-07","designCode":"09-설계-10","constCode":"10-시공-07"},"09-008":{"salesCode":"09-교영-08","designCode":"09-설계-12","constCode":"10-시공-11"},"09-009":{"salesCode":"09-교영-09","designCode":"09-설계-13","constCode":"11-시공-03"},"09-010":{"salesCode":"09-교영-10","designCode":"09-설계-14","constCode":"11-시공-08"},"09-011":{"salesCode":"09-교영-11","designCode":"09-설계-15","constCode":"14-시공-07"},"09-012":{"salesCode":"09-교영-12","designCode":"09-설계-16","constCode":""},"09-013":{"salesCode":"09-교영-13","designCode":"09-설계-17","constCode":"15-시공-13"},"09-014":{"salesCode":"09-교영-14","designCode":"09-설계-19","constCode":"12-시공-16"},"09-015":{"salesCode":"09-교영-15","designCode":"09-설계-20","constCode":"11-시공-18"},"09-016":{"salesCode":"09-교영-16","designCode":"09-설계-21","constCode":"11-시공-17"},"09-017":{"salesCode":"09-교영-17","designCode":"09-설계-22","constCode":"11-시공-16"},"09-018":{"salesCode":"09-교영-18","designCode":"09-설계-24","constCode":""},"09-019":{"salesCode":"09-교영-19","designCode":"09-설계-25","constCode":"12-시공-18"},"09-020":{"salesCode":"09-교영-20","designCode":"09-설계-26","constCode":""},"09-021":{"salesCode":"09-교영-21","designCode":"09-설계-27","constCode":""},"09-022":{"salesCode":"09-교영-22","designCode":"09-설계-29","constCode":"12-시공-31"},"09-023":{"salesCode":"09-교영-23","designCode":"09-설계-30","constCode":""},"09-024":{"salesCode":"09-교영-24","designCode":"09-설계-31","constCode":"12-시공-21"},"09-025":{"salesCode":"09-교영-25","designCode":"09-설계-32","constCode":""},"09-026":{"salesCode":"09-교영-26","designCode":"09-설계-33","constCode":"13-시공-23"},"09-027":{"salesCode":"09-교영-27","designCode":"09-설계-34","constCode":""},"08-002":{"salesCode":"08-교영-02","designCode":"08-설계-01","constCode":"08-시공-04"},"08-003":{"salesCode":"08-교영-03","designCode":"08-설계-02","constCode":"08-시공-09"},"08-004":{"salesCode":"08-교영-04","designCode":"08-설계-03","constCode":"08-시공-11"},"08-005":{"salesCode":"08-교영-05","designCode":"08-설계-04","constCode":"08-시공-12"},"08-006":{"salesCode":"08-교영-06","designCode":"08-설계-05","constCode":"09-시공-04"},"08-007":{"salesCode":"08-교영-07","designCode":"08-설계-06","constCode":"09-시공-02"},"08-008":{"salesCode":"08-교영-08","designCode":"08-설계-07","constCode":"08-시공-15"},"08-009":{"salesCode":"08-교영-09","designCode":"08-설계-08","constCode":"09-시공-01"},"08-010":{"salesCode":"08-교영-10","designCode":"08-설계-09","constCode":"09-시공-03"},"08-011":{"salesCode":"08-교영-11","designCode":"08-설계-10","constCode":"09-시공-07"},"08-012":{"salesCode":"08-교영-12","designCode":"08-설계-11","constCode":"09-시공-12"},"08-013":{"salesCode":"08-교영-13","designCode":"08-설계-12","constCode":"08-시공-08"},"08-014":{"salesCode":"08-교영-14","designCode":"08-설계-13","constCode":"08-시공-13"},"08-015":{"salesCode":"08-교영-15","designCode":"08-설계-14","constCode":"08-시공-14"},"08-016":{"salesCode":"08-교영-16","designCode":"08-설계-15","constCode":"09-시공-08"},"08-017":{"salesCode":"08-교영-17","designCode":"08-설계-16","constCode":"09-시공-06"},"08-018":{"salesCode":"08-교영-18","designCode":"08-설계-17","constCode":""},"08-019":{"salesCode":"","designCode":"08-설계-19","constCode":"11-시공-22"},"08-020":{"salesCode":"","designCode":"08-설계-20","constCode":"11-시공-23"},"08-021":{"salesCode":"08-교영-21","designCode":"08-설계-22","constCode":"13-시공-02"},"08-022":{"salesCode":"08-교영-22","designCode":"08-설계-23","constCode":"11-시공-30"},"08-023":{"salesCode":"08-교영-23","designCode":"08-설계-25","constCode":"11-시공-29"},"08-024":{"salesCode":"","designCode":"08-설계-26","constCode":"11-시공-29"},"08-025":{"salesCode":"08-교영-25","designCode":"08-설계-27","constCode":"12-시공-30"},"08-026":{"salesCode":"08-교영-26","designCode":"08-설계-28","constCode":""},"08-027":{"salesCode":"08-교영-27","designCode":"08-설계-29","constCode":""},"08-028":{"salesCode":"08-교영-28","designCode":"08-설계-31","constCode":""},"07-008":{"salesCode":"07-교영-08","designCode":"07-설계-01","constCode":"07-시공-02"},"07-009":{"salesCode":"07-교영-09","designCode":"07-설계-02","constCode":"08-시공-03"},"07-010":{"salesCode":"07-교영-10","designCode":"07-설계-03","constCode":"08-시공-06"},"07-011":{"salesCode":"07-교영-11","designCode":"07-설계-04","constCode":"08-시공-10"},"07-012":{"salesCode":"07-교영-12","designCode":"07-설계-05","constCode":"09-시공-13"},"07-014":{"salesCode":"07-교영-14","designCode":"07-설계-06","constCode":"11-시공-01"},"07-013":{"salesCode":"07-교영-13","designCode":"07-설계-07","constCode":"08-시공-05"},"07-007":{"salesCode":"07-교영-07","designCode":"07-설계-08","constCode":"12-시공-38"},"07-015":{"salesCode":"07-교영-15","designCode":"07-설계-09","constCode":""},"07-016":{"salesCode":"07-교영-16","designCode":"07-설계-10","constCode":""},"07-017":{"salesCode":"07-교영-17","designCode":"07-설계-11","constCode":"15-시공-01"},"07-018":{"salesCode":"07-교영-18","designCode":"07-설계-12","constCode":""},"06-014":{"salesCode":"","designCode":"06-설계-15","constCode":""},"06-001":{"salesCode":"06-교영-01","designCode":"06-설계-01","constCode":"06-시공-02"},"06-002":{"salesCode":"06-교영-02","designCode":"06-설계-02","constCode":"06-시공-01"},"06-003":{"salesCode":"06-교영-03","designCode":"06-설계-03","constCode":"06-시공-03"},"06-004":{"salesCode":"06-교영-04","designCode":"06-설계-04","constCode":"07-시공-01"},"06-005":{"salesCode":"06-교영-05","designCode":"06-설계-05","constCode":"07-시공-03"},"06-006":{"salesCode":"06-교영-06","designCode":"06-설계-06","constCode":""},"06-007":{"salesCode":"06-교영-07","designCode":"06-설계-07","constCode":"07-시공-05"},"06-008":{"salesCode":"06-교영-08","designCode":"06-설계-08","constCode":"08-시공-01"},"06-009":{"salesCode":"06-교영-09","designCode":"06-설계-09","constCode":"08-시공-02"},"06-010":{"salesCode":"06-교영-10","designCode":"06-설계-10","constCode":"08-시공-07"},"06-011":{"salesCode":"06-교영-11","designCode":"06-설계-11","constCode":""},"06-012":{"salesCode":"06-교영-12","designCode":"06-설계-13","constCode":"09-시공-09"},"06-013":{"salesCode":"06-교영-13","designCode":"06-설계-14","constCode":"10-시공-10"}};
|
||
const state = {
|
||
rows: [],
|
||
filteredRows: [],
|
||
selectedRow: null,
|
||
detail: null,
|
||
bridgeOverviews: [],
|
||
budgetPlan: null,
|
||
sourceTable: '',
|
||
sourceColumns: {},
|
||
};
|
||
const FACTORY_ADDRESS = '충청남도 당진시 고대면 성산로 464';
|
||
|
||
const searchInput = document.getElementById('searchInput');
|
||
const contractTypeFilter = document.getElementById('contractTypeFilter');
|
||
const applicationTypeFilter = document.getElementById('applicationTypeFilter');
|
||
const reloadButton = document.getElementById('reloadButton');
|
||
const countChip = document.getElementById('countChip');
|
||
const sourceChip = document.getElementById('sourceChip');
|
||
const statusText = document.getElementById('statusText');
|
||
const resultBody = document.getElementById('resultBody');
|
||
const detailTitle = document.getElementById('detailTitle');
|
||
const detailMeta = document.getElementById('detailMeta');
|
||
const detailBody = document.getElementById('detailBody');
|
||
const bridgeMeta = document.getElementById('bridgeMeta');
|
||
const bridgeBody = document.getElementById('bridgeBody');
|
||
const detailSyncButton = document.getElementById('detailSyncButton');
|
||
const remarkModal = document.getElementById('remarkModal');
|
||
const remarkModalBody = document.getElementById('remarkModalBody');
|
||
const remarkModalTitle = document.getElementById('remarkModalTitle');
|
||
const remarkModalClose = document.getElementById('remarkModalClose');
|
||
const planModal = document.getElementById('planModal');
|
||
const planModalBody = document.getElementById('planModalBody');
|
||
const planModalTitle = document.getElementById('planModalTitle');
|
||
const planModalClose = document.getElementById('planModalClose');
|
||
let syncedAt = '';
|
||
|
||
function escapeHtml(value) {
|
||
return String(value ?? '')
|
||
.replaceAll('&', '&')
|
||
.replaceAll('<', '<')
|
||
.replaceAll('>', '>')
|
||
.replaceAll('"', '"')
|
||
.replaceAll("'", ''');
|
||
}
|
||
|
||
function toHtmlWithBreaks(value) {
|
||
return escapeHtml(value || '').replaceAll('\n', '<br>');
|
||
}
|
||
|
||
function formatCellValue(value) {
|
||
const text = String(value || '').trim();
|
||
if (!text) return '-';
|
||
return text
|
||
.replace(/\+/g, '+\n')
|
||
.replace(/~/g, '~\n')
|
||
.replace(/=/g, '=\n')
|
||
.replace(/\//g, '/\n')
|
||
.replace(/(?<=\d)-(?=\d)/g, '-\n');
|
||
}
|
||
|
||
function extractBusinessCodeKey(value) {
|
||
const text = String(value || '').trim();
|
||
const match = text.match(/\d{2}-\d{3}/);
|
||
return match ? match[0] : text;
|
||
}
|
||
|
||
function resolveLinkedCodes(detail) {
|
||
const apiLinked = detail.linkedCodes || {};
|
||
if (apiLinked.salesCode || apiLinked.designCode) {
|
||
return apiLinked;
|
||
}
|
||
const key = extractBusinessCodeKey(detail.businessCode);
|
||
return LINKED_CODE_BY_BUSINESS[key] || { salesCode: '', designCode: '' };
|
||
}
|
||
|
||
function compactLinkedCodeName(name, fallbackName) {
|
||
const cleanName = String(name || '').trim();
|
||
const cleanFallback = String(fallbackName || '').trim();
|
||
if (!cleanName) return cleanFallback;
|
||
if (cleanFallback && (cleanName.length > 24 || cleanName.includes('[') || cleanName.includes('민간투자사업'))) {
|
||
return cleanFallback;
|
||
}
|
||
return cleanName;
|
||
}
|
||
|
||
function formatLinkedCodeLabel(code, name, fallbackName = '') {
|
||
const cleanCode = String(code || '').trim();
|
||
const cleanName = compactLinkedCodeName(name, fallbackName);
|
||
if (!cleanCode) return '-';
|
||
if (!cleanName) return cleanCode;
|
||
return `${cleanName}(${cleanCode})`;
|
||
}
|
||
|
||
function normalizeCrossbeamRows(crossbeam) {
|
||
if (Array.isArray(crossbeam)) {
|
||
return crossbeam.length ? crossbeam : [{ height: '', length: '', quantity: '', remarks: '' }];
|
||
}
|
||
if (crossbeam && typeof crossbeam === 'object') {
|
||
return [crossbeam];
|
||
}
|
||
return [{ height: '', length: '', quantity: '', remarks: '' }];
|
||
}
|
||
|
||
function renderBudgetPlanInlineTables(plan) {
|
||
if (!plan) return '-';
|
||
const inputDays = plan.inputDays || {};
|
||
const inputRebar = plan.inputRebar || {};
|
||
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
||
const predeck = plan.predeck || {};
|
||
const crossbeamRows = normalizeCrossbeamRows(plan.crossbeam);
|
||
|
||
return `
|
||
<div class="inline-plan-wrap">
|
||
<table class="plan-table">
|
||
<tr>
|
||
<th colspan="5">투입일수 (일)</th>
|
||
<th colspan="3">투입철근 (Ton)</th>
|
||
</tr>
|
||
<tr>
|
||
<th>제작</th>
|
||
<th>인장</th>
|
||
<th>거치</th>
|
||
<th>판넬</th>
|
||
<th>총투입일</th>
|
||
<th>공장</th>
|
||
<th>현장</th>
|
||
<th>합계</th>
|
||
</tr>
|
||
<tr>
|
||
<td>${escapeHtml(inputDays.fabrication || '-')}</td>
|
||
<td>${escapeHtml(inputDays.tensioning || '-')}</td>
|
||
<td>${escapeHtml(inputDays.erection || '-')}</td>
|
||
<td>${escapeHtml(inputDays.panel || '-')}</td>
|
||
<td>${escapeHtml(inputDays.total || '-')}</td>
|
||
<td>${escapeHtml(inputRebar.factory || '-')}</td>
|
||
<td>${escapeHtml(inputRebar.site || '-')}</td>
|
||
<td>${escapeHtml(inputRebar.total || '-')}</td>
|
||
</tr>
|
||
</table>
|
||
<div class="plan-section-title">교량제원</div>
|
||
<table class="plan-table">
|
||
<tr>
|
||
<th rowspan="${Math.max(girderSpecs.length, 1) + 1}">거더</th>
|
||
<th>연장(m)</th>
|
||
<th>폭원(m)</th>
|
||
<th>길이(m)</th>
|
||
<th>형고(m)</th>
|
||
<th>수량(본)</th>
|
||
<th>거푸집(조)</th>
|
||
<th>비고</th>
|
||
</tr>
|
||
${
|
||
girderSpecs.length
|
||
? girderSpecs.map((row) => `
|
||
<tr>
|
||
<td>${escapeHtml(row.extension || '-')}</td>
|
||
<td>${escapeHtml(row.width || '-')}</td>
|
||
<td>${escapeHtml(row.length || '-')}</td>
|
||
<td>${escapeHtml(row.height || '-')}</td>
|
||
<td>${escapeHtml(row.quantity || '-')}</td>
|
||
<td>${escapeHtml(row.formCount || '-')}</td>
|
||
<td class="plan-left">${escapeHtml(row.remarks || '-')}</td>
|
||
</tr>
|
||
`).join('')
|
||
: `
|
||
<tr>
|
||
<td colspan="7">내용이 없습니다.</td>
|
||
</tr>
|
||
`
|
||
}
|
||
</table>
|
||
<table class="plan-table plan-table-split">
|
||
<tr>
|
||
<th colspan="4">프리덱</th>
|
||
<th colspan="4">가로보</th>
|
||
</tr>
|
||
<tr>
|
||
<th>일반부(㎡)</th>
|
||
<th>중분대(㎡)</th>
|
||
<th>방호벽부(㎡)</th>
|
||
<th>비고</th>
|
||
<th>형고(m)</th>
|
||
<th>길이(m)</th>
|
||
<th>수량(EA)</th>
|
||
<th>비고</th>
|
||
</tr>
|
||
${crossbeamRows.map((row, index) => `
|
||
<tr>
|
||
${index === 0 ? `
|
||
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.generalArea || '-')}</td>
|
||
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.medianArea || '-')}</td>
|
||
<td rowspan="${crossbeamRows.length}">${escapeHtml(predeck.barrierArea || '-')}</td>
|
||
<td rowspan="${crossbeamRows.length}" class="plan-left">${escapeHtml(predeck.remarks || '-')}</td>
|
||
` : ''}
|
||
<td>${escapeHtml(row.height || '-')}</td>
|
||
<td>${escapeHtml(row.length || '-')}</td>
|
||
<td>${escapeHtml(row.quantity || '-')}</td>
|
||
<td class="plan-left">${escapeHtml(row.remarks || '-')}</td>
|
||
</tr>
|
||
`).join('')}
|
||
</table>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function splitApplicationTypes(value) {
|
||
return String(value || '')
|
||
.split('||')
|
||
.map((item) => item.trim())
|
||
.filter(Boolean);
|
||
}
|
||
|
||
function compareProjectCodeDesc(a, b) {
|
||
return String(b.projectCode || '').localeCompare(String(a.projectCode || ''), 'ko', {
|
||
numeric: true,
|
||
sensitivity: 'base',
|
||
});
|
||
}
|
||
|
||
function renderRows(rows) {
|
||
if (!rows.length) {
|
||
resultBody.innerHTML = '<tr><td colspan="2" class="empty">조건에 맞는 프로젝트가 없습니다.</td></tr>';
|
||
return;
|
||
}
|
||
|
||
resultBody.innerHTML = rows.map((row) => `
|
||
<tr class="project-row ${state.selectedRow && state.selectedRow.projectCode === row.projectCode ? 'selected' : ''}" data-code="${escapeHtml(row.projectCode)}">
|
||
<td class="code">${escapeHtml(row.projectCode)}</td>
|
||
<td>${escapeHtml(row.projectName)}</td>
|
||
</tr>
|
||
`).join('');
|
||
|
||
Array.from(resultBody.querySelectorAll('.project-row')).forEach((element) => {
|
||
element.addEventListener('click', () => {
|
||
const projectCode = element.getAttribute('data-code');
|
||
const selected = state.rows.find((row) => row.projectCode === projectCode);
|
||
if (selected) {
|
||
state.selectedRow = selected;
|
||
renderRows(state.filteredRows);
|
||
loadDetail(selected.projectCode, selected.projectName, false);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function populateFilterOptions() {
|
||
const previousContractType = contractTypeFilter.value;
|
||
const previousApplicationType = applicationTypeFilter.value;
|
||
const contractTypes = Array.from(new Set(
|
||
state.rows.map((row) => String(row.contractType || '').trim()).filter(Boolean)
|
||
)).sort((a, b) => a.localeCompare(b, 'ko'));
|
||
const applicationTypes = Array.from(new Set(
|
||
state.rows.flatMap((row) => splitApplicationTypes(row.applicationType))
|
||
)).sort((a, b) => a.localeCompare(b, 'ko'));
|
||
|
||
contractTypeFilter.innerHTML = ['<option value="">전체 보기</option>']
|
||
.concat(contractTypes.map((value) => `<option value="${escapeHtml(value)}">${escapeHtml(value)}</option>`))
|
||
.join('');
|
||
applicationTypeFilter.innerHTML = ['<option value="">전체 보기</option>']
|
||
.concat(applicationTypes.map((value) => `<option value="${escapeHtml(value)}">${escapeHtml(value)}</option>`))
|
||
.join('');
|
||
|
||
if (contractTypes.includes(previousContractType)) {
|
||
contractTypeFilter.value = previousContractType;
|
||
}
|
||
if (applicationTypes.includes(previousApplicationType)) {
|
||
applicationTypeFilter.value = previousApplicationType;
|
||
}
|
||
}
|
||
|
||
function renderDetail(detail) {
|
||
if (!detail) {
|
||
detailTitle.textContent = '계약정보 표';
|
||
detailMeta.textContent = '시공코드나 약칭을 클릭하면 DB 캐시를 먼저 보여줍니다. 최신 정보가 필요하면 새로 가져오기를 누르세요.';
|
||
detailBody.innerHTML = '<tr><td colspan="2" class="empty">선택된 시공코드가 없습니다.</td></tr>';
|
||
bridgeMeta.textContent = '공사규모와 공사개요를 교량명 기준으로 매칭해서 보여줍니다.';
|
||
bridgeBody.innerHTML = '<tr><td colspan="19" class="empty">선택된 시공코드가 없습니다.</td></tr>';
|
||
return;
|
||
}
|
||
|
||
detailTitle.textContent = `${detail.projectName || '-'} [${detail.projectCode}]`;
|
||
detailMeta.textContent = `사업코드 ${detail.businessCode || '-'} · 마지막 동기화 ${detail.syncedAt || '-'}`;
|
||
renderDetailRows(detail);
|
||
renderMergedBridgeRows(detail.scaleRows || [], state.bridgeOverviews || [], state.budgetPlan);
|
||
}
|
||
|
||
function renderDetailRows(detail) {
|
||
const linkedCodes = resolveLinkedCodes(detail);
|
||
const linkedCodeText = [
|
||
formatLinkedCodeLabel(linkedCodes.salesCode, linkedCodes.salesName, detail.projectName),
|
||
formatLinkedCodeLabel(linkedCodes.designCode, linkedCodes.designName, detail.projectName),
|
||
].join(' · ');
|
||
const rows = [
|
||
['사업코드', detail.businessCode],
|
||
['약칭', detail.projectName],
|
||
['시공코드', detail.projectCode],
|
||
['연계코드', linkedCodeText],
|
||
['현장위치', renderSiteLocationCell(detail.siteLocation, detail.projectName)],
|
||
['발주처', detail.clientName],
|
||
['최종계약금액', detail.finalContractAmountText],
|
||
['계약종류', detail.contractType],
|
||
];
|
||
if (state.budgetPlan) {
|
||
rows.push(
|
||
['총투입일', state.budgetPlan.totalInputDays || '-'],
|
||
['투입철근', state.budgetPlan.totalRebarTon || '-'],
|
||
['공사개요', renderBudgetPlanInlineTables(state.budgetPlan)],
|
||
);
|
||
}
|
||
detailBody.innerHTML = rows.map(([label, value]) => `
|
||
<tr>
|
||
<th>${escapeHtml(label)}</th>
|
||
<td>${typeof value === 'string' && (value.includes('<table') || value.includes('class="map-location"')) ? value : escapeHtml(value || '-')}</td>
|
||
</tr>
|
||
`).join('');
|
||
Array.from(detailBody.querySelectorAll('.map-button')).forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
openBridgeMap(button.getAttribute('data-bridge') || '', button.getAttribute('data-location') || '');
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderSiteLocationCell(siteLocation, projectName) {
|
||
const location = normalizeValue(siteLocation);
|
||
const disabled = !location || location === '-';
|
||
return `
|
||
<div class="map-location">
|
||
<div>${toHtmlWithBreaks(location)}</div>
|
||
<button
|
||
class="map-button"
|
||
type="button"
|
||
data-bridge="${escapeAttr(projectName || '')}"
|
||
data-location="${escapeAttr(location)}"
|
||
${disabled ? 'disabled' : ''}
|
||
title="공장 위치와 현장 위치를 지도에서 보기"
|
||
>지도</button>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function normalizeBridgeName(value) {
|
||
return String(value || '')
|
||
.replace(/\s+/g, '')
|
||
.replace(/\([^)]*\)/g, '')
|
||
.replace(/\[[^\]]*\]/g, '')
|
||
.trim();
|
||
}
|
||
|
||
function normalizeValue(value, fallback = '-') {
|
||
const text = String(value || '').trim();
|
||
return text || fallback;
|
||
}
|
||
|
||
function formatConstructionPeriod(value) {
|
||
const text = String(value || '').trim();
|
||
if (!text) return '-';
|
||
const parts = text.split('~').map((item) => item.trim()).filter(Boolean);
|
||
if (parts.length >= 2) {
|
||
return `${parts[0]}\n${parts[1]}`;
|
||
}
|
||
return text;
|
||
}
|
||
|
||
function formatBridgeLocation(value) {
|
||
const text = String(value || '').trim();
|
||
if (!text) return '-';
|
||
if (text.length <= 18) return text;
|
||
const midpoint = Math.floor(text.length / 2);
|
||
let splitIndex = text.indexOf(' ', midpoint);
|
||
if (splitIndex === -1) {
|
||
splitIndex = text.lastIndexOf(' ', midpoint);
|
||
}
|
||
if (splitIndex === -1) {
|
||
return text;
|
||
}
|
||
return `${text.slice(0, splitIndex).trim()}\n${text.slice(splitIndex + 1).trim()}`;
|
||
}
|
||
|
||
function normalizeMapQuery(value) {
|
||
return String(value || '')
|
||
.replace(/\s+/g, ' ')
|
||
.replace(/\n+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
function extractAddressForMap(value) {
|
||
const original = normalizeMapQuery(value);
|
||
const pointMatch = original.match(/\(시점\)\s*([^()]+?)(?=\s*\(종점\)|$)/);
|
||
if (pointMatch && pointMatch[1]) {
|
||
return normalizeMapQuery(pointMatch[1]);
|
||
}
|
||
const pointLabelMatch = original.match(/시점\s*[::]?\s*([^()]+?)(?=\s*종점\s*[::]?|$)/);
|
||
if (pointLabelMatch && pointLabelMatch[1]) {
|
||
return normalizeMapQuery(pointLabelMatch[1]);
|
||
}
|
||
|
||
let text = original
|
||
.replace(/^\(주\)\s*장헌\s*/, '')
|
||
.replace(/^\(주\)장헌\s*/, '')
|
||
.replace(/^\[.*?\]\s*/, '')
|
||
.replace(/\(.*?\)/g, ' ')
|
||
.replace(/\[.*?\]/g, ' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
const addressStart = text.search(/(서울특별시|서울시|부산광역시|부산시|대구광역시|대구시|인천광역시|인천시|광주광역시|광주시|대전광역시|대전시|울산광역시|울산시|세종특별자치시|세종시|경기도|강원특별자치도|강원도|충청북도|충북|충청남도|충남|전북특별자치도|전라북도|전북|전라남도|전남|경상북도|경북|경상남도|경남|제주특별자치도|제주도)/);
|
||
if (addressStart > 0) {
|
||
text = text.slice(addressStart).trim();
|
||
}
|
||
text = text
|
||
.split(/\s*~\s*/)[0]
|
||
.split(/\s+종점\s+/)[0]
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
return text;
|
||
}
|
||
|
||
async function openBridgeMap(bridgeName, bridgeLocation) {
|
||
const site = extractAddressForMap(bridgeLocation);
|
||
if (!site || site === '-') {
|
||
alert('교량 위치 정보가 없습니다.');
|
||
return;
|
||
}
|
||
const popup = window.open(
|
||
'',
|
||
`jhBridgeRouteMap_${Date.now()}`,
|
||
'popup=yes,width=1280,height=820,left=80,top=60,resizable=yes,scrollbars=yes'
|
||
);
|
||
if (!popup) {
|
||
alert('팝업이 차단되었습니다. 브라우저 팝업 허용 후 다시 눌러주세요.');
|
||
return;
|
||
}
|
||
popup.document.write('<!doctype html><meta charset="utf-8"><title>길찾기 준비 중</title><body style="font-family:sans-serif;padding:24px;">네이버 자동차 길찾기를 준비하는 중입니다...</body>');
|
||
popup.focus();
|
||
|
||
try {
|
||
const params = new URLSearchParams({
|
||
origin: FACTORY_ADDRESS,
|
||
destination: site,
|
||
});
|
||
const response = await fetch(`/api/naver-route-url?${params.toString()}`, { cache: 'no-store' });
|
||
const result = await response.json();
|
||
if (!response.ok || !result.ok || !result.url) {
|
||
throw new Error(result.error || '네이버 길찾기 URL을 만들지 못했습니다.');
|
||
}
|
||
popup.location.href = result.url;
|
||
} catch (error) {
|
||
const fallbackUrl = `https://map.naver.com/p/search/${encodeURIComponent(site)}`;
|
||
popup.document.body.innerHTML = `
|
||
<div style="font-family:sans-serif;padding:24px;line-height:1.6;">
|
||
<h2 style="margin:0 0 12px;color:#b91c1c;">네이버 자동차 길찾기를 바로 열지 못했습니다.</h2>
|
||
<p>현장 주소: <strong>${escapeHtml(site)}</strong></p>
|
||
<p style="white-space:pre-wrap;color:#7f1d1d;">${escapeHtml(error.message || String(error))}</p>
|
||
<p><a href="${escapeAttr(fallbackUrl)}" style="color:#0f766e;font-weight:700;">네이버지도에서 현장 위치 먼저 열기</a></p>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
function formatStatus(scaleRow, overviewRow) {
|
||
const status = overviewRow?.constructionStatus || scaleRow?.constructionStatus || '';
|
||
return status || '-';
|
||
}
|
||
|
||
function escapeAttr(value) {
|
||
return escapeHtml(value).replaceAll('\n', ' ');
|
||
}
|
||
|
||
function renderBudgetPlanModal(plan, bridgeName) {
|
||
if (!plan) {
|
||
planModalBody.textContent = '내용이 없습니다.';
|
||
return;
|
||
}
|
||
const inputDays = plan.inputDays || {};
|
||
const inputRebar = plan.inputRebar || {};
|
||
const girderSpecs = Array.isArray(plan.girderSpecs) ? plan.girderSpecs : [];
|
||
const predeck = plan.predeck || {};
|
||
const crossbeamRows = normalizeCrossbeamRows(plan.crossbeam);
|
||
|
||
planModalTitle.textContent = `${bridgeName || plan.projectName || '프로젝트'} 공사시행계획서`;
|
||
planModalBody.innerHTML = `
|
||
<div class="plan-section-title">투입일수 / 투입철근</div>
|
||
<table class="plan-table">
|
||
<tr><th>제작</th><td>${escapeHtml(inputDays.fabrication || '-')}</td><th>공장 철근</th><td>${escapeHtml(inputRebar.factory || '-')}</td></tr>
|
||
<tr><th>인장</th><td>${escapeHtml(inputDays.tensioning || '-')}</td><th>현장 철근</th><td>${escapeHtml(inputRebar.site || '-')}</td></tr>
|
||
<tr><th>거치</th><td>${escapeHtml(inputDays.erection || '-')}</td><th>철근 합계</th><td>${escapeHtml(inputRebar.total || '-')}</td></tr>
|
||
<tr><th>판넬</th><td>${escapeHtml(inputDays.panel || '-')}</td><th>총투입일</th><td>${escapeHtml(inputDays.total || '-')}</td></tr>
|
||
</table>
|
||
<div class="plan-section-title">교량제원 - 거더</div>
|
||
<table class="plan-table">
|
||
<tr><th>연장</th><th>폭원</th><th>길이</th><th>형고</th><th>수량</th><th>거푸집</th><th>비고</th></tr>
|
||
${girderSpecs.length ? girderSpecs.map((row) => `
|
||
<tr>
|
||
<td>${escapeHtml(row.extension || '-')}</td>
|
||
<td>${escapeHtml(row.width || '-')}</td>
|
||
<td>${escapeHtml(row.length || '-')}</td>
|
||
<td>${escapeHtml(row.height || '-')}</td>
|
||
<td>${escapeHtml(row.quantity || '-')}</td>
|
||
<td>${escapeHtml(row.formCount || '-')}</td>
|
||
<td>${escapeHtml(row.remarks || '-')}</td>
|
||
</tr>
|
||
`).join('') : '<tr><td colspan="7">내용이 없습니다.</td></tr>'}
|
||
</table>
|
||
<div class="plan-section-title">교량제원 - 프리덱 / 가로보</div>
|
||
<table class="plan-table">
|
||
<tr><th colspan="2">프리덱</th><th>형고(m)</th><th>길이(m)</th><th>수량(EA)</th><th>비고</th></tr>
|
||
${crossbeamRows.map((row, index) => `
|
||
<tr>
|
||
${index === 0 ? `<th>일반부(㎡)</th><td>${escapeHtml(predeck.generalArea || '-')}</td>` : ''}
|
||
${index === 1 ? `<th>중분대(㎡)</th><td>${escapeHtml(predeck.medianArea || '-')}</td>` : ''}
|
||
${index === 2 ? `<th>방호벽부(㎡)</th><td>${escapeHtml(predeck.barrierArea || '-')}</td>` : ''}
|
||
${index >= 3 ? `<th>비고</th><td>${escapeHtml(predeck.remarks || '-')}</td>` : ''}
|
||
<td>${escapeHtml(row.height || '-')}</td>
|
||
<td>${escapeHtml(row.length || '-')}</td>
|
||
<td>${escapeHtml(row.quantity || '-')}</td>
|
||
<td>${escapeHtml(row.remarks || '-')}</td>
|
||
</tr>
|
||
`).join('')}
|
||
${crossbeamRows.length < 4 ? `
|
||
${crossbeamRows.length <= 0 ? '' : ''}
|
||
${crossbeamRows.length < 2 ? `<tr><th>중분대(㎡)</th><td>${escapeHtml(predeck.medianArea || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||
${crossbeamRows.length < 3 ? `<tr><th>방호벽부(㎡)</th><td>${escapeHtml(predeck.barrierArea || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||
${crossbeamRows.length < 4 ? `<tr><th>비고</th><td>${escapeHtml(predeck.remarks || '-')}</td><td colspan="4"></td></tr>` : ''}
|
||
` : ''}
|
||
</table>
|
||
`;
|
||
}
|
||
|
||
function mergeBridgeRows(scaleRows, overviews, budgetPlan) {
|
||
const mergedMap = new Map();
|
||
const order = [];
|
||
|
||
const ensureRow = (name, seed = {}) => {
|
||
const key = normalizeBridgeName(name) || `__row_${order.length}`;
|
||
if (!mergedMap.has(key)) {
|
||
mergedMap.set(key, { key, bridgeName: String(name || '').trim(), scaleRow: null, overviewRow: null, ...seed });
|
||
order.push(key);
|
||
}
|
||
return mergedMap.get(key);
|
||
};
|
||
|
||
(scaleRows || []).forEach((row) => {
|
||
const target = ensureRow(row.bridgeName, {});
|
||
target.bridgeName = target.bridgeName || row.bridgeName || '';
|
||
target.scaleRow = row;
|
||
});
|
||
|
||
(overviews || []).forEach((row) => {
|
||
const target = ensureRow(row.bridgeName || row.bridgeDisplayName, {});
|
||
target.bridgeName = target.bridgeName || row.bridgeName || row.bridgeDisplayName || '';
|
||
target.overviewRow = row;
|
||
});
|
||
|
||
return order.map((key) => {
|
||
const row = mergedMap.get(key);
|
||
const scaleRow = row.scaleRow || {};
|
||
const overviewRow = row.overviewRow || {};
|
||
return {
|
||
bridgeName: row.bridgeName || overviewRow.bridgeName || scaleRow.bridgeName || '-',
|
||
applicationType: normalizeValue(overviewRow.applicationType),
|
||
constructionStatus: formatStatus(scaleRow, overviewRow),
|
||
constructionPeriod: formatConstructionPeriod(overviewRow.constructionPeriod),
|
||
spanLengthDown: normalizeValue(overviewRow.spanLengthDown),
|
||
spanLengthUp: normalizeValue(overviewRow.spanLengthUp, scaleRow.spanLength || '-'),
|
||
widthDown: normalizeValue(overviewRow.widthDown),
|
||
widthUp: normalizeValue(overviewRow.widthUp, scaleRow.width || '-'),
|
||
girderHeightSupport: normalizeValue(overviewRow.girderHeightSupport),
|
||
girderHeightCenter: normalizeValue(overviewRow.girderHeightCenter, scaleRow.girderHeight || '-'),
|
||
spanCompositionDown: normalizeValue(overviewRow.spanCompositionDown),
|
||
spanCompositionUp: normalizeValue(overviewRow.spanCompositionUp),
|
||
girderCount: scaleRow.girderCount || '-',
|
||
crossbeamCount: scaleRow.crossbeamCount || '-',
|
||
panelCount: scaleRow.panelCount || '-',
|
||
rebarCount: scaleRow.rebarCount || '-',
|
||
rawBridgeLocation: overviewRow.bridgeLocation || '',
|
||
bridgeLocation: formatBridgeLocation(overviewRow.bridgeLocation),
|
||
remarks: overviewRow.remarks || '-',
|
||
};
|
||
});
|
||
}
|
||
|
||
function renderMergedBridgeRows(scaleRows, overviews, budgetPlan) {
|
||
const mergedRows = mergeBridgeRows(scaleRows, overviews, budgetPlan);
|
||
const siteLocation = normalizeValue(state.detail?.siteLocation || state.selectedRow?.siteLocation || '');
|
||
const hasSiteLocation = siteLocation && siteLocation !== '-';
|
||
bridgeMeta.textContent = mergedRows.length
|
||
? `공사규모 ${scaleRows.length}건 · 공사개요 ${overviews.length}건을 교량명 기준으로 매칭했습니다.`
|
||
: '연결된 공사규모나 공사개요 데이터가 없습니다.';
|
||
|
||
if (!mergedRows.length) {
|
||
bridgeBody.innerHTML = '<tr><td colspan="19" class="empty">표시할 교량 매칭 정보가 없습니다.</td></tr>';
|
||
return;
|
||
}
|
||
|
||
bridgeBody.innerHTML = mergedRows.map((row) => `
|
||
<tr>
|
||
<td class="cell-text">${escapeHtml(row.bridgeName)}</td>
|
||
<td class="cell-text">${escapeHtml(row.applicationType)}</td>
|
||
<td class="bridge-summary">${toHtmlWithBreaks(row.constructionStatus)}</td>
|
||
<td class="bridge-summary">${toHtmlWithBreaks(row.constructionPeriod)}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.spanLengthDown))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.spanLengthUp))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.widthDown))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.widthUp))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.girderHeightSupport))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.girderHeightCenter))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.spanCompositionDown))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.spanCompositionUp))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.girderCount))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.crossbeamCount))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.panelCount))}</td>
|
||
<td class="cell-num bridge-summary">${toHtmlWithBreaks(formatCellValue(row.rebarCount))}</td>
|
||
<td class="bridge-summary cell-wide">
|
||
<div class="map-location">
|
||
<div>${toHtmlWithBreaks(row.bridgeLocation)}</div>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<button
|
||
class="remark-button"
|
||
type="button"
|
||
data-bridge="${escapeHtml(row.bridgeName)}"
|
||
data-remarks="${escapeHtml(row.remarks === '-' ? '' : row.remarks)}"
|
||
${row.remarks && row.remarks !== '-' ? '' : 'disabled'}
|
||
title="${row.remarks && row.remarks !== '-' ? '특이사항 보기' : '특이사항 없음'}"
|
||
>i</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
|
||
Array.from(bridgeBody.querySelectorAll('.remark-button')).forEach((button) => {
|
||
button.addEventListener('click', () => {
|
||
const bridgeName = button.getAttribute('data-bridge') || '교량';
|
||
const remarks = button.getAttribute('data-remarks') || '내용이 없습니다.';
|
||
openRemarkModal(`${bridgeName} 특이사항`, remarks);
|
||
});
|
||
});
|
||
}
|
||
|
||
function openRemarkModal(title, body) {
|
||
remarkModalTitle.textContent = title || '특이사항';
|
||
remarkModalBody.textContent = body || '내용이 없습니다.';
|
||
remarkModal.classList.add('open');
|
||
remarkModal.setAttribute('aria-hidden', 'false');
|
||
}
|
||
|
||
function closeRemarkModal() {
|
||
remarkModal.classList.remove('open');
|
||
remarkModal.setAttribute('aria-hidden', 'true');
|
||
}
|
||
|
||
function openPlanModal(bridgeName) {
|
||
renderBudgetPlanModal(state.budgetPlan, bridgeName);
|
||
planModal.classList.add('open');
|
||
planModal.setAttribute('aria-hidden', 'false');
|
||
}
|
||
|
||
function closePlanModal() {
|
||
planModal.classList.remove('open');
|
||
planModal.setAttribute('aria-hidden', 'true');
|
||
}
|
||
|
||
function applyFilter() {
|
||
const keyword = searchInput.value.trim().toLowerCase();
|
||
const selectedContractType = contractTypeFilter.value.trim();
|
||
const selectedApplicationType = applicationTypeFilter.value.trim();
|
||
if (!keyword) {
|
||
state.filteredRows = [...state.rows];
|
||
} else {
|
||
state.filteredRows = state.rows.filter((row) => {
|
||
return row.projectCode.toLowerCase().includes(keyword)
|
||
|| row.projectName.toLowerCase().includes(keyword);
|
||
});
|
||
}
|
||
|
||
if (selectedContractType) {
|
||
state.filteredRows = state.filteredRows.filter((row) => (row.contractType || '') === selectedContractType);
|
||
}
|
||
if (selectedApplicationType) {
|
||
state.filteredRows = state.filteredRows.filter((row) => splitApplicationTypes(row.applicationType).includes(selectedApplicationType));
|
||
}
|
||
state.filteredRows.sort(compareProjectCodeDesc);
|
||
|
||
countChip.textContent = `총 ${state.rows.length.toLocaleString()}건 / 표시 ${state.filteredRows.length.toLocaleString()}건`;
|
||
statusText.textContent = keyword
|
||
? `"${keyword}" 검색 결과 ${state.filteredRows.length.toLocaleString()}건입니다.`
|
||
: `프로젝트 목록 ${state.filteredRows.length.toLocaleString()}건을 표시 중입니다.`;
|
||
renderRows(state.filteredRows);
|
||
}
|
||
|
||
async function loadRows(refresh = false) {
|
||
statusText.textContent = refresh
|
||
? 'ERP 시공 코드를 DB로 동기화하는 중입니다.'
|
||
: 'DB에 저장된 시공 코드 목록을 불러오는 중입니다.';
|
||
reloadButton.disabled = true;
|
||
try {
|
||
const response = await fetch(`/api/erp-project-codes?page=const&refresh=${refresh ? '1' : '0'}`, { cache: 'no-store' });
|
||
const data = await response.json();
|
||
if (!response.ok) {
|
||
throw new Error(data.error || '프로젝트 목록 조회에 실패했습니다.');
|
||
}
|
||
|
||
state.rows = Array.isArray(data.rows) ? data.rows : [];
|
||
state.sourceTable = data.source || '';
|
||
state.sourceColumns = { projectCode: data.page || 'const', projectName: 'ProjectNickname' };
|
||
syncedAt = data.syncedAt || '';
|
||
populateFilterOptions();
|
||
|
||
sourceChip.textContent = state.sourceTable
|
||
? `원본 ERP → DB · page=${state.sourceColumns.projectCode || 'const'}${syncedAt ? ` · ${syncedAt}` : ''}`
|
||
: '원본 정보 없음';
|
||
|
||
applyFilter();
|
||
} catch (error) {
|
||
state.rows = [];
|
||
state.filteredRows = [];
|
||
countChip.textContent = '조회 실패';
|
||
sourceChip.textContent = '원본 테이블 확인 실패';
|
||
statusText.textContent = error.message || String(error);
|
||
resultBody.innerHTML = `<tr><td colspan="2" class="empty">${escapeHtml(error.message || String(error))}</td></tr>`;
|
||
} finally {
|
||
reloadButton.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function loadDetail(projectCode, projectName, refresh = false) {
|
||
if (!projectCode) {
|
||
renderDetail(null);
|
||
return;
|
||
}
|
||
detailMeta.textContent = refresh
|
||
? 'ERP 계약정보를 DB로 동기화하는 중입니다.'
|
||
: 'DB 캐시에 저장된 계약정보를 불러오는 중입니다.';
|
||
bridgeMeta.textContent = refresh
|
||
? 'ERP 공사규모와 공사개요를 동기화하는 중입니다.'
|
||
: 'DB 캐시에 저장된 공사규모와 공사개요를 불러오는 중입니다.';
|
||
detailSyncButton.disabled = true;
|
||
try {
|
||
const detailUrl = `/api/erp-contract-detail?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=${refresh ? '1' : '0'}`;
|
||
const overviewUrl = `/api/erp-bridge-overviews?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=${refresh ? '1' : '0'}`;
|
||
const budgetPlanUrl = `/api/erp-budget-plan?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=${refresh ? '1' : '0'}`;
|
||
const [detailResponse, overviewResponse, budgetPlanResponse] = await Promise.all([
|
||
fetch(detailUrl, { cache: 'no-store' }),
|
||
fetch(overviewUrl, { cache: 'no-store' }),
|
||
fetch(budgetPlanUrl, { cache: 'no-store' }),
|
||
]);
|
||
const data = await detailResponse.json();
|
||
const overviewData = await overviewResponse.json();
|
||
const budgetPlanData = await budgetPlanResponse.json();
|
||
const fetchFreshOverviewIfNeeded = async () => {
|
||
if (refresh || (overviewResponse.ok && Array.isArray(overviewData.overviews) && overviewData.overviews.length)) {
|
||
return Array.isArray(overviewData.overviews) ? overviewData.overviews : [];
|
||
}
|
||
try {
|
||
const freshOverviewResponse = await fetch(
|
||
`/api/erp-bridge-overviews?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||
{ cache: 'no-store' }
|
||
);
|
||
const freshOverviewData = await freshOverviewResponse.json();
|
||
if (freshOverviewResponse.ok) {
|
||
return Array.isArray(freshOverviewData.overviews) ? freshOverviewData.overviews : [];
|
||
}
|
||
} catch (error) {
|
||
}
|
||
return Array.isArray(overviewData.overviews) ? overviewData.overviews : [];
|
||
};
|
||
const fetchFreshBudgetPlanIfNeeded = async () => {
|
||
if (refresh || (budgetPlanResponse.ok && budgetPlanData.plan)) {
|
||
return budgetPlanResponse.ok ? (budgetPlanData.plan || null) : null;
|
||
}
|
||
try {
|
||
const freshBudgetPlanResponse = await fetch(
|
||
`/api/erp-budget-plan?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||
{ cache: 'no-store' }
|
||
);
|
||
const freshBudgetPlanData = await freshBudgetPlanResponse.json();
|
||
if (freshBudgetPlanResponse.ok) {
|
||
return freshBudgetPlanData.plan || null;
|
||
}
|
||
} catch (error) {
|
||
}
|
||
return budgetPlanResponse.ok ? (budgetPlanData.plan || null) : null;
|
||
};
|
||
|
||
if (!detailResponse.ok) {
|
||
if (!refresh && detailResponse.status === 404) {
|
||
state.bridgeOverviews = await fetchFreshOverviewIfNeeded();
|
||
state.budgetPlan = await fetchFreshBudgetPlanIfNeeded();
|
||
detailTitle.textContent = `${projectName || '-'} [${projectCode}]`;
|
||
detailMeta.textContent = 'DB 캐시에 계약정보가 없어 ERP에서 기본 계약정보를 가져오는 중입니다.';
|
||
try {
|
||
const freshDetailResponse = await fetch(
|
||
`/api/erp-contract-detail?page=const&projectCode=${encodeURIComponent(projectCode)}&projectName=${encodeURIComponent(projectName || '')}&refresh=1`,
|
||
{ cache: 'no-store' }
|
||
);
|
||
const freshDetailData = await freshDetailResponse.json();
|
||
if (freshDetailResponse.ok) {
|
||
state.detail = freshDetailData.detail || null;
|
||
if (state.selectedRow && state.detail) {
|
||
state.selectedRow.businessCode = state.detail.businessCode || state.selectedRow.businessCode || '';
|
||
state.selectedRow.siteLocation = state.detail.siteLocation || state.selectedRow.siteLocation || '';
|
||
state.selectedRow.clientName = state.detail.clientName || state.selectedRow.clientName || '';
|
||
state.selectedRow.finalContractAmountText = state.detail.finalContractAmountText || state.selectedRow.finalContractAmountText || '';
|
||
state.selectedRow.contractType = state.detail.contractType || state.selectedRow.contractType || '';
|
||
state.selectedRow.syncedAt = state.detail.syncedAt || state.selectedRow.syncedAt || '';
|
||
}
|
||
renderDetail(state.detail);
|
||
return;
|
||
}
|
||
} catch (error) {
|
||
}
|
||
|
||
state.detail = null;
|
||
const selectedSummary = state.selectedRow || {};
|
||
detailMeta.textContent = '기본 계약정보를 아직 가져오지 않았습니다. 새 정보 다시 가져오기를 누르면 ERP에서 수집합니다.';
|
||
renderDetailRows({
|
||
businessCode: selectedSummary.businessCode || '',
|
||
projectName: projectName || selectedSummary.projectName || '',
|
||
projectCode,
|
||
siteLocation: selectedSummary.siteLocation || '',
|
||
clientName: selectedSummary.clientName || '',
|
||
finalContractAmountText: selectedSummary.finalContractAmountText || '',
|
||
contractType: selectedSummary.contractType || '',
|
||
syncedAt: selectedSummary.syncedAt || '',
|
||
});
|
||
renderMergedBridgeRows([], state.bridgeOverviews || [], state.budgetPlan);
|
||
return;
|
||
}
|
||
throw new Error(data.error || '계약정보 조회에 실패했습니다.');
|
||
}
|
||
|
||
state.detail = data.detail || null;
|
||
state.bridgeOverviews = await fetchFreshOverviewIfNeeded();
|
||
state.budgetPlan = await fetchFreshBudgetPlanIfNeeded();
|
||
if (state.selectedRow && state.detail) {
|
||
state.selectedRow.businessCode = state.detail.businessCode || state.selectedRow.businessCode || '';
|
||
state.selectedRow.siteLocation = state.detail.siteLocation || state.selectedRow.siteLocation || '';
|
||
state.selectedRow.clientName = state.detail.clientName || state.selectedRow.clientName || '';
|
||
state.selectedRow.finalContractAmountText = state.detail.finalContractAmountText || state.selectedRow.finalContractAmountText || '';
|
||
state.selectedRow.contractType = state.detail.contractType || state.selectedRow.contractType || '';
|
||
state.selectedRow.syncedAt = state.detail.syncedAt || state.selectedRow.syncedAt || '';
|
||
state.selectedRow.applicationType = Array.from(new Set(
|
||
state.bridgeOverviews.map((item) => String(item.applicationType || '').trim()).filter(Boolean)
|
||
)).sort((a, b) => a.localeCompare(b, 'ko')).join('||') || state.selectedRow.applicationType || '';
|
||
const rowIndex = state.rows.findIndex((row) => row.projectCode === projectCode);
|
||
if (rowIndex >= 0) {
|
||
state.rows[rowIndex] = { ...state.rows[rowIndex], ...state.selectedRow };
|
||
populateFilterOptions();
|
||
}
|
||
}
|
||
renderDetail(state.detail);
|
||
} catch (error) {
|
||
state.detail = null;
|
||
state.bridgeOverviews = [];
|
||
state.budgetPlan = null;
|
||
detailTitle.textContent = `${projectName || '-'} [${projectCode}]`;
|
||
detailMeta.textContent = error.message || String(error);
|
||
detailBody.innerHTML = `<tr><td colspan="2" class="empty">${escapeHtml(error.message || String(error))}</td></tr>`;
|
||
bridgeMeta.textContent = error.message || String(error);
|
||
bridgeBody.innerHTML = `<tr><td colspan="19" class="empty">${escapeHtml(error.message || String(error))}</td></tr>`;
|
||
} finally {
|
||
detailSyncButton.disabled = false;
|
||
}
|
||
}
|
||
|
||
remarkModalClose.addEventListener('click', closeRemarkModal);
|
||
remarkModal.addEventListener('click', (event) => {
|
||
if (event.target === remarkModal) {
|
||
closeRemarkModal();
|
||
}
|
||
});
|
||
planModalClose.addEventListener('click', closePlanModal);
|
||
planModal.addEventListener('click', (event) => {
|
||
if (event.target === planModal) {
|
||
closePlanModal();
|
||
}
|
||
});
|
||
document.addEventListener('keydown', (event) => {
|
||
if (event.key !== 'Escape') return;
|
||
if (remarkModal.classList.contains('open')) closeRemarkModal();
|
||
if (planModal.classList.contains('open')) closePlanModal();
|
||
});
|
||
searchInput.addEventListener('input', applyFilter);
|
||
contractTypeFilter.addEventListener('change', applyFilter);
|
||
applicationTypeFilter.addEventListener('change', applyFilter);
|
||
reloadButton.addEventListener('click', () => loadRows(true));
|
||
detailSyncButton.addEventListener('click', () => {
|
||
if (state.selectedRow) {
|
||
loadDetail(state.selectedRow.projectCode, state.selectedRow.projectName, true);
|
||
}
|
||
});
|
||
loadRows(false).then(() => {
|
||
if (!state.rows.length) {
|
||
loadRows(true);
|
||
} else {
|
||
state.selectedRow = state.rows[0];
|
||
renderRows(state.filteredRows);
|
||
loadDetail(state.selectedRow.projectCode, state.selectedRow.projectName, false);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|