화면별 매뉴얼·이 화면 설명 버튼·탭 새로고침/경고·최근방문 기록 보강.

- 매뉴얼: 화면(소메뉴)별 용어·버튼·필드 설명으로 확장 + 기본정보 페이지 신규,
  개요에 용어 사전 추가 (종량제 지식 없는 사용자 대상)
- "이 화면 설명" 버튼: 화면 경로→매뉴얼 매핑(Config\Manual::screenHelp,
  manual_help_url_for_path). 워크스페이스 탭은 새 탭으로, 직접 페이지는 새 창으로
- 워크스페이스: 개별 탭 새로고침(↻) 버튼, 탭 2개 이상일 때만 새로고침 경고,
  사이드바 하단 링크(매뉴얼 등)도 탭으로 열기
- 임베드: 탭 내 링크/폼 embed 유지(중첩 헤더 방지), 매뉴얼 리다이렉트 embed 유지
- 사이드바 하단: 종합그래프 → 사용자 매뉴얼 링크
- 최근 방문 메뉴: embed 페이지에도 방문 기록, 대시보드는 storage 이벤트로 실시간 갱신
- E2E qa_sweep 추가(주요 화면 콘솔/오버레이/매뉴얼/도움말 매핑 점검)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
taekyoungc
2026-06-08 19:04:41 +09:00
parent c15e01bfa7
commit e8d58b5837
15 changed files with 578 additions and 162 deletions

View File

@@ -9,6 +9,8 @@ declare(strict_types=1);
* @var bool $bare true면 본문을 카드 래퍼 없이 그대로 출력(대시보드용)
*/
$bare = ! empty($bare);
helper('admin');
$helpUrl = function_exists('manual_help_url_for_path') ? manual_help_url_for_path() : '';
?>
<!DOCTYPE html>
<html lang="ko">
@@ -50,8 +52,15 @@ tailwind.config = {
</head>
<body>
<div style="padding: 0.875rem 1rem 1.25rem;">
<?php if (! empty($title)): ?>
<h1 class="embed-titlebar"><i class="fa-solid fa-folder-open" style="color:#007bff;"></i><?= esc($title) ?></h1>
<?php if (! empty($title) || $helpUrl !== ''): ?>
<div style="display:flex;align-items:center;justify-content:space-between;gap:.5rem;margin:0 0 .75rem;">
<h1 class="embed-titlebar" style="margin:0;"><?php if (! empty($title)): ?><i class="fa-solid fa-folder-open" style="color:#007bff;"></i><?= esc($title) ?><?php endif; ?></h1>
<?php if ($helpUrl !== ''): ?>
<a href="<?= esc($helpUrl, 'attr') ?>" class="embed-help no-print" style="display:inline-flex;align-items:center;gap:.3rem;padding:.3rem .6rem;border-radius:6px;background:#eef2f7;border:1px solid #dde4ec;color:#1a2b4b;text-decoration:none;font-size:.75rem;font-weight:600;white-space:nowrap;">
<i class="fa-regular fa-circle-question"></i> 이 화면 설명
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (session()->getFlashdata('success')): ?>
<div class="embed-flash ok"><?= esc(session()->getFlashdata('success')) ?></div>
@@ -73,6 +82,61 @@ tailwind.config = {
if (window.top !== window.self && /\/login(\/|$)/.test(location.pathname)) {
try { window.top.location.href = location.href; } catch (e) {}
}
// 방문한 업무 메뉴 경로 기록 (대시보드 "최근 방문 메뉴"용). localStorage 는 동일 출처 탭과 공유된다.
try {
var rp = (location.pathname || '').replace(/\/+$/, '') || '/';
if (rp !== '/' && !/\/login|\/logout|\/register|\/manual/.test(rp)) {
var RK = 'jrj_recent_menus';
var ra = JSON.parse(localStorage.getItem(RK) || '[]');
if (!Array.isArray(ra)) ra = [];
ra = ra.filter(function (x) { return x && x.p && x.p !== rp; });
ra.unshift({ p: rp, t: Date.now() });
localStorage.setItem(RK, JSON.stringify(ra.slice(0, 12)));
}
} catch (e) {}
// 탭(iframe) 안에서의 링크·폼 이동은 항상 embed 유지 → 중첩 헤더/사이드바 방지
function withEmbed(href) {
try {
var u = new URL(href, location.href);
if (u.origin !== location.origin) return null;
if (u.searchParams.get('embed') === '1') return null;
u.searchParams.set('embed', '1');
return u.href;
} catch (e) { return null; }
}
// "이 화면 설명" → 워크스페이스 새 탭으로 매뉴얼 열기(없으면 새 창)
document.addEventListener('click', function (e) {
var h = e.target.closest ? e.target.closest('a.embed-help') : null;
if (!h) return;
e.preventDefault(); e.stopPropagation();
var url = h.getAttribute('href');
try {
if (window.parent && window.parent !== window && typeof window.parent.wsOpenTab === 'function') {
window.parent.wsOpenTab(url, '도움말'); return;
}
} catch (err) {}
window.open(url, '_blank');
}, true);
document.addEventListener('click', function (e) {
var a = e.target.closest ? e.target.closest('a[href]') : null;
if (!a || a.classList.contains('embed-help')) return;
var t = (a.getAttribute('target') || '').toLowerCase();
if (t && t !== '_self') return;
if (a.hasAttribute('download')) return;
var href = a.getAttribute('href') || '';
if (!href || href.charAt(0) === '#' || /^(javascript:|mailto:|tel:|data:)/i.test(href)) return;
var ne = withEmbed(href);
if (ne) a.setAttribute('href', ne);
}, true);
document.addEventListener('submit', function (e) {
var f = e.target;
if (!f || f.tagName !== 'FORM') return;
var ne = withEmbed(f.getAttribute('action') || location.href);
if (ne) f.setAttribute('action', ne);
}, true);
// 표 '번호' 컬럼 역순 채번 (사이트 레이아웃과 동일)
var run = function () {
document.querySelectorAll('table').forEach(function (table) {