Files
KintoneAppBuilder/frontend/src/pages/RoleManagement.vue
2024-12-23 14:29:49 +08:00

254 lines
8.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="work" label="ロール管理" />
</q-breadcrumbs>
</div>
<div class="row" style="min-height: 80vh;">
<div class="col-auto">
<div class="q-pa-md" style="width: 250px">
<q-list bordered separator class="rounded-borders">
<q-item active v-if="allLoading" active-class="menu-active">
<q-item-section class="text-weight-bold"> 読み込み中... </q-item-section>
</q-item>
<q-item v-else v-for="item in roles" :key="item.id" clickable v-ripple :active="selected.id === item.id" @click="roleClicked(item)" active-class="menu-active">
<q-item-section class="text-weight-bold"> {{ item.label }} </q-item-section>
</q-item>
</q-list>
</div>
</div>
<div class="col">
<q-table title="ユーザーリスト" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading"
:pagination="pagination" >
<template v-slot:top>
<q-btn color="primary" :disable="loading" label="追加" @click="showAddRoleDialog" />
<q-space />
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:body-cell-status="props">
<q-td :props="props">
<div class="row">
<div v-if="props.row.isActive">
<q-chip square color="positive" text-color="white" icon="done" label="使用可能" size="sm" />
</div>
<div v-else>
<q-chip square color="negative" text-color="white" icon="block" label="使用不可" size="sm" />
</div>
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
label="システム管理者" size="sm" />
</div>
</q-td>
</template>
<template v-slot:header-cell-status="p">
<q-th :props="p">
<div class="row items-center">
<label class="q-mr-md">{{ p.col.label }}</label>
<q-select v-model="statusFilter" :options="options" @update:model-value="updateStatusFilter" borderless
dense options-dense style="font-size: 12px; padding-top: 1px;" />
</div>
</q-th>
</template>
<template v-slot:body-cell-actions="p">
<q-td auto-width :props="p">
<table-action-menu :row="p.row" minWidth="180px" max-width="200px" :actions="actionList" />
</q-td>
</template>
</q-table>
</div>
</div>
<show-dialog v-model:visible="showSelectUser" name="ユーザー選択" @close="closeSelectUserDialog" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="dgFilter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<user-select-box ref="userDialog" name="ユーザー" type="single" :filter="dgFilter" :filterInitRowsFunc="filterInitRows" />
</show-dialog>
<q-dialog v-model="deleteDialog" persistent>
<q-card>
<q-card-section class="row items-center">
<q-icon name="warning" color="warning" size="2em" />
<div class="q-ml-sm text-weight-bold">ロールメンバーを削除</div>
</q-card-section>
<q-card-section class="q-py-none">
<!-- <span class="q-ml-sm">この役割を与えられたユーザーはメンバー役に再配置されます</span> -->
<div class="q-mx-sm">ユーザー{{targetRow.email}}{{selected.label}}の役割から</div>
<div class="q-mx-sm">本当に外しますか</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="OK" color="primary" :loading="deleteUserRoleLoading" @click="deleteUserRole" />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { IUser } from 'src/types/UserTypes';
import ShowDialog from 'src/components/ShowDialog.vue';
import UserSelectBox from 'src/components/dialog/UserSelectBox.vue';
import TableActionMenu from 'components/TableActionMenu.vue';
import { api } from 'boot/axios';
const columns = [
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
{ name: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true },
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
{ name: 'status', label: '状況', field: 'status', align: 'left' },
{ name: 'actions', label: '操作', field: 'actions' }
];
const roles = ref([])
const selected = ref();
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
const allLoading = ref(true);
const loading = ref(false);
const filter = ref('');
const statusFilter = ref('全データ');
const dgFilter = ref('');
const allRows = ref([]);
const targetRow = ref<IUser>();
const userDialog = ref();
const showSelectUser=ref(false);
const isAdding = ref(false);
const deleteDialog = ref(false);
const deleteUserRoleLoading = ref(false);
const actionList = [
// { label: '移動', icon: 'account_tree', action: toEditFlowPage },
{ separator: true },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
];
const options = ['全データ', 'システム管理者のみ', '使用可能', '使用不可']
const roleClicked = async (role) => {
selected.value = role;
}
const rows = computed(() => allRows.value.filter((item) => item.roles?.includes(selected.value.id) ));
const rowIds = computed(() => new Set(rows.value?.map((item) => item.id)));
const getUsers = async (filter = () => true) => {
loading.value = true;
allRows.value = []
const result = await api.get(`api/v1/users`);
allRows.value = result.data.data.map((item) => {
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active, roles: item.roles.map(role => role.id) }
}).filter(filter);
loading.value = false;
}
const getRoles = async () => {
const result = await api.get(`api/v1/roles`);
roles.value = result.data.data.map((item) => {
return { id: item.id, label: item.description }
})
selected.value = roles.value[0];
}
const updateStatusFilter = (status) => {
switch (status) {
case 'システム管理者のみ':
getUsers((row) => row.isSuperuser)
break;
case '使用可能':
getUsers((row) => row.isActive)
break;
case '使用不可':
getUsers((row) => !row.isActive)
break;
default:
getUsers()
break;
}
}
onMounted(async () => {
allLoading.value = true;
await Promise.all([
getRoles(),
getUsers()
]);
allLoading.value = false;
});
const filterInitRows = (row: {id: string}) => {
return !rowIds.value.has(row.id);
}
const showAddRoleDialog = () => {
showSelectUser.value = true;
isAdding.value = false;
dgFilter.value = ''
}
const closeSelectUserDialog = async (val: 'OK'|'Cancel') => {
showSelectUser.value = true;
if (val == 'OK' && userDialog.value.selected[0]) {
isAdding.value = true;
const user = userDialog.value.selected[0];
const result = await api.post(`api/v1/userrole`, {
'userid': user.id,
'roleids': user.roles.concat(selected.value.id)
});
await getUsers();
}
showSelectUser.value = false;
isAdding.value = false;
}
function removeRow(user:IUser) {
targetRow.value = user;
deleteDialog.value = true;
}
const deleteUserRole = async () => {
if (targetRow.value?.id) {
deleteUserRoleLoading.value = true;
// TODO 返回不需要重新读
const result = await api.post(`api/v1/userrole`, {
'userid': targetRow.value.id,
'roleids': targetRow.value.roles.filter(e => e !== selected.value.id)
});
await getUsers();
deleteUserRoleLoading.value = false;
deleteDialog.value = false;
}
}
</script>
<style lang="scss" scoped>
.menu-active {
color: white;
background: var(--q-primary)
}
</style>