Skip to content
Snippets Groups Projects
AdminUsersView.vue 8.17 KiB
Newer Older
  • Learn to ignore specific revisions
  • <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" />
    
        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 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 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>