feat: 대시보드 바로가기 새 탭 열기 + 탭 새로고침 시각 피드백

- 업무 현황의 "자주 가는 화면"·"최근 방문 메뉴"·메뉴검색 결과 클릭 시
  워크스페이스 새 탭으로 열기(부모 wsOpenTab 호출, 밖이면 화면 이동 폴백)
- 탭 새로고침(↻): 아이콘 회전 + 화면 잠깐 페이드 후 복구로 새로고침 확인 가능하게

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
taekyoungc
2026-06-13 23:53:22 +09:00
parent fd3da428ab
commit 287691328e
2 changed files with 36 additions and 9 deletions

View File

@@ -129,7 +129,7 @@ $donutCss = $donutStops !== [] ? implode(', ', $donutStops) : '#e5e7eb 0% 100%';
}
var html = '<p class="text-[11px] text-white/70 mb-1.5"><i class="fa-regular fa-clock mr-1"></i>최근 방문 메뉴</p>';
r.forEach(function (m) {
html += '<a href="' + m.url + '" class="block text-[12px] px-2 py-1.5 rounded bg-white/12 hover:bg-white/25 mb-1 truncate" title="' + esc(m.name) + '">' + esc(m.name) +
html += '<a href="' + m.url + '" data-title="' + esc(m.name) + '" class="js-tab-link block text-[12px] px-2 py-1.5 rounded bg-white/12 hover:bg-white/25 mb-1 truncate" title="' + esc(m.name) + '">' + esc(m.name) +
(m.parent ? ' <span class="text-white/55">· ' + esc(m.parent) + '</span>' : '') + '</a>';
});
box.innerHTML = html;
@@ -160,7 +160,28 @@ $donutCss = $donutStops !== [] ? implode(', ', $donutStops) : '#e5e7eb 0% 100%';
list.classList.remove('hidden');
}
function go(url) { if (url) window.location.href = url; }
// 워크스페이스(부모) 안이면 새 탭으로 열기, 아니면 현재 화면 이동
function openInTab(url, name) {
try {
if (window.parent && window.parent !== window && typeof window.parent.wsOpenTab === 'function') {
window.parent.wsOpenTab(url, name || '');
return true;
}
} catch (e) {}
return false;
}
function go(url, name) { if (!openInTab(url, name) && url) window.location.href = url; }
// 자주 가는 화면·최근 방문 메뉴 링크 → 새 탭으로 열기
document.addEventListener('click', function (e) {
var a = e.target.closest ? e.target.closest('a.js-tab-link') : null;
if (!a) return;
var url = a.getAttribute('href');
if (!url || url.charAt(0) === '#') return;
if (openInTab(url, a.getAttribute('data-title') || (a.textContent || '').trim())) {
e.preventDefault(); e.stopPropagation();
}
}, true);
function highlight() {
Array.prototype.forEach.call(list.children, function (li, i) {
@@ -178,20 +199,20 @@ $donutCss = $donutStops !== [] ? implode(', ', $donutStops) : '#e5e7eb 0% 100%';
if (e.key === 'Enter') {
e.preventDefault();
var q = input.value.trim();
if (active >= 0 && current[active]) { go(current[active].url); return; }
if (active >= 0 && current[active]) { go(current[active].url, current[active].name); return; }
// 전체 이름 정확히 일치 우선
var exact = FLAT.filter(function (m) { return norm(m.name) === norm(q); });
if (exact.length) { go(exact[0].url); return; }
if (current.length) { go(current[0].url); return; }
if (exact.length) { go(exact[0].url, exact[0].name); return; }
if (current.length) { go(current[0].url, current[0].name); return; }
var any = matches(q);
if (any.length) { go(any[0].url); return; }
if (any.length) { go(any[0].url, any[0].name); return; }
alert('일치하는 메뉴가 없습니다.');
}
});
list.addEventListener('mousedown', function (e) {
var li = e.target.closest('li[data-url]');
if (li) { e.preventDefault(); go(li.getAttribute('data-url')); }
if (li) { e.preventDefault(); var i = +li.getAttribute('data-i'); go(li.getAttribute('data-url'), current[i] && current[i].name); }
});
document.addEventListener('click', function (e) {
@@ -279,7 +300,7 @@ $donutCss = $donutStops !== [] ? implode(', ', $donutStops) : '#e5e7eb 0% 100%';
];
foreach ($links as [$path, $label, $desc, $icon, $c]):
?>
<a href="<?= base_url($path) ?>" class="group flex items-center gap-3 px-3 py-2 rounded border border-gray-200 hover:border-blue-500 hover:bg-blue-50/40 transition">
<a href="<?= base_url($path) ?>" data-title="<?= esc($label, 'attr') ?>" class="js-tab-link group flex items-center gap-3 px-3 py-2 rounded border border-gray-200 hover:border-blue-500 hover:bg-blue-50/40 transition">
<div class="h-8 w-8 rounded-full bg-<?= $c ?>-50 text-<?= $c ?>-600 flex items-center justify-center shrink-0">
<i class="fa-solid <?= $icon ?>"></i>
</div>