워크스페이스(탭) 도입 — 로그인 후 기본 화면을 탭 작업공간으로.

- /workspace: 헤더+사이드바+탭바+iframe 패널. 메뉴 클릭=탭 열기,
  전환해도 폼·스크롤·조회결과 등 작업 상태 유지(세션 동안)
- 로그인 후 / = 워크스페이스(첫 탭=대시보드). iframe 내부는 임베드 렌더
- 임베드 레이아웃(bag/layout/embed): 헤더·사이드바 없이 본문만
- 임베드 판정: ?embed=1 또는 Sec-Fetch-Dest=iframe (iframe 내 링크·폼·
  리다이렉트까지 중첩 크롬 없이 처리)
- iframe 안 세션만료 시 상위 창을 로그인으로 전환(auth/_shell)
- 포털 헤더에 워크스페이스 진입 링크, E2E(workspace.spec) 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
taekyoungc
2026-06-08 13:32:53 +09:00
parent 600a79788e
commit c15e01bfa7
10 changed files with 381 additions and 14 deletions

View File

@@ -214,8 +214,10 @@ class Bag extends BaseController
private function render(string $title, string $viewFile, array $data = []): string
{
// 사이트 업무 페이지 공통 셸: gov-portal 디자인(헤더+대메뉴+클릭형 좌측 사이드바).
return view('bag/layout/portal', [
// /workspace 탭(iframe) 안에서는 임베드 레이아웃(헤더·사이드바 없이 본문만).
$layout = $this->isEmbeddedRequest() ? 'bag/layout/embed' : 'bag/layout/portal';
return view($layout, [
'title' => $title,
'content' => view($viewFile, $data),
]);

View File

@@ -48,6 +48,20 @@ abstract class BaseController extends Controller
*
* @param array<string, mixed> $contentData
*/
/**
* 워크스페이스 탭(iframe) 안에서 열린 요청인지. ?embed=1 또는 Sec-Fetch-Dest=iframe.
* iframe 내 링크 이동·폼 전송·리다이렉트까지 모두 임베드로 처리되도록 헤더로도 판정한다.
*/
protected function isEmbeddedRequest(): bool
{
if ($this->request->getGet('embed') !== null) {
return true;
}
$dest = strtolower(trim((string) $this->request->getHeaderLine('Sec-Fetch-Dest')));
return $dest === 'iframe' || $dest === 'frame';
}
protected function renderWorkPage(string $title, string $contentView, array $contentData = []): string
{
$content = view($contentView, $contentData);
@@ -61,8 +75,8 @@ abstract class BaseController extends Controller
$path = substr($path, strlen('index.php/'));
}
if ($path === 'bag' || str_starts_with($path, 'bag/')) {
// 사이트 업무 페이지: gov-portal 디자인 셸 적용
return view('bag/layout/portal', [
// /workspace 탭(iframe) 안에서는 임베드 레이아웃, 아니면 gov-portal
return view($this->isEmbeddedRequest() ? 'bag/layout/embed' : 'bag/layout/portal', [
'title' => $title,
'content' => $content,
]);

View File

@@ -10,19 +10,37 @@ class Home extends BaseController
public function index()
{
if (session()->get('logged_in')) {
// 메인(/) — gov-portal 디자인 셸 + 종량제 실데이터 대시보드.
helper('admin');
return view('bag/layout/portal', [
'title' => '업무 현황',
'bare' => true,
'content' => view('bag/dashboard_portal', $this->portalDashboardData()),
]);
// 워크스페이스 탭(iframe) 안: 대시보드 본문을 임베드로.
if ($this->isEmbeddedRequest()) {
return view('bag/layout/embed', [
'title' => '업무 현황',
'bare' => true,
'content' => view('bag/dashboard_portal', $this->portalDashboardData()),
]);
}
// 로그인 후 기본 화면 = 워크스페이스(탭). 메뉴를 탭으로 열어 작업 상태 유지.
return view('bag/layout/workspace');
}
return view('welcome_message');
}
/**
* 워크스페이스 — 메뉴를 탭(iframe)으로 열어두고 작업 상태를 유지하는 화면.
*/
public function workspace()
{
if (! session()->get('logged_in')) {
return redirect()->to(base_url('login'));
}
helper('admin');
return view('bag/layout/workspace');
}
/**
* 메인 대시보드용 — 종량제 시스템에 실제 존재하는 데이터만 집계.
*