From f1b56718990db19aa50ac76945a9c4ccc54e030f Mon Sep 17 00:00:00 2001 From: Norman Niati Date: Wed, 20 May 2026 17:06:40 +0200 Subject: [PATCH] feat(plugin-id/ui): replace user list row actions with a gear menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per Fabrice's review (chantier I.1): replace the two per-row action buttons (edit + delete) on the user list with a single gear button opening a menu of 5 actions: Edit, Delete, Lock/Unlock, Isolate/Restore, Reset password. Lock/isolate/reset reuse the existing user action endpoints. Edit still navigates to the routed edit page — it will be rewired to the dialog in chantier I.2. --- ui/src/views/UserListView.vue | 85 ++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/ui/src/views/UserListView.vue b/ui/src/views/UserListView.vue index 6bb84b0..03e9081 100644 --- a/ui/src/views/UserListView.vue +++ b/ui/src/views/UserListView.vue @@ -51,12 +51,34 @@ mdi-lock-open-variant @@ -85,6 +107,21 @@ + + + + + {{ t('user.' + actionType) }} + {{ t('user.' + actionType + 'Confirm', { id: actionTarget?.id }) }} + + + {{ t('common.cancel') }} + {{ t('common.confirm') }} + + + @@ -120,6 +157,12 @@ const deleteTarget = ref(null) const deleting = ref(false) const bulkDeleteDialog = ref(false) +// Row gear-menu action state (lock/unlock, isolate/restore, reset). +const actionDialog = ref(false) +const actionType = ref('') +const actionTarget = ref(null) +const actionLoading = ref(false) + const headers = computed(() => [ { title: t('user.login'), key: 'id', sortable: true }, { title: t('user.firstName'), key: 'firstName', sortable: true }, @@ -180,6 +223,36 @@ async function confirmBulkDelete() { dt.load({ page: 1, itemsPerPage: itemsPerPage.value }) } +function startUserAction(item, type) { + actionTarget.value = item + actionType.value = type + actionDialog.value = true +} + +async function confirmUserAction() { + if (dt.demoMode.value) { + errorStore.push({ message: t('user.demoAction'), status: 0 }) + actionDialog.value = false + return + } + actionLoading.value = true + const id = actionTarget.value.id + const actions = { + lock: () => api.del(`rest/service/id/user/${id}/lock`), + unlock: () => api.put(`rest/service/id/user/${id}/unlock`), + isolate: () => api.del(`rest/service/id/user/${id}/isolate`), + restore: () => api.put(`rest/service/id/user/${id}/restore`), + resetPassword: () => api.put(`rest/service/id/user/${id}/reset`), + } + await actions[actionType.value]() + actionLoading.value = false + actionDialog.value = false + actionTarget.value = null + // Reload the current page so the status icon and the contextual + // lock/isolate menu labels reflect the new state. + dt.load(lastOptions) +} + onMounted(() => { appStore.setBreadcrumbs( [