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 %}
-
+ {% if most_wished_product.image %}
+
+ {% 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 %}
-
+ {% if most_popular_product.image %}
+
+ {% 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,
}
)