refactor: unify bag and admin layout routing

This commit is contained in:
taekyoungc
2026-04-08 00:18:01 +09:00
parent 5b0c3fac97
commit 89f80edc5d
15 changed files with 832 additions and 354 deletions

View File

@@ -1,16 +1,17 @@
<?php
/**
* dense(정보 집약 표·KPI) + charts(Chart.js) 혼합 대시보드
* dense(표·KPI) + Chart.js 혼합 본문 `bag/layout/main` 삽입
*
* @var string $lgLabel
*/
$lgLabel = $lgLabel ?? '북구';
$mbName = session()->get('mb_name') ?? '담당자';
$dashHome = base_url('dashboard');
$dashBlend = base_url('dashboard/blend');
$dashClassic = base_url('dashboard/classic-mock');
$dashModern = base_url('dashboard/modern');
$dashDense = base_url('dashboard/dense');
$dashCharts = base_url('dashboard/charts');
$dashBlend = base_url('dashboard/blend');
$kpiTop = [
['icon' => 'fa-triangle-exclamation', 'c' => 'text-amber-700', 'bg' => 'bg-amber-50', 'v' => '3', 'l' => '재고부족', 'sub' => '품목'],
@@ -67,111 +68,53 @@ $notices = [
'봉투 단가 조정 예고 — 3/1 적용 예정 (안내문 배포 완료)',
];
?>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>종량제 시스템 종합·그래프 혼합</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
body {
font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
font-size: 12px;
color: #333;
}
.nav-top a.nav-active {
color: #2b4c8c;
font-weight: 600;
border-bottom: 2px solid #2b4c8c;
padding-bottom: 2px;
margin-bottom: -2px;
}
.dense-table th, .dense-table td { padding: 0.25rem 0.4rem; line-height: 1.25; }
.dense-table thead th { font-size: 11px; font-weight: 600; color: #555; background: #f3f4f6; border-bottom: 1px solid #d1d5db; }
.dense-table tbody td { border-bottom: 1px solid #eee; font-size: 11px; }
.spark { display: flex; align-items: flex-end; gap: 2px; height: 36px; }
.spark span { flex: 1; background: linear-gradient(180deg, #3b82f6, #93c5fd); border-radius: 1px; min-width: 4px; }
.chart-card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
box-shadow: 0 1px 2px rgba(0,0,0,.04);
}
.chart-card h2 {
font-size: 11px;
font-weight: 700;
color: #1f2937;
padding: 0.4rem 0.5rem;
border-bottom: 1px solid #f3f4f6;
background: #fafafa;
}
.chart-wrap { position: relative; height: 180px; padding: 0.4rem 0.5rem 0.5rem; }
.chart-wrap.tall { height: 260px; }
.chart-wrap.wide { height: 220px; }
</style>
</head>
<body class="bg-[#f0f2f5] flex flex-col min-h-screen">
<header class="border-b border-gray-300 bg-white shadow-sm shrink-0" data-purpose="top-navigation">
<div class="flex items-center justify-between px-3 py-1.5 gap-3 flex-wrap">
<div class="flex items-center gap-2 shrink-0">
<div class="flex items-center gap-2 text-green-700 font-bold text-base">
<i class="fa-solid fa-recycle text-lg"></i>
<span>종량제 시스템</span>
</div>
<span class="hidden sm:inline text-[11px] text-gray-500 border-l border-gray-300 pl-2">
<?= esc($lgLabel) ?> · <strong class="text-gray-700"><?= esc($mbName) ?></strong>님
</span>
</div>
<nav class="nav-top hidden lg:flex flex-wrap items-center gap-3 xl:gap-4 text-[13px] font-medium text-gray-700">
<a class="nav-active flex items-center gap-1 whitespace-nowrap" href="<?= esc($dashBlend) ?>">
<i class="fa-solid fa-gauge-high"></i> 업무 현황
</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-regular fa-file-lines"></i> 문서 관리</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-box-open"></i> 규격</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-bag-shopping"></i> 봉투 양식</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-table"></i> 데이터 양식</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-clock-rotate-left"></i> 사용 내역</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="<?= base_url('bag/inventory-inquiry') ?>"><i class="fa-solid fa-boxes-stacked"></i> 재고 현황</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="<?= base_url('bag/waste-suibal-enterprise') ?>"><i class="fa-solid fa-table-list"></i> 수불 현황</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-chart-line"></i> 통계 분석</a>
<a class="flex items-center gap-1 hover:text-blue-600 whitespace-nowrap" href="#"><i class="fa-solid fa-gear"></i> 설정</a>
</nav>
<div class="flex items-center gap-1.5 shrink-0 text-[11px]">
<a href="<?= esc($dashClassic) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline">클래식</a>
<span class="text-gray-300 hidden md:inline">|</span>
<a href="<?= esc($dashModern) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline">모던</a>
<span class="text-gray-300 hidden md:inline">|</span>
<a href="<?= esc($dashDense) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline" title="정보 집약">종합</a>
<span class="text-gray-300 hidden md:inline">|</span>
<a href="<?= esc($dashCharts) ?>" class="text-[#2b4c8c] hover:underline whitespace-nowrap hidden md:inline" title="그래프만">차트</a>
<a href="<?= base_url('logout') ?>" class="text-gray-500 hover:text-gray-800 p-1 ml-1" title="로그아웃">
<i class="fa-solid fa-arrow-right-from-bracket"></i>
</a>
</div>
</div>
</header>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
.blend-dash-inner {
font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif;
font-size: 12px;
color: #333;
}
.blend-dash-inner .dense-table th, .blend-dash-inner .dense-table td { padding: 0.25rem 0.4rem; line-height: 1.25; }
.blend-dash-inner .dense-table thead th { font-size: 11px; font-weight: 600; color: #555; background: #f3f4f6; border-bottom: 1px solid #d1d5db; }
.blend-dash-inner .dense-table tbody td { border-bottom: 1px solid #eee; font-size: 11px; }
.blend-dash-inner .spark { display: flex; align-items: flex-end; gap: 2px; height: 36px; }
.blend-dash-inner .spark span { flex: 1; background: linear-gradient(180deg, #3b82f6, #93c5fd); border-radius: 1px; min-width: 4px; }
.blend-dash-inner .chart-card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 0.25rem;
box-shadow: 0 1px 2px rgba(0,0,0,.04);
}
.blend-dash-inner .chart-card h2 {
font-size: 11px;
font-weight: 700;
color: #1f2937;
padding: 0.4rem 0.5rem;
border-bottom: 1px solid #f3f4f6;
background: #fafafa;
}
.blend-dash-inner .chart-wrap { position: relative; height: 180px; padding: 0.4rem 0.5rem 0.5rem; }
.blend-dash-inner .chart-wrap.tall { height: 260px; }
.blend-dash-inner .chart-wrap.wide { height: 220px; }
</style>
<div class="bg-gradient-to-r from-[#eff5fb] to-[#e8eef8] border-b border-gray-300 px-3 py-1 flex flex-wrap items-center justify-between gap-2 text-[11px] shrink-0">
<div class="blend-dash-inner bg-[#f0f2f5] -mx-4 -my-4 p-2 sm:p-3 min-h-full">
<div class="bg-gradient-to-r from-[#eff5fb] to-[#e8eef8] border border-gray-300 rounded-sm px-3 py-1 flex flex-wrap items-center justify-between gap-2 text-[11px] mb-2">
<span class="font-semibold text-gray-800">
<i class="fa-solid fa-layer-group text-[#2b4c8c] mr-1"></i>종합·그래프 혼합 현황
<span class="font-normal text-gray-500 ml-1">· dense /KPI + Chart.js</span>
<span class="font-normal text-gray-500 ml-1">· dense /KPI + Chart.js (목업)</span>
</span>
<div class="flex flex-wrap items-center gap-2 text-gray-600">
<span><i class="fa-regular fa-calendar mr-0.5"></i><?= date('Y-m-d (D) H:i') ?></span>
<span class="text-gray-300">|</span>
<span>기준지자체 <strong class="text-gray-800"><?= esc($lgLabel) ?></strong></span>
<button type="button" class="bg-[#2b4c8c] text-white px-2 py-0.5 rounded text-[11px]"><i class="fa-solid fa-rotate mr-0.5"></i>새로고침</button>
<span><?= esc($lgLabel) ?> · <strong class="text-gray-800"><?= esc($mbName) ?></strong></span>
<button type="button" class="bg-[#2b4c8c] text-white px-2 py-0.5 rounded text-[11px]" onclick="location.reload()"><i class="fa-solid fa-rotate mr-0.5"></i>새로고침</button>
</div>
</div>
<main class="flex-1 overflow-y-auto p-2 sm:p-3">
<?php if (session()->getFlashdata('success')): ?>
<div class="mb-2 p-2 rounded border border-green-200 bg-green-50 text-green-800 text-[11px]"><?= esc(session()->getFlashdata('success')) ?></div>
<?php endif; ?>
<div class="space-y-2">
<div class="mb-2 flex flex-wrap gap-2">
<?php foreach ($notices as $n): ?>
<div class="flex-1 min-w-[200px] flex items-center gap-2 bg-amber-50 border border-amber-200 text-amber-900 px-2 py-1 rounded text-[11px]">
@@ -196,7 +139,6 @@ $notices = [
<?php endforeach; ?>
</div>
<!-- dense: 재고 / 발주·신청 / 로그+스파크 -->
<div class="grid grid-cols-1 xl:grid-cols-12 gap-2 mb-2">
<section class="xl:col-span-4 bg-white border border-gray-200 rounded shadow-sm overflow-hidden">
<div class="px-2 py-1 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
@@ -295,7 +237,6 @@ $notices = [
</section>
</div>
<!-- charts: 요약 차트 4 (dense 아래) -->
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-2 mb-2">
<section class="chart-card">
<h2><i class="fa-solid fa-chart-pie text-[#2b4c8c] mr-1"></i>규격 출고 비중</h2>
@@ -432,13 +373,14 @@ $notices = [
</div>
</div>
<p class="text-center text-[10px] text-gray-400">
<strong class="text-gray-600">/dashboard/blend</strong> ( + 차트 혼합)
<p class="text-center text-[10px] text-gray-400 pb-1">
메인 <a href="<?= esc($dashHome) ?>" class="text-[#2b4c8c] hover:underline">/dashboard</a>
· 동일 본문 <a href="<?= esc($dashBlend) ?>" class="text-[#2b4c8c] hover:underline">/dashboard/blend</a>
· <a href="<?= esc($dashDense) ?>" class="text-[#2b4c8c] hover:underline">/dashboard/dense</a>
· <a href="<?= esc($dashCharts) ?>" class="text-[#2b4c8c] hover:underline">/dashboard/charts</a>
· <a href="<?= esc($dashClassic) ?>" class="text-[#2b4c8c] hover:underline">클래식</a>
</p>
</main>
</div>
<script>
(function () {
@@ -696,5 +638,4 @@ $notices = [
});
})();
</script>
</body>
</html>
</div>

View File

@@ -2,26 +2,24 @@
declare(strict_types=1);
helper('admin');
$siteNavTree = get_site_nav_tree();
$uriObj = service('request')->getUri();
$currentPath = trim((string) $uriObj->getPath(), '/');
if (str_starts_with($currentPath, 'index.php/')) {
$currentPath = substr($currentPath, strlen('index.php/'));
}
$currentPath = current_nav_request_path();
$mbLevel = (int) session()->get('mb_level');
$isAdmin = ($mbLevel === \Config\Roles::LEVEL_SUPER_ADMIN || $mbLevel === \Config\Roles::LEVEL_LOCAL_ADMIN);
$dashboardPathAliases = ['dashboard', 'dashboard/blend'];
$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();
?>
<!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>
<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>
@@ -64,32 +62,19 @@ body { overflow: hidden; }
</head>
<body class="bg-gray-100 text-gray-800 flex flex-col h-screen font-sans antialiased select-none">
<!-- BEGIN: Top Navigation -->
<header class="relative bg-white border-b border-gray-300 h-12 flex items-center justify-between px-4 shrink-0 z-50">
<header class="relative bg-white border-b border-gray-300 h-12 flex items-center justify-between px-4 shrink-0 z-[100]">
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<div class="w-6 h-6 flex items-center justify-center shrink-0">
<svg class="h-5 w-5" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<rect width="16" height="16" fill="#2563eb"/><rect x="2" y="2" width="7" height="7" fill="white"/><rect x="5" y="5" width="9" height="9" fill="white"/>
</svg>
</div>
<a href="<?= base_url() ?>" class="text-base font-semibold text-gray-800 tracking-tight hover:text-blue-600">쓰레기봉투 물류시스템</a>
</div>
<?= view('components/header_brand') ?>
</div>
<nav class="hidden md:flex gap-5 text-sm font-medium text-gray-600">
<?php if (! empty($siteNavTree)): ?>
<?php foreach ($siteNavTree as $navItem): ?>
<?php
$navLink = trim((string) $navItem->mm_link, '/');
$isActive = ($currentPath === $navLink);
$navLink = menu_link_preferred_href_path($navItem->mm_link ?? null, $currentPath);
$isActive = site_nav_link_matches_current($navItem->mm_link ?? null, $currentPath, $dashboardPathAliases);
if (! $isActive && ! empty($navItem->children)) {
foreach ($navItem->children as $ch) {
$childPath = trim((string) $ch->mm_link, '/');
if ($currentPath === $childPath) {
$isActive = true;
break;
}
// 기본코드 세부는 메뉴에 직접 링크 없음 → 기본코드관리(bag/code-kinds)와 동일 메뉴군으로 표시
if ($childPath === 'bag/code-kinds' && str_starts_with($currentPath, 'bag/code-details')) {
if (site_nav_link_matches_current($ch->mm_link ?? null, $currentPath, $dashboardPathAliases)) {
$isActive = true;
break;
}
@@ -98,20 +83,21 @@ body { overflow: hidden; }
?>
<div class="relative group">
<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($navItem->mm_link) ?>">
href="<?= $navLink !== '' ? base_url($navLink) : '#' ?>">
<?= esc($navItem->mm_name) ?>
</a>
<?php if (! empty($navItem->children)): ?>
<?php /* pt-1: 부모와 패널이 호버 끊김 방지. z-50: 제목 바보다 위 */ ?>
<div class="absolute left-0 top-full z-50 hidden pt-1 min-w-[12rem] group-hover:block group-focus-within:block">
<?php /* -mt-1 + pt-2: 부모 링크와 패널이 살짝 겹쳐 호버기지 않게 함. z-index: 드롭다운 클릭 우선 */ ?>
<div class="absolute left-0 top-full z-[200] -mt-1 pt-2 min-w-[12rem] hidden 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 = trim((string) ($child->mm_link ?? ''));
$childLink = menu_link_preferred_href_path($child->mm_link ?? null, $currentPath);
$childCurrent = menu_link_matches_request($child->mm_link ?? null, $currentPath, $dashboardPathAliases);
?>
<?php if ($childLink !== ''): ?>
<a href="<?= base_url($childLink) ?>"
class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-blue-50 whitespace-nowrap">
class="block px-3 py-1.5 text-sm hover:bg-blue-50 whitespace-nowrap <?= $childCurrent ? 'text-blue-700 font-semibold bg-blue-50' : 'text-gray-700' ?>">
<?= esc($child->mm_name) ?>
</a>
<?php else: ?>
@@ -127,17 +113,12 @@ body { overflow: hidden; }
<?php endforeach; ?>
<?php endif; ?>
</nav>
<div class="flex items-center gap-2">
<?php if ($effectiveLgName !== null): ?>
<span class="text-sm text-gray-600" title="현재 작업 지자체"><?= esc($effectiveLgName) ?></span>
<?php endif; ?>
<?php if ($isAdmin): ?>
<a href="<?= base_url('admin') ?>" class="text-gray-500 hover:text-blue-600 text-sm">관리자</a>
<?php endif; ?>
<a href="<?= base_url('logout') ?>" class="text-gray-500 hover:text-red-600 transition-colors inline-block p-1 rounded hover:bg-red-50" title="로그아웃">
<svg class="h-5 w-5 inline" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12" stroke-linecap="round" stroke-linejoin="round"/></svg> 종료
</a>
</div>
<?= view('components/header_user_tools', [
'userNav' => $userNav,
'effectiveLgName' => $effectiveLgName,
'showSiteLink' => false,
'showAdminLink' => $isAdmin,
]) ?>
</header>
<!-- END: Top Navigation -->
<div class="bg-title-bar text-white px-4 py-2 text-sm font-medium shrink-0">
@@ -153,7 +134,7 @@ body { overflow: hidden; }
<?= $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>종량제 시스템</span>
<span><?= date('Y.m.d (D) g:i:sA') ?></span>
</footer>
</body>