2026-06-08 13:32:53 +09:00
< ? php
declare ( strict_types = 1 );
/**
* 워크스페이스 ( 탭 ) 셸 — 크롬처럼 메뉴를 탭 ( iframe ) 으로 열어두고 작업 상태 유지 .
* 헤더 + 대메뉴 ( 클릭 ) + 좌측 사이드바 ( 소메뉴 ) + 탭바 + iframe 패널 .
* 탭은 세션 ( 이 셸이 열려 있는 동안 ) 만 유지된다 .
*/
helper ( 'admin' );
$gov = gov_portal_nav_context ( false );
$navPartial = [
'govNavItems' => $gov [ 'navItems' ],
'govNavJson' => $gov [ 'navJson' ],
'govActiveParentIdx' => $gov [ 'activeParentIdx' ],
'govCurrentPath' => gov_portal_nav_match_path ( $gov [ 'currentPath' ]),
'govDashboardAliases' => $gov [ 'dashboardAliases' ],
'govActiveChildHref' => '' ,
];
$mbLevel = ( int ) session () -> get ( 'mb_level' );
$mbName = ( string ) ( session () -> get ( 'mb_name' ) ? ? '담당자' );
$levelName = config ( \Config\Roles :: class ) -> getLevelName ( $mbLevel );
$isAdmin = ( $mbLevel === \Config\Roles :: LEVEL_SUPER_ADMIN || $mbLevel === \Config\Roles :: LEVEL_LOCAL_ADMIN );
$effectiveLgIdx = admin_effective_lg_idx ();
$lgLabel = '' ;
if ( $effectiveLgIdx ) {
$lgRow = model ( \App\Models\LocalGovernmentModel :: class ) -> find ( $effectiveLgIdx );
$lgLabel = $lgRow ? ( string ) $lgRow -> lg_name : '' ;
}
?>
<! DOCTYPE html >
< html lang = " ko " class = " gov-portal-html " >
< head >
< meta charset = " utf-8 " />
< meta content = " width=device-width, initial-scale=1.0 " name = " viewport " />
2026-06-09 14:43:24 +09:00
< title > 워크스페이스 · GBLS </ title >
2026-06-08 13:32:53 +09:00
< link href = " https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css " rel = " stylesheet " />
< link rel = " stylesheet " href = " https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css " />
< style >
< ? php include __DIR__ . '/../../home/_dashboard_gov_portal_brand_css.php' ; ?>
< ? php include __DIR__ . '/../../home/_dashboard_gov_portal_topnav_css.php' ; ?>
< ? php include __DIR__ . '/../../home/_dashboard_gov_portal_chrome_css.php' ; ?>
html , body { height : 100 % ; }
body . gov - portal - shell { height : 100 vh ; overflow : hidden ; }
. ws - main { display : flex ; flex - direction : column ; flex : 1 ; min - width : 0 ; }
2026-06-10 10:19:37 +09:00
. ws - topbar { display : flex ; align - items : stretch ; background : #e9eef5; border-bottom: 1px solid var(--border); }
. ws - tabbar { display : flex ; align - items : stretch ; gap : 2 px ; padding : 4 px 6 px 0 ; overflow - x : auto ; min - height : 36 px ; flex : 1 ; min - width : 0 ; }
2026-06-08 13:32:53 +09:00
. ws - tab { display : inline - flex ; align - items : center ; gap : . 4 rem ; max - width : 200 px ; padding : . 35 rem . 6 rem ; background : #f5f7fa; border: 1px solid var(--border); border-bottom: none; border-radius: 7px 7px 0 0; font-size: .78rem; color: #555; cursor: pointer; white-space: nowrap; }
. ws - tab . t - name { overflow : hidden ; text - overflow : ellipsis ; max - width : 150 px ; }
. ws - tab . active { background : #fff; color: var(--navy); font-weight: 700; box-shadow: 0 -2px 0 #007bff inset; }
2026-06-10 10:19:37 +09:00
. ws - tab . focused - tab { box - shadow : 0 - 2 px 0 #243a5e inset; }
2026-06-08 19:04:41 +09:00
. ws - tab . t - refresh , . ws - tab . t - close { width : 16 px ; height : 16 px ; line - height : 14 px ; text - align : center ; border - radius : 50 % ; color : #999; font-size: 12px; }
. ws - tab . t - refresh : hover { background : #dbeafe; color: #1d4ed8; }
2026-06-08 13:32:53 +09:00
. ws - tab . t - close : hover { background : #e2e8f0; color: #333; }
2026-06-10 10:19:37 +09:00
/* 분할 레이아웃 컨트롤 */
. ws - layout { display : flex ; align - items : center ; gap : 3 px ; padding : 4 px 8 px ; flex - shrink : 0 ; border - left : 1 px solid var ( -- border ); }
. ws - layout button { width : 30 px ; height : 26 px ; border : 1 px solid var ( -- border ); background : #f5f7fa; border-radius: 6px; color: #64748b; cursor: pointer; font-size: 12px; display: inline-flex; align-items: center; justify-content: center; }
. ws - layout button : hover { background : #e2e8f0; color: #334155; }
. ws - layout button . active { background : #243a5e; color: #fff; border-color: #243a5e; }
. ws - panels { flex : 1 ; position : relative ; min - height : 0 ; background : #cbd5e1; }
. ws - frame { position : absolute ; left : 0 ; top : 0 ; width : 100 % ; height : 100 % ; border : 0 ; display : none ; background : #fff; box-sizing: border-box; }
/* 분할 칸 헤더 */
. ws - slot - head { position : absolute ; display : none ; align - items : center ; gap : . 3 rem ; height : 28 px ; padding : 0 . 3 rem 0 . 6 rem ; background : #eef2f7; border-bottom: 1px solid var(--border); font-size: .72rem; color: #475569; box-sizing: border-box; cursor: pointer; z-index: 3; }
. ws - slot - head . focused { background : #dbeafe; color: var(--navy); box-shadow: inset 0 2px 0 #007bff; font-weight: 700; }
. ws - slot - head . sh - name { flex : 1 ; overflow : hidden ; text - overflow : ellipsis ; white - space : nowrap ; }
. ws - slot - head . sh - btn { width : 18 px ; height : 18 px ; line - height : 16 px ; text - align : center ; border - radius : 50 % ; color : #94a3b8; flex-shrink: 0; font-size: 12px; }
. ws - slot - head . sh - btn : hover { background : #fff; color: #334155; }
. ws - slot - empty { position : absolute ; display : none ; align - items : center ; justify - content : center ; background : #f8fafc; color: #94a3b8; font-size: .8rem; box-sizing: border-box; cursor: pointer; z-index: 1; text-align: center; padding: 1rem; }
/* 분할 구분선 드래그 핸들 */
. ws - gutter { position : absolute ; display : none ; z - index : 6 ; background : transparent ; }
. ws - gutter . v { width : 9 px ; height : 100 % ; top : 0 ; cursor : col - resize ; }
. ws - gutter . h { width : 100 % ; height : 9 px ; left : 0 ; cursor : row - resize ; }
. ws - gutter : hover { background : rgba ( 37 , 99 , 235 , . 25 ); }
/* 드래그 중 iframe 위에서도 마우스 이벤트를 받기 위한 오버레이 */
. ws - drag - overlay { position : absolute ; inset : 0 ; z - index : 50 ; display : none ; }
. 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; }
2026-06-11 17:26:36 +09:00
. ws - toast { position : fixed ; left : 50 % ; bottom : 26 px ; transform : translateX ( - 50 % ) translateY ( 10 px ); 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 ); }
2026-06-08 13:32:53 +09:00
</ style >
</ head >
< body class = " gov-portal-shell " >
< header class = " portal-header " >
< div class = " portal-header-inner " >
< ? = 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; " >
2026-06-11 17:26:36 +09:00
< 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 >
2026-06-08 13:32:53 +09:00
< span class = " user-line " >
< ? php if ( $lgLabel !== '' ) : ?> <strong><?= esc($lgLabel) ?></strong> · <?php endif; ?>
< ? = esc ( $levelName ) ?> · <?= esc($mbName) ?>님
</ span >
< ? php if ( $isAdmin ) : ?>
< a href = " <?= base_url('admin') ?> " title = " 관리자 페이지 " style = " display:inline-flex;align-items:center;gap:.3rem;padding:.25rem .6rem;border-radius:6px;background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.3);color:#fff;text-decoration:none;font-size:.75rem;font-weight:600; " >< i class = " fa-solid fa-gear " ></ i > 관리자 </ a >
< ? php endif ; ?>
< a href = " <?= base_url('logout') ?> " title = " 로그아웃 " style = " display:inline-flex;align-items:center;gap:.3rem;padding:.25rem .6rem;border-radius:6px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.22);color:#fff;text-decoration:none;font-size:.75rem;font-weight:600; " >< i class = " fa-solid fa-right-from-bracket " ></ i > 로그아웃 </ a >
</ div >
</ div >
</ header >
< div class = " layout " >
< ? = view ( 'home/_dashboard_gov_portal_sidebar' , $navPartial ) ?>
< main class = " ws-main " >
2026-06-10 10:19:37 +09:00
< div class = " ws-topbar " >
< div class = " ws-tabbar " id = " wsTabBar " role = " tablist " ></ div >
< div class = " ws-layout " id = " wsLayout " >
< button type = " button " data - mode = " single " title = " 1분할 (한 화면) " >< i class = " fa-regular fa-square " ></ i ></ button >
< 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 >
2026-06-11 17:26:36 +09:00
< 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 >
2026-06-10 10:19:37 +09:00
</ div >
</ div >
2026-06-11 17:26:36 +09:00
< div class = " ws-toast " id = " wsToast " role = " status " aria - live = " polite " ></ div >
2026-06-08 13:32:53 +09:00
< 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 >
< div > 왼쪽 메뉴를 클릭하면 탭으로 열립니다 . 여러 화면을 열어두고 전환해도 작업 내용이 유지됩니다 .</ div >
</ div >
</ div >
</ main >
</ div >
< ? = view ( 'home/_dashboard_gov_portal_nav_script_base' , $navPartial ) ?>
< script >
( function () {
var bar = document . getElementById ( 'wsTabBar' );
var panels = document . getElementById ( 'wsPanels' );
var empty = document . getElementById ( 'wsEmpty' );
var tabs = {}; // id -> {url,title,frame,btn}
var order = [];
var MAX = 12 ;
2026-06-10 10:19:37 +09:00
// 분할 레이아웃 상태
var layout = 'single' ; // single | lr | tb | quad
var slots = [ null ]; // 칸별로 배치된 tab id (길이 = 칸 수)
var focused = 0 ; // 포커스된 칸 인덱스
2026-06-11 17:26:36 +09:00
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 );
}
2026-06-08 13:32:53 +09:00
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 ; }
catch ( e ) { return u + ( u . indexOf ( '?' ) >= 0 ? '&' : '?' ) + 'embed=1' ; }
}
2026-06-10 10:19:37 +09:00
// 분할 비율 (좌 컬럼 폭 / 위 행 높이), 드래그로 조절
var vRatio = 0.5 , hRatio = 0.5 ;
var RATIO_MIN = 0.12 , RATIO_MAX = 0.88 ;
function pct ( r ) { return ( r * 100 ) . toFixed ( 3 ) + '%' ; }
// 레이아웃별 칸 사각형 (2px 간격, 비율 반영) — left/top/width/height CSS 문자열
function rectsFor ( mode ) {
var Lw = 'calc(' + pct ( vRatio ) + ' - 1px)' ; // 좌 컬럼 폭
var Rl = 'calc(' + pct ( vRatio ) + ' + 1px)' ; // 우 컬럼 시작
var Rw = 'calc(' + pct ( 1 - vRatio ) + ' - 1px)' ; // 우 컬럼 폭
var Th = 'calc(' + pct ( hRatio ) + ' - 1px)' ; // 위 행 높이
var Bt = 'calc(' + pct ( hRatio ) + ' + 1px)' ; // 아래 행 시작
var Bh = 'calc(' + pct ( 1 - hRatio ) + ' - 1px)' ; // 아래 행 높이
if ( mode === 'lr' ) return [
{ l : '0' , t : '0' , w : Lw , h : '100%' },
{ l : Rl , t : '0' , w : Rw , h : '100%' }
];
if ( mode === 'tb' ) return [
{ l : '0' , t : '0' , w : '100%' , h : Th },
{ l : '0' , t : Bt , w : '100%' , h : Bh }
];
if ( mode === 'quad' ) return [
{ l : '0' , t : '0' , w : Lw , h : Th },
{ l : Rl , t : '0' , w : Rw , h : Th },
{ l : '0' , t : Bt , w : Lw , h : Bh },
{ l : Rl , t : Bt , w : Rw , h : Bh }
];
return [{ l : '0' , t : '0' , w : '100%' , h : '100%' }]; // single
}
2026-06-08 19:52:53 +09:00
var STORE_KEY = 'jrj_ws_tabs' ;
2026-06-09 14:43:24 +09:00
var WS_OWNER = '<?= (string) (session()->get(' mb_idx ') ?? ' ') ?>' ; // 탭 저장 소유자(로그인 사용자) 식별
2026-06-08 19:52:53 +09:00
function persist () {
try {
2026-06-10 10:19:37 +09:00
sessionStorage . setItem ( STORE_KEY , JSON . stringify ({
2026-06-09 14:43:24 +09:00
owner : WS_OWNER ,
2026-06-08 19:52:53 +09:00
tabs : order . map ( function ( id ) { return { url : tabs [ id ] . url , title : tabs [ id ] . title }; }),
2026-06-10 10:19:37 +09:00
layout : layout ,
focused : focused ,
vRatio : vRatio ,
hRatio : hRatio ,
slots : slots . map ( function ( id ) { return ( id && tabs [ id ]) ? tabs [ id ] . url : null ; })
}));
2026-06-08 19:52:53 +09:00
} catch ( e ) {}
}
2026-06-10 10:19:37 +09:00
// 분할 칸 헤더·빈칸 안내 요소 4개 미리 생성
var slotHeads = [], slotEmpties = [];
for ( var si = 0 ; si < 4 ; si ++ ) {
( function ( idx ) {
var h = document . createElement ( 'div' );
h . className = 'ws-slot-head' ;
var nm = document . createElement ( 'span' ); nm . className = 'sh-name' ;
var rl = document . createElement ( 'span' ); rl . className = 'sh-btn sh-reload' ; rl . textContent = '↻' ; rl . title = '이 칸 새로고침' ;
var cl = document . createElement ( 'span' ); cl . className = 'sh-btn sh-clear' ; cl . textContent = '× ' ; cl . title = '이 칸 비우기' ;
h . appendChild ( nm ); h . appendChild ( rl ); h . appendChild ( cl );
h . addEventListener ( 'click' , function ( e ) {
if ( e . target === rl ) { var sid = slots [ idx ]; if ( sid ) reloadTab ( sid ); return ; }
if ( e . target === cl ) { slots [ idx ] = null ; render (); return ; }
focusSlot ( idx );
});
panels . appendChild ( h );
var emp = document . createElement ( 'div' );
emp . className = 'ws-slot-empty' ;
emp . textContent = '+ 위 탭에서 이 칸에 표시할 화면을 선택하세요' ;
emp . addEventListener ( 'click' , function () { focusSlot ( idx ); });
panels . appendChild ( emp );
slotHeads . push ( h ); slotEmpties . push ( emp );
})( si );
}
// 분할 구분선(드래그 핸들) + 드래그용 투명 오버레이
var vGutter = document . createElement ( 'div' ); vGutter . className = 'ws-gutter v' ;
var hGutter = document . createElement ( 'div' ); hGutter . className = 'ws-gutter h' ;
var dragOverlay = document . createElement ( 'div' ); dragOverlay . className = 'ws-drag-overlay' ;
panels . appendChild ( vGutter ); panels . appendChild ( hGutter ); panels . appendChild ( dragOverlay );
function clampRatio ( r ) { return Math . min ( RATIO_MAX , Math . max ( RATIO_MIN , r )); }
function startDrag ( axis ) {
dragOverlay . className = 'ws-drag-overlay ' + axis ;
dragOverlay . style . display = 'block' ;
function move ( ev ) {
var rect = panels . getBoundingClientRect ();
if ( axis === 'v' ) vRatio = clampRatio (( ev . clientX - rect . left ) / rect . width );
else hRatio = clampRatio (( ev . clientY - rect . top ) / rect . height );
render ();
}
function up () {
document . removeEventListener ( 'mousemove' , move );
document . removeEventListener ( 'mouseup' , up );
dragOverlay . style . display = 'none' ;
persist ();
}
document . addEventListener ( 'mousemove' , move );
document . addEventListener ( 'mouseup' , up );
}
vGutter . addEventListener ( 'mousedown' , function ( e ) { e . preventDefault (); startDrag ( 'v' ); });
hGutter . addEventListener ( 'mousedown' , function ( e ) { e . preventDefault (); startDrag ( 'h' ); });
// 더블클릭 시 50%로 초기화
vGutter . addEventListener ( 'dblclick' , function () { vRatio = 0.5 ; render (); persist (); });
hGutter . addEventListener ( 'dblclick' , function () { hRatio = 0.5 ; render (); persist (); });
var layoutBtns = Array . prototype . slice . call ( document . querySelectorAll ( '#wsLayout button' ));
// CSS 길이 가감 (calc(0 + 28px) 같은 무효 표현 방지)
function addPx ( val , px ) { return val === '0' ? ( px + 'px' ) : ( 'calc(' + val + ' + ' + px + 'px)' ); }
function subPx ( val , px ) { return 'calc(' + val + ' - ' + px + 'px)' ; }
function render () {
var rects = rectsFor ( layout );
var n = rects . length ;
var split = layout !== 'single' ;
// single 모드에서 포커스 칸이 비면 가장 최근 탭으로 자동 채움
if ( ! split && ! ( slots [ 0 ] && tabs [ slots [ 0 ]]) && order . length ) {
slots [ 0 ] = order [ order . length - 1 ];
}
if ( focused >= n ) focused = n - 1 ;
var shown = {};
for ( var i = 0 ; i < 4 ; i ++ ) {
var head = slotHeads [ i ], emp = slotEmpties [ i ];
if ( i >= n ) { head . style . display = 'none' ; emp . style . display = 'none' ; continue ; }
var r = rects [ i ];
var bodyTop = split ? addPx ( r . t , 28 ) : r . t ;
var bodyH = split ? subPx ( r . h , 28 ) : r . h ;
if ( split ) {
head . style . display = 'flex' ;
head . style . left = r . l ; head . style . top = r . t ; head . style . width = r . w ;
head . classList . toggle ( 'focused' , i === focused );
var sid = slots [ i ];
head . querySelector ( '.sh-name' ) . textContent = ( sid && tabs [ sid ]) ? tabs [ sid ] . title : '비어 있음' ;
} else {
head . style . display = 'none' ;
}
var id = slots [ i ];
if ( id && tabs [ id ]) {
shown [ id ] = true ;
var f = tabs [ id ] . frame ;
f . style . left = r . l ; f . style . top = bodyTop ; f . style . width = r . w ; f . style . height = bodyH ;
f . style . display = 'block' ;
emp . style . display = 'none' ;
} else if ( split ) {
emp . style . display = 'flex' ;
emp . style . left = r . l ; emp . style . top = bodyTop ; emp . style . width = r . w ; emp . style . height = bodyH ;
} else {
emp . style . display = 'none' ;
}
}
// 어느 칸에도 없는 iframe 은 숨김 / 포커스 칸 프레임에 active 표시
Object . keys ( tabs ) . forEach ( function ( k ) {
if ( ! shown [ k ]) tabs [ k ] . frame . style . display = 'none' ;
tabs [ k ] . frame . classList . toggle ( 'active' , k === slots [ focused ]);
});
// 탭 버튼 강조 (표시 중 = active, 포커스 칸 = focused-tab)
2026-06-08 13:32:53 +09:00
Object . keys ( tabs ) . forEach ( function ( k ) {
2026-06-10 10:19:37 +09:00
var pos = slots . indexOf ( k );
tabs [ k ] . btn . classList . toggle ( 'active' , pos >= 0 && pos < n );
tabs [ k ] . btn . classList . toggle ( 'focused-tab' , split && slots [ focused ] === k );
2026-06-08 13:32:53 +09:00
});
2026-06-10 10:19:37 +09:00
// 분할 구분선 위치/표시
if ( layout === 'lr' || layout === 'quad' ) { vGutter . style . display = 'block' ; vGutter . style . left = 'calc(' + pct ( vRatio ) + ' - 4px)' ; }
else { vGutter . style . display = 'none' ; }
if ( layout === 'tb' || layout === 'quad' ) { hGutter . style . display = 'block' ; hGutter . style . top = 'calc(' + pct ( hRatio ) + ' - 4px)' ; }
else { hGutter . style . display = 'none' ; }
// 레이아웃 버튼 강조
layoutBtns . forEach ( function ( b ) { b . classList . toggle ( 'active' , b . getAttribute ( 'data-mode' ) === layout ); });
// 전체 빈 상태
if ( empty ) empty . style . display = order . length === 0 ? 'flex' : 'none' ;
// 포커스 칸 화면에 맞춰 좌측 사이드바 동기화
var fid = slots [ focused ];
try { if ( window . govPortalNav ) window . govPortalNav . syncByUrl ( fid && tabs [ fid ] ? tabs [ fid ] . url : '