Files
KintoneAppBuilder/frontend/src/components/ShareDomain/ShareDomainDialog.vue
2025-09-10 10:06:28 +09:00

277 lines
8.5 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>
<q-dialog :auto-close="false" :model-value="visible" persistent bordered>
<q-card class="dialog-content" >
<q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ dialogTitle }}</q-toolbar-title>
<q-btn flat round dense icon="close" @click="close" />
</q-toolbar>
<q-card-section class="q-mx-md " >
<q-select
v-permissions="props.actionPermissions.edit"
class="q-mt-md"
:disable="loading||!domain.domainActive"
filled
dense
v-model="canSharedUserFilter"
use-input
input-debounce="0"
:options="canSharedUserFilteredOptions"
clearable
:placeholder="canSharedUserFilter ? '' : domain.domainActive ? '権限を付与するユーザーを選択' : '接続先が無効なため、権限を付与できません'"
@filter="filterFn">
<template v-slot:selected-item="scope">
<span v-if="canSharedUserFilter">
{{ canSharedUserFilter.fullName }} {{ canSharedUserFilter.email }}
<role-label :domain="domain" :user="scope.opt"></role-label>
</span>
</template>
<template v-slot:after>
<q-btn :disable="!canSharedUserFilter" :loading="addLoading" label="付与" color="primary" @click="shareTo(canSharedUserFilter as IUserDisplayWithShareRole)" />
</template>
<template v-slot:option="scope">
<q-item v-bind="scope.itemProps">
<q-item-section avatar>{{scope.opt.id}}</q-item-section>
<q-item-section>{{scope.opt.fullName}}</q-item-section>
<q-item-section>{{scope.opt.email}}</q-item-section>
<q-item-section side>
<div style="width: 6.5em;">
<role-label :domain="domain" :user="scope.opt"></role-label>
</div>
</q-item-section>
</q-item>
</template>
</q-select>
<sharing-user-list class="q-mt-md" style="height: 330px" :users="sharedUsers" :loading="loading" :title="userListTitle">
<template v-slot:body-cell-role="{ row }">
<q-td auto-width>
<role-label :domain="domain" :user="row"></role-label>
</q-td>
</template>
<template v-slot:actions="{ row }">
<q-btn v-permissions="props.actionPermissions.edit" round title="解除" flat color="primary" :disable="isActionDisable && isActionDisable(row)" padding="xs" size="1em" :loading="row.isRemoving" icon="person_off" @click="removeShareTo(row)" />
</template>
</sharing-user-list>
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn flat label="確定" @click="checkClose" />
<q-btn flat label="キャンセル" @click="close" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
import { IUser, IUserDisplay } from '../../types/UserTypes';
import { api } from 'boot/axios';
import { useAuthStore } from 'stores/useAuthStore';
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
import RoleLabel from 'components/ShareDomain/RoleLabel.vue';
import { Dialog } from 'quasar'
const authStore = useAuthStore();
interface Props {
modelValue: boolean;
domain: IDomainOwnerDisplay;
dialogTitle: string;
userListTitle: string;
isActionDisable?: (user: IUserDisplay) => boolean;
shareApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
removeSharedApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
getSharedApi: (domain: IDomainOwnerDisplay) => Promise<any>;
actionPermissions: { 'list': string, 'edit': string };
}
interface IUserDisplayWithShareRole extends IUserDisplay {
isRemoving: boolean;
role: number;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'close'): void;
}>();
const addLoading = ref(false);
const loading = ref(true);
const visible = ref(props.modelValue);
const allUsers = ref<IUserDisplayWithShareRole[]>([]);
const sharedUsers = ref<IUserDisplayWithShareRole[]>([]);
const sharedUsersIdSet = new Set<number>();
const canSharedUsers = ref<IUserDisplayWithShareRole[]>([]);
const canSharedUserFilter = ref<IUserDisplayWithShareRole>();
const canSharedUserFilteredOptions = ref<IUserDisplayWithShareRole[]>([]);
const filterFn = (val:string, update: (cb: () => void) => void) => {
update(() => {
if (val === '') {
canSharedUserFilteredOptions.value = canSharedUsers.value;
return;
}
const needle = val.toLowerCase();
canSharedUserFilteredOptions.value = canSharedUsers.value.filter(v =>
v.email.toLowerCase().indexOf(needle) > -1 || v.fullNameSearch.toLowerCase().indexOf(needle) > -1);
})
}
watch(
() => props.modelValue,
async (newValue) => {
visible.value = newValue;
sharedUsers.value = [];
canSharedUserFilter.value = undefined
loading.value = false;
addLoading.value = false;
if (newValue) {
if (Object.keys(allUsers.value).length == 0) {
await getUsers();
}
await loadShared();
}
}
);
watch(
() => visible.value,
(newValue) => {
emit('update:modelValue', newValue);
}
);
const checkClose = () => {
if (!canSharedUserFilter.value) {
close();
return;
}
Dialog.create({
title: '注意',
message: '選択済だがまだ付与未完了のユーザーがあります。<br>必要な操作を選んでください。',
html: true,
persistent: true,
ok: {
color: 'primary',
label: '付与'
},
cancel: '直接閉じる',
}).onCancel(() => {
close();
}).onOk(() => {
shareTo(canSharedUserFilter.value as IUserDisplayWithShareRole);
});
};
const close = () => {
emit('close');
};
const shareTo = async (user: IUserDisplayWithShareRole) => {
addLoading.value = true;
loading.value = true;
await props.shareApi(user, props.domain);
await loadShared();
canSharedUserFilter.value = undefined;
loading.value = false;
addLoading.value = false;
}
const removeShareTo = async (user: IUserDisplayWithShareRole) => {
loading.value = true;
user.isRemoving = true;
await props.removeSharedApi(user, props.domain);
if (isCurrentDomain()) {
await authStore.loadCurrentDomain();
}
await loadShared();
loading.value = false;
};
const isCurrentDomain = () => {
return props.domain.id === authStore.currentDomain.id;
}
const loadShared = async () => {
loading.value = true;
sharedUsersIdSet.clear();
const { data } = await props.getSharedApi(props.domain);
sharedUsers.value = data.data.reduce((arr: IUserDisplayWithShareRole[], item: IUser) => {
const val = itemToDisplay(item);
if(!sharedUsersIdSet.has(val.id)) {
sharedUsersIdSet.add(val.id);
// for sort
if (isOwner(val.id)) {
val.role = 2;
} else if (isManager(val.id)) {
val.role = 1;
} else {
val.role = 0;
}
arr.push(val);
}
return arr;
}, []).sort((a: IUserDisplayWithShareRole, b: IUserDisplayWithShareRole) => b.role - a.role);
canSharedUsers.value = allUsers.value.filter((item) => !sharedUsersIdSet.has(item.id));
canSharedUserFilteredOptions.value = canSharedUsers.value;
loading.value = false;
}
function isOwner(userId: number) {
return userId === props.domain?.owner?.id
}
function isManager(userId: number) {
return false // TODO
}
const getUsers = async () => {
loading.value = true;
const result = await api.get('api/v1/users');
allUsers.value = result.data.data.map(itemToDisplay);
loading.value = false;
}
const itemToDisplay = (item: IUser) => {
return {
id: item.id,
firstName: item.first_name,
lastName: item.last_name,
fullNameSearch: (item.last_name + item.first_name).toLowerCase(),
fullName: item.last_name + ' ' + item.first_name,
email: item.email,
isSuperuser: item.is_superuser,
isActive: item.is_active,
role: 0,
} as IUserDisplayWithShareRole
}
</script>
<style lang="scss">
.dialog-content {
width: 700px !important;
max-width: 80vw !important;
max-height: 80vh;
.q-select {
min-width: 0 !important;
}
}
</style>