역할/부서 권한 관리 페이지 테넌트별 그룹핑 기능 추가

- 전체 테넌트 선택 시 역할/부서를 테넌트별로 그룹화하여 표시
- 테넌트별 섹션 헤더 추가 (회색 라벨)
- 선택 시 [테넌트명] 역할/부서명 형식으로 표시
- 단일 테넌트 선택 시 기존 UI 유지
This commit is contained in:
2025-11-26 15:58:54 +09:00
parent 69b04ae041
commit f029d78614
5 changed files with 192 additions and 44 deletions

View File

@@ -2,6 +2,8 @@
namespace App\Http\Controllers;
use App\Models\Tenants\Department;
use App\Models\Tenants\Tenant;
use App\Services\DepartmentPermissionService;
use Illuminate\Http\Request;
@@ -22,14 +24,27 @@ public function index(Request $request)
$tenantId = session('selected_tenant_id');
// 부서 목록 조회
$departments = \App\Models\Tenants\Department::query();
$departmentsQuery = Department::query()->orderBy('tenant_id')->orderBy('sort_order')->orderBy('name');
if ($tenantId && $tenantId !== 'all') {
$departments->where('tenant_id', $tenantId);
// 특정 테넌트 선택 시
$departments = $departmentsQuery->where('tenant_id', $tenantId)->get();
$departmentsByTenant = null;
} else {
// 전체 선택 시 테넌트별 그룹핑
$departments = $departmentsQuery->get();
$departmentsByTenant = $departments->groupBy('tenant_id');
// 테넌트 정보 로드
$tenantIds = $departmentsByTenant->keys()->filter()->toArray();
$tenants = Tenant::whereIn('id', $tenantIds)->pluck('company_name', 'id');
}
$departments = $departments->orderBy('sort_order')->orderBy('name')->get();
return view('department-permissions.index', [
'departments' => $departments,
'departmentsByTenant' => $departmentsByTenant ?? null,
'tenants' => $tenants ?? collect(),
'selectedTenantId' => $tenantId,
]);
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Http\Controllers;
use App\Models\Role;
use App\Models\Tenants\Tenant;
use App\Services\RolePermissionService;
use Illuminate\Http\Request;
@@ -22,14 +24,27 @@ public function index(Request $request)
$tenantId = session('selected_tenant_id');
// 역할 목록 조회
$roles = \App\Models\Role::query();
$rolesQuery = Role::query()->orderBy('tenant_id')->orderBy('name');
if ($tenantId && $tenantId !== 'all') {
$roles->where('tenant_id', $tenantId);
// 특정 테넌트 선택 시
$roles = $rolesQuery->where('tenant_id', $tenantId)->get();
$rolesByTenant = null;
} else {
// 전체 선택 시 테넌트별 그룹핑
$roles = $rolesQuery->get();
$rolesByTenant = $roles->groupBy('tenant_id');
// 테넌트 정보 로드
$tenantIds = $rolesByTenant->keys()->filter()->toArray();
$tenants = Tenant::whereIn('id', $tenantIds)->pluck('company_name', 'id');
}
$roles = $roles->orderBy('name')->get();
return view('role-permissions.index', [
'roles' => $roles,
'rolesByTenant' => $rolesByTenant ?? null,
'tenants' => $tenants ?? collect(),
'selectedTenantId' => $tenantId,
]);
}
}

View File

@@ -0,0 +1,50 @@
# 역할/부서 권한 관리 - 테넌트별 그룹핑
## 변경 일자
2025-11-26
## 변경 목적
- "전체" 테넌트 선택 시 역할/부서가 혼합되어 표시되는 문제 해결
- 테넌트별로 그룹핑하여 가독성 향상
## 변경 파일
### 1. Controllers
| 파일 | 변경 내용 |
|------|----------|
| `app/Http/Controllers/RolePermissionController.php` | 테넌트별 역할 그룹핑 로직 추가 |
| `app/Http/Controllers/DepartmentPermissionController.php` | 테넌트별 부서 그룹핑 로직 추가 |
**주요 로직:**
```php
if ($tenantId && $tenantId !== 'all') {
// 특정 테넌트 선택 시 기존 방식
$roles = $rolesQuery->where('tenant_id', $tenantId)->get();
$rolesByTenant = null;
} else {
// 전체 선택 시 테넌트별 그룹핑
$roles = $rolesQuery->get();
$rolesByTenant = $roles->groupBy('tenant_id');
$tenantIds = $rolesByTenant->keys()->filter()->toArray();
$tenants = Tenant::whereIn('id', $tenantIds)->pluck('company_name', 'id');
}
```
### 2. Views
| 파일 | 변경 내용 |
|------|----------|
| `resources/views/role-permissions/index.blade.php` | 테넌트별 섹션 헤더 및 그룹핑 UI |
| `resources/views/department-permissions/index.blade.php` | 테넌트별 섹션 헤더 및 그룹핑 UI |
**UI 변경:**
- 전체 테넌트 선택 시: 테넌트별 섹션으로 구분 (회색 라벨)
- 선택된 역할/부서 표시: `[테넌트명] 역할명 역할` 형식
- 기존 단일 테넌트 선택 시: 기존 UI 유지
## 테스트 결과
- PHP 문법 검사: 통과
- Laravel Pint: 통과
- 브라우저 테스트: 테넌트별 그룹핑 정상 동작
## 관련 이슈
- tenants 테이블 컬럼명: `company_name` (name 아님)

View File

@@ -11,25 +11,57 @@
<!-- 부서 선택 -->
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="px-6 py-4">
<div class="flex flex-wrap items-center gap-3">
<span class="text-sm font-medium text-gray-700">부서 선택:</span>
@foreach($departments as $department)
<button
type="button"
class="department-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors
{{ $loop->first ? 'bg-blue-700 text-white border-blue-700' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50' }}"
data-department-id="{{ $department->id }}"
data-department-name="{{ $department->name }}"
hx-get="/api/admin/department-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"department_id": {{ $department->id }}}'
onclick="selectDepartment(this)"
>
{{ $department->name }}
</button>
@if($departmentsByTenant)
{{-- 전체 테넌트 선택 : 테넌트별 그룹핑 --}}
@foreach($departmentsByTenant as $tenantId => $tenantDepartments)
<div class="mb-4 last:mb-0">
<div class="flex items-center gap-2 mb-2">
<span class="px-2 py-1 text-xs font-semibold bg-gray-100 text-gray-600 rounded">
{{ $tenants[$tenantId] ?? '미지정' }}
</span>
</div>
<div class="flex flex-wrap items-center gap-3 pl-2">
@foreach($tenantDepartments as $department)
<button
type="button"
class="department-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
data-department-id="{{ $department->id }}"
data-department-name="{{ $department->name }}"
data-tenant-name="{{ $tenants[$tenantId] ?? '미지정' }}"
hx-get="/api/admin/department-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"department_id": {{ $department->id }}}'
onclick="selectDepartment(this)"
>
{{ $department->name }}
</button>
@endforeach
</div>
</div>
@endforeach
</div>
@else
{{-- 특정 테넌트 선택 : 기존 방식 --}}
<div class="flex flex-wrap items-center gap-3">
<span class="text-sm font-medium text-gray-700">부서 선택:</span>
@foreach($departments as $department)
<button
type="button"
class="department-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors
{{ $loop->first ? 'bg-blue-700 text-white border-blue-700' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50' }}"
data-department-id="{{ $department->id }}"
data-department-name="{{ $department->name }}"
hx-get="/api/admin/department-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"department_id": {{ $department->id }}}'
onclick="selectDepartment(this)"
>
{{ $department->name }}
</button>
@endforeach
</div>
@endif
</div>
</div>
@@ -109,9 +141,11 @@ function selectDepartment(button) {
// 부서 정보 저장
const departmentId = button.getAttribute('data-department-id');
const departmentName = button.getAttribute('data-department-name');
const tenantName = button.getAttribute('data-tenant-name');
document.getElementById('departmentIdInput').value = departmentId;
document.getElementById('selected-department-name').textContent = departmentName + ' 부서';
const displayName = tenantName ? `[${tenantName}] ${departmentName} 부서` : `${departmentName} 부서`;
document.getElementById('selected-department-name').textContent = displayName;
// 액션 버튼 표시
document.getElementById('action-buttons').style.display = 'block';

View File

@@ -11,25 +11,57 @@
<!-- 역할 선택 버튼 -->
<div class="bg-white rounded-lg shadow-sm mb-6">
<div class="px-6 py-4">
<div class="flex flex-wrap items-center gap-3">
<span class="text-sm font-medium text-gray-700">역할 선택:</span>
@foreach($roles as $role)
<button
type="button"
class="role-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors
{{ $loop->first ? 'bg-blue-700 text-white border-blue-700' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50' }}"
data-role-id="{{ $role->id }}"
data-role-name="{{ $role->name }}"
hx-get="/api/admin/role-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"role_id": {{ $role->id }}}'
onclick="selectRole(this)"
>
{{ $role->name }}
</button>
@if($rolesByTenant)
{{-- 전체 테넌트 선택 : 테넌트별 그룹핑 --}}
@foreach($rolesByTenant as $tenantId => $tenantRoles)
<div class="mb-4 last:mb-0">
<div class="flex items-center gap-2 mb-2">
<span class="px-2 py-1 text-xs font-semibold bg-gray-100 text-gray-600 rounded">
{{ $tenants[$tenantId] ?? '미지정' }}
</span>
</div>
<div class="flex flex-wrap items-center gap-3 pl-2">
@foreach($tenantRoles as $role)
<button
type="button"
class="role-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors bg-white text-gray-700 border-gray-300 hover:bg-gray-50"
data-role-id="{{ $role->id }}"
data-role-name="{{ $role->name }}"
data-tenant-name="{{ $tenants[$tenantId] ?? '미지정' }}"
hx-get="/api/admin/role-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"role_id": {{ $role->id }}}'
onclick="selectRole(this)"
>
{{ $role->name }}
</button>
@endforeach
</div>
</div>
@endforeach
</div>
@else
{{-- 특정 테넌트 선택 : 기존 방식 --}}
<div class="flex flex-wrap items-center gap-3">
<span class="text-sm font-medium text-gray-700">역할 선택:</span>
@foreach($roles as $role)
<button
type="button"
class="role-button px-4 py-2 text-sm font-medium rounded-lg border transition-colors
{{ $loop->first ? 'bg-blue-700 text-white border-blue-700' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50' }}"
data-role-id="{{ $role->id }}"
data-role-name="{{ $role->name }}"
hx-get="/api/admin/role-permissions/matrix"
hx-target="#permission-matrix"
hx-include="[name='guard_name']"
hx-vals='{"role_id": {{ $role->id }}}'
onclick="selectRole(this)"
>
{{ $role->name }}
</button>
@endforeach
</div>
@endif
</div>
</div>
@@ -109,9 +141,11 @@ function selectRole(button) {
// 역할 정보 저장
const roleId = button.getAttribute('data-role-id');
const roleName = button.getAttribute('data-role-name');
const tenantName = button.getAttribute('data-tenant-name');
document.getElementById('roleIdInput').value = roleId;
document.getElementById('selected-role-name').textContent = roleName + ' 역할';
const displayName = tenantName ? `[${tenantName}] ${roleName} 역할` : `${roleName} 역할`;
document.getElementById('selected-role-name').textContent = displayName;
// 액션 버튼 표시
document.getElementById('action-buttons').style.display = 'block';