admin dashboard

This commit is contained in:
Flatlogic Bot 2026-01-26 05:25:39 +00:00
parent 121c77dd5f
commit e35fea8cf0
7 changed files with 399 additions and 14 deletions

View File

@ -23,6 +23,11 @@ class ProfileInline(admin.StackedInline):
class CustomUserAdmin(UserAdmin):
inlines = (ProfileInline,)
def get_inline_instances(self, request, obj=None):
if not obj:
return list()
return super(CustomUserAdmin, self).get_inline_instances(request, obj)
class ParcelAdmin(admin.ModelAdmin):
list_display = ('tracking_number', 'shipper', 'carrier', 'price', 'status', 'payment_status', 'created_at')
list_filter = ('status', 'payment_status', 'created_at')
@ -166,4 +171,4 @@ admin.site.register(Country, CountryAdmin)
admin.site.register(Governate)
admin.site.register(City)
admin.site.register(PlatformProfile, PlatformProfileAdmin)
admin.site.register(Testimonial, TestimonialAdmin)
admin.site.register(Testimonial, TestimonialAdmin)

View File

@ -0,0 +1,295 @@
{% extends "admin/index.html" %}
{% load i18n static dashboard_stats %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.css" integrity="sha512-/zs32ZEJh+/EO2N1b0PEdoA10JkdC3zJ8L5FTiQu82LR9S/rOQNfQN7U59U9BC12swNeRAz3HSzIL2vpp4fv3w==" crossorigin="anonymous" />
<style>
:root {
--primary-color: #4f46e5;
--secondary-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--text-dark: #1f2937;
--text-light: #6b7280;
--bg-card: #ffffff;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.dashboard-container {
margin-bottom: 30px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--bg-card);
padding: 20px;
border-radius: 12px;
box-shadow: var(--shadow);
border: 1px solid #e5e7eb;
transition: transform 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
}
.stat-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.stat-title {
font-size: 0.875rem;
font-weight: 600;
color: var(--text-light);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.stat-value {
font-size: 1.875rem;
font-weight: 700;
color: var(--text-dark);
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.25rem;
}
.icon-revenue { background: #d1fae5; color: #059669; }
.icon-parcels { background: #dbeafe; color: #2563eb; }
.icon-drivers { background: #fef3c7; color: #d97706; }
.icon-pending { background: #fee2e2; color: #dc2626; }
.charts-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
@media (max-width: 1024px) {
.charts-grid {
grid-template-columns: 1fr;
}
}
.chart-container, .recent-activity {
background: var(--bg-card);
padding: 20px;
border-radius: 12px;
box-shadow: var(--shadow);
border: 1px solid #e5e7eb;
}
.section-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-dark);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #f3f4f6;
}
.activity-list {
list-style: none;
padding: 0;
margin: 0;
}
.activity-item {
display: flex;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f3f4f6;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-avatar {
width: 36px;
height: 36px;
background: #e5e7eb;
border-radius: 50%;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: bold;
color: var(--text-light);
}
.activity-details {
flex: 1;
}
.activity-text {
font-size: 0.9rem;
color: var(--text-dark);
font-weight: 500;
}
.activity-subtext {
font-size: 0.75rem;
color: var(--text-light);
}
.status-badge {
padding: 4px 8px;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 600;
}
.status-delivered { background: #d1fae5; color: #065f46; }
.status-pending { background: #fef3c7; color: #92400e; }
.status-cancelled { background: #fee2e2; color: #b91c1c; }
</style>
{% endblock %}
{% block content %}
{% get_dashboard_stats as stats %}
<div class="dashboard-container">
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">{% trans "Total Revenue" %}</span>
<div class="stat-icon icon-revenue">💰</div>
</div>
<div class="stat-value">{{ stats.total_revenue|floatform:2 }} <span style="font-size: 1rem; color: #6b7280;">OMR</span></div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">{% trans "Total Parcels" %}</span>
<div class="stat-icon icon-parcels">📦</div>
</div>
<div class="stat-value">{{ stats.total_parcels }}</div>
<div style="font-size: 0.8rem; color: #6b7280; margin-top: 5px;">
{{ stats.delivered_parcels }} Delivered
</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">{% trans "Active Drivers" %}</span>
<div class="stat-icon icon-drivers">🚚</div>
</div>
<div class="stat-value">{{ stats.drivers_count }}</div>
</div>
<div class="stat-card">
<div class="stat-header">
<span class="stat-title">{% trans "Pending Orders" %}</span>
<div class="stat-icon icon-pending"></div>
</div>
<div class="stat-value">{{ stats.pending_parcels }}</div>
</div>
</div>
<!-- Charts & Activity -->
<div class="charts-grid">
<div class="chart-container">
<h3 class="section-title">{% trans "Shipments Overview (Last 7 Days)" %}</h3>
<canvas id="shipmentsChart" height="150"></canvas>
</div>
<div class="recent-activity">
<h3 class="section-title">{% trans "Recent Activity" %}</h3>
<ul class="activity-list">
{% for parcel in stats.recent_parcels %}
<li class="activity-item">
<div class="activity-avatar">
{{ parcel.shipper.username|slice:":1"|upper }}
</div>
<div class="activity-details">
<div class="activity-text">{% trans "New Parcel" %} #{{ parcel.tracking_number|slice:":8" }}...</div>
<div class="activity-subtext">{{ parcel.created_at|timesince }} {% trans "ago" %}</div>
</div>
<span class="status-badge status-{{ parcel.status }}">
{{ parcel.get_status_display }}
</span>
</li>
{% empty %}
<li class="activity-item">{% trans "No recent activity." %}</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<!-- Standard Admin Index Content -->
{{ block.super }}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js" integrity="sha512-d9xgZrVZpmmQlfonhQUvTR7lMPtO7NkZMkA0ABN3PHCbKA5nqylQ/yWlFAyY6hYgdF1Qh6nYiuADWwKB4C2WSw==" crossorigin="anonymous"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var ctx = document.getElementById('shipmentsChart').getContext('2d');
var myChart = new Chart(ctx, {
type: 'line',
data: {
labels: {{ stats.chart_labels|safe }},
datasets: [{
label: '{% trans "Parcels" %}',
data: {{ stats.chart_data|safe }},
backgroundColor: 'rgba(79, 70, 229, 0.1)',
borderColor: '#4f46e5',
borderWidth: 2,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#4f46e5',
pointRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
yAxes: [{
ticks: {
beginAtZero: true,
stepSize: 1
},
gridLines: {
display: false
}
}],
xAxes: [{
gridLines: {
color: '#f3f4f6'
}
}]
},
legend: {
display: false
}
}
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,49 @@
from django import template
from django.contrib.auth.models import User
from core.models import Parcel, Profile
from django.db.models import Sum, Count
from django.utils import timezone
from datetime import timedelta
import json
register = template.Library()
@register.simple_tag
def get_dashboard_stats():
# User Stats
total_users = User.objects.count()
drivers_count = Profile.objects.filter(role='driver').count()
shippers_count = Profile.objects.filter(role='shipper').count()
# Parcel Stats
total_parcels = Parcel.objects.count()
delivered_parcels = Parcel.objects.filter(status='delivered').count()
pending_parcels = Parcel.objects.filter(status='pending').count()
# Financials
total_revenue = Parcel.objects.filter(status='delivered').aggregate(Sum('price'))['price__sum'] or 0
# Recent Activity
recent_parcels = Parcel.objects.select_related('shipper', 'shipper__profile').order_by('-created_at')[:5]
# Chart Data (Last 7 Days)
today = timezone.now().date()
dates = [(today - timedelta(days=i)).strftime('%Y-%m-%d') for i in range(6, -1, -1)]
daily_counts = []
for date_str in dates:
count = Parcel.objects.filter(created_at__date=date_str).count()
daily_counts.append(count)
return {
'total_users': total_users,
'drivers_count': drivers_count,
'shippers_count': shippers_count,
'total_parcels': total_parcels,
'delivered_parcels': delivered_parcels,
'pending_parcels': pending_parcels,
'total_revenue': total_revenue,
'recent_parcels': recent_parcels,
'chart_labels': json.dumps(dates),
'chart_data': json.dumps(daily_counts),
}

Binary file not shown.

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 05:03+0000\n"
"POT-Creation-Date: 2026-01-26 05:21+0000\n"
"PO-Revision-Date: 2026-01-25 07:13+0000\n"
"Last-Translator: Gemini\n"
"Language-Team: Arabic\n"
@ -22,46 +22,46 @@ msgstr ""
msgid "Profiles"
msgstr "الملفات الشخصية"
#: core/admin.py:52
#: core/admin.py:57
msgid "Export Selected to CSV"
msgstr "تصدير المحدد إلى CSV"
#: core/admin.py:56
#: core/admin.py:61
msgid "General Info"
msgstr "معلومات عامة"
#: core/admin.py:59
#: core/admin.py:64
msgid "Policies"
msgstr "السياسات"
#: core/admin.py:62
#: core/admin.py:67
msgid "Payment Configuration"
msgstr "إعدادات الدفع"
#: core/admin.py:65
#: core/admin.py:70
msgid "WhatsApp Configuration (Wablas Gateway)"
msgstr "إعدادات واتساب (بوابة Wablas)"
#: core/admin.py:67
#: core/admin.py:72
msgid ""
"Configure your Wablas API connection. Use \"Test WhatsApp Configuration\" to "
"verify."
msgstr ""
"قم بتكوين اتصال Wablas API الخاص بك. استخدم \"اختبار إعدادات واتساب\" للتحقق."
#: core/admin.py:134
#: core/admin.py:139
msgid "Test WhatsApp"
msgstr "اختبار واتساب"
#: core/admin.py:136
#: core/admin.py:141
msgid "Test Email"
msgstr "اختبار البريد الإلكتروني"
#: core/admin.py:138
#: core/admin.py:143
msgid "Actions"
msgstr "إجراءات"
#: core/admin.py:149
#: core/admin.py:154
msgid "Tools"
msgstr "أدوات"
@ -376,7 +376,7 @@ msgstr "حدث في"
msgid "Parcel"
msgstr "طرد"
#: core/models.py:165
#: core/models.py:165 core/templates/admin/index.html:258
msgid "Parcels"
msgstr "طرود"
@ -506,6 +506,42 @@ msgstr "تقييمات السائق"
msgid "Home"
msgstr "الرئيسية"
#: core/templates/admin/index.html:180
msgid "Total Revenue"
msgstr "إجمالي الإيرادات"
#: core/templates/admin/index.html:188
msgid "Total Parcels"
msgstr "إجمالي الطرود"
#: core/templates/admin/index.html:199
msgid "Active Drivers"
msgstr "السائقون النشطون"
#: core/templates/admin/index.html:207
msgid "Pending Orders"
msgstr "طلبات قيد الانتظار"
#: core/templates/admin/index.html:217
msgid "Shipments Overview (Last 7 Days)"
msgstr "نظرة عامة على الشحنات (آخر 7 أيام)"
#: core/templates/admin/index.html:222
msgid "Recent Activity"
msgstr "النشاط الأخير"
#: core/templates/admin/index.html:230
msgid "New Parcel"
msgstr "طرد جديد"
#: core/templates/admin/index.html:231
msgid "ago"
msgstr "مضت"
#: core/templates/admin/index.html:238
msgid "No recent activity."
msgstr "لا يوجد نشاط حديث."
#: core/templates/base.html:9 core/templates/base.html:204
msgid "Small Shipments, Smart Delivery"
msgstr "شحنات صغيرة، توصيل ذكي"
@ -1279,4 +1315,4 @@ msgstr "شكراً لملاحظاتك!"
#~ msgstr "لم ترسل أي شحنات بعد."
#~ msgid "Find Loads"
#~ msgstr "البحث عن شحنات"
#~ msgstr "البحث عن شحنات"