feat(mng): 사용자 역할/부서 매핑 및 부서관리 복구/영구삭제 기능 추가

사용자 관리:
- 사용자 등록/수정 시 테넌트별 역할/부서 선택 기능 추가
- Department, UserRole, DepartmentUser 모델 추가
- User 모델에 역할/부서 관계 및 헬퍼 메서드 추가
- syncRoles/syncDepartments 메서드 (forceDelete로 유니크 키 충돌 방지)
- 체크박스 UI로 다중 선택 지원

부서 관리:
- Soft Delete 필터 (정상만/전체/삭제된 항목만)
- 복구(restore) 및 영구삭제(forceDelete) 기능 추가
- Department 모델에 SoftDeletes 트레이트 추가
- 삭제된 항목 빨간 배경 + "삭제됨" 배지 표시
This commit is contained in:
2025-11-26 20:28:07 +09:00
parent f59e7051ac
commit d4534f0d3f
16 changed files with 601 additions and 59 deletions

View File

@@ -154,4 +154,58 @@ public function destroy(Request $request, int $id): JsonResponse
'message' => '부서가 삭제되었습니다.',
]);
}
/**
* 부서 복원
*/
public function restore(Request $request, int $id): JsonResponse
{
$result = $this->departmentService->restoreDepartment($id);
if (! $result) {
return response()->json([
'success' => false,
'message' => '부서 복원에 실패했습니다.',
], 400);
}
if ($request->header('HX-Request')) {
return response()->json([
'success' => true,
'message' => '부서가 복원되었습니다.',
]);
}
return response()->json([
'success' => true,
'message' => '부서가 복원되었습니다.',
]);
}
/**
* 부서 영구 삭제
*/
public function forceDelete(Request $request, int $id): JsonResponse
{
$result = $this->departmentService->forceDeleteDepartment($id);
if (! $result) {
return response()->json([
'success' => false,
'message' => '부서 영구 삭제에 실패했습니다. (하위 부서가 존재할 수 있습니다)',
], 400);
}
if ($request->header('HX-Request')) {
return response()->json([
'success' => true,
'message' => '부서가 영구 삭제되었습니다.',
]);
}
return response()->json([
'success' => true,
'message' => '부서가 영구 삭제되었습니다.',
]);
}
}

View File

@@ -2,6 +2,8 @@
namespace App\Http\Controllers;
use App\Models\Department;
use App\Models\Role;
use App\Services\UserService;
use Illuminate\Http\Request;
use Illuminate\View\View;
@@ -25,7 +27,13 @@ public function index(Request $request): View
*/
public function create(): View
{
return view('users.create');
$tenantId = session('selected_tenant_id');
// 역할/부서 목록 (테넌트별)
$roles = $tenantId ? Role::where('tenant_id', $tenantId)->orderBy('name')->get() : collect();
$departments = $tenantId ? Department::where('tenant_id', $tenantId)->where('is_active', true)->orderBy('name')->get() : collect();
return view('users.create', compact('roles', 'departments'));
}
/**
@@ -39,6 +47,16 @@ public function edit(int $id): View
abort(404, '사용자를 찾을 수 없습니다.');
}
return view('users.edit', compact('user'));
$tenantId = session('selected_tenant_id');
// 역할/부서 목록 (테넌트별)
$roles = $tenantId ? Role::where('tenant_id', $tenantId)->orderBy('name')->get() : collect();
$departments = $tenantId ? Department::where('tenant_id', $tenantId)->where('is_active', true)->orderBy('name')->get() : collect();
// 사용자의 현재 역할/부서 ID 목록
$userRoleIds = $tenantId ? $user->userRoles()->where('tenant_id', $tenantId)->pluck('role_id')->toArray() : [];
$userDepartmentIds = $tenantId ? $user->departmentUsers()->where('tenant_id', $tenantId)->pluck('department_id')->toArray() : [];
return view('users.edit', compact('user', 'roles', 'departments', 'userRoleIds', 'userDepartmentIds'));
}
}

View File

@@ -30,6 +30,10 @@ public function rules(): array
'role' => 'nullable|string|max:50',
'is_active' => 'nullable|boolean',
'is_super_admin' => 'nullable|boolean',
'role_ids' => 'nullable|array',
'role_ids.*' => 'integer|exists:roles,id',
'department_ids' => 'nullable|array',
'department_ids.*' => 'integer|exists:departments,id',
];
}

View File

@@ -43,6 +43,10 @@ public function rules(): array
'role' => 'nullable|string|max:50',
'is_active' => 'nullable|boolean',
'is_super_admin' => 'nullable|boolean',
'role_ids' => 'nullable|array',
'role_ids.*' => 'integer|exists:roles,id',
'department_ids' => 'nullable|array',
'department_ids.*' => 'integer|exists:departments,id',
];
}

74
app/Models/Department.php Normal file
View File

@@ -0,0 +1,74 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
class Department extends Model
{
use SoftDeletes;
protected $table = 'departments';
protected $fillable = [
'tenant_id',
'parent_id',
'code',
'name',
'description',
'is_active',
'sort_order',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'tenant_id' => 'integer',
'parent_id' => 'integer',
'is_active' => 'boolean',
'sort_order' => 'integer',
];
protected $hidden = [
'deleted_by',
'deleted_at',
];
/**
* 상위 부서
*/
public function parent(): BelongsTo
{
return $this->belongsTo(self::class, 'parent_id');
}
/**
* 하위 부서
*/
public function children(): HasMany
{
return $this->hasMany(self::class, 'parent_id');
}
/**
* 부서-사용자 매핑 (DepartmentUser pivot)
*/
public function departmentUsers(): HasMany
{
return $this->hasMany(DepartmentUser::class, 'department_id');
}
/**
* 부서에 속한 사용자들 (belongsToMany)
*/
public function users()
{
return $this->belongsToMany(User::class, 'department_user')
->withTimestamps()
->withPivot(['tenant_id', 'is_primary', 'joined_at', 'left_at']);
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class DepartmentUser extends Model
{
use SoftDeletes;
protected $table = 'department_user';
protected $fillable = [
'tenant_id',
'department_id',
'user_id',
'is_primary',
'joined_at',
'left_at',
'created_by',
'updated_by',
'deleted_by',
];
protected $casts = [
'tenant_id' => 'integer',
'department_id' => 'integer',
'user_id' => 'integer',
'is_primary' => 'boolean',
'joined_at' => 'datetime',
'left_at' => 'datetime',
];
/**
* 부서
*/
public function department(): BelongsTo
{
return $this->belongsTo(Department::class, 'department_id');
}
/**
* 사용자
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
}

View File

@@ -9,12 +9,13 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Permission\Traits\HasPermissions;
use Spatie\Permission\Traits\HasRoles;
class Department extends Model
{
use HasPermissions, HasRoles, ModelTrait; // 부서도 권한/역할을 가짐
use HasPermissions, HasRoles, ModelTrait, SoftDeletes; // 부서도 권한/역할을 가짐
protected $table = 'departments';

View File

@@ -4,6 +4,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@@ -86,4 +87,44 @@ public function currentTenant()
return $this->tenants()->find($tenantId);
}
/**
* 관계: 사용자-역할 (user_roles 테이블, 테넌트별)
*/
public function userRoles(): HasMany
{
return $this->hasMany(UserRole::class);
}
/**
* 관계: 사용자-부서 (department_user 테이블, 테넌트별)
*/
public function departmentUsers(): HasMany
{
return $this->hasMany(DepartmentUser::class);
}
/**
* 특정 테넌트의 역할 목록 조회
*/
public function getRolesForTenant(int $tenantId)
{
return $this->userRoles()
->where('tenant_id', $tenantId)
->with('role')
->get()
->pluck('role');
}
/**
* 특정 테넌트의 부서 목록 조회
*/
public function getDepartmentsForTenant(int $tenantId)
{
return $this->departmentUsers()
->where('tenant_id', $tenantId)
->with('department')
->get()
->pluck('department');
}
}

44
app/Models/UserRole.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class UserRole extends Model
{
use SoftDeletes;
protected $table = 'user_roles';
protected $fillable = [
'user_id',
'tenant_id',
'role_id',
'assigned_at',
];
protected $casts = [
'user_id' => 'integer',
'tenant_id' => 'integer',
'role_id' => 'integer',
'assigned_at' => 'datetime',
];
/**
* 사용자
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* 역할
*/
public function role(): BelongsTo
{
return $this->belongsTo(Role::class);
}
}

View File

@@ -15,13 +15,22 @@ public function getDepartments(array $filters = [], int $perPage = 15): LengthAw
{
$tenantId = session('selected_tenant_id');
$query = Department::query()->with('parent');
$query = Department::query()->withTrashed()->with('parent');
// Tenant 필터링 (선택된 경우에만)
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
// 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'];
@@ -134,6 +143,55 @@ public function deleteDepartment(int $id): bool
return $department->delete();
}
/**
* 부서 복원
*/
public function restoreDepartment(int $id): bool
{
$tenantId = session('selected_tenant_id');
$query = Department::onlyTrashed();
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
$department = $query->find($id);
if (! $department) {
return false;
}
return $department->restore();
}
/**
* 부서 영구 삭제
*/
public function forceDeleteDepartment(int $id): bool
{
$tenantId = session('selected_tenant_id');
$query = Department::withTrashed();
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
$department = $query->find($id);
if (! $department) {
return false;
}
// 하위 부서가 있는 경우 삭제 불가
if ($department->children()->count() > 0) {
return false;
}
return $department->forceDelete();
}
/**
* 부서 코드 중복 체크
*/

View File

@@ -2,7 +2,9 @@
namespace App\Services;
use App\Models\DepartmentUser;
use App\Models\User;
use App\Models\UserRole;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Hash;
@@ -87,6 +89,13 @@ public function createUser(array $data): User
'is_default' => true,
'joined_at' => now(),
]);
// 역할/부서 동기화
$roleIds = $data['role_ids'] ?? [];
$departmentIds = $data['department_ids'] ?? [];
$this->syncRoles($user, $tenantId, $roleIds);
$this->syncDepartments($user, $tenantId, $departmentIds);
}
return $user;
@@ -102,6 +111,8 @@ public function updateUser(int $id, array $data): bool
return false;
}
$tenantId = session('selected_tenant_id');
// 비밀번호가 입력된 경우만 업데이트
if (! empty($data['password'])) {
$data['password'] = Hash::make($data['password']);
@@ -115,9 +126,67 @@ public function updateUser(int $id, array $data): bool
// 수정자 정보
$data['updated_by'] = auth()->id();
// 역할/부서 동기화 (테넌트가 선택된 경우)
if ($tenantId) {
$roleIds = $data['role_ids'] ?? [];
$departmentIds = $data['department_ids'] ?? [];
$this->syncRoles($user, $tenantId, $roleIds);
$this->syncDepartments($user, $tenantId, $departmentIds);
}
// role_ids, department_ids는 User 모델의 fillable이 아니므로 제거
unset($data['role_ids'], $data['department_ids']);
return $user->update($data);
}
/**
* 사용자 역할 동기화 (특정 테넌트)
*/
public function syncRoles(User $user, int $tenantId, array $roleIds): void
{
// 기존 역할 삭제 (해당 테넌트만) - forceDelete로 실제 삭제
UserRole::withTrashed()
->where('user_id', $user->id)
->where('tenant_id', $tenantId)
->forceDelete();
// 새 역할 추가
foreach ($roleIds as $roleId) {
UserRole::create([
'user_id' => $user->id,
'tenant_id' => $tenantId,
'role_id' => $roleId,
'assigned_at' => now(),
]);
}
}
/**
* 사용자 부서 동기화 (특정 테넌트)
*/
public function syncDepartments(User $user, int $tenantId, array $departmentIds): void
{
// 기존 부서 삭제 (해당 테넌트만) - forceDelete로 실제 삭제
DepartmentUser::withTrashed()
->where('user_id', $user->id)
->where('tenant_id', $tenantId)
->forceDelete();
// 새 부서 추가 (첫 번째를 primary로)
foreach ($departmentIds as $index => $departmentId) {
DepartmentUser::create([
'user_id' => $user->id,
'tenant_id' => $tenantId,
'department_id' => $departmentId,
'is_primary' => $index === 0,
'joined_at' => now(),
'created_by' => auth()->id(),
]);
}
}
/**
* 사용자 삭제 (Soft Delete)
*/

View File

@@ -23,7 +23,7 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
</div>
<!-- 활성 상태 필터 -->
<div class="w-48">
<div class="w-40">
<select name="is_active" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">전체 상태</option>
<option value="1">활성</option>
@@ -31,6 +31,15 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
</select>
</div>
<!-- 삭제 상태 필터 -->
<div class="w-40">
<select name="trashed" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="">정상만</option>
<option value="with">전체 (삭제 포함)</option>
<option value="only">삭제된 항목만</option>
</select>
</div>
<!-- 검색 버튼 -->
<button type="submit" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg transition">
검색
@@ -85,5 +94,35 @@ class="bg-white rounded-lg shadow-sm overflow-hidden">
});
}
};
// 복원 확인
window.confirmRestore = function(id, name) {
if (confirm(`"${name}" 부서를 복원하시겠습니까?`)) {
htmx.ajax('POST', `/api/admin/departments/${id}/restore`, {
target: '#department-table',
swap: 'none',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
}).then(() => {
htmx.trigger('#department-table', 'filterSubmit');
});
}
};
// 영구 삭제 확인
window.confirmForceDelete = function(id, name) {
if (confirm(`"${name}" 부서를 영구 삭제하시겠습니까?\n\n⚠ 이 작업은 되돌릴 수 없습니다!`)) {
htmx.ajax('DELETE', `/api/admin/departments/${id}/force`, {
target: '#department-table',
swap: 'none',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
}).then(() => {
htmx.trigger('#department-table', 'filterSubmit');
});
}
};
</script>
@endpush

View File

@@ -11,21 +11,25 @@
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@forelse($departments as $department)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<tr class="{{ $department->trashed() ? 'bg-red-50' : 'hover:bg-gray-50' }}">
<td class="px-6 py-4 whitespace-nowrap text-sm {{ $department->trashed() ? 'text-gray-400' : 'text-gray-900' }}">
{{ $department->code }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-medium text-gray-900">{{ $department->name }}</div>
<div class="text-sm font-medium {{ $department->trashed() ? 'text-gray-400' : 'text-gray-900' }}">{{ $department->name }}</div>
@if($department->description)
<div class="text-sm text-gray-500">{{ Str::limit($department->description, 50) }}</div>
<div class="text-sm {{ $department->trashed() ? 'text-gray-300' : 'text-gray-500' }}">{{ Str::limit($department->description, 50) }}</div>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm {{ $department->trashed() ? 'text-gray-400' : 'text-gray-500' }}">
{{ $department->parent?->name ?? '-' }}
</td>
<td class="px-6 py-4 whitespace-nowrap">
@if($department->is_active)
@if($department->trashed())
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
삭제됨
</span>
@elseif($department->is_active)
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
활성
</span>
@@ -35,16 +39,25 @@
</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<td class="px-6 py-4 whitespace-nowrap text-sm {{ $department->trashed() ? 'text-gray-400' : 'text-gray-500' }}">
{{ $department->sort_order }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="{{ route('departments.edit', $department->id) }}" class="text-blue-600 hover:text-blue-900 mr-3">
수정
</a>
<button onclick="confirmDelete({{ $department->id }}, '{{ $department->name }}')" class="text-red-600 hover:text-red-900">
삭제
</button>
@if($department->trashed())
<button onclick="confirmRestore({{ $department->id }}, '{{ $department->name }}')" class="text-green-600 hover:text-green-900 mr-3">
복원
</button>
<button onclick="confirmForceDelete({{ $department->id }}, '{{ $department->name }}')" class="text-red-600 hover:text-red-900">
영구삭제
</button>
@else
<a href="{{ route('departments.edit', $department->id) }}" class="text-blue-600 hover:text-blue-900 mr-3">
수정
</a>
<button onclick="confirmDelete({{ $department->id }}, '{{ $department->name }}')" class="text-red-600 hover:text-red-900">
삭제
</button>
@endif
</td>
</tr>
@empty

View File

@@ -90,28 +90,59 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
</div>
</div>
<!-- 권한 설정 -->
<!-- 역할 설정 -->
<div class="mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">권한 설정</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">역할</label>
<input type="text" name="role" maxlength="50"
placeholder="예: 관리자, 사용자"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">역할 설정</h2>
@if($roles->isNotEmpty())
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
@foreach($roles as $role)
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer">
<input type="checkbox" name="role_ids[]" value="{{ $role->id }}"
{{ in_array($role->id, old('role_ids', [])) ? 'checked' : '' }}
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">{{ $role->name }}</span>
</label>
@endforeach
</div>
<div class="flex items-center gap-4">
<label class="flex items-center">
<input type="checkbox" name="is_active" value="1" checked
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">활성 상태</span>
</label>
<label class="flex items-center">
<input type="checkbox" name="is_super_admin" value="1"
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">슈퍼 관리자</span>
</label>
@else
<p class="text-sm text-gray-500">선택 가능한 역할이 없습니다.</p>
@endif
</div>
<!-- 부서 설정 -->
<div class="mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">부서 설정</h2>
@if($departments->isNotEmpty())
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
@foreach($departments as $department)
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer">
<input type="checkbox" name="department_ids[]" value="{{ $department->id }}"
{{ in_array($department->id, old('department_ids', [])) ? 'checked' : '' }}
class="h-4 w-4 text-green-600 rounded focus:ring-2 focus:ring-green-500">
<span class="ml-2 text-sm text-gray-700">{{ $department->name }}</span>
</label>
@endforeach
</div>
<p class="text-xs text-gray-500 mt-2"> 번째로 선택한 부서가 부서로 설정됩니다.</p>
@else
<p class="text-sm text-gray-500">선택 가능한 부서가 없습니다.</p>
@endif
</div>
<!-- 계정 상태 -->
<div class="mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">계정 상태</h2>
<div class="flex items-center gap-6">
<label class="flex items-center">
<input type="checkbox" name="is_active" value="1" checked
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">활성 상태</span>
</label>
<label class="flex items-center">
<input type="checkbox" name="is_super_admin" value="1"
class="h-4 w-4 text-red-600 rounded focus:ring-2 focus:ring-red-500">
<span class="ml-2 text-sm text-gray-700">슈퍼 관리자</span>
</label>
</div>
</div>

View File

@@ -94,31 +94,61 @@ class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none foc
</div>
</div>
<!-- 권한 설정 -->
<!-- 역할 설정 -->
<div class="mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">권한 설정</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">역할</label>
<input type="text" name="role" maxlength="50"
value="{{ old('role', $user->role) }}"
placeholder="예: 관리자, 사용자"
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">역할 설정</h2>
@if($roles->isNotEmpty())
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
@foreach($roles as $role)
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer">
<input type="checkbox" name="role_ids[]" value="{{ $role->id }}"
{{ in_array($role->id, old('role_ids', $userRoleIds)) ? 'checked' : '' }}
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">{{ $role->name }}</span>
</label>
@endforeach
</div>
<div class="flex items-center gap-4">
<label class="flex items-center">
<input type="checkbox" name="is_active" value="1"
{{ old('is_active', $user->is_active) ? 'checked' : '' }}
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">활성 상태</span>
</label>
<label class="flex items-center">
<input type="checkbox" name="is_super_admin" value="1"
{{ old('is_super_admin', $user->is_super_admin) ? 'checked' : '' }}
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">슈퍼 관리자</span>
</label>
@else
<p class="text-sm text-gray-500">선택 가능한 역할이 없습니다.</p>
@endif
</div>
<!-- 부서 설정 -->
<div class="mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">부서 설정</h2>
@if($departments->isNotEmpty())
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
@foreach($departments as $department)
<label class="flex items-center p-3 border border-gray-200 rounded-lg hover:bg-gray-50 cursor-pointer">
<input type="checkbox" name="department_ids[]" value="{{ $department->id }}"
{{ in_array($department->id, old('department_ids', $userDepartmentIds)) ? 'checked' : '' }}
class="h-4 w-4 text-green-600 rounded focus:ring-2 focus:ring-green-500">
<span class="ml-2 text-sm text-gray-700">{{ $department->name }}</span>
</label>
@endforeach
</div>
<p class="text-xs text-gray-500 mt-2"> 번째로 선택한 부서가 부서로 설정됩니다.</p>
@else
<p class="text-sm text-gray-500">선택 가능한 부서가 없습니다.</p>
@endif
</div>
<!-- 계정 상태 -->
<div class="mb-8">
<h2 class="text-lg font-semibold text-gray-800 mb-4 pb-2 border-b">계정 상태</h2>
<div class="flex items-center gap-6">
<label class="flex items-center">
<input type="checkbox" name="is_active" value="1"
{{ old('is_active', $user->is_active) ? 'checked' : '' }}
class="h-4 w-4 text-blue-600 rounded focus:ring-2 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">활성 상태</span>
</label>
<label class="flex items-center">
<input type="checkbox" name="is_super_admin" value="1"
{{ old('is_super_admin', $user->is_super_admin) ? 'checked' : '' }}
class="h-4 w-4 text-red-600 rounded focus:ring-2 focus:ring-red-500">
<span class="ml-2 text-sm text-gray-700">슈퍼 관리자</span>
</label>
</div>
</div>

View File

@@ -53,6 +53,8 @@
Route::get('/{id}', [DepartmentController::class, 'show'])->name('show');
Route::put('/{id}', [DepartmentController::class, 'update'])->name('update');
Route::delete('/{id}', [DepartmentController::class, 'destroy'])->name('destroy');
Route::post('/{id}/restore', [DepartmentController::class, 'restore'])->name('restore');
Route::delete('/{id}/force', [DepartmentController::class, 'forceDelete'])->name('forceDelete');
});
// 사용자 관리 API
@@ -113,4 +115,13 @@
Route::post('/deny-all', [\App\Http\Controllers\Api\Admin\DepartmentPermissionController::class, 'denyAll'])->name('denyAll');
Route::post('/reset', [\App\Http\Controllers\Api\Admin\DepartmentPermissionController::class, 'reset'])->name('reset');
});
// 개인 권한 관리 API
Route::prefix('user-permissions')->name('user-permissions.')->group(function () {
Route::get('/matrix', [\App\Http\Controllers\Api\Admin\UserPermissionController::class, 'getMatrix'])->name('matrix');
Route::post('/toggle', [\App\Http\Controllers\Api\Admin\UserPermissionController::class, 'toggle'])->name('toggle');
Route::post('/allow-all', [\App\Http\Controllers\Api\Admin\UserPermissionController::class, 'allowAll'])->name('allowAll');
Route::post('/deny-all', [\App\Http\Controllers\Api\Admin\UserPermissionController::class, 'denyAll'])->name('denyAll');
Route::post('/reset', [\App\Http\Controllers\Api\Admin\UserPermissionController::class, 'reset'])->name('reset');
});
});