get('logged_in')) { helper('admin'); // 워크스페이스 탭(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'); } /** * 메인 대시보드용 — 종량제 시스템에 실제 존재하는 데이터만 집계. * * @return array */ 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; } // 지도용 — 현재 지자체 지정판매소(이름·주소). 좌표는 클라이언트(카카오 지오코딩)에서 변환. $mapShops = []; try { if ($lgIdx !== null && $db->tableExists('designated_shop')) { $rows = $db->table('designated_shop') ->select('ds_name, ds_addr, ds_addr_jibun') ->where('ds_lg_idx', $lgIdx) ->where('ds_addr IS NOT NULL') ->where('ds_addr <>', '') ->orderBy('ds_idx', 'ASC') ->limit(40) ->get()->getResultArray(); foreach ($rows as $r) { $addr = trim((string) ($r['ds_addr'] ?? '')); if ($addr === '') { continue; } $mapShops[] = [ 'name' => (string) ($r['ds_name'] ?? ''), 'addr' => $addr, 'jibun' => trim((string) ($r['ds_addr_jibun'] ?? '')), ]; } } } catch (\Throwable $e) { $mapShops = []; } // 최근 활동(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, 'mapShops' => $mapShops, 'kakaoJsKey' => config(\Config\Kakao::class)->javascriptKey, 'menuSearchOptions' => (function_exists('gov_portal_nav_context') && function_exists('gov_portal_menu_search_options')) ? gov_portal_menu_search_options(gov_portal_nav_context(false)['navItems']) : [], 'menuFlat' => $this->buildMenuFlat(), ]; } /** * 메뉴검색 자동완성용 — 사이트 메뉴를 (상위·메뉴명·URL) 평탄 목록으로. * * @return list */ private function buildMenuFlat(): array { if (! function_exists('gov_portal_nav_context')) { return []; } $flat = []; foreach (gov_portal_nav_context(false)['navItems'] as $parent) { $pName = (string) ($parent['name'] ?? ''); if (! empty($parent['children'])) { foreach ($parent['children'] as $child) { $url = (string) ($child['url'] ?? ''); if ($url === '') { continue; } $flat[] = ['parent' => $pName, 'name' => (string) ($child['name'] ?? ''), 'url' => $url]; } } elseif (! empty($parent['url'])) { $flat[] = ['parent' => '', 'name' => $pName, 'url' => (string) $parent['url']]; } } return $flat; } /** * 로그인 후 메인 — site 메뉴 레이아웃 + 종합·그래프(blend) 본문 */ public function dashboard() { return view('bag/layout/main', [ 'title' => '업무 현황 · 종합·그래프', 'content' => view('bag/dashboard_blend_inner', [ 'lgLabel' => $this->resolveLgLabel(), ]), ]); } /** * 로그인 후 메인 — 단순형 요약 대시보드. URL: /dashboard/simple * 기존 /dashboard 화면이 복잡하다는 피드백용으로, 핵심 지표·링크만 노출. */ public function dashboardSimple() { return view('bag/layout/main', [ 'title' => '업무 현황 · 요약', 'content' => view('bag/lg_dashboard_simple', [ 'lgLabel' => $this->resolveLgLabel(), ]), ]); } /** * 로그인 후 메인 — 중간 밀도 대시보드. URL: /dashboard/compact * /dashboard 보다 단순하지만 simple 보다 정보량을 늘린 화면. */ public function dashboardCompact() { return view('bag/layout/main', [ 'title' => '업무 현황 · 컴팩트', 'content' => view('bag/lg_dashboard_compact', [ 'lgLabel' => $this->resolveLgLabel(), ]), ]); } /** * 디자인 시안(기존 /dashboard 연결 화면) */ public function dashboardClassicMock() { return $this->renderDashboard(); } /** * 로그인 후 메인 — 모던형(세로 사이드바) 레이아웃. URL: /dashboard/modern */ public function dashboardModern() { return view('bag/lg_dashboard_modern', [ 'lgLabel' => $this->resolveLgLabel(), ]); } /** * 로그인 후 메인 — 정보 집약형 종합 현황. URL: /dashboard/dense */ public function dashboardDense() { return view('bag/lg_dashboard_dense', [ 'lgLabel' => $this->resolveLgLabel(), ]); } /** * 로그인 후 메인 — 그래프 중심(Chart.js). URL: /dashboard/charts */ public function dashboardCharts() { return view('bag/lg_dashboard_charts', [ 'lgLabel' => $this->resolveLgLabel(), ]); } /** * /dashboard 와 동일 본문(호환 URL) */ public function dashboardBlend() { return $this->dashboard(); } /** * 로그인 후 메인 — 라이트(축약) 대시보드. URL: /dashboard/lite * dashboard_blend 의 일부 KPI/표/차트만 남긴 단순화 화면. */ public function dashboardLite() { return view('bag/layout/main', [ 'title' => '업무 현황 · 라이트', 'content' => view('bag/dashboard_blend_lite_inner', [ 'lgLabel' => $this->resolveLgLabel(), ]), ]); } /** * 공공 포털형(국가재난관리정보시스템 스타일) 메인 시안. * URL: /dashboard/gov-portal — 기능은 요약 대시보드와 동일, UI만 별도 레이아웃. */ public function dashboardGovPortal() { if (! session()->get('logged_in')) { return redirect()->to(base_url('login')); } helper('admin'); return view('home/dashboard_gov_portal', gov_portal_dashboard_view_data($this->resolveLgLabel(), 'base')); } /** * 공공 포털형 변형 — 가로 MY MENU·와이드 맵·KPI 띠. URL: /dashboard/gov-portal-strip */ public function dashboardGovPortalStrip() { if (! session()->get('logged_in')) { return redirect()->to(base_url('login')); } helper('admin'); return view('home/_dashboard_gov_portal_strip_layout', array_merge( gov_portal_dashboard_view_data($this->resolveLgLabel(), 'strip'), ['stripInnerView' => 'home/_dashboard_gov_portal_strip_home_inner'] )); } /** * 공공 포털형(기본) — 기본 코드관리 UI 시안. URL: /dashboard/gov-portal/code-kinds */ public function dashboardGovPortalCodeKinds() { return $this->renderGovPortalCodeKinds('base'); } /** * 공공 포털형(변형 strip) — 기본 코드관리 UI 시안. URL: /dashboard/gov-portal-strip/code-kinds */ public function dashboardGovPortalStripCodeKinds() { return $this->renderGovPortalCodeKinds('strip'); } /** * @return \CodeIgniter\HTTP\RedirectResponse|string */ private function renderGovPortalCodeKinds(string $variant) { if (! session()->get('logged_in')) { return redirect()->to(base_url('login')); } helper('admin'); $portalPath = gov_portal_code_kinds_portal_path($variant); $lgIdx = admin_effective_lg_idx(); $level = (int) session()->get('mb_level'); $filters = [ 'q_code' => $this->request->getGet('q_code'), 'q_name' => $this->request->getGet('q_name'), 'q_state' => $this->request->getGet('q_state'), ]; $builder = new GovPortalCodeKindsPage(); $ckIdx = (int) ($this->request->getGet('ck_idx') ?? 0); $pageData = $builder->buildPageData($lgIdx, $level, $lgIdx, $ckIdx, $filters); if ($ckIdx === 0 && $pageData['codeKinds'] !== []) { $pageData = $builder->buildPageData( $lgIdx, $level, $lgIdx, (int) $pageData['codeKinds'][0]->ck_idx, $filters ); } $viewData = array_merge( gov_portal_dashboard_view_data($this->resolveLgLabel(), $variant), $pageData, [ 'govActiveChildHref' => $portalPath, 'pageBaseUrl' => site_url($portalPath), ] ); if ($variant === 'strip') { return view('home/_dashboard_gov_portal_strip_layout', array_merge($viewData, [ 'stripInnerView' => 'home/_gov_portal_code_kinds_body', 'stripIncludeWorkCss' => true, 'stripShowProfileLinks' => true, ])); } return view('home/dashboard_gov_portal_code_kinds', $viewData); } /** * 재고 조회(수불) 화면 (목업) */ public function inventoryInquiry() { return view('bag/inventory_inquiry'); } /** * 종량제 수불 그리드 (엔터프라이즈형 목업, 상단 가로 메뉴 + 병합 헤더 표) */ public function wasteSuibalEnterprise() { return view('bag/waste_suibal_enterprise'); } protected function renderDashboard() { return view('bag/lg_dashboard', [ 'lgLabel' => $this->resolveLgLabel(), ]); } /** * 세션 mb_lg_idx 기준 지자체명 (DB 없거나 실패 시 데모용 문구) */ protected function resolveLgLabel(): string { try { helper('admin'); $idx = function_exists('admin_effective_lg_idx') ? admin_effective_lg_idx() : null; if ($idx === null) { $raw = session()->get('mb_lg_idx'); $idx = ($raw !== null && $raw !== '') ? (int) $raw : null; } if ($idx === null) { return '지자체 미지정'; } $row = model(LocalGovernmentModel::class)->find((int) $idx); if ($row && isset($row->lg_name) && $row->lg_name !== '') { return (string) $row->lg_name; } } catch (\Throwable $e) { // 테이블 미생성 등 } return '지자체'; } }