style: 표/패널 UI 전면 통일 + 화면설명 드로어·글씨크기·탭 개선

표 디자인
- 모든 표를 가벼운 스타일로 통일(.data-table 경량화: 작은 회색 헤더·연한 구분선·hover)
- 표/패널 바깥 테두리 둥글게(rounded-lg) 일괄 적용, 표 래퍼에 패딩 카드(p-4) 통일
- 표 헤더·데이터 정렬을 전 화면 좌측 기준으로 통일
  - .data-table th/td text-align:left (전역), 흩어진 center/right 정렬 정리
  - 재디자인 Tailwind 표(포장단위·단가·기본코드·담당자·업체·판매대행소·무료대상자·지정판매소)도 셀 좌측화
- 기본정보관리 등 나머지 소메뉴 표를 기본 코드 관리 스타일(가벼운 표·상태 pill)로 재디자인

워크스페이스/공통
- "이 화면 설명" → 새 탭 대신 우측 드로어 팝업(현재 화면과 동시에 보기, Esc·드래그 폭조절)
- 상단바 글씨 크기 조절(A−/A+), 작업 내용에 zoom 적용
- 탭 최대치 도달 시 자동 삭제 대신 안내 토스트, "모두 닫기"(업무 현황 탭은 보존)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
taekyoungc
2026-06-11 17:26:36 +09:00
parent 912ffdbe23
commit a8afaf4af2
108 changed files with 1198 additions and 965 deletions

View File

@@ -76,6 +76,8 @@ if ($effectiveLgIdx) {
.ws-drag-overlay.v { cursor: col-resize; }
.ws-drag-overlay.h { cursor: row-resize; }
.ws-empty { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #94a3b8; font-size: .9rem; gap: .5rem; z-index: 2; background: #f0f4f8; }
.ws-toast { position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(10px); background: #243a5e; color: #fff; padding: .6rem 1rem; border-radius: 8px; font-size: .8rem; font-weight: 600; box-shadow: 0 8px 22px rgba(0,0,0,.25); opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s; z-index: 10000; max-width: 80vw; text-align: center; }
.ws-toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
</style>
</head>
<body class="gov-portal-shell">
@@ -84,6 +86,11 @@ if ($effectiveLgIdx) {
<?= view('home/_dashboard_gov_portal_brand', ['brandHref' => base_url('workspace')]) ?>
<?= view('home/_dashboard_gov_portal_topnav_click', $navPartial) ?>
<div class="portal-header-utils" style="display:flex;align-items:center;gap:.5rem;">
<div class="ws-fontctl" title="글씨 크기 조절" style="display:inline-flex;align-items:center;gap:2px;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.25);border-radius:6px;padding:1px;">
<button type="button" id="wsFontMinus" title="글씨 작게" style="width:24px;height:22px;border:0;background:transparent;color:#fff;cursor:pointer;font-size:11px;line-height:1;border-radius:5px;">A</button>
<span id="wsFontPct" style="min-width:34px;text-align:center;color:#fff;font-size:.68rem;font-weight:600;">100%</span>
<button type="button" id="wsFontPlus" title="글씨 크게" style="width:24px;height:22px;border:0;background:transparent;color:#fff;cursor:pointer;font-size:14px;line-height:1;border-radius:5px;">A+</button>
</div>
<span class="user-line">
<?php if ($lgLabel !== ''): ?><strong><?= esc($lgLabel) ?></strong> · <?php endif; ?>
<?= esc($levelName) ?> · <?= esc($mbName) ?>님
@@ -107,8 +114,10 @@ if ($effectiveLgIdx) {
<button type="button" data-mode="lr" title="2분할 (좌우)"><i class="fa-solid fa-table-columns"></i></button>
<button type="button" data-mode="tb" title="2분할 (상하)"><i class="fa-solid fa-table-columns fa-rotate-90"></i></button>
<button type="button" data-mode="quad" title="4분할"><i class="fa-solid fa-table-cells-large"></i></button>
<button type="button" id="wsCloseAll" title="모든 탭 닫기" style="margin-left:6px;width:auto;padding:0 8px;gap:4px;"><i class="fa-regular fa-rectangle-xmark"></i><span style="font-size:11px;">모두 닫기</span></button>
</div>
</div>
<div class="ws-toast" id="wsToast" role="status" aria-live="polite"></div>
<div class="ws-panels" id="wsPanels">
<div class="ws-empty" id="wsEmpty">
<i class="fa-regular fa-window-restore" style="font-size:1.6rem;opacity:.5;"></i>
@@ -133,6 +142,15 @@ if ($effectiveLgIdx) {
var slots = [null]; // 칸별로 배치된 tab id (길이 = 칸 수)
var focused = 0; // 포커스된 칸 인덱스
var toastEl = document.getElementById('wsToast'), toastTimer = null;
function showToast(msg) {
if (!toastEl) return;
toastEl.textContent = msg;
toastEl.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(function () { toastEl.classList.remove('show'); }, 2800);
}
function norm(u) { try { var a = new URL(u, location.origin); return a.pathname + a.search; } catch (e) { return u; } }
function withEmbed(u) {
try { var a = new URL(u, location.origin); a.searchParams.set('embed', '1'); return a.pathname + a.search; }
@@ -342,6 +360,20 @@ if ($effectiveLgIdx) {
b.addEventListener('click', function () { setLayout(b.getAttribute('data-mode')); });
});
// 모든 탭 닫기 — 기본 "업무 현황"(대시보드) 탭은 남긴다
var DASH_URL = '<?= base_url('/') ?>';
function closeAllTabs() {
var dashId = norm(DASH_URL);
var others = order.filter(function (id) { return id !== dashId; });
if (!others.length) return; // 업무 현황만 있으면 닫을 것 없음
if (!window.confirm('업무 현황을 제외한 모든 탭(' + others.length + '개)을 닫을까요?')) return;
others.forEach(function (id) { closeTab(id); });
if (!tabs[dashId]) openTab(DASH_URL, '업무 현황'); else placeInFocused(dashId);
showToast('업무 현황만 남기고 모두 닫았습니다.');
}
var closeAllBtn = document.getElementById('wsCloseAll');
if (closeAllBtn) closeAllBtn.addEventListener('click', closeAllTabs);
function reloadTab(id) {
var t = tabs[id];
if (!t) return;
@@ -362,7 +394,11 @@ if ($effectiveLgIdx) {
function openTab(url, title, opts) {
var id = norm(url);
if (tabs[id]) { if (!(opts && opts.noFocus)) placeInFocused(id); return; }
if (order.length >= MAX) { closeTab(order[0]); } // 오래된 탭 정리
// 최대치 도달 시: 자동 삭제하지 않고 안내만 (복원 중에는 안내 생략)
if (order.length >= MAX) {
if (!(opts && opts.noFocus)) showToast('탭은 최대 ' + MAX + '개까지 열 수 있습니다. 사용하지 않는 탭을 닫아 주세요.');
return;
}
var frame = document.createElement('iframe');
frame.className = 'ws-frame';
frame.src = withEmbed(url);
@@ -430,6 +466,22 @@ if ($effectiveLgIdx) {
}
document.addEventListener('keydown', handleShortcut);
// 글씨 크기 조절 — 각 탭(iframe) 내용에 zoom 적용. localStorage 공유 + storage 이벤트로 새 탭/실시간 반영.
var FONT_KEY = 'jrj_font_scale';
function curFontScale() { var s = parseInt(localStorage.getItem(FONT_KEY) || '100', 10); return (s >= 70 && s <= 150) ? s : 100; }
function setFontScale(s) {
s = Math.min(150, Math.max(70, s));
try { localStorage.setItem(FONT_KEY, String(s)); } catch (e) {}
var pct = document.getElementById('wsFontPct'); if (pct) pct.textContent = s + '%';
Object.keys(tabs).forEach(function (k) { try { tabs[k].frame.contentDocument.documentElement.style.zoom = (s / 100); } catch (e) {} });
}
(function () {
var pct = document.getElementById('wsFontPct'); if (pct) pct.textContent = curFontScale() + '%';
var plus = document.getElementById('wsFontPlus'), minus = document.getElementById('wsFontMinus');
if (plus) plus.addEventListener('click', function () { setFontScale(curFontScale() + 10); });
if (minus) minus.addEventListener('click', function () { setFontScale(curFontScale() - 10); });
})();
// 좌측 사이드바 소메뉴 클릭 → 포커스 칸에 열기
document.querySelector('.sidebar').addEventListener('click', function (e) {
var a = e.target.closest('a[href]');