feat: 사용자 목록 테넌트 컬럼 추가 및 컨텍스트 메뉴 개선
- 사용자 목록에 테넌트 컬럼 추가 (기본 테넌트 인디고 배지) - UserService: tenants 관계 eager loading 추가 - 컨텍스트 메뉴 우클릭 → 좌클릭 변경 (캡처링 방식) - 전체 blade 파일 툴팁 통일: '클릭하여 메뉴 열기' - flow-tester 오류 분석 문구 수정
This commit is contained in:
@@ -30,12 +30,15 @@ public function getUsers(array $filters = [], int $perPage = 15): LengthAwarePag
|
||||
$query->where('is_super_admin', false);
|
||||
}
|
||||
|
||||
// 역할/부서 관계 eager loading (테넌트별)
|
||||
// 역할/부서/테넌트 관계 eager loading (테넌트별)
|
||||
if ($tenantId) {
|
||||
$query->with([
|
||||
'userRoles' => fn ($q) => $q->where('tenant_id', $tenantId)->with('role'),
|
||||
'departmentUsers' => fn ($q) => $q->where('tenant_id', $tenantId)->with('department'),
|
||||
'tenants',
|
||||
]);
|
||||
} else {
|
||||
$query->with(['tenants']);
|
||||
}
|
||||
|
||||
// 테넌트 필터링 (user_tenants pivot을 통한 필터링)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* 컨텍스트 메뉴 (우클릭 메뉴)
|
||||
* 테넌트명, 사용자명 등에서 우클릭 시 해당 위치에 메뉴 표시
|
||||
* 컨텍스트 메뉴 (클릭 메뉴)
|
||||
* 테넌트명, 사용자명 등에서 클릭 시 해당 위치에 메뉴 표시
|
||||
*/
|
||||
|
||||
class ContextMenu {
|
||||
@@ -15,11 +15,8 @@ class ContextMenu {
|
||||
this.menuElement = document.getElementById('context-menu');
|
||||
if (!this.menuElement) return;
|
||||
|
||||
// 우클릭 이벤트 등록 (이벤트 위임)
|
||||
document.addEventListener('contextmenu', (e) => this.handleContextMenu(e));
|
||||
|
||||
// 클릭 시 메뉴 닫기
|
||||
document.addEventListener('click', () => this.hide());
|
||||
// 좌클릭 이벤트 등록 (캡처링 - 다른 핸들러보다 먼저 실행)
|
||||
document.addEventListener('click', (e) => this.handleClick(e), true);
|
||||
|
||||
// ESC 키로 메뉴 닫기
|
||||
document.addEventListener('keydown', (e) => {
|
||||
@@ -30,11 +27,20 @@ class ContextMenu {
|
||||
document.addEventListener('scroll', () => this.hide(), true);
|
||||
}
|
||||
|
||||
handleContextMenu(e) {
|
||||
handleClick(e) {
|
||||
const trigger = e.target.closest('[data-context-menu]');
|
||||
if (!trigger) return;
|
||||
|
||||
// 메뉴 영역 내 클릭은 무시 (메뉴 항목 클릭 허용)
|
||||
if (e.target.closest('#context-menu')) return;
|
||||
|
||||
// 트리거가 아니면 메뉴 숨기기
|
||||
if (!trigger) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const menuType = trigger.dataset.contextMenu;
|
||||
const entityId = trigger.dataset.entityId;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{{-- 컨텍스트 메뉴 (우클릭 메뉴) --}}
|
||||
{{-- 컨텍스트 메뉴 (클릭 메뉴) --}}
|
||||
<div id="context-menu"
|
||||
class="hidden fixed z-[100] bg-white rounded-lg shadow-lg border border-gray-200 py-1 min-w-[160px]"
|
||||
style="left: 0; top: 0;">
|
||||
|
||||
@@ -469,7 +469,7 @@ function copyErrorForAI() {
|
||||
});
|
||||
}
|
||||
|
||||
markdown += `\n---\n*이 오류를 분석하고 해결 방법을 제안해주세요.*\n`;
|
||||
markdown += `\n---\n*이 오류를 스킬을 이용해서 분석하고 해결 방법을 제안해주세요.*\n`;
|
||||
|
||||
// 클립보드에 복사
|
||||
navigator.clipboard.writeText(markdown).then(() => {
|
||||
|
||||
@@ -41,7 +41,7 @@ class="border-gray-300 rounded-lg text-sm focus:ring-primary focus:border-primar
|
||||
data-context-menu="tenant"
|
||||
data-entity-id="{{ $currentTenant->id }}"
|
||||
data-entity-name="{{ $currentTenant->company_name }}"
|
||||
title="우클릭하여 메뉴 열기">
|
||||
title="클릭하여 메뉴 열기">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
|
||||
@@ -45,7 +45,7 @@ class="border-gray-300 rounded-lg text-sm focus:ring-primary focus:border-primar
|
||||
data-context-menu="tenant"
|
||||
data-entity-id="{{ $currentTenant->id }}"
|
||||
data-entity-name="{{ $currentTenant->company_name }}"
|
||||
title="우클릭하여 메뉴 열기">
|
||||
title="클릭하여 메뉴 열기">
|
||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
data-context-menu="user"
|
||||
data-entity-id="{{ $user['id'] }}"
|
||||
data-entity-name="{{ $user['name'] }}"
|
||||
title="우클릭하여 메뉴 열기">{{ $user['name'] }}</div>
|
||||
title="클릭하여 메뉴 열기">{{ $user['name'] }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $user['email'] }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
@@ -144,7 +144,7 @@
|
||||
data-context-menu="user"
|
||||
data-entity-id="{{ $user['id'] }}"
|
||||
data-entity-name="{{ $user['name'] }}"
|
||||
title="우클릭하여 메뉴 열기">{{ $user['name'] }}</div>
|
||||
title="클릭하여 메뉴 열기">{{ $user['name'] }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $user['email'] }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
data-context-menu="user"
|
||||
data-entity-id="{{ $item['user_id'] }}"
|
||||
data-entity-name="{{ $item['user_name'] }}"
|
||||
title="우클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
title="클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $item['email'] }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
@@ -70,7 +70,7 @@
|
||||
data-context-menu="user"
|
||||
data-entity-id="{{ $item['user_id'] }}"
|
||||
data-entity-name="{{ $item['user_name'] }}"
|
||||
title="우클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
title="클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
<div class="text-xs text-gray-500">{{ $item['email'] }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
@@ -118,7 +118,7 @@
|
||||
data-context-menu="user"
|
||||
data-entity-id="{{ $item['user_id'] }}"
|
||||
data-entity-name="{{ $item['user_name'] }}"
|
||||
title="우클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
title="클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="text-gray-500">{{ $item['email'] }}</div>
|
||||
@@ -160,7 +160,7 @@
|
||||
data-context-menu="user"
|
||||
data-entity-id="{{ $item['user_id'] }}"
|
||||
data-entity-name="{{ $item['user_name'] }}"
|
||||
title="우클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
title="클릭하여 메뉴 열기">{{ $item['user_name'] }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-2">
|
||||
<div class="text-gray-500">{{ $item['email'] }}</div>
|
||||
|
||||
@@ -131,7 +131,7 @@ function getPermissionConfig(string $type): array
|
||||
data-context-menu="tenant"
|
||||
data-entity-id="{{ $permission->tenant->id }}"
|
||||
data-entity-name="{{ $permission->tenant->company_name }}"
|
||||
title="우클릭하여 메뉴 열기">{{ $permission->tenant->company_name }}</span>
|
||||
title="클릭하여 메뉴 열기">{{ $permission->tenant->company_name }}</span>
|
||||
@else
|
||||
<span class="text-gray-400">전역</span>
|
||||
@endif
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
data-context-menu="tenant"
|
||||
data-entity-id="{{ $role->tenant->id }}"
|
||||
data-entity-name="{{ $role->tenant->company_name }}"
|
||||
title="우클릭하여 메뉴 열기">
|
||||
title="클릭하여 메뉴 열기">
|
||||
{{ $role->tenant->company_name }}
|
||||
</span>
|
||||
@else
|
||||
|
||||
@@ -100,7 +100,7 @@ class="w-20 h-20 rounded-full object-cover">
|
||||
data-context-menu="tenant"
|
||||
data-entity-id="{{ $tenant->id }}"
|
||||
data-entity-name="{{ $tenant->company_name }}"
|
||||
title="우클릭하여 메뉴 열기">
|
||||
title="클릭하여 메뉴 열기">
|
||||
{{ $tenant->company_name }}
|
||||
@if($tenant->pivot->is_default)
|
||||
<span class="text-[10px]">(기본)</span>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<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>
|
||||
@@ -20,18 +21,30 @@
|
||||
{{ $user->user_id ?? '-' }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="text-sm font-medium text-gray-900 cursor-pointer hover:text-blue-600"
|
||||
data-context-menu="user"
|
||||
data-entity-id="{{ $user->id }}"
|
||||
data-entity-name="{{ $user->name }}"
|
||||
title="우클릭하여 메뉴 열기"
|
||||
onclick="event.stopPropagation()">
|
||||
<div class="text-sm font-medium text-gray-900">
|
||||
{{ $user->name }}
|
||||
</div>
|
||||
@if($user->is_super_admin && auth()->user()?->is_super_admin)
|
||||
<span class="text-xs text-red-600 font-semibold">슈퍼 관리자</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-500">
|
||||
@if($user->tenants && $user->tenants->count() > 0)
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach($user->tenants as $tenant)
|
||||
<span class="px-1.5 py-0.5 text-xs rounded cursor-pointer hover:ring-2 hover:ring-indigo-300 {{ $tenant->pivot->is_default ? 'bg-indigo-100 text-indigo-700' : 'bg-gray-100 text-gray-600' }}"
|
||||
data-context-menu="tenant"
|
||||
data-entity-id="{{ $tenant->id }}"
|
||||
data-entity-name="{{ $tenant->company_name }}"
|
||||
title="클릭하여 메뉴 열기">
|
||||
{{ $tenant->company_name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<span class="text-gray-400">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{{ $user->email }}
|
||||
</td>
|
||||
@@ -117,7 +130,7 @@ class="text-blue-600 hover:text-blue-900 mr-3">
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-6 py-4 text-center text-gray-500">
|
||||
<td colspan="8" class="px-6 py-4 text-center text-gray-500">
|
||||
사용자가 없습니다.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user