gov-portal 디자인을 시스템 전체에 적용한다.
- 사이트 업무 페이지: 공통 셸 bag/layout/portal(헤더+대메뉴 클릭+좌측 사이드바 소메뉴) - 관리자 페이지: admin/layout 을 동일 포털 셸로 재작성(관리자 메뉴 트리, 폴백) - 메인(/): gov-portal 대시보드, 종량제 실데이터만(재고/주문/승인/활동로그) - 로그인/회원가입/2차인증/TOTP: 공통 auth/_shell 로 통일, 사이트 공통 로고 - 버튼색 통일: btn-search 등 주요 버튼을 #243a5e(메뉴바 네이비보다 살짝 밝게), 밝은 파랑 채움 버튼(#2b4c8c/#1e548a)도 동일 색으로 - gov_portal_nav_context() 임의 메뉴 트리 수용, 업무 셸은 실제 bag/* 링크 유지 - Admin\Menu 권한거부 리다이렉트 admin/dashboard(404) → admin 수정 - E2E redesign.spec.js 추가, 기능 무변경 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,181 +1,151 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* 관리자 공통 레이아웃 — gov-portal 디자인 적용판.
|
||||
* 헤더 + 관리자 대메뉴(클릭) + 좌측 사이드바(소메뉴) + 본문($content).
|
||||
*
|
||||
* @var string $title
|
||||
* @var string $content
|
||||
*/
|
||||
helper('admin');
|
||||
$uriObj = service('request')->getUri();
|
||||
$n = $uriObj->getTotalSegments();
|
||||
$uri = $n >= 2 ? $uriObj->getSegment(2) : '';
|
||||
$seg3 = $n >= 3 ? $uriObj->getSegment(3) : '';
|
||||
$mbLevel = (int) session()->get('mb_level');
|
||||
|
||||
$mbLevel = (int) session()->get('mb_level');
|
||||
$isSuperAdmin = \Config\Roles::isSuperAdminEquivalent($mbLevel);
|
||||
$effectiveLgIdx = admin_effective_lg_idx();
|
||||
$effectiveLgName = null;
|
||||
$mbName = (string) (session()->get('mb_name') ?? '담당자');
|
||||
$levelName = config(\Config\Roles::class)->getLevelName($mbLevel);
|
||||
|
||||
$effectiveLgIdx = admin_effective_lg_idx();
|
||||
$effectiveLgName = '';
|
||||
if ($effectiveLgIdx) {
|
||||
$lgRow = model(\App\Models\LocalGovernmentModel::class)->find($effectiveLgIdx);
|
||||
$effectiveLgName = $lgRow ? $lgRow->lg_name : null;
|
||||
$effectiveLgName = $lgRow ? (string) $lgRow->lg_name : '';
|
||||
}
|
||||
$userNav = session_user_nav_display();
|
||||
$currentPath = current_nav_request_path();
|
||||
$adminNavTree = get_admin_nav_tree();
|
||||
|
||||
/** DB 링크(mm_link)만 사용. 짧게 적은 항목(menus 등)은 실제 URI(admin/menus)와 맞춰 후보 비교 */
|
||||
$adminNavItemIsCurrent = static function (?string $mmLink) use ($currentPath): bool {
|
||||
return menu_link_matches_request($mmLink, $currentPath, []);
|
||||
};
|
||||
$adminTree = function_exists('get_admin_nav_tree') ? get_admin_nav_tree() : [];
|
||||
$gov = gov_portal_nav_context(false, $adminTree);
|
||||
|
||||
/** 메뉴가 DB에서 안 쓰일 때만(폴백 상단바) 세그먼트 기반 활성 */
|
||||
$isActive = static function (string $path) use ($uri, $seg3) {
|
||||
if ($path === 'admin' || $path === '') return $uri === '';
|
||||
if ($path === 'users') return $uri === 'users';
|
||||
if ($path === 'login-history') return $uri === 'access' && $seg3 === 'login-history';
|
||||
if ($path === 'approvals') return $uri === 'access' && $seg3 === 'approvals';
|
||||
if ($path === 'roles') return $uri === 'roles';
|
||||
if ($path === 'menus') return $uri === 'menus';
|
||||
if ($path === 'local-governments') return $uri === 'local-governments';
|
||||
if ($path === 'select-local-government') return $uri === 'select-local-government';
|
||||
if ($path === 'designated-shops') return $uri === 'designated-shops';
|
||||
return false;
|
||||
};
|
||||
// 관리자 메뉴가 비어 있으면(지자체 미선택 등) 핵심 항목 폴백 노출
|
||||
$navItems = $gov['navItems'];
|
||||
if ($navItems === []) {
|
||||
$mk = static fn (string $name, string $path): array => [
|
||||
'idx' => 0, 'name' => $name, 'href' => $path, 'url' => base_url($path),
|
||||
];
|
||||
$navItems = [
|
||||
['idx' => 0, 'name' => '대시보드', 'href' => 'admin', 'url' => base_url('admin'), 'children' => [], 'hasChildren' => false],
|
||||
['idx' => 0, 'name' => '회원·접근', 'href' => '', 'url' => '', 'hasChildren' => true, 'children' => [
|
||||
$mk('회원 관리', 'admin/users'), $mk('로그인 이력', 'admin/access/login-history'), $mk('승인 대기', 'admin/access/approvals'),
|
||||
]],
|
||||
['idx' => 0, 'name' => '시스템', 'href' => '', 'url' => '', 'hasChildren' => true, 'children' => [
|
||||
$mk('역할', 'admin/roles'), $mk('메뉴', 'admin/menus'),
|
||||
]],
|
||||
];
|
||||
if ($isSuperAdmin) {
|
||||
$navItems[] = ['idx' => 0, 'name' => '지자체', 'href' => '', 'url' => '', 'hasChildren' => true, 'children' => [
|
||||
$mk('지자체 전환', 'admin/select-local-government'), $mk('지자체 관리', 'admin/local-governments'),
|
||||
]];
|
||||
}
|
||||
}
|
||||
$navJson = json_encode($navItems, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS);
|
||||
|
||||
$navPartial = [
|
||||
'govNavItems' => $navItems,
|
||||
'govNavJson' => $navJson,
|
||||
'govActiveParentIdx' => $gov['activeParentIdx'],
|
||||
'govCurrentPath' => $gov['currentPath'],
|
||||
'govDashboardAliases' => $gov['dashboardAliases'],
|
||||
'govActiveChildHref' => $gov['currentPath'],
|
||||
];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<html lang="ko" class="gov-portal-html">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title><?= esc($title ?? '관리자') ?> - 종량제 시스템</title>
|
||||
<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"/>
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet"/>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: { sans: ['"Malgun Gothic"', '"Noto Sans KR"', 'sans-serif'] },
|
||||
colors: {
|
||||
'system-header': '#ffffff',
|
||||
'title-bar': '#2c3e50',
|
||||
'control-panel': '#f8f9fa',
|
||||
'btn-search': '#1c4e80',
|
||||
'btn-excel-border': '#28a745',
|
||||
'btn-excel-text': '#28a745',
|
||||
'btn-print-border': '#ced4da',
|
||||
'btn-exit': '#d9534f',
|
||||
fontFamily: { sans: ['Pretendard', '"Malgun Gothic"', '"Noto Sans KR"', 'sans-serif'] },
|
||||
colors: {
|
||||
'system-header': '#ffffff', 'title-bar': '#1a2b4b', 'control-panel': '#f8f9fa',
|
||||
'btn-search': '#243a5e', 'btn-excel-border': '#28a745', 'btn-excel-text': '#28a745',
|
||||
'btn-print-border': '#ced4da', 'btn-exit': '#d9534f',
|
||||
},
|
||||
fontSize: { 'xxs': '0.65rem' }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style data-purpose="global-font-scale">
|
||||
/* 전체 텍스트 +2px 확대 (요청). 로고(.app-brand)는 16px 로 유지. */
|
||||
html { font-size: 18px; }
|
||||
.app-brand, .app-brand * { font-size: 16px; }
|
||||
</style>
|
||||
<style data-purpose="table-layout">
|
||||
.data-table { width: 100%; border-collapse: collapse; font-family: 'Malgun Gothic', 'Noto Sans KR', sans-serif; }
|
||||
<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'; ?>
|
||||
.data-table { width: 100%; border-collapse: collapse; font-family: 'Pretendard', 'Malgun Gothic', 'Noto Sans KR', sans-serif; }
|
||||
.data-table th, .data-table td { border: 1px solid #ccc; padding: 4px 8px; white-space: nowrap; font-size: 13px; }
|
||||
.data-table th { background-color: #e9ecef; text-align: center; vertical-align: middle; font-weight: bold; color: #333; }
|
||||
.data-table tbody tr:nth-child(even) td { background-color: #f9f9f9; }
|
||||
.data-table tbody tr:nth-child(even) { background-color: #f9f9f9; }
|
||||
.data-table tbody tr:hover td { background-color: #e6f7ff !important; }
|
||||
.main-content-area { height: calc(100vh - 170px); overflow: auto; }
|
||||
body { overflow: hidden; }
|
||||
@media print {
|
||||
header, footer, .no-print, nav { display: none !important; }
|
||||
.main-content-area { height: auto !important; overflow: visible !important; }
|
||||
body { overflow: visible !important; }
|
||||
.bg-title-bar { display: none !important; }
|
||||
.bg-control-panel { break-inside: avoid; }
|
||||
.portal-header, .sidebar, .portal-footer, .no-print, nav.portal-top-nav { display: none !important; }
|
||||
body.gov-portal-shell { background: #fff; display: block; }
|
||||
.gov-portal-shell .main.work-main { overflow: visible !important; padding: 0 !important; }
|
||||
.print-header { display: block !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100 text-gray-800 flex flex-col h-screen font-sans antialiased select-none">
|
||||
<header class="relative bg-white border-b border-gray-300 h-12 flex items-center justify-between px-4 shrink-0 z-50">
|
||||
<div class="flex items-center gap-2 shrink-0">
|
||||
<?= view('components/header_brand', ['href' => base_url('admin')]) ?>
|
||||
</div>
|
||||
<nav class="hidden md:flex gap-5 text-sm font-medium text-gray-600">
|
||||
<?php if (! empty($adminNavTree)): ?>
|
||||
<?php foreach ($adminNavTree as $navItem): ?>
|
||||
<?php
|
||||
$hasChildren = ! empty($navItem->children);
|
||||
$parentLink = menu_link_preferred_href_path($navItem->mm_link ?? null, $currentPath);
|
||||
$activeChild = $hasChildren ? menu_active_child_for_parent($navItem, $currentPath, []) : null;
|
||||
$parentIsCurrent = $adminNavItemIsCurrent($navItem->mm_link ?? null);
|
||||
if (! $parentIsCurrent && $activeChild !== null) {
|
||||
$parentIsCurrent = true;
|
||||
}
|
||||
?>
|
||||
<div class="relative group">
|
||||
<a class="<?= $parentIsCurrent ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>"
|
||||
href="<?= $parentLink !== '' ? base_url($parentLink) : '#' ?>">
|
||||
<?= esc($navItem->mm_name) ?>
|
||||
<body class="gov-portal-shell select-none">
|
||||
<header class="portal-header">
|
||||
<div class="portal-header-inner">
|
||||
<?= view('home/_dashboard_gov_portal_brand', ['brandHref' => base_url('admin')]) ?>
|
||||
<?= view('home/_dashboard_gov_portal_topnav_click', $navPartial) ?>
|
||||
<div class="portal-header-utils" style="display:flex;align-items:center;gap:.5rem;">
|
||||
<span class="user-line">
|
||||
<?php if ($effectiveLgName !== ''): ?><strong><?= esc($effectiveLgName) ?></strong> · <?php endif; ?>
|
||||
<?= esc($levelName) ?> · <?= esc($mbName) ?>님
|
||||
</span>
|
||||
<a href="<?= base_url('/') ?>" 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;white-space:nowrap;">
|
||||
<i class="fa-solid fa-house"></i> 사이트
|
||||
</a>
|
||||
<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;white-space:nowrap;">
|
||||
<i class="fa-solid fa-right-from-bracket"></i> 로그아웃
|
||||
</a>
|
||||
<?php if ($hasChildren): ?>
|
||||
<?php /* 사이트 메뉴와 동일: 호버 끊김 방지 pt-1, 키보드 포커스, z-index */ ?>
|
||||
<div class="absolute left-0 top-full z-50 hidden pt-1 min-w-[12rem] group-hover:block group-focus-within:block">
|
||||
<div class="bg-white border border-gray-200 rounded shadow-lg py-1">
|
||||
<?php foreach ($navItem->children as $child): ?>
|
||||
<?php
|
||||
$childLink = menu_link_preferred_href_path($child->mm_link ?? null, $currentPath);
|
||||
$childIsCurrent = $activeChild !== null
|
||||
&& (int) ($child->mm_idx ?? 0) === (int) ($activeChild->mm_idx ?? -1);
|
||||
?>
|
||||
<?php if ($childLink !== ''): ?>
|
||||
<a href="<?= base_url($childLink) ?>"
|
||||
class="block px-3 py-1.5 text-sm hover:bg-blue-50 whitespace-nowrap <?= $childIsCurrent ? 'text-blue-700 font-semibold bg-blue-50' : 'text-gray-700' ?>">
|
||||
<?= esc($child->mm_name) ?>
|
||||
</a>
|
||||
<?php else: ?>
|
||||
<span class="block px-3 py-1.5 text-sm text-gray-400 cursor-default whitespace-nowrap" title="메뉴 관리에서 링크를 설정해 주세요">
|
||||
<?= esc($child->mm_name) ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<a class="<?= $isActive('') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin') ?>">대시보드</a>
|
||||
<a class="<?= $isActive('users') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/users') ?>">회원 관리</a>
|
||||
<a class="<?= $isActive('login-history') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/access/login-history') ?>">로그인 이력</a>
|
||||
<a class="<?= $isActive('approvals') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/access/approvals') ?>">승인 대기</a>
|
||||
<a class="<?= $isActive('roles') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/roles') ?>">역할</a>
|
||||
<a class="<?= $isActive('menus') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/menus') ?>">메뉴</a>
|
||||
<?php if ($isSuperAdmin): ?>
|
||||
<a class="<?= $isActive('select-local-government') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/select-local-government') ?>">지자체 전환</a>
|
||||
<a class="<?= $isActive('local-governments') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('admin/local-governments') ?>">지자체</a>
|
||||
<?php endif; ?>
|
||||
<a class="<?= $isActive('designated-shops') ? 'text-blue-700 font-bold border-b-2 border-blue-700 pb-3 -mb-3' : 'hover:text-blue-600' ?>" href="<?= base_url('bag/designated-shops') ?>">지정판매소</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<?= view('components/header_user_tools', [
|
||||
'userNav' => $userNav,
|
||||
'effectiveLgName' => $effectiveLgName,
|
||||
'showSiteLink' => true,
|
||||
'showAdminLink' => false,
|
||||
]) ?>
|
||||
</header>
|
||||
<div class="bg-title-bar text-white px-4 py-2 text-sm font-medium shrink-0">
|
||||
<?= esc($title ?? '관리자') ?>
|
||||
</div>
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="mx-4 mt-2 p-3 rounded-lg bg-green-50 text-green-700 text-sm" role="alert"><?= esc(session()->getFlashdata('success')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="mx-4 mt-2 p-3 rounded-lg bg-red-50 text-red-700 text-sm" role="alert"><?= esc(session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('errors')): ?>
|
||||
<div class="mx-4 mt-2 p-3 rounded-lg bg-red-50 text-red-700 text-sm space-y-1" role="alert">
|
||||
<?php foreach (session()->getFlashdata('errors') as $err): ?><p><?= esc($err) ?></p><?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<main class="main-content-area flex-grow bg-white p-4">
|
||||
<?= $content ?>
|
||||
</main>
|
||||
<footer class="bg-gray-200 border-t border-gray-300 px-4 py-1 text-xs text-gray-600 flex items-center justify-between shrink-0">
|
||||
<span>종량제 시스템 관리자</span>
|
||||
<span><?= date('Y.m.d (D) g:i:sA') ?></span>
|
||||
</footer>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<?= view('home/_dashboard_gov_portal_sidebar', $navPartial) ?>
|
||||
|
||||
<main class="main work-main main-content-area">
|
||||
<?php if (! empty($title)): ?>
|
||||
<h1 class="work-titlebar"><i class="fa-solid fa-gear tb-ico"></i><?= esc($title) ?></h1>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="work-flash ok"><?= esc(session()->getFlashdata('success')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="work-flash err"><?= esc(session()->getFlashdata('error')) ?></div>
|
||||
<?php endif; ?>
|
||||
<?php if (session()->getFlashdata('errors')): ?>
|
||||
<div class="work-flash err"><?php foreach (session()->getFlashdata('errors') as $err): ?><div><?= esc($err) ?></div><?php endforeach; ?></div>
|
||||
<?php endif; ?>
|
||||
<div class="work-surface">
|
||||
<?= $content ?>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="portal-footer">
|
||||
<span>종량제 시스템 관리자</span>
|
||||
<span><?= date('Y.m.d (D) H:i') ?></span>
|
||||
</footer>
|
||||
|
||||
<?= view('home/_dashboard_gov_portal_nav_script_base', $navPartial) ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user