diff --git a/app/Services/PermissionAnalyzeService.php b/app/Services/PermissionAnalyzeService.php index d361db9d..150d5229 100644 --- a/app/Services/PermissionAnalyzeService.php +++ b/app/Services/PermissionAnalyzeService.php @@ -286,6 +286,11 @@ private function getUsersByTenant(?int $tenantId): Collection { $query = User::where('is_active', true); + // 일반 관리자는 슈퍼관리자를 볼 수 없음 + if (! auth()->user()?->is_super_admin) { + $query->where('is_super_admin', false); + } + if ($tenantId) { $query->whereHas('tenants', function ($q) use ($tenantId) { $q->where('tenants.id', $tenantId) @@ -358,9 +363,10 @@ private function getUserRoles(int $userId, ?int $tenantId): array public function traceUsersWithPermission(int $menuId, string $permissionType = 'view', ?int $tenantId = null, string $guardName = 'api'): array { $permissionName = "menu:{$menuId}.{$permissionType}"; + $excludeSuperAdmin = ! auth()->user()?->is_super_admin; // 역할로 권한이 있는 사용자 - $usersFromRole = DB::table('model_has_roles as mhr') + $usersFromRoleQuery = DB::table('model_has_roles as mhr') ->join('role_has_permissions as rhp', 'rhp.role_id', '=', 'mhr.role_id') ->join('permissions as p', 'p.id', '=', 'rhp.permission_id') ->join('users as u', 'u.id', '=', 'mhr.model_id') @@ -369,12 +375,18 @@ public function traceUsersWithPermission(int $menuId, string $permissionType = ' ->where('mhr.model_type', User::class) ->where('p.guard_name', $guardName) ->where('p.name', $permissionName) - ->where('u.is_active', true) - ->get(); + ->where('u.is_active', true); + + // 일반 관리자는 슈퍼관리자를 볼 수 없음 + if ($excludeSuperAdmin) { + $usersFromRoleQuery->where('u.is_super_admin', false); + } + + $usersFromRole = $usersFromRoleQuery->get(); // 부서로 권한이 있는 사용자 $now = now(); - $usersFromDepartment = DB::table('department_user as du') + $usersFromDepartmentQuery = DB::table('department_user as du') ->join('permission_overrides as po', function ($j) use ($now) { $j->on('po.model_id', '=', 'du.department_id') ->where('po.model_type', Department::class) @@ -396,15 +408,19 @@ public function traceUsersWithPermission(int $menuId, string $permissionType = ' ->where('p.name', $permissionName) ->where('u.is_active', true); + if ($excludeSuperAdmin) { + $usersFromDepartmentQuery->where('u.is_super_admin', false); + } + if ($tenantId) { - $usersFromDepartment->where('du.tenant_id', $tenantId) + $usersFromDepartmentQuery->where('du.tenant_id', $tenantId) ->where('po.tenant_id', $tenantId); } - $usersFromDepartment = $usersFromDepartment->get(); + $usersFromDepartment = $usersFromDepartmentQuery->get(); // 개인 ALLOW 오버라이드가 있는 사용자 - $usersFromPersonal = DB::table('permission_overrides as po') + $usersFromPersonalQuery = DB::table('permission_overrides as po') ->join('permissions as p', 'p.id', '=', 'po.permission_id') ->join('users as u', 'u.id', '=', 'po.model_id') ->select('u.id as user_id', 'u.name as user_name', 'u.email', 'po.effect') @@ -421,14 +437,18 @@ public function traceUsersWithPermission(int $menuId, string $permissionType = ' $w->whereNull('po.effective_to')->orWhere('po.effective_to', '>=', $now); }); - if ($tenantId) { - $usersFromPersonal->where('po.tenant_id', $tenantId); + if ($excludeSuperAdmin) { + $usersFromPersonalQuery->where('u.is_super_admin', false); } - $usersFromPersonal = $usersFromPersonal->get(); + if ($tenantId) { + $usersFromPersonalQuery->where('po.tenant_id', $tenantId); + } + + $usersFromPersonal = $usersFromPersonalQuery->get(); // 개인 DENY 오버라이드가 있는 사용자 - $usersWithDeny = DB::table('permission_overrides as po') + $usersWithDenyQuery = DB::table('permission_overrides as po') ->join('permissions as p', 'p.id', '=', 'po.permission_id') ->join('users as u', 'u.id', '=', 'po.model_id') ->select('u.id as user_id', 'u.name as user_name', 'u.email') @@ -445,11 +465,15 @@ public function traceUsersWithPermission(int $menuId, string $permissionType = ' $w->whereNull('po.effective_to')->orWhere('po.effective_to', '>=', $now); }); - if ($tenantId) { - $usersWithDeny->where('po.tenant_id', $tenantId); + if ($excludeSuperAdmin) { + $usersWithDenyQuery->where('u.is_super_admin', false); } - $usersWithDeny = $usersWithDeny->get(); + if ($tenantId) { + $usersWithDenyQuery->where('po.tenant_id', $tenantId); + } + + $usersWithDeny = $usersWithDenyQuery->get(); return [ 'by_role' => $usersFromRole->map(function ($item) { diff --git a/config/app.php b/config/app.php index 423eed59..a8a8f51b 100644 --- a/config/app.php +++ b/config/app.php @@ -65,7 +65,7 @@ | */ - 'timezone' => 'UTC', + 'timezone' => env('APP_TIMEZONE', 'Asia/Seoul'), /* |-------------------------------------------------------------------------- diff --git a/resources/views/archived-records/partials/table.blade.php b/resources/views/archived-records/partials/table.blade.php index a03cf554..5e42e255 100644 --- a/resources/views/archived-records/partials/table.blade.php +++ b/resources/views/archived-records/partials/table.blade.php @@ -2,15 +2,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/resources/views/boards/partials/table.blade.php b/resources/views/boards/partials/table.blade.php index 3e809f44..2c2e4a71 100644 --- a/resources/views/boards/partials/table.blade.php +++ b/resources/views/boards/partials/table.blade.php @@ -2,14 +2,14 @@
ID작업 설명대상 테넌트대상 정보레코드 타입레코드 수삭제자삭제일시작업ID작업 설명대상 테넌트대상 정보레코드 타입레코드 수삭제자삭제일시작업
- - - - - - - - + + + + + + + + diff --git a/resources/views/department-permissions/partials/permission-matrix.blade.php b/resources/views/department-permissions/partials/permission-matrix.blade.php index 3e277603..357e8206 100644 --- a/resources/views/department-permissions/partials/permission-matrix.blade.php +++ b/resources/views/department-permissions/partials/permission-matrix.blade.php @@ -2,17 +2,17 @@
ID코드게시판명유형필드 수상태생성일액션ID코드게시판명유형필드 수상태생성일액션
- - - - - - - - - - - + + + + + + + + + + + @@ -24,11 +24,11 @@ data-menu-id="{{ $menu->id }}" data-parent-id="{{ $menu->parent_id ?? '' }}" data-depth="{{ $menu->depth ?? 0 }}"> - - - - @foreach($permissionTypes as $type) -
순번메뉴명URL순서조회생성수정삭제승인내보내기관리순번메뉴명URL순서조회생성수정삭제승인내보내기관리
+ {{ $index + 1 }} -
+
+
{{-- 트리 구조 표시 --}} @if(($menu->depth ?? 0) > 0) └─ @@ -59,20 +59,20 @@ class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outl
+ {{ $menu->url }} + {{ $menu->sort_order }} + id][$type]) && $permissions[$menu->id][$type] ? 'checked' : '' }} - class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" + class="h-5 w-5 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" hx-post="/api/admin/department-permissions/toggle" hx-trigger="click" hx-target="#permission-matrix" diff --git a/resources/views/dev-tools/flow-tester/index.blade.php b/resources/views/dev-tools/flow-tester/index.blade.php index d15afb0a..4eb24ffd 100644 --- a/resources/views/dev-tools/flow-tester/index.blade.php +++ b/resources/views/dev-tools/flow-tester/index.blade.php @@ -78,12 +78,12 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc - - - - - - + + + + + + @@ -139,7 +139,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
이름카테고리스텝최근 실행상태액션이름카테고리스텝최근 실행상태액션
- - - - - - - - + + + + + + + + diff --git a/resources/views/menus/partials/table.blade.php b/resources/views/menus/partials/table.blade.php index 52a19d39..fbea6886 100644 --- a/resources/views/menus/partials/table.blade.php +++ b/resources/views/menus/partials/table.blade.php @@ -4,23 +4,23 @@ {{-- 체크박스 (가져오기 모드일 때만 표시) --}} @if($importMode ?? false) - @else - + @endif - - - - - - - - + + + + + + + + diff --git a/resources/views/permission-analyze/index.blade.php b/resources/views/permission-analyze/index.blade.php index a65c4fbd..0249ef17 100644 --- a/resources/views/permission-analyze/index.blade.php +++ b/resources/views/permission-analyze/index.blade.php @@ -67,8 +67,8 @@ class="px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring
- -
+ +

메뉴 트리

@@ -87,9 +87,9 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none foc
- -
-
+ +
+

선택된 메뉴

diff --git a/resources/views/permissions/partials/table.blade.php b/resources/views/permissions/partials/table.blade.php index c3d5e26c..3106e786 100644 --- a/resources/views/permissions/partials/table.blade.php +++ b/resources/views/permissions/partials/table.blade.php @@ -105,15 +105,15 @@ function getPermissionConfig(string $type): array
No.메뉴명URL정렬활성숨김작업No.메뉴명URL정렬활성숨김작업
+ No.메뉴명URL정렬활성숨김구분작업No.메뉴명URL정렬활성숨김구분작업
- - - - - - - - - + + + + + + + + + diff --git a/resources/views/project-management/index.blade.php b/resources/views/project-management/index.blade.php index 45b8754e..06cb06e3 100644 --- a/resources/views/project-management/index.blade.php +++ b/resources/views/project-management/index.blade.php @@ -218,7 +218,7 @@
@@ -452,10 +452,10 @@ class="p-6">

- + - 스크럼 항목 수정 + 스크럼 항목 수정

- - @endsection @push('scripts') @@ -731,118 +628,47 @@ function renderOpenIssues(container, issues) { container.innerHTML = html; } - // 빠른 스크럼 모달 관련 함수 - function openQuickScrumModal(projectId, projectName, logId) { - document.getElementById('quickScrumProjectId').value = projectId; - document.getElementById('quickScrumProjectName').textContent = projectName; - document.getElementById('quickScrumLogId').value = logId || ''; - document.getElementById('quickScrumModal').classList.remove('hidden'); - - // 폼 초기화 - document.getElementById('quickScrumAssignee').value = ''; - document.getElementById('quickScrumContent').value = ''; - document.querySelector('input[name="status"][value="todo"]').checked = true; - - // 담당자 입력창에 포커스 - setTimeout(() => { - document.getElementById('quickScrumAssignee').focus(); - }, 100); - } - - function closeQuickScrumModal() { - document.getElementById('quickScrumModal').classList.add('hidden'); - } - - async function submitQuickScrum(event) { - event.preventDefault(); - - const form = document.getElementById('quickScrumForm'); - const submitBtn = document.getElementById('quickScrumSubmitBtn'); - const projectId = document.getElementById('quickScrumProjectId').value; - const logId = document.getElementById('quickScrumLogId').value; - - const formData = { - project_id: parseInt(projectId), - log_date: new Date().toISOString().split('T')[0], - assignee_name: document.getElementById('quickScrumAssignee').value, - content: document.getElementById('quickScrumContent').value, - status: document.querySelector('input[name="status"]:checked').value, - assignee_type: 'user' - }; - - submitBtn.disabled = true; - submitBtn.textContent = '추가 중...'; - - try { - let response; - - if (logId) { - // 기존 로그에 항목 추가 - response = await fetch(`/api/admin/pm/daily-logs/${logId}/entries`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content - }, - body: JSON.stringify(formData) - }); - } else { - // 새 로그 생성과 함께 항목 추가 - response = await fetch('/api/admin/pm/daily-logs', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content - }, - body: JSON.stringify({ - project_id: formData.project_id, - log_date: formData.log_date, - entries: [{ - assignee_type: formData.assignee_type, - assignee_name: formData.assignee_name, - content: formData.content, - status: formData.status - }] - }) - }); - } - - const data = await response.json(); - - if (response.ok && data.success) { - closeQuickScrumModal(); - // 페이지 새로고침으로 데이터 반영 - window.location.reload(); - } else { - alert(data.message || '스크럼 항목 추가에 실패했습니다.'); - } - } catch (error) { - console.error('Error:', error); - alert('오류가 발생했습니다. 다시 시도해주세요.'); - } finally { - submitBtn.disabled = false; - submitBtn.textContent = '추가'; - } - } - // ESC 키로 모달 닫기 document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { - if (!document.getElementById('quickScrumModal').classList.contains('hidden')) { - closeQuickScrumModal(); - } if (!document.getElementById('editEntryModal').classList.contains('hidden')) { closeEditEntryModal(); } } }); - // 스크럼 항목 수정 모달 관련 함수 (담당자별 그룹 편집) + // 스크럼 항목 추가/수정 모달 관련 함수 (담당자별 그룹 편집) let currentEditEntries = []; // 현재 편집 중인 항목들 let entriesToDelete = []; // 삭제할 항목 ID 목록 + // 스크럼 항목 추가 모달 열기 (새 항목) + function openAddEntryModal(projectId, projectName, logId) { + currentEditEntries = []; + entriesToDelete = []; + + document.getElementById('editEntryProjectId').value = projectId; + document.getElementById('editEntryProjectName').textContent = projectName; + document.getElementById('editEntryLogId').value = logId || ''; + document.getElementById('editEntryIsAddMode').value = 'true'; + document.getElementById('editEntryAssignee').value = ''; + + // 모달 제목 변경 (추가 모드) + document.getElementById('editModalTitleText').textContent = '스크럼 항목 추가'; + const icon = document.getElementById('editModalIcon'); + icon.innerHTML = ''; + + // 빈 항목 1개로 시작 + renderEntryRows([{ id: '', content: '', status: 'todo' }]); + + document.getElementById('editEntryModal').classList.remove('hidden'); + + // 담당자 입력창에 포커스 + setTimeout(() => { + document.getElementById('editEntryAssignee').focus(); + }, 100); + } + + // 스크럼 항목 수정 모달 열기 (기존 항목) function openEditEntryModal(entriesJson, assigneeName, projectId, projectName) { const entries = JSON.parse(entriesJson); currentEditEntries = entries; @@ -850,8 +676,15 @@ function openEditEntryModal(entriesJson, assigneeName, projectId, projectName) { document.getElementById('editEntryProjectId').value = projectId; document.getElementById('editEntryProjectName').textContent = projectName; + document.getElementById('editEntryLogId').value = entries[0]?.daily_log_id || ''; + document.getElementById('editEntryIsAddMode').value = 'false'; document.getElementById('editEntryAssignee').value = assigneeName; + // 모달 제목 변경 (수정 모드) + document.getElementById('editModalTitleText').textContent = '스크럼 항목 수정'; + const icon = document.getElementById('editModalIcon'); + icon.innerHTML = ''; + // 항목 컨테이너 초기화 및 렌더링 renderEntryRows(entries); @@ -965,6 +798,9 @@ function closeEditEntryModal() { const submitBtn = document.getElementById('editEntrySubmitBtn'); const assigneeName = document.getElementById('editEntryAssignee').value; + const projectId = document.getElementById('editEntryProjectId').value; + const logId = document.getElementById('editEntryLogId').value; + const isAddMode = document.getElementById('editEntryIsAddMode').value === 'true'; const container = document.getElementById('editEntriesContainer'); const rows = container.querySelectorAll('.entry-row'); @@ -972,23 +808,27 @@ function closeEditEntryModal() { submitBtn.textContent = '저장 중...'; try { - const promises = []; + // 항목 데이터 수집 + const newEntries = []; + const updatePromises = []; - // 삭제 처리 - for (const entryId of entriesToDelete) { - promises.push( - fetch(`/api/admin/daily-logs/entries/${entryId}`, { - method: 'DELETE', - headers: { - 'Accept': 'application/json', - 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content - } - }) - ); + // 삭제 처리 (수정 모드에서만) + if (!isAddMode) { + for (const entryId of entriesToDelete) { + updatePromises.push( + fetch(`/api/admin/daily-logs/entries/${entryId}`, { + method: 'DELETE', + headers: { + 'Accept': 'application/json', + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content + } + }) + ); + } } - // 수정/생성 처리 - rows.forEach((row, index) => { + // 항목 처리 + rows.forEach((row) => { const entryId = row.dataset.entryId; const content = row.querySelector('textarea').value.trim(); const status = row.querySelector('input[type="hidden"]').value; @@ -1004,7 +844,7 @@ function closeEditEntryModal() { if (entryId) { // 기존 항목 수정 - promises.push( + updatePromises.push( fetch(`/api/admin/daily-logs/entries/${entryId}`, { method: 'PUT', headers: { @@ -1016,31 +856,76 @@ function closeEditEntryModal() { }) ); } else { - // 새 항목 - 기존 항목의 daily_log_id를 사용 - const existingEntry = currentEditEntries.find(e => e.id); - if (existingEntry && existingEntry.daily_log_id) { - promises.push( - fetch(`/api/admin/pm/daily-logs/${existingEntry.daily_log_id}/entries`, { + // 새 항목 + newEntries.push(data); + } + }); + + // 새 항목 처리 + if (newEntries.length > 0) { + if (logId) { + // 기존 로그에 항목 추가 + for (const entry of newEntries) { + updatePromises.push( + fetch(`/api/admin/daily-logs/${logId}/entries`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, - body: JSON.stringify(data) + body: JSON.stringify(entry) }) ); } - } - }); + } else if (isAddMode) { + // 새 로그 생성과 함께 항목 추가 + const response = await fetch('/api/admin/daily-logs', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content + }, + body: JSON.stringify({ + project_id: parseInt(projectId), + log_date: new Date().toISOString().split('T')[0], + entries: newEntries + }) + }); - await Promise.all(promises); + const data = await response.json(); + if (!response.ok || !data.success) { + throw new Error(data.message || '스크럼 항목 추가에 실패했습니다.'); + } + } else { + // 수정 모드에서 새 항목 추가 - 기존 항목의 daily_log_id 사용 + const existingEntry = currentEditEntries.find(e => e.id); + if (existingEntry && existingEntry.daily_log_id) { + for (const entry of newEntries) { + updatePromises.push( + fetch(`/api/admin/daily-logs/${existingEntry.daily_log_id}/entries`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content + }, + body: JSON.stringify(entry) + }) + ); + } + } + } + } + + await Promise.all(updatePromises); closeEditEntryModal(); window.location.reload(); } catch (error) { console.error('Error:', error); - alert('오류가 발생했습니다. 다시 시도해주세요.'); + alert(error.message || '오류가 발생했습니다. 다시 시도해주세요.'); } finally { submitBtn.disabled = false; submitBtn.textContent = '저장'; diff --git a/resources/views/project-management/projects/index.blade.php b/resources/views/project-management/projects/index.blade.php index d1d83490..f4efd3f5 100644 --- a/resources/views/project-management/projects/index.blade.php +++ b/resources/views/project-management/projects/index.blade.php @@ -6,8 +6,11 @@
- - ← 대시보드 + + + + + 대시보드

diff --git a/resources/views/project-management/projects/show.blade.php b/resources/views/project-management/projects/show.blade.php index 47df58c9..e4cb05bc 100644 --- a/resources/views/project-management/projects/show.blade.php +++ b/resources/views/project-management/projects/show.blade.php @@ -16,12 +16,12 @@ @endif

-
- + diff --git a/resources/views/role-permissions/partials/permission-matrix.blade.php b/resources/views/role-permissions/partials/permission-matrix.blade.php index f583f11d..bf576594 100644 --- a/resources/views/role-permissions/partials/permission-matrix.blade.php +++ b/resources/views/role-permissions/partials/permission-matrix.blade.php @@ -2,17 +2,17 @@
ID권한명테넌트가드할당된 역할할당된 부서생성일수정일액션ID권한명테넌트가드할당된 역할할당된 부서생성일수정일액션
- - - - - - - - - - - + + + + + + + + + + + @@ -24,11 +24,11 @@ data-menu-id="{{ $menu->id }}" data-parent-id="{{ $menu->parent_id ?? '' }}" data-depth="{{ $menu->depth ?? 0 }}"> - - - - @foreach($permissionTypes as $type) -
순번메뉴명URL순서조회생성수정삭제승인내보내기관리순번메뉴명URL순서조회생성수정삭제승인내보내기관리
+ {{ $index + 1 }} -
+
+
{{-- 트리 구조 표시 --}} @if(($menu->depth ?? 0) > 0) └─ @@ -59,20 +59,20 @@ class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outl
+ {{ $menu->url }} + {{ $menu->sort_order }} + id][$type]) && $permissions[$menu->id][$type] ? 'checked' : '' }} - class="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" + class="h-5 w-5 rounded border-gray-300 text-primary focus:ring-primary cursor-pointer" hx-post="/api/admin/role-permissions/toggle" hx-target="#permission-matrix" hx-include="[name='role_id'],[name='guard_name']" diff --git a/resources/views/roles/partials/table.blade.php b/resources/views/roles/partials/table.blade.php index 372f5abe..974be888 100644 --- a/resources/views/roles/partials/table.blade.php +++ b/resources/views/roles/partials/table.blade.php @@ -7,16 +7,16 @@ - + @if($isAllTenants) - + @endif - - - - - - + + + + + + diff --git a/resources/views/user-permissions/partials/permission-matrix.blade.php b/resources/views/user-permissions/partials/permission-matrix.blade.php index 14a331f7..ea582d6d 100644 --- a/resources/views/user-permissions/partials/permission-matrix.blade.php +++ b/resources/views/user-permissions/partials/permission-matrix.blade.php @@ -2,17 +2,17 @@
IDID테넌트테넌트역할 이름Guard설명권한 수생성일액션역할 이름Guard설명권한 수생성일액션
- - - - - - - - - - - + + + + + + + + + + + @@ -24,11 +24,11 @@ data-menu-id="{{ $menu->id }}" data-parent-id="{{ $menu->parent_id ?? '' }}" data-depth="{{ $menu->depth ?? 0 }}"> - - - - @foreach($permissionTypes as $type) @@ -103,10 +103,10 @@ class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outl $title = '미설정 (클릭: 개인 허용으로 변경)'; } @endphp -
순번메뉴명URL순서조회생성수정삭제승인내보내기관리순번메뉴명URL순서조회생성수정삭제승인내보내기관리
+ {{ $index + 1 }} -
+
+
{{-- 트리 구조 표시 --}} @if(($menu->depth ?? 0) > 0) └─ @@ -59,12 +59,12 @@ class="toggle-btn flex items-center text-blue-500 hover:text-blue-700 focus:outl
+ {{ $menu->url }} + {{ $menu->sort_order }} +