feat(ui): 사이드바 메뉴 그룹화 및 접기/펼치기 기능 구현

- 메뉴를 4개 그룹으로 재구성 (시스템 관리, 권한 관리, 생산 관리, 시스템)
- 그룹 접기/펼치기 기능 추가 (localStorage 상태 저장)
- 그룹 내 메뉴 인덴트 적용 (2rem)
- 그룹 구분선 및 시각적 계층 구조 강화
- 권한 테이블 레이아웃 최적화 (한 줄 표시)
- 사이드바 메뉴 가이드 문서 작성 (SIDEBAR_MENU_GUIDE.md)

변경 파일:
- resources/views/partials/sidebar.blade.php
- resources/views/permissions/partials/table.blade.php
- SIDEBAR_MENU_GUIDE.md (신규)
This commit is contained in:
2025-11-25 11:53:17 +09:00
parent 3992adc800
commit c921ef43f9
3 changed files with 507 additions and 171 deletions

230
SIDEBAR_MENU_GUIDE.md Normal file
View File

@@ -0,0 +1,230 @@
# Sidebar Menu Guide
## 개요
MNG 관리자 패널의 좌측 사이드바 메뉴 구조 및 스타일 가이드입니다.
## 메뉴 구조
### 1. 대시보드 (단독 메뉴)
- 경로: `/dashboard`
- 아이콘: Home
- 그룹 없이 최상단 단독 배치
### 2. 시스템 관리 그룹
- **테넌트 관리** - `/tenants`
- **사용자 관리** - `/users`
- **부서 관리** - `/departments`
- **메뉴 관리** - `/menus`
### 3. 권한 관리 그룹
- **역할 관리** - `/roles`
- **권한 관리** - `/permissions` ✅ 완료
- **역할 권한 관리** - `#` (미구현)
- **부서 권한 관리** - `#` (미구현)
- **개인 권한 관리** - `#` (미구현)
- **권한 분석** - `#` (미구현)
### 4. 생산 관리 그룹
- **제품 관리** - `#` (미구현)
- **자재 관리** - `#` (미구현)
- **BOM 관리** - `#` (미구현)
- **카테고리 관리** - `#` (미구현)
### 5. 시스템 그룹
- **시스템 설정** - `#` (미구현)
- **감사 로그** - `#` (미구현)
- **삭제된 데이터 백업** - `#` (미구현)
## 스타일 가이드
### 그룹 헤더
```html
<button class="w-full flex items-center justify-between px-3 py-2 text-xs font-bold text-gray-600 uppercase tracking-wider hover:bg-gray-50 rounded">
<span>그룹명</span>
<svg id="group-icon" class="w-3 h-3 transition-transform">
<!-- Chevron down icon -->
</svg>
</button>
```
**스타일 특징:**
- 폰트: `text-xs font-bold text-gray-600`
- 대문자: `uppercase tracking-wider`
- Hover: `hover:bg-gray-50`
- 상단 구분선: `border-t border-gray-200 mt-2 pt-4 pb-1`
### 그룹 내 메뉴 (인덴트)
```html
<a href="{{ route('...') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4">...</svg>
<span class="font-medium">메뉴명</span>
</a>
```
**스타일 특징:**
- **인덴트**: `style="padding-left: 2rem;"` (inline style로 확실히 적용)
- 아이콘: `w-4 h-4`
- 텍스트: `text-sm font-medium`
- Hover: `hover:bg-gray-100`
- 활성 상태: `bg-primary text-white hover:bg-primary`
### 일반 메뉴 (대시보드)
```html
<a href="{{ route('dashboard') }}"
class="flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100">
<svg class="w-4 h-4">...</svg>
<span class="font-medium">대시보드</span>
</a>
```
**스타일 특징:**
- 인덴트 없음: `px-3`
- 아이콘: `w-4 h-4`
- 텍스트: `text-sm font-medium`
## 그룹 접기/펼치기 기능
### JavaScript 구현
```javascript
function toggleGroup(groupId) {
const group = document.getElementById(groupId);
const icon = document.getElementById(groupId + '-icon');
if (group.style.display === 'none') {
group.style.display = 'block';
icon.style.transform = 'rotate(0deg)';
localStorage.setItem('sidebar-' + groupId, 'open');
} else {
group.style.display = 'none';
icon.style.transform = 'rotate(-90deg)';
localStorage.setItem('sidebar-' + groupId, 'closed');
}
}
```
### 상태 저장
- localStorage를 사용하여 그룹 열림/닫힘 상태 저장
- 페이지 새로고침 후에도 상태 유지
- Key format: `sidebar-{group-id}`
### 아이콘 애니메이션
- 열림: `rotate(0deg)` (▼)
- 닫힘: `rotate(-90deg)` (▶)
- Transition: `transition-transform`
## 그룹 ID 목록
```javascript
['system-group', 'permission-group', 'production-group', 'system-settings-group']
```
## 활성 상태 표시
### Blade 조건문
```php
{{ request()->routeIs('permissions.*') ? 'bg-primary text-white hover:bg-primary' : '' }}
```
**패턴:**
- 현재 라우트가 해당 메뉴의 라우트와 일치하면 `bg-primary text-white` 적용
- Wildcard 사용: `permissions.*` (모든 하위 라우트 포함)
## 크기 및 간격
### 아이콘 크기
- 메뉴 아이콘: `w-4 h-4` (16px)
- 그룹 화살표: `w-3 h-3` (12px)
### 간격
- 메뉴 간격: `space-y-1` (4px)
- 아이콘-텍스트 간격: `gap-2` (8px)
- 그룹 상단 간격: `mt-2 pt-4` (8px + 16px)
- 그룹 하단 간격: `pb-1` (4px)
### 패딩
- 일반 메뉴: `px-3 py-2` (12px, 8px)
- 그룹 내 메뉴: `padding-left: 2rem;` (32px) + `pr-3 py-2`
- 그룹 헤더: `px-3 py-2`
## 색상 팔레트
### 텍스트
- 일반 메뉴: `text-gray-700`
- 그룹 헤더: `text-gray-600`
- 활성 메뉴: `text-white`
### 배경
- 일반 Hover: `hover:bg-gray-100`
- 그룹 헤더 Hover: `hover:bg-gray-50`
- 활성 메뉴: `bg-primary` (프로젝트 primary color)
### 구분선
- 그룹 구분: `border-gray-200`
## 파일 위치
```
mng/resources/views/partials/sidebar.blade.php
```
## 새 메뉴 추가 방법
### 1. 일반 메뉴 (그룹 없이)
```html
<li>
<a href="{{ route('new-menu.index') }}"
class="flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('new-menu.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-4 h-4"><!-- 아이콘 --></svg>
<span class="font-medium">새 메뉴</span>
</a>
</li>
```
### 2. 그룹 내 메뉴
```html
<li>
<a href="{{ route('new-menu.index') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('new-menu.*') ? 'bg-primary text-white hover:bg-primary' : '' }}"
style="padding-left: 2rem;">
<svg class="w-4 h-4"><!-- 아이콘 --></svg>
<span class="font-medium">새 메뉴</span>
</a>
</li>
```
### 3. 새 그룹 추가
```html
<li class="pt-4 pb-1 border-t border-gray-200 mt-2">
<button onclick="toggleGroup('new-group')" class="w-full flex items-center justify-between px-3 py-2 text-xs font-bold text-gray-600 uppercase tracking-wider hover:bg-gray-50 rounded">
<span>새 그룹</span>
<svg id="new-group-icon" class="w-3 h-3 transition-transform">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<ul id="new-group" class="space-y-1 mt-1">
<!-- 그룹 내 메뉴들 -->
</ul>
</li>
```
**주의:** JavaScript에서 그룹 ID 배열에 'new-group' 추가 필요
## 주의사항
1. **인덴트는 inline style 사용**: Tailwind의 `pl-6` 대신 `style="padding-left: 2rem;"` 사용
2. **그룹 구분선 필수**: 각 그룹에 `border-t border-gray-200` 추가
3. **아이콘 크기 일관성**: 메뉴는 `w-4 h-4`, 그룹 화살표는 `w-3 h-3`
4. **활성 상태 체크**: `request()->routeIs()` 사용
5. **localStorage 동기화**: 새 그룹 추가 시 JavaScript 배열 업데이트
## 최근 변경 이력
### 2025-01-25
- 권한 관리 기능 완료 (`/permissions`)
- 미래 메뉴 5개 추가 (역할 권한, 부서 권한, 개인 권한, 권한 분석, 삭제된 데이터 백업)
- 그룹 접기/펼치기 기능 추가
- 그룹 내 메뉴 인덴트 적용 (2rem)
- 그룹 구분선 및 스타일 강화

View File

@@ -8,159 +8,235 @@
<!-- Navigation Menu -->
<nav class="flex-1 overflow-y-auto p-4">
<ul class="space-y-2">
<ul class="space-y-1">
<!-- 대시보드 -->
<li>
<a href="{{ route('dashboard') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('dashboard') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
class="flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('dashboard') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
</svg>
<span class="font-medium">대시보드</span>
</a>
</li>
<!-- 테넌트 관리 -->
<li>
<a href="{{ route('tenants.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('tenants.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
<!-- 시스템 관리 그룹 -->
<li class="pt-4 pb-1 border-t border-gray-200 mt-2">
<button onclick="toggleGroup('system-group')" class="w-full flex items-center justify-between px-3 py-2 text-xs font-bold text-gray-600 uppercase tracking-wider hover:bg-gray-50 rounded">
<span>시스템 관리</span>
<svg id="system-group-icon" class="w-3 h-3 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
<span class="font-medium">테넌트 관리</span>
</a>
</button>
<ul id="system-group" class="space-y-1 mt-1">
<li>
<a href="{{ route('tenants.index') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('tenants.*') ? 'bg-primary text-white hover:bg-primary' : '' }}"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<span class="font-medium">테넌트 관리</span>
</a>
</li>
<li>
<a href="{{ route('users.index') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('users.*') ? 'bg-primary text-white hover:bg-primary' : '' }}"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<span class="font-medium">사용자 관리</span>
</a>
</li>
<li>
<a href="{{ route('departments.index') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('departments.*') ? 'bg-primary text-white hover:bg-primary' : '' }}"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<span class="font-medium">부서 관리</span>
</a>
</li>
<li>
<a href="{{ route('menus.index') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('menus.*') ? 'bg-primary text-white hover:bg-primary' : '' }}"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<span class="font-medium">메뉴 관리</span>
</a>
</li>
</ul>
</li>
<!-- 사용자 관리 -->
<li>
<a href="{{ route('users.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('users.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
<!-- 권한 관리 그룹 -->
<li class="pt-4 pb-1 border-t border-gray-200 mt-2">
<button onclick="toggleGroup('permission-group')" class="w-full flex items-center justify-between px-3 py-2 text-xs font-bold text-gray-600 uppercase tracking-wider hover:bg-gray-50 rounded">
<span>권한 관리</span>
<svg id="permission-group-icon" class="w-3 h-3 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
<span class="font-medium">사용자 관리</span>
</a>
</button>
<ul id="permission-group" class="space-y-1 mt-1">
<li>
<a href="{{ route('roles.index') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('roles.*') ? 'bg-primary text-white hover:bg-primary' : '' }}"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<span class="font-medium">역할 관리</span>
</a>
</li>
<li>
<a href="{{ route('permissions.index') }}"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100 {{ request()->routeIs('permissions.*') ? 'bg-primary text-white hover:bg-primary' : '' }}"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
<span class="font-medium">권한 관리</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
</svg>
<span class="font-medium">역할 권한 관리</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<span class="font-medium">부서 권한 관리</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
<span class="font-medium">개인 권한 관리</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
</svg>
<span class="font-medium">권한 분석</span>
</a>
</li>
</ul>
</li>
<!-- 역할 관리 -->
<li>
<a href="{{ route('roles.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('roles.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
<!-- 생산 관리 그룹 -->
<li class="pt-4 pb-1 border-t border-gray-200 mt-2">
<button onclick="toggleGroup('production-group')" class="w-full flex items-center justify-between px-3 py-2 text-xs font-bold text-gray-600 uppercase tracking-wider hover:bg-gray-50 rounded">
<span>생산 관리</span>
<svg id="production-group-icon" class="w-3 h-3 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
<span class="font-medium">역할 관리</span>
</a>
</button>
<ul id="production-group" class="space-y-1 mt-1">
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
<span class="font-medium">제품 관리</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
</svg>
<span class="font-medium">자재 관리</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
<span class="font-medium">BOM 관리</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
<span class="font-medium">카테고리 관리</span>
</a>
</li>
</ul>
</li>
<!-- 권한 관리 -->
<li>
<a href="{{ route('permissions.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('permissions.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
<!-- 시스템 그룹 -->
<li class="pt-4 pb-1 border-t border-gray-200 mt-2">
<button onclick="toggleGroup('system-settings-group')" class="w-full flex items-center justify-between px-3 py-2 text-xs font-bold text-gray-600 uppercase tracking-wider hover:bg-gray-50 rounded">
<span>시스템</span>
<svg id="system-settings-group-icon" class="w-3 h-3 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
<span class="font-medium">권한 관리</span>
</a>
</li>
<!-- 부서 관리 -->
<li>
<a href="{{ route('departments.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('departments.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
<span class="font-medium">부서 관리</span>
</a>
</li>
<!-- 메뉴 관리 -->
<li>
<a href="{{ route('menus.index') }}"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100 {{ request()->routeIs('menus.*') ? 'bg-primary text-white hover:bg-primary' : '' }}">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<span class="font-medium">메뉴 관리</span>
</a>
</li>
<!-- 구분선 -->
<li class="py-2">
<div class="border-t border-gray-200"></div>
</li>
<!-- 제품 관리 -->
<li>
<a href="#"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
</svg>
<span class="font-medium">제품 관리</span>
</a>
</li>
<!-- 자재 관리 -->
<li>
<a href="#"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
</svg>
<span class="font-medium">자재 관리</span>
</a>
</li>
<!-- BOM 관리 -->
<li>
<a href="#"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01" />
</svg>
<span class="font-medium">BOM 관리</span>
</a>
</li>
<!-- 카테고리 관리 -->
<li>
<a href="#"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" />
</svg>
<span class="font-medium">카테고리 관리</span>
</a>
</li>
<!-- 구분선 -->
<li class="py-2">
<div class="border-t border-gray-200"></div>
</li>
<!-- 시스템 설정 -->
<li>
<a href="#"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span class="font-medium">시스템 설정</span>
</a>
</li>
<!-- 감사 로그 -->
<li>
<a href="#"
class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-100">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span class="font-medium">감사 로그</span>
</a>
</button>
<ul id="system-settings-group" class="space-y-1 mt-1">
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span class="font-medium">시스템 설정</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<span class="font-medium">감사 로그</span>
</a>
</li>
<li>
<a href="#"
class="flex items-center gap-2 pr-3 py-2 rounded-lg text-sm text-gray-700 hover:bg-gray-100"
style="padding-left: 2rem;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
<span class="font-medium">삭제된 데이터 백업</span>
</a>
</li>
</ul>
</li>
</ul>
</nav>
@@ -183,3 +259,33 @@ class="flex items-center gap-3 px-4 py-3 rounded-lg text-gray-700 hover:bg-gray-
</div>
</div>
</aside>
<script>
function toggleGroup(groupId) {
const group = document.getElementById(groupId);
const icon = document.getElementById(groupId + '-icon');
if (group.style.display === 'none') {
group.style.display = 'block';
icon.style.transform = 'rotate(0deg)';
localStorage.setItem('sidebar-' + groupId, 'open');
} else {
group.style.display = 'none';
icon.style.transform = 'rotate(-90deg)';
localStorage.setItem('sidebar-' + groupId, 'closed');
}
}
// 페이지 로드 시 저장된 상태 복원
document.addEventListener('DOMContentLoaded', function() {
['system-group', 'permission-group', 'production-group', 'system-settings-group'].forEach(function(groupId) {
const state = localStorage.getItem('sidebar-' + groupId);
if (state === 'closed') {
const group = document.getElementById(groupId);
const icon = document.getElementById(groupId + '-icon');
group.style.display = 'none';
icon.style.transform = 'rotate(-90deg)';
}
});
});
</script>

View File

@@ -18,12 +18,12 @@ function formatPermissionName(string $permissionName): string
// 권한 타입별 설정
$permissionConfig = getPermissionConfig($permissionType);
// HTML 생성
$html = '<div class="flex items-center gap-2">';
// HTML 생성 (Admin과 동일한 스타일)
$html = '<div class="flex items-center gap-1 flex-nowrap">';
// 메뉴 태그 (회색 배지)
$html .= sprintf(
'<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 text-gray-700">메뉴 #%d</span>',
'<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 500; border-radius: 0.375rem; background-color: rgb(243 244 246); color: rgb(31 41 55);">메뉴 #%d</span>',
$menuId
);
@@ -35,8 +35,8 @@ function formatPermissionName(string $permissionName): string
// 권한 타입 배지
$html .= sprintf(
'<span class="inline-flex items-center px-2 py-0.5 text-xs font-semibold rounded-full %s" title="%s">%s</span>',
$permissionConfig['class'],
'<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 600; border-radius: 0.375rem; %s" title="%s">%s</span>',
$permissionConfig['style'],
$permissionConfig['label'],
$permissionConfig['badge']
);
@@ -51,7 +51,7 @@ function formatPermissionName(string $permissionName): string
}
/**
* 권한 타입별 설정 반환
* 권한 타입별 설정 반환 (Admin과 동일한 색상)
*/
function getPermissionConfig(string $type): array
{
@@ -59,44 +59,44 @@ function getPermissionConfig(string $type): array
'view' => [
'badge' => 'V',
'label' => '조회',
'class' => 'bg-blue-100 text-blue-800',
'style' => 'background-color: rgb(219 234 254); color: rgb(30 64 175);',
],
'create' => [
'badge' => 'C',
'label' => '생성',
'class' => 'bg-green-100 text-green-800',
'style' => 'background-color: rgb(220 252 231); color: rgb(21 128 61);',
],
'update' => [
'badge' => 'U',
'label' => '수정',
'class' => 'bg-orange-100 text-orange-800',
'style' => 'background-color: rgb(254 215 170); color: rgb(154 52 18);',
],
'delete' => [
'badge' => 'D',
'label' => '삭제',
'class' => 'bg-red-100 text-red-800',
'style' => 'background-color: rgb(254 202 202); color: rgb(153 27 27);',
],
'approve' => [
'badge' => 'A',
'label' => '승인',
'class' => 'bg-purple-100 text-purple-800',
'style' => 'background-color: rgb(233 213 255); color: rgb(107 33 168);',
],
'export' => [
'badge' => 'E',
'label' => '내보내기',
'class' => 'bg-sky-100 text-sky-600',
'style' => 'background-color: rgb(207 250 254); color: rgb(14 116 144);',
],
'manage' => [
'badge' => 'M',
'label' => '관리',
'class' => 'bg-gray-100 text-gray-800',
'style' => 'background-color: rgb(243 244 246); color: rgb(31 41 55);',
],
];
return $configs[$type] ?? [
'badge' => strtoupper(substr($type, 0, 1)),
'label' => $type,
'class' => 'bg-gray-100 text-gray-800',
'style' => 'background-color: rgb(243 244 246); color: rgb(31 41 55);',
];
}
@endphp
@@ -105,39 +105,39 @@ function getPermissionConfig(string $type): array
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">ID</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">권한명</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">테넌트</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">가드</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">할당된 역할</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">할당된 부서</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">생성일</th>
<th class="px-6 py-3 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">수정일</th>
<th class="px-6 py-3 text-right text-sm font-semibold text-gray-700 uppercase tracking-wider">액션</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">ID</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">권한명</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">테넌트</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">가드</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">할당된 역할</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">할당된 부서</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">생성일</th>
<th class="px-4 py-2 text-left text-sm font-semibold text-gray-700 uppercase tracking-wider">수정일</th>
<th class="px-4 py-2 text-right text-sm font-semibold text-gray-700 uppercase tracking-wider">액션</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($permissions as $permission)
<tr>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
{{ $permission->id }}
</td>
<td class="px-6 py-4">
<td class="px-4 py-3 whitespace-nowrap">
{!! formatPermissionName($permission->name) !!}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{{ $permission->tenant?->company_name ?? '전역' }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full {{ $permission->guard_name === 'web' ? 'bg-blue-100 text-blue-800' : 'bg-blue-100 text-blue-800' }}">
<td class="px-4 py-3 whitespace-nowrap">
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 500; border-radius: 0.375rem; background-color: rgb(219 234 254); color: rgb(30 64 175);">
{{ $permission->guard_name }}
</span>
</td>
<td class="px-6 py-4 text-sm text-gray-900">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
@if($permission->roles->isNotEmpty())
<div class="flex flex-wrap gap-1">
<div class="flex flex-nowrap gap-1">
@foreach($permission->roles as $role)
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-green-100 text-green-800">
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 500; border-radius: 0.375rem; background-color: rgb(220 252 231); color: rgb(21 128 61);">
{{ $role->name }}
</span>
@endforeach
@@ -146,11 +146,11 @@ function getPermissionConfig(string $type): array
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-6 py-4 text-sm text-gray-900">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900">
@if($permission->departments->isNotEmpty())
<div class="flex flex-wrap gap-1">
<div class="flex flex-nowrap gap-1">
@foreach($permission->departments as $department)
<span class="inline-flex items-center px-2 py-0.5 text-xs font-medium rounded-full bg-yellow-100 text-yellow-800">
<span style="display: inline-flex; align-items: center; padding: 0.125rem 0.5rem; font-size: 0.75rem; font-weight: 500; border-radius: 0.375rem; background-color: rgb(254 249 195); color: rgb(133 77 14);">
{{ $department->name }}
</span>
@endforeach
@@ -159,13 +159,13 @@ function getPermissionConfig(string $type): array
<span class="text-gray-400">-</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{{ $permission->created_at?->format('Y-m-d H:i') ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">
{{ $permission->updated_at?->format('Y-m-d H:i') ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<td class="px-4 py-3 whitespace-nowrap text-right text-sm font-medium">
<a href="{{ route('permissions.edit', $permission->id) }}"
class="text-blue-600 hover:text-blue-900 mr-3">
수정