diff --git a/app/Config/App.php b/app/Config/App.php index 531410b..baa8668 100644 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -40,7 +40,7 @@ class App extends BaseConfig * something else. If you have configured your web server to remove this file * from your site URIs, set this variable to an empty string. */ - public string $indexPage = 'index.php'; + public string $indexPage = ''; /** * -------------------------------------------------------------------------- diff --git a/app/Config/Roles.php b/app/Config/Roles.php index 8f04fcf..9413059 100644 --- a/app/Config/Roles.php +++ b/app/Config/Roles.php @@ -43,11 +43,49 @@ class Roles extends BaseConfig } /** - * 기본코드(종류·세부) 등록·수정·삭제 가능 (지자체·super·본부 관리자) + * 기본코드(종류·세부) 등록·수정·삭제 가능 (super admin(4) · 본부 관리자(5)만) */ public static function canManageCodeMaster(int $level): bool { - return $level === self::LEVEL_LOCAL_ADMIN || self::isSuperAdminEquivalent($level); + return self::isSuperAdminEquivalent($level); + } + + /** + * 기본코드 종류(code_kind) CRUD — super·본부만 + */ + public static function canManageCodeKindMaster(int $level): bool + { + return self::isSuperAdminEquivalent($level); + } + + /** + * 플랫폼 공통 세부코드(CSV·시드) 수정·삭제 — super·본부만 + */ + public static function canEditPlatformCodeDetail(int $level): bool + { + return self::isSuperAdminEquivalent($level); + } + + /** + * 세부코드 행 단위 수정/삭제 가능 여부 + * + * @param object $row code_detail (cd_source, cd_lg_idx) + */ + public static function canEditCodeDetailRow(int $level, object $row, ?int $adminEffectiveLgIdx): bool + { + if (! self::canManageCodeMaster($level)) { + return false; + } + $src = $row->cd_source ?? 'platform'; + $lg = (int) ($row->cd_lg_idx ?? 0); + if ($src === 'platform' && $lg === 0) { + return self::canEditPlatformCodeDetail($level); + } + if (self::isSuperAdminEquivalent($level)) { + return true; + } + + return $adminEffectiveLgIdx !== null && $adminEffectiveLgIdx > 0 && $lg === $adminEffectiveLgIdx; } /** diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 3994ec1..9bebfda 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -17,6 +17,9 @@ $routes->get('bag/waste-suibal-enterprise', 'Home::wasteSuibalEnterprise'); // 사이트 메뉴 (/bag/*) $routes->get('bag/basic-info', 'Bag::basicInfo'); +$routes->get('bag/prices', 'Bag::prices'); +$routes->post('bag/prices', 'Bag::prices'); +$routes->get('bag/packaging-units', 'Bag::packagingUnits'); $routes->get('bag/code-kinds', 'Bag::codeKinds'); $routes->get('bag/code-details/(:num)', 'Bag::codeDetails/$1'); @@ -40,6 +43,7 @@ $routes->post('bag/issue/store', 'Bag::issueStore'); $routes->post('bag/issue/cancel/(:num)', 'Bag::issueCancel/$1'); $routes->get('bag/order/create', 'Bag::orderCreate'); $routes->post('bag/order/store', 'Bag::orderStore'); +$routes->post('bag/order/cancel/(:num)', 'Bag::orderCancel/$1'); $routes->get('bag/receiving/create', 'Bag::receivingCreate'); $routes->post('bag/receiving/store', 'Bag::receivingStore'); $routes->get('bag/sale/create', 'Bag::saleCreate'); @@ -47,6 +51,108 @@ $routes->post('bag/sale/store', 'Bag::saleStore'); $routes->get('bag/shop-order/create', 'Bag::shopOrderCreate'); $routes->post('bag/shop-order/store', 'Bag::shopOrderStore'); +// 메인 사이트 메뉴용 업무 URL (관리자 권한). 동일 컨트롤러가 URI 가 bag 이면 메인 사이트 레이아웃으로 렌더. +$routes->group('bag', ['filter' => 'adminAuth'], static function ($routes): void { + $routes->get('managers', 'Admin\Manager::index'); + $routes->get('managers/create', 'Admin\Manager::create'); + $routes->post('managers/store', 'Admin\Manager::store'); + $routes->get('managers/edit/(:num)', 'Admin\Manager::edit/$1'); + $routes->post('managers/update/(:num)', 'Admin\Manager::update/$1'); + $routes->post('managers/delete/(:num)', 'Admin\Manager::delete/$1'); + + $routes->get('sales-agencies', 'Admin\SalesAgency::index'); + $routes->get('sales-agencies/create', 'Admin\SalesAgency::create'); + $routes->post('sales-agencies/store', 'Admin\SalesAgency::store'); + $routes->get('sales-agencies/edit/(:num)', 'Admin\SalesAgency::edit/$1'); + $routes->post('sales-agencies/update/(:num)', 'Admin\SalesAgency::update/$1'); + $routes->post('sales-agencies/delete/(:num)', 'Admin\SalesAgency::delete/$1'); + + $routes->get('companies', 'Admin\Company::index'); + $routes->get('companies/create', 'Admin\Company::create'); + $routes->post('companies/store', 'Admin\Company::store'); + $routes->get('companies/edit/(:num)', 'Admin\Company::edit/$1'); + $routes->post('companies/update/(:num)', 'Admin\Company::update/$1'); + $routes->post('companies/delete/(:num)', 'Admin\Company::delete/$1'); + + $routes->get('free-recipients', 'Admin\FreeRecipient::index'); + $routes->get('free-recipients/create', 'Admin\FreeRecipient::create'); + $routes->post('free-recipients/store', 'Admin\FreeRecipient::store'); + $routes->get('free-recipients/edit/(:num)', 'Admin\FreeRecipient::edit/$1'); + $routes->post('free-recipients/update/(:num)', 'Admin\FreeRecipient::update/$1'); + $routes->post('free-recipients/delete/(:num)', 'Admin\FreeRecipient::delete/$1'); + + $routes->get('designated-shops/export', 'Admin\DesignatedShop::export'); + $routes->get('designated-shops/map', 'Admin\DesignatedShop::map'); + $routes->get('designated-shops/status', 'Admin\DesignatedShop::status'); + $routes->get('designated-shops', 'Admin\DesignatedShop::index'); + $routes->get('designated-shops/create', 'Admin\DesignatedShop::create'); + $routes->post('designated-shops/store', 'Admin\DesignatedShop::store'); + $routes->get('designated-shops/edit/(:num)', 'Admin\DesignatedShop::edit/$1'); + $routes->post('designated-shops/update/(:num)', 'Admin\DesignatedShop::update/$1'); + $routes->post('designated-shops/delete/(:num)', 'Admin\DesignatedShop::delete/$1'); + + $routes->get('bag-prices', 'Admin\BagPrice::index'); + $routes->get('bag-prices/create', 'Admin\BagPrice::create'); + $routes->post('bag-prices/store', 'Admin\BagPrice::store'); + $routes->get('bag-prices/edit/(:num)', 'Admin\BagPrice::edit/$1'); + $routes->post('bag-prices/update/(:num)', 'Admin\BagPrice::update/$1'); + $routes->post('bag-prices/delete/(:num)', 'Admin\BagPrice::delete/$1'); + $routes->get('bag-prices/history/(:num)', 'Admin\BagPrice::history/$1'); + + $routes->get('bag-orders/export', 'Admin\BagOrder::export'); + $routes->get('bag-orders', 'Admin\BagOrder::index'); + $routes->get('bag-orders/create', 'Admin\BagOrder::create'); + $routes->post('bag-orders/store', 'Admin\BagOrder::store'); + $routes->get('bag-orders/detail/(:num)', 'Admin\BagOrder::detail/$1'); + $routes->post('bag-orders/cancel/(:num)', 'Admin\BagOrder::cancel/$1'); + $routes->post('bag-orders/delete/(:num)', 'Admin\BagOrder::delete/$1'); + + $routes->get('bag-receivings', 'Admin\BagReceiving::index'); + $routes->get('bag-receivings/create', 'Admin\BagReceiving::create'); + $routes->post('bag-receivings/store', 'Admin\BagReceiving::store'); + + $routes->get('bag-inventory/export', 'Admin\BagInventory::export'); + $routes->get('bag-inventory', 'Admin\BagInventory::index'); + + $routes->get('shop-orders', 'Admin\ShopOrder::index'); + $routes->get('shop-orders/create', 'Admin\ShopOrder::create'); + $routes->post('shop-orders/store', 'Admin\ShopOrder::store'); + $routes->post('shop-orders/cancel/(:num)', 'Admin\ShopOrder::cancel/$1'); + + $routes->get('bag-sales/export', 'Admin\BagSale::export'); + $routes->get('bag-sales', 'Admin\BagSale::index'); + $routes->get('bag-sales/create', 'Admin\BagSale::create'); + $routes->post('bag-sales/store', 'Admin\BagSale::store'); + + $routes->get('bag-issues', 'Admin\BagIssue::index'); + $routes->get('bag-issues/create', 'Admin\BagIssue::create'); + $routes->post('bag-issues/store', 'Admin\BagIssue::store'); + $routes->post('bag-issues/cancel/(:num)', 'Admin\BagIssue::cancel/$1'); + + $routes->get('packaging-units/manage', 'Admin\PackagingUnit::index'); + $routes->get('packaging-units/manage/create', 'Admin\PackagingUnit::create'); + $routes->post('packaging-units/manage/store', 'Admin\PackagingUnit::store'); + $routes->get('packaging-units/manage/edit/(:num)', 'Admin\PackagingUnit::edit/$1'); + $routes->post('packaging-units/manage/update/(:num)', 'Admin\PackagingUnit::update/$1'); + $routes->post('packaging-units/manage/delete/(:num)', 'Admin\PackagingUnit::delete/$1'); + $routes->get('packaging-units/manage/history/(:num)', 'Admin\PackagingUnit::history/$1'); + + $routes->get('reports/sales-ledger', 'Admin\SalesReport::salesLedger'); + $routes->get('reports/daily-summary', 'Admin\SalesReport::dailySummary'); + $routes->get('reports/period-sales', 'Admin\SalesReport::periodSales'); + $routes->get('reports/supply-demand', 'Admin\SalesReport::supplyDemand'); + $routes->get('reports/yearly-sales', 'Admin\SalesReport::yearlySales'); + $routes->get('reports/shop-sales', 'Admin\SalesReport::shopSales'); + $routes->get('reports/hometax-export', 'Admin\SalesReport::hometaxExport'); + $routes->get('reports/returns', 'Admin\SalesReport::returns'); + $routes->get('reports/lot-flow', 'Admin\SalesReport::lotFlow'); + $routes->get('reports/misc-flow', 'Admin\SalesReport::miscFlow'); + $routes->post('reports/misc-flow', 'Admin\SalesReport::miscFlowStore'); + + $routes->get('password-change', 'Admin\PasswordChange::index'); + $routes->post('password-change', 'Admin\PasswordChange::update'); +}); + // Auth $routes->get('login', 'Auth::showLoginForm'); $routes->post('login', 'Auth::login'); @@ -90,10 +196,6 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo $routes->post('local-governments/update/(:num)', 'Admin\LocalGovernment::update/$1'); $routes->post('local-governments/delete/(:num)', 'Admin\LocalGovernment::delete/$1'); - // 비밀번호 변경 (P2-20) - $routes->get('password-change', 'Admin\PasswordChange::index'); - $routes->post('password-change', 'Admin\PasswordChange::update'); - // 기본코드 종류 관리 (P2-01) — 등록·수정·삭제는 관리자 전용 $routes->get('code-kinds/create', 'Admin\CodeKind::create'); $routes->post('code-kinds/store', 'Admin\CodeKind::store'); @@ -108,112 +210,26 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo $routes->post('code-details/update/(:num)', 'Admin\CodeDetail::update/$1'); $routes->post('code-details/delete/(:num)', 'Admin\CodeDetail::delete/$1'); - // 봉투 단가 관리 (P2-03/04) - $routes->get('bag-prices', 'Admin\BagPrice::index'); - $routes->get('bag-prices/create', 'Admin\BagPrice::create'); - $routes->post('bag-prices/store', 'Admin\BagPrice::store'); - $routes->get('bag-prices/edit/(:num)', 'Admin\BagPrice::edit/$1'); - $routes->post('bag-prices/update/(:num)', 'Admin\BagPrice::update/$1'); - $routes->post('bag-prices/delete/(:num)', 'Admin\BagPrice::delete/$1'); - $routes->get('bag-prices/history/(:num)', 'Admin\BagPrice::history/$1'); - - // 발주 관리 (P3-01~05) - $routes->get('bag-orders/export', 'Admin\BagOrder::export'); - $routes->get('bag-orders', 'Admin\BagOrder::index'); - $routes->get('bag-orders/create', 'Admin\BagOrder::create'); - $routes->post('bag-orders/store', 'Admin\BagOrder::store'); - $routes->get('bag-orders/detail/(:num)', 'Admin\BagOrder::detail/$1'); - $routes->post('bag-orders/cancel/(:num)', 'Admin\BagOrder::cancel/$1'); - $routes->post('bag-orders/delete/(:num)', 'Admin\BagOrder::delete/$1'); - - // 입고 관리 (P3-06~09) - $routes->get('bag-receivings', 'Admin\BagReceiving::index'); - $routes->get('bag-receivings/create', 'Admin\BagReceiving::create'); - $routes->post('bag-receivings/store', 'Admin\BagReceiving::store'); - - // 재고 현황 (P3-10) - $routes->get('bag-inventory/export', 'Admin\BagInventory::export'); - $routes->get('bag-inventory', 'Admin\BagInventory::index'); - - // 주문 접수 관리 (P4-01~03) - $routes->get('shop-orders', 'Admin\ShopOrder::index'); - $routes->get('shop-orders/create', 'Admin\ShopOrder::create'); - $routes->post('shop-orders/store', 'Admin\ShopOrder::store'); - $routes->post('shop-orders/cancel/(:num)', 'Admin\ShopOrder::cancel/$1'); - - // 판매/반품 관리 (P4-04~07) - $routes->get('bag-sales/export', 'Admin\BagSale::export'); - $routes->get('bag-sales', 'Admin\BagSale::index'); - $routes->get('bag-sales/create', 'Admin\BagSale::create'); - $routes->post('bag-sales/store', 'Admin\BagSale::store'); - - // 무료용 불출 관리 (P4-08~10) - $routes->get('bag-issues', 'Admin\BagIssue::index'); - $routes->get('bag-issues/create', 'Admin\BagIssue::create'); - $routes->post('bag-issues/store', 'Admin\BagIssue::store'); - $routes->post('bag-issues/cancel/(:num)', 'Admin\BagIssue::cancel/$1'); - - // 포장 단위 관리 (P2-05/06) - $routes->get('packaging-units', 'Admin\PackagingUnit::index'); - $routes->get('packaging-units/create', 'Admin\PackagingUnit::create'); - $routes->post('packaging-units/store', 'Admin\PackagingUnit::store'); - $routes->get('packaging-units/edit/(:num)', 'Admin\PackagingUnit::edit/$1'); - $routes->post('packaging-units/update/(:num)', 'Admin\PackagingUnit::update/$1'); - $routes->post('packaging-units/delete/(:num)', 'Admin\PackagingUnit::delete/$1'); - $routes->get('packaging-units/history/(:num)', 'Admin\PackagingUnit::history/$1'); - - // 현황/리포트 (Phase 5) - $routes->get('reports/sales-ledger', 'Admin\SalesReport::salesLedger'); - $routes->get('reports/daily-summary', 'Admin\SalesReport::dailySummary'); - $routes->get('reports/period-sales', 'Admin\SalesReport::periodSales'); - $routes->get('reports/supply-demand', 'Admin\SalesReport::supplyDemand'); - $routes->get('reports/yearly-sales', 'Admin\SalesReport::yearlySales'); - $routes->get('reports/shop-sales', 'Admin\SalesReport::shopSales'); - $routes->get('reports/hometax-export', 'Admin\SalesReport::hometaxExport'); - $routes->get('reports/returns', 'Admin\SalesReport::returns'); - $routes->get('reports/lot-flow', 'Admin\SalesReport::lotFlow'); - $routes->get('reports/misc-flow', 'Admin\SalesReport::miscFlow'); - $routes->post('reports/misc-flow', 'Admin\SalesReport::miscFlowStore'); - - // 판매 대행소 관리 (P2-07/08) - $routes->get('sales-agencies', 'Admin\SalesAgency::index'); - $routes->get('sales-agencies/create', 'Admin\SalesAgency::create'); - $routes->post('sales-agencies/store', 'Admin\SalesAgency::store'); - $routes->get('sales-agencies/edit/(:num)', 'Admin\SalesAgency::edit/$1'); - $routes->post('sales-agencies/update/(:num)', 'Admin\SalesAgency::update/$1'); - $routes->post('sales-agencies/delete/(:num)', 'Admin\SalesAgency::delete/$1'); - - // 담당자 관리 (P2-09/10) - $routes->get('managers', 'Admin\Manager::index'); - $routes->get('managers/create', 'Admin\Manager::create'); - $routes->post('managers/store', 'Admin\Manager::store'); - $routes->get('managers/edit/(:num)', 'Admin\Manager::edit/$1'); - $routes->post('managers/update/(:num)', 'Admin\Manager::update/$1'); - $routes->post('managers/delete/(:num)', 'Admin\Manager::delete/$1'); - - // 업체 관리 (P2-11/12) - $routes->get('companies', 'Admin\Company::index'); - $routes->get('companies/create', 'Admin\Company::create'); - $routes->post('companies/store', 'Admin\Company::store'); - $routes->get('companies/edit/(:num)', 'Admin\Company::edit/$1'); - $routes->post('companies/update/(:num)', 'Admin\Company::update/$1'); - $routes->post('companies/delete/(:num)', 'Admin\Company::delete/$1'); - - // 무료용 대상자 관리 (P2-13/14) - $routes->get('free-recipients', 'Admin\FreeRecipient::index'); - $routes->get('free-recipients/create', 'Admin\FreeRecipient::create'); - $routes->post('free-recipients/store', 'Admin\FreeRecipient::store'); - $routes->get('free-recipients/edit/(:num)', 'Admin\FreeRecipient::edit/$1'); - $routes->post('free-recipients/update/(:num)', 'Admin\FreeRecipient::update/$1'); - $routes->post('free-recipients/delete/(:num)', 'Admin\FreeRecipient::delete/$1'); - - $routes->get('designated-shops/export', 'Admin\DesignatedShop::export'); - $routes->get('designated-shops/map', 'Admin\DesignatedShop::map'); - $routes->get('designated-shops/status', 'Admin\DesignatedShop::status'); - $routes->get('designated-shops', 'Admin\DesignatedShop::index'); - $routes->get('designated-shops/create', 'Admin\DesignatedShop::create'); - $routes->post('designated-shops/store', 'Admin\DesignatedShop::store'); - $routes->get('designated-shops/edit/(:num)', 'Admin\DesignatedShop::edit/$1'); - $routes->post('designated-shops/update/(:num)', 'Admin\DesignatedShop::update/$1'); - $routes->post('designated-shops/delete/(:num)', 'Admin\DesignatedShop::delete/$1'); + // 구 업무 URL → /bag/* (실제 처리는 bag 그룹). GET 301, POST 307. + $adminToBagPrefixes = [ + 'managers', + 'sales-agencies', + 'companies', + 'free-recipients', + 'designated-shops', + 'bag-prices', + 'bag-orders', + 'bag-receivings', + 'bag-inventory', + 'shop-orders', + 'bag-sales', + 'bag-issues', + 'packaging-units', + 'reports', + 'password-change', + ]; + foreach ($adminToBagPrefixes as $p) { + $routes->match(['get', 'post'], $p, 'Admin\WorkMovedToBag::toBag/' . $p); + $routes->match(['get', 'post'], $p . '/(:any)', 'Admin\WorkMovedToBag::toBag/' . $p . '/$1'); + } }); diff --git a/app/Controllers/Admin/WorkMovedToBag.php b/app/Controllers/Admin/WorkMovedToBag.php new file mode 100644 index 0000000..c9732cd --- /dev/null +++ b/app/Controllers/Admin/WorkMovedToBag.php @@ -0,0 +1,41 @@ +request->getUri()->getQuery(); + if ($query !== '') { + $target .= '?' . $query; + } + + $code = $this->request->getMethod() === 'post' ? 307 : 301; + + return redirect()->to($target)->setStatusCode($code); + } +} diff --git a/app/Controllers/BaseController.php b/app/Controllers/BaseController.php index ab45a77..a027808 100644 --- a/app/Controllers/BaseController.php +++ b/app/Controllers/BaseController.php @@ -42,4 +42,31 @@ abstract class BaseController extends Controller // Preload any models, libraries, etc, here. // $this->session = service('session'); } + + /** + * /admin/* 또는 /bag/* 업무 화면 공통: 요청이 bag 이면 메인 사이트 레이아웃, 아니면 관리자 레이아웃. + * + * @param array $contentData + */ + protected function renderWorkPage(string $title, string $contentView, array $contentData = []): string + { + $content = view($contentView, $contentData); + $uri = service('request')->getUri(); + $seg1 = $uri->getSegment(1); + $seg2 = $uri->getSegment(2); + + // 지정판매소 관리는 관리자 전용 기능으로, /bag 경로여도 관리자 레이아웃을 유지한다. + $forceAdminLayoutOnBag = ($seg1 === 'bag' && $seg2 === 'designated-shops'); + if ($seg1 === 'bag' && ! $forceAdminLayoutOnBag) { + return view('bag/layout/main', [ + 'title' => $title, + 'content' => $content, + ]); + } + + return view('admin/layout', [ + 'title' => $title, + 'content' => $content, + ]); + } } diff --git a/app/Controllers/Home.php b/app/Controllers/Home.php index e120e06..89c7065 100644 --- a/app/Controllers/Home.php +++ b/app/Controllers/Home.php @@ -16,11 +16,16 @@ class Home extends BaseController } /** - * 로그인 후 원래 메인 화면 (admin 유사 레이아웃 + site 메뉴 호버 드롭다운) + * 로그인 후 메인 — site 메뉴 레이아웃 + 종합·그래프(blend) 본문 */ public function dashboard() { - return view('bag/daily_inventory'); + return view('bag/layout/main', [ + 'title' => '업무 현황 · 종합·그래프', + 'content' => view('bag/dashboard_blend_inner', [ + 'lgLabel' => $this->resolveLgLabel(), + ]), + ]); } /** @@ -62,13 +67,11 @@ class Home extends BaseController } /** - * dense(표·KPI) + charts(Chart.js) 혼합. URL: /dashboard/blend + * /dashboard 와 동일 본문(호환 URL) */ public function dashboardBlend() { - return view('bag/lg_dashboard_blend', [ - 'lgLabel' => $this->resolveLgLabel(), - ]); + return $this->dashboard(); } /** @@ -114,4 +117,5 @@ class Home extends BaseController return '북구 (데모)'; } + } diff --git a/app/Helpers/admin_helper.php b/app/Helpers/admin_helper.php index ea11dd1..516a411 100644 --- a/app/Helpers/admin_helper.php +++ b/app/Helpers/admin_helper.php @@ -6,8 +6,9 @@ use Config\Roles; if (! function_exists('admin_effective_lg_idx')) { /** - * 현재 로그인한 관리자가 작업 대상으로 사용하는 지자체 PK. - * Super/본부 관리자 → admin_selected_lg_idx, 지자체 관리자 → mb_lg_idx, 그 외 null. + * 관리자 화면·사이트 메뉴·Bag 등에서 쓰는 작업 지자체 PK. + * Super/본부 → admin_selected_lg_idx(미선택 시 null). + * 지자체관리자·지정판매소·일반 사용자 → mb_lg_idx(없으면 null). */ function admin_effective_lg_idx(): ?int { @@ -16,7 +17,9 @@ if (! function_exists('admin_effective_lg_idx')) { $idx = session()->get('admin_selected_lg_idx'); return $idx !== null && $idx !== '' ? (int) $idx : null; } - if ($level === Roles::LEVEL_LOCAL_ADMIN) { + if ($level === Roles::LEVEL_LOCAL_ADMIN + || $level === Roles::LEVEL_SHOP + || $level === Roles::LEVEL_CITIZEN) { $idx = session()->get('mb_lg_idx'); return $idx !== null && $idx !== '' ? (int) $idx : null; } @@ -24,6 +27,23 @@ if (! function_exists('admin_effective_lg_idx')) { } } +if (! function_exists('resolve_site_menu_lg_idx')) { + /** + * site 상단 메뉴(menu 테이블) 조회용 지자체 PK. + * admin_effective_lg_idx() 우선(메뉴 관리·Bag과 동일), 없으면 mb_lg_idx, 그다음 기본 1. + */ + function resolve_site_menu_lg_idx(): int + { + $lgIdx = admin_effective_lg_idx(); + if ($lgIdx !== null) { + return $lgIdx; + } + $raw = session()->get('mb_lg_idx'); + + return ($raw !== null && $raw !== '') ? (int) $raw : 1; + } +} + if (! function_exists('get_admin_nav_items')) { /** * 관리자 상단 메뉴 항목 (DB menu 테이블, admin 타입, 현재 지자체·mb_level 기준, 평면 배열). @@ -130,11 +150,7 @@ if (! function_exists('get_site_nav_tree')) { function get_site_nav_tree(): array { try { - $lgIdx = session()->get('mb_lg_idx'); - // 시민 등 지자체 정보가 세션에 없으면 기본 지자체(1) 기준으로 메뉴를 보여 준다. - if ($lgIdx === null || $lgIdx === '') { - $lgIdx = 1; - } + $lgIdx = resolve_site_menu_lg_idx(); $typeRow = model(\App\Models\MenuTypeModel::class)->getByCode('site'); if (! $typeRow) { return []; @@ -156,3 +172,319 @@ if (! function_exists('get_site_nav_tree')) { } } } + +if (! function_exists('current_nav_request_path')) { + /** + * 메뉴 활성·mm_link 비교용 현재 경로 (라우트 기준, base_url 뒤 세그먼트). + * request->getPath() · uri_string() · SiteURI::getRoutePath() 중 비어 있지 않은 값을 사용. + */ + function current_nav_request_path(): string + { + helper('url'); + + $request = service('request'); + // 프레임워크 권장: uri_string() = baseURL 기준 경로 (우선) + $candidates = [trim(uri_string(), '/')]; + if ($request instanceof \CodeIgniter\HTTP\IncomingRequest) { + $candidates[] = trim((string) $request->getPath(), '/'); + } + $uri = $request->getUri(); + if ($uri instanceof \CodeIgniter\HTTP\SiteURI) { + $candidates[] = trim($uri->getRoutePath(), '/'); + } + + $path = ''; + foreach ($candidates as $c) { + if ($c !== '') { + $path = $c; + break; + } + } + + while (str_starts_with($path, 'index.php/')) { + $path = substr($path, strlen('index.php/')); + } + + // baseURL 에 경로가 있으면(서브폴더 설치) URI 앞에 붙은 동일 접두 제거 + $basePath = parse_url(config(\Config\App::class)->baseURL, PHP_URL_PATH); + $basePath = is_string($basePath) ? trim($basePath, '/') : ''; + if ($basePath !== '' && $path !== '' && ($path === $basePath || str_starts_with($path, $basePath . '/'))) { + $path = $path === $basePath ? '' : substr($path, strlen($basePath) + 1); + } + + return $path; + } +} + +if (! function_exists('normalize_menu_link_for_url')) { + /** + * menu.mm_link 를 base_url() 인자로 쓸 수 있는 상대 경로로 정규화합니다. + * http(s)://... 전체 URL이면 path 만 사용하고, 앞뒤 공백·슬래시를 정리합니다. + */ + function normalize_menu_link_for_url(?string $mmLink): string + { + $s = trim((string) $mmLink); + if ($s === '') { + return ''; + } + if (str_contains($s, '://')) { + $path = parse_url($s, PHP_URL_PATH); + $s = is_string($path) ? trim($path, '/') : ''; + } else { + $s = trim($s, '/'); + } + + while (str_starts_with($s, 'index.php/')) { + $s = substr($s, strlen('index.php/')); + } + if (str_starts_with($s, 'public/')) { + $s = substr($s, strlen('public/')); + } + + $basePath = parse_url(config(\Config\App::class)->baseURL, PHP_URL_PATH); + $basePath = is_string($basePath) ? trim($basePath, '/') : ''; + if ($basePath !== '' && $s !== '' && ($s === $basePath || str_starts_with($s, $basePath . '/'))) { + $s = $s === $basePath ? '' : substr($s, strlen($basePath) + 1); + } + + return $s; + } +} + +if (! function_exists('mgmt_url')) { + /** + * 업무 화면 링크: 정식 URL 은 /bag/* (adminAuth). 포장 단위 CRUD 는 packaging-units/manage 로 치환. + */ + function mgmt_url(string $path): string + { + helper('url'); + $path = trim($path, '/'); + // bag/packaging-units 는 조회 전용(Bag) — CRUD 는 /bag/packaging-units/manage/* 로 분리 + if ($path === 'packaging-units') { + $path = 'packaging-units/manage'; + } elseif (str_starts_with($path, 'packaging-units/')) { + $path = 'packaging-units/manage/' . substr($path, strlen('packaging-units/')); + } + + return site_url('bag/' . $path); + } +} + +if (! function_exists('work_area_home_url')) { + /** + * 지자체 미선택 등으로 돌아갈 때: bag 업무 중이면 대시보드, 관리자면 admin 홈. + */ + function work_area_home_url(): string + { + helper('url'); + $seg1 = service('request')->getUri()->getSegment(1); + + return ($seg1 === 'bag') ? site_url('dashboard') : site_url('admin'); + } +} + +if (! function_exists('format_ymd_korean')) { + /** + * Y-m-d 날짜를 '2026년 1월 5일' 형식으로 (월·일은 숫자, 월명은 한글 '월'). + */ + function format_ymd_korean(?string $ymd): string + { + if ($ymd === null || trim($ymd) === '') { + return '—'; + } + $t = \DateTimeImmutable::createFromFormat('Y-m-d', trim($ymd)); + if ($t === false) { + return $ymd; + } + + return $t->format('Y') . '년 ' . (int) $t->format('n') . '월 ' . (int) $t->format('j') . '일'; + } +} + +if (! function_exists('parse_ymd_from_triple')) { + /** + * 연·월·일 GET 값으로 Y-m-d 생성 (유효하지 않은 날짜는 null). + */ + function parse_ymd_from_triple(?string $y, ?string $m, ?string $d): ?string + { + if ($y === null || $y === '' || $m === null || $m === '' || $d === null || $d === '') { + return null; + } + $yi = (int) $y; + $mi = (int) $m; + $di = (int) $d; + if ($yi < 1000 || $yi > 9999 || ! checkdate($mi, $di, $yi)) { + return null; + } + + return sprintf('%04d-%02d-%02d', $yi, $mi, $di); + } +} + +if (! function_exists('site_nav_resolved_link_path')) { + /** + * 사이트 상단 메뉴 URL 세그먼트. mm_link(DB)만 사용 (비어 있으면 빈 문자열). + * + * @param string|null $mmName 호환용(미사용). + * + * @return string base_url() 인자 세그먼트(앞뒤 슬래시 없음) + */ + function site_nav_resolved_link_path(?string $mmLink, ?string $mmName = null): string + { + return normalize_menu_link_for_url($mmLink); + } +} + +if (! function_exists('menu_link_candidate_paths')) { + /** + * 활성 비교용 경로 후보. DB에 "menus" 처럼 짧게 넣은 경우 실제 URI가 admin/menus·bag/… 일 수 있어, + * 현재 요청 경로에 맞게 admin/·bag/ 접두를 붙인 후보도 만든다. (슬래시 포함·admin 단독은 그대로 1개만) + * + * @return list + */ + function menu_link_candidate_paths(?string $mmLink, string $currentPath): array + { + $p = normalize_menu_link_for_url($mmLink); + if ($p === '') { + return []; + } + if (str_contains($p, '/') || $p === 'admin') { + $cands = [$p]; + if (preg_match('#^bag/packaging-units/manage(/.*)?$#', $p, $m)) { + $cands[] = 'admin/packaging-units' . ($m[1] ?? ''); + } elseif (preg_match('#^admin/packaging-units(/.*)?$#', $p, $m)) { + $cands[] = 'bag/packaging-units/manage' . ($m[1] ?? ''); + } elseif (str_starts_with($p, 'admin/')) { + $cands[] = 'bag/' . substr($p, strlen('admin/')); + } elseif (str_starts_with($p, 'bag/')) { + $cands[] = 'admin/' . substr($p, strlen('bag/')); + } + + return array_values(array_unique($cands)); + } + $out = [$p]; + if (str_starts_with($currentPath, 'admin/') || $currentPath === 'admin') { + $out[] = 'admin/' . $p; + } + if (str_starts_with($currentPath, 'bag/') || $currentPath === 'bag') { + $out[] = 'bag/' . $p; + } + + return array_values(array_unique($out)); + } +} + +if (! function_exists('menu_link_preferred_href_path')) { + /** + * base_url() 용 경로: 짧게 저장된 mm_link 는 현재 요청 기준 admin/·bag/ 후보 중 가장 알맞은 것 사용. + */ + function menu_link_preferred_href_path(?string $mmLink, string $currentPath): string + { + $cands = menu_link_candidate_paths($mmLink, $currentPath); + if ($cands === []) { + return ''; + } + foreach ($cands as $c) { + $cl = strtolower($currentPath); + $cc = strtolower($c); + if ($cl === $cc || str_starts_with($cl, $cc . '/')) { + return $c; + } + } + foreach ($cands as $c) { + if (str_contains($c, '/')) { + return $c; + } + } + + return $cands[0]; + } +} + +if (! function_exists('menu_single_path_matches_request')) { + /** + * 단일 정규 경로가 현재 요청 path 와 일치하는지. + * + * @param list $dashboardPathAliases + */ + function menu_single_path_matches_request(string $path, string $currentPath, array $dashboardPathAliases = []): bool + { + if ($path === '') { + return false; + } + $pathLower = strtolower($path); + $currentLower = strtolower($currentPath); + $aliasesLower = array_map(strtolower(...), $dashboardPathAliases); + + if ($dashboardPathAliases !== [] + && in_array($pathLower, $aliasesLower, true) + && in_array($currentLower, $aliasesLower, true)) { + return true; + } + if ($currentLower === $pathLower) { + return true; + } + if ($pathLower === 'admin') { + return false; + } + + return str_starts_with($currentLower, $pathLower . '/'); + } +} + +if (! function_exists('menu_link_matches_request')) { + /** + * 메뉴 mm_link(DB)가 현재 요청과 같은 메뉴인지. 비어 있으면 false. + * + * @param list $dashboardPathAliases + */ + function menu_link_matches_request(?string $mmLink, string $currentPath, array $dashboardPathAliases = []): bool + { + foreach (menu_link_candidate_paths($mmLink, $currentPath) as $cand) { + if (menu_single_path_matches_request($cand, $currentPath, $dashboardPathAliases)) { + return true; + } + } + + return false; + } +} + +if (! function_exists('site_nav_link_matches_current')) { + /** + * 사이트 상단 메뉴 활성 여부 (경로 후보·대시보드 별칭은 menu_link_matches_request 와 동일). + * + * @param list $dashboardPathAliases + */ + function site_nav_link_matches_current(?string $mmLink, string $currentPath, array $dashboardPathAliases = []): bool + { + return menu_link_matches_request($mmLink, $currentPath, $dashboardPathAliases); + } +} + +if (! function_exists('session_user_nav_display')) { + /** + * 상단 메뉴바용: 로그인 사용자 이름·역할 표시 + * + * @return array{name: string, role_label: string}|null + */ + function session_user_nav_display(): ?array + { + if (! session()->get('logged_in')) { + return null; + } + + $name = trim((string) session()->get('mb_name')); + if ($name === '') { + $name = (string) session()->get('mb_id'); + } + + $level = (int) session()->get('mb_level'); + $roleLabel = config('Roles')->getLevelName($level); + + return [ + 'name' => $name, + 'role_label' => $roleLabel, + ]; + } +} diff --git a/app/Views/admin/layout.php b/app/Views/admin/layout.php index 2972153..1fc8265 100644 --- a/app/Views/admin/layout.php +++ b/app/Views/admin/layout.php @@ -12,15 +12,17 @@ if ($effectiveLgIdx) { $lgRow = model(\App\Models\LocalGovernmentModel::class)->find($effectiveLgIdx); $effectiveLgName = $lgRow ? $lgRow->lg_name : null; } -$currentPath = trim((string) $uriObj->getPath(), '/'); -if (str_starts_with($currentPath, 'index.php/')) { - $currentPath = substr($currentPath, strlen('index.php/')); -} +$userNav = session_user_nav_display(); +$currentPath = current_nav_request_path(); $adminNavTree = get_admin_nav_tree(); -$isActive = static function (string $path) use ($uri, $seg3, $currentPath, $adminNavTree) { - if (! empty($adminNavTree)) { - return $currentPath === trim($path, '/'); - } + +/** DB 링크(mm_link)만 사용. 짧게 적은 항목(menus 등)은 실제 URI(admin/menus)와 맞춰 후보 비교 */ +$adminNavItemIsCurrent = static function (?string $mmLink) use ($currentPath): bool { + return menu_link_matches_request($mmLink, $currentPath, []); +}; + +/** 메뉴가 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'; @@ -38,7 +40,7 @@ $isActive = static function (string $path) use ($uri, $seg3, $currentPath, $admi -<?= esc($title ?? '관리자') ?> - 쓰레기봉투 물류시스템 +<?= esc($title ?? '관리자') ?> - 종량제 시스템 - - - - - -
- -
+ + + -
+
+
종합·그래프 혼합 현황 - · dense 표/KPI + Chart.js + · dense 표/KPI + Chart.js (목업)
| - 기준지자체 - + · +
-
- getFlashdata('success')): ?> -
getFlashdata('success')) ?>
- - +
@@ -196,7 +139,6 @@ $notices = [
-
@@ -295,7 +237,6 @@ $notices = [
-

규격 출고 비중

@@ -432,13 +373,14 @@ $notices = [
-

- /dashboard/blend (표 + 차트 혼합) +

+ 메인 /dashboard + · 동일 본문 /dashboard/blend · /dashboard/dense · /dashboard/charts · 클래식

-
+
- - +
diff --git a/app/Views/bag/layout/main.php b/app/Views/bag/layout/main.php index 71a851b..1c2bf70 100644 --- a/app/Views/bag/layout/main.php +++ b/app/Views/bag/layout/main.php @@ -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(); ?> -<?= esc($title ?? '쓰레기봉투 물류시스템') ?> +<?= esc($title ?? '종량제 시스템') ?>