admin dashboard
This commit is contained in:
parent
121c77dd5f
commit
e35fea8cf0
Binary file not shown.
@ -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)
|
||||
|
||||
295
core/templates/admin/index.html
Normal file
295
core/templates/admin/index.html
Normal 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 %}
|
||||
BIN
core/templatetags/__pycache__/dashboard_stats.cpython-311.pyc
Normal file
BIN
core/templatetags/__pycache__/dashboard_stats.cpython-311.pyc
Normal file
Binary file not shown.
49
core/templatetags/dashboard_stats.py
Normal file
49
core/templatetags/dashboard_stats.py
Normal 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.
@ -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 "البحث عن شحنات"
|
||||
Loading…
x
Reference in New Issue
Block a user