Features: 1) Fallback to default avatar image in avatar_url method; 2) Added header component for admin dashboard template; 3) Improved layout sections in admin dashboard templates for consistent spacing;

Fixes: 1) Prevent `.initialized` file creation in debug mode;

Extra: 1) Refactored and reorganized admin dashboard templates for better readability and maintainability.
This commit is contained in:
Egor Pavlovich Gorbunov 2025-12-10 21:44:17 +03:00
parent 76cea49c06
commit 625fe6e350
5 changed files with 103 additions and 84 deletions

View file

@ -185,7 +185,8 @@ class Command(BaseCommand):
(User.objects.filter(Q(language="") | ~Q(language__in=valid_codes)).update(language=settings.LANGUAGE_CODE)) (User.objects.filter(Q(language="") | ~Q(language__in=valid_codes)).update(language=settings.LANGUAGE_CODE))
try: try:
initialized_path.write_text(settings.RELEASE_DATE.isoformat(), encoding="utf-8") if not settings.DEBUG:
initialized_path.write_text(settings.RELEASE_DATE.isoformat(), encoding="utf-8")
except Exception as exc: except Exception as exc:
logger.error("Failed to update .initialized file: %s", exc) logger.error("Failed to update .initialized file: %s", exc)

View file

@ -1,6 +1,6 @@
{% load i18n unfold %} {% load i18n unfold %}
<div class="font-semibold text-2xl text-font-important-light tracking-tight dark:text-font-important-dark{% if class %} {{ class }}{% endif %}">
<div class="flex flex-wrap items-center gap-2"> <a>{% trans "Dashboard" %}</a>
{% with cur=tf|default:30 %} {% with cur=tf|default:30 %}
<a href="?tf=7" <a href="?tf=7"
class="px-3 py-1 rounded-full text-sm border transition-colors {% if cur == 7 %} bg-gray-900 text-white dark:bg-white dark:text-gray-900 border-transparent {% else %} border-gray-300 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 {% endif %}">7d</a> class="px-3 py-1 rounded-full text-sm border transition-colors {% if cur == 7 %} bg-gray-900 text-white dark:bg-white dark:text-gray-900 border-transparent {% else %} border-gray-300 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 {% endif %}">7d</a>

View file

@ -1,69 +1,72 @@
{% load i18n unfold %} {% load i18n unfold %}
{% component "unfold/components/card.html" %} <div class="mt-6 md:mt-8">
{% component "unfold/components/title.html" %} {% component "unfold/components/card.html" %}
{% trans "Most wished product" %} {% component "unfold/components/title.html" %}
{% endcomponent %} {% trans "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 %}
{% component "unfold/components/text.html" %}
{% trans "No data yet." %}
{% endcomponent %} {% endcomponent %}
{% endif %} {% if most_wished_products %}
{% endcomponent %} <ul class="flex flex-col divide-y divide-gray-200 dark:divide-base-700/50">
{% for p in most_wished_products %}
{% component "unfold/components/card.html" %} <li class="py-2 first:pt-0 last:pb-0">
{% component "unfold/components/title.html" %} <a href="{{ p.admin_url }}" class="flex items-center gap-4">
{% trans "Most popular product" %} {% 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 %}
{% component "unfold/components/text.html" %}
{% trans "No data yet." %}
{% endcomponent %}
{% endif %}
{% endcomponent %} {% endcomponent %}
{% if most_popular_products %} </div>
<ul class="flex flex-col divide-y divide-gray-200 dark:divide-base-700/50"> <div class="mt-6 md:mt-8">
{% for p in most_popular_products %} {% component "unfold/components/card.html" %}
<li class="py-2 first:pt-0 last:pb-0"> {% component "unfold/components/title.html" %}
<a href="{{ p.admin_url }}" class="flex items-center gap-4"> {% trans "Most popular product" %}
{% 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 %}
{% component "unfold/components/text.html" %}
{% trans "No data yet." %}
{% endcomponent %} {% endcomponent %}
{% endif %} {% if most_popular_products %}
{% endcomponent %} <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 %}
{% component "unfold/components/text.html" %}
{% trans "No data yet." %}
{% endcomponent %}
{% endif %}
{% endcomponent %}
</div>

View file

@ -17,33 +17,44 @@
{% component "unfold/components/container.html" %} {% component "unfold/components/container.html" %}
<div class="flex flex-col min-h-screen"> <div class="flex flex-col min-h-screen">
{% component "unfold/components/title.html" %} {% component "unfold/components/title.html" %}
{% trans "Dashboard" %} {% include "admin/dashboard/_header.html" %}
{% include "admin/dashboard/_filters.html" %}
{% endcomponent %} {% endcomponent %}
{% include "admin/dashboard/_kpis.html" %} <div class="mt-6 md:mt-8">
{% include "admin/dashboard/_kpis.html" %}
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6 items-start">
{% include "admin/dashboard/_daily_sales.html" %}
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6"> <div class="mt-6 md:mt-8">
{% include "admin/dashboard/_low_stock.html" %} {% include "admin/dashboard/_low_stock.html" %}
</div>
<div class="mt-6 md:mt-8">
{% include "admin/dashboard/_most_returned.html" %} {% include "admin/dashboard/_most_returned.html" %}
</div> </div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6"> <div class="mt-6 md:mt-8">
{% include "admin/dashboard/_customers_mix.html" %} {% include "admin/dashboard/_customers_mix.html" %}
{% if shipped_vs_digital.digital_qty|default:0 > 0 and shipped_vs_digital.shipped_qty|default:0 > 0 %}
{% include "admin/dashboard/_shipped_vs_digital.html" %}
{% endif %}
</div> </div>
{% include "admin/dashboard/_top_categories.html" %} {% if shipped_vs_digital.digital_qty|default:0 > 0 and shipped_vs_digital.shipped_qty|default:0 > 0 %}
{% include "admin/dashboard/_product_lists.html" %} <div class="mt-6 md:mt-8">
{% include "admin/dashboard/_shipped_vs_digital.html" %}
</div>
{% endif %}
{% component "unfold/components/separator.html" %} <div class="mt-6 md:mt-8">
{% endcomponent %} {% include "admin/dashboard/_top_categories.html" %}
</div>
<div class="mt-6 md:mt-8">
{% include "admin/dashboard/_product_lists.html" %}
</div>
<div class="mt-6 md:mt-8">
{% include "admin/dashboard/_daily_sales.html" %}
</div>
{% component "unfold/components/separator.html" %}{% endcomponent %}
<div class="mt-4 mt-auto"> <div class="mt-4 mt-auto">
{% component "unfold/components/text.html" with class="text-center text-xs text-gray-500 dark:text-gray-400" %} {% component "unfold/components/text.html" with class="text-center text-xs text-gray-500 dark:text-gray-400" %}

View file

@ -19,6 +19,7 @@ from django.db.models import (
TextField, TextField,
UUIDField, UUIDField,
) )
from django.templatetags.static import static
from django.utils import timezone from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -100,8 +101,11 @@ class User(AbstractUser, NiceModel): # type: ignore [django-manager-missing]
objects = UserManager() # type: ignore [misc, assignment] objects = UserManager() # type: ignore [misc, assignment]
@cached_property @cached_property
def avatar_url(self): def avatar_url(self) -> str:
return self.avatar.url try:
return self.avatar.url
except ValueError:
return static("person.png")
def add_to_recently_viewed(self, product_uuid): def add_to_recently_viewed(self, product_uuid):
recently_viewed = self.recently_viewed recently_viewed = self.recently_viewed