diff --git a/extensions/pagetop-user/src/component/admin/user_table.rs b/extensions/pagetop-user/src/component/admin/user_table.rs index 97664a13..b0e64cc1 100644 --- a/extensions/pagetop-user/src/component/admin/user_table.rs +++ b/extensions/pagetop-user/src/component/admin/user_table.rs @@ -7,6 +7,7 @@ use pagetop_htmx::hx_table::sort_link; use crate::ADMIN_USERS_PATH; use crate::LOCALES_USER; use crate::account::UserStatus; +use crate::handlers::admin::users::available_roles; use crate::permission::UserPermission; use crate::service::user_admin::{UserListItem, UserSortField}; use crate::user_path; @@ -68,6 +69,11 @@ impl Component for UserTable { let can_assign_roles = cx .request() .is_some_and(|r| has_permission(r, &UserPermission::AssignRoles)); + // Sólo hace falta consultar si hay algún rol asignable cuando el botón vaya a mostrarse. + let has_assignable_roles = can_assign_roles + && available_roles(&[]) + .await + .is_ok_and(|roles| !roles.is_empty()); for user in self.items() { let status = user.status; @@ -79,7 +85,10 @@ impl Component for UserTable { .with_cell(user.display_name.as_deref().unwrap_or("-")) .with_cell(roles_cell(user, cx).await) .with_cell(Lc::t(status_key(status), &LOCALES_USER)) - .with_cell(actions_cell(user, &waypoint, can_assign_roles, cx).await), + .with_cell( + actions_cell(user, &waypoint, can_assign_roles, has_assignable_roles, cx) + .await, + ), ); } @@ -203,12 +212,14 @@ fn username_cell(user: &UserListItem, waypoint: &Waypoint) -> Html { } // Construye la celda de acciones: editar siempre, y gestionar roles sólo si el usuario autenticado -// tiene permiso para asignarlos. Devuelve un componente `Html` para que el marcado se genere -// cuando `Table` renderice la celda, no al construir la fila. +// tiene permiso para asignarlos; en ese caso, deshabilitado si no hay ningún rol asignable en todo +// el sistema. Devuelve un componente `Html` para que el marcado se genere cuando `Table` renderice +// la celda, no al construir la fila. async fn actions_cell( user: &UserListItem, waypoint: &Waypoint, can_assign_roles: bool, + has_assignable_roles: bool, cx: &mut Context, ) -> Html { let id = user.id; @@ -229,6 +240,7 @@ async fn actions_cell( Button::anchor(Lc::t("btn-manage-roles", &LOCALES_USER), roles_href) .with_style(button::Style::Solid(Intent::Neutral)) .with_size(button::Size::Small) + .with_disabled(!has_assignable_roles) .render(cx) .await, ) diff --git a/extensions/pagetop-user/src/handlers/admin/users.rs b/extensions/pagetop-user/src/handlers/admin/users.rs index d2458aac..6aa0f853 100644 --- a/extensions/pagetop-user/src/handlers/admin/users.rs +++ b/extensions/pagetop-user/src/handlers/admin/users.rs @@ -112,8 +112,11 @@ fn search_bar(current_query: Option) -> Html { // **< available_roles >**************************************************************************** // Roles asignables desde la UI de usuarios: excluye "anonymous" (nunca se asigna explícitamente) -// y "authenticated" (implícito, nunca se asigna). -async fn available_roles(selected: &[i32]) -> Result, AuthError> { +// y "authenticated" (implícito, nunca se asigna). `pub(crate)` porque también la usa +// `component::admin::user_table` para decidir si hay algo que gestionar. +pub(crate) async fn available_roles( + selected: &[i32], +) -> Result, AuthError> { let items = role_admin::list_roles(&role_admin::RoleListParams { sort: role_admin::RoleSortField::Weight, dir: SortDir::Asc, @@ -276,6 +279,10 @@ async fn render_user_edit( let can_toggle_admin = request .extension::() .is_some_and(|a| a.is_admin && a.id != id); + let has_assignable_roles = match available_roles(&[]).await { + Ok(roles) => !roles.is_empty(), + Err(_) => return ErrorPage::InternalError(Some(request)).into_response(), + }; let mut page = Page::admin(request); let back_href = waypoint.or(page.context().route(ADMIN_USERS_PATH)); let title = Lc::t("title-admin-user-edit", &LOCALES_USER); @@ -284,6 +291,7 @@ async fn render_user_edit( status, user.is_admin, can_toggle_admin, + has_assignable_roles, &waypoint, page.context(), ); @@ -328,6 +336,7 @@ fn edit_actions( status: UserStatus, target_is_admin: bool, can_toggle_admin: bool, + has_assignable_roles: bool, waypoint: &Waypoint, cx: &mut Context, ) -> Flex { @@ -370,7 +379,8 @@ fn edit_actions( ) .with_child( Button::anchor(Lc::t("btn-manage-roles", &LOCALES_USER), roles_href) - .with_style(button::Style::Solid(Intent::Neutral)), + .with_style(button::Style::Solid(Intent::Neutral)) + .with_disabled(!has_assignable_roles), ) .with_child( Button::anchor(Lc::t("btn-reset-password", &LOCALES_USER), password_href)