html / index.html
blueellia's picture
Upload index.html
d489611 verified
Raw History Blame Contribute Delete
27.5 kB
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>κ΅­λ―Όμ—°κΈˆ κ°€μž… 사업μž₯ λ‚΄μ—­</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{
--bg:#ffffff;--bg2:#f8f9fa;--bg3:#f1f3f5;--border:#dee2e6;
--text:#212529;--muted:#868e96;--accent:#3b82f6;--green:#10b981;
--yellow:#f59e0b;--red:#ef4444;--sidebar:#ffffff;
}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Pretendard',-apple-system,sans-serif;background:var(--bg);color:var(--text);height:100vh;display:flex;flex-direction:column;overflow:hidden}
.topbar{display:flex;align-items:center;justify-content:space-between;padding:0 1.5rem;height:54px;background:#fff;border-bottom:1px solid var(--border);flex-shrink:0;box-shadow:0 1px 4px rgba(0,0,0,.06)}
.topbar h1{font-size:15px;font-weight:700;color:var(--text)}.topbar h1 span{color:var(--accent)}
.topbar-right{display:flex;align-items:center;gap:8px}
.status-badge{font-size:11px;color:var(--muted);background:var(--bg3);padding:3px 10px;border-radius:10px;border:1px solid var(--border)}
.upload-label{display:flex;align-items:center;gap:5px;padding:5px 12px;border-radius:6px;background:var(--accent);color:#fff;font-size:11px;font-weight:600;cursor:pointer}
.main{display:flex;flex:1;overflow:hidden}
.sidebar{width:260px;flex-shrink:0;background:#fff;border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden}
.sel-wrap{padding:10px 14px;border-bottom:1px solid var(--border)}
.sel-wrap label{font-size:10px;font-weight:600;color:var(--muted);letter-spacing:.8px;text-transform:uppercase;display:block;margin-bottom:5px}
.sel-box{width:100%;background:#fff;border:1px solid var(--border);color:var(--text);padding:7px 10px;border-radius:6px;font-size:12px;font-family:inherit;outline:none;cursor:pointer}
.sel-box:focus{border-color:var(--accent)}
.stats-row{display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border)}
.stat{background:var(--bg2);border-radius:8px;padding:10px 12px;border:1px solid var(--border)}
.stat .n{font-size:18px;font-weight:700;color:var(--accent);line-height:1}
.stat .l{font-size:10px;color:var(--muted);margin-top:3px}
.search-wrap{padding:8px 14px;border-bottom:1px solid var(--border)}
.search{width:100%;background:#fff;border:1px solid var(--border);color:var(--text);padding:7px 10px;border-radius:6px;font-size:12px;font-family:inherit;outline:none}
.search:focus{border-color:var(--accent)}
.search::placeholder{color:var(--muted)}
.list-hdr{display:flex;justify-content:space-between;padding:7px 14px;background:var(--bg3);border-bottom:1px solid var(--border)}
.list-hdr span{font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.5px}
.list-wrap{flex:1;overflow-y:auto}
.list-wrap::-webkit-scrollbar{width:4px}
.list-wrap::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}
.list-item{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid #f1f3f5;cursor:pointer;transition:background .1s}
.list-item:hover{background:#f8f9ff}
.list-item.active{background:#eff6ff;border-left:2px solid var(--accent)}
.item-left{flex:1;min-width:0}
.item-name{font-size:12px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.item-sub{font-size:10px;color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.item-emp{font-size:13px;font-weight:700;color:var(--green);white-space:nowrap;margin-left:8px}
.item-emp span{font-size:9px;color:var(--muted);font-weight:400}
.pill{display:inline-block;padding:1px 6px;border-radius:3px;font-size:9px;font-weight:600;margin-right:3px}
.pill-c{background:#dbeafe;color:#1d4ed8}
.pill-i{background:#d1fae5;color:#065f46}
.content{flex:1;display:flex;flex-direction:column;overflow:hidden}
.tab-bar{display:flex;align-items:center;padding:0 1rem;border-bottom:1px solid var(--border);background:#fff;height:44px;gap:2px}
.tab{padding:0 14px;height:100%;display:flex;align-items:center;font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;transition:all .15s}
.tab.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:600}
.tab-actions{margin-left:auto}
.dl-btn{display:flex;align-items:center;gap:5px;padding:6px 14px;border-radius:6px;background:var(--bg2);color:var(--text);font-size:11px;font-weight:600;cursor:pointer;border:1px solid var(--border);font-family:inherit;transition:all .15s}
.dl-btn:hover{background:var(--green);color:#fff;border-color:var(--green)}
.panel{display:none;flex:1;overflow:hidden}
.panel.active{display:flex;flex-direction:column}
#map{flex:1;width:100%}
.detail-scroll{flex:1;overflow-y:auto;padding:1.2rem;background:var(--bg2)}
.detail-scroll::-webkit-scrollbar{width:4px}
.detail-scroll::-webkit-scrollbar-thumb{background:var(--border)}
.d-card{background:#fff;border:1px solid var(--border);border-radius:10px;padding:1rem 1.2rem;margin-bottom:.8rem;box-shadow:0 1px 3px rgba(0,0,0,.04)}
.d-card h3{font-size:10px;font-weight:700;color:var(--muted);letter-spacing:.8px;text-transform:uppercase;margin-bottom:.8rem}
.d-row{display:flex;padding:7px 0;border-bottom:1px solid #f1f3f5}
.d-row:last-child{border-bottom:none}
.d-key{font-size:11px;color:var(--muted);min-width:110px}
.d-val{font-size:11px;color:var(--text);font-weight:500}
.formula{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:10px 14px;font-family:'Courier New',monospace;font-size:11px;color:#1d4ed8;margin-top:6px}
.highlight-box{margin-top:10px;padding:10px 14px;background:#eff6ff;border-radius:6px;border-left:3px solid var(--accent)}
.highlight-box .hl{font-size:11px;color:var(--muted);margin-bottom:4px}
.highlight-box .hv{font-size:20px;font-weight:700;color:var(--accent)}
.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:8px;color:var(--muted);text-align:center}
.empty .ei{font-size:36px;opacity:.3}
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:1000;backdrop-filter:blur(4px)}
.ov-box{background:#fff;border:1px solid var(--border);border-radius:16px;padding:2.5rem;text-align:center;max-width:380px;width:90%;box-shadow:0 20px 60px rgba(0,0,0,.15)}
.ov-box h2{font-size:18px;font-weight:700;margin-bottom:6px;color:var(--text)}
.ov-box p{font-size:12px;color:var(--muted);margin-bottom:1.2rem;line-height:1.6}
.drop-zone{border:2px dashed var(--border);border-radius:10px;padding:2rem;cursor:pointer;transition:all .2s;background:var(--bg2)}
.drop-zone:hover{border-color:var(--accent);background:#eff6ff}
.drop-zone .di{font-size:36px;margin-bottom:8px}
.drop-zone p{font-size:11px;color:var(--muted)}
.prog-bar{height:4px;background:var(--border);border-radius:2px;overflow:hidden;margin-top:.8rem;display:none}
.prog-fill{height:100%;background:var(--accent);border-radius:2px;transition:width .3s}
#loadMsg{font-size:11px;color:var(--muted);margin-top:6px;display:none}
</style>
</head>
<body>
<div class="overlay" id="ov">
<div class="ov-box">
<h2>πŸ“Š κ΅­λ―Όμ—°κΈˆ 사업μž₯ λŒ€μ‹œλ³΄λ“œ</h2>
<p>κ΅­λ―Όμ—°κΈˆκ³΅λ‹¨ κ°€μž… 사업μž₯ λ‚΄μ—­ μ—‘μ…€ νŒŒμΌμ„<br>μ—…λ‘œλ“œν•˜λ©΄ λ°”λ‘œ μ‹œμž‘λ©λ‹ˆλ‹€</p>
<div class="drop-zone" id="dropZone" onclick="document.getElementById('fi').click()">
<div class="di">πŸ“</div>
<p>ν΄λ¦­ν•˜κ±°λ‚˜ νŒŒμΌμ„ λ“œλž˜κ·Έν•˜μ„Έμš”<br><span style="font-size:10px">.xlsx / .xls</span></p>
</div>
<div class="prog-bar" id="pb"><div class="prog-fill" id="pf" style="width:0%"></div></div>
<p id="loadMsg"></p>
<input type="file" id="fi" accept=".xlsx,.xls" style="display:none">
</div>
</div>
<div class="topbar">
<h1>κ΅­λ―Όμ—°κΈˆ <span>κ°€μž… 사업μž₯ λ‚΄μ—­</span></h1>
<div class="topbar-right">
<div class="status-badge" id="sb">파일 λ‘œλ”© λŒ€κΈ° 쀑</div>
<label class="upload-label">πŸ“‚ 파일 ꡐ체<input type="file" accept=".xlsx,.xls" onchange="loadFile(this.files[0])" style="display:none"></label>
</div>
</div>
<div class="main">
<div class="sidebar">
<div class="sel-wrap">
<label>μ‹œ / 도</label>
<select class="sel-box" id="sidoSel" onchange="updateGu()"><option value="">선택</option></select>
</div>
<div class="sel-wrap">
<label>ꡬ / ꡰ</label>
<select class="sel-box" id="guSel" onchange="filterData()"><option value="">선택</option></select>
</div>
<div class="stats-row">
<div class="stat"><div class="n" id="s1">-</div><div class="l">전체</div></div>
<div class="stat"><div class="n" id="s2">-</div><div class="l">법인</div></div>
<div class="stat"><div class="n" id="s3">-</div><div class="l">총 직원</div></div>
<div class="stat"><div class="n" id="s4">-</div><div class="l">평균 직원</div></div>
</div>
<div class="search-wrap">
<input class="search" id="srch" placeholder="πŸ” 사업μž₯λͺ… 검색..." oninput="filterList()">
</div>
<div class="list-hdr">
<span>사업μž₯</span>
<span>μ§μ›μˆ˜</span>
</div>
<div class="list-wrap" id="lw">
<div class="empty"><div class="ei">πŸ—‚οΈ</div><p>μ‹œ/도와 ꡬ/ꡰ을 μ„ νƒν•˜μ„Έμš”</p></div>
</div>
</div>
<div class="content">
<div class="tab-bar">
<div class="tab active" onclick="showTab('map')">πŸ—ΊοΈ 지도</div>
<div class="tab" onclick="showTab('detail')">πŸ“‹ 상세</div>
<div class="tab-actions">
<button class="dl-btn" onclick="dlExcel()">⬇️ μ—‘μ…€ λ‹€μš΄λ‘œλ“œ</button>
</div>
</div>
<div class="panel active" id="panel-map"><div id="map"></div></div>
<div class="panel" id="panel-detail">
<div class="detail-scroll" id="dc">
<div class="empty"><div class="ei">πŸ‘†</div><p style="font-size:13px;font-weight:600">사업μž₯을 μ„ νƒν•˜μ„Έμš”</p><p style="font-size:11px;margin-top:4px;color:var(--muted)">λͺ©λ‘μ—μ„œ ν΄λ¦­ν•˜λ©΄ 상세 정보가 ν‘œμ‹œλ©λ‹ˆλ‹€</p></div>
</div>
</div>
</div>
</div>
<script>
const C={
'강남ꡬ':[37.5172,127.0473],'강동ꡬ':[37.5301,127.1238],'강뢁ꡬ':[37.6397,127.0259],'κ°•μ„œκ΅¬':[37.5509,126.8495],
'관악ꡬ':[37.4784,126.9516],'광진ꡬ':[37.5385,127.0822],'ꡬ둜ꡬ':[37.4954,126.8876],'금천ꡬ':[37.4602,126.9000],
'노원ꡬ':[37.6543,127.0568],'도봉ꡬ':[37.6688,127.0471],'λ™λŒ€λ¬Έκ΅¬':[37.5744,127.0396],'λ™μž‘κ΅¬':[37.5124,126.9393],
'마포ꡬ':[37.5638,126.9084],'μ„œλŒ€λ¬Έκ΅¬':[37.5791,126.9368],'μ„œμ΄ˆκ΅¬':[37.4837,127.0324],'성동ꡬ':[37.5633,127.0371],
'성뢁ꡬ':[37.5894,127.0167],'μ†‘νŒŒκ΅¬':[37.5145,127.1058],'μ–‘μ²œκ΅¬':[37.5170,126.8665],'μ˜λ“±ν¬κ΅¬':[37.5264,126.8962],
'μš©μ‚°κ΅¬':[37.5324,126.9904],'은평ꡬ':[37.6176,126.9227],'μ’…λ‘œκ΅¬':[37.5735,126.9789],'쀑ꡬ':[37.5579,126.9942],'μ€‘λž‘κ΅¬':[37.6063,127.0926],
'κΈˆμ •κ΅¬':[35.2428,129.0927],'κΈ°μž₯κ΅°':[35.2445,129.2220],'남ꡬ':[35.1367,129.0845],'동ꡬ':[35.1299,129.0453],
'λ™λž˜κ΅¬':[35.1993,129.0841],'뢀산진ꡬ':[35.1631,129.0531],'뢁ꡬ':[35.1977,128.9898],'사상ꡬ':[35.1524,128.9921],
'μ‚¬ν•˜κ΅¬':[35.1046,128.9742],'μ„œκ΅¬':[35.0978,129.0245],'수영ꡬ':[35.1454,129.1133],'μ—°μ œκ΅¬':[35.1764,129.0804],
'μ˜λ„κ΅¬':[35.0906,129.0671],'ν•΄μš΄λŒ€κ΅¬':[35.1631,129.1639],
'μˆ˜μ›μ‹œ':[37.2636,127.0286],'μ„±λ‚¨μ‹œ':[37.4201,127.1264],'κ³ μ–‘μ‹œ':[37.6584,126.8320],'μš©μΈμ‹œ':[37.2410,127.1775],
'λΆ€μ²œμ‹œ':[37.5035,126.7660],'μ•ˆμ‚°μ‹œ':[37.3219,126.8310],'μ•ˆμ–‘μ‹œ':[37.3943,126.9568],'λ‚¨μ–‘μ£Όμ‹œ':[37.6360,127.2165],
'ν™”μ„±μ‹œ':[37.1996,126.8312],'ν‰νƒμ‹œ':[36.9921,127.1128],'μ˜μ •λΆ€μ‹œ':[37.7382,127.0337],'μ‹œν₯μ‹œ':[37.3800,126.8030],
'νŒŒμ£Όμ‹œ':[37.7601,126.7799],'κ΄‘λͺ…μ‹œ':[37.4784,126.8642],'κΉ€ν¬μ‹œ':[37.6153,126.7156],'κ΄‘μ£Όμ‹œ':[37.4293,127.2558],
'κ΅°ν¬μ‹œ':[37.3615,126.9349],'ν•˜λ‚¨μ‹œ':[37.5394,127.2148],'μ˜€μ‚°μ‹œ':[37.1520,127.0776],'μ΄μ²œμ‹œ':[37.2722,127.4350],
'μ•ˆμ„±μ‹œ':[37.0081,127.2797],'μ˜μ™•μ‹œ':[37.3448,126.9686],'양평ꡰ':[37.4914,127.4875],'μ—¬μ£Όμ‹œ':[37.2984,127.6376],
'κ³Όμ²œμ‹œ':[37.4292,126.9876],'κ΅¬λ¦¬μ‹œ':[37.5943,127.1296],'ν¬μ²œμ‹œ':[37.8948,127.2002],'μ–‘μ£Όμ‹œ':[37.7852,127.0457],
'λ™λ‘μ²œμ‹œ':[37.9039,127.0606],'가평ꡰ':[37.8314,127.5101],'μ—°μ²œκ΅°':[38.0965,127.0747],
'뢄당ꡬ':[37.3845,127.1218],'μˆ˜μ •κ΅¬':[37.4389,127.1378],'쀑원ꡬ':[37.4418,127.1480],'덕양ꡬ':[37.6344,126.8350],
'일산동ꡬ':[37.6592,126.8218],'μΌμ‚°μ„œκ΅¬':[37.6740,126.7680],'κΈ°ν₯ꡬ':[37.2751,127.1152],'μˆ˜μ§€κ΅¬':[37.3220,127.0977],
'처인ꡬ':[37.2375,127.2018],'단원ꡬ':[37.3213,126.8187],'상둝ꡬ':[37.2983,126.8721],'λ§Œμ•ˆκ΅¬':[37.3862,126.9369],
'λ™μ•ˆκ΅¬':[37.3947,126.9492],'μ˜€μ •κ΅¬':[37.5032,126.7872],'원미ꡬ':[37.5087,126.7577],'μ†Œμ‚¬κ΅¬':[37.4830,126.8017],
'κΆŒμ„ κ΅¬':[37.2505,127.0040],'νŒ”λ‹¬κ΅¬':[37.2869,127.0173],'μ˜ν†΅κ΅¬':[37.2517,127.0509],'μž₯μ•ˆκ΅¬':[37.3050,127.0138],
'미좔홀ꡬ':[37.4561,126.7052],'μ—°μˆ˜κ΅¬':[37.4106,126.6780],'남동ꡬ':[37.4469,126.7310],
'뢀평ꡬ':[37.5074,126.7219],'계양ꡬ':[37.5379,126.7378],'κ°•ν™”κ΅°':[37.7480,126.4879],
'λ‹¬μ„œκ΅¬':[35.8285,128.5330],'달성ꡰ':[35.7747,128.4314],'μˆ˜μ„±κ΅¬':[35.8583,128.6310],
'λŒ€λ•κ΅¬':[36.3462,127.4151],'μœ μ„±κ΅¬':[36.3624,127.3563],'광산ꡬ':[35.1397,126.7936],'울주ꡰ':[35.5225,129.2440],
'의창ꡬ':[35.2271,128.6811],'성산ꡬ':[35.2217,128.6921],'λ§ˆμ‚°ν•©ν¬κ΅¬':[35.1827,128.5644],
'λ§ˆμ‚°νšŒμ›κ΅¬':[35.2184,128.5868],'진해ꡬ':[35.1541,128.6971],'κΉ€ν•΄μ‹œ':[35.2286,128.8893],
'μ–‘μ‚°μ‹œ':[35.3350,129.0370],'μ§„μ£Όμ‹œ':[35.1799,128.1076],'κ±°μ œμ‹œ':[34.8804,128.6213],
'ν†΅μ˜μ‹œ':[34.8544,128.4330],'λ°€μ–‘μ‹œ':[35.5039,128.7459],'μ‚¬μ²œμ‹œ':[35.0003,128.0645],
'ν•¨μ•ˆκ΅°':[35.2715,128.4063],'μ°½λ…•κ΅°':[35.5446,128.4922],'κ³ μ„±κ΅°':[34.9748,128.3224],
'남해ꡰ':[34.8374,127.8917],'ν•˜λ™κ΅°':[35.0671,127.7518],'μ‚°μ²­κ΅°':[35.4153,127.8737],
'함양ꡰ':[35.5203,127.7239],'κ±°μ°½κ΅°':[35.6878,127.9101],'ν•©μ²œκ΅°':[35.5664,128.1658],'의령ꡰ':[35.3219,128.2616],
'ν¬ν•­μ‹œ':[36.0190,129.3435],'κ²½μ£Όμ‹œ':[35.8562,129.2247],'κΉ€μ²œμ‹œ':[36.1397,128.1135],
'μ•ˆλ™μ‹œ':[36.5684,128.7294],'κ΅¬λ―Έμ‹œ':[36.1196,128.3441],'μ˜μ£Όμ‹œ':[36.8057,128.6236],
'μ˜μ²œμ‹œ':[35.9735,128.9383],'μƒμ£Όμ‹œ':[36.4110,128.1597],'λ¬Έκ²½μ‹œ':[36.5863,128.1861],
'κ²½μ‚°μ‹œ':[35.8249,128.7414],'달성ꡰ':[35.7747,128.4314],
'μ²œμ•ˆμ‹œ':[36.8151,127.1139],'κ³΅μ£Όμ‹œ':[36.4465,127.1190],'λ³΄λ Ήμ‹œ':[36.3334,126.6127],
'μ•„μ‚°μ‹œ':[36.7893,127.0021],'μ„œμ‚°μ‹œ':[36.7848,126.4503],'λ…Όμ‚°μ‹œ':[36.1872,127.0986],
'λ‹Ήμ§„μ‹œ':[36.8896,126.6454],'홍성ꡰ':[36.6010,126.6600],'μ˜ˆμ‚°κ΅°':[36.6817,126.8451],
'νƒœμ•ˆκ΅°':[36.7456,126.2978],'동남ꡬ':[36.7914,127.1619],'μ„œλΆκ΅¬':[36.8421,127.0961],
'μ²­μ£Όμ‹œ':[36.6424,127.4890],'μΆ©μ£Όμ‹œ':[36.9911,127.9261],'μ œμ²œμ‹œ':[37.1326,128.1909],
'상당ꡬ':[36.6575,127.5291],'μ„œμ›κ΅¬':[36.6166,127.4509],'ν₯덕ꡬ':[36.6405,127.4255],'청원ꡬ':[36.6527,127.5027],
'μ „μ£Όμ‹œ':[35.8242,127.1480],'κ΅°μ‚°μ‹œ':[35.9674,126.7368],'μ΅μ‚°μ‹œ':[35.9483,126.9579],
'μ •μμ‹œ':[35.5700,126.8562],'λ‚¨μ›μ‹œ':[35.4169,127.3899],'κΉ€μ œμ‹œ':[35.8032,126.8813],
'완산ꡬ':[35.8150,127.1127],'덕진ꡬ':[35.8566,127.1568],
'λͺ©ν¬μ‹œ':[34.8120,126.3922],'μ—¬μˆ˜μ‹œ':[34.7604,127.6622],'μˆœμ²œμ‹œ':[34.9506,127.4875],
'λ‚˜μ£Όμ‹œ':[35.0160,126.7108],'κ΄‘μ–‘μ‹œ':[34.9407,127.6963],
'μΆ˜μ²œμ‹œ':[37.8813,127.7298],'μ›μ£Όμ‹œ':[37.3422,127.9202],'κ°•λ¦‰μ‹œ':[37.7519,128.8760],
'λ™ν•΄μ‹œ':[37.5243,129.1140],'μ†μ΄ˆμ‹œ':[38.2071,128.5916],'μ‚Όμ²™μ‹œ':[37.4498,129.1658],
'μ œμ£Όμ‹œ':[33.4996,126.5312],'μ„œκ·€ν¬μ‹œ':[33.2541,126.5600],
};
let allData=[],filteredData=[],currentData=[],mapObj,mkrs=[];
// ── 지도 μ΄ˆκΈ°ν™” (OSM 라이트 타일) ──
mapObj=L.map('map',{zoomControl:true}).setView([36.5,127.8],7);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
attribution:'Β© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
maxZoom:19,
crossOrigin:true
}).addTo(mapObj);
function rand(n=0.012){return(Math.random()-.5)*n}
function updateMap(data){
mkrs.forEach(m=>mapObj.removeLayer(m));
mkrs=[];
if(!data.length)return;
const gu=document.getElementById('guSel').value;
const center=C[gu];
if(center){
mapObj.setView(center,13);
data.slice(0,600).forEach(r=>{
const lat=center[0]+rand();
const lng=center[1]+rand();
const emp=r.κ°€μž…μžμˆ˜||0;
const radius=Math.max(5,Math.min(22,emp/8));
const col=emp>=100?'#ef4444':emp>=30?'#f59e0b':emp>=10?'#3b82f6':'#10b981';
const m=L.circleMarker([lat,lng],{
radius,color:'#fff',weight:1.5,fillColor:col,fillOpacity:.8
}).bindPopup(`
<div style="font-family:'Pretendard',sans-serif;min-width:200px;padding:4px">
<b style="font-size:13px;color:#1e293b">${r.사업μž₯λͺ…}</b><br>
<span style="font-size:10px;color:#94a3b8">${r.μ—…μ’…||''}</span>
<hr style="border:none;border-top:1px solid #e2e8f0;margin:6px 0">
<div style="font-size:11px;color:#475569">πŸ‘₯ μ§μ›μˆ˜: <b style="color:#10b981">${emp.toLocaleString()}λͺ…</b></div>
<div style="font-size:10px;color:#94a3b8;margin-top:4px">${r.μ£Όμ†Œ||''}</div>
</div>
`,{maxWidth:250}).addTo(mapObj);
mkrs.push(m);
});
} else {
// μ’Œν‘œ μ—†λŠ” μ§€μ—­ μ•ˆλ‚΄
const el=document.createElement('div');
el.style.cssText='position:absolute;bottom:20px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,.7);color:#fff;padding:8px 16px;border-radius:20px;font-size:11px;z-index:500;pointer-events:none';
el.textContent=`'${gu}' 지도 μ’Œν‘œ 미등둝 β€” 리슀트/엑셀은 정상 μž‘λ™ν•©λ‹ˆλ‹€`;
document.getElementById('panel-map').style.position='relative';
document.getElementById('panel-map').appendChild(el);
setTimeout(()=>el.remove(),4000);
}
}
function getSido(a){
for(const s of['μ„œμšΈνŠΉλ³„μ‹œ','λΆ€μ‚°κ΄‘μ—­μ‹œ','λŒ€κ΅¬κ΄‘μ—­μ‹œ','μΈμ²œκ΄‘μ—­μ‹œ','κ΄‘μ£Όκ΄‘μ—­μ‹œ','λŒ€μ „κ΄‘μ—­μ‹œ','μšΈμ‚°κ΄‘μ—­μ‹œ','μ„Έμ’…νŠΉλ³„μžμΉ˜μ‹œ','경기도','강원도','좩청뢁도','좩청남도','전라뢁도','전라남도','경상뢁도','경상남도','μ œμ£ΌνŠΉλ³„μžμΉ˜λ„'])
if(a.includes(s))return s;
return '기타';
}
function getGu(a){
const p=a.split(' ');
for(const x of p)if((x.endsWith('ꡬ')||x.endsWith('ꡰ'))&&x.length>2)return x;
for(const x of p)if(x.endsWith('μ‹œ')&&x.length>2&&!['νŠΉλ³„μ‹œ','κ΄‘μ—­μ‹œ','μžμΉ˜μ‹œ'].some(s=>x.includes(s)))return x;
return '기타';
}
function loadFile(file){
if(!file)return;
document.getElementById('ov').style.display='flex';
document.getElementById('pb').style.display='block';
document.getElementById('loadMsg').style.display='block';
document.getElementById('loadMsg').textContent='파일 μ½λŠ” 쀑...';
const reader=new FileReader();
reader.onload=e=>{
document.getElementById('pf').style.width='30%';
document.getElementById('loadMsg').textContent='데이터 νŒŒμ‹± 쀑...';
setTimeout(()=>{
try{
const wb=XLSX.read(e.target.result,{type:'array'});
const raw=XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
document.getElementById('pf').style.width='70%';
document.getElementById('loadMsg').textContent=`${raw.length.toLocaleString()}건 처리 쀑...`;
setTimeout(()=>{
allData=raw.map(r=>({
사업μž₯λͺ…:String(r['사업μž₯λͺ…']||''),
μ—…μ’…:String(r['사업μž₯μ—…μ’…μ½”λ“œλͺ…']||r['μ—…μ’…']||''),
μ£Όμ†Œ:String(r['사업μž₯λ„λ‘œλͺ…μƒμ„Έμ£Όμ†Œ']||''),
κ°€μž…μžμˆ˜:parseInt(r['κ°€μž…μžμˆ˜'])||0,
λ‹Ήμ›”κ³ μ§€κΈˆμ•‘:parseInt(r['λ‹Ήμ›”κ³ μ§€κΈˆμ•‘'])||0,
법인여뢀:(r['사업μž₯ν˜•νƒœκ΅¬λΆ„μ½”λ“œ 1 법인 2 개인']==1||r['사업μž₯ν˜•νƒœκ΅¬λΆ„μ½”λ“œ']==1)?'법인':'개인',
μ‹œλ„:getSido(String(r['사업μž₯λ„λ‘œλͺ…μƒμ„Έμ£Όμ†Œ']||'')),
ꡬꡰ:getGu(String(r['사업μž₯λ„λ‘œλͺ…μƒμ„Έμ£Όμ†Œ']||'')),
}));
buildSido();
document.getElementById('pf').style.width='100%';
document.getElementById('sb').textContent=`총 ${allData.length.toLocaleString()}개 λ‘œλ“œ`;
document.getElementById('loadMsg').textContent='μ™„λ£Œ!';
setTimeout(()=>document.getElementById('ov').style.display='none',500);
},100);
}catch(err){document.getElementById('loadMsg').textContent='였λ₯˜: '+err.message;}
},100);
};
reader.readAsArrayBuffer(file);
}
function buildSido(){
const s=document.getElementById('sidoSel');
const sidos=[...new Set(allData.map(r=>r.μ‹œλ„))].filter(x=>x!=='기타').sort();
s.innerHTML='<option value="">선택</option>';
sidos.forEach(x=>{const o=document.createElement('option');o.value=o.textContent=x;s.appendChild(o);});
}
function updateGu(){
const sido=document.getElementById('sidoSel').value;
const g=document.getElementById('guSel');
g.innerHTML='<option value="">선택</option>';
if(!sido)return;
const gus=[...new Set(allData.filter(r=>r.μ‹œλ„===sido).map(r=>r.ꡬꡰ))].filter(x=>x!=='기타').sort();
gus.forEach(x=>{const o=document.createElement('option');o.value=o.textContent=x;g.appendChild(o);});
filterData();
}
function filterData(){
const sido=document.getElementById('sidoSel').value;
const gu=document.getElementById('guSel').value;
if(!sido||!gu){currentData=[];renderList([]);updateStats([]);return;}
currentData=allData.filter(r=>r.μ‹œλ„===sido&&r.ꡬꡰ===gu).sort((a,b)=>b.κ°€μž…μžμˆ˜-a.κ°€μž…μžμˆ˜);
filteredData=[...currentData];
document.getElementById('srch').value='';
renderList(filteredData);
updateStats(filteredData);
updateMap(filteredData);
}
function filterList(){
const q=document.getElementById('srch').value.toLowerCase();
filteredData=q?currentData.filter(r=>r.사업μž₯λͺ….toLowerCase().includes(q)||r.μ—…μ’….toLowerCase().includes(q)):[...currentData];
renderList(filteredData);
updateMap(filteredData);
}
function updateStats(d){
const t=d.length,corp=d.filter(r=>r.법인여뢀==='법인').length;
const emp=d.reduce((s,r)=>s+r.κ°€μž…μžμˆ˜,0);
document.getElementById('s1').textContent=t.toLocaleString();
document.getElementById('s2').textContent=corp.toLocaleString();
document.getElementById('s3').textContent=emp.toLocaleString();
document.getElementById('s4').textContent=t?Math.round(emp/t):0;
}
function renderList(data){
const w=document.getElementById('lw');
if(!data.length){w.innerHTML='<div class="empty"><div class="ei">πŸ”</div><p>κ²°κ³Ό μ—†μŒ</p></div>';return;}
w.innerHTML=data.slice(0,300).map((r,i)=>`
<div class="list-item" onclick="selectItem(${i})" id="li-${i}">
<div class="item-left">
<div class="item-name">${r.사업μž₯λͺ…}</div>
<div class="item-sub"><span class="pill ${r.법인여뢀==='법인'?'pill-c':'pill-i'}">${r.법인여뢀}</span>${r.μ—…μ’….slice(0,18)}</div>
</div>
<div class="item-emp">${(r.κ°€μž…μžμˆ˜||0).toLocaleString()}<span>λͺ…</span></div>
</div>
`).join('')+(data.length>300?`<div style="padding:10px;text-align:center;font-size:11px;color:var(--muted)">μƒμœ„ 300개 ν‘œμ‹œ (전체 ${data.length.toLocaleString()}개)</div>`:'');
}
function selectItem(i){
const r=filteredData[i];
document.querySelectorAll('.list-item').forEach(el=>el.classList.remove('active'));
document.getElementById('li-'+i)?.classList.add('active');
const mi=Math.round(r.λ‹Ήμ›”κ³ μ§€κΈˆμ•‘/0.09);
const mr=Math.round(mi/12);
document.getElementById('dc').innerHTML=`
<div class="d-card">
<h3>κΈ°λ³Έ 정보</h3>
<div class="d-row"><div class="d-key">사업μž₯λͺ…</div><div class="d-val" style="font-size:14px;font-weight:700">${r.사업μž₯λͺ…}</div></div>
<div class="d-row"><div class="d-key">법인 ꡬ뢄</div><div class="d-val"><span class="pill ${r.법인여뢀==='법인'?'pill-c':'pill-i'}" style="font-size:11px;padding:3px 8px">${r.법인여뢀}</span></div></div>
<div class="d-row"><div class="d-key">μ—…μ’…</div><div class="d-val">${r.μ—…μ’…||'-'}</div></div>
<div class="d-row"><div class="d-key">μ£Όμ†Œ</div><div class="d-val">${r.μ£Όμ†Œ||'-'}</div></div>
</div>
<div class="d-card">
<h3>규λͺ¨ μ§€ν‘œ</h3>
<div class="d-row"><div class="d-key">μ§μ›μˆ˜ (κ°€μž…μžμˆ˜)</div><div class="d-val" style="font-size:20px;font-weight:700;color:var(--green)">${r.κ°€μž…μžμˆ˜.toLocaleString()}λͺ…</div></div>
<div class="d-row"><div class="d-key">λ‹Ήμ›”κ³ μ§€κΈˆμ•‘</div><div class="d-val" style="color:var(--yellow);font-weight:600">${r.λ‹Ήμ›”κ³ μ§€κΈˆμ•‘.toLocaleString()}원</div></div>
</div>
<div class="d-card">
<h3>πŸ’‘ ν‡΄μ§μ—°κΈˆ 규λͺ¨ μΆ”μ •</h3>
<div class="formula">μ›” μž„κΈˆμ΄μ•‘ = ${r.λ‹Ήμ›”κ³ μ§€κΈˆμ•‘.toLocaleString()} Γ· 0.09
= ${mi.toLocaleString()}원
μ›” ν‡΄μ§κΈˆ = ${mi.toLocaleString()} Γ· 12
= ${mr.toLocaleString()}원
μ—° ν‡΄μ§κΈˆ β‰ˆ ${(mr*12).toLocaleString()}원</div>
<div class="highlight-box"><div class="hl">μ˜ˆμƒ μ—°κ°„ 적립 규λͺ¨</div><div class="hv">μ•½ ${Math.round(mr*12/100000000*10)/10}얡원 / λ…„</div></div>
</div>`;
showTab('detail');
}
function showTab(id){
document.querySelectorAll('.tab').forEach((t,i)=>t.classList.toggle('active',['map','detail'][i]===id));
document.querySelectorAll('.panel').forEach(p=>p.classList.remove('active'));
document.getElementById('panel-'+id).classList.add('active');
if(id==='map')setTimeout(()=>mapObj.invalidateSize(),100);
}
// ── μ—‘μ…€ λ‹€μš΄λ‘œλ“œ (사업μž₯λ„λ‘œλͺ…μƒμ„Έμ£Όμ†Œ 포함) ──
function dlExcel(){
const d=filteredData.length?filteredData:currentData;
if(!d.length){alert('지역을 μ„ νƒν•΄μ£Όμ„Έμš”');return;}
const sido=document.getElementById('sidoSel').value;
const gu=document.getElementById('guSel').value;
const ws=XLSX.utils.json_to_sheet(d.map(r=>({
사업μž₯λͺ…: r.사업μž₯λͺ…,
μ—…μ’…: r.μ—…μ’…,
법인여뢀: r.법인여뢀,
사업μž₯λ„λ‘œλͺ…μƒμ„Έμ£Όμ†Œ: r.μ£Όμ†Œ,
κ°€μž…μžμˆ˜: r.κ°€μž…μžμˆ˜,
λ‹Ήμ›”κ³ μ§€κΈˆμ•‘: r.λ‹Ήμ›”κ³ μ§€κΈˆμ•‘,
μ›”μž„κΈˆμ΄μ•‘μΆ”μ •: Math.round(r.λ‹Ήμ›”κ³ μ§€κΈˆμ•‘/0.09),
μ›”ν‡΄μ§κΈˆμ λ¦½μ•‘μΆ”μ •: Math.round(r.λ‹Ήμ›”κ³ μ§€κΈˆμ•‘/0.09/12),
})));
const wb=XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb,ws,gu||'λͺ©λ‘');
XLSX.writeFile(wb,`κ΅­λ―Όμ—°κΈˆ_${sido}_${gu}_사업μž₯.xlsx`);
}
const dz=document.getElementById('dropZone');
dz.addEventListener('dragover',e=>{e.preventDefault();dz.style.borderColor='var(--accent)'});
dz.addEventListener('dragleave',()=>{dz.style.borderColor='var(--border)'});
dz.addEventListener('drop',e=>{e.preventDefault();loadFile(e.dataTransfer.files[0])});
document.getElementById('fi').addEventListener('change',e=>loadFile(e.target.files[0]));
</script>
</body>
</html>