feat: 사용자 목록 테넌트 컬럼 추가 및 컨텍스트 메뉴 개선

- 사용자 목록에 테넌트 컬럼 추가 (기본 테넌트 인디고 배지)
- UserService: tenants 관계 eager loading 추가
- 컨텍스트 메뉴 우클릭 → 좌클릭 변경 (캡처링 방식)
- 전체 blade 파일 툴팁 통일: '클릭하여 메뉴 열기'
- flow-tester 오류 분석 문구 수정
This commit is contained in:
2025-12-09 10:28:46 +09:00
parent b585369889
commit 428d3d9e83
12 changed files with 52 additions and 30 deletions

View File

@@ -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을 통한 필터링)

View File

@@ -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;

View File

@@ -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;">

View File

@@ -469,7 +469,7 @@ function copyErrorForAI() {
});
}
markdown += `\n---\n*이 오류를 분석하고 해결 방법을 제안해주세요.*\n`;
markdown += `\n---\n*이 오류를 스킬을 이용해서 분석하고 해결 방법을 제안해주세요.*\n`;
// 클립보드에 복사
navigator.clipboard.writeText(markdown).then(() => {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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>