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:
@@ -345,7 +345,7 @@ class Menu extends BaseController
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->to(base_url('admin/dashboard'))
|
||||
return redirect()->to(base_url('admin'))
|
||||
->with('error', '메뉴 관리는 레벨4 이상만 접근할 수 있습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,10 @@ class Auth extends BaseController
|
||||
return redirect()->to('/');
|
||||
}
|
||||
|
||||
return view('auth/login');
|
||||
return view('auth/login', [
|
||||
'pageTitle' => '로그인 - 종량제 시스템',
|
||||
'cardMax' => 'max-w-md',
|
||||
]);
|
||||
}
|
||||
|
||||
public function login()
|
||||
@@ -156,7 +159,9 @@ class Auth extends BaseController
|
||||
}
|
||||
|
||||
return view('auth/login_two_factor', [
|
||||
'memberId' => $member->mb_id,
|
||||
'memberId' => $member->mb_id,
|
||||
'pageTitle' => '2차 인증 - 종량제 시스템',
|
||||
'cardMax' => 'max-w-md',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -239,6 +244,8 @@ class Auth extends BaseController
|
||||
'memberId' => $member->mb_id,
|
||||
'qrDataUri' => $qrDataUri,
|
||||
'secret' => $secret,
|
||||
'pageTitle' => '2차 인증 등록 - 종량제 시스템',
|
||||
'cardMax' => 'max-w-lg',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -341,6 +348,8 @@ class Auth extends BaseController
|
||||
|
||||
return view('auth/register', [
|
||||
'localGovernments' => $localGovernments,
|
||||
'pageTitle' => '회원가입 - 종량제 시스템',
|
||||
'cardMax' => 'max-w-md',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -214,7 +214,8 @@ class Bag extends BaseController
|
||||
|
||||
private function render(string $title, string $viewFile, array $data = []): string
|
||||
{
|
||||
return view('bag/layout/main', [
|
||||
// 사이트 업무 페이지 공통 셸: gov-portal 디자인(헤더+대메뉴+클릭형 좌측 사이드바).
|
||||
return view('bag/layout/portal', [
|
||||
'title' => $title,
|
||||
'content' => view($viewFile, $data),
|
||||
]);
|
||||
|
||||
@@ -61,7 +61,8 @@ abstract class BaseController extends Controller
|
||||
$path = substr($path, strlen('index.php/'));
|
||||
}
|
||||
if ($path === 'bag' || str_starts_with($path, 'bag/')) {
|
||||
return view('bag/layout/main', [
|
||||
// 사이트 업무 페이지: gov-portal 디자인 셸 적용
|
||||
return view('bag/layout/portal', [
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
]);
|
||||
|
||||
@@ -10,14 +10,141 @@ class Home extends BaseController
|
||||
public function index()
|
||||
{
|
||||
if (session()->get('logged_in')) {
|
||||
// 메인(/) 본문은 「요약(simple)」 대시보드로 노출한다.
|
||||
// 종래의 「종합·그래프(blend)」 본문은 /dashboard (또는 /dashboard/blend)로 이동.
|
||||
return $this->dashboardSimple();
|
||||
// 메인(/) — gov-portal 디자인 셸 + 종량제 실데이터 대시보드.
|
||||
helper('admin');
|
||||
|
||||
return view('bag/layout/portal', [
|
||||
'title' => '업무 현황',
|
||||
'bare' => true,
|
||||
'content' => view('bag/dashboard_portal', $this->portalDashboardData()),
|
||||
]);
|
||||
}
|
||||
|
||||
return view('welcome_message');
|
||||
}
|
||||
|
||||
/**
|
||||
* 메인 대시보드용 — 종량제 시스템에 실제 존재하는 데이터만 집계.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function portalDashboardData(): array
|
||||
{
|
||||
helper('admin');
|
||||
$db = \Config\Database::connect();
|
||||
$lgIdx = function_exists('admin_effective_lg_idx') ? admin_effective_lg_idx() : null;
|
||||
if ($lgIdx === null) {
|
||||
$raw = session()->get('mb_lg_idx');
|
||||
$lgIdx = ($raw !== null && $raw !== '') ? (int) $raw : null;
|
||||
}
|
||||
|
||||
$inventory = [];
|
||||
$totalQty = 0;
|
||||
$orderCount = 0;
|
||||
$palette = ['#3b82f6', '#10b981', '#f59e0b', '#6366f1', '#ef4444', '#0ea5e9', '#14b8a6', '#a855f7', '#f97316'];
|
||||
|
||||
try {
|
||||
if ($lgIdx !== null && $db->tableExists('bag_inventory')) {
|
||||
$rows = $db->table('bag_inventory')
|
||||
->select('bi_bag_name, bi_bag_code, bi_qty')
|
||||
->where('bi_lg_idx', $lgIdx)
|
||||
->orderBy('bi_qty', 'DESC')
|
||||
->get()->getResultArray();
|
||||
foreach ($rows as $r) {
|
||||
$inventory[] = [
|
||||
'name' => (string) ($r['bi_bag_name'] ?? $r['bi_bag_code'] ?? ''),
|
||||
'qty' => (int) ($r['bi_qty'] ?? 0),
|
||||
];
|
||||
$totalQty += (int) ($r['bi_qty'] ?? 0);
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$inventory = [];
|
||||
}
|
||||
|
||||
// 재고 구성(상위 품목 비율)
|
||||
$stockMix = [];
|
||||
foreach (array_slice($inventory, 0, 6) as $i => $item) {
|
||||
$stockMix[] = [
|
||||
'name' => $item['name'],
|
||||
'value' => $totalQty > 0 ? (int) round($item['qty'] / $totalQty * 100) : 0,
|
||||
'color' => $palette[$i % count($palette)],
|
||||
];
|
||||
}
|
||||
// 부족 재고(수량 적은 하위 품목) — 최대 재고 대비 비율
|
||||
$maxQty = $inventory !== [] ? max(array_column($inventory, 'qty')) : 0;
|
||||
$lowStock = [];
|
||||
foreach (array_slice(array_reverse($inventory), 0, 5) as $item) {
|
||||
$lowStock[] = [
|
||||
'name' => $item['name'],
|
||||
'qty' => $item['qty'],
|
||||
'percent' => $maxQty > 0 ? (int) round($item['qty'] / $maxQty * 100) : 0,
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
if ($lgIdx !== null && $db->tableExists('shop_order')) {
|
||||
$orderCount = (int) $db->table('shop_order')
|
||||
->where('so_lg_idx', $lgIdx)
|
||||
->where('so_status', 'normal')
|
||||
->countAllResults();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$orderCount = 0;
|
||||
}
|
||||
|
||||
$pendingApprovals = 0;
|
||||
try {
|
||||
if ($db->tableExists('member_approval_request')) {
|
||||
$pendingApprovals = (int) $db->table('member_approval_request')
|
||||
->where('mar_status', 'pending')
|
||||
->countAllResults();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$pendingApprovals = 0;
|
||||
}
|
||||
|
||||
// 최근 활동(activity_log) — 실제 변경 이력
|
||||
$actionLabel = ['create' => '등록', 'update' => '수정', 'delete' => '삭제', 'cancel' => '취소'];
|
||||
$tableLabel = [
|
||||
'bag_order' => '발주', 'bag_receiving' => '입고', 'bag_sale' => '판매',
|
||||
'bag_issue' => '불출', 'bag_inventory' => '재고', 'shop_order' => '주문접수',
|
||||
'designated_shop' => '지정판매소', 'bag_price' => '단가', 'member' => '회원',
|
||||
];
|
||||
$recent = [];
|
||||
try {
|
||||
if ($db->tableExists('activity_log')) {
|
||||
$logs = $db->table('activity_log')
|
||||
->select('al_action, al_table, al_regdate')
|
||||
->orderBy('al_idx', 'DESC')->limit(6)->get()->getResultArray();
|
||||
foreach ($logs as $l) {
|
||||
$t = (string) ($l['al_regdate'] ?? '');
|
||||
$recent[] = [
|
||||
'time' => $t !== '' ? date('m.d H:i', strtotime($t)) : '',
|
||||
'text' => ($tableLabel[$l['al_table']] ?? (string) $l['al_table'])
|
||||
. ' ' . ($actionLabel[$l['al_action']] ?? (string) $l['al_action']),
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$recent = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'lgLabel' => $this->resolveLgLabel(),
|
||||
'mbName' => (string) (session()->get('mb_name') ?? '담당자'),
|
||||
'mbId' => (string) (session()->get('mb_id') ?? ''),
|
||||
'levelName' => config(\Config\Roles::class)->getLevelName((int) session()->get('mb_level')),
|
||||
'totalQty' => $totalQty,
|
||||
'itemCount' => count($inventory),
|
||||
'orderCount' => $orderCount,
|
||||
'pendingApprovals' => $pendingApprovals,
|
||||
'stockMix' => $stockMix,
|
||||
'lowStock' => $lowStock,
|
||||
'recentActivity' => $recent,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 후 메인 — site 메뉴 레이아웃 + 종합·그래프(blend) 본문
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user