From 0d79aa3d3754642a995ed0005b6edb7bc832c69f Mon Sep 17 00:00:00 2001 From: kent Date: Sun, 21 Dec 2025 14:19:26 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20[api-explorer]=20=EC=A6=90=EA=B2=A8?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=20=EB=B0=8F=20=ED=83=9C=EA=B7=B8=EB=AA=85=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 즐겨찾기 클릭 시 404 오류 수정 (selectEndpointByPath 함수 추가) - 태그명 형식을 "한글명 (English)"로 변경 - 사용자 목록 조회 오류 수정 (user_tenants 피벗 테이블 사용) - 즐겨찾기 토글 시 페이지 새로고침 없이 로컬 상태 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../DevTools/ApiExplorerController.php | 17 +- .../dev-tools/api-explorer/index.blade.php | 224 ++++++++++++++++-- .../api-explorer/partials/sidebar.blade.php | 113 ++++++++- 3 files changed, 336 insertions(+), 18 deletions(-) diff --git a/app/Http/Controllers/DevTools/ApiExplorerController.php b/app/Http/Controllers/DevTools/ApiExplorerController.php index f8dc2c45..1026d43a 100644 --- a/app/Http/Controllers/DevTools/ApiExplorerController.php +++ b/app/Http/Controllers/DevTools/ApiExplorerController.php @@ -450,9 +450,22 @@ public function setDefaultEnvironment(int $id): JsonResponse */ public function users(): JsonResponse { - $tenantId = auth()->user()->tenant_id; + // user_tenants 피벗 테이블에서 기본 테넌트 조회 + $defaultTenant = \DB::table('user_tenants') + ->where('user_id', auth()->id()) + ->where('is_default', true) + ->first(); - $users = \App\Models\User::where('tenant_id', $tenantId) + if (!$defaultTenant) { + return response()->json([]); + } + + $tenantId = $defaultTenant->tenant_id; + + // 해당 테넌트에 속한 사용자 목록 조회 + $users = \App\Models\User::whereHas('tenants', function ($query) use ($tenantId) { + $query->where('tenant_id', $tenantId); + }) ->select(['id', 'name', 'email']) ->orderBy('name') ->limit(100) diff --git a/resources/views/dev-tools/api-explorer/index.blade.php b/resources/views/dev-tools/api-explorer/index.blade.php index 5e37ac2c..cb869549 100644 --- a/resources/views/dev-tools/api-explorer/index.blade.php +++ b/resources/views/dev-tools/api-explorer/index.blade.php @@ -471,12 +471,194 @@ function filterEndpoints() { renderSidebar(grouped); } - // 사이드바 렌더링 + // 태그명 한글 매핑 + const tagNameMap = { + // 인증/사용자 + 'Auth': '인증', + 'User': '사용자', + 'Admin-Users': '관리자-사용자', + 'UserInvitation': '사용자 초대', + 'UserRole': '사용자 역할', + 'Member': '회원', + + // 결재 + 'Approvals': '결재', + 'Approval Forms': '결재 양식', + 'Approval Lines': '결재선', + + // 조직 + 'Tenant': '테넌트', + 'Tenant.Fields': '테넌트 필드', + 'Tenant.Option Groups': '테넌트 옵션그룹', + 'Tenant.Option Values': '테넌트 옵션값', + 'Tenant.Profiles': '테넌트 프로필', + 'TenantStatField': '테넌트 통계필드', + 'Menu': '메뉴', + 'Role': '역할', + 'RolePermission': '역할 권한', + 'Permission': '권한', + 'Department': '부서', + + // 품목/제품 + 'Product': '제품', + 'Items': '품목', + 'Items BOM': '품목 BOM', + 'Items Files': '품목 파일', + 'ItemMaster': '품목마스터', + 'ItemMaster-Relationships': '품목마스터 관계', + 'Category': '카테고리', + 'Category-Fields': '카테고리 필드', + 'Category-Logs': '카테고리 로그', + 'Category-Templates': '카테고리 템플릿', + 'Classification': '분류', + + // 설계/BOM + 'Model': '모델', + 'ModelVersion': '모델 버전', + 'BomTemplate': 'BOM 템플릿', + 'Design BOM': '설계 BOM', + 'Design Audit': '설계 감사', + 'BOM Calculation': 'BOM 계산', + + // 견적/주문 + 'Estimate': '견적', + 'Quote': '견적서', + 'Plans': '플랜', + + // 거래처 + 'Client': '거래처', + 'ClientGroup': '거래처 그룹', + 'Sites': '현장', + + // 재무/회계 + 'Account': '계정', + 'BankAccounts': '계좌', + 'Cards': '카드', + 'Deposits': '입금', + 'Withdrawals': '출금', + 'Sales': '매출', + 'Purchases': '매입', + 'Payments': '결제', + 'TaxInvoices': '세금계산서', + 'BadDebt': '악성채권', + 'Pricing': '단가', + 'Loans': '대출', + + // HR + 'Employees': '직원', + 'Attendances': '근태', + 'Leaves': '휴가', + 'Payrolls': '급여', + 'WorkSettings': '근무설정', + + // 파일/게시판 + 'Files': '파일', + 'Folder': '폴더', + 'Board': '게시판', + 'Post': '게시글', + 'Popup': '팝업', + + // 설정 + 'Settings - Common Codes': '설정 - 공통코드', + 'Settings - Fields': '설정 - 필드', + 'NotificationSetting': '알림설정', + 'BarobillSettings': '바로빌설정', + 'Subscriptions': '구독', + + // 시스템 + 'Dashboard': '대시보드', + 'Reports': '리포트', + 'AI Reports': 'AI 리포트', + 'Push': '푸시', + 'Internal': '내부', + 'API Key 인증': 'API Key 인증', + + 'default': '기타' + }; + + // 태그명 한글로 변환 (한글명 (English) 형식) + function getKoreanTagName(tag) { + const koreanName = tagNameMap[tag]; + if (koreanName && koreanName !== tag) { + return `${koreanName} (${tag})`; + } + return tag; + } + + // 사이드바 렌더링 (즐겨찾기 포함) function renderSidebar(groupedEndpoints) { const container = document.getElementById('endpoint-list'); const tags = Object.keys(groupedEndpoints).sort(); - if (tags.length === 0) { + let html = ''; + + // 즐겨찾기 섹션 (메서드 필터 적용) + const bookmarkedItems = allEndpoints.filter(ep => { + const key = ep.path + '|' + ep.method; + if (!bookmarkedEndpoints.includes(key)) return false; + + // 메서드 필터 적용 + if (activeMethodFilters.length > 0 && !activeMethodFilters.includes(ep.method)) { + return false; + } + + // 검색어 필터 적용 + const searchQuery = document.getElementById('search-input').value.toLowerCase().trim(); + if (searchQuery) { + const searchTargets = [ + ep.path || '', + ep.summary || '', + ep.description || '', + ep.operationId || '', + ...(ep.tags || []) + ].map(s => s.toLowerCase()); + return searchTargets.some(target => target.includes(searchQuery)); + } + + return true; + }); + + if (bookmarkedItems.length > 0) { + html += ` +
+
+ + + + + 즐겨찾기 (${bookmarkedItems.length}) + +
+
+ `; + + bookmarkedItems.forEach(endpoint => { + html += ` +
+ + ${endpoint.method} + + + ${escapeHtml(endpoint.path)} + + +
+ `; + }); + + html += ` +
+
+ `; + } + + // 검색/필터 결과가 없는 경우 + if (tags.length === 0 && bookmarkedItems.length === 0) { container.innerHTML = `
@@ -488,10 +670,11 @@ function renderSidebar(groupedEndpoints) { return; } - let html = ''; + // 태그별 그룹 tags.forEach(tag => { const endpoints = groupedEndpoints[tag]; const tagSlug = tag.toLowerCase().replace(/[^a-z0-9]/g, '-'); + const koreanTag = getKoreanTagName(tag); html += `
@@ -500,7 +683,7 @@ function renderSidebar(groupedEndpoints) { - ${escapeHtml(tag)} + ${escapeHtml(koreanTag)} ${endpoints.length}
@@ -564,6 +747,16 @@ function selectEndpoint(operationId, element) { }); } + // 엔드포인트 선택 (path와 method로 검색) - 즐겨찾기용 + function selectEndpointByPath(path, method, element) { + const endpoint = allEndpoints.find(ep => ep.path === path && ep.method === method); + if (endpoint) { + selectEndpoint(endpoint.operationId, element); + } else { + showToast('해당 API를 찾을 수 없습니다.', 'error'); + } + } + // API 실행 async function executeApi(event) { event.preventDefault(); @@ -931,7 +1124,7 @@ function fillFormFromHistory(data) { // (필요시 endpoint URL에서 파싱하여 채울 수 있음) } - // 즐겨찾기 토글 + // 즐겨찾기 토글 (새로고침 없이 로컬 상태 업데이트) async function toggleBookmark(endpoint, method, button) { const response = await fetch('{{ route("dev-tools.api-explorer.bookmarks.toggle") }}', { method: 'POST', @@ -943,20 +1136,23 @@ function fillFormFromHistory(data) { }); const result = await response.json(); + const key = endpoint + '|' + method; if (result.action === 'added') { - button.classList.add('text-yellow-500'); - button.classList.remove('text-gray-400'); + // 즐겨찾기 추가 + if (!bookmarkedEndpoints.includes(key)) { + bookmarkedEndpoints.push(key); + } } else { - button.classList.remove('text-yellow-500'); - button.classList.add('text-gray-400'); + // 즐겨찾기 제거 + const index = bookmarkedEndpoints.indexOf(key); + if (index > -1) { + bookmarkedEndpoints.splice(index, 1); + } } - // 사이드바 새로고침 - htmx.ajax('GET', '{{ route("dev-tools.api-explorer.endpoints") }}', { - target: '#endpoint-list', - swap: 'innerHTML' - }); + // 현재 필터 상태 유지하면서 사이드바 다시 렌더링 + filterEndpoints(); } // 유틸리티 diff --git a/resources/views/dev-tools/api-explorer/partials/sidebar.blade.php b/resources/views/dev-tools/api-explorer/partials/sidebar.blade.php index 7e9fda64..7fd719be 100644 --- a/resources/views/dev-tools/api-explorer/partials/sidebar.blade.php +++ b/resources/views/dev-tools/api-explorer/partials/sidebar.blade.php @@ -11,7 +11,7 @@
@foreach($bookmarks as $bookmark) -
+
{{ $bookmark->method }} @@ -30,15 +30,124 @@ class="text-yellow-500 hover:text-yellow-600">
@endif +@php + // 태그명 한글 매핑 + $tagNameMap = [ + // 인증/사용자 + 'Auth' => '인증', + 'User' => '사용자', + 'Admin-Users' => '관리자-사용자', + 'UserInvitation' => '사용자 초대', + 'UserRole' => '사용자 역할', + 'Member' => '회원', + + // 결재 + 'Approvals' => '결재', + 'Approval Forms' => '결재 양식', + 'Approval Lines' => '결재선', + + // 조직 + 'Tenant' => '테넌트', + 'Tenant.Fields' => '테넌트 필드', + 'Tenant.Option Groups' => '테넌트 옵션그룹', + 'Tenant.Option Values' => '테넌트 옵션값', + 'Tenant.Profiles' => '테넌트 프로필', + 'TenantStatField' => '테넌트 통계필드', + 'Menu' => '메뉴', + 'Role' => '역할', + 'RolePermission' => '역할 권한', + 'Permission' => '권한', + 'Department' => '부서', + + // 품목/제품 + 'Product' => '제품', + 'Items' => '품목', + 'Items BOM' => '품목 BOM', + 'Items Files' => '품목 파일', + 'ItemMaster' => '품목마스터', + 'ItemMaster-Relationships' => '품목마스터 관계', + 'Category' => '카테고리', + 'Category-Fields' => '카테고리 필드', + 'Category-Logs' => '카테고리 로그', + 'Category-Templates' => '카테고리 템플릿', + 'Classification' => '분류', + + // 설계/BOM + 'Model' => '모델', + 'ModelVersion' => '모델 버전', + 'BomTemplate' => 'BOM 템플릿', + 'Design BOM' => '설계 BOM', + 'Design Audit' => '설계 감사', + 'BOM Calculation' => 'BOM 계산', + + // 견적/주문 + 'Estimate' => '견적', + 'Quote' => '견적서', + 'Plans' => '플랜', + + // 거래처 + 'Client' => '거래처', + 'ClientGroup' => '거래처 그룹', + 'Sites' => '현장', + + // 재무/회계 + 'Account' => '계정', + 'BankAccounts' => '계좌', + 'Cards' => '카드', + 'Deposits' => '입금', + 'Withdrawals' => '출금', + 'Sales' => '매출', + 'Purchases' => '매입', + 'Payments' => '결제', + 'TaxInvoices' => '세금계산서', + 'BadDebt' => '악성채권', + 'Pricing' => '단가', + 'Loans' => '대출', + + // HR + 'Employees' => '직원', + 'Attendances' => '근태', + 'Leaves' => '휴가', + 'Payrolls' => '급여', + 'WorkSettings' => '근무설정', + + // 파일/게시판 + 'Files' => '파일', + 'Folder' => '폴더', + 'Board' => '게시판', + 'Post' => '게시글', + 'Popup' => '팝업', + + // 설정 + 'Settings - Common Codes' => '설정 - 공통코드', + 'Settings - Fields' => '설정 - 필드', + 'NotificationSetting' => '알림설정', + 'BarobillSettings' => '바로빌설정', + 'Subscriptions' => '구독', + + // 시스템 + 'Dashboard' => '대시보드', + 'Reports' => '리포트', + 'AI Reports' => 'AI 리포트', + 'Push' => '푸시', + 'Internal' => '내부', + 'API Key 인증' => 'API Key 인증', + ]; +@endphp + {{-- 태그별 엔드포인트 그룹 --}} @forelse($endpointsByTag as $tag => $endpoints) + @php + $koreanTag = $tagNameMap[$tag] ?? null; + $displayTag = $koreanTag && $koreanTag !== $tag ? "{$koreanTag} ({$tag})" : $tag; + @endphp
- {{ $tag }} + {{ $displayTag }} {{ $endpoints->count() }}