1// DOM ë¡ë í ì¤í 2document.addEventListener("DOMContentLoaded", async () => { 3 let container = document.querySelector(".app-header"); 4 5 document.addEventListener("click", (e) => { 6 let targetEl = e.target; 7 8 if (targetEl.closest(".move-shop-add")) { 9 e.preventDefault(); 10 e.stopImmediatePropagation(); 11 12 moveToShopAdd(targetEl); 13 } 14 15 if (targetEl.closest('.auto-reply-setting')) { 16 e.preventDefault(); 17 showAutoReplyConfirmModal(targetEl.closest('.auto-reply-setting')); 18 } 19 }); 20 21 // ìë ëê¸ ON/OFF ìíë sendHttpGetReviewSummary()ìì ìì½ê³¼ í¨ê» ë ë (placeholder ëì ì ê±°) 22 // ë¨ì ë°ë¡ëê¸ ê°ì íë¡ê·¸ë ì¤ë° (í ì¹´ë) 23 renderHomeRemainCount(); 24 25 // eìºì ì¹´ë ì´ê¸°í (ì¹´ë 미ì¶ë ¥(ê¸°ë¥ OFF) ì ë´ë¶ìì ì¦ì ì¢ ë£) 26 initEcashCard(); 27 28 waitForI18nInitialization(() => { 29 if (container?.dataset?.resultCode == "W2016") { 30 let modal = new DynamicModal( 31 i18n.t('shop.noItemTitle'), // 'ì¬ì ì¥ì ë³ê²½í´ 주ì¸ì' 32 i18n.t('shop.noItemMessage') // 'ì¬ì ì¥ì´ ë±ë¡ëì´ìì§ ìê±°ë ìì ëììµëë¤.' 33 ); 34 35 modal.setModalSize(MODAL_SIZE_SM); 36 modal.removeCancelButton(); 37 modal.setOptions({allowClose: false}); 38 modal.setConfirmButton(i18n.t('confirm'), () => { 39 let shopListModal = document.querySelector("#shopListModal"); 40 41 modal.hide(); 42 if (shopListModal) { 43 let modalInstance = bootstrap.Modal.getOrCreateInstance(shopListModal); 44 45 // 무조건 ë³ê²½í´ì¼íë¯ë¡ ë«ê¸° ë²í¼ ì ê±° 46 shopListModal.querySelector('.btn-close')?.remove(); 47 modalInstance.show(); 48 } 49 }); 50 51 modal.show(); 52 } 53 54 if (document.querySelector('.has-subscribe')) { 55 sendHttpGetReviewSummary(); 56 } 57 }); 58 59 // ê°ê² ë³ê²½ ëª¨ë¬ ëì¸ë ì´ë²¤í¸ (ì í ì¤í¨ ì ëª©ë¡ ê°±ì ì©ì¼ë¡ ì¬í¸ì¶ ê°ë¥) 60 async function loadShopListModal(e) { 61 let modal = e.target; 62 63 if (document.querySelector('#homeContainer')) { 64 modal.querySelector('.btn-home').classList.add('d-none'); 65 } 66 67 let sendHttp = new SendHttp('/shop/listJson'); 68 sendHttp.data = { allItem: '1' }; 69 sendHttp.isStoreData = true; 70 sendHttp.storeExpireMs = 1000 * 60; // ì¬ì ì¥ ëª©ë¡ì ìí/ì ë³´ê° ìë²ì¸¡ìì ë°ë ì ìì´ 60ì´ë¡ ì§§ê² 71 72 JsLoadingOverlay.setOptions(commonLoaderOpitons); 73 JsLoadingOverlay.show({ containerID: 'shopListModalBody', overlayOpacity: 0.0, lockScroll: false }); // 모ë¬ìì ëì¸ëë lockScrollì falseë¡ ì¤ì 74 let resultMap = await sendHttp.send(); 75 JsLoadingOverlay.hide(); 76 77 // ìë¬ ì²ë¦¬ 78 if (resultMap.code != RESULT_CODE_SUCCESS) { 79 errorHandling(resultMap.code, resultMap.message); 80 return; 81 } 82 83 let shopList = resultMap.list; 84 if (shopList == null || shopList.length == 0) { 85 // ê²°ê³¼ê° ìì ê²½ì° (ì ìì ì¼ë¡ë ë°ìíì§ ìì) 86 modal.querySelector('.modal-body').innerHTML = `<p class="text-center text-gray-600 mb-0 mt-3">${i18n.t('noList', {item: i18n.withJosa('shop.label', 'ê°')})}</p>`; 87 return; 88 } 89 90 // ëª©ë¡ ìì± 91 let shopListEl = document.createElement('div'); 92 shopListEl.classList.add('list-group', 'list-group-flush', 'my-n3', 'pb-3'); 93 94 let guideText = '<div class="text-gray-600 mb-6">ì¬ì ì¥ì ìµë 5ê°ì ì¬ì ì¥ë§ ë±ë¡í ì ìì´ì.</div>'; 95 shopListEl.innerHTML = guideText; 96 97 let currentShopId = container.dataset.shopId; 98 shopList.forEach((shop) => { 99 let shopEl = document.createElement('div'); 100 shopEl.classList.add('list-group-item', 'list-group-item-action', 'w-auto', 'd-flex', 'justify-content-between', 'align-items-center', 'rounded-4', "border-0", "mx-n3", "cursor-pointer"); 101 shopEl.dataset.shopId = shop.id; 102 103 shopEl.innerHTML = ` 104 <div class="form-check form-check-custom form-check-sm form-check-solid d-flex align-items-start py-2 flex-grow-1 min-w-0"> 105 <input class="form-check-input mt-1" type="radio" name="shopI
105d" value="${shop.id}" id="shopId${shop.id}" ${shop.id == currentShopId ? "checked" : ""} /> 106 <label class="d-flex flex-column ms-3 min-w-0" for="shopId${shop.id}"> 107 <div class="d-flex align-items-center"> 108 <span class="fs-4 fw-bold">${shop.businessNumber}</span> 109 <shop-status contract-status="${shop.contractStatus ?? 0}" amend-complete="${shop?.isCollectionAmendChanged ?? ''}" prepay-status="${shop.prepayStatus ?? 0}" terminate-status="${shop.terminateStatus}" size="sm"></shop-status> 110 </div> 111 <div class="text-gray-600 text-truncate">${shop.name}</div> 112 </label> 113 </div> 114 `; 115 116 shopEl.addEventListener('click', async (e) => { 117 e.preventDefault(); 118 e.stopImmediatePropagation(); 119 120 let shopId = e.currentTarget.dataset.shopId; 121 // if (shopId == currentShopId) { 122 // return; 123 // } 124 125 let sendHttp = new SendHttp('/shop/changeCurrentShop'); 126 sendHttp.method = 'POST'; 127 sendHttp.isNoDelay = true; 128 sendHttp.data = new FormData(); 129 sendHttp.data.append('id', shopId); 130 131 JsLoadingOverlay.show(); 132 let resultMap = await sendHttp.send(); 133 JsLoadingOverlay.hide(); 134 135 if (resultMap.code != RESULT_CODE_SUCCESS) { 136 if (resultMap.code == RESULT_CODE_NO_ITEM) { 137 toastr.error(i18n.t('shop.deleted')); 138 shopEl.remove(); 139 removeSessionItem('/shop/listJson'); 140 return; 141 } else { 142 errorHandling(resultMap.code, resultMap.message); 143 144 // ì¤í¨ ìì¸ì´ ëª©ë¡ ìí ë³íì¼ ì ìì¼ë ìºì ë¹ì°ê³ ëª©ë¡ ê°±ì 145 removeSessionItem('/shop/listJson'); 146 loadShopListModal({ target: modal }); 147 148 return; // ì í ì¤í¨ ì 리ë¡ë/ë¸ë¡ëìºì¤í¸ ë°©ì§ 149 } 150 } 151 152 // ì¬ì ì¥ ë³ê²½ í ë¤ë¡ê°ê¸° ì ì íë ì¬ì ì¥ì´ ì ì©ì´ ìë기ë문ì ë¤ë¡ê°ê¸° í ìë¡ê³ 침 íëê·¸ ì¤ì 153 sessionStorage.setItem('refresh-on-back', true); 154 broadcastShopChange(shopId); 155 location.reload(); 156 }); 157 158 shopListEl.appendChild(shopEl); 159 }); 160 161 modal.querySelector('.modal-body').innerHTML = ''; 162 modal.querySelector('.modal-body').append(shopListEl); 163 164 // ì¬ì ì¥ ê°ì를 ì¬ì ì¥ ì¶ê° ë²í¼ì ì¤ì 165 modal.querySelector('.move-shop-add').dataset.shopCount = shopList.length; 166 } 167 168 document.querySelector('#shopListModal')?.addEventListener('show.bs.modal', loadShopListModal); 169 170 async function sendHttpGetReviewSummary() { 171 let sendHttp = new SendHttp('review/summary'); 172 173 let resultMap = await sendHttp.send(); 174 if (resultMap.code == RESULT_CODE_SUCCESS) { 175 let data = resultMap.data; 176 177 // ìë ëê¸ ON/OFF ìí â ìì½ê³¼ ê°ì ìëµì¼ë¡ ë ë (placeholder ëì ì ê±°, í/리뷰 ê³µì©) 178 // useAutoReply: Y=ì¼ì§, N=꺼ì§. 미ìëµ/íë ìì ì 기존 dataset('Y' 기본) ì ì§ 179 let autoReplyRow = document.querySelector('.auto-reply-setting'); 180 if (autoReplyRow && data.summary != null) { 181 if (data.summary.useAutoReply) { 182 autoReplyRow.dataset.pause = data.summary.useAutoReply; 183 } 184 autoReplyRow.querySelector('.auto-reply-title').innerText = i18n.t('baroReply.autoReply.title'); 185 renderAutoReplyLabel(autoReplyRow, autoReplyRow.dataset.pause !== 'N'); 186 } 187 188 if (data.summary != null && document.querySelector('#homeContainer')) { 189 document.querySelector('#reviewSummarySection .total-avg-score .item-content').innerHTML = ` 190 <div class="price-lg d-flex align-items-center"> 191 ${ratingsIcon(data.summary.avgScore, 'icon-sm')}
192 <span class="number ms-2 mt-1">${data.summary.avgScore.toFixed(1)}</span> 193 </div> 194 `; 195 document.querySelector('#reviewSummarySection .total-count .item-content').innerHTML = ` 196 <div class="price-lg d-flex align-items-center"> 197 <span class="number">${numberWithCommas(data.summary.totalCount)}</span> 198 <span class="unit">ê±´</span> 199 </div> 200 `; 201 document.querySelector('#reviewSummarySection .total-response-count .item-content').innerHTML = ` 202 <div class="price-lg d-flex align-items-center"> 203 <span class="number">${numberWithCommas(data.summary.replyCount)}</span> 204 <span class="unit">ê±´</span> 205 </div> 206 `; 207 document.querySelector('#reviewSummarySection .total-no-response-count .item-content').innerHTML = ` 208 <div class="price-lg d-flex align-items-center"> 209 <span class="number">${numberWithCommas(data.summary.noReplyCount)}</span> 210 <span class="unit">ê±´</span> 211 </div> 212 `; 213 214 } 215 } 216 } 217 218 // === ìë ëê¸ ON/OFF (í/리뷰 ê³µì©) === 219 // ìë ëê¸ ìí ë¼ë²¨ ë ë (ì¼ì§: success, 꺼ì§: gray) 220 // fs-4ë í ì¤í¸ìë§ ë¶ì¬ â placeholderë ë¤ë¥¸ í목과 ëì¼í 기본 í°í¸ ëì´ ì ì§ 221 function renderAutoReplyLabel(rowEl, isOn) { 222 let labelEl = rowEl.querySelector('.auto-reply-status'); 223 let text = isOn ? i18n.t('baroReply.autoReply.statusOn') : i18n.t('baroReply.autoReply.statusOff'); 224 labelEl.innerHTML = `<span class="fs-4 fw-semibold ${isOn ? 'text-success' : 'text-gray-600'}">${text}</span>`; 225 } 226 227 // ìë ëê¸ ìí placeholder ë ë (ì¡°í/ë³ê²½ ì¤ â HTML ì´ê¸° ë§í¬ì ê³¼ ëì¼) 228 function renderAutoReplyPlaceholder(rowEl) { 229 rowEl.querySelector('.auto-reply-status').innerHTML = '<span class="placeholder min-w-125px rounded-2 bg-gray-600"></span>'; 230 } 231 232 // ìë ë±ë¡ íì¸ ëª¨ë¬ â íì¬ ìíì ë°ë ëì 1ê° + ì·¨ì (ìí ì¸ì§í) 233 function showAutoReplyConfirmModal(rowEl) { 234 // íì¬ êº¼ì§('N')ì´ë©´ íì±í, ì¼ì§ì´ë©´ ë¹íì±í 235 let targetOn = (rowEl.dataset.pause === 'N'); 236 let prefix = targetOn ? 'baroReply.autoReply.activate' : 'baroReply.autoReply.deactivate'; 237 238 let modal = new DynamicModal(i18n.t(prefix + 'Title')); 239 modal.setOptions({ customClass: 'modal-sm' }); 240 modal.setContents(`<p class="mb-0">${i18n.t(prefix + 'Desc')}</p>`); 241 modal.setConfirmButton(i18n.t(prefix + 'Confirm'), () => applyAutoReplyStatus(modal, rowEl, targetOn)); 242 modal.setCancelButton(i18n.t('cancel')); 243 modal.show(); 244 } 245 246 // ìë ëê¸ ON/OFF ì ì© (ì¬ì©ì í¬í¸ â ì¸ì ê°ë§¹ì 기ì¤, id ë¶íì) 247 async function applyAutoReplyStatus(modal, rowEl, on) { 248 // ëª¨ë¬ ë«ê³ ë³ê²½ ì¤ placeholder íì (ë°ëë ëë ë¶ì¬, send 기본 ëë ì´ ëì ë ¸ì¶) 249 modal.btModal.hide(); 250 let prevOn = (rowEl.dataset.pause !== 'N'); // ì¤í¨ ì ë³µìì© 251 renderAutoReplyPlaceholder(rowEl); 252 253 let sendHttp = new SendHttp('shop/updateUseAutoReply'); 254 sendHttp.method = 'POST'; 255 sendHttp.data = new FormData(); 256 sendHttp.data.append('useAutoReply', on ? 'Y' : 'N'); 257 258 let resultMap = await sendHttp.send(); 259 if (resultMap?.code != RESULT_CODE_SUCCESS) { 260 renderAutoReplyLabel(rowEl, prevOn); // ìë ìíë¡ ë³µì 261 return; 262 } 263 264 rowEl.dataset.pause = on ? 'Y' : 'N'; 265 renderAutoReplyLabel(rowEl, on); 266 infoAlert.show('success', on ? i18n.t('baroReply.autoReply.on') : i18n.t('baroReply.autoReply.off')); 267 } 268 269 // === ë¨ì ë°ë¡ëê¸ ê°ì íë¡ê·¸ë ì¤ë° (í ì¹´ë ì ì©) === 270 async function renderHomeRemainCount() { 271 let contentEl = document.querySelector('.total-remain-count .item-content'); 272 if (!contentEl) { 273 return; 274 } 275 276 let resultMap = await new SendHttp('review/getSubscribe').send(); 277 if (resultMap?.code != RESULT_CODE_SUCCESS) { 278 return; 279 } 280 281 let data = resultMap.data; 282 let limitCount = data?.limitCount || 0; 283 let useCount = data?.useCount || 0; 284 let isUnlimited = data?.paymentVO?.productSubType == 'unlimit'; 285 286 // 구ë ì: ë¼ë²¨ì 구ë ì¤ì¸ 구ë ëª (ìíëª )ì¼ë¡, ê·¸ ì¸ì 기본 ë¼ë²¨ë¡ íì (ë¡ë© íë ì´ì¤íë ì ê±°) 287 let productName = data?.paymentVO?.type == 'smallbiz' ? 'ììê³µì¸ì§ì' : data?.paymentVO?.productName; 288 let nameEl = document.querySelector('.total-remain-count .remain-count-name'); 289 if (nameEl) { 290 nameEl.innerText = productName || 'ë¨ì ë°ë¡ëê¸'; 291 } 292 293 contentEl.innerHTML = buildHomeRemainCount(Math.max(0, limitCount - useCount), limitCount, isUnlimited); 294 } 295 296 // ë¨ì ê°ì íë¡ê·¸ë ì¤ë° HTML (review.js buildRemainCountì í ì¹´ëì© ê°ì ë²ì ) 297 function buildHomeRemainCount(count, limit, isUnlimited) { 298 if (isUnlimited) { 299 return ` 300 <div class="d-flex align-items-center gap-2"> 301 <div class="progress rounded h-10px w-100px bg-secondary"><div class="progress-bar bg-success" style="width: 100%;"></div></div> 302 <div class="fw-semibold fs-base">무ì í</div> 303 </div> 304 `; 305 } 306 307 let percent = limit > 0 ? Math.min((count / limit) * 100, 100) : 0; 308 let barColor = 'bg-success'; 309 if (percent <= 10) { 310 barColor = 'bg-danger'; 311 } else if (percent <= 50) { 312 barColor = 'bg-warning'; 313 } 314 315 return ` 316 <div class="d-flex align-items-center gap-2"> 317 <div class="progress rounded h-10px w-100px bg-secondary"><div class="progress-bar ${barColor}" style="width: ${percent}%;"></div></div> 318 <div class="price-lg d-flex align-items-center">
319 <span class="number">${numberWithCommas(count)}</span> 320 <span class="unit">ê±´</span> 321 </div> 322 </div> 323 `; 324 } 325 326}); 327 328// ì¬ì ì¥ ì¶ê° ë²í¼ í´ë¦ ì ì²ë¦¬ 329function moveToShopAdd(targetEl) { 330 let maxCount = 5; 331 332 let currentCount = targetEl.dataset.shopCount ?? 0; 333 if (currentCount >= maxCount) { 334 infoAlert.show('danger', i18n.t('shop.overMaxCount', { count: maxCount })); 335 return; 336 } 337 338 location.href = '/shop/add'; 339} 340// eìºì ì¹´ë ì´ê¸°í 341async function initEcashCard() { 342 let ecashCard = document.querySelector('#ecashCard'); 343 if (!ecashCard) return; 344 345 let ecashStatus = ecashCard.dataset.ecashStatus; 346 let confirmModal = new bootstrap.Modal(document.querySelector('#ecashRequestConfirmModal')); 347 348 // ìí íë³: active / locked / prepayStop / pending / rejected / inactive 349 let state = 'inactive'; 350 let rejectReason = ''; 351 352 let accountFlags = { gas: false, water: false }; 353 if (ecashStatus == '1') { 354 // íì±í ìí: ê¸ì¡ ìì½ ë ¸ì¶ + ìì¡/ë´ì ë¡ë (ë©ë¶ ë²í¼ì íì ë ¸ì¶ëì´ ìì) 355 state = 'active';
356 ecashCard.querySelector('.ecash-amount').classList.remove('d-none'); 357 ecashCard.querySelector('.ecash-badge-on').classList.remove('d-none'); 358 ecashCard.querySelector('.ecash-more').classList.remove('d-none'); 359 await loadEcashInfo(); 360 await loadEcashPaymentList(); 361 // ì ì¥ë ì¡°í ì ë³´ ì¬ë¶ (ê°ì¤/ìë) 362 accountFlags = await loadAccountRegistration(); 363 } else if (ecashStatus == '9') { 364 state = 'locked'; 365 } else if (ecashStatus == '2') { 366 // ì ì ì° ì¤ì§ì ìí ì´ì© ì í (ì ì ì° ì¬ê° ì ê´ë¦¬ìê° ë³µì) 367 state = 'prepayStop'; 368 } else { 369 // ë¹íì±: ì ì² ìë¸ìí íì¸ (미ì ì²/ë기/ê±°ì ) 370 let req = await getEcashRequestState(); 371 state = req.state; 372 rejectReason = req.rejectReason || ''; 373 } 374 375 // ë기/ê±°ì /ì ê¸/ì ì ì° ì¤ì§ ìí: í¤ë ë°°ì§ + ì¤ëª 문 376 if (state === 'pending') { 377 showEcashNotice('badge-light-brand-1', 'ì¬ì¬ì¤', 'ì ì² í ì¹ì¸ê¹ì§ ìµë 1~2ì¼ì´ ììë¼ì.'); 378 } else if (state === 'rejected') { 379 showEcashNotice('badge-light-danger', 'ê±°ì ', 'ìì½ì§ë§ ì ì²ì´ ê±°ì ëìì´ì.' + (rejectReason ? ' ì¬ì : ' + escapeHtmlText(rejectReason) : '')); 380 } else if (state === 'locked') { 381 showEcashNotice('badge-light-danger', 'ì´ì©ì í', 'ê´ë¦¬ìì ìí´ eìºì ì´ì©ì´ ì íëììµëë¤.'); 382 } else if (state === 'prepayStop') { 383 showEcashNotice('badge-light-danger', 'ì´ì©ì í', i18n.t('ecash.prepayStopNotice')); 384 } else if (state === 'inactive') { 385 showEcashNotice('badge-secondary', '미ì´ì©', null); 386 let badgeStatus = ecashCard.querySelector('.ecash-badge-status'); 387 if (badgeStatus) { 388 badgeStatus.classList.add('cursor-pointer'); 389 badgeStatus.addEventListener('click', () => { 390 openEcashRequestFlow(); 391 }); 392 } 393 } 394 395 // ë©ë¶ ë²í¼ ëì: ìíë³ ë¶ê¸° 396 ecashCard.querySelectorAll('.ecash-pay-btn').forEach(btn => { 397 btn.addEventListener('click', (e) => { 398 if (state === 'active') { 399 // ì ì¥ë ì¡°í ì ë³´ê° ìì¼ë©´ 모ë¬ìì ë°ë¡ ë©ë¶, ë¹ìê¸ ì ë³´ ìì¼ë©´ ìë´ 400 let payType = btn.dataset.payType || ''; 401 if ((payType === 'gas' || payType === 'water') && accountFlags[payType]) { 402 // ì ì¥ë ì ë³´ ìì â 모ë¬ìì ë°ë¡ ë©ë¶ (ëª¨ë¬ ììì ìë ì´ëì²ë¡ ë¹ ì ¸ëê° ì ìê² href ì ë¬) 403 e.preventDefault(); 404 showEcashPaymentModal(payType, btn.getAttribute('href')); 405 } else if (payType === 'emergency' && !accountFlags.gas && !accountFlags.water) { 406 // ë¹ìê¸ ì ë³´ ìì â ìë´ ëª¨ë¬ 407 e.preventDefault(); 408 showEmergencyGuideModal(); 409 } else if (btn.getAttribute('href') === '#') { 410 // ì¤ì ë©ë¶ íë©´ ì´ë (미구í í목ì ìë´) 411 e.preventDefault(); 412 toastr.info('ì¤ë¹ ì¤ì¸ ìë¹ì¤ì ëë¤.'); 413 } 414 // ê·¸ ì¸: ë§í¬ëë¡ ì´ë (ì무ê²ë í
414ì§ ìì) 415 return; 416 } 417 e.preventDefault(); 418 if (state === 'pending') { 419 toastr.info('eìºì ì¬ì© ì ì²ì´ ì ìëì´ ì¹ì¸ ë기 ì¤ì ëë¤.'); 420 } else if (state === 'locked' || state === 'prepayStop') { 421 let content = state === 'prepayStop' ? i18n.t('ecash.prepayStopNotice') : 'ê´ë¦¬ìì ìí´ <span class="fw-semibold">eìºì ì´ì©ì´ ì í</span>ëììµëë¤.'; 422 let modal = new DynamicModal('eìºì ì´ì© ì í', content); 423 modal.setModalSize(MODAL_SIZE_SM); 424 modal.setConfirmButton(i18n.t('close'), () => modal.hide()); 425 modal.setCancelButton(i18n.t('qna.label'), () => { 426 modal.hide(); 427 location.href = '/qna/add'; 428 }); 429 modal.show(); 430 } else { 431 // inactive / rejected â ì ì²(ì¬ì ì²) ìë´ ëª¨ë¬ 432 openEcashRequestFlow(); 433 } 434 }); 435 }); 436 437 document.querySelector('#ecashRequestConfirmBtn').addEventListener('click', async () => { 438 confirmModal.hide(); 439 440 let sendHttp = new SendHttp('/ecash/request'); 441 sendHttp.method = 'POST'; 442 443 JsLoadingOverlay.show(); 444 let resultMap = await sendHttp.send(); 445 JsLoadingOverlay.hide(); 446 447 if (resultMap.code != RESULT_CODE_SUCCESS) { 448 // ê³µëì¸ì¦ì 미ë±ë¡ â ë±ë¡ ì ë ëª¨ë¬ 449 if (resultMap.code === 'W1031') { 450 showCertRequiredForEcash(); 451 return; 452 } 453 errorHandling(resultMap.code, resultMap.message); 454 return; 455 } 456 457 toastr.success('eìºì ì¬ì© ì ì²ì´ ì ìëììµëë¤.'); 458 setTimeout(() => location.reload(), 1000); 459 }); 460} 461 462// === in-flight ê°ë: ëë¸í´ë¦ ì¤ë³µ ì¤í ë°©ì§ === 463let ecashRequestFlowBusy = false; 464 465// === eìºì ì²êµ¬ì ì¡°í ê²°ê³¼ ìºì: ì¸ë¶ ì¤í¬ëí(ê°ì¤ì¬/ìëì¬ ë¡ê·¸ì¸) ì±ë¥ ê°ì === 466// payTypeë³ë¡ ì±ê³µ ê±´ë§ 2ë¶ê° ë³´ê´. ì¡°í ì¤í¨ë ìºìíì§ ìì ì¦ì ì¬ìë ê°ë¥ 467// ë©ë¶ ì±ê³µ ì location.reload()ë¡ íì´ì§ ì¬ë¡ëëë¯ë¡ ìºì 무í¨í ë¶íì 468let ecashBillCache = {}; 469const ECASH_BILL_CACHE_TTL_MS = 2 * 60 * 1000; // 2ë¶ 470 471// === eìºì ì ì² ì§ì ê³µíµ í¨ì: ì¸ì¦ì íì¸ â ë±ë¡ íì ëª¨ë¬ ì ë ëë ì ì² ëª¨ë¬ ì´ê¸° === 472async function openEcashRequestFlow() { 473 if (ecashRequestFlowBusy) return; 474 ecashRequestFlowBusy = true; 475 476 try { 477 // ê³µëì¸ì¦ì ë±ë¡ ì¬ë¶ íì¸ 478 let sendHttp = new SendHttp('/cert/get'); 479 sendHttp.method = 'GET'; 480 let result = await sendHttp.send(); 481 482 if (result.code === RESULT_CODE_SUCCESS && result.data) { 483 // ì¸ì¦ì ë±ë¡ë¨ â ì ì² ëª¨ë¬ ì´ê¸° 484 let confirmModal = bootstrap.Modal.getOrCreateInstance(document.querySelector('#ecashRequestConfirmModal')); 485 confirmModal.show(); 486 } else { 487 // ì¸ì¦ì 미ë±ë¡ â ë±ë¡ ì ë ëª¨ë¬ 488 showCertRequiredForEcash(); 489 } 490 } finally {
491 ecashRequestFlowBusy = false; 492 } 493} 494 495// === ê³µëì¸ì¦ì ë±ë¡ íì ëª¨ë¬ === 496function showCertRequiredForEcash() { 497 let modal = new DynamicModal(i18n.t('cert.registerTitle'), i18n.t('ecash.cert.requestMessage')); 498 modal.setModalSize(MODAL_SIZE_SM); 499 modal.setConfirmButton(i18n.t('cert.registerButton'), () => { 500 location.href = '/shop/updateCert'; 501 }); 502 modal.show(); 503} 504 505async function loadEcashInfo() { 506 let sendHttp = new SendHttp('/ecash/info'); 507 let resultMap = await sendHttp.send(); 508 509 if (resultMap.code != RESULT_CODE_SUCCESS) return; 510 511 let data = resultMap.data; 512 let limit = data.ecashLimit || 0; 513 let used = data.usedAmount || 0; 514 let available = data.ecashBalance || 0; 515 516 document.querySelector('.ecash-info-limit').textContent = numberWithCommas(limit); 517 document.querySelector('.ecash-info-used').textContent = numberWithCommas(used); 518 document.querySelector('.ecash-info-available').textContent = numberWithCommas(available); 519 520 // ë©ë¶ íëª©ë³ ì§í ì¤ ê±´ íì (ë¼ë²¨ ì 'ë기 Nê±´' ë±ì§ â ì ê¸/ë©ë¶ ë°©í¥ ì¤ë¦½ì´) 521 let ecashCard = document.querySelector('#ecashCard'); 522 if (ecashCard) { 523 let pendingCountMap = data.pendingCountMap || {}; 524 ecashCard.querySelectorAll('.ecash-pending-badge').forEach(badge => { 525 let type = badge.dataset.pendingType; 526 let count = (pendingCountMap[type] || 0); 527 badge.textContent = `ë기 ${count}ê±´`;
528 badge.classList.toggle('d-none', count === 0); 529 }); 530 } 531} 532 533async function loadEcashPaymentList() { 534 let sendHttp = new SendHttp('/ecash/listJson'); 535 sendHttp.data = { recordCountPerPage: 10 }; 536 let resultMap = await sendHttp.send(); 537 538 if (resultMap.code != RESULT_CODE_SUCCESS) return; 539 540 let list = resultMap.list || []; 541 let listEl = document.querySelector('#ecashCard .ecash-payment-list'); 542 if (!listEl) return; 543 544 if (list.length == 0) { 545 listEl.innerHTML = '<p class="text-center text-gray-500 py-4 mb-0">ì¬ì© ë´ìì´ ììµëë¤.</p>'; 546 return; 547 } 548 549 let html = ''; 550 list.forEach(item => { 551 let amountHtml = formatEcashAmount(item); 552 html += ` 553 <div class="d-flex justify-content-between align-items-center py-2"> 554 <div> 555 <span class="text-gray-800 fw-semibold">${getEcashTypeLabel(item.type)}</span> 556 <span class="text-gray-500 fs-7 ms-2">${formatDate(item.created)}</span> 557 </div> 558 ${amountHtml} 559 </div> 560 `; 561 }); 562 563 listEl.innerHTML = html; 564} 565 566function formatEcashAmount(item) { 567 let amount = item.billAmount || 0; 568 if (item.type === 'balance_adjust') { 569 let sign = amount >= 0 ? '+' : ''; 570 let color = amount >= 0 ? 'text-primary' : 'text-gray-600'; 571 return `<span class="fw-bold ${color}">${sign}${numberWithCommas(amount)}ì</span>`; 572 } 573 return `<span class="fw-bold text-gray-600">-${numberWithCommas(Math.abs(amount))}ì</span>`; 574} 575 576function getEcashTypeLabel(type) { 577 switch (type) { 578 case 'gas': return 'ëìê°ì¤'; 579 case 'water': return 'ìëìê¸'; 580 case 'national_tax': return 'ì§ë¡(êµì¸)'; 581 case 'local_tax': return 'ì§ë¡(ì§ë°©ì¸)'; 582 case 'general_giro': return 'ì§ë¡(ì¼ë°ì§ë¡)'; 583 case 'giro': return 'ì§ë¡'; 584 case 'emergency': return 'ë¹ìê¸'; 585 default: return type || '-'; 586 } 587} 588 589// ì¹´ë ìë¨ ìí ìë´: í¤ë ìí ë°°ì§ + ì¤ëª 문 (descHtmlì HTML íì© - ê³ ê°ì¼í° ë§í¬ ë±) 590function showEcashNotice(badgeClass, badgeText, descHtml) { 591 let card = document.querySelector('#ecashCard'); 592 if (!card) return; 593 594 let iconColorMap = { 595 'badge-light-brand-1': 'text-brand-1', 596 'badge-light-danger': 'text-danger', 597 'badge-secondary': 'text-gray-600' 598 }; 599 let iconColor = iconColorMap[badgeClass] ?? ''; 600 601 let badge = card.querySelector('.ecash-badge-status'); 602 badge.classList.add(badgeClass); 603 badge.innerHTML = '<i class="fa-solid fa-check ' + iconColor + ' me-1"></i><span>' + badgeText + '</span>'; 604 badge.classList.remove('d-none'); 605 606 if (descHtml != null) { 607 let desc = card.querySelector('.ecash-notice'); 608 desc.innerHTML = descHtml; 609 desc.classList.remove('d-none'); 610 } 611} 612 613// HTML ì´ì¤ì¼ì´í (ê´ë¦¬ì ì ë ¥ê° ë± ìì ì²ë¦¬) 614function escapeHtmlText(str) { 615 let div = document.createElement('div'); 616 div.textContent = str == null ? '' : str; 617 return div.innerHTML; 618} 619 620// ì ì¥ë ì¡°í ì ë³´ ì¬ë¶ ì¡°í (ê°ì¤/ìë) 621async function loadAccountRegistration() { 622 let sendHttp = new SendHttp('/ecash/inquiryAccount'); 623 let resultMap = await sendHttp.send(); 624 625 if (resultMap.code != RESULT_CODE_SUCCESS) { 626 return { gas: false, water: false }; 627 } 628 629 let data = resultMap.data || {}; 630 return { 631 gas: data.gas === true, 632 water: data.water === true 633 }; 634} 635 636// ë¡ë© 문구 ì ì ëë©ì´ì (site.js ì§í 모ë¬ë¤ê³¼ ëì¼: 0.7ì´ ì£¼ê¸°ë¡ . â .. â ... ìí) 637// ì ì position-absolute ë¡ ëì ê°ìê° ëì´ë ë¬¸êµ¬ê° ë°ë¦¬ì§ ìëë¤ 638// ë°í: ì ëë©ì´ì ì¤ì§ í¨ì (ë¡ë©ì´ ëëë©´ ë°ëì í¸ì¶í´ íì´ë¨¸ë¥¼ ì 리íë¤) 639function startDotsAnimation(textEl, baseText) { 640 let dotCount = 1; 641 let timer = null; 642 643 const tick = () => { 644 textEl.innerHTML = `<div class="position-relative">${baseText}<span class="position-absolute">${'.'.repeat(dotCount)}
644</span></div>`; 645 dotCount = dotCount % 3 + 1; 646 timer = setTimeout(tick, 700); 647 }; 648 tick(); 649 650 return () => clearTimeout(timer); 651} 652 653// ë©ë¶ ëª¨ë¬ (ì ì¥ë ì ë³´ë¡ ì¡°í + ì ê¸) 654// payPageUrl: 기존 ë©ë¶ íë©´ 주ì (í ë²í¼ì href. ì²êµ¬ì ë³ê²½Â·ì¡°í ì ë³´ ìì ì ì´ íë©´ìì íë¤) 655async function showEcashPaymentModal(payType, payPageUrl) { 656 let config = ECASH_TYPE_CONFIG[payType]; 657 if (!config) return; 658 659 // ë©ë¶ íë©´(pay.html)ì 'ë©ë¶ íì¸' 모ë¬(#ecashPayConfirmModal)ê³¼ ëì¼íê² êµ¬ì±íë¤ 660 // ì 목 'eìºì ë©ë¶', 기본(md) í¬ê¸°, ìì§ ê°ì´ë° ì ë ¬ 661 let modal = new DynamicModal('eìºì ë©ë¶'); 662 modal.setOptions({ verticalCenter: true }); 663 664 // ì´ ëª¨ë¬ì ì ì¥ë ì ë³´ë¡ ì¡°íí ìµì ì²êµ¬ì 1ê±´ë§ ë³´ì¬ì¤ë¤. ë¤ë¥¸ ì²êµ¬ìì 기존 ë©ë¶ íë©´ìì ì¡°ííë¤ 665 // ì¡°í ì±ê³µ ììë footer ì¼ìª½ ë²í¼ì´, ì¡°í ì¤í¨Â·ì´ë¯¸ ë©ë¶ ìë´ììë footer ì¼ìª½ ë²í¼ì´ ê·¸ íµë¡ê° ëë¤ 666 const payPageHref = payPageUrl || `/ecash/${payType}`; 667 668 // ë¡ë©ì ëª¨ë¬ ë³¸ë¬¸ ì Loader ë¡ íìíê³ (prepay.js ìì¸ ëª¨ë¬ë¤ê³¼ ëì¼í ê´ì©êµ¬) ê·¸ ìëì ìë´ ë¬¸êµ¬ë¥¼ ëë¤. 669 // ë¡ëë 컨í ì´ë ê°ì´ë°ì ì ë ë°°ì¹ëë¯ë¡, ëì´ë¥¼ ê°ì§ ì ì© ì¹¸ì ì£¼ê³ ë¬¸êµ¬ë ê·¸ ë¤ì íë¦ì ëëë¤ 670 // ë²í¼ì ê°ì·ë¤ê°(hideButton) ë´ì©ì´ ì¤ë¹ëë©´ ë¤ì ë³´ì¬ì¤ë¤. 671 // removeConfirmButton/removeCancelButton ì ê°ê° ìë ë²í¼ì ë¤ì ìë³´ë¯ë¡ ëì ê°ì´ í¸ì¶íë©´ ì ëë¤ 672 modal.setContents(`<div class="py-4"> 673 <div class="ecash-loading-icon min-h-150px"></div> 674 <div class="ecash-loading-text text-center text-gray-600 mt-2"></div> 675 </div>`); 676 modal.hideButton(); 677 modal.show(); 678 679 // ëì´ë min-h-150px ë¡ ê³ ì íë¯ë¡ Loader ì 기본 ëì´(min-h-200px)ë ëë¤ 680 let loader = new Loader(modal.container.querySelector('.ecash-loading-icon'), false); 681 loader.show(); 682 let stopDots = startDotsAnimation(modal.container.querySelector('.ecash-loading-text'), 'ì²êµ¬ì를 íì¸íê³ ìì´ì'); 683 const endLoading = () => { loader.hide(); stopDots(); }; 684 685 let inquireResult; 686 try { 687 // ìºì íì¸: payTypeì´ 2ë¶ ì´ë´ì ì¡°íë ê²°ê³¼ê° ìì¼ë©´ ì¬ì¬ì© 688 if (ecashBillCache[payType] && ecashBillCache[payType].expire > Date.now()) { 689 inquireResult = { ok: true, bill: ecashBillCache[payType].bill }; 690 } else { 691 // ì ì¥ë ì ë³´ë¡ ì¡°í (cred={}, billMonthApi='')
692 inquireResult = await ecashInquireBill(payType, {}, ''); 693 // ì¡°í ì±ê³µìë§ ìºìì ì ì¥ (ì¤í¨ë ì¦ì ì¬ìëí ì ìì´ì¼ í¨) 694 if (inquireResult.ok) { 695 ecashBillCache[payType] = { 696 bill: inquireResult.bill, 697 expire: Date.now() + ECASH_BILL_CACHE_TTL_MS 698 }; 699 } 700 } 701 } catch (err) { 702 endLoading(); 703 showEcashModalNotice(modal, 'ì²êµ¬ì ì¡°íì ì¤í¨íìµëë¤. ì ì í ë¤ì ìëí´ì£¼ì¸ì.', payPageHref); 704 return; 705 } 706 707 if (!inquireResult.ok) { 708 endLoading(); 709 showEcashModalNotice(modal, inquireResult.message || 'ì²êµ¬ì ì¡°íì ì¤í¨íìµëë¤.', payPageHref); 710 return; 711 } 712 713 let bill = inquireResult.bill; 714 let billAmount = parseInt(bill.billAmt) || 0; 715 716 // ì´ë¯¸ ë©ë¶ ì¬ë¶ íì¸ + ë©ë¶ ì ë³´ ì¡°í (ë³ë ¬ ì²ë¦¬) 717 let [checkResult, infoResult] = await Promise.all([ 718 ecashCheckPaidBill(payType, bill.payerNo, bill.billDt),
719 ecashLoadIncomeInfo(payType, billAmount) 720 ]); 721 endLoading(); 722 723 if (checkResult.paid) { 724 showEcashPaidDetail(modal, checkResult.item, payPageHref); 725 return; 726 } 727 728 if (infoResult.code != RESULT_CODE_SUCCESS) { 729 let msg = ecashIncomeErrorMessage(infoResult) || 'ë©ë¶ ì 보를 ë¶ë¬ì¤ì§ 못íìµëë¤.'; 730 showEcashModalNotice(modal, msg, payPageHref); 731 return; 732 } 733 734 let info = infoResult.data; 735 let feeAmount = info.feeAmount || 0; 736 let totalAmount = info.ecashAmount || 0; 737 let shortage = totalAmount - info.ecashBalance; 738 739 // í 모ë¬ì ë¤ì ì¡°í ê²°ê³¼ íë©´ì´ ìì¼ë¯ë¡ ì²êµ¬ ë´ì(ê³ ê°ëª Â·ê³ ê°ë²í¸Â·ì²êµ¬ì)ì ëª¨ë¬ ìì í¨ê» ë£ê³ , 740 // ìë ê¸ì¡ ë°ì¤ì íµì¼ëê² ê°ì bg-light rounded-3 p-4 ë°ì¤ë¡ 묶ëë¤. 741 // (buildDetailItemRow ì mx-n2/px-2 ë ììëë¯ë¡ ê¸ìë ê¸ì¡ íê³¼ ê°ì ì ì ë§ëë¤) 742 // ë©ë¶ íë©´ ì´ëì footer ì¼ìª½ ë²í¼ì´ ë§¡ëë¤ (ìë setCancelButton) 743 // ê³ ê°ëª ì ê°ì´ ìì ëë§ (ìëë 미ì ê³µ) 744 let payerName = (bill.payerName || '').trim(); 745 let hasName = payerName !== '' && payerName !== '-'; 746 let billInfoHtml = `<div class="bg-light rounded-3 p-4 mb-4"> 747 ${hasName ? buildDetailItemRow({ name: 'ê³ ê°ëª ', price: payerName, depth: 2, isNoPrice: true }) : ''} 748 ${buildDetailItemRow({ name: config.payerLabel, price: bill.payerNo || '-', depth: 2, isNoPrice: true })} 749 ${buildDetailItemRow({ name: 'ì²êµ¬ì', price: ecashFormatBillDt(bill.billDt), depth: 2, isNoPrice: true })} 750 </div>`; 751 752 // ê¸ì¡ ë°ì¤ ìë(ì ê¸ ìí·ê°ìê³ì¢Â·ìë´)ë ë©ë¶ íë©´ê³¼ ëì¼í ê²ì ì´ë¤ (ecash-pay-core.js) 753 modal.setContents(billInfoHtml + ECASH_PAY_CONFIRM_BODY_HTML); 754 ecashInitBankSelect(modal.container); 755 modal.container.querySelector('#ecashModalAmountBox').innerHTML = 756 ecashAmountBoxHtml(billAmount, feeAmount, totalAmount, info.ecashBalance - totalAmount, info.feeRate, info.ecashBalance, info.transferFee); 757 758 // footer ë [ë¤ë¥¸ ë¬ ì¡°í][ë©ë¶]. setConfirmButton ì ì·¨ì ë²í¼ 문구를 기본ê°ì¼ë¡ ëë리ë¯ë¡ 759 // setCancelButton ë³´ë¤ ë¨¼ì í¸ì¶í´ì¼ íë¤. (common.js DynamicModal 주ì 참조) 760 modal.setConfirmButton('ë©ë¶', async () => { 761 // ì ê¸ ìí/ê³ì¢ ê²ì¦ (ecash-pay-core.jsì ecashReadPayInput() í¸ì¶) 762 let payInput = ecashReadPayInput(modal.container); 763 if (!payInput) return; 764 765 // ìì¡ íì¸ 766 if (shortage > 0) { 767 ecashShortageModal({ currentBalance: info.ecashBalance, totalAmount: totalAmount, shortage: shortage }); 768 return; 769 } 770 771 // ë²í¼ ë¹íì±í (ì¤ë³µ í´ë¦ ë°©ì§) 772 let confirmBtn = modal.container.querySelector('button.confirm'); 773 if (confirmBtn) confirmBtn.disabled = true; 774 775 // ë©ë¶ ìì² 776 let extBillName = (config.extPrefix + ' ' + (bill.payerName || '')).trim();
777 let requestResult = await ecashRequestIncome({ 778 type: payType, 779 bankId: payInput.bankId, 780 bankAccount: payInput.bankAccount, 781 amount: billAmount, 782 payerNo: bill.payerNo, 783 billMonth: bill.billDt, 784 extBillName: extBillName 785 }); 786 787 // ë²í¼ 복구 788 if (confirmBtn) confirmBtn.disabled = false; 789 790 if (requestResult.code != RESULT_CODE_SUCCESS) { 791 // ecash-pay-core.jsì ecashIncomeErrorMessage() í¸ì¶ (ìì¡ ë¶ì¡±ì ë¶ì¡± 모ë¬ë¡ ì²ë¦¬ëì´ null) 792 let errorMessage = ecashIncomeErrorMessage(requestResult); 793 if (errorMessage) toastr.error(errorMessage); 794 return; 795 } 796 797 toastr.success('ë©ë¶ ìì²ì´ ì ìëììµëë¤.'); 798 setTimeout(() => location.reload(), 1000); 799 }); 800 801 modal.setCancelButton('ë¤ë¥¸ ë¬ ì¡°í', () => { location.href = payPageHref; }); 802 modal.showButton(); // ë¡ë© ëì ê°ì¶°ë ë²í¼ ìì 복구 803} 804 805// ì¡°í ì¤í¨Â·ì´ë¯¸ ë©ë¶ì²ë¼ ë ì§íí ê² ìì ë: ìë´ë§ ë¨ê¸°ê³ footer ë²í¼ì¼ë¡ ì 리íë¤. 806// message: ìë´ ë¬¸êµ¬ 807// payPageHref: ë¤ë¥¸ ë¬ ì¡°í ë²í¼ì ì´ë 주ì 808// footer ë [ë¤ë¥¸ ë¬ ì¡°í][ë«ê¸°] ë ë²í¼. setConfirmButton ì ì·¨ì ë²í¼ 문구를 기본ê°ì¼ë¡ ëë리ë¯ë¡ 809// setCancelButton ë³´ë¤ ë¨¼ì í¸ì¶í´ì¼ íë¤. (common.js DynamicModal 주ì 참조) 810function showEcashModalNotice(modal, message, payPageHref) { 811 modal.setContents(message); 812 modal.setConfirmButton(i18n.t('close'), () => modal.hide()); 813 modal.setCancelButton('ë¤ë¥¸ ë¬ ì¡°í', () => { location.href = payPageHref; }); 814 modal.showButton(); 815} 816 817// 기ë©ë¶ ê±´ ìì¸ íì (pay.html ì showPaidDetail ê³¼ ëì¼ êµ¬ì¡°ë¥¼ 모ë¬ìì íì) 818// checkResult.item: 기ë©ë¶ ê±´ ì ë³´ 819// payPageHref: ë¤ë¥¸ ë¬ ì¡°í ë²í¼ì ì´ë 주ì 820function showEcashPaidDetail(modal, item, payPageHref) { 821 let cardHtml = ecashPaidDetailCardHtml(item); 822 modal.setContents(cardHtml); 823 modal.setConfirmButton(i18n.t('close'), () => modal.hide()); 824 modal.setCancelButton('ë¤ë¥¸ ë¬ ì¡°í', () => { location.href = payPageHref; }); 825 modal.showButton(); 826} 827 828// ë¹ìê¸ ìë´ ëª¨ë¬ 829// ìë´Â·ì ë ëª¨ë¬ ê³µì© í í릿(#iconModal)ì ê·¸ëë¡ ì´ë¤ â site.js ì íìê°ì ì ë ëª¨ë¬ ë±ê³¼ ê°ì ë°©ì 830// ëìê°ì¤Â·ìëìê¸ì¼ë¡ ê°ë ë²í¼ì ëì§ ìëë¤. ë°ë¡ ë¤ eìºì ì¹´ëì ì´ë¯¸ ê°ì ë²í¼ì´ ìì´ ì§ì ì ì´ ê²¹ì¹ë¤ 831function showEmergencyGuideModal() { 832 let modal = new DynamicModal(); 833 modal.cloneModal(document.querySelector('#iconModal')); 834 modal.setOptions({ verticalCenter: true }); 835 836 modal.container.querySelector('.item-icon').src = '/assets/images/icon/icon-warning.gif'; 837 modal.container.querySelector('.item-title').innerHTML = 'ìì§ ë¹ìê¸ì ë°ì ì ìì´ì'; 838 modal.container.querySelector('.item-subtitle').innerHTML = 'ëìê°ì¤ë ìëìê¸ì ì²êµ¬ ë´ìì<br>í ë²ì´ë¼ë ì¡°íí´ì¼ ë°ì ì ìì´ì.'; 839 840 modal.show(); 841} 842 843// ì ì² ìë¸ìí ì¡°í â { state: 'inactive'|'pending'|'rejected', rejectReason } 844async function getEcashRequestState() { 845 let sendHttp = new SendHttp('/ecash/getRequestStatus'); 846 let resultMap = await sendHttp.send(); 847 848 if (resultMap.code != RESULT_CODE_SUCCESS) return { state: 'inactive' }; 849 850 let data = resultMap.data; 851 if (!data || !data.hasRequest) return { state: 'inactive' }; 852 if (data.status == 'R') return { state: 'pending' }; 853 if (data.status == 'D') return { state: 'rejected', rejectReason: data.rejectReason }; 854 return { state: 'inactive' }; 855}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.