Files
jongryangje/app/Views/admin/designated_shop/index.php
taekyoungc a61e7887ea feat: 지정판매소 관리 화면 개선 — 좌우 배치·상세 폼·리스트 정렬·동코드 표시
- 리스트(좌)/상세정보(우) 좌우 배치, 상세를 라벨-값 폼 형태로 변경
- 리스트 컬럼(번호·대표자·상호·구군(동코드)·지정일·구역·상태) + 컬럼별 정렬
- 페이징 제거 → 전체 로드 후 패널 내 스크롤
- 주소로 동 기본코드(D) 산출해 저장·표시(ds_dong_code), 등록 시 주소 선택하면 동코드 표시
- 인쇄·엑셀저장을 관리 화면에도 추가(엑셀에 동코드·지정일 포함)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 18:38:54 +09:00

648 lines
27 KiB
PHP
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.
<?php
helper('admin');
$currentPath = current_nav_request_path();
if ($currentPath === 'bag/designated-shops') {
$readOnly = false;
} elseif ($currentPath === 'bag/designated-shops/browse') {
$readOnly = true;
} else {
$readOnly = ! empty($readOnly);
}
?>
<?= view('components/print_header', ['printTitle' => $readOnly ? '지정판매소 조회 목록' : '지정판매소 목록']) ?>
<style>
/* 목록 왼쪽 → 지정판매소 정보 오른쪽 (좌우 2열) */
.ds-split {
display: flex;
flex-direction: row;
gap: 0.5rem;
min-height: 0;
flex: 1 1 auto;
align-items: stretch;
}
.ds-list-panel {
flex: 1 1 55%;
min-width: 0;
display: flex;
flex-direction: column;
min-height: 0;
border: 1px solid #ccc;
background: #fff;
/* 기본은 내용 높이만 차지(리스트를 10행 정도로 제한하고 아래 빈 공간 없이). */
align-self: flex-start;
}
/* 지정판매소 리스트: 헤더 + 약 15행만 보이고 나머지는 스크롤 (행 39px×15 + 헤더 34) */
.ds-list-scroll { max-height: 619px; }
.ds-list-panel thead th {
position: sticky;
top: 0;
background: #f8f9fa;
z-index: 2;
box-shadow: inset 0 -1px 0 #e5e7eb;
}
.ds-detail-panel {
flex: 1 1 45%;
min-width: 0;
min-height: 12rem;
border: 1px solid #ccc;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
/* 좁은 화면에서는 다시 위·아래로 쌓아 가독성 확보 */
@media (max-width: 1024px) {
.ds-split { flex-direction: column; }
.ds-list-panel { flex: 0 1 auto; max-height: 42vh; align-self: stretch; }
.ds-detail-panel { flex: 1 1 auto; }
}
.ds-panel-title {
font-size: 12px;
font-weight: bold;
padding: 6px 10px;
background: linear-gradient(180deg, #fafafa 0%, #e9ecef 100%);
border-bottom: 1px solid #ccc;
color: #333;
}
.ds-summary-bar {
font-size: 12px;
padding: 6px 10px;
background: #fff3cd;
border: 1px solid #ffc107;
color: #333;
}
.ds-row-selected td { background-color: #cce5ff !important; }
.ds-detail-inner { padding: 10px; overflow: auto; flex: 1; }
/* 원본 지정판매소 정보: 라벨 고정폭 + 2열 값(우측 값이 더 넓음), 주소·개인전화는 전폭 */
.ds-detail-form {
font-size: 12px;
border: 1px solid #bbb;
background: #fff;
}
.ds-row {
display: grid;
gap: 0;
border-bottom: 1px solid #ccc;
}
.ds-detail-form > .ds-row:last-child { border-bottom: none; }
/* 그 외 2+2 동일 비율 (상호명 | 우편번호 등) */
.ds-row-4-even {
grid-template-columns: 5.5rem minmax(0, 1fr) 5.5rem minmax(0, 1fr);
}
/* 판매소번호 전폭 행 — 값을 우편·주소 필드처럼 넓게 */
.ds-value-shop-wide {
font-weight: 600;
font-variant-numeric: tabular-nums;
letter-spacing: 0.04em;
font-size: 13px;
padding-top: 8px;
padding-bottom: 8px;
}
/* 라벨 | 값(나머지 전체) — 도로명·지번·개인전화·이메일 */
.ds-row-wide {
grid-template-columns: 5.5rem minmax(0, 1fr);
}
.ds-row-wide-tall .ds-field-value {
min-height: 3.25rem;
align-content: start;
}
/* 도로명주소 + 카카오맵 버튼 */
.ds-field-value-with-map {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 8px;
}
.ds-field-value-with-map .ds-addr-text {
flex: 1 1 12rem;
min-width: 0;
word-break: break-word;
}
.ds-field-label {
background: #eef2f5;
border-right: 1px solid #ccc;
padding: 5px 8px;
font-weight: 600;
color: #333;
display: flex;
align-items: center;
}
.ds-field-value {
padding: 5px 8px;
background: #fff;
word-break: break-word;
border-right: 1px solid #ccc;
}
.ds-row-4-even > *:nth-child(4n) { border-right: none; }
.ds-row-wide > .ds-field-value { border-right: none; }
.ds-field-hint {
font-size: 11px;
color: #b91c1c;
margin-top: 4px;
line-height: 1.35;
}
@media (max-width: 720px) {
.ds-row-4-even { grid-template-columns: 5rem 1fr; }
}
.ds-detail-actions { padding: 10px; border-top: 1px solid #ccc; background: #eee; }
.ds-detail-info-wrap { overflow-x: auto; }
.ds-detail-info-wrap .data-table th { white-space: nowrap; }
.ds-detail-info-wrap th.ds-col-tight-head,
.ds-detail-info-wrap td.ds-col-tight-cell {
max-width: 6.5rem;
width: 6.5rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ds-list-panel .ds-col-tight {
max-width: 6rem;
width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ds-list-panel .ds-col-zip {
width: 4.5rem;
max-width: 5rem;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.ds-list-panel .ds-col-addr-list {
max-width: 11rem;
min-width: 6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ds-list-panel .ds-col-detail-list {
max-width: 8rem;
min-width: 4rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ds-ro-road-btn { margin-left: 6px; vertical-align: middle; }
</style>
<?php
$listBasePath = $readOnly ? 'designated-shops/browse' : 'designated-shops';
?>
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
<div class="flex flex-wrap items-center justify-between gap-y-2">
<span class="text-sm font-bold text-gray-700"><?= $readOnly ? '지정판매소 조회' : '지정판매소 관리' ?></span>
<div class="flex items-center gap-2">
<a href="<?= mgmt_url('designated-shops/export') ?>" class="no-print border border-btn-excel-border text-btn-excel-text px-3 py-1 rounded-sm text-sm hover:bg-green-50 transition">엑셀저장</a>
<button type="button" onclick="window.print()" class="no-print border border-btn-print-border text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50 transition">인쇄</button>
<?php if (! $readOnly): ?>
<a href="<?= mgmt_url('designated-shops/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">지정판매소 등록</a>
<?php endif; ?>
</div>
</div>
</section>
<section class="p-2 bg-white border-b border-gray-200 no-print">
<form method="GET" action="<?= mgmt_url($listBasePath) ?>" class="flex flex-wrap items-center gap-2">
<span class="text-sm font-semibold text-gray-700 mr-1">지정판매소 검색</span>
<label class="text-sm text-gray-600">상호명</label>
<input type="text" name="ds_name" value="<?= esc($dsName ?? '') ?>" placeholder="상호명" class="border border-gray-300 rounded px-2 py-1 text-sm w-36"/>
<label class="text-sm text-gray-600">구·군 코드</label>
<select name="ds_gugun_code" class="border border-gray-300 rounded px-2 py-1 text-sm min-w-[14rem]">
<option value="">전체</option>
<?php foreach (($gugunCodes ?? []) as $gc): ?>
<?php $gCode = (string) ($gc->ds_gugun_code ?? ''); ?>
<option value="<?= esc($gCode) ?>" <?= ($dsGugunCode ?? '') === $gCode ? 'selected' : '' ?>><?= esc((string) (($gugunNameMap[$gCode] ?? '') !== '' ? $gugunNameMap[$gCode] : $gCode)) ?></option>
<?php endforeach; ?>
</select>
<label class="text-sm text-gray-600">상태</label>
<select name="ds_state" class="border border-gray-300 rounded px-2 py-1 text-sm">
<option value="">전체</option>
<option value="1" <?= ($dsState ?? '') === '1' ? 'selected' : '' ?>>정상</option>
<option value="2" <?= ($dsState ?? '') === '2' ? 'selected' : '' ?>>폐업</option>
<option value="3" <?= ($dsState ?? '') === '3' ? 'selected' : '' ?>>직권해지</option>
</select>
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
<a href="<?= mgmt_url($listBasePath) ?>" class="border border-gray-300 text-gray-600 px-3 py-1 rounded-sm text-sm hover:bg-gray-50">초기화</a>
</form>
</section>
<?php
$sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
?>
<div class="ds-summary-bar no-print mx-2 mt-2 rounded-sm">
건수 : <?= (int) ($sc['total'] ?? 0) ?>
(정상 : <?= (int) ($sc[1] ?? 0) ?> / 폐업 : <?= (int) ($sc[2] ?? 0) ?> / 해지 : <?= (int) ($sc[3] ?? 0) ?>)
</div>
<div class="ds-split no-print mx-2 mb-2 mt-2 flex-1 min-h-0">
<div class="ds-list-panel">
<div class="ds-panel-title shrink-0">지정판매소 리스트</div>
<div class="ds-list-scroll overflow-auto flex-1 min-h-0">
<table class="w-full text-[13px]">
<thead>
<tr class="text-left text-[11px] font-semibold text-gray-500 border-b border-gray-200">
<th class="ds-sort-th py-2.5 px-2 w-14 text-left cursor-pointer select-none" data-sort-key="no" data-sort-type="num">번호<span class="ds-sort-ind"></span></th>
<th class="ds-sort-th py-2.5 px-2 cursor-pointer select-none" data-sort-key="rep" data-sort-type="str">대표자명<span class="ds-sort-ind"></span></th>
<th class="ds-sort-th py-2.5 px-2 cursor-pointer select-none" data-sort-key="name" data-sort-type="str">상호명<span class="ds-sort-ind"></span></th>
<th class="ds-sort-th py-2.5 px-2 cursor-pointer select-none" data-sort-key="dong" data-sort-type="str">구·군(동코드)<span class="ds-sort-ind"></span></th>
<th class="ds-sort-th py-2.5 px-2 w-24 text-left cursor-pointer select-none" data-sort-key="designated" data-sort-type="str">지정일<span class="ds-sort-ind"></span></th>
<th class="ds-sort-th py-2.5 px-2 cursor-pointer select-none" data-sort-key="zone" data-sort-type="str">구역<span class="ds-sort-ind"></span></th>
<th class="ds-sort-th py-2.5 px-2 w-16 text-left cursor-pointer select-none" data-sort-key="state" data-sort-type="num">상태<span class="ds-sort-ind"></span></th>
</tr>
</thead>
<tbody id="ds-list-body">
<?php foreach ($list as $i => $row): ?>
<?php
$sn = (string) ($row->ds_shop_no ?? '');
if (preg_match('/(\d{3})$/', $sn, $m)) {
$shortNo = $m[1];
} elseif ($sn !== '' && strlen($sn) >= 3) {
$shortNo = substr($sn, -3);
} else {
$shortNo = $sn;
}
$st = (int) ($row->ds_state ?? 1);
$stLabel = $st === 1 ? '' : ($st === 2 ? '폐업' : '해지');
$ggCode = (string) ($row->ds_gugun_code ?? '');
$ggLabel = (string) (($gugunNameMap[$ggCode] ?? '') !== '' ? $gugunNameMap[$ggCode] : $ggCode);
$da = $row->ds_designated_at ?? null;
$daDisp = ($da !== null && $da !== '' && (string) $da !== '0000-00-00') ? substr((string) $da, 0, 10) : '';
$zone = (string) ($row->ds_zone_code ?? '');
$zipList = trim((string) ($row->ds_zip ?? ''));
$roadL = trim((string) ($row->ds_addr ?? ''));
$jibunL = trim((string) ($row->ds_addr_jibun ?? ''));
$addrMainList = $roadL !== '' ? $roadL : $jibunL;
$addrDetailList = trim((string) ($row->ds_addr_detail ?? ''));
$addrCombinedList = trim($addrMainList . ' ' . $addrDetailList);
if ($addrCombinedList === '') {
$addrCombinedList = $addrMainList;
}
// 구·군(동코드) 표시: 동코드가 있으면 "동코드 (동명)", 없으면 구·군명
$dongDisp = (string) (($dongDisplayMap[(int) ($row->ds_idx ?? 0)] ?? '') !== '' ? $dongDisplayMap[(int) ($row->ds_idx ?? 0)] : $ggLabel);
?>
<tr class="ds-list-row cursor-pointer border-b border-gray-200 last:border-0 hover:bg-blue-50/60"
data-row-index="<?= (int) $i ?>" role="button" tabindex="0"
data-sort-no="<?= esc((string) (preg_match('/\d+/', $shortNo, $mn) ? (int) $mn[0] : 0), 'attr') ?>"
data-sort-rep="<?= esc($row->ds_rep_name ?? '', 'attr') ?>"
data-sort-name="<?= esc($row->ds_name ?? '', 'attr') ?>"
data-sort-dong="<?= esc($dongDisp, 'attr') ?>"
data-sort-designated="<?= esc($daDisp, 'attr') ?>"
data-sort-zone="<?= esc($zone, 'attr') ?>"
data-sort-state="<?= (int) $st ?>">
<td class="py-2.5 px-2 text-left font-mono text-gray-700"><?= esc($shortNo) ?></td>
<td class="py-2.5 px-2 text-gray-600 ds-col-tight" title="<?= esc($row->ds_rep_name ?? '') ?>"><?= esc($row->ds_rep_name ?? '') ?></td>
<td class="py-2.5 px-2 font-medium text-gray-900 ds-col-tight" title="<?= esc($row->ds_name ?? '') ?>"><?= esc($row->ds_name ?? '') ?></td>
<td class="py-2.5 px-2 text-gray-600 font-mono" title="<?= esc($dongDisp) ?>"><?= esc($dongDisp) ?></td>
<td class="py-2.5 px-2 text-left text-gray-500 text-[12px]"><?= esc($daDisp) ?></td>
<td class="py-2.5 px-2 text-gray-600" title="<?= esc($zone) ?>"><?= esc($zone) ?></td>
<td class="py-2.5 px-2 text-left">
<?php if ($st === 1): ?>
<span class="inline-block px-2 py-0.5 rounded-full text-[11px] font-medium bg-emerald-50 text-emerald-700">정상</span>
<?php elseif ($stLabel !== ''): ?>
<span class="inline-block px-2 py-0.5 rounded-full text-[11px] font-medium bg-gray-100 text-gray-500"><?= esc($stLabel) ?></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="ds-detail-panel">
<div class="ds-panel-title shrink-0">지정판매소 정보</div>
<div class="ds-detail-inner" id="ds-detail-box">
<p id="ds-detail-placeholder" class="text-sm text-gray-500 py-6 text-center">위 목록에서 행을 선택하세요.</p>
<div id="ds-detail-fields" class="hidden">
<!-- 제목 왼쪽 · 내용 오른쪽 (라벨/값) 폼 형태 -->
<div class="ds-detail-form" id="ds-detail-info-table" aria-label="지정판매소 상세">
<div class="ds-row ds-row-wide">
<div class="ds-field-label">판매소번호</div>
<div class="ds-field-value ds-value-shop-wide" data-ro="ds_shop_no">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">상호명</div>
<div class="ds-field-value" data-ro="ds_name">—</div>
<div class="ds-field-label">우편번호</div>
<div class="ds-field-value" data-ro="ds_zip">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">사업자번호</div>
<div class="ds-field-value" data-ro="ds_biz_no">—</div>
<div class="ds-field-label">일반전화</div>
<div class="ds-field-value" data-ro="ds_tel">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">대표자명</div>
<div class="ds-field-value" data-ro="ds_rep_name">—</div>
<div class="ds-field-label">이메일</div>
<div class="ds-field-value" data-ro="ds_email">—</div>
</div>
<div class="ds-row ds-row-wide">
<div class="ds-field-label">도로명주소</div>
<div class="ds-field-value ds-field-value-with-map">
<span class="ds-addr-text" data-ro="ds_addr">—</span>
<button type="button" class="no-print border border-btn-print-border text-gray-700 px-2 py-0.5 rounded-sm text-xs hover:bg-gray-50 shrink-0" id="ds-ro-map-btn">지도 보기</button>
</div>
</div>
<div class="ds-row ds-row-wide">
<div class="ds-field-label">지번주소</div>
<div class="ds-field-value" data-ro="ds_addr_jibun">—</div>
</div>
<div class="ds-row ds-row-wide">
<div class="ds-field-label">상세주소</div>
<div class="ds-field-value" data-ro="ds_addr_detail">—</div>
</div>
<div class="ds-row ds-row-wide">
<div class="ds-field-label">개인전화</div>
<div class="ds-field-value" data-ro="ds_rep_phone">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">지정일자</div>
<div class="ds-field-value" data-ro="ds_designated_at">—</div>
<div class="ds-field-label">업태</div>
<div class="ds-field-value" data-ro="ds_biz_type">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">업종</div>
<div class="ds-field-value" data-ro="ds_biz_kind">—</div>
<div class="ds-field-label">구·군(동코드)</div>
<div class="ds-field-value" data-ro="dong_display">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">구역</div>
<div class="ds-field-value" data-ro="ds_zone_code">—</div>
<div class="ds-field-label">종사업장번호</div>
<div class="ds-field-value" data-ro="ds_branch_no">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">가상계좌</div>
<div class="ds-field-value" data-ro="ds_va_bank">—</div>
<div class="ds-field-label">계좌번호</div>
<div class="ds-field-value" data-ro="ds_va_account">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">영업상태</div>
<div class="ds-field-value" data-ro="state_label">—</div>
<div class="ds-field-label">변경일자</div>
<div class="ds-field-value" data-ro="ds_state_changed_at">—</div>
</div>
<div class="ds-row ds-row-wide">
<div class="ds-field-label">변경사유</div>
<div class="ds-field-value" data-ro="ds_change_reason">—</div>
</div>
<div class="ds-row ds-row-4-even">
<div class="ds-field-label">지자체</div>
<div class="ds-field-value" data-ro="lg_name">—</div>
<div class="ds-field-label">등록일시</div>
<div class="ds-field-value" data-ro="ds_regdate">—</div>
</div>
</div>
</div>
</div>
<?php if (! $readOnly): ?>
<div class="ds-detail-actions no-print flex flex-wrap items-center gap-3 shrink-0">
<a id="ds-edit-link" href="#" class="text-blue-700 hover:underline text-sm font-medium pointer-events-none opacity-40">수정</a>
<form id="ds-delete-form" method="POST" action="" class="inline" onsubmit="return confirm('이 지정판매소를 삭제하시겠습니까?');">
<?= csrf_field() ?>
<button type="submit" id="ds-delete-btn" class="text-red-600 hover:underline text-sm pointer-events-none opacity-40" disabled>삭제</button>
</form>
</div>
<?php endif; ?>
</div>
</div>
<?php if (isset($pager)): ?>
<div class="mt-2 mb-2 mx-2 no-print"><?= $pager->links() ?></div>
<?php endif; ?>
<?= view('components/kakao_map_modal', ['kakaoJavascriptKey' => $kakaoJavascriptKey ?? '']) ?>
<script type="application/json" id="ds-detail-json"><?= $detailRowsJson ?? '[]' ?></script>
<script>
(function () {
var raw = document.getElementById('ds-detail-json');
var rows = [];
try {
rows = JSON.parse(raw.textContent || '[]');
} catch (e) {
rows = [];
}
var readOnly = <?= json_encode($readOnly) ?>;
var body = document.getElementById('ds-list-body');
var placeholder = document.getElementById('ds-detail-placeholder');
var fieldsWrap = document.getElementById('ds-detail-fields');
var infoTable = document.getElementById('ds-detail-info-table');
var editLink = readOnly ? null : document.getElementById('ds-edit-link');
var delForm = readOnly ? null : document.getElementById('ds-delete-form');
var delBtn = readOnly ? null : document.getElementById('ds-delete-btn');
// mgmt_url() 이 path 를 trim 하므로 'edit/33' 이 아니라 'edit33' 로 붙지 않게 슬래시를 넣음
var editBase = <?= json_encode(mgmt_url('designated-shops/edit')) ?>;
var delBase = <?= json_encode(mgmt_url('designated-shops/delete')) ?>;
function textVal(v) {
return (v === '' || v == null) ? '—' : String(v);
}
function buildKakaoMapSearchQuery(d) {
var road = String(d.ds_addr || '').trim();
var jibun = String(d.ds_addr_jibun || '').trim();
var detail = String(d.ds_addr_detail || '').trim();
var q = road || jibun;
if (detail) {
q = q ? (q + ' ' + detail) : detail;
}
return q;
}
function fillDetailInfoTable(d) {
if (!infoTable) return;
infoTable.querySelectorAll('[data-ro]').forEach(function (el) {
var k = el.getAttribute('data-ro');
var v = d[k];
if (k === 'ds_va_account') {
v = d.ds_va_account || d.ds_va_number || '';
}
el.textContent = textVal(v);
});
window.__dsDetailForMap = d;
}
function selectIndex(idx) {
if (!rows.length || idx < 0 || idx >= rows.length) return;
var d = rows[idx];
Array.prototype.forEach.call(body.querySelectorAll('tr.ds-list-row'), function (tr) {
tr.classList.remove('ds-row-selected');
});
var tr = body.querySelector('tr[data-row-index="' + idx + '"]');
if (tr) tr.classList.add('ds-row-selected');
placeholder.classList.add('hidden');
fieldsWrap.classList.remove('hidden');
fillDetailInfoTable(d);
if (!readOnly && editLink && delForm && delBtn) {
var id = d.ds_idx;
editLink.href = editBase + '/' + id;
editLink.classList.remove('pointer-events-none', 'opacity-40');
delForm.action = delBase + '/' + id;
delBtn.disabled = false;
delBtn.classList.remove('pointer-events-none', 'opacity-40');
}
}
if (body) {
body.addEventListener('click', function (e) {
var tr = e.target.closest('tr.ds-list-row');
if (!tr) return;
var idx = parseInt(tr.getAttribute('data-row-index'), 10);
if (!isNaN(idx)) selectIndex(idx);
});
body.addEventListener('keydown', function (e) {
if (e.key !== 'Enter' && e.key !== ' ') return;
var tr = e.target.closest('tr.ds-list-row');
if (!tr) return;
e.preventDefault();
var idx = parseInt(tr.getAttribute('data-row-index'), 10);
if (!isNaN(idx)) selectIndex(idx);
});
}
// ── 컬럼 헤더 클릭 정렬 (DOM 행 재배치 — data-row-index 유지) ──
(function initSort() {
if (!body) return;
var ths = document.querySelectorAll('.ds-sort-th');
var curKey = null, curDir = 'asc';
function apply(key, type, dir) {
var trs = Array.prototype.slice.call(body.querySelectorAll('tr.ds-list-row'));
trs.sort(function (a, b) {
var va = a.getAttribute('data-sort-' + key) || '';
var vb = b.getAttribute('data-sort-' + key) || '';
var r;
if (type === 'num') {
r = (parseFloat(va) || 0) - (parseFloat(vb) || 0);
} else {
r = String(va).localeCompare(String(vb), 'ko');
}
return dir === 'asc' ? r : -r;
});
trs.forEach(function (tr) { body.appendChild(tr); });
}
ths.forEach(function (th) {
th.addEventListener('click', function () {
var key = th.getAttribute('data-sort-key');
var type = th.getAttribute('data-sort-type') || 'str';
if (curKey === key) {
curDir = curDir === 'asc' ? 'desc' : 'asc';
} else {
curKey = key; curDir = 'asc';
}
apply(key, type, curDir);
ths.forEach(function (t) {
var ind = t.querySelector('.ds-sort-ind');
if (ind) ind.textContent = (t === th) ? (curDir === 'asc' ? ' ▲' : ' ▼') : '';
});
});
});
})();
var mapBtnRo = document.getElementById('ds-ro-map-btn');
if (mapBtnRo) {
mapBtnRo.addEventListener('click', function (ev) {
ev.preventDefault();
var d = window.__dsDetailForMap;
if (!d) return;
var q = buildKakaoMapSearchQuery(d);
if (!q) {
window.alert('표시할 주소 정보가 없습니다.');
return;
}
if (typeof window.openDesignatedShopKakaoMap === 'function') {
window.openDesignatedShopKakaoMap(q);
} else {
window.open('https://map.kakao.com/link/search/' + encodeURIComponent(q), '_blank', 'noopener,noreferrer');
}
});
}
if (rows.length > 0) {
selectIndex(0);
} else if (!readOnly && editLink && delBtn) {
editLink.classList.add('pointer-events-none', 'opacity-40');
delBtn.disabled = true;
delBtn.classList.add('pointer-events-none', 'opacity-40');
}
})();
</script>
<!-- 인쇄용: 전체 테이블 -->
<div class="hidden print:block print:p-4">
<table class="w-full data-table text-xs">
<thead>
<tr>
<th class="text-center">번호</th>
<th>지자체</th>
<th>구·군(동코드)</th>
<th class="text-center">지정일</th>
<th>구역</th>
<th>대표자명</th>
<th>상호명</th>
<th>우편번호</th>
<th>주소</th>
<th>사업자번호</th>
<th>전화</th>
<th>판매소번호</th>
<th>가상계좌</th>
<th class="text-center">상태</th>
<th>등록일</th>
</tr>
</thead>
<tbody>
<?php foreach ($list as $row): ?>
<?php
$snP = (string) ($row->ds_shop_no ?? '');
if (preg_match('/(\d{3})$/', $snP, $mP)) {
$shortNoP = $mP[1];
} elseif ($snP !== '' && strlen($snP) >= 3) {
$shortNoP = substr($snP, -3);
} else {
$shortNoP = $snP;
}
$daP = $row->ds_designated_at ?? null;
$daDispP = ($daP !== null && $daP !== '' && (string) $daP !== '0000-00-00') ? substr((string) $daP, 0, 10) : '';
$stP = (int) ($row->ds_state ?? 1);
$stLabP = $stP === 1 ? '정상' : ($stP === 2 ? '폐업' : '직권해지');
$zipP = trim((string) ($row->ds_zip ?? ''));
$roadP = trim((string) ($row->ds_addr ?? ''));
$jibP = trim((string) ($row->ds_addr_jibun ?? ''));
$addrP = $roadP !== '' ? $roadP : $jibP;
$detP = trim((string) ($row->ds_addr_detail ?? ''));
$addrCombinedP = trim($addrP . ' ' . $detP);
if ($addrCombinedP === '') {
$addrCombinedP = $addrP;
}
?>
<tr>
<td class="text-center"><?= esc($shortNoP) ?></td>
<td class="text-left"><?= esc($lgMap[$row->ds_lg_idx] ?? '') ?></td>
<?php
$gCodeP = (string) ($row->ds_gugun_code ?? '');
$ggLabelP = (string) (($gugunNameMap[$gCodeP] ?? '') !== '' ? $gugunNameMap[$gCodeP] : $gCodeP);
$dongDispP = (string) (($dongDisplayMap[(int) ($row->ds_idx ?? 0)] ?? '') !== '' ? $dongDisplayMap[(int) ($row->ds_idx ?? 0)] : $ggLabelP);
?>
<td class="text-left"><?= esc($dongDispP) ?></td>
<td class="text-center"><?= esc($daDispP) ?></td>
<td class="text-left"><?= esc($row->ds_zone_code ?? '') ?></td>
<td class="text-left"><?= esc($row->ds_rep_name ?? '') ?></td>
<td class="text-left"><?= esc($row->ds_name ?? '') ?></td>
<td class="text-left"><?= esc($zipP) ?></td>
<td class="text-left"><?= esc($addrCombinedP) ?></td>
<td class="text-left"><?= esc($row->ds_biz_no ?? '') ?></td>
<td class="text-left"><?= esc($row->ds_tel ?? '') ?></td>
<td class="text-left"><?= esc($row->ds_shop_no) ?></td>
<td class="text-left"><?= esc($row->ds_va_number) ?></td>
<td class="text-center"><?= esc($stLabP) ?></td>
<td class="text-left"><?= esc($row->ds_regdate ?? '') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>