diff --git a/engine/core/locale/ru_RU/LC_MESSAGES/django.mo b/engine/core/locale/ru_RU/LC_MESSAGES/django.mo index ec455d60..31b9d3ab 100644 Binary files a/engine/core/locale/ru_RU/LC_MESSAGES/django.mo and b/engine/core/locale/ru_RU/LC_MESSAGES/django.mo differ diff --git a/engine/core/locale/ru_RU/LC_MESSAGES/django.po b/engine/core/locale/ru_RU/LC_MESSAGES/django.po index 73c3f2a0..06af18ad 100644 --- a/engine/core/locale/ru_RU/LC_MESSAGES/django.po +++ b/engine/core/locale/ru_RU/LC_MESSAGES/django.po @@ -2805,7 +2805,7 @@ msgstr "Выручка (нетто, 30d)" #: engine/core/templates/admin/index.html:43 msgid "Returns (30d)" -msgstr "Возвращение (30 дней)" +msgstr "Возвраты (30 дней)" #: engine/core/templates/admin/index.html:52 msgid "Processed orders (30d)" @@ -2813,7 +2813,7 @@ msgstr "Обработанные заказы (30d)" #: engine/core/templates/admin/index.html:65 msgid "Sales vs Returns (30d)" -msgstr "Продажи против возвратов (30d)" +msgstr "Продажи и Возвраты (30d)" #: engine/core/templates/admin/index.html:82 msgid "Gross" @@ -2837,7 +2837,7 @@ msgstr "Ссылки недоступны." #: engine/core/templates/admin/index.html:119 msgid "Most wished product" -msgstr "Самый желанный продукт" +msgstr "Самые желанные товары" #: engine/core/templates/admin/index.html:128 #: engine/core/templates/admin/index.html:144 @@ -2846,7 +2846,7 @@ msgstr "Данных пока нет." #: engine/core/templates/admin/index.html:135 msgid "Most popular product" -msgstr "Самый популярный продукт" +msgstr "Самые популярные товары" #: engine/core/templates/digital_order_created_email.html:7 #: engine/core/templates/digital_order_created_email.html:100 diff --git a/engine/core/templates/admin/index.html b/engine/core/templates/admin/index.html index 48f0f5c2..e6cb458d 100644 --- a/engine/core/templates/admin/index.html +++ b/engine/core/templates/admin/index.html @@ -15,6 +15,7 @@ {% block content %} {% component "unfold/components/container.html" %} +
{% component "unfold/components/title.html" %} {% trans "Dashboard" %}
@@ -121,10 +122,26 @@ {% component "unfold/components/title.html" %} {% trans "Most wished product" %} {% endcomponent %} - {% if most_wished_product %} + {% if most_wished_products %} + + {% elif most_wished_product %} - {{ most_wished_product.name }} + {% if most_wished_product.image %} + {{ most_wished_product.name }} + {% endif %} {{ most_wished_product.name }} {% else %} @@ -138,10 +155,26 @@ {% component "unfold/components/title.html" %} {% trans "Most popular product" %} {% endcomponent %} - {% if most_popular_product %} + {% if most_popular_products %} + + {% elif most_popular_product %} - {{ most_popular_product.name }} + {% if most_popular_product.image %} + {{ most_popular_product.name }} + {% endif %} {{ most_popular_product.name }} {% else %} @@ -156,12 +189,12 @@ {% component "unfold/components/separator.html" %} {% endcomponent %} -
-
+
{% component "unfold/components/text.html" with class="text-center text-xs text-gray-500 dark:text-gray-400" %} eVibes {{ evibes_version }} · Wiseless Team {% endcomponent %}
+
{% endcomponent %} {% endblock %} \ No newline at end of file diff --git a/engine/core/views.py b/engine/core/views.py index 012211c3..0182b080 100644 --- a/engine/core/views.py +++ b/engine/core/views.py @@ -438,18 +438,21 @@ def dashboard_callback(request, context): } ) + # Single most wished product (backward compatibility) most_wished: dict[str, str | int | float | None] | None = None + # Top 10 most wished products + most_wished_list: list[dict[str, str | int | float | None]] = [] with suppress(Exception): - wished = ( + wished_qs = ( Wishlist.objects.filter(user__is_active=True, user__is_staff=False) .values("products") .exclude(products__isnull=True) .annotate(cnt=Count("products")) .order_by("-cnt") - .first() ) - if wished and wished.get("products"): - product = Product.objects.filter(pk=wished["products"]).first() + wished_first = wished_qs.first() + if wished_first and wished_first.get("products"): + product = Product.objects.filter(pk=wished_first["products"]).first() if product: img = product.images.first().image_url if product.images.exists() else "" most_wished = { @@ -458,17 +461,42 @@ def dashboard_callback(request, context): "admin_url": reverse("admin:core_product_change", args=[product.pk]), } + # Build top 10 list + wished_top10 = list(wished_qs[:10]) + if wished_top10: + counts_map = {row["products"]: row["cnt"] for row in wished_top10 if row.get("products")} + products = Product.objects.filter(pk__in=counts_map.keys()) + # Preserve order as in wished_top10 + product_by_id = {p.pk: p for p in products} + for row in wished_top10: + pid = row.get("products") + if not pid or pid not in product_by_id: + continue + p = product_by_id[pid] + img = p.images.first().image_url if p.images.exists() else "" + most_wished_list.append( + { + "name": p.name, + "image": img, + "admin_url": reverse("admin:core_product_change", args=[p.pk]), + "count": int(row.get("cnt", 0)), + } + ) + + # Single most popular product (backward compatibility) most_popular: dict[str, str | int | float | None] | None = None + # Top 10 most popular products by quantity + most_popular_list: list[dict[str, str | int | float | None]] = [] with suppress(Exception): - popular = ( + popular_qs = ( OrderProduct.objects.filter(status="FINISHED", order__status="FINISHED", product__isnull=False) .values("product") .annotate(total_qty=Sum("quantity")) .order_by("-total_qty") - .first() ) - if popular and popular.get("product"): - product = Product.objects.filter(pk=popular["product"]).first() + popular_first = popular_qs.first() + if popular_first and popular_first.get("product"): + product = Product.objects.filter(pk=popular_first["product"]).first() if product: img = product.images.first().image_url if product.images.exists() else "" most_popular = { @@ -477,6 +505,26 @@ def dashboard_callback(request, context): "admin_url": reverse("admin:core_product_change", args=[product.pk]), } + popular_top10 = list(popular_qs[:10]) + if popular_top10: + qty_map = {row["product"]: row["total_qty"] for row in popular_top10 if row.get("product")} + products = Product.objects.filter(pk__in=qty_map.keys()) + product_by_id = {p.pk: p for p in products} + for row in popular_top10: + pid = row.get("product") + if not pid or pid not in product_by_id: + continue + p = product_by_id[pid] + img = p.images.first().image_url if p.images.exists() else "" + most_popular_list.append( + { + "name": p.name, + "image": img, + "admin_url": reverse("admin:core_product_change", args=[p.pk]), + "count": int(row.get("total_qty", 0) or 0), + } + ) + context.update( { "custom_variable": "value", @@ -488,6 +536,8 @@ def dashboard_callback(request, context): "quick_links": quick_links, "most_wished_product": most_wished, "most_popular_product": most_popular, + "most_wished_products": most_wished_list, + "most_popular_products": most_popular_list, } )