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):
|
class CustomUserAdmin(UserAdmin):
|
||||||
inlines = (ProfileInline,)
|
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):
|
class ParcelAdmin(admin.ModelAdmin):
|
||||||
list_display = ('tracking_number', 'shipper', 'carrier', 'price', 'status', 'payment_status', 'created_at')
|
list_display = ('tracking_number', 'shipper', 'carrier', 'price', 'status', 'payment_status', 'created_at')
|
||||||
list_filter = ('status', 'payment_status', 'created_at')
|
list_filter = ('status', 'payment_status', 'created_at')
|
||||||
|
|||||||
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 ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \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"
|
"PO-Revision-Date: 2026-01-25 07:13+0000\n"
|
||||||
"Last-Translator: Gemini\n"
|
"Last-Translator: Gemini\n"
|
||||||
"Language-Team: Arabic\n"
|
"Language-Team: Arabic\n"
|
||||||
@ -22,46 +22,46 @@ msgstr ""
|
|||||||
msgid "Profiles"
|
msgid "Profiles"
|
||||||
msgstr "الملفات الشخصية"
|
msgstr "الملفات الشخصية"
|
||||||
|
|
||||||
#: core/admin.py:52
|
#: core/admin.py:57
|
||||||
msgid "Export Selected to CSV"
|
msgid "Export Selected to CSV"
|
||||||
msgstr "تصدير المحدد إلى CSV"
|
msgstr "تصدير المحدد إلى CSV"
|
||||||
|
|
||||||
#: core/admin.py:56
|
#: core/admin.py:61
|
||||||
msgid "General Info"
|
msgid "General Info"
|
||||||
msgstr "معلومات عامة"
|
msgstr "معلومات عامة"
|
||||||
|
|
||||||
#: core/admin.py:59
|
#: core/admin.py:64
|
||||||
msgid "Policies"
|
msgid "Policies"
|
||||||
msgstr "السياسات"
|
msgstr "السياسات"
|
||||||
|
|
||||||
#: core/admin.py:62
|
#: core/admin.py:67
|
||||||
msgid "Payment Configuration"
|
msgid "Payment Configuration"
|
||||||
msgstr "إعدادات الدفع"
|
msgstr "إعدادات الدفع"
|
||||||
|
|
||||||
#: core/admin.py:65
|
#: core/admin.py:70
|
||||||
msgid "WhatsApp Configuration (Wablas Gateway)"
|
msgid "WhatsApp Configuration (Wablas Gateway)"
|
||||||
msgstr "إعدادات واتساب (بوابة Wablas)"
|
msgstr "إعدادات واتساب (بوابة Wablas)"
|
||||||
|
|
||||||
#: core/admin.py:67
|
#: core/admin.py:72
|
||||||
msgid ""
|
msgid ""
|
||||||
"Configure your Wablas API connection. Use \"Test WhatsApp Configuration\" to "
|
"Configure your Wablas API connection. Use \"Test WhatsApp Configuration\" to "
|
||||||
"verify."
|
"verify."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"قم بتكوين اتصال Wablas API الخاص بك. استخدم \"اختبار إعدادات واتساب\" للتحقق."
|
"قم بتكوين اتصال Wablas API الخاص بك. استخدم \"اختبار إعدادات واتساب\" للتحقق."
|
||||||
|
|
||||||
#: core/admin.py:134
|
#: core/admin.py:139
|
||||||
msgid "Test WhatsApp"
|
msgid "Test WhatsApp"
|
||||||
msgstr "اختبار واتساب"
|
msgstr "اختبار واتساب"
|
||||||
|
|
||||||
#: core/admin.py:136
|
#: core/admin.py:141
|
||||||
msgid "Test Email"
|
msgid "Test Email"
|
||||||
msgstr "اختبار البريد الإلكتروني"
|
msgstr "اختبار البريد الإلكتروني"
|
||||||
|
|
||||||
#: core/admin.py:138
|
#: core/admin.py:143
|
||||||
msgid "Actions"
|
msgid "Actions"
|
||||||
msgstr "إجراءات"
|
msgstr "إجراءات"
|
||||||
|
|
||||||
#: core/admin.py:149
|
#: core/admin.py:154
|
||||||
msgid "Tools"
|
msgid "Tools"
|
||||||
msgstr "أدوات"
|
msgstr "أدوات"
|
||||||
|
|
||||||
@ -376,7 +376,7 @@ msgstr "حدث في"
|
|||||||
msgid "Parcel"
|
msgid "Parcel"
|
||||||
msgstr "طرد"
|
msgstr "طرد"
|
||||||
|
|
||||||
#: core/models.py:165
|
#: core/models.py:165 core/templates/admin/index.html:258
|
||||||
msgid "Parcels"
|
msgid "Parcels"
|
||||||
msgstr "طرود"
|
msgstr "طرود"
|
||||||
|
|
||||||
@ -506,6 +506,42 @@ msgstr "تقييمات السائق"
|
|||||||
msgid "Home"
|
msgid "Home"
|
||||||
msgstr "الرئيسية"
|
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
|
#: core/templates/base.html:9 core/templates/base.html:204
|
||||||
msgid "Small Shipments, Smart Delivery"
|
msgid "Small Shipments, Smart Delivery"
|
||||||
msgstr "شحنات صغيرة، توصيل ذكي"
|
msgstr "شحنات صغيرة، توصيل ذكي"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user