feat: GBLS 리브랜딩 + 매뉴얼 보강 + 워크스페이스/코드관리 UX 개선
리브랜딩 - 서비스명 "종량제 시스템" → "GBLS", 헤더 로고에 풀네임(Garbage Bag Logistics System) 병기 (gov-portal·공통 브랜드·로그인/welcome 셸·타이틀·푸터 전반) 매뉴얼 - 신규 페이지 [로그인·회원가입·계정](01_account.md): 가입 항목·관리자 승인·2차 인증 흐름 - [화면 구성·워크스페이스·단축키]에 계정 전환 시 탭 초기화 안내 추가 워크스페이스(탭) - 탭 전환 시 좌측 사이드바 강조 동기화(메뉴 없는 화면은 강조 해제, 경로 폴백 매칭) - 소메뉴 좌측 아이콘(▸/·) 전부 제거 — 활성 메뉴는 배경 강조로만 구분 - 탭을 사용자(mb_idx)별로 격리: 다른 아이디 로그인 시 이전 탭 복원 안 함 - 사이드바 FAQ 링크 제거(자주 묻는 질문은 매뉴얼에 통합) 기본 코드관리 화면 - 업무현황 카드 스타일로 재디자인(가벼운 표·상태/범위 pill·단일 구분선) - render()에 $bare 옵션 추가 → 이미 카드형인 화면은 바깥 래퍼 생략 기타 - .claude/settings.local.json(개인 권한 설정) .gitignore 추가 - e2e: 워크스페이스(동기화·계정격리) + 매뉴얼(계정·단축키·검색) 케이스 보강 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -176,3 +176,6 @@ blob-report/
|
|||||||
/phpunit*.xml
|
/phpunit*.xml
|
||||||
# 최상위 개발 문서/스크린샷 폴더만 제외 (app/Docs 등 하위 docs 경로는 추적).
|
# 최상위 개발 문서/스크린샷 폴더만 제외 (app/Docs 등 하위 docs 경로는 추적).
|
||||||
/docs/
|
/docs/
|
||||||
|
|
||||||
|
# Claude Code 개인 권한 설정(비밀 포함) — 커밋 금지
|
||||||
|
.claude/settings.local.json
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class Manual extends BaseConfig
|
|||||||
*/
|
*/
|
||||||
public array $pages = [
|
public array $pages = [
|
||||||
'overview' => ['title' => '시작하기·시스템 개요', 'file' => '00_overview.md'],
|
'overview' => ['title' => '시작하기·시스템 개요', 'file' => '00_overview.md'],
|
||||||
|
'account' => ['title' => '로그인·회원가입·계정', 'file' => '01_account.md'],
|
||||||
'workspace' => ['title' => '화면 구성·워크스페이스·단축키', 'file' => '05_workspace.md'],
|
'workspace' => ['title' => '화면 구성·워크스페이스·단축키', 'file' => '05_workspace.md'],
|
||||||
'flow' => ['title' => '핵심 업무 흐름', 'file' => '10_workflow.md'],
|
'flow' => ['title' => '핵심 업무 흐름', 'file' => '10_workflow.md'],
|
||||||
'order' => ['title' => '발주·입고', 'file' => '20_order_receiving.md'],
|
'order' => ['title' => '발주·입고', 'file' => '20_order_receiving.md'],
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Auth extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
return view('auth/login', [
|
return view('auth/login', [
|
||||||
'pageTitle' => '로그인 - 종량제 시스템',
|
'pageTitle' => '로그인 - GBLS',
|
||||||
'cardMax' => 'max-w-md',
|
'cardMax' => 'max-w-md',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -160,7 +160,7 @@ class Auth extends BaseController
|
|||||||
|
|
||||||
return view('auth/login_two_factor', [
|
return view('auth/login_two_factor', [
|
||||||
'memberId' => $member->mb_id,
|
'memberId' => $member->mb_id,
|
||||||
'pageTitle' => '2차 인증 - 종량제 시스템',
|
'pageTitle' => '2차 인증 - GBLS',
|
||||||
'cardMax' => 'max-w-md',
|
'cardMax' => 'max-w-md',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,7 @@ class Auth extends BaseController
|
|||||||
'memberId' => $member->mb_id,
|
'memberId' => $member->mb_id,
|
||||||
'qrDataUri' => $qrDataUri,
|
'qrDataUri' => $qrDataUri,
|
||||||
'secret' => $secret,
|
'secret' => $secret,
|
||||||
'pageTitle' => '2차 인증 등록 - 종량제 시스템',
|
'pageTitle' => '2차 인증 등록 - GBLS',
|
||||||
'cardMax' => 'max-w-lg',
|
'cardMax' => 'max-w-lg',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -348,7 +348,7 @@ class Auth extends BaseController
|
|||||||
|
|
||||||
return view('auth/register', [
|
return view('auth/register', [
|
||||||
'localGovernments' => $localGovernments,
|
'localGovernments' => $localGovernments,
|
||||||
'pageTitle' => '회원가입 - 종량제 시스템',
|
'pageTitle' => '회원가입 - GBLS',
|
||||||
'cardMax' => 'max-w-md',
|
'cardMax' => 'max-w-md',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ class Bag extends BaseController
|
|||||||
return $row ? trim((string) ($row->mg_name ?? '')) : '';
|
return $row ? trim((string) ($row->mg_name ?? '')) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private function render(string $title, string $viewFile, array $data = []): string
|
private function render(string $title, string $viewFile, array $data = [], bool $bare = false): string
|
||||||
{
|
{
|
||||||
// /workspace 탭(iframe) 안에서는 임베드 레이아웃(헤더·사이드바 없이 본문만).
|
// /workspace 탭(iframe) 안에서는 임베드 레이아웃(헤더·사이드바 없이 본문만).
|
||||||
$layout = $this->isEmbeddedRequest() ? 'bag/layout/embed' : 'bag/layout/portal';
|
$layout = $this->isEmbeddedRequest() ? 'bag/layout/embed' : 'bag/layout/portal';
|
||||||
@@ -220,6 +220,7 @@ class Bag extends BaseController
|
|||||||
return view($layout, [
|
return view($layout, [
|
||||||
'title' => $title,
|
'title' => $title,
|
||||||
'content' => view($viewFile, $data),
|
'content' => view($viewFile, $data),
|
||||||
|
'bare' => $bare, // true면 바깥 카드 래퍼 없이 본문을 그대로(이미 카드형 화면용)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +592,7 @@ class Bag extends BaseController
|
|||||||
'selectedKind' => $selectedKind,
|
'selectedKind' => $selectedKind,
|
||||||
'detailList' => $detailList,
|
'detailList' => $detailList,
|
||||||
'rowCanEdit' => $rowCanEdit,
|
'rowCanEdit' => $rowCanEdit,
|
||||||
]);
|
], true); // 본문이 이미 카드 2개라 바깥 래퍼 생략
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Home extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 메인 대시보드용 — 종량제 시스템에 실제 존재하는 데이터만 집계.
|
* 메인 대시보드용 — GBLS에 실제 존재하는 데이터만 집계.
|
||||||
*
|
*
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
|||||||
61
app/Docs/manual/01_account.md
Normal file
61
app/Docs/manual/01_account.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# 로그인 · 회원가입 · 계정
|
||||||
|
|
||||||
|
시스템을 쓰려면 **계정(아이디)** 이 필요합니다. 이 페이지는 회원가입부터 로그인, 2차 인증, 로그아웃까지의 과정을 설명합니다.
|
||||||
|
|
||||||
|
## 1. 회원가입
|
||||||
|
|
||||||
|
로그인 화면 아래쪽 **[회원가입]** 을 눌러 신청합니다. 입력 항목은 다음과 같습니다.
|
||||||
|
|
||||||
|
| 항목 | 필수 | 설명 |
|
||||||
|
|---|:---:|---|
|
||||||
|
| **아이디** | 필수 | 로그인에 쓰는 ID. **4자 이상**, 이미 쓰는 아이디는 사용할 수 없습니다. |
|
||||||
|
| **비밀번호** | 필수 | **4자 이상**. 안전을 위해 영문·숫자·기호를 섞는 것을 권장합니다. |
|
||||||
|
| **비밀번호 확인** | 필수 | 위 비밀번호와 똑같이 한 번 더 입력(오타 방지). |
|
||||||
|
| **이름** | 필수 | 담당자 이름. |
|
||||||
|
| **이메일** | 선택 | 안내용. 입력 시 형식 검사를 합니다. |
|
||||||
|
| **연락처** | 선택 | 전화번호. |
|
||||||
|
| **지자체** | 선택 | 소속 지자체. 해당되면 선택합니다. |
|
||||||
|
| **사용자 역할** | 필수 | 신청할 권한(아래 표 참고). 실제 권한은 **관리자 승인 시 확정**됩니다. |
|
||||||
|
|
||||||
|
> 이메일·연락처 같은 개인정보는 **암호화되어 저장**됩니다.
|
||||||
|
|
||||||
|
### 신청할 수 있는 역할
|
||||||
|
|
||||||
|
| 역할 | 주로 하는 일 |
|
||||||
|
|---|---|
|
||||||
|
| **일반 사용자** | 기본 조회 |
|
||||||
|
| **지정판매소** | 봉투 판매·반품, 자기 판매 현황 조회 |
|
||||||
|
| **지자체 관리자** | 소속 지자체의 발주·입고·재고·불출·판매·리포트 전반 |
|
||||||
|
|
||||||
|
### 가입 후 — 관리자 승인 대기
|
||||||
|
|
||||||
|
회원가입을 제출하면 **바로 로그인되지 않고 "승인 대기" 상태**가 됩니다.
|
||||||
|
|
||||||
|
- **관리자가 승인하면** 그때부터 로그인할 수 있습니다.
|
||||||
|
- 승인 전에 로그인하면 *"관리자 승인 후 로그인 가능합니다."* 안내가 나옵니다.
|
||||||
|
- 승인이 **반려**되면 *"승인이 반려되었습니다. 관리자에게 문의해 주세요."* 가 나옵니다 — 담당 관리자에게 문의하세요.
|
||||||
|
|
||||||
|
## 2. 로그인
|
||||||
|
|
||||||
|
로그인 화면에서 **아이디**와 **비밀번호**를 입력합니다.
|
||||||
|
|
||||||
|
- 성공하면 **워크스페이스**(탭 작업공간)로 들어갑니다.
|
||||||
|
- 아이디·비밀번호가 틀리면 안내 메시지가 나옵니다. (승인 대기/반려 상태도 위와 같이 안내됩니다.)
|
||||||
|
|
||||||
|
### 2차 인증(OTP)
|
||||||
|
|
||||||
|
보안 설정에 따라 비밀번호 확인 뒤 **2차 인증** 단계가 나올 수 있습니다.
|
||||||
|
|
||||||
|
- **이미 OTP를 등록한 경우** — 스마트폰 인증 앱(예: Google Authenticator)에 표시되는 **6자리 숫자**를 입력합니다.
|
||||||
|
- **처음 사용하는 경우** — 화면의 **QR코드 또는 설정 키**를 인증 앱에 등록한 뒤, 앱에 나온 6자리 숫자로 설정을 완료합니다. 이후 로그인부터 OTP를 입력하게 됩니다.
|
||||||
|
|
||||||
|
> OTP 숫자는 일정 시간마다 바뀌므로, **현재 표시된 숫자**를 입력해야 합니다. 휴대폰 시간이 자동(네트워크 시간)으로 맞춰져 있어야 정확합니다.
|
||||||
|
|
||||||
|
## 3. 로그아웃
|
||||||
|
|
||||||
|
화면 오른쪽 위 **[로그아웃]** 을 누르면 안전하게 종료됩니다. 공용 PC라면 사용 후 꼭 로그아웃하세요.
|
||||||
|
|
||||||
|
## 4. 비밀번호·계정 문제
|
||||||
|
|
||||||
|
- **비밀번호를 바꾸거나 분실**한 경우, 계정·권한 변경은 **담당 관리자**가 처리합니다. 관리자에게 문의하세요.
|
||||||
|
- 권한(역할)을 바꾸고 싶을 때도 관리자에게 요청하면 됩니다.
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
- **브라우저를 새로고침**하거나 **관리자 페이지에 갔다가 돌아와도** 열어 두었던 탭이 **다시 복원**됩니다.
|
- **브라우저를 새로고침**하거나 **관리자 페이지에 갔다가 돌아와도** 열어 두었던 탭이 **다시 복원**됩니다.
|
||||||
- 단, **브라우저 탭(창)을 완전히 닫으면** 작업공간은 초기화됩니다. (이 유지는 "이번 접속 동안"만 적용됩니다.)
|
- 단, **브라우저 탭(창)을 완전히 닫으면** 작업공간은 초기화됩니다. (이 유지는 "이번 접속 동안"만 적용됩니다.)
|
||||||
|
- **다른 아이디로 로그인하면** 이전 사용자의 탭은 복원되지 않고 **기본 화면으로 초기화**됩니다. (계정별로 분리됩니다.)
|
||||||
- 복원되는 것은 **열려 있던 화면 목록**입니다. 관리자 페이지를 거치는 등 작업공간을 완전히 벗어났던 경우, 각 화면은 새로 불러와지므로 **입력 중이던 폼 내용까지 그대로 살아나지는 않습니다.**
|
- 복원되는 것은 **열려 있던 화면 목록**입니다. 관리자 페이지를 거치는 등 작업공간을 완전히 벗어났던 경우, 각 화면은 새로 불러와지므로 **입력 중이던 폼 내용까지 그대로 살아나지는 않습니다.**
|
||||||
|
|
||||||
## 3. 키보드 단축키
|
## 3. 키보드 단축키
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ $navPartial = [
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title><?= esc($title ?? '관리자') ?> - 종량제 시스템</title>
|
<title><?= esc($title ?? '관리자') ?> - GBLS</title>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
@@ -142,7 +142,7 @@ tailwind.config = {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="portal-footer">
|
<footer class="portal-footer">
|
||||||
<span>종량제 시스템 관리자</span>
|
<span>GBLS 관리자</span>
|
||||||
<span><?= date('Y.m.d (D) H:i') ?></span>
|
<span><?= date('Y.m.d (D) H:i') ?></span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ $subtitle = $subtitle ?? '종량제 쓰레기봉투 물류시스템';
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title><?= esc($pageTitle ?? '종량제 시스템') ?></title>
|
<title><?= esc($pageTitle ?? 'GBLS') ?></title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
@@ -39,11 +39,14 @@ if (window.top !== window.self) { try { window.top.location.href = <?= json_enco
|
|||||||
</script>
|
</script>
|
||||||
<body class="bg-portal-bg text-gray-700 flex flex-col min-h-screen font-sans antialiased">
|
<body class="bg-portal-bg text-gray-700 flex flex-col min-h-screen font-sans antialiased">
|
||||||
<header class="bg-navy text-white h-12 flex items-center justify-between px-4 shrink-0 shadow">
|
<header class="bg-navy text-white h-12 flex items-center justify-between px-4 shrink-0 shadow">
|
||||||
<a href="<?= base_url() ?>" class="flex items-center gap-2 shrink-0 text-base font-bold tracking-tight hover:opacity-90" title="종량제 시스템">
|
<a href="<?= base_url() ?>" class="flex items-center gap-2 shrink-0 tracking-tight hover:opacity-90" title="GBLS (Garbage Bag Logistics System)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-6 w-6 text-white shrink-0" aria-hidden="true" focusable="false">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-6 w-6 text-white shrink-0" aria-hidden="true" focusable="false">
|
||||||
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="whitespace-nowrap">종량제 시스템</span>
|
<span class="leading-none flex flex-col">
|
||||||
|
<strong class="text-base font-extrabold tracking-wide">GBLS</strong>
|
||||||
|
<span class="text-[0.56rem] font-medium text-white/65 tracking-tight whitespace-nowrap">Garbage Bag Logistics System</span>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -12,32 +12,40 @@ $showKindActions = $canManageKinds;
|
|||||||
$selectedKindId = (int) ($selectedKind->ck_idx ?? 0);
|
$selectedKindId = (int) ($selectedKind->ck_idx ?? 0);
|
||||||
$colCount = 6 + ($showKindActions ? 1 : 0);
|
$colCount = 6 + ($showKindActions ? 1 : 0);
|
||||||
$detailColCount = 7 + ($canManageDetails ? 1 : 0);
|
$detailColCount = 7 + ($canManageDetails ? 1 : 0);
|
||||||
|
|
||||||
|
/** 상태 배지 (업무현황 스타일의 가벼운 pill) */
|
||||||
|
$stateBadge = static function (int $state): string {
|
||||||
|
return $state === 1
|
||||||
|
? '<span class="inline-block px-2 py-0.5 rounded-full text-[11px] font-medium bg-emerald-50 text-emerald-700">사용</span>'
|
||||||
|
: '<span class="inline-block px-2 py-0.5 rounded-full text-[11px] font-medium bg-gray-100 text-gray-500">미사용</span>';
|
||||||
|
};
|
||||||
?>
|
?>
|
||||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||||
<section>
|
<!-- 기본코드 종류 -->
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2 mb-2 border-b pb-1">
|
<section class="rounded-xl bg-white border border-gray-200 p-4 shadow-sm">
|
||||||
<h3 class="text-base font-bold text-gray-700">기본코드 종류</h3>
|
<div class="flex flex-wrap items-center justify-between gap-2 mb-3">
|
||||||
<div class="flex flex-wrap items-center gap-2 text-xs sm:text-sm">
|
<h2 class="text-sm font-bold text-gray-900"><i class="fa-solid fa-layer-group text-blue-600 mr-1"></i>기본코드 종류</h2>
|
||||||
<?php if ($canManageKinds): ?>
|
<?php if ($canManageKinds): ?>
|
||||||
<a href="<?= base_url('admin/code-kinds/create') ?>" class="inline-flex items-center rounded bg-[#243a5e] px-3 py-1.5 text-white shadow hover:opacity-90">기본코드 등록</a>
|
<a href="<?= base_url('admin/code-kinds/create') ?>" class="inline-flex items-center rounded-lg bg-[#243a5e] px-3 py-1.5 text-white text-xs font-semibold shadow-sm hover:opacity-90">기본코드 등록</a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="text-gray-500">코드 종류 등록·수정은 super admin·본부 관리자만 가능합니다.</span>
|
<span class="text-gray-400 text-[11px]">코드 종류 등록·수정은 super admin·본부 관리자만 가능합니다.</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-gray-300 overflow-auto">
|
<div class="overflow-auto">
|
||||||
<table class="data-table w-full">
|
<table class="w-full text-[13px]">
|
||||||
<thead><tr>
|
<thead>
|
||||||
<th class="w-14">번호</th>
|
<tr class="text-left text-[11px] font-semibold text-gray-500 border-b border-gray-200">
|
||||||
<th class="w-24">코드</th>
|
<th class="py-2.5 px-2 w-12 text-center">번호</th>
|
||||||
<th>코드명</th>
|
<th class="py-2.5 px-2 w-20">코드</th>
|
||||||
<th class="w-24">세부코드</th>
|
<th class="py-2.5 px-2">코드명</th>
|
||||||
<th class="w-20">상태</th>
|
<th class="py-2.5 px-2 w-20 text-center">세부코드</th>
|
||||||
<th class="w-40">등록일</th>
|
<th class="py-2.5 px-2 w-16 text-center">상태</th>
|
||||||
<?php if ($showKindActions): ?>
|
<th class="py-2.5 px-2 w-32">등록일</th>
|
||||||
<th class="w-36">작업</th>
|
<?php if ($showKindActions): ?>
|
||||||
<?php endif; ?>
|
<th class="py-2.5 px-2 w-28 text-center">작업</th>
|
||||||
</tr></thead>
|
<?php endif; ?>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if (! empty($codeKinds)): ?>
|
<?php if (! empty($codeKinds)): ?>
|
||||||
<?php $i = 0; foreach ($codeKinds as $row): $i++; ?>
|
<?php $i = 0; foreach ($codeKinds as $row): $i++; ?>
|
||||||
@@ -45,16 +53,16 @@ $detailColCount = 7 + ($canManageDetails ? 1 : 0);
|
|||||||
$isSelected = (int) $row->ck_idx === $selectedKindId;
|
$isSelected = (int) $row->ck_idx === $selectedKindId;
|
||||||
$detailUrl = base_url('bag/code-kinds?ck_idx=' . (int) $row->ck_idx);
|
$detailUrl = base_url('bag/code-kinds?ck_idx=' . (int) $row->ck_idx);
|
||||||
?>
|
?>
|
||||||
<tr class="<?= $isSelected ? 'bg-blue-50' : '' ?> cursor-pointer hover:bg-blue-50"
|
<tr class="border-b border-gray-200 last:border-0 cursor-pointer hover:bg-blue-50/60 <?= $isSelected ? 'bg-blue-50' : '' ?>"
|
||||||
onclick="window.location.href='<?= esc($detailUrl, 'attr') ?>'">
|
onclick="window.location.href='<?= esc($detailUrl, 'attr') ?>'">
|
||||||
<td class="text-center"><?= (string) $i ?></td>
|
<td class="py-2.5 px-2 text-center text-gray-500"><?= (string) $i ?></td>
|
||||||
<td class="text-center font-mono"><?= esc($row->ck_code) ?></td>
|
<td class="py-2.5 px-2 text-center font-mono text-gray-700"><?= esc($row->ck_code) ?></td>
|
||||||
<td><?= esc($row->ck_name) ?></td>
|
<td class="py-2.5 px-2 font-medium text-gray-900"><?= esc($row->ck_name) ?></td>
|
||||||
<td class="text-center"><?= (int) ($countMap[$row->ck_idx] ?? 0) ?>개</td>
|
<td class="py-2.5 px-2 text-center text-gray-600"><?= (int) ($countMap[$row->ck_idx] ?? 0) ?>개</td>
|
||||||
<td class="text-center"><?= (int) ($row->ck_state ?? 0) === 1 ? '사용' : '미사용' ?></td>
|
<td class="py-2.5 px-2 text-center"><?= $stateBadge((int) ($row->ck_state ?? 0)) ?></td>
|
||||||
<td class="text-left"><?= esc($row->ck_regdate ?? '') ?></td>
|
<td class="py-2.5 px-2 text-gray-500 text-[12px]"><?= esc($row->ck_regdate ?? '') ?></td>
|
||||||
<?php if ($showKindActions): ?>
|
<?php if ($showKindActions): ?>
|
||||||
<td class="text-center text-sm" onclick="event.stopPropagation()">
|
<td class="py-2.5 px-2 text-center text-xs" onclick="event.stopPropagation()">
|
||||||
<a href="<?= base_url('admin/code-kinds/edit/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline mr-1">수정</a>
|
<a href="<?= base_url('admin/code-kinds/edit/' . (int) $row->ck_idx) ?>" class="text-blue-600 hover:underline mr-1">수정</a>
|
||||||
<form action="<?= base_url('admin/code-kinds/delete/' . (int) $row->ck_idx) ?>" method="POST" class="inline" onsubmit="return confirm('이 코드 종류를 삭제하시겠습니까?');">
|
<form action="<?= base_url('admin/code-kinds/delete/' . (int) $row->ck_idx) ?>" method="POST" class="inline" onsubmit="return confirm('이 코드 종류를 삭제하시겠습니까?');">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
@@ -65,42 +73,43 @@ $detailColCount = 7 + ($canManageDetails ? 1 : 0);
|
|||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<tr><td colspan="<?= (string) $colCount ?>" class="text-center text-gray-400 py-4">등록된 코드 종류가 없습니다.</td></tr>
|
<tr><td colspan="<?= (string) $colCount ?>" class="text-center text-gray-400 py-6">등록된 코드 종류가 없습니다.</td></tr>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<!-- 세부코드 -->
|
||||||
<div class="flex flex-wrap items-center justify-between gap-2 mb-2 border-b pb-1">
|
<section class="rounded-xl bg-white border border-gray-200 p-4 shadow-sm">
|
||||||
<h3 class="text-base font-bold text-gray-700">
|
<div class="flex flex-wrap items-center justify-between gap-2 mb-3">
|
||||||
세부코드
|
<h2 class="text-sm font-bold text-gray-900">
|
||||||
|
<i class="fa-solid fa-list-ul text-emerald-600 mr-1"></i>세부코드
|
||||||
<?php if ($selectedKind !== null): ?>
|
<?php if ($selectedKind !== null): ?>
|
||||||
— <?= esc($selectedKind->ck_name) ?> (<?= esc($selectedKind->ck_code) ?>)
|
<span class="font-medium text-gray-400">— <?= esc($selectedKind->ck_name) ?> (<?= esc($selectedKind->ck_code) ?>)</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</h3>
|
</h2>
|
||||||
<?php if ($canManageDetails && $selectedKind !== null): ?>
|
<?php if ($canManageDetails && $selectedKind !== null): ?>
|
||||||
<a href="<?= base_url('admin/code-details/' . (int) $selectedKind->ck_idx . '/create') ?>" class="inline-flex items-center rounded bg-[#243a5e] px-3 py-1.5 text-white shadow hover:opacity-90 text-sm">세부코드 등록</a>
|
<a href="<?= base_url('admin/code-details/' . (int) $selectedKind->ck_idx . '/create') ?>" class="inline-flex items-center rounded-lg bg-[#243a5e] px-3 py-1.5 text-white text-xs font-semibold shadow-sm hover:opacity-90">세부코드 등록</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($selectedKind === null): ?>
|
<?php if ($selectedKind === null): ?>
|
||||||
<div class="border border-gray-300 rounded p-6 text-center text-gray-500">왼쪽에서 코드 종류를 선택해 주세요.</div>
|
<div class="py-10 text-center text-sm text-gray-400">왼쪽에서 코드 종류를 선택해 주세요.</div>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="border border-gray-300 overflow-auto">
|
<div class="overflow-auto">
|
||||||
<table class="data-table w-full">
|
<table class="w-full text-[13px]">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="text-left text-[11px] font-semibold text-gray-500 border-b border-gray-200">
|
||||||
<th class="w-16">번호</th>
|
<th class="py-2.5 px-2 w-12 text-center">번호</th>
|
||||||
<th class="w-24">코드</th>
|
<th class="py-2.5 px-2 w-20">코드</th>
|
||||||
<th>코드명</th>
|
<th class="py-2.5 px-2">코드명</th>
|
||||||
<th class="w-24">범위</th>
|
<th class="py-2.5 px-2 w-16 text-center">범위</th>
|
||||||
<th class="w-20">정렬</th>
|
<th class="py-2.5 px-2 w-14 text-center">정렬</th>
|
||||||
<th class="w-20">상태</th>
|
<th class="py-2.5 px-2 w-16 text-center">상태</th>
|
||||||
<th class="w-40">등록일</th>
|
<th class="py-2.5 px-2 w-32">등록일</th>
|
||||||
<?php if ($canManageDetails): ?>
|
<?php if ($canManageDetails): ?>
|
||||||
<th class="w-28">작업</th>
|
<th class="py-2.5 px-2 w-24 text-center">작업</th>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -111,16 +120,18 @@ $detailColCount = 7 + ($canManageDetails ? 1 : 0);
|
|||||||
$isPlatform = (($row->cd_source ?? 'platform') === 'platform' && (int) ($row->cd_lg_idx ?? 0) === 0);
|
$isPlatform = (($row->cd_source ?? 'platform') === 'platform' && (int) ($row->cd_lg_idx ?? 0) === 0);
|
||||||
$scopeLabel = $isPlatform ? '공통' : '지자체';
|
$scopeLabel = $isPlatform ? '공통' : '지자체';
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr class="border-b border-gray-200 last:border-0 hover:bg-gray-50">
|
||||||
<td class="text-center"><?= (string) $dNo ?></td>
|
<td class="py-2.5 px-2 text-center text-gray-500"><?= (string) $dNo ?></td>
|
||||||
<td class="text-center font-mono"><?= esc($row->cd_code) ?></td>
|
<td class="py-2.5 px-2 text-center font-mono text-gray-700"><?= esc($row->cd_code) ?></td>
|
||||||
<td><?= esc($row->cd_name) ?></td>
|
<td class="py-2.5 px-2 font-medium text-gray-900"><?= esc($row->cd_name) ?></td>
|
||||||
<td class="text-center text-xs"><?= esc($scopeLabel) ?></td>
|
<td class="py-2.5 px-2 text-center">
|
||||||
<td class="text-center"><?= (int) ($row->cd_sort ?? 0) ?></td>
|
<span class="inline-block px-2 py-0.5 rounded-full text-[11px] font-medium <?= $isPlatform ? 'bg-blue-50 text-blue-700' : 'bg-amber-50 text-amber-700' ?>"><?= esc($scopeLabel) ?></span>
|
||||||
<td class="text-center"><?= (int) ($row->cd_state ?? 0) === 1 ? '사용' : '미사용' ?></td>
|
</td>
|
||||||
<td class="text-left"><?= esc($row->cd_regdate ?? '') ?></td>
|
<td class="py-2.5 px-2 text-center text-gray-600"><?= (int) ($row->cd_sort ?? 0) ?></td>
|
||||||
|
<td class="py-2.5 px-2 text-center"><?= $stateBadge((int) ($row->cd_state ?? 0)) ?></td>
|
||||||
|
<td class="py-2.5 px-2 text-gray-500 text-[12px]"><?= esc($row->cd_regdate ?? '') ?></td>
|
||||||
<?php if ($canManageDetails): ?>
|
<?php if ($canManageDetails): ?>
|
||||||
<td class="text-center text-sm">
|
<td class="py-2.5 px-2 text-center text-xs">
|
||||||
<?php if (! empty($rowCanEdit[$row->cd_idx])): ?>
|
<?php if (! empty($rowCanEdit[$row->cd_idx])): ?>
|
||||||
<a href="<?= base_url('admin/code-details/edit/' . (int) $row->cd_idx) ?>" class="text-blue-600 hover:underline">수정</a>
|
<a href="<?= base_url('admin/code-details/edit/' . (int) $row->cd_idx) ?>" class="text-blue-600 hover:underline">수정</a>
|
||||||
<form action="<?= base_url('admin/code-details/delete/' . (int) $row->cd_idx) ?>" method="POST" class="ml-1 inline" onsubmit="return confirm('이 세부코드를 삭제하시겠습니까?');">
|
<form action="<?= base_url('admin/code-details/delete/' . (int) $row->cd_idx) ?>" method="POST" class="ml-1 inline" onsubmit="return confirm('이 세부코드를 삭제하시겠습니까?');">
|
||||||
@@ -135,7 +146,7 @@ $detailColCount = 7 + ($canManageDetails ? 1 : 0);
|
|||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<tr><td colspan="<?= (string) $detailColCount ?>" class="text-center text-gray-400 py-4">등록된 세부코드가 없습니다.</td></tr>
|
<tr><td colspan="<?= (string) $detailColCount ?>" class="text-center text-gray-400 py-6">등록된 세부코드가 없습니다.</td></tr>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ $userNav = session_user_nav_display();
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>종량제 시스템</title>
|
<title>GBLS</title>
|
||||||
<!-- Tailwind CSS v3 with Plugins -->
|
<!-- Tailwind CSS v3 with Plugins -->
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h3 class="font-bold text-gray-700 mb-1">시스템 개요</h3>
|
<h3 class="font-bold text-gray-700 mb-1">시스템 개요</h3>
|
||||||
<p>종량제 시스템은 지자체 종량제 쓰레기봉투의 발주, 입고, 재고, 판매, 불출 등 전체 물류 프로세스를 관리합니다.</p>
|
<p><strong>GBLS</strong>(Garbage Bag Logistics System)는 지자체 종량제 쓰레기봉투의 발주, 입고, 재고, 판매, 불출 등 전체 물류 프로세스를 관리합니다.</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ $helpUrl = function_exists('manual_help_url_for_path') ? manual_help_url_for_pat
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title><?= esc($title ?? '종량제 시스템') ?></title>
|
<title><?= esc($title ?? 'GBLS') ?></title>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ $userNav = session_user_nav_display();
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title><?= esc($title ?? '종량제 시스템') ?></title>
|
<title><?= esc($title ?? 'GBLS') ?></title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<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&display=swap" rel="stylesheet"/>
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet"/>
|
||||||
<script>
|
<script>
|
||||||
@@ -139,7 +139,7 @@ body { overflow: hidden; }
|
|||||||
<?= $content ?>
|
<?= $content ?>
|
||||||
</main>
|
</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">
|
<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>GBLS</span>
|
||||||
<span><?= date('Y.m.d (D) g:i:sA') ?></span>
|
<span><?= date('Y.m.d (D) g:i:sA') ?></span>
|
||||||
</footer>
|
</footer>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ $helpUrl = function_exists('manual_help_url_for_path') ? manual_help_url_for_pat
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title><?= esc($title ?? '종량제 시스템') ?></title>
|
<title><?= esc($title ?? 'GBLS') ?></title>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
@@ -143,7 +143,7 @@ tailwind.config = {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="portal-footer">
|
<footer class="portal-footer">
|
||||||
<span>종량제 시스템</span>
|
<span>GBLS</span>
|
||||||
<span><?= date('Y.m.d (D) H:i') ?></span>
|
<span><?= date('Y.m.d (D) H:i') ?></span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ if ($effectiveLgIdx) {
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>워크스페이스 · 종량제 시스템</title>
|
<title>워크스페이스 · GBLS</title>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"/>
|
||||||
<style>
|
<style>
|
||||||
@@ -106,9 +106,11 @@ if ($effectiveLgIdx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var STORE_KEY = 'jrj_ws_tabs';
|
var STORE_KEY = 'jrj_ws_tabs';
|
||||||
|
var WS_OWNER = '<?= (string) (session()->get('mb_idx') ?? '') ?>'; // 탭 저장 소유자(로그인 사용자) 식별
|
||||||
function persist() {
|
function persist() {
|
||||||
try {
|
try {
|
||||||
var data = {
|
var data = {
|
||||||
|
owner: WS_OWNER,
|
||||||
tabs: order.map(function (id) { return { url: tabs[id].url, title: tabs[id].title }; }),
|
tabs: order.map(function (id) { return { url: tabs[id].url, title: tabs[id].title }; }),
|
||||||
active: activeId
|
active: activeId
|
||||||
};
|
};
|
||||||
@@ -242,6 +244,11 @@ if ($effectiveLgIdx) {
|
|||||||
(function restore() {
|
(function restore() {
|
||||||
var saved = null;
|
var saved = null;
|
||||||
try { saved = JSON.parse(sessionStorage.getItem(STORE_KEY) || 'null'); } catch (e) {}
|
try { saved = JSON.parse(sessionStorage.getItem(STORE_KEY) || 'null'); } catch (e) {}
|
||||||
|
// 저장된 탭의 소유자가 현재 로그인 사용자와 다르면(같은 브라우저 탭에서 계정 전환 등) 복원하지 않고 초기화
|
||||||
|
if (saved && saved.owner !== WS_OWNER) {
|
||||||
|
try { sessionStorage.removeItem(STORE_KEY); } catch (e) {}
|
||||||
|
saved = null;
|
||||||
|
}
|
||||||
if (saved && saved.tabs && saved.tabs.length) {
|
if (saved && saved.tabs && saved.tabs.length) {
|
||||||
saved.tabs.forEach(function (t) { if (t && t.url) openTab(t.url, t.title); });
|
saved.tabs.forEach(function (t) { if (t && t.url) openTab(t.url, t.title); });
|
||||||
if (saved.active && tabs[saved.active]) activate(saved.active);
|
if (saved.active && tabs[saved.active]) activate(saved.active);
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ $mbName = session()->get('mb_name') ?? '담당자';
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>종량제 시스템 — 업무 현황</title>
|
<title>GBLS — 업무 현황</title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ $dashBlend = base_url('dashboard/blend');
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>종량제 시스템 — 통계·그래프 현황</title>
|
<title>GBLS — 통계·그래프 현황</title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ $notices = [
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>종량제 시스템 — 종합 현황 (정보집약)</title>
|
<title>GBLS — 종합 현황 (정보집약)</title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ $dashBlend = base_url('dashboard/blend');
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>종량제 시스템 — 업무 현황 (모던)</title>
|
<title>GBLS — 업무 현황 (모던)</title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"/>
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ $lowStock = [
|
|||||||
<i class="fa-solid fa-building-columns"></i>
|
<i class="fa-solid fa-building-columns"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<p class="text-xs font-semibold text-gray-900 group-hover:text-blue-800">종량제 시스템 · 포털</p>
|
<p class="text-xs font-semibold text-gray-900 group-hover:text-blue-800">GBLS · 포털</p>
|
||||||
<p class="text-[11px] text-gray-500 truncate">기본 · 변형(strip) 시안</p>
|
<p class="text-[11px] text-gray-500 truncate">기본 · 변형(strip) 시안</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>종량제 시스템 — 봉투 수불 현황</title>
|
<title>GBLS — 봉투 수불 현황</title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ $href = $href ?? base_url();
|
|||||||
/** @var string $linkClass Anchor + inner flex typography */
|
/** @var string $linkClass Anchor + inner flex typography */
|
||||||
$linkClass = $linkClass ?? 'app-brand flex items-center gap-2 shrink-0 text-base font-semibold text-gray-800 tracking-tight hover:text-blue-600';
|
$linkClass = $linkClass ?? 'app-brand flex items-center gap-2 shrink-0 text-base font-semibold text-gray-800 tracking-tight hover:text-blue-600';
|
||||||
?>
|
?>
|
||||||
<a href="<?= esc($href) ?>" class="<?= esc($linkClass, 'attr') ?>" title="종량제 시스템">
|
<a href="<?= esc($href) ?>" class="<?= esc($linkClass, 'attr') ?>" title="GBLS (Garbage Bag Logistics System)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-6 w-6 text-blue-900 translate-y-[1px] shrink-0" aria-hidden="true" focusable="false">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-6 w-6 text-blue-900 translate-y-[1px] shrink-0" aria-hidden="true" focusable="false">
|
||||||
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="whitespace-nowrap">종량제 시스템</span>
|
<span class="leading-none flex flex-col">
|
||||||
|
<strong class="font-extrabold tracking-wide">GBLS</strong>
|
||||||
|
<span class="text-[0.56rem] font-medium text-gray-400 tracking-tight whitespace-nowrap">Garbage Bag Logistics System</span>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 종량제 시스템 — 미니멀 에코 마크 (링 + 잎)
|
* GBLS — 미니멀 에코 마크 (링 + 잎)
|
||||||
*
|
*
|
||||||
* @var string $svgClass Tailwind classes for the SVG root
|
* @var string $svgClass Tailwind classes for the SVG root
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -11,5 +11,8 @@ $brandHref = $brandHref ?? base_url('dashboard/gov-portal');
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">
|
||||||
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span>종량제 시스템</span>
|
<span style="display:inline-flex;flex-direction:column;line-height:1.02;">
|
||||||
|
<strong style="font-size:1.02rem;font-weight:800;letter-spacing:.5px;">GBLS</strong>
|
||||||
|
<span style="font-size:.56rem;font-weight:500;opacity:.72;letter-spacing:.1px;white-space:nowrap;">Garbage Bag Logistics System</span>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<title>종량제 시스템</title>
|
<title>GBLS</title>
|
||||||
|
|||||||
@@ -30,10 +30,9 @@
|
|||||||
var chHref = (child.href || '').toLowerCase().replace(/^\//, '');
|
var chHref = (child.href || '').toLowerCase().replace(/^\//, '');
|
||||||
var on = activeHref ? (chHref === activeHref) : (hasOverride ? false : ci === 0);
|
var on = activeHref ? (chHref === activeHref) : (hasOverride ? false : ci === 0);
|
||||||
if (child.href) {
|
if (child.href) {
|
||||||
li.innerHTML = '<a href="' + child.url + '" class="' + (on ? 'active' : '') + '">' +
|
li.innerHTML = '<a href="' + child.url + '" class="' + (on ? 'active' : '') + '">' + child.name + '</a>';
|
||||||
'<span class="menu-ico">' + (on ? '▸' : '·') + '</span>' + child.name + '</a>';
|
|
||||||
} else {
|
} else {
|
||||||
li.innerHTML = '<span class="menu-sub" style="opacity:.65;"><span class="menu-ico">+</span>' + child.name + '</span>';
|
li.innerHTML = '<span class="menu-sub" style="opacity:.65;">' + child.name + '</span>';
|
||||||
}
|
}
|
||||||
listEl.appendChild(li);
|
listEl.appendChild(li);
|
||||||
});
|
});
|
||||||
@@ -67,21 +66,42 @@
|
|||||||
try { var a = new URL(u, location.origin); return (a.pathname + a.search).toLowerCase(); }
|
try { var a = new URL(u, location.origin); return (a.pathname + a.search).toLowerCase(); }
|
||||||
catch (e) { return (u || '').toLowerCase(); }
|
catch (e) { return (u || '').toLowerCase(); }
|
||||||
}
|
}
|
||||||
|
function pathOnly(u) {
|
||||||
|
try { return new URL(u, location.origin).pathname.toLowerCase().replace(/\/$/, ''); }
|
||||||
|
catch (e) { return (u || '').toLowerCase(); }
|
||||||
|
}
|
||||||
|
// 현재 사이드바의 모든 소메뉴에서 강조(active)를 해제 — 메뉴에 없는 화면(대시보드 등)에서 사용
|
||||||
|
function clearSidebarActive() {
|
||||||
|
listEl.querySelectorAll('a').forEach(function (a) {
|
||||||
|
a.classList.remove('active');
|
||||||
|
});
|
||||||
|
}
|
||||||
window.govPortalNav = {
|
window.govPortalNav = {
|
||||||
// URL 로 소속 대메뉴/소메뉴를 찾아 사이드바 강조를 갱신. 일치 없으면 false.
|
// URL 로 소속 대메뉴/소메뉴를 찾아 사이드바 강조를 갱신. 일치 없으면(대시보드 등) 강조 해제.
|
||||||
syncByUrl: function (url) {
|
syncByUrl: function (url) {
|
||||||
var target = pathOf(url);
|
var target = pathOf(url), targetPath = pathOnly(url), fb = null;
|
||||||
for (var p = 0; p < navData.length; p++) {
|
for (var p = 0; p < navData.length; p++) {
|
||||||
var par = navData[p];
|
var par = navData[p];
|
||||||
var kids = (par.children && par.children.length) ? par.children : (par.href ? [par] : []);
|
var kids = (par.children && par.children.length) ? par.children : (par.href ? [par] : []);
|
||||||
for (var i = 0; i < kids.length; i++) {
|
for (var i = 0; i < kids.length; i++) {
|
||||||
if (kids[i].url && pathOf(kids[i].url) === target) {
|
if (!kids[i].url) continue;
|
||||||
|
if (pathOf(kids[i].url) === target) { // 정확 일치(경로+쿼리)
|
||||||
setActiveTrigger(p);
|
setActiveTrigger(p);
|
||||||
renderSidebar(p, (kids[i].href || '').toLowerCase().replace(/^\//, ''));
|
renderSidebar(p, (kids[i].href || '').toLowerCase().replace(/^\//, ''));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!fb && pathOnly(kids[i].url) === targetPath) { // 경로만 일치(쿼리 무시) 폴백
|
||||||
|
fb = { p: p, href: (kids[i].href || '').toLowerCase().replace(/^\//, '') };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (fb) {
|
||||||
|
setActiveTrigger(fb.p);
|
||||||
|
renderSidebar(fb.p, fb.href);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 어느 메뉴와도 일치하지 않으면(예: 업무 현황 대시보드) 화살표 강조 해제
|
||||||
|
clearSidebarActive();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,12 +22,11 @@ $activeChildHref = strtolower(ltrim((string) ($govActiveChildHref ?? ''), '/'));
|
|||||||
<li>
|
<li>
|
||||||
<?php if ($child['href'] !== ''): ?>
|
<?php if ($child['href'] !== ''): ?>
|
||||||
<a href="<?= esc($child['url']) ?>" class="<?= $isChildActive ? 'active' : '' ?>">
|
<a href="<?= esc($child['url']) ?>" class="<?= $isChildActive ? 'active' : '' ?>">
|
||||||
<span class="menu-ico"><?= $isChildActive ? '▸' : '·' ?></span>
|
|
||||||
<?= esc($child['name']) ?>
|
<?= esc($child['name']) ?>
|
||||||
</a>
|
</a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<span class="menu-sub" style="opacity:.65;cursor:default;">
|
<span class="menu-sub" style="opacity:.65;cursor:default;">
|
||||||
<span class="menu-ico">·</span><?= esc($child['name']) ?>
|
<?= esc($child['name']) ?>
|
||||||
</span>
|
</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</li>
|
</li>
|
||||||
@@ -35,7 +34,7 @@ $activeChildHref = strtolower(ltrim((string) ($govActiveChildHref ?? ''), '/'));
|
|||||||
<?php elseif ($activeParent['href'] !== ''): ?>
|
<?php elseif ($activeParent['href'] !== ''): ?>
|
||||||
<li>
|
<li>
|
||||||
<a href="<?= esc($activeParent['url']) ?>" class="active">
|
<a href="<?= esc($activeParent['url']) ?>" class="active">
|
||||||
<span class="menu-ico">▸</span><?= esc($activeParent['name']) ?>
|
<?= esc($activeParent['name']) ?>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -51,9 +50,7 @@ $activeChildHref = strtolower(ltrim((string) ($govActiveChildHref ?? ''), '/'));
|
|||||||
<a href="<?= base_url('admin/select-local-government') ?>" style="color:#fff;text-decoration:underline;">지자체 선택</a>
|
<a href="<?= base_url('admin/select-local-government') ?>" style="color:#fff;text-decoration:underline;">지자체 선택</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="sb-links">
|
<div class="sb-links">
|
||||||
<a href="<?= base_url('bag/help') ?>">나의 할일</a>
|
|
||||||
<a href="<?= base_url('bag/manual') ?>">사용자 매뉴얼</a>
|
<a href="<?= base_url('bag/manual') ?>">사용자 매뉴얼</a>
|
||||||
<a href="<?= base_url('bag/help') ?>">FAQ</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||||
<title>홈 - 종량제 시스템</title>
|
<title>홈 - GBLS</title>
|
||||||
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
||||||
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
<style data-purpose="global-font-scale">html{font-size:18px}.app-brand,.app-brand *{font-size:16px}</style>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet"/>
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet"/>
|
||||||
@@ -45,6 +45,6 @@ tailwind.config = {
|
|||||||
<a href="<?= base_url('logout') ?>" class="inline-block bg-btn-exit text-white px-4 py-2 rounded-sm text-sm shadow hover:bg-red-700 transition">로그아웃</a>
|
<a href="<?= base_url('logout') ?>" class="inline-block bg-btn-exit text-white px-4 py-2 rounded-sm text-sm shadow hover:bg-red-700 transition">로그아웃</a>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<footer class="bg-gray-200 border-t border-gray-300 px-4 py-1 text-xs text-gray-600 shrink-0">종량제 시스템</footer>
|
<footer class="bg-gray-200 border-t border-gray-300 px-4 py-1 text-xs text-gray-600 shrink-0">GBLS</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ tailwind.config = {
|
|||||||
</head>
|
</head>
|
||||||
<body class="bg-portal-bg text-gray-700 flex flex-col h-screen font-sans antialiased">
|
<body class="bg-portal-bg text-gray-700 flex flex-col h-screen font-sans antialiased">
|
||||||
<header class="bg-navy text-white h-12 flex items-center justify-between px-4 shrink-0 shadow">
|
<header class="bg-navy text-white h-12 flex items-center justify-between px-4 shrink-0 shadow">
|
||||||
<a href="<?= base_url() ?>" class="flex items-center gap-2 text-base font-bold tracking-tight hover:opacity-90">
|
<a href="<?= base_url() ?>" class="flex items-center gap-2 tracking-tight hover:opacity-90" title="GBLS (Garbage Bag Logistics System)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-6 w-6 text-white shrink-0" aria-hidden="true" focusable="false">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="h-6 w-6 text-white shrink-0" aria-hidden="true" focusable="false">
|
||||||
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
<path fill="currentColor" d="M9 3a1 1 0 00-1 1v1H5.75a.75.75 0 000 1.5h12.5a.75.75 0 000-1.5H16V4a1 1 0 00-1-1H9zm9 4H6v11a2 2 0 002 2h8a2 2 0 002-2V7zM10 9a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0110 9zm4 0a.75.75 0 01.75.75v6a.75.75 0 01-1.5 0v-6A.75.75 0 0114 9z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="whitespace-nowrap">종량제 시스템</span>
|
<span class="leading-none flex flex-col">
|
||||||
|
<strong class="text-base font-extrabold tracking-wide">GBLS</strong>
|
||||||
|
<span class="text-[0.56rem] font-medium text-white/65 tracking-tight whitespace-nowrap">Garbage Bag Logistics System</span>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<nav class="flex gap-3 text-sm font-medium">
|
<nav class="flex gap-3 text-sm font-medium">
|
||||||
<a class="px-3 py-1 rounded hover:bg-white/10" href="<?= base_url('login') ?>">로그인</a>
|
<a class="px-3 py-1 rounded hover:bg-white/10" href="<?= base_url('login') ?>">로그인</a>
|
||||||
|
|||||||
@@ -33,6 +33,15 @@ test.describe('사용자 매뉴얼', () => {
|
|||||||
await expect(page.locator('.manual-prose')).toContainText('바코드');
|
await expect(page.locator('.manual-prose')).toContainText('바코드');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('로그인·회원가입 페이지 렌더 + 승인/2차인증 안내', async ({ page }) => {
|
||||||
|
await login(page, 'user');
|
||||||
|
await page.goto('/bag/manual/account');
|
||||||
|
await expect(page.locator('.manual-prose h1')).toContainText('회원가입');
|
||||||
|
await expect(page.locator('.manual-prose')).toContainText('승인');
|
||||||
|
await expect(page.locator('.manual-prose')).toContainText('2차 인증');
|
||||||
|
await expect(page.locator('.manual-toc a', { hasText: '로그인·회원가입' })).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('워크스페이스·단축키 페이지 렌더 + 단축키 표 노출', async ({ page }) => {
|
test('워크스페이스·단축키 페이지 렌더 + 단축키 표 노출', async ({ page }) => {
|
||||||
await login(page, 'user');
|
await login(page, 'user');
|
||||||
await page.goto('/bag/manual/workspace');
|
await page.goto('/bag/manual/workspace');
|
||||||
|
|||||||
@@ -67,6 +67,32 @@ test.describe('워크스페이스 탭', () => {
|
|||||||
await expect(page.locator('.ws-tab')).toHaveCount(2);
|
await expect(page.locator('.ws-tab')).toHaveCount(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('다른 아이디로 로그인하면 이전 탭이 복원되지 않음', async ({ page }) => {
|
||||||
|
// admin 으로 워크스페이스에서 소메뉴 탭을 추가
|
||||||
|
await login(page, 'admin');
|
||||||
|
await page.goto('/admin/select-local-government');
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const r = document.querySelector('input[name="lg_idx"][value="1"]');
|
||||||
|
if (r) { r.checked = true; r.form.submit(); }
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(700);
|
||||||
|
await page.goto('/workspace');
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
await page.locator('.sidebar .my-menu-list a').first().click();
|
||||||
|
await page.waitForTimeout(1500);
|
||||||
|
await expect(page.locator('.ws-tab')).toHaveCount(2);
|
||||||
|
|
||||||
|
// 같은 브라우저 탭에서 로그아웃 → 다른 아이디(local)로 로그인
|
||||||
|
await page.goto('/logout');
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await login(page, 'local');
|
||||||
|
await page.goto('/workspace');
|
||||||
|
await page.waitForTimeout(2500);
|
||||||
|
|
||||||
|
// 이전 사용자의 탭들은 사라지고 기본 대시보드 탭만 남아야 함
|
||||||
|
await expect(page.locator('.ws-tab')).toHaveCount(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('편의: 가운데클릭 닫기·사이드바 동기화', async ({ page }) => {
|
test('편의: 가운데클릭 닫기·사이드바 동기화', async ({ page }) => {
|
||||||
await login(page, 'admin');
|
await login(page, 'admin');
|
||||||
await page.goto('/admin/select-local-government');
|
await page.goto('/admin/select-local-government');
|
||||||
@@ -81,13 +107,28 @@ test.describe('워크스페이스 탭', () => {
|
|||||||
|
|
||||||
await expect(page.locator('.ws-tab')).toHaveCount(1);
|
await expect(page.locator('.ws-tab')).toHaveCount(1);
|
||||||
|
|
||||||
// 소메뉴를 탭으로 열기 → 사이드바에서 해당 항목이 active 로 동기화됨
|
// 대시보드(업무 현황) 탭은 메뉴에 없으므로 사이드바에 active(▸) 가 없어야 함
|
||||||
|
await expect(page.locator('.sidebar .my-menu-list a.active')).toHaveCount(0);
|
||||||
|
|
||||||
|
// 소메뉴를 탭으로 열기 → 그 메뉴가 사이드바에서 active(▸) 로 강조됨
|
||||||
const firstMenu = page.locator('.sidebar .my-menu-list a').first();
|
const firstMenu = page.locator('.sidebar .my-menu-list a').first();
|
||||||
const menuText = (await firstMenu.textContent() || '').trim();
|
const menuText = (await firstMenu.textContent() || '').trim();
|
||||||
await firstMenu.click();
|
await firstMenu.click();
|
||||||
await page.waitForTimeout(1500);
|
await page.waitForTimeout(1500);
|
||||||
await expect(page.locator('.ws-tab')).toHaveCount(2);
|
await expect(page.locator('.ws-tab')).toHaveCount(2);
|
||||||
await expect(page.locator('.sidebar .my-menu-list a.active')).toBeVisible();
|
const activeLink = page.locator('.sidebar .my-menu-list a.active');
|
||||||
|
await expect(activeLink).toHaveCount(1);
|
||||||
|
await expect(activeLink).toContainText(menuText.replace(/^[▸·]\s*/, ''));
|
||||||
|
|
||||||
|
// 대시보드 탭으로 전환 → active(▸) 강조가 해제되어야 함 (탭↔사이드바 동기화)
|
||||||
|
await page.locator('.ws-tab').nth(0).click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await expect(page.locator('.sidebar .my-menu-list a.active')).toHaveCount(0);
|
||||||
|
|
||||||
|
// 메뉴 탭으로 다시 전환 → active(▸) 가 그 메뉴로 복귀
|
||||||
|
await page.locator('.ws-tab').nth(1).click();
|
||||||
|
await page.waitForTimeout(500);
|
||||||
|
await expect(page.locator('.sidebar .my-menu-list a.active')).toHaveCount(1);
|
||||||
|
|
||||||
// 두 번째 탭 가운데(휠) 클릭으로 닫기
|
// 두 번째 탭 가운데(휠) 클릭으로 닫기
|
||||||
await page.locator('.ws-tab').nth(1).click({ button: 'middle' });
|
await page.locator('.ws-tab').nth(1).click({ button: 'middle' });
|
||||||
|
|||||||
Reference in New Issue
Block a user