fix: 지정판매소 리스트 기본 10행 표시 + 스크롤(#25 일부)

리스트 스크롤 영역을 헤더+약 10행(424px)으로 제한하고 나머지는 스크롤.
스크롤 시 헤더 고정(sticky), 좌측 패널을 내용 높이로 맞춰(align-self)
리스트 아래 빈 공간 방지. 좁은 화면(1024px 이하)에선 기존 동작 유지.
This commit is contained in:
taekyoungc
2026-07-03 19:15:03 +09:00
parent f0d59b1694
commit 9b28d78a90

View File

@@ -11,27 +11,37 @@ if ($currentPath === 'bag/designated-shops') {
?> ?>
<?= view('components/print_header', ['printTitle' => $readOnly ? '지정판매소 조회 목록' : '지정판매소 목록']) ?> <?= view('components/print_header', ['printTitle' => $readOnly ? '지정판매소 조회 목록' : '지정판매소 목록']) ?>
<style> <style>
/* 목록 → 지정판매소 정보 아래 (가로 2열 없음) */ /* 목록 왼쪽 → 지정판매소 정보 오른쪽 (좌우 2열) */
.ds-split { .ds-split {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
gap: 0.5rem; gap: 0.5rem;
min-height: 0; min-height: 0;
flex: 1 1 auto; flex: 1 1 auto;
align-items: stretch;
} }
.ds-list-panel { .ds-list-panel {
flex: 0 1 auto; flex: 1 1 55%;
width: 100%; min-width: 0;
max-height: 42vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; min-height: 0;
border: 1px solid #ccc; border: 1px solid #ccc;
background: #fff; background: #fff;
/* 기본은 내용 높이만 차지(리스트를 10행 정도로 제한하고 아래 빈 공간 없이). */
align-self: flex-start;
}
/* 지정판매소 리스트: 헤더 + 약 10행만 보이고 나머지는 스크롤 */
.ds-list-scroll { max-height: 424px; }
.ds-list-panel thead th {
position: sticky;
top: 0;
background: #f8f9fa;
z-index: 2;
box-shadow: inset 0 -1px 0 #e5e7eb;
} }
.ds-detail-panel { .ds-detail-panel {
flex: 1 1 auto; flex: 1 1 45%;
width: 100%;
min-width: 0; min-width: 0;
min-height: 12rem; min-height: 12rem;
border: 1px solid #ccc; border: 1px solid #ccc;
@@ -39,6 +49,12 @@ if ($currentPath === 'bag/designated-shops') {
display: flex; display: flex;
flex-direction: column; 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 { .ds-panel-title {
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
@@ -222,21 +238,14 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
<div class="ds-split no-print mx-2 mb-2 mt-2 flex-1 min-h-0"> <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-list-panel">
<div class="ds-panel-title shrink-0">지정판매소 리스트</div> <div class="ds-panel-title shrink-0">지정판매소 리스트</div>
<div class="overflow-auto flex-1 min-h-0"> <div class="ds-list-scroll overflow-auto flex-1 min-h-0">
<table class="w-full text-[13px]"> <table class="w-full text-[13px]">
<thead> <thead>
<tr class="text-left text-[11px] font-semibold text-gray-500 border-b border-gray-200"> <tr class="text-left text-[11px] font-semibold text-gray-500 border-b border-gray-200">
<th class="py-2.5 px-2 w-14 text-left">번호</th> <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="py-2.5 px-2 w-24">구·군</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="py-2.5 px-2 w-24 text-left">지정일</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="py-2.5 px-2 w-24">구역</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>
<th class="py-2.5 px-2 ds-col-tight">대표자명</th>
<th class="py-2.5 px-2 ds-col-tight">상호명</th>
<th class="py-2.5 px-2 ds-col-zip text-left">우편번호</th>
<th class="py-2.5 px-2 text-left">주소</th>
<th class="py-2.5 px-2 w-28">사업자번호</th>
<th class="py-2.5 px-2 w-28">전화</th>
<th class="py-2.5 px-2 w-16 text-left">상태</th>
</tr> </tr>
</thead> </thead>
<tbody id="ds-list-body"> <tbody id="ds-list-body">
@@ -267,17 +276,15 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
$addrCombinedList = $addrMainList; $addrCombinedList = $addrMainList;
} }
?> ?>
<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"> <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-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-left font-mono text-gray-700"><?= esc($shortNo) ?></td>
<td class="py-2.5 px-2 text-gray-600"><?= esc($ggLabel) ?></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"><?= esc($zone) ?></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 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 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-left font-mono text-gray-700 ds-col-zip" title="<?= esc($zipList) ?>"><?= esc($zipList) ?></td>
<td class="py-2.5 px-2 text-gray-600 ds-col-addr-list" title="<?= esc($addrCombinedList) ?>"><?= esc($addrCombinedList) ?></td>
<td class="py-2.5 px-2 font-mono text-gray-700"><?= esc($row->ds_biz_no ?? '') ?></td>
<td class="py-2.5 px-2 font-mono text-gray-700"><?= esc($row->ds_tel ?? '') ?></td>
<td class="py-2.5 px-2 text-left"> <td class="py-2.5 px-2 text-left">
<?php if ($st === 1): ?> <?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> <span class="inline-block px-2 py-0.5 rounded-full text-[11px] font-medium bg-emerald-50 text-emerald-700">정상</span>
@@ -297,69 +304,89 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
<div class="ds-detail-inner" id="ds-detail-box"> <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> <p id="ds-detail-placeholder" class="text-sm text-gray-500 py-6 text-center">위 목록에서 행을 선택하세요.</p>
<div id="ds-detail-fields" class="hidden"> <div id="ds-detail-fields" class="hidden">
<div class="ds-detail-info-wrap"> <!-- 제목 왼쪽 · 내용 오른쪽 (라벨/값) 폼 형태 -->
<table class="w-full data-table text-sm" id="ds-detail-info-table" aria-label="지정판매소 상세"> <div class="ds-detail-form" id="ds-detail-info-table" aria-label="지정판매소 상세">
<thead> <div class="ds-row ds-row-wide">
<tr> <div class="ds-field-label">판매소번호</div>
<th>판매소번호</th> <div class="ds-field-value ds-value-shop-wide" data-ro="ds_shop_no">—</div>
<th class="ds-col-tight-head">상호명</th> </div>
<th>우편번호</th> <div class="ds-row ds-row-4-even">
<th>사업자번호</th> <div class="ds-field-label">상호명</div>
<th>일반전화</th> <div class="ds-field-value" data-ro="ds_name">—</div>
<th class="ds-col-tight-head">대표자명</th> <div class="ds-field-label">우편번호</div>
<th>이메일</th> <div class="ds-field-value" data-ro="ds_zip">—</div>
<th>업태</th> </div>
<th>업종</th> <div class="ds-row ds-row-4-even">
<th>지정일자</th> <div class="ds-field-label">사업자번호</div>
<th>지자체</th> <div class="ds-field-value" data-ro="ds_biz_no">—</div>
<th>도로명주소</th> <div class="ds-field-label">일반전화</div>
<th>지번주소</th> <div class="ds-field-value" data-ro="ds_tel">—</div>
<th>상세주소</th> </div>
<th>개인전화</th> <div class="ds-row ds-row-4-even">
<th>구·군</th> <div class="ds-field-label">대표자명</div>
<th>구역</th> <div class="ds-field-value" data-ro="ds_rep_name">—</div>
<th>가상계좌(은행)</th> <div class="ds-field-label">이메일</div>
<th>계좌번호</th> <div class="ds-field-value" data-ro="ds_email">—</div>
<th>종사업장번호</th> </div>
<th>변경일자</th> <div class="ds-row ds-row-wide">
<th>영업상태</th> <div class="ds-field-label">도로명주소</div>
<th>등록일시</th> <div class="ds-field-value ds-field-value-with-map">
<th>변경사유</th> <span class="ds-addr-text" data-ro="ds_addr">—</span>
<th class="no-print w-14 text-center">지도</th> <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>
</tr> </div>
</thead> </div>
<tbody> <div class="ds-row ds-row-wide">
<tr> <div class="ds-field-label">지번주소</div>
<td class="text-left" data-ro="ds_shop_no">—</td> <div class="ds-field-value" data-ro="ds_addr_jibun">—</div>
<td class="text-left ds-col-tight-cell" data-ro="ds_name">—</td> </div>
<td class="text-left" data-ro="ds_zip">—</td> <div class="ds-row ds-row-wide">
<td class="text-left" data-ro="ds_biz_no">—</td> <div class="ds-field-label">상세주소</div>
<td class="text-left" data-ro="ds_tel">—</td> <div class="ds-field-value" data-ro="ds_addr_detail">—</div>
<td class="text-left ds-col-tight-cell" data-ro="ds_rep_name">—</td> </div>
<td class="text-left" data-ro="ds_email">—</td> <div class="ds-row ds-row-wide">
<td class="text-left" data-ro="ds_biz_type">—</td> <div class="ds-field-label">개인전화</div>
<td class="text-left" data-ro="ds_biz_kind">—</td> <div class="ds-field-value" data-ro="ds_rep_phone">—</div>
<td class="text-left" data-ro="ds_designated_at">—</td> </div>
<td class="text-left" data-ro="lg_name">—</td> <div class="ds-row ds-row-4-even">
<td class="text-left min-w-[10rem]"><span data-ro="ds_addr">—</span></td> <div class="ds-field-label">지정일자</div>
<td class="text-left" data-ro="ds_addr_jibun">—</td> <div class="ds-field-value" data-ro="ds_designated_at">—</div>
<td class="text-left" data-ro="ds_addr_detail">—</td> <div class="ds-field-label">업태</div>
<td class="text-left" data-ro="ds_rep_phone">—</td> <div class="ds-field-value" data-ro="ds_biz_type">—</div>
<td class="text-left" data-ro="gugun_name">—</td> </div>
<td class="text-left" data-ro="ds_zone_code">—</td> <div class="ds-row ds-row-4-even">
<td class="text-left" data-ro="ds_va_bank">—</td> <div class="ds-field-label">업종</div>
<td class="text-left" data-ro="ds_va_account">—</td> <div class="ds-field-value" data-ro="ds_biz_kind">—</div>
<td class="text-left" data-ro="ds_branch_no">—</td> <div class="ds-field-label">구·군</div>
<td class="text-left" data-ro="ds_state_changed_at">—</td> <div class="ds-field-value" data-ro="gugun_name">—</div>
<td class="text-left" data-ro="state_label">—</td> </div>
<td class="text-left" data-ro="ds_regdate">—</td> <div class="ds-row ds-row-4-even">
<td class="text-left min-w-[8rem]" data-ro="ds_change_reason">—</td> <div class="ds-field-label">구역</div>
<td class="text-center no-print"> <div class="ds-field-value" data-ro="ds_zone_code">—</div>
<button type="button" class="border border-btn-print-border text-gray-700 px-2 py-0.5 rounded-sm text-xs hover:bg-gray-50" id="ds-ro-map-btn">지도</button> <div class="ds-field-label">종사업장번호</div>
</td> <div class="ds-field-value" data-ro="ds_branch_no">—</div>
</tr> </div>
</tbody> <div class="ds-row ds-row-4-even">
</table> <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> </div>
</div> </div>
@@ -469,6 +496,44 @@ $sc = $stateCounts ?? ['total' => 0, 1 => 0, 2 => 0, 3 => 0];
}); });
} }
// ── 컬럼 헤더 클릭 정렬 (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'); var mapBtnRo = document.getElementById('ds-ro-map-btn');
if (mapBtnRo) { if (mapBtnRo) {
mapBtnRo.addEventListener('click', function (ev) { mapBtnRo.addEventListener('click', function (ev) {