Merge branch 'main' into storefront-nuxt

This commit is contained in:
Egor Pavlovich Gorbunov 2025-11-16 17:10:38 +03:00
commit b489405783
4 changed files with 103 additions and 20 deletions

View file

@ -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

View file

@ -15,6 +15,7 @@
{% block content %}
{% component "unfold/components/container.html" %}
<div class="flex flex-col min-h-screen">
{% component "unfold/components/title.html" %}
{% trans "Dashboard" %}
<br/>
@ -121,10 +122,26 @@
{% component "unfold/components/title.html" %}
{% trans "Most wished product" %}
{% endcomponent %}
{% if most_wished_product %}
{% if most_wished_products %}
<ul class="flex flex-col divide-y divide-gray-200 dark:divide-base-700/50">
{% for p in most_wished_products %}
<li class="py-2 first:pt-0 last:pb-0">
<a href="{{ p.admin_url }}" class="flex items-center gap-4">
{% if p.image %}
<img src="{{ p.image }}" alt="{{ p.name }}" class="w-12 h-12 object-cover rounded"/>
{% endif %}
<span class="font-medium flex-1 truncate">{{ p.name }}</span>
<span class="text-xs px-2 py-0.5 rounded bg-base-700/[.06] dark:bg-white/[.06] text-gray-700 dark:text-gray-200">{{ p.count }}</span>
</a>
</li>
{% endfor %}
</ul>
{% elif most_wished_product %}
<a href="{{ most_wished_product.admin_url }}" class="flex items-center gap-4">
{% if most_wished_product.image %}
<img src="{{ most_wished_product.image }}" alt="{{ most_wished_product.name }}"
class="w-16 h-16 object-cover rounded"/>
{% endif %}
<span class="font-medium">{{ most_wished_product.name }}</span>
</a>
{% else %}
@ -138,10 +155,26 @@
{% component "unfold/components/title.html" %}
{% trans "Most popular product" %}
{% endcomponent %}
{% if most_popular_product %}
{% if most_popular_products %}
<ul class="flex flex-col divide-y divide-gray-200 dark:divide-base-700/50">
{% for p in most_popular_products %}
<li class="py-2 first:pt-0 last:pb-0">
<a href="{{ p.admin_url }}" class="flex items-center gap-4">
{% if p.image %}
<img src="{{ p.image }}" alt="{{ p.name }}" class="w-12 h-12 object-cover rounded"/>
{% endif %}
<span class="font-medium flex-1 truncate">{{ p.name }}</span>
<span class="text-xs px-2 py-0.5 rounded bg-base-700/[.06] dark:bg-white/[.06] text-gray-700 dark:text-gray-200">{{ p.count }}</span>
</a>
</li>
{% endfor %}
</ul>
{% elif most_popular_product %}
<a href="{{ most_popular_product.admin_url }}" class="flex items-center gap-4">
{% if most_popular_product.image %}
<img src="{{ most_popular_product.image }}" alt="{{ most_popular_product.name }}"
class="w-16 h-16 object-cover rounded"/>
{% endif %}
<span class="font-medium">{{ most_popular_product.name }}</span>
</a>
{% else %}
@ -156,12 +189,12 @@
{% component "unfold/components/separator.html" %}
{% endcomponent %}
<div class="mt-4">
<br/>
<div class="mt-4 mt-auto">
{% component "unfold/components/text.html" with class="text-center text-xs text-gray-500 dark:text-gray-400" %}
eVibes {{ evibes_version }} · Wiseless Team
{% endcomponent %}
</div>
</div>
{% endcomponent %}
{% endblock %}

View file

@ -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,
}
)