add shop dashboard

sync3
Razmig Sarkissian 5 months ago
parent 03cab14cf2
commit 38843a996a
  1. 115
      shop/admin.py
  2. 163
      shop/templates/admin/shop/dashboard.html
  3. 5
      shop/templates/admin/shop/order/change_list.html

@ -4,12 +4,72 @@ from django.utils.html import format_html
from django.urls import path
from django.http import HttpResponseRedirect
from django import forms
from django.db.models import Sum, Count, Avg
from datetime import datetime, timedelta
from django.utils import timezone
from .models import (
Product, Color, Size, Order, OrderItem, GuestUser, Coupon, CouponUsage,
OrderStatus, ShippingAddress
)
class ShopAdminSite(admin.AdminSite):
site_header = "Shop Administration"
site_title = "Shop Admin Portal"
index_title = "Welcome to Shop Administration"
def index(self, request, extra_context=None):
"""Custom admin index view with dashboard"""
# Calculate order statistics
order_status_data = []
total_orders = Order.objects.count()
total_revenue = Order.objects.aggregate(Sum('total_price'))['total_price__sum'] or 0
# Get data for each status
for status_choice in OrderStatus.choices:
status_code, status_label = status_choice
orders_for_status = Order.objects.filter(status=status_code)
count = orders_for_status.count()
total_amount = orders_for_status.aggregate(Sum('total_price'))['total_price__sum'] or 0
avg_order_value = orders_for_status.aggregate(Avg('total_price'))['total_price__avg'] or 0
percentage = (count / total_orders * 100) if total_orders > 0 else 0
order_status_data.append({
'status': status_code,
'label': status_label,
'count': count,
'total_amount': total_amount,
'avg_order_value': avg_order_value,
'percentage': percentage
})
# Recent activity calculations
now = timezone.now()
today = now.date()
week_ago = today - timedelta(days=7)
month_ago = today - timedelta(days=30)
orders_today = Order.objects.filter(date_ordered__date=today).count()
orders_this_week = Order.objects.filter(date_ordered__date__gte=week_ago).count()
orders_this_month = Order.objects.filter(date_ordered__date__gte=month_ago).count()
orders_to_prepare = Order.objects.filter(status=OrderStatus.PAID).count()
extra_context = extra_context or {}
extra_context.update({
'order_status_data': order_status_data,
'total_orders': total_orders,
'total_revenue': total_revenue,
'orders_today': orders_today,
'orders_this_week': orders_this_week,
'orders_this_month': orders_this_month,
'orders_to_prepare': orders_to_prepare,
})
return render(request, 'admin/shop/dashboard.html', extra_context)
# Create an instance of the custom admin site
shop_admin_site = ShopAdminSite(name='shop_admin')
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ("title", "ordering_value", "price", "cut")
@ -131,6 +191,57 @@ class OrderAdmin(admin.ModelAdmin):
}),
)
def dashboard_view(self, request):
"""Dashboard view with order statistics"""
# Calculate order statistics
order_status_data = []
total_orders = Order.objects.count()
total_revenue = Order.objects.aggregate(Sum('total_price'))['total_price__sum'] or 0
# Get data for each status
for status_choice in OrderStatus.choices:
status_code, status_label = status_choice
orders_for_status = Order.objects.filter(status=status_code)
count = orders_for_status.count()
total_amount = orders_for_status.aggregate(Sum('total_price'))['total_price__sum'] or 0
avg_order_value = orders_for_status.aggregate(Avg('total_price'))['total_price__avg'] or 0
percentage = (count / total_orders * 100) if total_orders > 0 else 0
order_status_data.append({
'status': status_code,
'label': status_label,
'count': count,
'total_amount': total_amount,
'avg_order_value': avg_order_value,
'percentage': percentage
})
# Recent activity calculations
now = timezone.now()
today = now.date()
week_ago = today - timedelta(days=7)
month_ago = today - timedelta(days=30)
orders_today = Order.objects.filter(date_ordered__date=today).count()
orders_this_week = Order.objects.filter(date_ordered__date__gte=week_ago).count()
orders_this_month = Order.objects.filter(date_ordered__date__gte=month_ago).count()
orders_to_prepare = Order.objects.filter(status=OrderStatus.PAID).count()
context = {
'title': 'Shop Dashboard',
'app_label': 'shop',
'opts': Order._meta,
'order_status_data': order_status_data,
'total_orders': total_orders,
'total_revenue': total_revenue,
'orders_today': orders_today,
'orders_this_week': orders_this_week,
'orders_this_month': orders_this_month,
'orders_to_prepare': orders_to_prepare,
}
return render(request, 'admin/shop/dashboard.html', context)
def changelist_view(self, request, extra_context=None):
# If 'show_preparation' parameter is in the request, show the preparation view
if 'show_preparation' in request.GET:
@ -194,9 +305,7 @@ class OrderAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super().get_urls()
custom_urls = [
path('prepare-all-orders/', self.admin_site.admin_view(self.prepare_all_orders), name='prepare_all_orders'),
path('prepare-order/<int:order_id>/', self.admin_site.admin_view(self.prepare_order), name='prepare_order'),
path('cancel-and-refund-order/<int:order_id>/', self.admin_site.admin_view(self.cancel_and_refund_order), name='cancel_and_refund_order'),
path('dashboard/', self.admin_site.admin_view(self.dashboard_view), name='shop_order_dashboard'),
]
return custom_urls + urls

@ -0,0 +1,163 @@
{% extends "admin/base_site.html" %}
{% load admin_urls %}
{% block title %}Shop Dashboard{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">Home</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label='shop' %}">Shop</a>
&rsaquo; Dashboard
</div>
{% endblock %}
{% block content %}
<div class="dashboard">
<div class="dashboard-stats" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin: 20px 0;">
<!-- Order Status Cards -->
<div class="stat-card" style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px;">
<h3 style="margin: 0 0 15px 0; color: #495057;">Orders by Status</h3>
<div class="status-list">
{% for status_data in order_status_data %}
<div class="status-item" style="display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #e9ecef;">
<span style="font-weight: 500; color: #495057;">{{ status_data.label }}</span>
<div style="display: flex; align-items: center; gap: 10px;">
<span class="count" style="background: #007bff; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: bold;">
{{ status_data.count }}
</span>
<span class="total" style="color: #28a745; font-weight: 500;">
€{{ status_data.total_amount|floatformat:2 }}
</span>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Total Summary Card -->
<div class="stat-card" style="background: linear-gradient(135deg, #007bff, #0056b3); color: white; border-radius: 8px; padding: 20px;">
<h3 style="margin: 0 0 15px 0;">Total Summary</h3>
<div class="summary-stats">
<div style="margin-bottom: 10px;">
<div style="font-size: 24px; font-weight: bold;">{{ total_orders }}</div>
<div style="opacity: 0.9;">Total Orders</div>
</div>
<div>
<div style="font-size: 24px; font-weight: bold;">€{{ total_revenue|floatformat:2 }}</div>
<div style="opacity: 0.9;">Total Revenue</div>
</div>
</div>
</div>
<!-- Recent Activity Card -->
<div class="stat-card" style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px;">
<h3 style="margin: 0 0 15px 0; color: #495057;">Recent Activity</h3>
<div class="recent-stats">
<div style="margin-bottom: 10px;">
<div style="font-size: 18px; font-weight: bold; color: #28a745;">{{ orders_today }}</div>
<div style="color: #6c757d; font-size: 14px;">Orders Today</div>
</div>
<div style="margin-bottom: 10px;">
<div style="font-size: 18px; font-weight: bold; color: #ffc107;">{{ orders_this_week }}</div>
<div style="color: #6c757d; font-size: 14px;">Orders This Week</div>
</div>
<div>
<div style="font-size: 18px; font-weight: bold; color: #17a2b8;">{{ orders_this_month }}</div>
<div style="color: #6c757d; font-size: 14px;">Orders This Month</div>
</div>
</div>
</div>
<!-- Quick Actions Card -->
<div class="stat-card" style="background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px;">
<h3 style="margin: 0 0 15px 0; color: #495057;">Quick Actions</h3>
<div class="quick-actions">
<a href="{% url 'admin:shop_order_changelist' %}"
style="display: block; padding: 8px 12px; margin-bottom: 8px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; text-align: center;">
View All Orders
</a>
<a href="{% url 'admin:shop_order_changelist' %}?show_preparation=1"
style="display: block; padding: 8px 12px; margin-bottom: 8px; background: #28a745; color: white; text-decoration: none; border-radius: 4px; text-align: center;">
Orders to Prepare ({{ orders_to_prepare }})
</a>
<a href="{% url 'admin:shop_product_changelist' %}"
style="display: block; padding: 8px 12px; margin-bottom: 8px; background: #6c757d; color: white; text-decoration: none; border-radius: 4px; text-align: center;">
Manage Products
</a>
<a href="{% url 'admin:shop_coupon_changelist' %}"
style="display: block; padding: 8px 12px; background: #ffc107; color: #212529; text-decoration: none; border-radius: 4px; text-align: center;">
Manage Coupons
</a>
</div>
</div>
</div>
<!-- Detailed Status Breakdown -->
<div class="detailed-breakdown" style="background: white; border: 1px solid #dee2e6; border-radius: 8px; padding: 20px; margin-top: 20px;">
<h3 style="margin: 0 0 20px 0; color: #495057;">Status Breakdown</h3>
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="background: #f8f9fa;">
<th style="padding: 12px; text-align: left; border-bottom: 2px solid #dee2e6;">Status</th>
<th style="padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;">Count</th>
<th style="padding: 12px; text-align: center; border-bottom: 2px solid #dee2e6;">Percentage</th>
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #dee2e6;">Total Value</th>
<th style="padding: 12px; text-align: right; border-bottom: 2px solid #dee2e6;">Avg Order Value</th>
</tr>
</thead>
<tbody>
{% for status_data in order_status_data %}
<tr style="border-bottom: 1px solid #dee2e6;">
<td style="padding: 12px; font-weight: 500;">{{ status_data.label }}</td>
<td style="padding: 12px; text-align: center;">
<span style="background: #007bff; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px;">
{{ status_data.count }}
</span>
</td>
<td style="padding: 12px; text-align: center;">{{ status_data.percentage|floatformat:1 }}%</td>
<td style="padding: 12px; text-align: right; color: #28a745; font-weight: 500;">
€{{ status_data.total_amount|floatformat:2 }}
</td>
<td style="padding: 12px; text-align: right;">
{% if status_data.count > 0 %}
€{{ status_data.avg_order_value|floatformat:2 }}
{% else %}
€0.00
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<style>
.dashboard {
max-width: 1200px;
margin: 0 auto;
}
.stat-card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.quick-actions a:hover {
opacity: 0.9;
}
@media (max-width: 768px) {
.dashboard-stats {
grid-template-columns: 1fr !important;
}
}
</style>
{% endblock %}

@ -2,6 +2,11 @@
{% block object-tools %}
<ul class="object-tools">
<li>
<a href="dashboard/" class="viewlink" style="background: #28a745; color: white;">
📊 Dashboard
</a>
</li>
<li>
<a href="?show_preparation=1" class="viewlink">
Orders to Prepare ({{ paid_orders_count }})

Loading…
Cancel
Save