Files
frontend/apps/sn/app/html/old-pipe-component-sample/index.html
T

1189 lines
35 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>노후관로관리 실제 화면 기반 샘플</title>
<style>
/*
실제 화면 기준 참조
- Layout: packages/common/components/src/ui/layout/nofooter.tsx
- Header: packages/common/components/src/ui/header/index.tsx
- LNB: packages/common/components/src/ui/lnb/index.tsx
- Gis/GisTree: packages/common/business_components/src/ui/map
- Detail: apps/sn/app/html/(map-layout)/map/components/popupComponent/featureClickEventPopup.tsx
- VerticalTable: apps/sn/app/components/ui/verticalTable.tsx
*/
:root {
--primary: #258eea;
--primary-dark: #152346;
--download-green: #1fd078;
--label-cell: #dbeafe;
--header-height: 60px;
--lnb-width: 220px;
--tree-width: 250px;
--detail-width: 350px;
--border: #e1e7eb;
--table-border: #dbe2e6;
--panel-border: #d1d5db;
--panel-outline: #bdbdbd;
--apply-green: #31a83b;
--label: #555;
--muted: #777;
--map-bg: #eee9d9;
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 100%;
height: 100%;
min-width: 1440px;
overflow: hidden;
color: #111;
font-family: "Noto Sans KR", "Malgun Gothic", Arial, sans-serif;
font-size: 14px;
background: #fff;
}
button,
input,
select {
font: inherit;
}
button {
cursor: pointer;
}
.app-shell {
height: 100vh;
display: grid;
grid-template-rows: var(--header-height) 1fr;
overflow: hidden;
}
.site-header {
display: flex;
align-items: center;
justify-content: space-between;
height: var(--header-height);
border-bottom: 1px solid var(--border);
background: #fff;
padding: 0 16px;
z-index: 30;
}
.brand-area {
display: flex;
align-items: center;
gap: 14px;
min-width: 210px;
}
.sidebar-trigger {
width: 30px;
height: 30px;
display: grid;
place-items: center;
border: 0;
background: transparent;
color: #111;
font-size: 22px;
line-height: 1;
}
.logo-block {
display: grid;
justify-items: center;
line-height: 1;
}
.logo-text {
font-size: 22px;
font-weight: 800;
letter-spacing: 1px;
}
.logo-text .blue {
color: #2392d0;
}
.logo-text .green {
color: #6ac04c;
}
.logo-caption {
margin-top: 5px;
color: #111;
font-size: 11px;
font-weight: 600;
letter-spacing: 4px;
white-space: nowrap;
}
.global-nav {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 18px;
min-width: 0;
overflow: hidden;
white-space: nowrap;
}
.global-nav a {
position: relative;
color: #111;
text-decoration: none;
font-size: 16px;
font-weight: 600;
letter-spacing: -0.04em;
}
.global-nav a::after {
content: "";
position: absolute;
right: -10px;
top: 50%;
width: 1px;
height: 16px;
background: var(--border);
transform: translateY(-50%);
}
.global-nav a:last-child::after {
display: none;
}
.header-tools {
display: flex;
align-items: center;
gap: 10px;
min-width: 120px;
justify-content: flex-end;
}
.search-circle {
width: 32px;
height: 32px;
border: 0;
border-radius: 999px;
color: #fff;
background: #1598e8;
font-size: 18px;
font-weight: 700;
}
.user-chip {
display: flex;
align-items: center;
gap: 5px;
color: #555;
font-size: 13px;
border: 0;
background: transparent;
}
.page-body {
min-height: 0;
display: flex;
overflow: hidden;
}
.lnb {
width: var(--lnb-width);
flex: 0 0 var(--lnb-width);
border-right: 1px solid var(--border);
background: #fff;
padding-top: 2px;
}
.lnb-item {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
height: 42px;
padding: 0 14px 0 26px;
color: #555;
text-decoration: none;
font-size: 14px;
font-weight: 500;
border-radius: 5px;
}
.lnb-item.active {
color: #258eea;
background: #f4f8fc;
}
.lnb-item .arrow {
color: #888;
font-size: 18px;
}
.content {
position: relative;
flex: 1;
min-width: 0;
overflow: hidden;
background: #f7f7f7;
}
.gis-map {
position: absolute;
inset: 0;
overflow: hidden;
background:
radial-gradient(ellipse at 28% 28%, rgba(95, 138, 93, 0.2) 0 19%, transparent 20%),
radial-gradient(ellipse at 53% 32%, rgba(226, 218, 166, 0.35) 0 24%, transparent 25%),
linear-gradient(104deg, transparent 0 56%, rgba(234, 219, 161, 0.72) 56.1% 58%, transparent 58.1%),
linear-gradient(4deg, transparent 0 31%, rgba(234, 219, 161, 0.72) 31.1% 33%, transparent 33.1%),
var(--map-bg);
}
.gis-map::before {
content: "";
position: absolute;
inset: 0;
opacity: 0.45;
background-image:
repeating-radial-gradient(ellipse at 30% 45%, transparent 0 22px, rgba(158, 151, 111, 0.35) 23px 24px, transparent 25px 42px),
repeating-linear-gradient(87deg, transparent 0 110px, rgba(180, 180, 160, 0.35) 111px 112px);
}
.river,
.road,
.map-line,
.pipe-line,
.area-polygon {
position: absolute;
z-index: 1;
transform-origin: left center;
}
.river {
width: 34px;
height: 100%;
left: 55%;
top: 0;
transform: rotate(-16deg);
background: rgba(95, 170, 202, 0.28);
border-radius: 999px;
}
.road {
height: 25px;
border: 1px solid rgba(190, 176, 126, 0.65);
border-radius: 999px;
background: #fff8dc;
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.78);
z-index: 2;
}
.map-line {
height: 2px;
border-radius: 999px;
z-index: 3;
}
.red-line {
background: #ff1b1b;
}
.purple-line {
background: #9b35ff;
}
.green-line {
background: #65d35f;
}
.pipe-line {
height: 4px;
border: 0;
padding: 0;
border-radius: 999px;
background: #496179;
z-index: 4;
}
.pipe-line.old {
height: 6px;
background: #e3222b;
box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.72), 0 0 0 7px rgba(227, 34, 43, 0.22);
}
.pipe-line.selected {
height: 8px;
box-shadow: 0 0 0 5px rgba(255, 255, 255, 0.95), 0 0 0 10px rgba(37, 142, 234, 0.26);
}
.map-label {
position: absolute;
z-index: 5;
padding: 4px 7px;
color: #444;
background: rgba(255, 255, 255, 0.78);
border: 1px solid #ccd6df;
border-radius: 2px;
font-size: 12px;
font-weight: 500;
pointer-events: none;
}
.area-polygon {
border: 2px solid #258eea;
background: rgba(37, 142, 234, 0.14);
clip-path: polygon(8% 9%, 90% 0, 100% 56%, 74% 100%, 12% 78%, 0 38%);
z-index: 3;
}
.area-polygon button {
width: 100%;
height: 100%;
border: 0;
background: transparent;
color: #14466f;
font-weight: 700;
}
.top-search {
position: absolute;
left: calc(var(--tree-width) + 280px);
top: 18px;
z-index: 20;
display: grid;
grid-template-columns: 150px 150px 180px 58px;
gap: 18px;
align-items: center;
padding: 10px 12px;
border-radius: 4px;
background: rgba(255, 255, 255, 0.72);
box-shadow: 0 0 12px rgba(0, 0, 0, 0.08);
}
.top-search select,
.top-search button {
height: 30px;
border-radius: 3px;
border: 1px solid #d7dde3;
font-size: 12px;
}
.top-search select {
padding: 0 10px;
color: #555;
background: #fff;
}
.top-search button {
border-color: #1a99ee;
color: #fff;
background: #1598e8;
font-weight: 700;
}
.gis-tree {
position: absolute;
left: 0;
top: 0;
z-index: 40;
width: var(--tree-width);
height: 100%;
display: flex;
flex-direction: column;
border-right: 1px solid var(--panel-border);
background: #fff;
overflow: hidden;
padding: 8px;
gap: 12px;
}
.tree-panel {
display: flex;
min-height: 0;
flex-direction: column;
border: 1px solid var(--panel-outline);
border-radius: 3px;
background: #fff;
overflow: hidden;
}
.tree-panel.layer-panel {
flex: 1 1 auto;
}
.tree-panel.config-panel {
flex: 0 0 288px;
}
.tree-panel.collapsed {
flex: 0 0 auto;
}
.tree-panel.collapsed .tree-scroll,
.tree-panel.collapsed .setting-body {
display: none;
}
.panel-title {
position: relative;
display: flex;
align-items: center;
justify-content: center;
min-height: 34px;
padding: 0 28px;
color: #4b5563;
border-bottom: 1px solid #eeeeee;
background: #fff;
font-size: 14px;
font-weight: 700;
text-align: center;
}
.panel-title .collapse-toggle {
position: absolute;
left: 8px;
top: 50%;
width: 18px;
height: 18px;
border: 0;
background: transparent;
color: #555;
transform: translateY(-50%);
font-size: 14px;
line-height: 1;
}
.gis-tree .tree-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 6px 10px 14px;
}
.tree-row {
display: flex;
align-items: center;
min-height: 26px;
color: #333;
font-size: 13px;
white-space: nowrap;
}
.tree-row.depth-1 {
padding-left: 18px;
}
.tree-row.depth-2 {
padding-left: 34px;
}
.tree-row input {
width: 14px;
height: 14px;
margin: 0 6px 0 0;
accent-color: #258eea;
}
.tree-row .twisty {
width: 14px;
color: #666;
font-size: 14px;
}
.tree-row .icon {
display: inline-block;
width: 12px;
height: 12px;
margin-right: 5px;
border: 1px solid #999;
vertical-align: middle;
}
.tree-row .list-icon {
margin-left: 5px;
width: 14px;
height: 14px;
border: 1px solid #999;
background:
linear-gradient(#777 0 0) 2px 3px / 10px 1px no-repeat,
linear-gradient(#777 0 0) 2px 6px / 10px 1px no-repeat,
linear-gradient(#777 0 0) 2px 9px / 10px 1px no-repeat,
#fff;
}
.tree-toggle {
position: absolute;
left: var(--tree-width);
top: 50%;
z-index: 41;
width: 24px;
height: 36px;
border: 1px solid #cfcfcf;
background: #fff;
color: #555;
transform: translateY(-50%);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.15);
}
.setting-body {
padding: 8px 12px 10px;
}
.setting-row {
display: flex;
align-items: center;
min-height: 30px;
gap: 5px;
color: #333;
font-size: 12px;
white-space: nowrap;
}
.setting-row label {
flex: 0 0 auto;
}
.setting-row input {
width: 40px;
height: 22px;
border: 1px solid #ccd6df;
padding: 0 4px;
color: #333;
background: #fff;
font-size: 12px;
text-align: center;
}
.apply-icon {
display: inline-grid;
place-items: center;
width: 22px;
height: 22px;
border: 0;
border-radius: 50%;
background: var(--apply-green);
color: #fff;
font-size: 15px;
font-weight: 700;
line-height: 1;
}
.field {
display: grid;
gap: 5px;
margin-bottom: 10px;
}
.field span {
color: #555;
font-size: 12px;
font-weight: 600;
}
.field input,
.field select {
width: 100%;
height: 32px;
border: 1px solid #ccd6df;
border-radius: 2px;
padding: 0 9px;
color: #333;
background: #fff;
font-size: 13px;
}
.btn-row {
display: flex;
justify-content: flex-end;
gap: 6px;
}
.btn {
height: 32px;
min-width: 58px;
border: 1px solid #cbd5e1;
border-radius: 2px;
background: #fff;
color: #111;
font-weight: 700;
font-size: 13px;
}
.btn.primary {
border-color: var(--primary);
background: var(--primary);
color: #fff;
}
.btn.download {
border-color: var(--download-green);
background: var(--download-green);
color: #fff;
}
.detail-popup {
position: absolute;
right: 0;
top: 0;
z-index: 50;
width: var(--detail-width);
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
border-left: 1px solid var(--panel-border);
background: #fff;
}
.detail-header {
position: relative;
min-height: 70px;
padding: 12px 38px 12px 14px;
background: var(--primary);
color: #fff;
}
.detail-header h3 {
margin: 0;
font-size: 15px;
line-height: 1.45;
font-weight: 700;
}
.detail-header .close {
position: absolute;
right: 11px;
top: 18px;
border: 0;
color: #fff;
background: transparent;
font-size: 20px;
}
.detail-scroll {
flex: 1;
overflow-y: auto;
padding-bottom: 20px;
}
.card-header {
margin: 0;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border);
padding: 12px 14px;
}
.card-header h3 {
margin: 0;
color: #333;
font-size: 14px;
font-weight: 700;
}
.vertical-card {
margin: 16px;
padding: 0;
overflow: hidden;
border: 1px solid var(--table-border);
background: #fff;
}
.vertical-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.vertical-table th,
.vertical-table td {
height: 41px;
border: 1px solid #e5e7eb;
padding: 8px 9px;
text-align: center;
font-size: 13px;
line-height: 1.35;
}
.vertical-table th {
width: 130px;
background: var(--label-cell);
color: #111827;
font-weight: 700;
}
.vertical-table td {
color: #111;
background: #fff;
font-weight: 500;
word-break: keep-all;
}
.list-modal {
position: fixed;
inset: 0;
z-index: 100;
display: none;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.34);
}
.list-modal.on {
display: flex;
}
.modal-body {
width: 1040px;
max-height: 78vh;
display: grid;
grid-template-rows: auto auto 1fr auto;
overflow: hidden;
background: #fff;
border: 1px solid var(--table-border);
box-shadow: 0 0 12px rgba(0, 0, 0, 0.25);
}
.modal-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: var(--primary);
color: #fff;
}
.modal-title h2 {
margin: 0;
font-size: 16px;
}
.modal-title button {
border: 0;
background: transparent;
color: #fff;
font-size: 18px;
}
.modal-search {
display: grid;
grid-template-columns: 120px 160px 160px 1fr;
gap: 10px;
align-items: end;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: #f8fafc;
}
.ui-table-wrap {
overflow: auto;
padding: 12px;
}
.ui-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.ui-table th,
.ui-table td {
height: 38px;
border: 1px solid var(--table-border);
padding: 8px;
text-align: center;
font-size: 13px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.ui-table th {
background: #eaf3fb;
color: #647784;
font-weight: 700;
}
.ui-table tbody tr:hover {
background: #edf4fb;
font-weight: 700;
}
.modal-footer {
padding: 10px 16px;
color: #666;
border-top: 1px solid var(--border);
font-size: 12px;
display: flex;
justify-content: space-between;
}
.toast {
position: fixed;
right: 24px;
bottom: 24px;
z-index: 120;
display: none;
max-width: 420px;
padding: 12px 14px;
color: #fff;
background: var(--primary-dark);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
font-weight: 700;
}
.toast.on {
display: block;
}
.hidden {
display: none !important;
}
</style>
</head>
<body>
<div class="app-shell">
<header class="site-header">
<div class="brand-area">
<button class="sidebar-trigger" type="button" title="메뉴">▯</button>
<div class="logo-block">
<div class="logo-text"><span class="blue">HASU</span><span class="green">ON</span></div>
<div class="logo-caption">디지털하수관로 시스템</div>
</div>
</div>
<nav class="global-nav" aria-label="상단 시스템 메뉴">
<a href="#">유역하수도포털</a>
<a href="#">통합플랫폼관리시스템</a>
<a href="#">디지털자기고 관리 시스템</a>
<a href="#">물재이용 대민사이트</a>
<a href="#">물재이용 대민</a>
<a href="#">하수처리시설 운영실태 분석 시스템</a>
<a href="#">디지털하수관로시스템</a>
<a href="#">하수도 통계 시스템</a>
</nav>
<div class="header-tools">
<button class="search-circle" type="button" title="검색">⌕</button>
<button class="user-chip" type="button">● 관리자⌄</button>
</div>
</header>
<div class="page-body">
<aside class="lnb" aria-label="좌측 메뉴">
<a class="lnb-item active" href="#">하수관망도</a>
<a class="lnb-item" href="#">I/I 분석 <span class="arrow">›</span></a>
<a class="lnb-item" href="#">운영정보 <span class="arrow">›</span></a>
</aside>
<main class="content">
<section class="gis-map" aria-label="GIS 지도">
<div class="river"></div>
<div class="road" style="left:20%;top:31%;width:66%;transform:rotate(3deg)"></div>
<div class="road" style="left:38%;top:57%;width:68%;transform:rotate(-18deg)"></div>
<div class="road" style="left:62%;top:8%;width:54%;transform:rotate(94deg)"></div>
<span class="map-label" style="left:46%;top:17%">하수처리시설</span>
<span class="map-label" style="left:55%;top:49%">왕숙천</span>
<span class="map-label" style="left:26%;top:24%">진건로</span>
<div class="map-line red-line" style="left:58%;top:12%;width:18%;transform:rotate(104deg)"></div>
<div class="map-line red-line" style="left:70%;top:20%;width:21%;transform:rotate(-18deg)"></div>
<div class="map-line purple-line" style="left:57%;top:74%;width:30%;transform:rotate(-33deg)"></div>
<div class="map-line green-line" style="left:56%;top:26%;width:18%;transform:rotate(-64deg)"></div>
<button class="pipe-line old selected" data-pipe="PPL-001" style="left:25%;top:31%;width:30%;transform:rotate(-14deg)" title="41360-PPL-0001"></button>
<button class="pipe-line" data-pipe="PPL-002" style="left:34%;top:49%;width:31%;transform:rotate(23deg)" title="41360-PPL-0002"></button>
<button class="pipe-line" data-pipe="PPL-003" style="left:59%;top:50%;width:28%;transform:rotate(-31deg)" title="11680-PPL-0142"></button>
<button class="pipe-line old" data-pipe="PPL-004" style="left:49%;top:64%;width:43%;transform:rotate(12deg)" title="11710-PPL-0209"></button>
<div class="area-polygon hidden" style="left:48%;top:23%;width:18%;height:22%">
<button type="button" data-area="AREA-001">진건처리구역</button>
</div>
<div class="area-polygon hidden" style="left:68%;top:48%;width:22%;height:25%">
<button type="button" data-area="AREA-002">송파처리구역</button>
</div>
</section>
<form class="top-search">
<select aria-label="시도"><option>시도</option><option>경기도</option></select>
<select aria-label="시군구"><option>시군구</option><option>용인시</option><option>남양주시</option></select>
<select aria-label="시설"><option>하수처리시설</option><option>하수관로</option></select>
<button type="button">조회</button>
</form>
<aside class="gis-tree" aria-label="시설물 레이어 트리">
<section id="layerPanel" class="tree-panel layer-panel">
<div class="panel-title">
<button class="collapse-toggle" type="button" data-target="layerPanel">⌄</button>
<span>시설물</span>
</div>
<div id="manageTree" class="tree-scroll">
<div class="tree-row"><span class="twisty">›</span><input type="checkbox" /><span>시설</span></div>
<div class="tree-row"><span class="twisty">›</span><input type="checkbox" checked /><span>침수위험지도(중점관리지역)</span></div>
<div class="tree-row"><span class="twisty">›</span><input type="checkbox" /><span>도시침수지도(정보제공포털)</span></div>
<div class="tree-row"><span class="twisty"></span><input type="checkbox" /><span>지반침하정보</span><button id="groundListIcon" class="list-icon" type="button" title="목록"></button></div>
<div class="tree-row"><span class="twisty"></span><input id="oldPipeLayer" type="checkbox" checked /><span>노후관로</span><button id="openListByTree" class="list-icon" type="button" title="목록"></button></div>
</div>
<div id="analysisTree" class="tree-scroll hidden">
<div class="tree-row"><span class="twisty">⌄</span><input type="checkbox" checked /><span>하수관로</span></div>
<div class="tree-row depth-1"><span class="twisty"></span><input type="checkbox" checked /><span>합류관</span></div>
<div class="tree-row depth-1"><span class="twisty"></span><input type="checkbox" checked /><span>우수관</span></div>
<div class="tree-row depth-1"><span class="twisty"></span><input type="checkbox" checked /><span>오수관</span></div>
<div class="tree-row"><span class="twisty"></span><input type="checkbox" checked /><span>하수처리구역</span></div>
<div class="tree-row"><span class="twisty"></span><input type="checkbox" /><span>노후관로</span><button id="openListByAnalysisTree" class="list-icon" type="button" title="목록"></button></div>
</div>
</section>
<section id="oldFilter" class="tree-panel config-panel">
<div class="panel-title">
<button class="collapse-toggle" type="button" data-target="oldFilter">⌄</button>
<span>지반침하정보</span>
</div>
<div class="setting-body">
<div class="setting-row">
<label for="ageInput">경과년도(년)</label>
<input id="ageInput" value="30" inputmode="numeric" />
<button id="applyAgeButton" class="apply-icon" type="button" title="적용">✓</button>
</div>
</div>
</section>
</aside>
<button class="tree-toggle" type="button">‹</button>
<aside class="detail-popup">
<header class="detail-header">
<h3 id="detailTitle">하수관로 정보 <br />관리번호 : 41360-PPL-0001</h3>
<button class="close" type="button">×</button>
</header>
<div class="detail-scroll">
<div class="card-header"><h3 id="detailSectionTitle">기본정보</h3></div>
<div class="vertical-card">
<table class="vertical-table"><tbody id="detailTable"></tbody></table>
</div>
</div>
</aside>
</main>
</div>
</div>
<div id="listModal" class="list-modal">
<section class="modal-body">
<header class="modal-title">
<h2>노후관로 현황</h2>
<button id="closeListModal" type="button">×</button>
</header>
<div class="modal-search">
<label class="field"><span>경과년도</span><input id="modalAge" value="20" /></label>
<label class="field"><span>시도</span><select id="modalSido"><option>전체</option><option>경기도</option><option>서울특별시</option></select></label>
<label class="field"><span>시군구</span><select id="modalSigungu"><option>전체</option><option>용인시</option><option>남양주시</option><option>송파구</option></select></label>
<div class="btn-row">
<button id="searchRows" class="btn primary" type="button">조회</button>
<button id="downloadRows" class="btn download" type="button">다운로드</button>
</div>
</div>
<div class="ui-table-wrap">
<table class="ui-table">
<thead><tr><th>NO</th><th>시설관리번호</th><th>설치일자</th><th>관경(mm)</th><th>연장(m)</th><th>용도</th><th>재질</th><th>경과년도</th></tr></thead>
<tbody id="gridBody"></tbody>
</table>
</div>
<footer class="modal-footer">
<span id="gridCount">조회 결과 0건</span>
<span>행 더블클릭 시 기존 `onFeatureSelect` 흐름으로 지도 이동 + 상세 표출</span>
</footer>
</section>
</div>
<div id="toast" class="toast"></div>
<script>
const currentYear = 2026;
let screen = "manage";
let threshold = 20;
const pipes = [
{ id: "PPL-001", fcltMngNo: "41360-PPL-0001", instMngNo: "41360PB005R-01", instlYmd: "1998-12-15", mapshtNo: "NJ52-9-12-034", pdmtVl: "450 mm", prlgVl: "132.5 m", usgCd: "합류관", mqltCd: "철근콘크리트관(RC)", avgGrdn: "2.1", drtwMltvArea: "2,140", sido: "경기도", sigungu: "남양주시", areaId: "AREA-001" },
{ id: "PPL-002", fcltMngNo: "41360-PPL-0002", instMngNo: "41360PB005R-02", instlYmd: "1987-06-03", mapshtNo: "NJ52-9-12-035", pdmtVl: "600 mm", prlgVl: "215.8 m", usgCd: "오수관", mqltCd: "흄관(HP)", avgGrdn: "1.8", drtwMltvArea: "3,560", sido: "경기도", sigungu: "남양주시", areaId: "AREA-001" },
{ id: "PPL-003", fcltMngNo: "11680-PPL-0142", instMngNo: "11680GN-0142", instlYmd: "2004-04-21", mapshtNo: "NJ52-5-07-110", pdmtVl: "300 mm", prlgVl: "88.4 m", usgCd: "우수관", mqltCd: "염화비닐관(PVC)", avgGrdn: "3.2", drtwMltvArea: "-", sido: "서울특별시", sigungu: "강남구", areaId: "AREA-002" },
{ id: "PPL-004", fcltMngNo: "11710-PPL-0209", instMngNo: "11710SP-0209", instlYmd: "1974-10-11", mapshtNo: "NJ52-5-07-114", pdmtVl: "800 mm", prlgVl: "310.2 m", usgCd: "합류관", mqltCd: "주철관(CIP)", avgGrdn: "1.2", drtwMltvArea: "5,210", sido: "서울특별시", sigungu: "송파구", areaId: "AREA-002" }
];
const areas = {
"AREA-001": { name: "진건처리구역", manageNo: "41360PB005R", sido: "경기도", sigungu: "남양주시", plant: "진건" },
"AREA-002": { name: "송파처리구역", manageNo: "11710SP001R", sido: "서울특별시", sigungu: "송파구", plant: "탄천" }
};
const $ = (id) => document.getElementById(id);
const ageOf = (pipe) => currentYear - Number(pipe.instlYmd.slice(0, 4));
function row(label, value) {
return `<tr><th>${label}</th><td>${value}</td></tr>`;
}
function renderPipeDetail(pipeId) {
const pipe = pipes.find((item) => item.id === pipeId);
if (!pipe) return;
document.querySelectorAll(".pipe-line").forEach((line) => line.classList.toggle("selected", line.dataset.pipe === pipeId));
$("detailTitle").innerHTML = `하수관로 정보 <br />관리번호 : ${pipe.fcltMngNo}`;
$("detailSectionTitle").textContent = "기본정보";
$("detailTable").innerHTML = [
row("시설관리번호", pipe.fcltMngNo),
row("기관관리번호", pipe.instMngNo),
row("설치일자", pipe.instlYmd),
row("도엽번호", pipe.mapshtNo),
row("관경(mm)", pipe.pdmtVl),
row("연장(m)", pipe.prlgVl),
row("용도", pipe.usgCd),
row("재질", pipe.mqltCd),
row("평균구배(‰)", pipe.avgGrdn),
row("오수배수면적(㎡)", pipe.drtwMltvArea),
row("경과년도", `${ageOf(pipe)}년`)
].join("");
}
function renderAreaDetail(areaId) {
const area = areas[areaId];
const areaPipes = pipes.filter((pipe) => pipe.areaId === areaId);
const ages = areaPipes.map(ageOf);
const group = (use, mode) => {
const values = areaPipes.filter((pipe) => pipe.usgCd === use).map(ageOf);
if (!values.length) return "-";
if (mode === "avg") return (values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(1);
if (mode === "max") return Math.max(...values);
return Math.min(...values);
};
document.querySelectorAll(".area-polygon").forEach((item) => item.classList.toggle("selected", item.querySelector("button").dataset.area === areaId));
$("detailTitle").innerHTML = `하수처리구역 정보 <br />관리번호 : ${area.manageNo}`;
$("detailSectionTitle").textContent = "기본정보";
$("detailTable").innerHTML = [
row("하수처리구역명", area.name),
row("시도명", area.sido),
row("시군구명", area.sigungu),
row("하수처리시설명", area.plant),
row("평균 매설년도", `전체 ${((ages.reduce((sum, value) => sum + value, 0) / ages.length)).toFixed(1)} / 합류 ${group("합류관", "avg")} / 우수 ${group("우수관", "avg")} / 오수 ${group("오수관", "avg")}`),
row("최장 매설년도", `전체 ${Math.max(...ages)} / 합류 ${group("합류관", "max")} / 우수 ${group("우수관", "max")} / 오수 ${group("오수관", "max")}`),
row("최신 매설년도", `전체 ${Math.min(...ages)} / 합류 ${group("합류관", "min")} / 우수 ${group("우수관", "min")} / 오수 ${group("오수관", "min")}`),
row("보수이력", "<button class='btn primary' type='button'>조회</button>")
].join("");
}
function rowsByFilter() {
const age = Number($("modalAge").value);
const sido = $("modalSido").value;
const sigungu = $("modalSigungu").value;
return pipes
.filter((pipe) => ageOf(pipe) >= age)
.filter((pipe) => sido === "전체" || pipe.sido === sido)
.filter((pipe) => sigungu === "전체" || pipe.sigungu === sigungu)
.sort((a, b) => ageOf(b) - ageOf(a));
}
function renderGrid() {
const age = Number($("modalAge").value);
if (!Number.isInteger(age) || age < 1 || age > 100) {
toast("경과년도는 1년~100년 범위 내에서 입력해주세요.");
return;
}
const rows = rowsByFilter();
$("gridCount").textContent = `조회 결과 ${rows.length.toLocaleString("ko-KR")}건`;
$("gridBody").innerHTML = rows.map((pipe, index) => `
<tr data-pipe="${pipe.id}">
<td>${index + 1}</td>
<td>${pipe.fcltMngNo}</td>
<td>${pipe.instlYmd}</td>
<td>${pipe.pdmtVl.replace(" mm", "")}</td>
<td>${pipe.prlgVl.replace(" m", "")}</td>
<td>${pipe.usgCd}</td>
<td>${pipe.mqltCd}</td>
<td>${ageOf(pipe)}</td>
</tr>
`).join("");
document.querySelectorAll("#gridBody tr").forEach((tr) => {
tr.addEventListener("dblclick", () => {
renderPipeDetail(tr.dataset.pipe);
$("listModal").classList.remove("on");
toast("선택 관로로 지도 이동 후 상세정보를 표시했습니다.");
});
});
}
function openList() {
$("modalAge").value = String(threshold);
$("modalSido").value = "전체";
$("modalSigungu").value = "전체";
renderGrid();
$("listModal").classList.add("on");
}
function toast(message) {
const target = $("toast");
target.textContent = message;
target.classList.add("on");
clearTimeout(toast.timer);
toast.timer = setTimeout(() => target.classList.remove("on"), 2400);
}
function setScreen(next) {
screen = next;
const isAnalysis = screen === "analysis";
$("manageTree").classList.toggle("hidden", isAnalysis);
$("analysisTree").classList.toggle("hidden", !isAnalysis);
$("oldFilter").classList.toggle("hidden", isAnalysis);
document.querySelectorAll(".area-polygon").forEach((item) => item.classList.toggle("hidden", !isAnalysis));
if (isAnalysis) renderAreaDetail("AREA-001");
else renderPipeDetail("PPL-001");
}
document.querySelectorAll(".pipe-line").forEach((line) => line.addEventListener("click", () => renderPipeDetail(line.dataset.pipe)));
document.querySelectorAll(".area-polygon button").forEach((button) => button.addEventListener("click", () => renderAreaDetail(button.dataset.area)));
$("applyAgeButton").addEventListener("click", () => {
const age = Number($("ageInput").value);
if (!Number.isInteger(age) || age < 1 || age > 100) {
toast("경과년도는 1년~100년 범위 내에서 입력해주세요.");
return;
}
threshold = age;
document.querySelectorAll(".pipe-line").forEach((line) => {
const pipe = pipes.find((item) => item.id === line.dataset.pipe);
line.classList.toggle("old", ageOf(pipe) >= threshold);
});
toast(`기준 ${threshold}년 이상 노후관로를 지도에 반영했습니다.`);
});
$("openListByTree").addEventListener("click", openList);
$("openListByAnalysisTree").addEventListener("click", openList);
$("groundListIcon").addEventListener("click", () => toast("지반침하정보 목록 아이콘 클릭 샘플입니다."));
$("closeListModal").addEventListener("click", () => $("listModal").classList.remove("on"));
$("searchRows").addEventListener("click", renderGrid);
$("downloadRows").addEventListener("click", () => toast("실제 구현 시 downloadExcelWithKoreanHeaders를 호출합니다."));
$("listModal").addEventListener("click", (event) => {
if (event.target === $("listModal")) $("listModal").classList.remove("on");
});
// 샘플 전환용: 실제 앱에서는 라우팅 메뉴로 분리된다.
window.showAgedPipeManage = () => setScreen("manage");
window.showAgedPipeAnalysis = () => setScreen("analysis");
document.querySelectorAll(".collapse-toggle").forEach((button) => {
button.addEventListener("click", () => {
const panel = $(button.dataset.target);
const collapsed = panel.classList.toggle("collapsed");
button.textContent = collapsed ? "›" : "⌄";
});
});
setScreen("manage");
</script>
</body>
</html>