|
| 1 | +<template> |
| 2 | + <span> |
| 3 | + <v-tooltip bottom> |
| 4 | + <template #activator="{ on, attrs }"> |
| 5 | + <v-btn |
| 6 | + color="primary" |
| 7 | + @click="dialog = true" |
| 8 | + icon |
| 9 | + v-bind="attrs" |
| 10 | + v-on="on" |
| 11 | + > |
| 12 | + <v-icon>group</v-icon> |
| 13 | + </v-btn> |
| 14 | + </template> |
| 15 | + <span>Manage Team Members</span> |
| 16 | + </v-tooltip> |
| 17 | + <v-dialog v-model="dialog" width="600"> |
| 18 | + <v-card> |
| 19 | + <v-card-title class="headline pb-0"> |
| 20 | + Manage Team Members |
| 21 | + </v-card-title> |
| 22 | + |
| 23 | + <v-card-text> |
| 24 | + <hr /> |
| 25 | + |
| 26 | + <v-row v-if="isDraft"> |
| 27 | + <v-col cols="9"> |
| 28 | + <form autocomplete="off"> |
| 29 | + <v-autocomplete |
| 30 | + v-model="userSearchSelection" |
| 31 | + clearable |
| 32 | + dense |
| 33 | + :filter="filterObject" |
| 34 | + hide-details |
| 35 | + :items="userSearchResults" |
| 36 | + label="Enter a name, e-mail, or username" |
| 37 | + :loading="isLoadingDropdown" |
| 38 | + return-object |
| 39 | + :search-input.sync="findUsers" |
| 40 | + > |
| 41 | + <!-- no data --> |
| 42 | + <template #no-data> |
| 43 | + <div class="px-2"> |
| 44 | + Can't find someone? They may not have joined the site.<br /> |
| 45 | + Kindly send them a link to the site and ask them to log |
| 46 | + in. |
| 47 | + </div> |
| 48 | + </template> |
| 49 | + <!-- selected user --> |
| 50 | + <template #selection="data"> |
| 51 | + <span |
| 52 | + v-bind="data.attrs" |
| 53 | + :input-value="data.selected" |
| 54 | + close |
| 55 | + @click="data.select" |
| 56 | + @click:close="remove(data.item)" |
| 57 | + > |
| 58 | + {{ data.item.fullName }} |
| 59 | + </span> |
| 60 | + </template> |
| 61 | + <!-- users found in dropdown --> |
| 62 | + <template #item="data"> |
| 63 | + <template v-if="typeof data.item !== 'object'"> |
| 64 | + <v-list-item-content v-text="data.item" /> |
| 65 | + </template> |
| 66 | + <template v-else> |
| 67 | + <v-list-item-content> |
| 68 | + <v-list-item-title v-html="data.item.fullName" /> |
| 69 | + <v-list-item-subtitle v-html="data.item.username" /> |
| 70 | + <v-list-item-subtitle v-html="data.item.email" /> |
| 71 | + </v-list-item-content> |
| 72 | + </template> |
| 73 | + </template> |
| 74 | + </v-autocomplete> |
| 75 | + </form> |
| 76 | + </v-col> |
| 77 | + <v-col cols="3"> |
| 78 | + <v-btn |
| 79 | + color="primary" |
| 80 | + :disabled="!userSearchSelection" |
| 81 | + :loading="isLoadingDropdown" |
| 82 | + @click="addUser" |
| 83 | + > |
| 84 | + <span>Add</span> |
| 85 | + </v-btn> |
| 86 | + </v-col> |
| 87 | + </v-row> |
| 88 | + <div v-else> |
| 89 | + You can only invite and manage team members while this form is a draft |
| 90 | + </div> |
| 91 | + |
| 92 | + <p class="mt-5"> |
| 93 | + <strong>Team members for this submission:</strong> |
| 94 | + </p> |
| 95 | + |
| 96 | + <v-skeleton-loader :loading="isLoadingTable" type="table-row"> |
| 97 | + <v-simple-table dense> |
| 98 | + <template> |
| 99 | + <thead> |
| 100 | + <tr> |
| 101 | + <th class="text-left">Name</th> |
| 102 | + <th class="text-left">Username</th> |
| 103 | + <th class="text-left">Email</th> |
| 104 | + <th class="text-left" v-if="isDraft">Actions</th> |
| 105 | + </tr> |
| 106 | + </thead> |
| 107 | + <tbody> |
| 108 | + <tr :key="item.userId" v-for="item in userTableList"> |
| 109 | + <td>{{ item.fullName }}</td> |
| 110 | + <td>{{ item.username }}</td> |
| 111 | + <td>{{ item.email }}</td> |
| 112 | + <td v-if="isDraft"> |
| 113 | + <v-btn |
| 114 | + color="red" |
| 115 | + icon |
| 116 | + :disabled="item.isOwner" |
| 117 | + @click="removeUser(item)" |
| 118 | + > |
| 119 | + <v-icon>remove_circle</v-icon> |
| 120 | + </v-btn> |
| 121 | + </td> |
| 122 | + </tr> |
| 123 | + </tbody> |
| 124 | + </template> |
| 125 | + </v-simple-table> |
| 126 | + </v-skeleton-loader> |
| 127 | + </v-card-text> |
| 128 | + |
| 129 | + <v-card-actions class="justify-center"> |
| 130 | + <v-btn class="mb-5 close-dlg" color="primary" @click="dialog = false"> |
| 131 | + <span>Close</span> |
| 132 | + </v-btn> |
| 133 | + </v-card-actions> |
| 134 | + </v-card> |
| 135 | + |
| 136 | + <BaseDialog |
| 137 | + v-model="showDeleteDialog" |
| 138 | + type="CONTINUE" |
| 139 | + @close-dialog="showDeleteDialog = false" |
| 140 | + @continue-dialog="modifyPermissions(userToDelete.id, []); showDeleteDialog = false" |
| 141 | + > |
| 142 | + <template #title>Remove {{ userToDelete.username }}</template> |
| 143 | + <template #text> |
| 144 | + Are you sure you wish to remove |
| 145 | + <strong>{{ userToDelete.username }}</strong |
| 146 | + >? They will no longer have permissions for this submission. |
| 147 | + </template> |
| 148 | + <template #button-text-continue> |
| 149 | + <span>Remove</span> |
| 150 | + </template> |
| 151 | + </BaseDialog> |
| 152 | + </v-dialog> |
| 153 | + </span> |
| 154 | +</template> |
| 155 | + |
| 156 | +<script> |
| 157 | +import { mapActions } from 'vuex'; |
| 158 | + |
| 159 | +import { FormPermissions } from '@/utils/constants'; |
| 160 | +import { rbacService, userService } from '@/services'; |
| 161 | + |
| 162 | +export default { |
| 163 | + name: 'ManageSubmissionUsers', |
| 164 | + props: { |
| 165 | + isDraft: { |
| 166 | + type: Boolean, |
| 167 | + required: true, |
| 168 | + }, |
| 169 | + submissionId: { |
| 170 | + type: String, |
| 171 | + required: true, |
| 172 | + }, |
| 173 | + }, |
| 174 | + data() { |
| 175 | + return { |
| 176 | + dialog: false, |
| 177 | + isLoadingTable: true, |
| 178 | + showDeleteDialog: false, |
| 179 | + userTableList: [], |
| 180 | + userToDelete: {}, |
| 181 | + |
| 182 | + // search box |
| 183 | + findUsers: null, |
| 184 | + isLoadingDropdown: false, |
| 185 | + userSearchResults: [], |
| 186 | + userSearchSelection: null, |
| 187 | + }; |
| 188 | + }, |
| 189 | + methods: { |
| 190 | + ...mapActions('notifications', ['addNotification']), |
| 191 | + // show users in dropdown that have a text match on multiple properties |
| 192 | + addUser() { |
| 193 | + if (this.userSearchSelection) { |
| 194 | + const id = this.userSearchSelection.id; |
| 195 | + if (this.userTableList.some((u) => u.id === id)) { |
| 196 | + this.addNotification({ |
| 197 | + type: 'warning', |
| 198 | + message: `User ${this.userSearchSelection.username} is already in the list of team members.`, |
| 199 | + }); |
| 200 | + } else { |
| 201 | + this.modifyPermissions(id, [ |
| 202 | + FormPermissions.SUBMISSION_UPDATE, |
| 203 | + FormPermissions.SUBMISSION_READ, |
| 204 | + ]); |
| 205 | + } |
| 206 | + } |
| 207 | + // reset search field |
| 208 | + this.userSearchSelection = null; |
| 209 | + }, |
| 210 | + filterObject(item, queryText) { |
| 211 | + return Object.values(item).some((v) => v !== null && v.toLocaleLowerCase().includes(queryText.toLocaleLowerCase())); |
| 212 | + }, |
| 213 | + async getSubmissionUsers() { |
| 214 | + this.isLoadingTable = true; |
| 215 | + try { |
| 216 | + const response = await rbacService.getSubmissionUsers({ |
| 217 | + formSubmissionId: this.submissionId, |
| 218 | + }); |
| 219 | + if (response.data) { |
| 220 | + this.userTableList = this.transformResponseToTable(response.data); |
| 221 | + } |
| 222 | + } catch (error) { |
| 223 | + this.addNotification({ |
| 224 | + message: |
| 225 | + 'An error occured while trying to fetch users for this submission.', |
| 226 | + consoleError: `Error getting users for ${this.submissionId}: ${error}`, |
| 227 | + }); |
| 228 | + } finally { |
| 229 | + this.isLoadingTable = false; |
| 230 | + } |
| 231 | + }, |
| 232 | + async modifyPermissions(userId, permissions) { |
| 233 | + this.isLoadingTable = true; |
| 234 | + try { |
| 235 | + // Add the selected user with read/update permissions on this submission |
| 236 | + const response = await rbacService.setSubmissionUserPermissions( |
| 237 | + { permissions: permissions }, |
| 238 | + { |
| 239 | + formSubmissionId: this.submissionId, |
| 240 | + userId: userId, |
| 241 | + } |
| 242 | + ); |
| 243 | + if (response.data) { |
| 244 | + this.userTableList = this.transformResponseToTable(response.data); |
| 245 | + } |
| 246 | + } catch (error) { |
| 247 | + this.addNotification({ |
| 248 | + message: |
| 249 | + 'An error occured while trying to update users for this submission.', |
| 250 | + consoleError: `Error setting user permissions. Sub: ${this.submissionId} User: ${userId} Error: ${error}`, |
| 251 | + }); |
| 252 | + } finally { |
| 253 | + this.isLoadingTable = false; |
| 254 | + } |
| 255 | + }, |
| 256 | + removeUser(userRow) { |
| 257 | + this.userToDelete = userRow; |
| 258 | + this.showDeleteDialog = true; |
| 259 | + }, |
| 260 | + transformResponseToTable(responseData) { |
| 261 | + return responseData |
| 262 | + .map((su) => { |
| 263 | + return { |
| 264 | + email: su.user.email, |
| 265 | + fullName: su.user.fullName, |
| 266 | + id: su.userId, |
| 267 | + isOwner: su.permissions.includes( |
| 268 | + FormPermissions.SUBMISSION_CREATE |
| 269 | + ), |
| 270 | + username: su.user.username, |
| 271 | + }; |
| 272 | + }) |
| 273 | + .sort((a, b) => b.isOwner - a.isOwner); |
| 274 | + }, |
| 275 | + }, |
| 276 | + watch: { |
| 277 | + // Get a list of user objects from database |
| 278 | + async findUsers(input) { |
| 279 | + if (!input) return; |
| 280 | + this.isLoadingDropdown = true; |
| 281 | + try { |
| 282 | + const response = await userService.getUsers({ search: input }); |
| 283 | + this.userSearchResults = response.data; |
| 284 | + } catch (error) { |
| 285 | + console.error(`Error getting users: ${error}`); // eslint-disable-line no-console |
| 286 | + } finally { |
| 287 | + this.isLoadingDropdown = false; |
| 288 | + } |
| 289 | + }, |
| 290 | + }, |
| 291 | + created() { |
| 292 | + this.getSubmissionUsers(); |
| 293 | + }, |
| 294 | +}; |
| 295 | +</script> |
0 commit comments