-
Daniel Göbel authoredDaniel Göbel authored
AdminUsersView.vue 8.17 KiB
<script setup lang="ts">
import { useUserStore } from "@/stores/users";
import { onMounted, reactive } from "vue";
import { RoleEnum, type UserOutExtended } from "@/client";
import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue";
import BootstrapToast from "@/components/BootstrapToast.vue";
import { Toast } from "bootstrap";
import CreateUserModal from "@/components/admin/CreateUserModal.vue";
const userRepository = useUserStore();
type RoleList = RoleEnum[];
let successRoleToast: Toast | undefined;
let successInvitationToast: Toast | undefined;
let errorToast: Toast | undefined;
const userState = reactive<{
loading: boolean;
users: UserOutExtended[];
newUserRoles: RoleList[];
editUserRoles: boolean[];
editedUser?: UserOutExtended;
errorMessage: string;
searchString: string;
userRoles: RoleEnum[];
inspectUser?: UserOutExtended;
searched: boolean;
}>({
loading: false,
users: [],
newUserRoles: [],
editUserRoles: [],
editedUser: undefined,
errorMessage: "",
searchString: "",
userRoles: [],
inspectUser: undefined,
searched: false,
});
function searchUsers() {
userState.loading = true;
userRepository
.fetchUsers(
userState.searchString ? userState.searchString : undefined,
userState.userRoles ? userState.userRoles : undefined,
)
.then((users) => {
userState.users = users;
userState.newUserRoles = users.map((user) => user.roles);
userState.editUserRoles = users.map(() => false);
})
.finally(() => {
userState.loading = false;
userState.searched = true;
});
}
function saveUserRoles(index: number) {
userState.loading = true;
userState.editedUser = userState.users[index];
userRepository
.updateUserRoles(userState.users[index].uid, userState.newUserRoles[index])
.then((user) => {
userState.users[index] = user;
userState.editUserRoles[index] = false;
successRoleToast?.show();
})
.catch((err) => {
userState.errorMessage = err.body?.detail;
errorToast?.show();
})
.finally(() => {
userState.loading = false;
});
}
function resetUserRoles(index: number) {
userState.editUserRoles[index] = false;
userState.newUserRoles[index] = JSON.parse(
JSON.stringify(userState.users[index].roles),
);
}
function resendInvitationEmail(uid: string) {
userState.loading = true;
userRepository
.resendInvitationEmail(uid)
.then(() => {
successInvitationToast?.show();
})
.finally(() => {
userState.loading = false;
});
}
onMounted(() => {
successRoleToast = new Toast("#change-role-success-toast");
successInvitationToast = new Toast("#resend-invitation-success-toast");
errorToast = new Toast("#change-role-error-toast");
});
</script>
<template>
<bootstrap-toast toast-id="resend-invitation-success-toast">
Successfully resend invitation email
</bootstrap-toast>
<bootstrap-toast toast-id="change-role-success-toast">
Successfully change roles of user {{ userState.editedUser?.display_name }}
</bootstrap-toast>
<bootstrap-toast toast-id="change-role-error-toast" color-class="danger">
<template #default
>Couldn't change the roles of
{{ userState.editedUser?.display_name }}
</template>
<template #body>Error: {{ userState.errorMessage }}</template>
</bootstrap-toast>
<create-user-modal modal-id="create-user-modal" />
<div
class="row border-bottom mb-4 justify-content-between align-items-center pb-2 pe-2"
>
<h2 class="w-fit">Manage Users</h2>
<button
type="button"
class="btn btn-primary w-fit"
data-bs-toggle="modal"
data-bs-target="#create-user-modal"
>
Invite User
</button>
</div>
<form @submit.prevent="searchUsers" id="admin-user-search-form">
<div class="d-flex justify-content-evenly align-content-center">
<div class="mx-2">
<label for="admin-user-state-select" class="form-label"
>User Roles</label
>
<select
v-model="userState.userRoles"
multiple
class="form-select mb-4 w-fit"
id="admin-user-state-select"
>
<option v-for="role in Object.values(RoleEnum)" :key="role">
{{ role }}
</option>
</select>
</div>
<div class="flex-fill mx-2">
<label for="admin-user-name-search" class="form-label"
>Name of a user</label
>
<div class="input-group">
<div class="input-group-text">
<font-awesome-icon icon="fa-solid fa-search" />
</div>
<input
id="admin-user-name-search"
type="text"
class="form-control"
maxlength="32"
minlength="3"
v-model="userState.searchString"
placeholder="Search for user name"
/>
</div>
</div>
</div>
<button
type="submit"
class="btn btn-primary w-fit"
:disabled="userState.loading"
>
Search
</button>
</form>
<table
class="table table-striped align-middle caption-top"
v-if="userState.users"
>
<caption>
Displaying
{{
userState.users.length
}}
Users
</caption>
<thead>
<tr>
<th scope="col"><b>Name</b></th>
<th scope="col">UID</th>
<th
v-for="role in Object.values(RoleEnum)"
:key="role"
scope="col"
class="text-center"
>
{{ role.toUpperCase() }}
</th>
<th scope="col"></th>
</tr>
</thead>
<tbody v-if="userState.users.length === 0">
<tr>
<td colspan="8" class="text-center fst-italic fw-light">
<template v-if="userState.searched"
>No Users found with specified filters
</template>
<template v-else>Select a filter and search for Users</template>
</td>
</tr>
</tbody>
<tbody v-else>
<tr v-for="(user, index) in userState.users" :key="user.uid">
<th scope="row">{{ user.display_name }}</th>
<td>{{ user.uid }}</td>
<td
v-for="role in Object.values(RoleEnum)"
:key="role"
class="text-center"
>
<input
type="checkbox"
class="form-check-input"
:value="role"
:disabled="!userState.editUserRoles[index]"
v-model="userState.newUserRoles[index]"
/>
</td>
<td class="text-end" v-if="userState.editUserRoles[index]">
<div
class="btn-group btn-group-sm"
role="group"
aria-label="Basic mixed styles example"
>
<button
type="button"
class="btn btn-success"
:disabled="userState.loading"
@click="saveUserRoles(index)"
>
Save
</button>
<button
type="button"
class="btn btn-danger"
:disabled="userState.loading"
@click="resetUserRoles(index)"
>
Cancel
</button>
</div>
</td>
<td class="text-end" v-else>
<div class="btn-group">
<button
type="button"
class="btn btn-sm btn-secondary"
@click="userState.editUserRoles[index] = true"
>
Edit Roles
</button>
<button
v-if="user.invitation_token_created_at"
type="button"
class="btn btn-secondary btn-sm dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<span class="visually-hidden">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu">
<li>
<a
class="dropdown-item"
:class="{ disabled: userState.loading }"
:aria-disabled="userState.loading"
@click="resendInvitationEmail(user.uid)"
>Resend invitation email</a
>
</li>
</ul>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<style scoped></style>