Fixes: 1) Fixed Hebrew translation line breaks and spacing inconsistencies; 2) Corrected punctuation and formatting in user reset password email template; 3) Fixed line breaks in model docstring for proper formatting. Extra: 1) Updated POT-Creation-Date to reflect latest commit; 2) Minor whitespace and punctuation fixes across multiple translation strings.
413 lines
No EOL
27 KiB
HTML
413 lines
No EOL
27 KiB
HTML
{% extends 'admin/base.html' %}
|
|
{% load i18n unfold static arith %}
|
|
|
|
{% block title %}
|
|
{% if subtitle %}
|
|
{{ subtitle }} |
|
|
{% endif %}
|
|
|
|
{{ title }} | {{ site_title|default:_('Django site admin') }}
|
|
{% endblock %}
|
|
|
|
{% block branding %}
|
|
{% include "unfold/helpers/site_branding.html" %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
{% component "unfold/components/container.html" %}
|
|
<div class="flex flex-col min-h-screen">
|
|
{% component "unfold/components/title.html" %}
|
|
{% trans "Dashboard" %}
|
|
<br/>
|
|
{% endcomponent %}
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4 mb-6">
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "Revenue (gross, 30d)" %}
|
|
{% endcomponent %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ revenue_gross_30|default:0 }}
|
|
{% endcomponent %}
|
|
{% endcomponent %}
|
|
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "Revenue (net, 30d)" %}
|
|
{% endcomponent %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ revenue_net_30|default:0 }}
|
|
{% endcomponent %}
|
|
{% endcomponent %}
|
|
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "Returns (30d)" %}
|
|
{% endcomponent %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% if currency_symbol %}{{ currency_symbol }}{% endif %}{{ returns_30|default:0 }}
|
|
{% endcomponent %}
|
|
{% endcomponent %}
|
|
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "Processed orders (30d)" %}
|
|
{% endcomponent %}
|
|
{% component "unfold/components/title.html" %}
|
|
{{ processed_orders_30|default:0 }}
|
|
{% endcomponent %}
|
|
{% endcomponent %}
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 xl:grid-cols-3 gap-6 items-start">
|
|
{% with gross=revenue_gross_30|default:0 returns=returns_30|default:0 %}
|
|
{% with total=gross|add:returns %}
|
|
{% component "unfold/components/card.html" with class="xl:col-span-2" %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% trans "Income overview" %}
|
|
{% endcomponent %}
|
|
{% if total and total > 0 %}
|
|
{% with net=revenue_net_30|default:0 %}
|
|
{% with tax_amt=gross|sub:net %}
|
|
{% with returns_capped=returns %}
|
|
{% if returns > gross %}
|
|
{% with returns_capped=gross %}{% endwith %}
|
|
{% endif %}
|
|
{% with tax_amt_pos=tax_amt %}
|
|
{% if tax_amt_pos < 0 %}
|
|
{% with tax_amt_pos=0 %}{% endwith %}
|
|
{% endif %}
|
|
{% with net_for_pie=gross|sub:tax_amt_pos|sub:returns_capped %}
|
|
{% if net_for_pie < 0 %}
|
|
{% with net_for_pie=0 %}{% endwith %}
|
|
{% endif %}
|
|
{% widthratio net_for_pie gross 360 as net_deg %}
|
|
{% widthratio tax_amt_pos gross 360 as tax_deg %}
|
|
{% widthratio returns_capped gross 360 as ret_deg %}
|
|
{% with net_end=net_deg %}
|
|
{% with tax_end=net_end|add:tax_deg %}
|
|
{% with ret_end=tax_end|add:ret_deg %}
|
|
<div class="flex flex-col sm:flex-row items-center gap-6">
|
|
<div class="relative w-48 h-48">
|
|
<canvas id="incomePieChart" width="192"
|
|
height="192"></canvas>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<span class="inline-block w-3 h-3 rounded-sm"
|
|
style="background:rgb(34,197,94)"></span>
|
|
<span class="text-sm text-gray-600 dark:text-gray-300">{% trans "Net" %}:</span>
|
|
<span class="font-semibold">{% if currency_symbol %}
|
|
{{ currency_symbol }}{% endif %}{{ net }}</span>
|
|
</div>
|
|
{% if tax_amt_pos > 0 %}
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<span class="inline-block w-3 h-3 rounded-sm"
|
|
style="background:rgb(249,115,22)"></span>
|
|
<span class="text-sm text-gray-600 dark:text-gray-300">{% trans "Taxes" %}:</span>
|
|
<span class="font-semibold">{% if currency_symbol %}
|
|
{{ currency_symbol }}{% endif %}{{ tax_amt_pos|floatformat:2 }}</span>
|
|
</div>
|
|
{% endif %}
|
|
<div class="flex items-center gap-2 mb-2">
|
|
<span class="inline-block w-3 h-3 rounded-sm"
|
|
style="background:rgb(239,68,68)"></span>
|
|
<span class="text-sm text-gray-600 dark:text-gray-300">{% trans "Returns" %}:</span>
|
|
<span class="font-semibold">{% if currency_symbol %}
|
|
{{ currency_symbol }}{% endif %}{{ returns }}</span>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="inline-block w-3 h-3 rounded-sm"
|
|
style="background:linear-gradient(90deg, rgba(0,0,0,0.15), rgba(0,0,0,0.15))"></span>
|
|
<span class="text-sm text-gray-600 dark:text-gray-300">{% trans "Gross (pie total)" %}:</span>
|
|
<span class="font-semibold">{% if currency_symbol %}
|
|
{{ currency_symbol }}{% endif %}{{ gross }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="{% static 'js/chart.js' %}"></script>
|
|
<script>
|
|
(function () {
|
|
try {
|
|
const ctx = document.getElementById('incomePieChart').getContext('2d');
|
|
const dataValues = [
|
|
{{ net_for_pie|floatformat:2 }},
|
|
{{ tax_amt_pos|floatformat:2 }},
|
|
{{ returns_capped|floatformat:2 }}
|
|
];
|
|
const labels = [
|
|
'{{ _("Net") }}',
|
|
'{{ _("Taxes") }}',
|
|
'{{ _("Returns") }}'
|
|
];
|
|
|
|
const colors = [
|
|
'rgb(34,197,94)',
|
|
'rgb(249,115,22)',
|
|
'rgb(239,68,68)'
|
|
];
|
|
|
|
const hasData = dataValues.some(function (v) {
|
|
return Number(v) > 0;
|
|
});
|
|
if (!hasData) {
|
|
return;
|
|
}
|
|
|
|
const currency = "{% if currency_symbol %}{{ currency_symbol }}{% endif %}";
|
|
new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
data: dataValues,
|
|
backgroundColor: colors,
|
|
borderWidth: 0,
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {position: 'bottom'},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function (context) {
|
|
const label = context.label || '';
|
|
const val = context.parsed || 0;
|
|
return `${label}: ${currency}${val}`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
cutout: '55%'
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console && console.warn && console.warn('Pie chart init failed', e);
|
|
}
|
|
})();
|
|
</script>
|
|
{% endwith %}
|
|
{% endwith %}
|
|
{% endwith %}
|
|
{% endwith %}
|
|
{% endwith %}
|
|
{% endwith %}
|
|
{% endwith %}
|
|
{% endwith %}
|
|
{% else %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "Not enough data for chart yet." %}
|
|
{% endcomponent %}
|
|
{% endif %}
|
|
{% endcomponent %}
|
|
{% endwith %}
|
|
{% endwith %}
|
|
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% trans "Quick Links" %}
|
|
{% endcomponent %}
|
|
{% if quick_links %}
|
|
{% component "unfold/components/navigation.html" with class="flex flex-col gap-1" items=quick_links %}
|
|
{% endcomponent %}
|
|
{% else %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "No links available." %}
|
|
{% endcomponent %}
|
|
{% endif %}
|
|
{% endcomponent %}
|
|
</div>
|
|
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% trans "Daily sales (30d)" %}
|
|
{% endcomponent %}
|
|
{% if daily_labels and daily_labels|length > 0 %}
|
|
<div class="w-full">
|
|
<canvas id="dailySalesChart" height="120"></canvas>
|
|
</div>
|
|
<script src="{% static 'js/chart.js' %}"></script>
|
|
{{ daily_labels|json_script:"daily-labels" }}
|
|
{{ daily_orders|json_script:"daily-orders" }}
|
|
{{ daily_gross|json_script:"daily-gross" }}
|
|
<script>
|
|
(function () {
|
|
try {
|
|
const labels = JSON.parse(document.getElementById('daily-labels').textContent);
|
|
const orders = JSON.parse(document.getElementById('daily-orders').textContent);
|
|
const gross = JSON.parse(document.getElementById('daily-gross').textContent);
|
|
|
|
const ctx = document.getElementById('dailySalesChart').getContext('2d');
|
|
const green = 'rgb(34,197,94)';
|
|
const blue = 'rgb(59,130,246)';
|
|
|
|
const currency = "{% if currency_symbol %}{{ currency_symbol }}{% endif %}";
|
|
|
|
new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: '{{ _("Orders (FINISHED)") }}',
|
|
data: orders,
|
|
borderColor: green,
|
|
backgroundColor: green,
|
|
borderWidth: 2,
|
|
tension: 0.25,
|
|
pointRadius: 2,
|
|
yAxisID: 'yOrders',
|
|
},
|
|
{
|
|
label: '{{ _("Gross revenue") }}',
|
|
data: gross,
|
|
borderColor: blue,
|
|
backgroundColor: blue,
|
|
borderWidth: 2,
|
|
tension: 0.25,
|
|
pointRadius: 2,
|
|
yAxisID: 'yRevenue',
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {mode: 'index', intersect: false},
|
|
plugins: {
|
|
legend: {display: true},
|
|
tooltip: {
|
|
callbacks: {
|
|
label: function (context) {
|
|
const label = context.dataset.label || '';
|
|
const val = context.parsed.y;
|
|
if (context.dataset.yAxisID === 'yRevenue') {
|
|
return `${label}: ${currency}${val}`;
|
|
}
|
|
return `${label}: ${val}`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: {display: false}
|
|
},
|
|
yOrders: {
|
|
type: 'linear',
|
|
position: 'left',
|
|
title: {display: true, text: '{{ _("Orders") }}'},
|
|
grid: {color: 'rgba(0,0,0,0.06)'},
|
|
ticks: {precision: 0}
|
|
},
|
|
yRevenue: {
|
|
type: 'linear',
|
|
position: 'right',
|
|
title: {display: true, text: '{{ _("Gross") }}'},
|
|
grid: {drawOnChartArea: false},
|
|
ticks: {
|
|
callback: function (value) {
|
|
return `${currency}${value}`;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console && console.warn && console.warn('Chart init failed', e);
|
|
}
|
|
})();
|
|
</script>
|
|
{% else %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "Not enough data for chart yet." %}
|
|
{% endcomponent %}
|
|
{% endif %}
|
|
{% endcomponent %}
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% trans "Most wished product" %}
|
|
{% endcomponent %}
|
|
{% 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 %}
|
|
{% endif %}
|
|
{% endcomponent %}
|
|
|
|
{% component "unfold/components/card.html" %}
|
|
{% component "unfold/components/title.html" %}
|
|
{% trans "Most popular product" %}
|
|
{% endcomponent %}
|
|
{% 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 %}
|
|
{% component "unfold/components/text.html" %}
|
|
{% trans "No data yet." %}
|
|
{% endcomponent %}
|
|
{% endif %}
|
|
{% endcomponent %}
|
|
</div>
|
|
|
|
|
|
{% component "unfold/components/separator.html" %}
|
|
{% endcomponent %}
|
|
|
|
<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 %} |