diff --git a/lib/modules/entities/entity_data.ex b/lib/modules/entities/entity_data.ex index 39d945c5..8017fbcf 100644 --- a/lib/modules/entities/entity_data.ex +++ b/lib/modules/entities/entity_data.ex @@ -820,8 +820,8 @@ defmodule PhoenixKit.Modules.Entities.EntityData do @doc """ Bulk updates the category of multiple records by UUIDs. - Since category is stored in the JSONB data column, this requires - fetching and updating each record individually. + Uses PostgreSQL jsonb_set to update the category field in the JSONB + data column in a single query. Returns a tuple with the count of updated records and nil. @@ -833,15 +833,21 @@ defmodule PhoenixKit.Modules.Entities.EntityData do def bulk_update_category(uuids, category) when is_list(uuids) do now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second) - records = from(d in __MODULE__, where: d.uuid in ^uuids) |> repo().all() - - Enum.each(records, fn record -> - updated_data = Map.put(record.data || %{}, "category", category) - changeset = changeset(record, %{data: updated_data, date_updated: now}) - repo().update(changeset) - end) - - {length(records), nil} + from(d in __MODULE__, + where: d.uuid in ^uuids, + update: [ + set: [ + data: + fragment( + "jsonb_set(COALESCE(?, '{}'::jsonb), '{category}', to_jsonb(?::text))", + d.data, + ^category + ), + date_updated: ^now + ] + ] + ) + |> repo().update_all([]) end @doc """ diff --git a/lib/modules/entities/web/data_navigator.ex b/lib/modules/entities/web/data_navigator.ex index 0e1e5f44..48c5b64d 100644 --- a/lib/modules/entities/web/data_navigator.ex +++ b/lib/modules/entities/web/data_navigator.ex @@ -11,6 +11,7 @@ defmodule PhoenixKit.Modules.Entities.Web.DataNavigator do alias PhoenixKit.Modules.Entities.EntityData alias PhoenixKit.Modules.Entities.Events alias PhoenixKit.Settings + alias PhoenixKit.Users.Auth.Scope alias PhoenixKit.Utils.Routes def mount(params, _session, socket) do @@ -363,53 +364,65 @@ defmodule PhoenixKit.Modules.Entities.Web.DataNavigator do end def handle_event("bulk_action", %{"action" => "archive"}, socket) do - ids = socket.assigns.selected_ids + if Scope.admin?(socket.assigns.phoenix_kit_current_scope) do + ids = socket.assigns.selected_ids - if ids == [] do - {:noreply, put_flash(socket, :error, gettext("No records selected"))} - else - {count, _} = EntityData.bulk_update_status(ids, "archived") + if ids == [] do + {:noreply, put_flash(socket, :error, gettext("No records selected"))} + else + {count, _} = EntityData.bulk_update_status(ids, "archived") - {:noreply, - socket - |> assign(:selected_ids, []) - |> refresh_data_stats() - |> apply_filters() - |> put_flash(:info, gettext("%{count} records archived", count: count))} + {:noreply, + socket + |> assign(:selected_ids, []) + |> refresh_data_stats() + |> apply_filters() + |> put_flash(:info, gettext("%{count} records archived", count: count))} + end + else + {:noreply, put_flash(socket, :error, gettext("Not authorized"))} end end def handle_event("bulk_action", %{"action" => "restore"}, socket) do - ids = socket.assigns.selected_ids + if Scope.admin?(socket.assigns.phoenix_kit_current_scope) do + ids = socket.assigns.selected_ids - if ids == [] do - {:noreply, put_flash(socket, :error, gettext("No records selected"))} - else - {count, _} = EntityData.bulk_update_status(ids, "published") + if ids == [] do + {:noreply, put_flash(socket, :error, gettext("No records selected"))} + else + {count, _} = EntityData.bulk_update_status(ids, "published") - {:noreply, - socket - |> assign(:selected_ids, []) - |> refresh_data_stats() - |> apply_filters() - |> put_flash(:info, gettext("%{count} records restored", count: count))} + {:noreply, + socket + |> assign(:selected_ids, []) + |> refresh_data_stats() + |> apply_filters() + |> put_flash(:info, gettext("%{count} records restored", count: count))} + end + else + {:noreply, put_flash(socket, :error, gettext("Not authorized"))} end end def handle_event("bulk_action", %{"action" => "delete"}, socket) do - ids = socket.assigns.selected_ids + if Scope.admin?(socket.assigns.phoenix_kit_current_scope) do + ids = socket.assigns.selected_ids - if ids == [] do - {:noreply, put_flash(socket, :error, gettext("No records selected"))} - else - {count, _} = EntityData.bulk_delete(ids) + if ids == [] do + {:noreply, put_flash(socket, :error, gettext("No records selected"))} + else + {count, _} = EntityData.bulk_delete(ids) - {:noreply, - socket - |> assign(:selected_ids, []) - |> refresh_data_stats() - |> apply_filters() - |> put_flash(:info, gettext("%{count} records deleted", count: count))} + {:noreply, + socket + |> assign(:selected_ids, []) + |> refresh_data_stats() + |> apply_filters() + |> put_flash(:info, gettext("%{count} records deleted", count: count))} + end + else + {:noreply, put_flash(socket, :error, gettext("Not authorized"))} end end @@ -418,36 +431,44 @@ defmodule PhoenixKit.Modules.Entities.Web.DataNavigator do %{"action" => "change_category", "category" => category}, socket ) do - ids = socket.assigns.selected_ids + if Scope.admin?(socket.assigns.phoenix_kit_current_scope) do + ids = socket.assigns.selected_ids - if ids == [] do - {:noreply, put_flash(socket, :error, gettext("No records selected"))} - else - {count, _} = EntityData.bulk_update_category(ids, category) + if ids == [] do + {:noreply, put_flash(socket, :error, gettext("No records selected"))} + else + {count, _} = EntityData.bulk_update_category(ids, category) - {:noreply, - socket - |> assign(:selected_ids, []) - |> refresh_data_stats() - |> apply_filters() - |> put_flash(:info, gettext("%{count} records updated", count: count))} + {:noreply, + socket + |> assign(:selected_ids, []) + |> refresh_data_stats() + |> apply_filters() + |> put_flash(:info, gettext("%{count} records updated", count: count))} + end + else + {:noreply, put_flash(socket, :error, gettext("Not authorized"))} end end def handle_event("bulk_action", %{"action" => "change_status", "status" => status}, socket) do - ids = socket.assigns.selected_ids + if Scope.admin?(socket.assigns.phoenix_kit_current_scope) do + ids = socket.assigns.selected_ids - if ids == [] do - {:noreply, put_flash(socket, :error, gettext("No records selected"))} - else - {count, _} = EntityData.bulk_update_status(ids, status) + if ids == [] do + {:noreply, put_flash(socket, :error, gettext("No records selected"))} + else + {count, _} = EntityData.bulk_update_status(ids, status) - {:noreply, - socket - |> assign(:selected_ids, []) - |> refresh_data_stats() - |> apply_filters() - |> put_flash(:info, gettext("%{count} records updated", count: count))} + {:noreply, + socket + |> assign(:selected_ids, []) + |> refresh_data_stats() + |> apply_filters() + |> put_flash(:info, gettext("%{count} records updated", count: count))} + end + else + {:noreply, put_flash(socket, :error, gettext("Not authorized"))} end end @@ -567,17 +588,21 @@ defmodule PhoenixKit.Modules.Entities.Web.DataNavigator do category = socket.assigns[:selected_category] || "all" search_term = socket.assigns[:search_term] || "" - # Start with all data and apply filters sequentially - entity_data_records = + # Apply entity and status filters first + pre_category_records = EntityData.list_all_data() |> filter_by_entity(entity_id) |> filter_by_status(status) + + # Extract categories BEFORE category filter so dropdown always shows all options + available_categories = EntityData.extract_unique_categories(pre_category_records) + + # Then apply remaining filters + entity_data_records = + pre_category_records |> filter_by_category(category) |> filter_by_search(search_term) - # Extract available categories from filtered results - available_categories = EntityData.extract_unique_categories(entity_data_records) - socket |> assign(:entity_data_records, entity_data_records) |> assign(:available_categories, available_categories) diff --git a/lib/modules/shop/shop.ex b/lib/modules/shop/shop.ex index f2b36a90..9efa4a94 100644 --- a/lib/modules/shop/shop.ex +++ b/lib/modules/shop/shop.ex @@ -749,7 +749,10 @@ defmodule PhoenixKit.Modules.Shop do |> repo().all() |> Map.new() rescue - _ -> %{} + e -> + require Logger + Logger.warning("Failed to load product counts by category: #{inspect(e)}") + %{} end @doc """ diff --git a/lib/modules/shop/web/catalog_product.ex b/lib/modules/shop/web/catalog_product.ex index 15d5809b..be6f0e97 100644 --- a/lib/modules/shop/web/catalog_product.ex +++ b/lib/modules/shop/web/catalog_product.ex @@ -264,7 +264,7 @@ defmodule PhoenixKit.Modules.Shop.Web.CatalogProduct do :category_icon_mode, Settings.get_setting_cached("shop_category_icon_mode", "none") ) - |> assign(:admin_edit_url, Routes.path("/admin/shop/products/#{product.id}")) + |> assign(:admin_edit_url, Routes.path("/admin/shop/products/#{product.uuid}/edit")) |> assign(:admin_edit_label, "Edit Product") {:ok, socket}