Files
jongryangje/app/Views/admin/layout.php
taekyoungc c8d1612f0e 운영 Whoops 방지를 위해 메뉴 활성 계산 의존성을 단순화
레이아웃에서 내부 헬퍼 함수를 직접 호출하지 않고 공개 메뉴 매칭 함수만 사용하도록 변경해 운영 환경 차이에 따른 오류 가능성을 줄였습니다.
2026-04-14 00:38:51 +09:00

188 lines
9.8 KiB
PHP

<?php
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');
$isSuperAdmin = \Config\Roles::isSuperAdminEquivalent($mbLevel);
$effectiveLgIdx = admin_effective_lg_idx();
$effectiveLgName = null;
if ($effectiveLgIdx) {
$lgRow = model(\App\Models\LocalGovernmentModel::class)->find($effectiveLgIdx);
$effectiveLgName = $lgRow ? $lgRow->lg_name : null;
}
$userNav = session_user_nav_display();
$currentPath = current_nav_request_path();
$adminNavTree = get_admin_nav_tree();
$activeAdminTopNav = null;
$activeAdminTopLen = -1;
$activeAdminTopNum = PHP_INT_MAX;
foreach ($adminNavTree as $topNode) {
$nodeLen = -1;
$activeChild = site_nav_active_child_for_parent($topNode, $currentPath, []);
if ($activeChild !== null) {
$nodeLen = strlen(menu_link_preferred_href_path($activeChild->mm_link ?? null, $currentPath));
} elseif (menu_link_matches_request($topNode->mm_link ?? null, $currentPath, [])) {
$nodeLen = strlen(menu_link_preferred_href_path($topNode->mm_link ?? null, $currentPath));
}
if ($nodeLen > $activeAdminTopLen || ($nodeLen === $activeAdminTopLen && (int) ($topNode->mm_num ?? 0) < $activeAdminTopNum)) {
$activeAdminTopNav = $topNode;
$activeAdminTopLen = $nodeLen;
$activeAdminTopNum = (int) ($topNode->mm_num ?? 0);
}
}
/** 메뉴가 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;
};
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title><?= esc($title ?? '관리자') ?> - 종량제 시스템</title>
<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&amp;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',
},
fontSize: { 'xxs': '0.65rem' }
}
}
}
</script>
<style data-purpose="table-layout">
.data-table { width: 100%; border-collapse: collapse; font-family: '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; }
.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);
$parentIsCurrent = ($activeAdminTopNav !== null && $navItem === $activeAdminTopNav);
?>
<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) ?>
</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
$activeChild = site_nav_active_child_for_parent($navItem, $currentPath, []);
?>
<?php foreach ($navItem->children as $child): ?>
<?php
$childLink = menu_link_preferred_href_path($child->mm_link ?? null, $currentPath);
$childIsCurrent = $activeChild !== null && $child === $activeChild;
?>
<?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>
</body>
</html>