Files
weeklyreport/frontend/components/common/RoleManageModal.vue

232 lines
7.4 KiB
Vue

<template>
<div class="modal fade show d-block" tabindex="-1" style="background: rgba(0,0,0,0.5)">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title">
<i class="bi bi-shield-lock me-2"></i>권한관리
</h5>
<button type="button" class="btn-close btn-close-white" @click="$emit('close')"></button>
</div>
<div class="modal-body">
<!-- 상단 버튼 -->
<div class="d-flex justify-content-between align-items-center mb-3">
<span>권한 목록 <strong>{{ roles.length }}</strong></span>
<div>
<button class="btn btn-primary btn-sm me-2" @click="showAddForm = true" v-if="!showAddForm">
<i class="bi bi-plus-lg me-1"></i>신규
</button>
<button
class="btn btn-outline-danger btn-sm"
@click="deleteSelected"
:disabled="selectedIds.length === 0"
>
<i class="bi bi-trash me-1"></i>선택 삭제
</button>
</div>
</div>
<!-- 신규 추가 -->
<div v-if="showAddForm" class="card mb-3 border-primary">
<div class="card-body">
<div class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label small">권한코드 *</label>
<input type="text" class="form-control form-control-sm" v-model="newRole.roleCode" placeholder="ROLE_XXX" />
</div>
<div class="col-md-4">
<label class="form-label small">권한명 *</label>
<input type="text" class="form-control form-control-sm" v-model="newRole.roleName" placeholder="권한명" />
</div>
<div class="col-md-4">
<button class="btn btn-primary btn-sm me-1" @click="createRole" :disabled="isCreating">
<span v-if="isCreating" class="spinner-border spinner-border-sm me-1"></span>
저장
</button>
<button class="btn btn-secondary btn-sm" @click="cancelAdd">취소</button>
</div>
</div>
</div>
</div>
<!-- 권한 목록 테이블 -->
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover mb-0">
<thead class="table-light">
<tr>
<th style="width: 40px" class="text-center">
<input type="checkbox" class="form-check-input" v-model="selectAll" @change="toggleSelectAll" />
</th>
<th style="width: 50px" class="text-center">No</th>
<th style="width: 180px">권한코드</th>
<th>권한명</th>
<th style="width: 80px" class="text-center">사용자 </th>
</tr>
</thead>
<tbody>
<tr v-if="isLoading">
<td colspan="5" class="text-center py-4">
<span class="spinner-border spinner-border-sm me-2"></span>로딩 ...
</td>
</tr>
<tr v-else v-for="(role, idx) in roles" :key="role.role_id">
<td class="text-center">
<input
type="checkbox"
class="form-check-input"
:value="role.role_id"
v-model="selectedIds"
:disabled="isProtectedRole(role.role_code)"
/>
</td>
<td class="text-center">{{ idx + 1 }}</td>
<td>
<input
type="text"
class="form-control form-control-sm border-0 bg-transparent"
v-model="role.role_code"
:disabled="isProtectedRole(role.role_code)"
@blur="updateRole(role)"
/>
</td>
<td>
<input
type="text"
class="form-control form-control-sm border-0 bg-transparent"
v-model="role.role_name"
@blur="updateRole(role)"
/>
</td>
<td class="text-center">{{ role.user_count }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="$emit('close')">닫기</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['close', 'updated'])
const isLoading = ref(true)
const isCreating = ref(false)
const roles = ref<any[]>([])
const selectedIds = ref<number[]>([])
const selectAll = ref(false)
const showAddForm = ref(false)
const newRole = ref({
roleCode: '',
roleName: ''
})
const protectedRoles = ['ROLE_ADMIN', 'ROLE_MANAGER', 'ROLE_USER']
function isProtectedRole(code: string): boolean {
return protectedRoles.includes(code)
}
onMounted(() => {
loadRoles()
})
async function loadRoles() {
try {
isLoading.value = true
const response = await $fetch<any>('/api/admin/role/list')
roles.value = response.roles || []
} catch (e) {
console.error(e)
alert('권한 목록을 불러오는데 실패했습니다.')
} finally {
isLoading.value = false
}
}
function toggleSelectAll() {
if (selectAll.value) {
selectedIds.value = roles.value
.filter(r => !isProtectedRole(r.role_code))
.map(r => r.role_id)
} else {
selectedIds.value = []
}
}
function cancelAdd() {
showAddForm.value = false
newRole.value = { roleCode: '', roleName: '' }
}
async function createRole() {
if (!newRole.value.roleCode || !newRole.value.roleName) {
alert('권한코드와 권한명은 필수입니다.')
return
}
try {
isCreating.value = true
await $fetch('/api/admin/role/create', {
method: 'POST',
body: newRole.value
})
cancelAdd()
await loadRoles()
emit('updated')
} catch (e: any) {
alert(e.data?.message || '권한 생성에 실패했습니다.')
} finally {
isCreating.value = false
}
}
async function updateRole(role: any) {
try {
await $fetch(`/api/admin/role/${role.role_id}/update`, {
method: 'PUT',
body: {
roleName: role.role_name
}
})
emit('updated')
} catch (e: any) {
alert(e.data?.message || '권한 수정에 실패했습니다.')
await loadRoles()
}
}
async function deleteSelected() {
if (selectedIds.value.length === 0) return
if (!confirm(`선택한 ${selectedIds.value.length}개의 권한을 삭제하시겠습니까?`)) return
try {
for (const id of selectedIds.value) {
await $fetch(`/api/admin/role/${id}/delete`, { method: 'DELETE' })
}
selectedIds.value = []
selectAll.value = false
await loadRoles()
emit('updated')
} catch (e: any) {
alert(e.data?.message || '권한 삭제에 실패했습니다.')
await loadRoles()
}
}
</script>
<style scoped>
.form-check-input {
cursor: pointer;
}
.form-control:disabled {
background-color: transparent;
}
</style>