diff --git a/app/Http/Controllers/Api/Admin/UserController.php b/app/Http/Controllers/Api/Admin/UserController.php index 2e3ee4a3..469577e9 100644 --- a/app/Http/Controllers/Api/Admin/UserController.php +++ b/app/Http/Controllers/Api/Admin/UserController.php @@ -140,4 +140,56 @@ public function destroy(int $id): JsonResponse ], 500); } } + + /** + * 사용자 복원 + */ + public function restore(Request $request, int $id): JsonResponse + { + $this->userService->restoreUser($id); + + // HTMX 요청 시 테이블 새로고침 트리거 + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => true, + 'message' => '사용자가 복원되었습니다.', + 'action' => 'refresh', + ]); + } + + return response()->json([ + 'success' => true, + 'message' => '사용자가 복원되었습니다.', + ]); + } + + /** + * 사용자 영구 삭제 (슈퍼관리자 전용) + */ + public function forceDestroy(Request $request, int $id): JsonResponse + { + // 슈퍼관리자 권한 체크 + if (!auth()->user()?->is_super_admin) { + return response()->json([ + 'success' => false, + 'message' => '권한이 없습니다.', + ], 403); + } + + $this->userService->forceDeleteUser($id); + + // HTMX 요청 시 테이블 새로고침 트리거 + if ($request->header('HX-Request')) { + return response()->json([ + 'success' => true, + 'message' => '사용자가 영구 삭제되었습니다.', + 'action' => 'refresh', + ]); + } + + return response()->json([ + 'success' => true, + 'message' => '사용자가 영구 삭제되었습니다.', + ]); + } } \ No newline at end of file diff --git a/app/Services/UserService.php b/app/Services/UserService.php index 894b54a6..89e1fa35 100644 --- a/app/Services/UserService.php +++ b/app/Services/UserService.php @@ -14,7 +14,7 @@ class UserService public function getUsers(array $filters = [], int $perPage = 15): LengthAwarePaginator { $tenantId = session('selected_tenant_id'); - $query = User::query(); + $query = User::query()->withTrashed(); // 테넌트 필터링 (user_tenants pivot을 통한 필터링) if ($tenantId) { @@ -23,6 +23,15 @@ public function getUsers(array $filters = [], int $perPage = 15): LengthAwarePag }); } + // Soft Delete 필터 + if (isset($filters['trashed'])) { + if ($filters['trashed'] === 'only') { + $query->onlyTrashed(); + } elseif ($filters['trashed'] === 'with') { + $query->withTrashed(); + } + } + // 검색 필터 if (! empty($filters['search'])) { $search = $filters['search']; @@ -125,6 +134,28 @@ public function deleteUser(int $id): bool return $user->delete(); } + /** + * 사용자 복원 + */ + public function restoreUser(int $id): bool + { + $user = User::onlyTrashed()->findOrFail($id); + return $user->restore(); + } + + /** + * 사용자 영구 삭제 (슈퍼관리자 전용) + */ + public function forceDeleteUser(int $id): bool + { + $user = User::withTrashed()->findOrFail($id); + + // 관련 데이터 먼저 삭제 + $user->tenants()->detach(); // user_tenants 관계 삭제 + + return $user->forceDelete(); + } + /** * 활성 사용자 목록 조회 (드롭다운용) */ diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php index 5fe5333f..68167fc8 100644 --- a/resources/views/users/index.blade.php +++ b/resources/views/users/index.blade.php @@ -88,5 +88,35 @@ class="bg-white rounded-lg shadow-sm overflow-hidden"> }); } }; + + // 복원 확인 + window.confirmRestore = function(id, name) { + if (confirm(`"${name}" 사용자를 복원하시겠습니까?`)) { + htmx.ajax('POST', `/api/admin/users/${id}/restore`, { + target: '#user-table', + swap: 'none', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + htmx.trigger('#user-table', 'filterSubmit'); + }); + } + }; + + // 영구삭제 확인 + window.confirmForceDelete = function(id, name) { + if (confirm(`⚠️ 경고: "${name}" 사용자를 영구 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다!`)) { + htmx.ajax('DELETE', `/api/admin/users/${id}/force`, { + target: '#user-table', + swap: 'none', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}' + } + }).then(() => { + htmx.trigger('#user-table', 'filterSubmit'); + }); + } + }; @endpush \ No newline at end of file diff --git a/resources/views/users/partials/table.blade.php b/resources/views/users/partials/table.blade.php index a302507a..8e404588 100644 --- a/resources/views/users/partials/table.blade.php +++ b/resources/views/users/partials/table.blade.php @@ -44,12 +44,27 @@ @endif