aging inventory logics, user list page and password set page ui change
This commit is contained in:
parent
d0ac8eba9a
commit
589417ca6d
@ -42,14 +42,16 @@ urlpatterns = [
|
||||
),
|
||||
|
||||
|
||||
#dashboards
|
||||
path("dashboards/dealer/", views.DealerDashboard,name="dealer_dashboard"),
|
||||
path( "dashboards/manager/", views.ManagerDashboard.as_view(),name="manager_dashboard"),
|
||||
path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"),
|
||||
path("dashboards/accountant/", views.AccountantDashboard.as_view(), name="accountant_dashboard"),
|
||||
path("dashboards/inventory/", views.InventoryDashboard.as_view(), name="inventory_dashboard"),
|
||||
|
||||
#dashboards for manager, dealer, inventory and accounatant
|
||||
path("dashboards/<slug:dealer_slug>/general/", views.general_dashboard,name="general_dashboard"),
|
||||
#dashboard for sales
|
||||
path("dashboards/<slug:dealer_slug>/sales/", views.sales_dashboard, name="sales_dashboard"),
|
||||
|
||||
path(
|
||||
"<slug:dealer_slug>/cars/aging-inventory/list",
|
||||
views.aging_inventory_list_view,
|
||||
name="aging_inventory_list",
|
||||
),
|
||||
path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"),
|
||||
path("export/format/", TableExport.export, name="export"),
|
||||
# Dealer URLs
|
||||
|
||||
@ -52,6 +52,7 @@ from django.forms import CharField, HiddenInput, ValidationError
|
||||
from django.shortcuts import HttpResponse
|
||||
|
||||
from django.db.models import Sum, F, Count
|
||||
from django.db.models.functions import ExtractMonth
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.db.models import Value
|
||||
@ -396,522 +397,426 @@ class TestView(TemplateView):
|
||||
|
||||
template_name = "inventory/cars_list_api.html"
|
||||
|
||||
# class DealerDashboard(LoginRequiredMixin, TemplateView):
|
||||
# """
|
||||
# ManagerDashboard class is a view handling the dashboard for a manager.
|
||||
|
||||
# Provides functionality to manage and view various statistics and data specific
|
||||
# to the dealer associated with the authenticated manager. It uses a specific
|
||||
# template and ensures authentication before granting access. The class
|
||||
# aggregates data about cars, leads, financial statistics, and other related
|
||||
# business information for display in the manager's dashboard.
|
||||
|
||||
# :ivar template_name: Path to the template used for rendering the manager's dashboard.
|
||||
# :type template_name: str
|
||||
# """
|
||||
|
||||
# template_name = "dashboards/dealer_dashbaord.html"
|
||||
|
||||
# # def dispatch(self, request, *args, **kwargs):
|
||||
# # if not request.user.is_authenticated:
|
||||
# # return redirect("welcome")
|
||||
# # if not getattr(request.user, "dealer", False):
|
||||
# # return HttpResponseForbidden(
|
||||
# # "You are not authorized to view this dashboard."
|
||||
# # )
|
||||
# # return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# dealer = get_user_type(self.request)
|
||||
# entity = dealer.entity
|
||||
|
||||
# qs = models.Car.objects.filter(dealer=dealer)
|
||||
# total_cars = qs.count()
|
||||
|
||||
# # stats = models.CarFinance.objects.filter(car__dealer=dealer).aggregate(
|
||||
# # total_cost_price=Sum("cost_price"),
|
||||
# # total_selling_price=Sum("selling_price"),
|
||||
# # )
|
||||
# # total_cost_price = stats["total_cost_price"] or 0
|
||||
# # total_selling_price = stats["total_selling_price"] or 0
|
||||
# # total_profit = total_selling_price - total_cost_price
|
||||
|
||||
# # new_leads = models.Lead.objects.filter(
|
||||
# # dealer=dealer, status=models.Status.NEW
|
||||
# # ).count()
|
||||
# # pending_leads = models.Lead.objects.filter(
|
||||
# # dealer=dealer, status=models.Status.CONTACTED
|
||||
# # ).count()
|
||||
# # canceled_leads = models.Lead.objects.filter(
|
||||
# # dealer=dealer, status=models.Status.UNQUALIFIED
|
||||
# # ).count()
|
||||
|
||||
# # car_status_qs = qs.values("status").annotate(count=Count("status"))
|
||||
# # car_status = {status: count for status, count in car_status_qs}
|
||||
|
||||
# # car_by_make_qs = qs.values("id_car_make__name").annotate(count=Count("id"))
|
||||
# # car_by_make = list(car_by_make_qs)
|
||||
|
||||
# # context["dealer"] = dealer
|
||||
# # context["total_activity"] = models.UserActivityLog.objects.filter(
|
||||
# # user=dealer.user
|
||||
# # ).count()
|
||||
# # context["total_cars"] = total_cars
|
||||
# # context["total_reservations"] = models.CarReservation.objects.filter(
|
||||
# # reserved_until__gte=timezone.now()
|
||||
# # ).count()
|
||||
# # context["total_cost_price"] = total_cost_price
|
||||
# # context["total_selling_price"] = total_selling_price
|
||||
# # context["total_profit"] = total_profit
|
||||
# # context["new_leads"] = new_leads
|
||||
# # context["pending_leads"] = pending_leads
|
||||
# # context["canceled_leads"] = canceled_leads
|
||||
# # context["car"] = json.dumps(car_by_make)
|
||||
|
||||
|
||||
# # context["available_cars"] =qs.filter(status='available').count()
|
||||
# # context["sold_cars"] = qs.filter(status='sold').count()
|
||||
# # context["reserved_cars"] = qs.filter(status='reserved').count()
|
||||
# # context["hold_cars"] =qs.filter(status='hold').count()
|
||||
# # context["damaged_cars"] = qs.filter(status='damaged').count()
|
||||
# # context["transfer_cars"] = qs.filter(status='transfer').count()
|
||||
# # context["present_inventory_count"]=total_cars-context["sold_cars"]-context["damaged_cars"]
|
||||
# # cars_sold=qs.filter(status='sold')
|
||||
# # cars_sold.aggregate(total_inventory_value=sum())
|
||||
|
||||
|
||||
|
||||
# context["customers"] = entity.get_customers().count()
|
||||
# context["staff"] = models.Staff.objects.filter(dealer=dealer).count()
|
||||
# context["total_leads"] = models.Lead.objects.filter(dealer=dealer).count()
|
||||
# context["invoices"] = entity.get_invoices().count()
|
||||
# context["estimates"] = entity.get_estimates().count()
|
||||
# context["purchase_orders"] = entity.get_purchase_orders().count()
|
||||
|
||||
|
||||
|
||||
# return context
|
||||
|
||||
|
||||
|
||||
from django.db.models import Sum, F, Count
|
||||
from django.db.models.functions import ExtractMonth
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
def DealerDashboard(request):
|
||||
dealer = request.dealer
|
||||
|
||||
# This part is correct as is, it filters cars by dealer and sold status
|
||||
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
|
||||
|
||||
@login_required
|
||||
def general_dashboard(request,dealer_slug):
|
||||
"""
|
||||
Renders the dealer dashboard with key performance indicators and chart data.
|
||||
"""
|
||||
dealer = get_object_or_404(models.Dealer,slug=dealer_slug)
|
||||
vat = models.VatRate.objects.filter(dealer=dealer,is_active=True).first()
|
||||
VAT_RATE=vat.rate
|
||||
today_local = timezone.localdate()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 1. Date Filtering
|
||||
# ----------------------------------------------------
|
||||
start_date_str = request.GET.get('start_date')
|
||||
end_date_str = request.GET.get('end_date')
|
||||
|
||||
# This part needs to be made timezone-aware and robust
|
||||
if not start_date_str:
|
||||
start_date = today_local - timedelta(days=30)
|
||||
else:
|
||||
if start_date_str and end_date_str:
|
||||
start_date = timezone.datetime.strptime(start_date_str, '%Y-%m-%d').date()
|
||||
|
||||
if not end_date_str:
|
||||
end_date = today_local
|
||||
else:
|
||||
end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d').date()
|
||||
|
||||
# Existing inventory calculations
|
||||
active_cars = models.Car.objects.filter(status__in=['available', 'reserved', 'damaged'])
|
||||
else:
|
||||
start_date = today_local - timedelta(days=30)
|
||||
end_date = today_local
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 2. Inventory KPIs
|
||||
# ----------------------------------------------------
|
||||
active_cars = models.Car.objects.filter(dealer=dealer).exclude(status='sold')
|
||||
|
||||
total_cars_in_inventory = active_cars.count()
|
||||
new_cars_qs= active_cars.filter(stock_type='new')
|
||||
used_cars_qs=active_cars.filter(stock_type='old')
|
||||
total_inventory_value = active_cars.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||
new_cars_qs = active_cars.filter(stock_type='new')
|
||||
total_new_cars_in_inventory = new_cars_qs.count()
|
||||
new_car_value = new_cars_qs.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||
used_cars_qs = active_cars.filter(stock_type='used')
|
||||
total_used_cars_in_inventory = used_cars_qs.count()
|
||||
total_inventory_value = sum([car.cost_price for car in active_cars])
|
||||
new_car_value=sum([car.cost_price for car in new_cars_qs])
|
||||
used_car_value=sum([car.cost_price for car in used_cars_qs])
|
||||
# Define the aging threshold (e.g., 90 days)
|
||||
used_car_value = used_cars_qs.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||
|
||||
aging_threshold_days = 60
|
||||
# Efficiently query the database for aging cars
|
||||
aging_inventory = active_cars.filter(created_at__lte=today_local - timedelta(days=aging_threshold_days))
|
||||
# You can get the count for display on the dashboard
|
||||
aging_inventory_count = aging_inventory.count()
|
||||
aging_inventory_count = active_cars.filter(receiving_date__date__lte=today_local - timedelta(days=aging_threshold_days)).count()
|
||||
|
||||
|
||||
|
||||
|
||||
# The database query will automatically be handled in a timezone-aware manner
|
||||
cars_sold_filtered = cars_sold.filter(
|
||||
# ----------------------------------------------------
|
||||
# 3. Sales KPIs (filtered by date)
|
||||
# ----------------------------------------------------
|
||||
cars_sold_filtered = models.Car.objects.filter(
|
||||
dealer=dealer,
|
||||
status='sold',
|
||||
sold_date__date__gte=start_date,
|
||||
sold_date__date__lte=end_date
|
||||
)
|
||||
|
||||
# Calculate summary data for the filtered results
|
||||
# General sales KPIs
|
||||
total_cars_sold = cars_sold_filtered.count()
|
||||
new_cars_sold=cars_sold_filtered.filter(stock_type='new')
|
||||
used_cars_sold=cars_sold_filtered.filter(stock_type='used')
|
||||
total_new_cars_sold=new_cars_sold.count()
|
||||
total_used_cars_sold=used_cars_sold.count()
|
||||
total_cost_of_cars_sold = cars_sold_filtered.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||
total_revenue_from_cars = cars_sold_filtered.aggregate(
|
||||
total=Sum(F('selling_price') - F('discount_amount'))
|
||||
)['total'] or 0
|
||||
|
||||
# Calculations for all cars
|
||||
total_cost_of_cars_sold = sum([car.cost_price for car in cars_sold_filtered])
|
||||
total_revenue_from_cars = sum([car.final_price for car in cars_sold_filtered])
|
||||
# Correct calculation for VAT based on the selling price
|
||||
total_vat_collected_from_cars = cars_sold_filtered.aggregate(
|
||||
total=Sum(F('selling_price') * VAT_RATE)
|
||||
)['total'] or 0
|
||||
|
||||
net_profit_from_cars = total_revenue_from_cars - total_cost_of_cars_sold
|
||||
total_vat_collected_from_cars = sum([car.vat_amount for car in cars_sold_filtered])
|
||||
total_discount = cars_sold_filtered.aggregate(total=Sum('discount_amount'))['total'] or 0
|
||||
|
||||
# Calculations specifically for used cars
|
||||
total_cost_of_used_cars_sold = sum([car.cost_price for car in used_cars_sold])
|
||||
total_revenue_from_used_cars = sum([car.final_price for car in used_cars_sold])
|
||||
net_profit_from_used_cars = total_revenue_from_used_cars - total_cost_of_used_cars_sold
|
||||
total_vat_collected_from_used_cars = sum([car.vat_amount for car in used_cars_sold])
|
||||
|
||||
# Calculations specifically for new cars
|
||||
total_cost_of_new_cars_sold = sum([car.cost_price for car in new_cars_sold])
|
||||
total_revenue_from_new_cars = sum([car.final_price for car in new_cars_sold])
|
||||
# Sales breakdown by type
|
||||
new_cars_sold = cars_sold_filtered.filter(stock_type='new')
|
||||
total_new_cars_sold = new_cars_sold.count()
|
||||
total_cost_of_new_cars_sold = new_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||
total_revenue_from_new_cars = new_cars_sold.aggregate(
|
||||
total=Sum(F('selling_price') - F('discount_amount'))
|
||||
)['total'] or 0
|
||||
net_profit_from_new_cars = total_revenue_from_new_cars - total_cost_of_new_cars_sold
|
||||
total_vat_collected_from_new_cars = sum([car.vat_amount for car in new_cars_sold])
|
||||
total_vat_collected_from_new_cars = new_cars_sold.aggregate(
|
||||
total=Sum(F('selling_price') * VAT_RATE)
|
||||
)['total'] or 0
|
||||
|
||||
used_cars_sold = cars_sold_filtered.filter(stock_type='used')
|
||||
total_used_cars_sold = used_cars_sold.count()
|
||||
total_cost_of_used_cars_sold = used_cars_sold.aggregate(total=Sum('cost_price'))['total'] or 0
|
||||
total_revenue_from_used_cars = used_cars_sold.aggregate(
|
||||
total=Sum(F('selling_price') - F('discount_amount'))
|
||||
)['total'] or 0
|
||||
net_profit_from_used_cars = total_revenue_from_used_cars - total_cost_of_used_cars_sold
|
||||
total_vat_collected_from_used_cars = used_cars_sold.aggregate(
|
||||
total=Sum(F('selling_price') * VAT_RATE)
|
||||
)['total'] or 0
|
||||
|
||||
# Service & Overall KPIs
|
||||
total_revenue_from_services = sum([car.get_additional_services()['total'] for car in cars_sold_filtered])
|
||||
total_vat_collected_from_services = sum([car.get_additional_services()['services_vat'] for car in cars_sold_filtered])
|
||||
total_vat_collected = total_vat_collected_from_cars + total_vat_collected_from_services
|
||||
total_revenue_generated = total_revenue_from_cars + total_revenue_from_services
|
||||
total_discount = sum([car.discount for car in cars_sold_filtered])
|
||||
|
||||
# Filter for expenses
|
||||
expenses = ItemModel.objects.filter(entity__admin__dealer=dealer, item_role='expense')
|
||||
total_expenses = sum([expense.default_amount for expense in expenses])
|
||||
|
||||
gross_profit = net_profit_from_cars - total_expenses
|
||||
|
||||
# ----------------------------------------------------
|
||||
# NEW LOGIC FOR CHARTS - Aggregating data by month
|
||||
# ----------------------------------------------------
|
||||
|
||||
# Group cars sold by month and aggregate totals
|
||||
expenses = models.ItemModel.objects.filter(entity__admin__dealer=dealer, item_role='expense')
|
||||
total_expenses = expenses.aggregate(total=Sum('default_amount'))['total'] or 0
|
||||
gross_profit = net_profit_from_cars - total_expenses
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 4. Chart Data Aggregation
|
||||
# ----------------------------------------------------
|
||||
monthly_sales_data = cars_sold_filtered.annotate(
|
||||
month=ExtractMonth('sold_date')
|
||||
).values('month').annotate(
|
||||
total_cars=Count('pk'),
|
||||
total_revenue=Sum(F('marked_price') - F('discount_amount')), # Corrected for marked price and discount_amount
|
||||
total_profit=Sum(F('marked_price') - F('discount_amount') - F('cost_price')) # Corrected profit calculation
|
||||
total_revenue=Sum(F('selling_price') - F('discount_amount')),
|
||||
total_profit=Sum(F('selling_price') - F('discount_amount') - F('cost_price'))
|
||||
).order_by('month')
|
||||
|
||||
# Initialize lists for chart data
|
||||
monthly_cars_sold = [0] * 12
|
||||
monthly_revenue = [0] * 12
|
||||
monthly_net_profit = [0] * 12
|
||||
|
||||
# Populate the lists from the queryset results
|
||||
for data in monthly_sales_data:
|
||||
month_index = data['month'] - 1 # Months are 1-12, so we need to adjust to 0-11
|
||||
month_index = data['month'] - 1
|
||||
monthly_cars_sold[month_index] = data['total_cars']
|
||||
monthly_revenue[month_index] = data['total_revenue']
|
||||
monthly_net_profit[month_index] = data['total_profit']
|
||||
# the monthly revenue is of this form: [0, 0, 0, 0, 0, 0, 0, Decimal('21000'), 0, 0, 0, 0] so javascript cannot recognise Decimale('21000') as no.
|
||||
monthly_revenue[month_index] = float(data['total_revenue']) if data['total_revenue'] else 0
|
||||
monthly_net_profit[month_index] = float(data['total_profit']) if data['total_profit'] else 0
|
||||
|
||||
monthly_cars_sold_json = json.dumps(monthly_cars_sold)
|
||||
monthly_revenue_json = json.dumps(monthly_revenue)
|
||||
monthly_net_profit_json = json.dumps(monthly_net_profit)
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Sales by MAKE
|
||||
# ----------------------------------------------------
|
||||
sales_by_make_data = cars_sold_filtered.values('id_car_make__name').annotate(
|
||||
car_count=Count('id_car_make__name')
|
||||
).order_by('-car_count')
|
||||
|
||||
sales_by_make_labels = [data['id_car_make__name'] for data in sales_by_make_data]
|
||||
sales_by_make_counts = [data['car_count'] for data in sales_by_make_data]
|
||||
|
||||
|
||||
# NEW: Convert the lists to JSON strings
|
||||
monthly_cars_sold_json = json.dumps(monthly_cars_sold)
|
||||
monthly_revenue_json = json.dumps([float(x) for x in monthly_revenue]) # Convert Decimal to float
|
||||
monthly_net_profit_json = json.dumps([float(x) for x in monthly_net_profit])
|
||||
|
||||
# ----------------------------------------------------
|
||||
# DATA FOR CAR SALES BY MODELS (for the new interactive chart)
|
||||
# ----------------------------------------------------
|
||||
|
||||
# Update the context dictionary with the new monthly data lists
|
||||
|
||||
# Get the selected make from the URL query parameter
|
||||
selected_make_sales= request.GET.get('make_sold', None)
|
||||
|
||||
|
||||
# Get a list of all unique makes for the dropdown
|
||||
all_makes_sold = list(cars_sold_filtered.values_list('id_car_make__name', flat=True).distinct().order_by('id_car_make__name'))
|
||||
|
||||
if selected_make_sales:
|
||||
# If a make is selected, filter the queryset
|
||||
sales_data_by_model = cars_sold_filtered.filter(
|
||||
id_car_make__name=selected_make_sales
|
||||
).values('id_car_model__name').annotate(
|
||||
count=Count('id_car_model__name')
|
||||
).order_by('-count')
|
||||
else:
|
||||
# If no make is selected, pass an empty list or some default data
|
||||
sales_data_by_model = []
|
||||
|
||||
|
||||
|
||||
|
||||
# 1. Inventory by Make (Pie Chart)
|
||||
|
||||
inventory_by_make_data = active_cars.values('id_car_make__name').annotate(
|
||||
car_count=Count('id_car_make__name')
|
||||
).order_by('-car_count')
|
||||
|
||||
inventory_by_make_labels = [data['id_car_make__name'] for data in inventory_by_make_data]
|
||||
inventory_by_make_counts = [data['car_count'] for data in inventory_by_make_data]
|
||||
|
||||
# 2. Inventory by Model (Bar Chart)
|
||||
selected_make_inventory = request.GET.get('make_inventory', None)
|
||||
|
||||
# Get all unique makes in inventory for the dropdown
|
||||
all_makes_inventory = list(active_cars.values_list('id_car_make__name', flat=True).distinct().order_by('id_car_make__name'))
|
||||
|
||||
if selected_make_inventory:
|
||||
inventory_data_by_model = active_cars.filter(
|
||||
id_car_make__name=selected_make_inventory
|
||||
).values('id_car_model__name').annotate(
|
||||
count=Count('id_car_model__name')
|
||||
).order_by('-count')
|
||||
else:
|
||||
# Default data
|
||||
inventory_data_by_model = []
|
||||
|
||||
context = {
|
||||
'start_date': start_date,
|
||||
'end_date': end_date,
|
||||
'cars_sold': cars_sold_filtered,
|
||||
'today': today_local,
|
||||
|
||||
# Inventory KPIs
|
||||
'total_cars_in_inventory': total_cars_in_inventory,
|
||||
'total_inventory_value': total_inventory_value,
|
||||
'total_new_cars_in_inventory': total_new_cars_in_inventory,
|
||||
'total_used_cars_in_inventory': total_used_cars_in_inventory,
|
||||
'new_car_value': new_car_value,
|
||||
'used_car_value': used_car_value,
|
||||
'aging_inventory_count': aging_inventory_count,
|
||||
|
||||
# Sales KPIs
|
||||
'total_cars_sold': total_cars_sold,
|
||||
'total_cost_of_cars_sold': total_cost_of_cars_sold,
|
||||
'total_revenue_from_cars': total_revenue_from_cars,
|
||||
'net_profit_from_cars': net_profit_from_cars,
|
||||
'total_vat_collected_from_cars': total_vat_collected_from_cars,
|
||||
'total_discount_on_cars': total_discount,
|
||||
|
||||
# Sales by Type
|
||||
'total_new_cars_sold': total_new_cars_sold,
|
||||
'total_used_cars_sold': total_used_cars_sold,
|
||||
'total_cost_of_new_cars_sold': total_cost_of_new_cars_sold,
|
||||
'total_revenue_from_new_cars': total_revenue_from_new_cars,
|
||||
'net_profit_from_new_cars': net_profit_from_new_cars,
|
||||
'total_vat_collected_from_new_cars': total_vat_collected_from_new_cars,
|
||||
'total_cost_of_used_cars_sold': total_cost_of_used_cars_sold,
|
||||
'total_revenue_from_used_cars': total_revenue_from_used_cars,
|
||||
'net_profit_from_used_cars': net_profit_from_used_cars,
|
||||
'total_vat_collected_from_used_cars': total_vat_collected_from_used_cars,
|
||||
|
||||
# Services and Overall KPIs
|
||||
'total_revenue_from_services': total_revenue_from_services,
|
||||
'total_vat_collected_from_services': total_vat_collected_from_services,
|
||||
'total_revenue_generated': total_revenue_generated,
|
||||
'total_vat_collected': total_vat_collected,
|
||||
'total_expenses': total_expenses,
|
||||
"gross_profit": gross_profit,
|
||||
'total_cars_in_inventory': total_cars_in_inventory,
|
||||
'total_used_cars_in_inventory': total_used_cars_in_inventory,
|
||||
'total_new_cars_in_inventory': total_new_cars_in_inventory,
|
||||
'total_inventory_value': total_inventory_value,
|
||||
'new_car_value':new_car_value,
|
||||
'used_car_value':used_car_value,
|
||||
'aging_inventory_count':aging_inventory_count,
|
||||
'total_new_cars_sold':total_new_cars_sold,
|
||||
'total_used_cars_sold':total_used_cars_sold,
|
||||
'total_cost_of_used_cars_sold': total_cost_of_used_cars_sold,
|
||||
'total_revenue_from_used_cars':total_revenue_from_used_cars,
|
||||
'net_profit_from_used_cars':net_profit_from_used_cars,
|
||||
'total_vat_collected_from_used_cars':total_vat_collected_from_used_cars,
|
||||
'total_cost_of_new_cars_sold': total_cost_of_new_cars_sold,
|
||||
'total_revenue_from_new_cars':total_revenue_from_new_cars,
|
||||
'net_profit_from_new_cars':net_profit_from_new_cars,
|
||||
'total_vat_collected_from_new_cars':total_vat_collected_from_new_cars,
|
||||
'gross_profit': gross_profit,
|
||||
|
||||
# Chart Data
|
||||
|
||||
|
||||
# New variables for charts
|
||||
'monthly_cars_sold_json': monthly_cars_sold_json,
|
||||
'monthly_revenue_json': monthly_revenue_json,
|
||||
'monthly_net_profit_json': monthly_net_profit_json,
|
||||
|
||||
|
||||
# Sales Chart Data
|
||||
'sales_by_make_labels_json': json.dumps(sales_by_make_labels),
|
||||
'sales_by_make_counts_json': json.dumps(sales_by_make_counts),
|
||||
'all_makes_sold': all_makes_sold,
|
||||
'selected_make_sales': selected_make_sales,
|
||||
'sales_data_by_model_json': json.dumps(list(sales_data_by_model)),
|
||||
|
||||
# New Inventory Chart Data
|
||||
'inventory_by_make_labels_json': json.dumps(inventory_by_make_labels),
|
||||
'inventory_by_make_counts_json': json.dumps(inventory_by_make_counts),
|
||||
'all_makes_inventory': all_makes_inventory,
|
||||
'selected_make_inventory': selected_make_inventory,
|
||||
'inventory_data_by_model_json': json.dumps(list(inventory_data_by_model)),
|
||||
|
||||
|
||||
}
|
||||
|
||||
return render(request, 'dashboards/dealer_dashboard.html', context)
|
||||
|
||||
|
||||
|
||||
|
||||
class ManagerDashboard(LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
ManagerDashboard class is a view handling the dashboard for a manager.
|
||||
|
||||
Provides functionality to manage and view various statistics and data specific
|
||||
to the dealer associated with the authenticated manager. It uses a specific
|
||||
template and ensures authentication before granting access. The class
|
||||
aggregates data about cars, leads, financial statistics, and other related
|
||||
business information for display in the manager's dashboard.
|
||||
|
||||
:ivar template_name: Path to the template used for rendering the manager's dashboard.
|
||||
:type template_name: str
|
||||
"""
|
||||
|
||||
template_name = "dashboards/manager_dashboard.html"
|
||||
|
||||
# def dispatch(self, request, *args, **kwargs):
|
||||
# if not request.user.is_authenticated:
|
||||
# return redirect("welcome")
|
||||
# if not getattr(request.user, "dealer", False):
|
||||
# return HttpResponseForbidden(
|
||||
# "You are not authorized to view this dashboard."
|
||||
# )
|
||||
# return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
|
||||
return render(request, 'dashboards/general_dashboard.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def sales_dashboard(request,dealer_slug):
|
||||
dealer = get_object_or_404(models.Dealer,slug=dealer_slug)
|
||||
today_local = timezone.localdate()
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 1. Date Filtering
|
||||
# ----------------------------------------------------
|
||||
start_date_str = request.GET.get('start_date')
|
||||
end_date_str = request.GET.get('end_date')
|
||||
|
||||
if start_date_str and end_date_str:
|
||||
start_date = timezone.datetime.strptime(start_date_str, '%Y-%m-%d').date()
|
||||
end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d').date()
|
||||
else:
|
||||
start_date = today_local - timedelta(days=30)
|
||||
end_date = today_local
|
||||
|
||||
# Filter leads by date range and dealer
|
||||
leads_filtered = models.Lead.objects.filter(
|
||||
dealer=dealer,
|
||||
created__date__gte=start_date,
|
||||
created__date__lte=end_date
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 2. Lead Sources Chart Logic
|
||||
# ----------------------------------------------------
|
||||
# Group leads by source and count them
|
||||
# This generates a list of dictionaries like [{'source': 'Showroom', 'count': 45}, ...]
|
||||
lead_sources_data = leads_filtered.values('source').annotate(
|
||||
count=Count('source')
|
||||
).order_by('-count')
|
||||
|
||||
# Separate the labels and counts for the chart
|
||||
lead_sources_labels = [item['source'] for item in lead_sources_data]
|
||||
lead_sources_counts = [item['count'] for item in lead_sources_data]
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 2. Lead Funnel Chart Logic
|
||||
# ----------------------------------------------------
|
||||
opportunity_filtered = models.Opportunity.objects.filter(
|
||||
dealer=dealer,
|
||||
created__date__gte=start_date,
|
||||
created__date__lte=end_date
|
||||
)
|
||||
opportunity_stage_data = opportunity_filtered.values('stage').annotate(
|
||||
count=Count('stage')
|
||||
).order_by('-count')
|
||||
# Separate the labels and counts for the chart
|
||||
opportunity_stage_labels = [item['stage'] for item in opportunity_stage_data ]
|
||||
opportunity_stage_counts = [item['count'] for item in opportunity_stage_data ]
|
||||
|
||||
|
||||
# 2. Inventory KPIs
|
||||
# ----------------------------------------------------
|
||||
active_cars = models.Car.objects.filter(dealer=dealer).exclude(status='sold')
|
||||
|
||||
total_cars_in_inventory = active_cars.count()
|
||||
new_cars_qs = active_cars.filter(stock_type='new')
|
||||
total_new_cars_in_inventory = new_cars_qs.count()
|
||||
used_cars_qs = active_cars.filter(stock_type='used')
|
||||
total_used_cars_in_inventory = used_cars_qs.count()
|
||||
aging_threshold_days = 60
|
||||
aging_inventory_count = active_cars.filter(receiving_date__date__lte=today_local - timedelta(days=aging_threshold_days)).count()
|
||||
|
||||
|
||||
context = {
|
||||
'start_date': start_date,
|
||||
'end_date': end_date,
|
||||
'lead_sources_labels_json': json.dumps(lead_sources_labels),
|
||||
'lead_sources_counts_json': json.dumps(lead_sources_counts),
|
||||
'opportunity_stage_labels_json': json.dumps(opportunity_stage_labels),
|
||||
'opportunity_stage_counts_json': json.dumps(opportunity_stage_counts),
|
||||
|
||||
# Inventory KPIs
|
||||
'total_cars_in_inventory': total_cars_in_inventory,
|
||||
'total_new_cars_in_inventory': total_new_cars_in_inventory,
|
||||
'total_used_cars_in_inventory': total_used_cars_in_inventory,
|
||||
'aging_inventory_count': aging_inventory_count,
|
||||
}
|
||||
|
||||
return render(request, 'dashboards/sales_dashboard.html', context)
|
||||
|
||||
|
||||
|
||||
class SalesDashboard(LoginRequiredMixin, TemplateView):
|
||||
|
||||
def aging_inventory_list_view(request, dealer_slug):
|
||||
"""
|
||||
SalesDashboard class provides a view for the sales dashboard.
|
||||
|
||||
This class is responsible for generating the context data required to
|
||||
display various statistics and information on the sales dashboard. It
|
||||
inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only
|
||||
authenticated users can access the view. The dashboard includes data
|
||||
such as the total number of cars, reservations, sold percentages,
|
||||
reserved percentages, and cars categorized by various statuses. The
|
||||
purpose of this dashboard is to provide dealership staff an overview
|
||||
of their inventory and related sales metrics.
|
||||
|
||||
:ivar template_name: The name of the HTML template used for rendering
|
||||
the sales dashboard.
|
||||
:type template_name: str
|
||||
Renders a paginated list of aging inventory for a specific dealer, with filters.
|
||||
"""
|
||||
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
|
||||
today_local = timezone.localdate()
|
||||
aging_threshold_days = 60
|
||||
|
||||
# Get filter parameters from the request
|
||||
selected_make = request.GET.get('make')
|
||||
selected_model = request.GET.get('model')
|
||||
selected_series = request.GET.get('series') # Changed 'serie' to 'series' for consistency
|
||||
selected_year = request.GET.get('year')
|
||||
selected_stock_type = request.GET.get('stock_type')
|
||||
|
||||
template_name = "dashboards/sales_dashboard.html"
|
||||
# Start with the base queryset for all aging cars.
|
||||
aging_cars_queryset = models.Car.objects.filter(
|
||||
dealer=dealer,
|
||||
receiving_date__date__lt=today_local - timedelta(days=aging_threshold_days)
|
||||
).exclude(status='sold')
|
||||
total_aging_inventory_value=aging_cars_queryset.aggregate(total=Sum('cost_price'))['total']
|
||||
|
||||
# Apply filters to the queryset if they exist. Chaining is fine here.
|
||||
if selected_make:
|
||||
aging_cars_queryset = aging_cars_queryset.filter(id_car_make__name=selected_make)
|
||||
if selected_model:
|
||||
aging_cars_queryset = aging_cars_queryset.filter(id_car_model__name=selected_model)
|
||||
if selected_series:
|
||||
aging_cars_queryset = aging_cars_queryset.filter(id_car_series__name=selected_series)
|
||||
if selected_year:
|
||||
aging_cars_queryset = aging_cars_queryset.filter(id_car_year__year=selected_year)
|
||||
if selected_stock_type:
|
||||
aging_cars_queryset = aging_cars_queryset.filter(stock_type=selected_stock_type)
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
dealer = self.request.dealer
|
||||
staff = getattr(self.request, "staff", None)
|
||||
total_cars = models.Car.objects.filter(dealer=dealer).count()
|
||||
total_reservations = models.CarReservation.objects.filter(
|
||||
reserved_by=self.request.user, reserved_until__gte=timezone.now()
|
||||
).count()
|
||||
|
||||
available_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.AVAILABLE
|
||||
).count()
|
||||
sold_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.SOLD
|
||||
).count()
|
||||
reserved_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.RESERVED
|
||||
).count()
|
||||
hold_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.HOLD
|
||||
).count()
|
||||
damaged_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.DAMAGED
|
||||
).count()
|
||||
transfer_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.TRANSFER
|
||||
).count()
|
||||
reserved_percentage = reserved_cars / total_cars * 100
|
||||
sold_percentage = sold_cars / total_cars * 100
|
||||
qs = (
|
||||
models.Car.objects.values("id_car_make__name")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("id_car_make__name")
|
||||
)
|
||||
car_by_make = list(qs)
|
||||
|
||||
context["dealer"] = dealer
|
||||
context["staff"] = staff
|
||||
context["total_cars"] = total_cars
|
||||
context["total_reservations"] = total_reservations
|
||||
context["reserved_percentage"] = reserved_percentage
|
||||
context["sold_percentage"] = sold_percentage
|
||||
context["available_cars"] = available_cars
|
||||
context["sold_cars"] = sold_cars
|
||||
context["reserved_cars"] = reserved_cars
|
||||
context["hold_cars"] = hold_cars
|
||||
context["damaged_cars"] = damaged_cars
|
||||
context["transfer_cars"] = transfer_cars
|
||||
context["car"] = json.dumps(car_by_make)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class InventoryDashboard(LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
SalesDashboard class provides a view for the sales dashboard.
|
||||
|
||||
This class is responsible for generating the context data required to
|
||||
display various statistics and information on the sales dashboard. It
|
||||
inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only
|
||||
authenticated users can access the view. The dashboard includes data
|
||||
such as the total number of cars, reservations, sold percentages,
|
||||
reserved percentages, and cars categorized by various statuses. The
|
||||
purpose of this dashboard is to provide dealership staff an overview
|
||||
of their inventory and related sales metrics.
|
||||
|
||||
:ivar template_name: The name of the HTML template used for rendering
|
||||
the sales dashboard.
|
||||
:type template_name: str
|
||||
"""
|
||||
|
||||
template_name = "dashboards/inventory_dashboard.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
dealer = self.request.dealer
|
||||
staff = getattr(self.request, "staff", None)
|
||||
total_cars = models.Car.objects.filter(dealer=dealer).count()
|
||||
total_reservations = models.CarReservation.objects.filter(
|
||||
reserved_by=self.request.user, reserved_until__gte=timezone.now()
|
||||
).count()
|
||||
|
||||
available_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.AVAILABLE
|
||||
).count()
|
||||
sold_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.SOLD
|
||||
).count()
|
||||
reserved_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.RESERVED
|
||||
).count()
|
||||
hold_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.HOLD
|
||||
).count()
|
||||
damaged_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.DAMAGED
|
||||
).count()
|
||||
transfer_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.TRANSFER
|
||||
).count()
|
||||
reserved_percentage = reserved_cars / total_cars * 100
|
||||
sold_percentage = sold_cars / total_cars * 100
|
||||
qs = (
|
||||
models.Car.objects.values("id_car_make__name")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("id_car_make__name")
|
||||
)
|
||||
car_by_make = list(qs)
|
||||
|
||||
context["dealer"] = dealer
|
||||
context["staff"] = staff
|
||||
context["total_cars"] = total_cars
|
||||
context["total_reservations"] = total_reservations
|
||||
context["reserved_percentage"] = reserved_percentage
|
||||
context["sold_percentage"] = sold_percentage
|
||||
context["available_cars"] = available_cars
|
||||
context["sold_cars"] = sold_cars
|
||||
context["reserved_cars"] = reserved_cars
|
||||
context["hold_cars"] = hold_cars
|
||||
context["damaged_cars"] = damaged_cars
|
||||
context["transfer_cars"] = transfer_cars
|
||||
context["car"] = json.dumps(car_by_make)
|
||||
|
||||
return context
|
||||
|
||||
class AccountantDashboard(LoginRequiredMixin, TemplateView):
|
||||
"""
|
||||
SalesDashboard class provides a view for the sales dashboard.
|
||||
|
||||
This class is responsible for generating the context data required to
|
||||
display various statistics and information on the sales dashboard. It
|
||||
inherits from `LoginRequiredMixin` and `TemplateView`, ensuring only
|
||||
authenticated users can access the view. The dashboard includes data
|
||||
such as the total number of cars, reservations, sold percentages,
|
||||
reserved percentages, and cars categorized by various statuses. The
|
||||
purpose of this dashboard is to provide dealership staff an overview
|
||||
of their inventory and related sales metrics.
|
||||
|
||||
:ivar template_name: The name of the HTML template used for rendering
|
||||
the sales dashboard.
|
||||
:type template_name: str
|
||||
"""
|
||||
|
||||
template_name = "dashboards/accountant_dashboard.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
dealer = self.request.dealer
|
||||
staff = getattr(self.request, "staff", None)
|
||||
total_cars = models.Car.objects.filter(dealer=dealer).count()
|
||||
total_reservations = models.CarReservation.objects.filter(
|
||||
reserved_by=self.request.user, reserved_until__gte=timezone.now()
|
||||
).count()
|
||||
|
||||
available_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.AVAILABLE
|
||||
).count()
|
||||
sold_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.SOLD
|
||||
).count()
|
||||
reserved_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.RESERVED
|
||||
).count()
|
||||
hold_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.HOLD
|
||||
).count()
|
||||
damaged_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.DAMAGED
|
||||
).count()
|
||||
transfer_cars = models.Car.objects.filter(
|
||||
dealer=dealer, status=models.CarStatusChoices.TRANSFER
|
||||
).count()
|
||||
reserved_percentage = reserved_cars / total_cars * 100
|
||||
sold_percentage = sold_cars / total_cars * 100
|
||||
qs = (
|
||||
models.Car.objects.values("id_car_make__name")
|
||||
.annotate(count=Count("id"))
|
||||
.order_by("id_car_make__name")
|
||||
)
|
||||
car_by_make = list(qs)
|
||||
|
||||
context["dealer"] = dealer
|
||||
context["staff"] = staff
|
||||
context["total_cars"] = total_cars
|
||||
context["total_reservations"] = total_reservations
|
||||
context["reserved_percentage"] = reserved_percentage
|
||||
context["sold_percentage"] = sold_percentage
|
||||
context["available_cars"] = available_cars
|
||||
context["sold_cars"] = sold_cars
|
||||
context["reserved_cars"] = reserved_cars
|
||||
context["hold_cars"] = hold_cars
|
||||
context["damaged_cars"] = damaged_cars
|
||||
context["transfer_cars"] = transfer_cars
|
||||
context["car"] = json.dumps(car_by_make)
|
||||
|
||||
return context
|
||||
# Get distinct values for filter dropdowns based on the initial, unfiltered aging cars queryset.
|
||||
# This ensures all possible filter options are always available.
|
||||
aging_base_queryset = models.Car.objects.filter(
|
||||
dealer=dealer,
|
||||
receiving_date__date__lt=today_local - timedelta(days=aging_threshold_days)
|
||||
).exclude(status='sold')
|
||||
|
||||
all_makes = aging_base_queryset.values_list('id_car_make__name', flat=True).distinct().order_by('id_car_make__name')
|
||||
all_models = aging_base_queryset.values_list('id_car_model__name', flat=True).distinct().order_by('id_car_model__name')
|
||||
all_series = aging_base_queryset.values_list('id_car_serie__name', flat=True).distinct().order_by('id_car_serie__name')
|
||||
all_stock_types = aging_base_queryset.values_list('stock_type', flat=True).distinct().order_by('stock_type')
|
||||
all_years = aging_base_queryset.values_list('year', flat=True).distinct().order_by('-year')
|
||||
|
||||
#
|
||||
# Set up pagination
|
||||
paginator = Paginator(aging_cars_queryset, 10)
|
||||
page_number = request.GET.get('page')
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
# Iterate only on the cars for the current page to add the age attribute.
|
||||
for car in page_obj.object_list:
|
||||
car.age_in_days = (today_local - car.receiving_date.date()).days
|
||||
|
||||
context = {
|
||||
"is_paginated": page_obj.has_other_pages,
|
||||
"cars": page_obj.object_list,
|
||||
'selected_make': selected_make,
|
||||
'selected_model': selected_model,
|
||||
'selected_series': selected_series, # Corrected variable name
|
||||
'selected_year': selected_year,
|
||||
'selected_stock_type': selected_stock_type,
|
||||
'all_makes': all_makes,
|
||||
'all_models': all_models,
|
||||
'all_series': all_series,
|
||||
'all_stock_types': all_stock_types,
|
||||
'all_years': all_years,
|
||||
'total_aging_inventory_value':total_aging_inventory_value
|
||||
|
||||
}
|
||||
|
||||
return render(request, 'dashboards/aging_inventory_list.html', context)
|
||||
|
||||
def terms_and_privacy(request):
|
||||
return render(request, "terms_and_privacy.html")
|
||||
@ -7134,14 +7039,14 @@ def send_lead_email(request, dealer_slug, slug, email_pk=None):
|
||||
|
||||
لقد أنشأنا ملفاً شخصياً لك في نظامنا لتتبع تفضيلاتك والسيارات التي تهتم بها. سنتواصل معك قريباً للمتابعة والإجابة على أي أسئلة أخرى قد تكون لديك.
|
||||
|
||||
في هذه الأثناء، لا تتردد في الاتصال بنا مباشرة على {lead.dealer.phone_number} أو زيارتنا مرة أخرى في أي وقت يناسبك.
|
||||
في هذه الأثناء، لا تتردد في الاتصال بنا مباشرة أو زيارتنا مرة أخرى في أي وقت يناسبك.
|
||||
|
||||
نتطلع إلى مساعدتك في العثور على سيارتك القادمة!
|
||||
|
||||
تحياتي،
|
||||
{lead.dealer.arabic_name}
|
||||
{lead.dealer.address}
|
||||
{lead.dealer.phone_number}
|
||||
{lead.dealer.phone_number}
|
||||
-----
|
||||
Dear {lead.full_name},
|
||||
|
||||
@ -7222,7 +7127,7 @@ class OpportunityCreateView(
|
||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs.get("dealer_slug"))
|
||||
form = super().get_form(form_class)
|
||||
form.fields["car"].queryset = models.Car.objects.filter(
|
||||
dealer=dealer, status="available", finances__marked_price__gt=0
|
||||
dealer=dealer, status="available", marked_price__gt=0
|
||||
)
|
||||
if self.request.is_dealer:
|
||||
form.fields["lead"].queryset = models.Lead.objects.filter(
|
||||
@ -7281,7 +7186,7 @@ class OpportunityUpdateView(
|
||||
dealer = get_object_or_404(models.Dealer, slug=self.kwargs.get("dealer_slug"))
|
||||
staff = getattr(self.request.user, "staff", None)
|
||||
form.fields["car"].queryset = models.Car.objects.filter(
|
||||
dealer=dealer, status="available", finances__marked_price__gt=0
|
||||
dealer=dealer, status="available",marked_price__gt=0
|
||||
)
|
||||
form.fields["lead"].queryset = models.Lead.objects.filter(
|
||||
dealer=dealer, staff=staff
|
||||
@ -11154,13 +11059,6 @@ def purchase_report_csv_export(request,dealer_slug):
|
||||
])
|
||||
return response
|
||||
|
||||
# @login_required
|
||||
# def car_sale_report_view(request,dealer_slug):
|
||||
# dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
|
||||
# cars_sold = models.Car.objects.filter(dealer=dealer,status='sold')
|
||||
# current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
# context={'cars_sold':cars_sold,'current_time':current_time }
|
||||
# return render(request,'ledger/reports/car_sale_report.html',context)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -11188,15 +11086,6 @@ def car_sale_report_view(request, dealer_slug):
|
||||
if selected_stock_type:
|
||||
cars_sold = cars_sold.filter(stock_type=selected_stock_type)
|
||||
|
||||
# Get distinct values for filter dropdowns
|
||||
makes = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_make__name', flat=True).distinct()
|
||||
models_qs = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_model__name', flat=True).distinct()
|
||||
|
||||
series = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_serie__name', flat=True).distinct()
|
||||
stock_types=models.Car.objects.filter(dealer=dealer, status='sold').values_list('stock_type', flat=True).distinct()
|
||||
years = models.Car.objects.filter(dealer=dealer, status='sold').values_list('year', flat=True).distinct().order_by('-year')
|
||||
|
||||
|
||||
|
||||
# # Calculate summary data for the filtered results
|
||||
total_cars_sold=cars_sold.count()
|
||||
@ -11211,6 +11100,15 @@ def car_sale_report_view(request, dealer_slug):
|
||||
|
||||
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
# Get distinct values for filter dropdowns
|
||||
base_sold_cars_queryset = models.Car.objects.filter(dealer=dealer, status='sold')
|
||||
makes =base_sold_cars_queryset.values_list('id_car_make__name', flat=True).distinct()
|
||||
models_qs =base_sold_cars_queryset.values_list('id_car_model__name', flat=True).distinct()
|
||||
|
||||
series =base_sold_cars_queryset.values_list('id_car_serie__name', flat=True).distinct()
|
||||
stock_types=base_sold_cars_queryset.values_list('stock_type', flat=True).distinct()
|
||||
years = base_sold_cars_queryset.values_list('year', flat=True).distinct().order_by('-year')
|
||||
|
||||
context = {
|
||||
'cars_sold': cars_sold,
|
||||
'total_cars_sold':total_cars_sold,
|
||||
@ -11238,7 +11136,6 @@ def car_sale_report_view(request, dealer_slug):
|
||||
return render(request, 'ledger/reports/car_sale_report.html', context)
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
def car_sale_report_csv_export(request, dealer_slug):
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.5 MiB |
BIN
static/images/customers/istockphoto-1188035960-612x612.jpg
Normal file
BIN
static/images/customers/istockphoto-1188035960-612x612.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
@ -1,218 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
|
||||
<h2 class="h3 fw-bold mb-0">Accountant Dashboard<i class="fas fa-chart-area text-primary ms-2"></i></h2>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Last 30 Days
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow">
|
||||
<li><a class="dropdown-item" href="#">Today</a></li>
|
||||
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
|
||||
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Revenue</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">$1.25M</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Net Profit</p>
|
||||
<h4 class="fw-bolder text-success mb-3">$1.25M</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Gross Profit</p>
|
||||
<h4 class="fw-bolder text-info mb-3">$1.25M</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Expense</p>
|
||||
<h4 class="fw-bolder text-danger mb-3">$1.25M</h4>
|
||||
</div>
|
||||
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="h5 fw-bold mb-3 text-dark">Breakdown by Vehicle Type</h4>
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-primary fw-bold mb-1">New Cars</p>
|
||||
<h4 class="fw-bolder mb-0">$1.25M</h4>
|
||||
<p class="text-muted small">Total Revenue</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-success fw-bold mb-1">Used Cars</p>
|
||||
<h4 class="fw-bolder mb-0">$0.75M</h4>
|
||||
<p class="text-muted small">Total Revenue</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-info fw-bold mb-1">New Cars</p>
|
||||
<h4 class="fw-bolder mb-0">$0.25M</h4>
|
||||
<p class="text-muted small">Net Profit</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-warning fw-bold mb-1">Used Cars</p>
|
||||
<h4 class="fw-bolder mb-0">$0.15M</h4>
|
||||
<p class="text-muted small">Net Profit</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Monthly Revenue & Profit</h5>
|
||||
</div>
|
||||
<div class="card-body" style="height: 400px;">
|
||||
<canvas id="revenueProfitChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
// Define a color palette that aligns with the Phoenix template
|
||||
const primaryColor = '#7249b6'; // A vibrant purple
|
||||
const secondaryColor = '#8193a6'; // A muted gray/blue
|
||||
const successColor = '#00d074'; // A bright green
|
||||
const dangerColor = '#e63757'; // A deep red
|
||||
|
||||
const ctx = document.getElementById('revenueProfitChart').getContext('2d');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Monthly Revenue',
|
||||
data: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
|
||||
borderColor: primaryColor,
|
||||
backgroundColor: 'rgba(114, 73, 182, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointBackgroundColor: primaryColor,
|
||||
pointRadius: 5,
|
||||
pointHoverRadius: 8
|
||||
},
|
||||
{
|
||||
label: 'Monthly Net Profit',
|
||||
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
|
||||
borderColor: successColor,
|
||||
backgroundColor: 'rgba(0, 208, 116, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointBackgroundColor: successColor,
|
||||
pointRadius: 5,
|
||||
pointHoverRadius: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
labels: {
|
||||
color: secondaryColor,
|
||||
boxWidth: 20
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: 'white',
|
||||
bodyColor: 'white',
|
||||
padding: 10,
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
if (context.parsed.y !== null) {
|
||||
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)'
|
||||
},
|
||||
ticks: {
|
||||
color: secondaryColor
|
||||
},
|
||||
border: {
|
||||
color: secondaryColor
|
||||
}
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)'
|
||||
},
|
||||
ticks: {
|
||||
color: secondaryColor
|
||||
},
|
||||
border: {
|
||||
color: secondaryColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
124
templates/dashboards/aging_inventory_list.html
Normal file
124
templates/dashboards/aging_inventory_list.html
Normal file
@ -0,0 +1,124 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load tenhal_tag %}
|
||||
{% block content %}
|
||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
||||
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
||||
{% trans "Aging Inventory" %}
|
||||
<i class="fas fa-box-open text-danger ms-2"></i>
|
||||
</h2>
|
||||
<h4 class="text-muted mb-3 ">{% trans "Aging Inventory Total" %} :: <span class=" text-danger">{{total_aging_inventory_value}}<span class="icon-saudi_riyal"></span></span></h4>
|
||||
<p class="text-muted mb-0">{% trans "Cars in inventory for more than 60 days." %}</p>
|
||||
|
||||
</div>
|
||||
|
||||
<form method="GET" class="d-flex flex-wrap align-items-center mb-4 g-3">
|
||||
<div class="col-sm-6 col-md-2 me-2">
|
||||
<label for="make-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Make:" %}</label>
|
||||
<select class="form-select" name="make" id="make-filter">
|
||||
<option value="">{% trans "All" %}</option>
|
||||
{% for make in all_makes %}
|
||||
<option value="{{ make }}" {% if make == selected_make %}selected{% endif %}>{{ make }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-2 me-2">
|
||||
<label for="model-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Model:" %}</label>
|
||||
<select class="form-select" name="model" id="model-filter">
|
||||
<option value="">{% trans "All" %}</option>
|
||||
{% for model in all_models %}
|
||||
<option value="{{ model }}" {% if model == selected_model %}selected{% endif %}>{{ model }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-2 me-2">
|
||||
<label for="series-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Series:" %}</label>
|
||||
<select class="form-select" name="series" id="series-filter">
|
||||
<option value="">{% trans "All" %}</option>
|
||||
{% for series in all_series %}
|
||||
<option value="{{ series }}" {% if series == selected_series %}selected{% endif %}>{{ series }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-2 me-2">
|
||||
<label for="year-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Year:" %}</label>
|
||||
<select class="form-select" name="year" id="year-filter">
|
||||
<option value="">{% trans "All" %}</option>
|
||||
{% for year in all_years %}
|
||||
<option value="{{ year }}" {% if year|stringformat:"s" == selected_year %}selected{% endif %}>{{ year }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-2 me-2">
|
||||
<label for="stock-type-filter" class="form-label mb-0 small text-uppercase fw-bold">{% trans "Stock Type:" %}</label>
|
||||
<select class="form-select" name="stock_type" id="stock-type-filter">
|
||||
<option value="">{% trans "All" %}</option>
|
||||
{% for stock_type in all_stock_types %}
|
||||
<option value="{{ stock_type }}" {% if stock_type == selected_stock_type %}selected{% endif %}>{{ stock_type|title }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary mt-4">{% trans "Filter" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if is_paginated %}
|
||||
<div class="d-flex justify-content-between mb-4">
|
||||
<span class="text-muted">{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}</span>
|
||||
<span class="text-muted">{% trans "Total Aging Cars:" %} {{ page_obj.paginator.count }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if cars %}
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-4">
|
||||
{% for car in cars %}
|
||||
<div class="col">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title text-danger fw-bold">
|
||||
<img src="{{car.logo}}" width="40" height="40" class=""> {{ car.id_car_make.name }} {{ car.id_car_model.name }} {{ car.id_car_serie.name }} {{ car.year}}
|
||||
</h5>
|
||||
<p class="card-text text-muted mb-2">
|
||||
<strong>{% trans "VIN:" %}</strong> {{ car.vin }}
|
||||
</p>
|
||||
<p class="card-text mb-2">
|
||||
<strong>{% trans "Age:" %}</strong>
|
||||
<span class="badge bg-danger">{{ car.age_in_days }} {% trans "days" %}</span>
|
||||
</p>
|
||||
<p class="card-text mb-2">
|
||||
<strong>{% trans "Acquisition Date:" %}</strong> {{ car.receiving_date|date:"F j, Y" }}
|
||||
</p>
|
||||
<div class="mt-auto pt-3 border-top">
|
||||
<a href="{% url 'car_detail' request.dealer.slug car.slug %}" class="btn btn-outline-primary btn-sm w-100">
|
||||
{% trans "View Details" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-success d-flex align-items-center" role="alert">
|
||||
<i class="fas fa-check-circle me-2"></i>
|
||||
<div>
|
||||
{% trans "Excellent! There are no cars in the aging inventory at the moment." %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<div class="d-flex">
|
||||
{% if is_paginated %}
|
||||
{% include 'partials/pagination.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
109
templates/dashboards/chart.html
Normal file
109
templates/dashboards/chart.html
Normal file
@ -0,0 +1,109 @@
|
||||
{% load i18n %}
|
||||
{% if request.is_dealer or request.is_manager or request.is_accountant %}
|
||||
<h3 class="fw-bold mb-3">
|
||||
{% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %}
|
||||
Monthly Performance Trends ({{ start_date }} - {{ end_date }})
|
||||
{% endblocktrans %}
|
||||
</h3>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Monthly Revenue & Profit" %}</h5>
|
||||
</div>
|
||||
<div class="card-body" style="height: 400px;">
|
||||
<canvas id="revenueProfitChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Monthly Cars Sold" %}</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="CarsSoldByMonthChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Sales by Make" %}</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="salesByBrandChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0 d-flex justify-content-between align-items-center">
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Models Sold" %}</h5>
|
||||
<form method="GET" class="d-flex align-items-center">
|
||||
<div class="form-group d-flex align-items-center me-2">
|
||||
<label for="carMakeSelectSales" class="form-label mb-0 me-2">{% trans "Select Make:" %}</label>
|
||||
<select id="carMakeSelectSales" class="form-select" name="make_sold">
|
||||
<option value="">{% trans "All Makes" %}</option>
|
||||
{% for make_sold in all_makes_sold %}
|
||||
<option value="{{ make_sold }}" {% if make_sold == selected_make_sales %}selected{% endif %}>{{ make_sold }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">{% trans "Go" %}</button>
|
||||
<input type="hidden" name="start_date" value="{{ start_date|date:'Y-m-d' }}">
|
||||
<input type="hidden" name="end_date" value="{{ end_date|date:'Y-m-d' }}">
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-body" style="height: 400px;">
|
||||
<canvas id="salesChartByModel"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
||||
<h3 class="fw-bold mb-3">{% trans "Inventory Trends" %}</h3>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Inventory by Make" %}</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="inventoryByMakeChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0 d-flex justify-content-between align-items-center">
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Models in Inventory" %}</h5>
|
||||
<form method="GET" class="d-flex align-items-center">
|
||||
<div class="form-group d-flex align-items-center me-2">
|
||||
<label for="carMakeSelectInventory" class="form-label mb-0 me-2">{% trans "Select Make:" %}</label>
|
||||
<select id="carMakeSelectInventory" class="form-select" name="make_inventory">
|
||||
<option value="">{% trans "All Makes" %}</option>
|
||||
{% for make_inv in all_makes_inventory %}
|
||||
<option value="{{ make_inv }}" {% if make_inv == selected_make_inventory %}selected{% endif %}>{{ make_inv }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">{% trans "Go" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-body" style="height: 400px;">
|
||||
<canvas id="inventoryByModelChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -1,508 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static %}
|
||||
{% block content %}
|
||||
<div class="content">
|
||||
<div class="row gy-3 mb-4 justify-content-between">
|
||||
<div class="col-xxl-6">
|
||||
<h2 class="mb-2 text-body-emphasis">CRM Dashboard</h2>
|
||||
<h5 class="text-body-tertiary fw-semibold mb-4">Check your business growth in one place</h5>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-sm-6 col-md-4 col-xl-3 col-xxl-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex d-sm-block justify-content-between">
|
||||
<div class="border-bottom-sm border-translucent mb-sm-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="d-flex align-items-center icon-wrapper-sm shadow-primary-100"
|
||||
style="transform: rotate(-7.45deg)">
|
||||
<span class="fa-solid fa-phone-alt text-primary fs-7 z-1 ms-2"></span>
|
||||
</div>
|
||||
<p class="text-body-tertiary fs-9 mb-0 ms-2 mt-3">Outgoing call</p>
|
||||
</div>
|
||||
<p class="text-primary mt-2 fs-6 fw-bold mb-0 mb-sm-4">
|
||||
3 <span class="fs-8 text-body lh-lg">Leads Today</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-center flex-between-end d-sm-block text-end text-sm-start">
|
||||
<span class="badge badge-phoenix badge-phoenix-success fs-10 mb-2">+24.5%</span>
|
||||
<p class="mb-0 fs-9 text-body-tertiary">Than Yesterday</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-xl-3 col-xxl-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex d-sm-block justify-content-between">
|
||||
<div class="border-bottom-sm border-translucent mb-sm-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="d-flex align-items-center icon-wrapper-sm shadow-info-100"
|
||||
style="transform: rotate(-7.45deg)">
|
||||
<span class="fa-solid fa-calendar text-info fs-7 z-1 ms-2"></span>
|
||||
</div>
|
||||
<p class="text-body-tertiary fs-9 mb-0 ms-2 mt-3">Outgoing meeting</p>
|
||||
</div>
|
||||
<p class="text-info mt-2 fs-6 fw-bold mb-0 mb-sm-4">
|
||||
12 <span class="fs-8 text-body lh-lg">This Week</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-center flex-between-end d-sm-block text-end text-sm-start">
|
||||
<span class="badge badge-phoenix badge-phoenix-warning fs-10 mb-2">+24.5%</span>
|
||||
<p class="mb-0 fs-9 text-body-tertiary">Than last week</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-xl-6 col-xxl-4 gy-5 gy-md-3">
|
||||
<div class="border-bottom border-translucent">
|
||||
<h5 class="pb-4 border-bottom border-translucent">Top 5 Lead Sources</h5>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item bg-transparent list-group-crm fw-bold text-body fs-9 py-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-normal fs-9 mx-1"> <span class="fw-bold">1.</span>None </span><span>(65)</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item bg-transparent list-group-crm fw-bold text-body fs-9 py-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-normal mx-1"><span class="fw-bold">2.</span>Online Store</span><span>(74)</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item bg-transparent list-group-crm fw-bold text-body fs-9 py-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-normal fs-9 mx-1"><span class="fw-bold">3.</span> Advertisement</span><span>(32)</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item bg-transparent list-group-crm fw-bold text-body fs-9 py-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-normal fs-9 mx-1"><span class="fw-bold">4.</span> Seminar Partner</span><span>(25)</span>
|
||||
</div>
|
||||
</li>
|
||||
<li class="list-group-item bg-transparent list-group-crm fw-bold text-body fs-9 py-2">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="fw-normal fs-9 mx-1"> <span class="fw-bold">5.</span> Partner</span><span>(23)</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xxl-6 mb-6">
|
||||
<h3>Contacts Created</h3>
|
||||
<p class="text-body-tertiary mb-1">Payment received across all channels</p>
|
||||
<div class="echart-contacts-created" style="min-height:270px; width:100%"></div>
|
||||
</div>
|
||||
<div class="col-12 col-xxl-6 mb-3 mb-sm-0">
|
||||
<div class="row">
|
||||
<div class="col-sm-7 col-md-8 col-xxl-8 mb-md-3 mb-lg-0">
|
||||
<h3>New Contacts by Source</h3>
|
||||
<p class="text-body-tertiary">Payment received across all channels</p>
|
||||
<div class="row g-0">
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-primary"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">Organic</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">80</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end-md-0 border-end-xl border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-success"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">Paid Search</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">65</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end border-end-md border-end-xl-0 border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-info"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">Direct</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">40</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-end-xl border-bottom border-bottom-xl-0 border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-info-light"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">Social</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">220</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-end border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-danger-lighter"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">Referrals</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">120</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-warning-light"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">Others</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">35</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5 col-md-4 col-xxl-4 my-3 my-sm-0">
|
||||
<div class="position-relative d-flex flex-center mb-sm-4 mb-xl-0 echart-contact-by-source-row mt-sm-7 mt-lg-4 mt-xl-0">
|
||||
<div class="echart-contact-by-source" style="min-height:245px;width:100%"></div>
|
||||
<div class="position-absolute rounded-circle bg-primary-subtle top-50 start-50 translate-middle d-flex flex-center"
|
||||
style="height:100px;
|
||||
width:100px">
|
||||
<h3 class="mb-0 text-primary-dark fw-bolder" data-label="data-label"></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xxl-6 mb-8">
|
||||
<div class="mb-3">
|
||||
<h3>New Users & Leads</h3>
|
||||
<p class="text-body-tertiary mb-0">Payment received across all channels</p>
|
||||
</div>
|
||||
<div class="row g-6">
|
||||
<div class="col-md-6 mb-2 mb-sm-0">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-2 text-info"
|
||||
data-feather="users"
|
||||
style="min-height:24px;
|
||||
width:24px"></span>
|
||||
<h4 class="text-body-tertiary mb-0">
|
||||
New Users :<span class="text-body-emphasis">42</span>
|
||||
</h4>
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-success d-inline-flex align-items-center ms-2"><span class="badge-label d-inline-block lh-base">+24.5%</span><span class="ms-1 fa-solid fa-caret-up d-inline-block lh-1"></span></span>
|
||||
</div>
|
||||
<div class="pb-0 pt-4">
|
||||
<div class="echarts-new-users" style="min-height:110px;width:100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-2 text-primary"
|
||||
data-feather="zap"
|
||||
style="height:24px;
|
||||
width:24px"></span>
|
||||
<h4 class="text-body-tertiary mb-0">
|
||||
New Leads :<span class="text-body-emphasis">45</span>
|
||||
</h4>
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-success d-inline-flex align-items-center ms-2"><span class="badge-label d-inline-block lh-base">+30.5%</span><span class="ms-1 fa-solid fa-caret-up d-inline-block lh-1"></span></span>
|
||||
</div>
|
||||
<div class="pb-0 pt-4">
|
||||
<div class="echarts-new-leads" style="min-height:110px;width:100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xxl-6">
|
||||
<div class="row align-items-start justify-content-between mb-4 g-3">
|
||||
<div class="col-auto">
|
||||
<h3>Ad Clicks</h3>
|
||||
<p class="text-body-tertiary lh-sm mb-0">Check effectiveness of your ads</p>
|
||||
</div>
|
||||
<div class="col-12 col-sm-4">
|
||||
<select class="form-select form-select-sm" id="select-ad-clicks-month">
|
||||
<option>Mar 1 - 31, 2022</option>
|
||||
<option>April 1 - 30, 2022</option>
|
||||
<option>May 1 - 31, 2022</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="echart-add-clicks-chart" style="min-height:385px;width:100%"></div>
|
||||
</div>
|
||||
<div class="col-12 col-xxl-6 mb-6 gy-0 gy-xxl-3">
|
||||
<div class="row align-items-start justify-content-between mb-4 g-3">
|
||||
<div class="col-auto">
|
||||
<h3>
|
||||
Deal Forecast<span class="fw-semibold">- $90,303</span>
|
||||
</h3>
|
||||
<p class="text-body-tertiary mb-1">Show what you offer here</p>
|
||||
</div>
|
||||
<div class="col-12 col-sm-4">
|
||||
<select class="form-select form-select-sm" id="select-ad-forcast-month">
|
||||
<option>Mar 1 - 31, 2022</option>
|
||||
<option>April 1 - 30, 2022</option>
|
||||
<option>May 1 - 31, 2022</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-100">
|
||||
<div class="d-flex flex-start">
|
||||
<p class="mb-2 text-body-tertiary fw-semibold fs-9"
|
||||
style="width: 20.72%">$21.0k</p>
|
||||
<p class="mb-2 text-body-tertiary fw-semibold fs-9"
|
||||
style="width: 35.76%">$3.4k</p>
|
||||
<p class="mb-2 text-body-tertiary fw-semibold fs-9"
|
||||
style="width: 25.38%">$15.1k</p>
|
||||
<p class="mb-2 text-body-tertiary fw-semibold fs-9"
|
||||
style="width: 25.14%">$4.6k</p>
|
||||
</div>
|
||||
<div class="progress mb-3 rounded-3" style="height: 10px;">
|
||||
<div class="progress-bar border-end border-2 bg-primary-dark"
|
||||
role="progressbar"
|
||||
style="width: 20.72%"
|
||||
aria-valuenow="20.72"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Appointment"></div>
|
||||
<div class="progress-bar border-end border-2"
|
||||
role="progressbar"
|
||||
style="width: 35.76%"
|
||||
aria-valuenow="35.76"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Qualified"></div>
|
||||
<div class="progress-bar bg-success border-end border-2"
|
||||
role="progressbar"
|
||||
style="width: 25.38%"
|
||||
aria-valuenow="25.38"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Closed Won"></div>
|
||||
<div class="progress-bar bg-info"
|
||||
role="progressbar"
|
||||
style="width: 25.14%"
|
||||
aria-valuenow="25.14"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="Contact Sent"></div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="mt-4 mb-3">Deal Forecast by Owner</h4>
|
||||
<div class="border-top border-bottom-0"
|
||||
id="dealForecastTable"
|
||||
data-list='{"valueNames":["contact","appointment","qualified","closed-won","contact-sent"],"page":5}'>
|
||||
<div class="table-responsive scrollbar">
|
||||
<table class="table fs-9 mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="sort border-end border-translucent white-space-nowrap align-middle ps-0 text-uppercase text-body-tertiary"
|
||||
scope="col"
|
||||
data-sort="contact"
|
||||
style="width:15%;
|
||||
min-width:100px">Contact</th>
|
||||
<th class="sort border-end border-translucent align-middle text-end px-3 text-uppercase text-body-tertiary"
|
||||
scope="col"
|
||||
data-sort="appointment"
|
||||
style="width:15%;
|
||||
min-width:95px">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<span class="fa-solid fa-square fs-11 text-primary me-2"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9">Appointment</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="sort border-end border-translucent align-middle text-end px-3 text-uppercase text-body-tertiary"
|
||||
scope="col"
|
||||
data-sort="qualified"
|
||||
style="width:20%;
|
||||
min-width:100px">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<span class="fa-solid fa-square fs-11 text-primary-light me-2"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9">Qualified</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="sort border-end border-translucent align-middle text-end px-3 text-uppercase text-body-tertiary"
|
||||
scope="col"
|
||||
data-sort="closed-won"
|
||||
style="width:20%;
|
||||
min-width:100px">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<span class="fa-solid fa-square fs-11 text-success me-2"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9">Closed Won</span>
|
||||
</div>
|
||||
</th>
|
||||
<th class="sort align-middle text-end ps-3 text-uppercase text-body-tertiary"
|
||||
scope="col"
|
||||
data-sort="contact-sent"
|
||||
style="width:20%;
|
||||
min-width:100px">
|
||||
<div class="d-inline-flex flex-center">
|
||||
<span class="fa-solid fa-square fs-11 text-info me-2"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9">Contact Sent</span>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list" id="table-deal-forecast-body">
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="contact border-end border-translucent align-middle white-space-nowrap py-2 ps-0 px-3">
|
||||
<a class="fw-semibold" href="#!">Carrie Anne</a>
|
||||
</td>
|
||||
<td class="appointment border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
1000
|
||||
</td>
|
||||
<td class="qualified border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$1256
|
||||
</td>
|
||||
<td class="closed-won border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$1200
|
||||
</td>
|
||||
<td class="contact-sent border-end-0 align-middle white-space-nowrap text-end fw-semibold text-body ps-3 py-2">
|
||||
$1200
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="contact border-end border-translucent align-middle white-space-nowrap py-2 ps-0 px-3">
|
||||
<a class="fw-semibold" href="#!">Milind Mikuja</a>
|
||||
</td>
|
||||
<td class="appointment border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
558
|
||||
</td>
|
||||
<td class="qualified border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$2531
|
||||
</td>
|
||||
<td class="closed-won border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$2200
|
||||
</td>
|
||||
<td class="contact-sent border-end-0 align-middle white-space-nowrap text-end fw-semibold text-body ps-3 py-2">
|
||||
$2200
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="contact border-end border-translucent align-middle white-space-nowrap py-2 ps-0 px-3">
|
||||
<a class="fw-semibold" href="#!">Stanley Drinkwater</a>
|
||||
</td>
|
||||
<td class="appointment border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
1100
|
||||
</td>
|
||||
<td class="qualified border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$100
|
||||
</td>
|
||||
<td class="closed-won border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$100
|
||||
</td>
|
||||
<td class="contact-sent border-end-0 align-middle white-space-nowrap text-end fw-semibold text-body ps-3 py-2">
|
||||
$100
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="contact border-end border-translucent align-middle white-space-nowrap py-2 ps-0 px-3">
|
||||
<a class="fw-semibold" href="#!">Josef Stravinsky</a>
|
||||
</td>
|
||||
<td class="appointment border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
856
|
||||
</td>
|
||||
<td class="qualified border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$326
|
||||
</td>
|
||||
<td class="closed-won border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$265
|
||||
</td>
|
||||
<td class="contact-sent border-end-0 align-middle white-space-nowrap text-end fw-semibold text-body ps-3 py-2">
|
||||
$265
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="contact border-end border-translucent align-middle white-space-nowrap py-2 ps-0 px-3">
|
||||
<a class="fw-semibold" href="#!">Roy Anderson</a>
|
||||
</td>
|
||||
<td class="appointment border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
1200
|
||||
</td>
|
||||
<td class="qualified border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$1452
|
||||
</td>
|
||||
<td class="closed-won border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$865
|
||||
</td>
|
||||
<td class="contact-sent border-end-0 align-middle white-space-nowrap text-end fw-semibold text-body ps-3 py-2">
|
||||
$865
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="contact border-end border-translucent align-middle white-space-nowrap py-2 ps-0 px-3">
|
||||
<a class="fw-semibold" href="#!">Oscar Wilde</a>
|
||||
</td>
|
||||
<td class="appointment border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
1020
|
||||
</td>
|
||||
<td class="qualified border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$950
|
||||
</td>
|
||||
<td class="closed-won border-end border-translucent align-middle white-space-nowrap text-end fw-semibold text-body py-2 px-3">
|
||||
$1000
|
||||
</td>
|
||||
<td class="contact-sent border-end-0 align-middle white-space-nowrap text-end fw-semibold text-body ps-3 py-2">
|
||||
$800
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle border-bottom-0 border-end border-translucent white-space-nowrap text-end fw-bold text-body-emphasis pt-2 lh-sm pb-0 px-3">
|
||||
</td>
|
||||
<td class="align-middle border-bottom-0 border-end border-translucent white-space-nowrap text-end fw-bold text-body-emphasis pt-2 lh-sm pb-0 px-3">
|
||||
4,744
|
||||
</td>
|
||||
<td class="align-middle border-bottom-0 border-end border-translucent white-space-nowrap text-end fw-bold text-body-emphasis pt-2 lh-sm pb-0 px-3">
|
||||
$5,665
|
||||
</td>
|
||||
<td class="align-middle border-bottom-0 border-end border-translucent white-space-nowrap text-end fw-bold text-body-emphasis pt-2 lh-sm pb-0 px-3">
|
||||
$4630
|
||||
</td>
|
||||
<td class="border-bottom-0 align-middle white-space-nowrap text-end fw-bold text-body-emphasis pt-2 pb-0 ps-3">
|
||||
$4630
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-lg-n4">
|
||||
<div class="row g-3 mb-9 mt-n7">
|
||||
<div class="col-xl-5">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h3>Lead Conversion</h3>
|
||||
<p class="text-body-tertiary mb-0">Stages of deals & conversion</p>
|
||||
<div class="echart-lead-conversion" style="min-height: 250px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-7">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<h3>Revenue Target</h3>
|
||||
<p class="text-body-tertiary">Country-wise target fulfilment</p>
|
||||
<div class="echart-revenue-target-conversion" style="min-height: 230px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer position-absolute">
|
||||
<div class="row g-0 justify-content-between align-items-center h-100">
|
||||
<div class="col-12 col-sm-auto text-center">
|
||||
<p class="mb-0 mt-2 mt-sm-0 text-body">
|
||||
Thank you for creating with Phoenix<span class="d-none d-sm-inline-block"></span><span class="d-none d-sm-inline-block mx-1">|</span>
|
||||
<br class="d-sm-none" />
|
||||
2024 ©<a class="mx-1" href="https://themewagon.com">Themewagon</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12 col-sm-auto text-center">
|
||||
<p class="mb-0 text-body-tertiary text-opacity-85">v1.20.1</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,477 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load tenhal_tag %}
|
||||
{% block title %}
|
||||
{{ _("Dealership Dashboard") |capfirst }}
|
||||
{% endblock title %}
|
||||
{% block content%}
|
||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
||||
<h2 class="h3 fw-bold text-dark mb-3 mb-md-0">Business Health Dashboard <i class="fas fa-chart-area text-primary ms-2"></i></h2>
|
||||
<form method="GET" class="date-filter-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="start-date" class="form-label">Start Date</label>
|
||||
<input type="date" class="form-control" id="start-date" name="start_date"
|
||||
value="{{ start_date|date:'Y-m-d' }}" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="end-date" class="form-label">End Date</label>
|
||||
<input type="date" class="form-control" id="end-date" name="end_date"
|
||||
value="{{ end_date|date:'Y-m-d' }}" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">Apply Filter</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cost of Cars Sold</p>
|
||||
<h4 class="fw-bolder text-secondary mb-3">{{total_cost_of_cars_sold|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Revenue from cars</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_revenue_from_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Net Profit From Cars</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{net_profit_from_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Discount on cars</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_discount_on_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total VAT collected from cars</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected_from_cars|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Revenue from Services</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_revenue_from_services|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total VAT collected from Services</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected_from_services|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue Generated" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_revenue_generated|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT Collected" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Expense" %}</p>
|
||||
<h4 class="fw-bolder text-danger mb-3">{{total_expenses|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
-2% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Gross Profit" %}</p>
|
||||
<h4 class="fw-bolder text-info mb-3">{{gross_profit|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT Collected" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{total_vat_collected|floatformat:2}}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars Sold</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_cars_sold }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars Sold</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_cars_sold }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Used Cars Sold</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_cars_sold }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans 'Inventory Value '%}</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_inventory_value|floatformat:2 }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars in Inventory</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Old Cars in Inventory</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_old_cars_in_inventory}}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total New Cars in Inventory</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_new_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars Value</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ new_car_value }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Old Cars Value</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ old_car_value }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+8% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Aging Inventory</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ aging_inventory_count}}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--charts-->
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Monthly Revenue & Profit</h5>
|
||||
</div>
|
||||
<div class="card-body" style="height: 400px;">
|
||||
<canvas id="revenueProfitChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="CarsSoldByMonthChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
{% endblock content %}
|
||||
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
// Define a color palette that aligns with the Phoenix template
|
||||
const primaryColor = '#7249b6'; // A vibrant purple
|
||||
const secondaryColor = '#8193a6'; // A muted gray/blue
|
||||
const successColor = '#00d074'; // A bright green
|
||||
const dangerColor = '#e63757'; // A deep red
|
||||
|
||||
// Monthly Cars Sold (Bar Chart)
|
||||
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
|
||||
new Chart(ctx1, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
datasets: [{
|
||||
label: 'Total Cars Sold',
|
||||
data: {{monthly_cars_sold_json|safe}},
|
||||
backgroundColor: primaryColor,
|
||||
borderColor: primaryColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' },
|
||||
ticks: { color: secondaryColor }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Monthly Revenue & Profit (Line Chart)
|
||||
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
|
||||
new Chart(ctx2, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Monthly Revenue',
|
||||
data: {{monthly_revenue_json|safe}},
|
||||
borderColor: primaryColor,
|
||||
backgroundColor: 'rgba(114, 73, 182, 0.1)', // Using primaryColor with transparency
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointBackgroundColor: primaryColor,
|
||||
pointRadius: 5,
|
||||
pointHoverRadius: 8
|
||||
},
|
||||
{
|
||||
label: 'Monthly Net Profit',
|
||||
data: {{monthly_net_profit_json|safe}},
|
||||
borderColor: successColor,
|
||||
backgroundColor: 'rgba(0, 208, 116, 0.1)', // Using successColor with transparency
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointBackgroundColor: successColor,
|
||||
pointRadius: 5,
|
||||
pointHoverRadius: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
labels: { color: '#495057', boxWidth: 20 }
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: 'white',
|
||||
bodyColor: 'white',
|
||||
padding: 10,
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
if (context.parsed.y !== null) {
|
||||
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'SAR' }).format(context.parsed.y);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' },
|
||||
ticks: { color: secondaryColor },
|
||||
border: { color: secondaryColor }
|
||||
},
|
||||
y: {
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' },
|
||||
ticks: { color: secondaryColor },
|
||||
border: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
266
templates/dashboards/financial_data_cards.html
Normal file
266
templates/dashboards/financial_data_cards.html
Normal file
@ -0,0 +1,266 @@
|
||||
{% load i18n %}
|
||||
{% if request.is_dealer or request.is_manager or request.is_accountant %}
|
||||
<h3 class="fw-bold mb-3">
|
||||
{% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %}
|
||||
Sales KPIs ({{ start_date }} - {{ end_date }})
|
||||
{% endblocktrans %}
|
||||
</h3>
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Cars Sold" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cars_sold }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue from Cars" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_revenue_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Net Profit from Cars" %}</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ net_profit_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Discount on Cars" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_discount_on_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Cost of Cars Sold" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cost_of_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT from Cars" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="fw-bold my-4">{% trans "Sales of New Cars" %}</h4>
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Sold" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_new_cars_sold }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Revenue" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_revenue_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Net Profit" %}</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ net_profit_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars VAT" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_new_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Cost" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cost_of_new_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 class="fw-bold my-4">{% trans "Sales of Used Cars" %}</h4>
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Sold" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_used_cars_sold }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Revenue" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_revenue_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Net Profit" %}</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ net_profit_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars VAT" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_used_cars|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Cost" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cost_of_used_cars_sold|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.is_dealer or request.is_manager or request.is_inventory %}
|
||||
<h3 class="fw-bold mb-3">{% trans "Inventory KPIs" %}</h3>
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Cars in Inventory" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Inventory Value" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_inventory_value|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars in Inventory" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_new_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars in Inventory" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_used_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars Inventory Value" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ new_car_value|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars Inventory Value" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ used_car_value|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-danger fw-bold small mb-1"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{% trans "Aging Inventory (> 60 days)" %}</a></p>
|
||||
<h4 class="fw-bolder text-danger mb-3"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{{ aging_inventory_count }}</a></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.is_dealer or request.is_manager or request.is_accountant %}
|
||||
<h3 class="fw-bold mb-3">
|
||||
{% blocktrans with start_date=start_date|date:"F j, Y" end_date=end_date|date:"F j, Y" %}
|
||||
Financial Health KPIs ({{ start_date }} - {{ end_date }})
|
||||
{% endblocktrans %}
|
||||
</h3>
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue from Services" %}</p>
|
||||
<h4 class="fw-bolder text-info mb-3">{{ total_revenue_from_services|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT from Services" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected_from_services|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Revenue Generated" %}</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ total_revenue_generated|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total VAT Collected" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_vat_collected|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Expenses" %}</p>
|
||||
<h4 class="fw-bolder text-danger mb-3">{{ total_expenses|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Gross Profit" %}</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ gross_profit|floatformat:2 }}<span class="icon-saudi_riyal"></span></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
429
templates/dashboards/general_dashboard.html
Normal file
429
templates/dashboards/general_dashboard.html
Normal file
@ -0,0 +1,429 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load tenhal_tag %}
|
||||
|
||||
{% block title %}
|
||||
{% trans "Dealership Dashboard"|capfirst %}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
||||
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
||||
{% if request.is_dealer %}
|
||||
{% trans "Business Health Dashboard" %}
|
||||
{% elif request.is_manger %}
|
||||
{% trans "Manager Dashboard" %}
|
||||
{% elif request.is_inventory %}
|
||||
{% trans "Inventory Dashboard" %}
|
||||
{% else %}
|
||||
{% trans "Accountant Dashboard" %}
|
||||
{% endif %}
|
||||
<i class="fas fa-chart-area text-primary ms-2"></i>
|
||||
</h2>
|
||||
<form method="GET" class="date-filter-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="start-date" class="form-label">{% trans "Start Date" %}</label>
|
||||
<input type="date" class="form-control" id="start-date" name="start_date"
|
||||
value="{{ start_date|date:'Y-m-d' }}" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="end-date" class="form-label">{% trans "End Date" %}</label>
|
||||
<input type="date" class="form-control" id="end-date" name="end_date"
|
||||
value="{{ end_date|date:'Y-m-d' }}" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">{% trans "Apply Filter" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="make_sold" value="{{ selected_make_sales }}">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
{% include 'dashboards/financial_data_cards.html' %}
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
{% include 'dashboards/chart.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
{% endblock content %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
// Define a color palette that aligns with the Phoenix template
|
||||
const primaryColor = '#7249b6';
|
||||
const secondaryColor = '#8193a6';
|
||||
const successColor = '#00d074';
|
||||
const dangerColor = '#e63757';
|
||||
const chartColors = [
|
||||
'#7249b6', '#00d074', '#e63757', '#17a2b8', '#ffc107',
|
||||
'#8193a6', '#28a745', '#6c757d', '#fd7e14', '#dc3545',
|
||||
'#20c997', '#6f42c1', '#e83e8c', '#6610f2', '#007bff',
|
||||
'#495057'
|
||||
];
|
||||
|
||||
// Pass translated strings from Django to JavaScript
|
||||
const translatedStrings = {
|
||||
monthlyCarsSoldLabel: "{% trans 'Total Cars Sold' %}",
|
||||
monthlyRevenueLabel: "{% trans 'Monthly Revenue' %}",
|
||||
monthlyNetProfitLabel: "{% trans 'Monthly Net Profit' %}",
|
||||
salesByMakeLabel: "{% trans 'Car Count by Make' %}",
|
||||
salesByModelPrefix: "{% trans 'Cars Sold for' %}",
|
||||
inventoryByMakeLabel: "{% trans 'Car Count by Make' %}",
|
||||
inventoryByModelLabel: "{% trans 'Cars in Inventory' %}",
|
||||
jan: "{% trans 'Jan' %}",
|
||||
feb: "{% trans 'Feb' %}",
|
||||
mar: "{% trans 'Mar' %}",
|
||||
apr: "{% trans 'Apr' %}",
|
||||
may: "{% trans 'May' %}",
|
||||
jun: "{% trans 'Jun' %}",
|
||||
jul: "{% trans 'Jul' %}",
|
||||
aug: "{% trans 'Aug' %}",
|
||||
sep: "{% trans 'Sep' %}",
|
||||
oct: "{% trans 'Oct' %}",
|
||||
nov: "{% trans 'Nov' %}",
|
||||
dec: "{% trans 'Dec' %}",
|
||||
cars: "{% trans 'cars' %}"
|
||||
};
|
||||
|
||||
|
||||
// Monthly Cars Sold (Bar Chart)
|
||||
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
|
||||
new Chart(ctx1, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: [
|
||||
translatedStrings.jan, translatedStrings.feb, translatedStrings.mar, translatedStrings.apr,
|
||||
translatedStrings.may, translatedStrings.jun, translatedStrings.jul, translatedStrings.aug,
|
||||
translatedStrings.sep, translatedStrings.oct, translatedStrings.nov, translatedStrings.dec
|
||||
],
|
||||
datasets: [{
|
||||
label: translatedStrings.monthlyCarsSoldLabel,
|
||||
data: {{ monthly_cars_sold_json|safe }},
|
||||
backgroundColor: primaryColor,
|
||||
borderColor: primaryColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' },
|
||||
ticks: {
|
||||
color: secondaryColor,
|
||||
callback: function(value) {
|
||||
if (Number.isInteger(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Monthly Revenue & Profit (Line Chart)
|
||||
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
|
||||
new Chart(ctx2, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [
|
||||
translatedStrings.jan, translatedStrings.feb, translatedStrings.mar, translatedStrings.apr,
|
||||
translatedStrings.may, translatedStrings.jun, translatedStrings.jul, translatedStrings.aug,
|
||||
translatedStrings.sep, translatedStrings.oct, translatedStrings.nov, translatedStrings.dec
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: translatedStrings.monthlyRevenueLabel,
|
||||
data: {{ monthly_revenue_json|safe }},
|
||||
borderColor: primaryColor,
|
||||
backgroundColor: 'rgba(114, 73, 182, 0.1)', // Using primaryColor with transparency
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointBackgroundColor: primaryColor,
|
||||
pointRadius: 5,
|
||||
pointHoverRadius: 8
|
||||
},
|
||||
{
|
||||
label: translatedStrings.monthlyNetProfitLabel,
|
||||
data: {{ monthly_net_profit_json|safe }},
|
||||
borderColor: successColor,
|
||||
backgroundColor: 'rgba(0, 208, 116, 0.1)', // Using successColor with transparency
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointBackgroundColor: successColor,
|
||||
pointRadius: 5,
|
||||
pointHoverRadius: 8
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
labels: { color: '#495057', boxWidth: 20 }
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: 'white',
|
||||
bodyColor: 'white',
|
||||
padding: 10,
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
if (context.parsed.y !== null) {
|
||||
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'SAR' }).format(context.parsed.y);
|
||||
}
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' },
|
||||
ticks: { color: secondaryColor },
|
||||
border: { color: secondaryColor }
|
||||
},
|
||||
y: {
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' },
|
||||
ticks: { color: secondaryColor },
|
||||
border: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Sales by Make (Pie Chart)
|
||||
function getChartColors(count) {
|
||||
const colors = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
colors.push(chartColors[i % chartColors.length]);
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
const ctx3 = document.getElementById('salesByBrandChart').getContext('2d');
|
||||
new Chart(ctx3, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: {{ sales_by_make_labels_json|safe }},
|
||||
datasets: [{
|
||||
label: translatedStrings.salesByMakeLabel,
|
||||
data: {{ sales_by_make_counts_json|safe }},
|
||||
backgroundColor: getChartColors({{ sales_by_make_counts_json|safe }}.length),
|
||||
hoverOffset: 15,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right',
|
||||
labels: { color: '#343a40', font: { size: 14 } }
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const label = context.label || '';
|
||||
const value = context.parsed || 0;
|
||||
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||
const percentage = ((value / total) * 100).toFixed(2);
|
||||
return `${label}: ${value} ${translatedStrings.cars} (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 4. Sales by Model (Bar Chart)
|
||||
// -----------------------------------------------------------
|
||||
const salesDataByModel = JSON.parse('{{ sales_data_by_model_json|safe }}');
|
||||
const canvasElementSales = document.getElementById('salesChartByModel');
|
||||
let chartInstanceSales = null;
|
||||
|
||||
if (salesDataByModel.length > 0) {
|
||||
const labels = salesDataByModel.map(item => item.id_car_model__name);
|
||||
const counts = salesDataByModel.map(item => item.count);
|
||||
const backgroundColor = labels.map((_, index) => getChartColors(labels.length)[index]);
|
||||
|
||||
chartInstanceSales = new Chart(canvasElementSales, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: `${translatedStrings.salesByModelPrefix} {{ selected_make_sales }}`,
|
||||
data: counts,
|
||||
backgroundColor: backgroundColor,
|
||||
borderColor: backgroundColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
if (Number.isInteger(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
label += Math.round(context.parsed.y);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 5. Inventory by Make (Pie Chart)
|
||||
// -----------------------------------------------------------
|
||||
const ctxInventoryMake = document.getElementById('inventoryByMakeChart').getContext('2d');
|
||||
new Chart(ctxInventoryMake, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: {{ inventory_by_make_labels_json|safe }},
|
||||
datasets: [{
|
||||
label: translatedStrings.inventoryByMakeLabel,
|
||||
data: {{ inventory_by_make_counts_json|safe }},
|
||||
backgroundColor: getChartColors({{ inventory_by_make_counts_json|safe }}.length),
|
||||
hoverOffset: 15,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right',
|
||||
labels: { color: '#343a40', font: { size: 14 } }
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const label = context.label || '';
|
||||
const value = context.parsed || 0;
|
||||
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||
const percentage = ((value / total) * 100).toFixed(2);
|
||||
return `${label}: ${value} ${translatedStrings.cars} (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------
|
||||
// 6. Inventory by Model (Bar Chart)
|
||||
// -----------------------------------------------------------
|
||||
const inventoryDataByModel = JSON.parse('{{ inventory_data_by_model_json|safe }}');
|
||||
const canvasInventoryModel = document.getElementById('inventoryByModelChart');
|
||||
const messageInventoryModel = document.getElementById('inventoryByModelMessage');
|
||||
|
||||
if (inventoryDataByModel.length > 0) {
|
||||
canvasInventoryModel.style.display = 'block';
|
||||
if (messageInventoryModel) {
|
||||
messageInventoryModel.style.display = 'none';
|
||||
}
|
||||
|
||||
const labels = inventoryDataByModel.map(item => item.id_car_model__name);
|
||||
const counts = inventoryDataByModel.map(item => item.count);
|
||||
const backgroundColor = getChartColors(labels.length);
|
||||
|
||||
new Chart(canvasInventoryModel, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [{
|
||||
label: translatedStrings.inventoryByModelLabel,
|
||||
data: counts,
|
||||
backgroundColor: backgroundColor,
|
||||
borderColor: backgroundColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: {
|
||||
callback: function(value) {
|
||||
if (Number.isInteger(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.dataset.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
label += Math.round(context.parsed.y);
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
canvasInventoryModel.style.display = 'none';
|
||||
if (messageInventoryModel) {
|
||||
messageInventoryModel.style.display = 'flex';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,300 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
{% block content %}
|
||||
|
||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
|
||||
<h2 class="h3 fw-bold mb-0">Inventory Dashboard <i class="fas fa-warehouse text-primary ms-2"></i></h2>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Last 30 Days
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow">
|
||||
<li><a class="dropdown-item" href="#">Today</a></li>
|
||||
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
|
||||
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars in Inventory</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars in Inventory</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ new_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Used Cars in Inventory</p>
|
||||
<h4 class="fw-bolder text-info mb-3">{{ old_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Inventory Value</p>
|
||||
<h4 class="fw-bolder text-warning mb-3">$5.8M</h4>
|
||||
</div>
|
||||
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
-2% from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-warning fw-bold mb-1">New Car Value</p>
|
||||
<h4 class="fw-bolder mb-0">$3.2M</h4>
|
||||
<p class="text-muted small">Total new cars value</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-info fw-bold mb-1">Used Car Value</p>
|
||||
<h4 class="fw-bolder mb-0">$2.6M</h4>
|
||||
<p class="text-muted small">Total used cars value</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-danger fw-bold mb-1">Average Time on Lot</p>
|
||||
<h4 class="fw-bolder mb-0">10 days</h4>
|
||||
<p class="text-muted small">Average for all vehicles</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-3">
|
||||
<div class="card h-100 p-4 shadow-sm border-0">
|
||||
<p class="text-danger fw-bold mb-1">Aging Inventory</p>
|
||||
<h4 class="fw-bolder mb-0">12 cars</h4>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
-4 cars from last month
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--charts-->
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Inventory by Make</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="inventoryByBrandChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Car Models by Make in Inventory</h5>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6 mb-4 mt-3">
|
||||
<label for="carMakeSelect" class="form-label fs-8 mb-2">Select a Car Make:</label>
|
||||
<select id="carMakeSelect" class="form-select">
|
||||
<option value="" disabled selected>-- Choose a make --</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="inventoryChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
{% endblock content %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
// Define a custom color palette that matches the Phoenix template
|
||||
const primaryColor = '#7249b6'; // A vibrant purple
|
||||
const secondaryColor = '#8193a6'; // A muted gray/blue
|
||||
const successColor = '#00d074'; // A bright green
|
||||
const infoColor = '#0085ff'; // A medium blue
|
||||
const warningColor = '#ffc83b'; // A yellow
|
||||
const dangerColor = '#e63757'; // A deep red
|
||||
|
||||
const ctx4 = document.getElementById('inventoryByBrandChart').getContext('2d');
|
||||
|
||||
// Chart.js configuration for the Pie Chart
|
||||
new Chart(ctx4, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Toyota', 'Ford', 'Honda', 'BMW', 'Other'],
|
||||
datasets: [{
|
||||
label: 'Car Count by Make',
|
||||
data: [45, 30, 25, 15, 10], // Sample data for car counts
|
||||
backgroundColor: [
|
||||
primaryColor,
|
||||
successColor,
|
||||
warningColor,
|
||||
infoColor,
|
||||
secondaryColor
|
||||
],
|
||||
hoverOffset: 15,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right', // Places the legend on the right side
|
||||
labels: {
|
||||
color: secondaryColor, // Use a muted color for a subtle look
|
||||
font: {
|
||||
size: 14
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)', // Bootstrap dark
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const label = context.label || '';
|
||||
const value = context.parsed || 0;
|
||||
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||
const percentage = ((value / total) * 100).toFixed(2);
|
||||
return `${label}: ${value} cars (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//chart for number of cars of each model in the inventory
|
||||
|
||||
|
||||
|
||||
// Sample data representing your inventory
|
||||
const inventoryData = [
|
||||
{ make: 'Ford', model: 'Mustang', count: 5 },
|
||||
{ make: 'Ford', model: 'F-150', count: 12 },
|
||||
{ make: 'Ford', model: 'Explorer', count: 8 },
|
||||
{ make: 'Toyota', model: 'Camry', count: 10 },
|
||||
{ make: 'Toyota', model: 'Corolla', count: 15 },
|
||||
{ make: 'Toyota', model: 'RAV4', count: 7 },
|
||||
{ make: 'BMW', model: 'X5', count: 4 },
|
||||
{ make: 'BMW', model: '3 Series', count: 6 },
|
||||
{ make: 'BMW', model: 'i8', count: 2 },
|
||||
];
|
||||
|
||||
// Get the unique list of car makes for the dropdown
|
||||
const carMakes = [...new Set(inventoryData.map(item => item.make))];
|
||||
|
||||
const selectElement = document.getElementById('carMakeSelect');
|
||||
const canvasElement = document.getElementById('inventoryChart');
|
||||
let chartInstance = null; // Stores the current chart instance
|
||||
|
||||
// Populate the dropdown menu
|
||||
carMakes.forEach(make => {
|
||||
const option = document.createElement('option');
|
||||
option.value = make;
|
||||
option.textContent = make;
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
|
||||
// Function to create or update the chart
|
||||
function renderChart(data, type = 'bar') {
|
||||
// Destroy the old chart instance if it exists
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
}
|
||||
|
||||
const chartLabels = data.map(item => item.model);
|
||||
const chartValues = data.map(item => item.count);
|
||||
|
||||
// Generate random colors for the chart segments
|
||||
const backgroundColor = chartLabels.map(() => `rgba(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, 0.6)`);
|
||||
const borderColor = backgroundColor.map(color => color.replace('0.6', '1'));
|
||||
|
||||
// Create the new chart
|
||||
chartInstance = new Chart(canvasElement, {
|
||||
type: type, // Can be 'bar' or 'pie'
|
||||
data: {
|
||||
labels: chartLabels,
|
||||
datasets: [{
|
||||
label: 'Number of Cars',
|
||||
data: chartValues,
|
||||
backgroundColor: backgroundColor,
|
||||
borderColor: borderColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Event listener for the dropdown menu
|
||||
selectElement.addEventListener('change', (event) => {
|
||||
const selectedMake = event.target.value;
|
||||
if (selectedMake) {
|
||||
const filteredData = inventoryData.filter(item => item.make === selectedMake);
|
||||
// The second parameter can be 'pie' to switch chart types
|
||||
renderChart(filteredData, 'bar'); // Use 'bar' for a bar chart
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,452 +0,0 @@
|
||||
{% load i18n static custom_filters django_ledger %}
|
||||
{% block content %}
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="row g-2">
|
||||
<h3 class="fs-4 fs-md-4 fs-xl-4 fw-black mb-4">
|
||||
<span class="text-gradient-info me-3">{{ staff }}</span>
|
||||
</h3>
|
||||
<div class="card mb-3">
|
||||
<div class="bg-holder"
|
||||
style="background-image:url({% static 'images/bg/38.png' %});
|
||||
background-position:left bottom;
|
||||
background-size:auto"></div>
|
||||
<div class="card-body d-flex justify-content-between position-relative">
|
||||
<div class="col-sm-7 col-md-8 col-xxl-8 mb-md-3 mb-lg-0">
|
||||
<h3 class="mb-3">{{ _("Inventory by Status") }}</h3>
|
||||
<div class="row g-0">
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-success"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Available") }}</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ available_cars }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end-md-0 border-end-xl border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-warning"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Sold") }}</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ sold_cars }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end border-end-md border-end-xl-0 border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-danger"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Reserved") }}</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ reserved_cars }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-end-xl border-bottom border-bottom-xl-0 border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-primary"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Transfer") }}</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ transfer_cars }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-end border-translucent">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-warning-light"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Hold") }}</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ hold_cars }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-4">
|
||||
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fa-solid fa-square fs-11 me-2 text-secondary-dark"
|
||||
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Damaged") }}</span>
|
||||
</div>
|
||||
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ damaged_cars }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-5 col-md-4 col-xxl-4 my-3 my-sm-0">
|
||||
<div class="position-relative d-flex flex-center mb-sm-4 mb-xl-0 echart-cars-by-status-container mt-sm-7 mt-lg-4 mt-xl-0">
|
||||
<div id="echart-cars-by-status" style="min-height:245px;width:100%"></div>
|
||||
<div class="position-absolute rounded-circle bg-primary-subtle top-50 start-50 translate-middle d-flex flex-center"
|
||||
style="height:100px;
|
||||
width:100px">
|
||||
<h3 class="mb-0 text-primary-dark fw-bolder" data-label="data-label"></h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-12 mb-8">
|
||||
<div class="mb-3">
|
||||
<h3>{{ _("New Leads and Customers") }}</h3>
|
||||
<p class="text-body-tertiary mb-0">{{ _("Payment received across all channels") }}</p>
|
||||
</div>
|
||||
<div class="row g-6">
|
||||
<div class="col-xl-6 mb-2 mb-sm-0">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-2 text-info"
|
||||
data-feather="users"
|
||||
style="min-height:24px;
|
||||
width:24px"></span>
|
||||
<h4 class="text-body-tertiary mb-0">
|
||||
{{ _("New Customers") }} :
|
||||
<span class="text-body-emphasis">42</span>
|
||||
</h4>
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-success d-inline-flex align-items-center ms-2">
|
||||
<span class="badge-label d-inline-block lh-base">+24.5%</span>
|
||||
<span class="ms-1 fa-solid fa-caret-up d-inline-block lh-1"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="pb-0 pt-4">
|
||||
<div class="echarts-new-users" style="min-height:110px;width:100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="me-2 text-primary"
|
||||
data-feather="zap"
|
||||
style="height:24px;
|
||||
width:24px"></span>
|
||||
<h4 class="text-body-tertiary mb-0">
|
||||
{{ _("New Leads") }} :<span class="text-body-emphasis">45</span>
|
||||
</h4>
|
||||
<span class="badge badge-phoenix fs-10 badge-phoenix-success d-inline-flex align-items-center ms-2">
|
||||
<span class="badge-label d-inline-block lh-base">+30.5%</span>
|
||||
<span class="ms-1 fa-solid fa-caret-up d-inline-block lh-1"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="pb-0 pt-4">
|
||||
<div class="echarts-new-leads" style="min-height:110px;width:100%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="row g-3 pe-xxl-3">
|
||||
<div class="col-12 col-xl-6 col-xxl-12">
|
||||
<div class="row">
|
||||
<div class="col-6 col-xl-12 col-xxl-6 border-bottom border-end border-end-xl-0 border-end-xxl pb-4 pt-4 pt-xl-0 pt-xxl-4 pe-4 pe-sm-5 pe-xl-0 pe-xxl-5">
|
||||
<h4 class="text-body mb-4">{% trans 'inventory'|upper %}</h4>
|
||||
<div class="d-md-flex flex-between-center">
|
||||
<div id="car-chart-by-make"
|
||||
class="order-sm-0 order-md-1"
|
||||
style="height:64px;
|
||||
width: 128px"></div>
|
||||
<div class="mt-4 mt-md-0">
|
||||
<h1 class="text-body-highlight mb-2">{{ total_cars }}</h1>
|
||||
<span class="badge badge-phoenix badge-phoenix-primary me-2 fs-10"> <span class="fs-10 text-body-secondary me-1">{{ _("As of") }}</span>{% now "SHORT_DATETIME_FORMAT" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-12 col-xxl-6 border-bottom py-4 ps-4 ps-sm-5 ps-xl-0 ps-xxl-5">
|
||||
<h4 class="text-body mb-4">{% trans 'inventory value'|upper %}</h4>
|
||||
<div class="d-md-flex flex-between-center">
|
||||
<div class="d-md-flex align-items-center gap-2 order-sm-0 order-md-1 fa-2x align-items-center">
|
||||
<i class="fas fa-money-check-alt fs-4 text-success-light dark__text-opacity-75"></i>
|
||||
<div class="d-flex d-md-block gap-2 align-items-center mt-1 mt-md-0">
|
||||
<p class="fs-9 mb-0 mb-md-2 text-body-tertiary text-nowrap"></p>
|
||||
<h4 class="text-body-highlight mb-0"></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 mt-md-0">
|
||||
<h3 class="text-body-highlight mb-2">
|
||||
{{ total_selling_price|currency_format }} <span class="icon-saudi_riyal"></span>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-12 col-xxl-6 border-bottom-xl border-bottom-xxl-0 border-end border-end-xl-0 border-end-xxl py-4 pe-4 pe-sm-5 pe-xl-0 pe-xxl-5">
|
||||
<h4 class="text-body mb-4">{% trans "Profits"|upper %}</h4>
|
||||
<div class="d-md-flex flex-between-center">
|
||||
<div class="d-md-flex align-items-center gap-2 order-sm-0 order-md-1">
|
||||
<span class="fa-solid fa-money-bill-trend-up fs-4 text-warning-light dark__text-opacity-75"
|
||||
data-bs-theme="light"></span>
|
||||
<div class="d-flex d-md-block gap-2 align-items-center mt-1 mt-md-0">
|
||||
<p class="fs-9 mb-0 mb-md-2 text-body-tertiary text-nowrap"></p>
|
||||
<h4 class="text-body-highlight mb-0"></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 mt-md-0">
|
||||
<h3 class="text-body-highlight mb-2">
|
||||
{{ total_profit|currency_format }} <span class="icon-saudi_riyal"></span>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-12 col-xxl-6 py-4 ps-4 ps-sm-5 ps-xl-0 ps-xxl-5">
|
||||
<h5 class="text-body mb-4">{{ _("Canceled Invoices") }}</h5>
|
||||
<div class="d-md-flex flex-between-center">
|
||||
<div class="chart-cancel-booking order-sm-0 order-md-1"
|
||||
style="height:54px;
|
||||
width:78px"></div>
|
||||
<div class="mt-3 mt-md-0">
|
||||
<h3 class="text-body-highlight mb-2">120.00</h3>
|
||||
<span class="badge badge-phoenix badge-phoenix-danger me-2 fs-10"> <span class="fa-solid fa-plus me-1"></span>5.76%</span>
|
||||
<span class="fs-9 text-body-secondary d-block d-sm-inline mt-1">{{ _("From last month") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6 col-xxl-12 mb-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-header pb-3">
|
||||
<div class="row justify-content-between g-3">
|
||||
<div class="col-auto">
|
||||
<h3 class="text-body-highlight">{{ _("Gross Profit") }}</h3>
|
||||
<p class="mb-0">Annual income according to the board</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center h-100 gy-5">
|
||||
<div class="col-12 col-md-auto col-xl-12 col-xxl-auto order-md-1 order-xl-0 order-xxl-1 px-md-8 px-xl-6">
|
||||
<div class="echart-gross-profit mx-auto mt-3 mt-md-0 mt-xl-3 mt-xxl-0"
|
||||
style="width: 250px;
|
||||
height: 250px"></div>
|
||||
</div>
|
||||
<div class="col-12 col-md-auto col-xl-12 col-xxl-auto flex-1 h-md-100">
|
||||
<div class="d-flex flex-column justify-content-between h-md-100 h-xl-auto h-xxl-100">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex gap-2">
|
||||
<div class="bullet-item bg-primary-light" data-bs-theme="light"></div>
|
||||
<div>
|
||||
<h6 class="mb-0 text-body fw-semibold mb-2">Flight</h6>
|
||||
<h5 class="mb-0 text-body">$162,791,400</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 text-primary">
|
||||
<span class="fw-bold"
|
||||
data-feather="trending-up"
|
||||
style="width: 24px;
|
||||
height: 24px"></span>
|
||||
<p class="mb-0 fw-bold">15.50%</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex gap-2">
|
||||
<div class="bullet-item bg-info-light" data-bs-theme="light"></div>
|
||||
<div>
|
||||
<h6 class="mb-0 text-body fw-semibold mb-2">Flight (Package)</h6>
|
||||
<h5 class="mb-0 text-body">$135,659,500</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 text-danger">
|
||||
<span class="fw-bold"
|
||||
data-feather="trending-down"
|
||||
style="width: 24px;
|
||||
height: 24px"></span>
|
||||
<p class="mb-0 fw-bold">11.09%</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex gap-2">
|
||||
<div class="bullet-item bg-warning-light" data-bs-theme="light"></div>
|
||||
<div>
|
||||
<h6 class="mb-0 text-body fw-semibold mb-2">Hotel</h6>
|
||||
<h5 class="mb-0 text-body">$271,319,000</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 text-warning">
|
||||
<span class="fw-bold"
|
||||
data-feather="trending-up"
|
||||
style="width: 24px;
|
||||
height: 24px"></span>
|
||||
<p class="mb-0 fw-bold">29.98%</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex gap-2">
|
||||
<div class="bullet-item bg-success-light" data-bs-theme="light"></div>
|
||||
<div>
|
||||
<h6 class="mb-0 text-body fw-semibold mb-2">Hotel (Package)</h6>
|
||||
<h5 class="mb-0 text-body">$162,791,400</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2 text-success">
|
||||
<span class="fw-bold"
|
||||
data-feather="trending-up"
|
||||
style="width: 24px;
|
||||
height: 24px"></span>
|
||||
<p class="mb-0 fw-bold">03.90%</p>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="d-none" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
/* Car Chart By Make */
|
||||
const getColor = (name, dom = document.documentElement) => {
|
||||
return getComputedStyle(dom).getPropertyValue(`--phoenix-${name}`).trim();
|
||||
};
|
||||
const handleTooltipPosition = ([pos, , dom, , size]) => {
|
||||
// only for mobile device
|
||||
if (window.innerWidth <= 540) {
|
||||
const tooltipHeight = dom.offsetHeight;
|
||||
const obj = {top: pos[1] - tooltipHeight - 20};
|
||||
obj[pos[0] < size.viewSize[0] / 2 ? 'left' : 'right'] = 5;
|
||||
return obj;
|
||||
}
|
||||
return null; // else default behaviour
|
||||
};
|
||||
|
||||
|
||||
const carData = {{ car|safe }}
|
||||
const carNames = carData.map(item => item.id_car_make__name);
|
||||
const carCounts = carData.map(item => item.count);
|
||||
|
||||
const car_chart = echarts.init(document.getElementById('car-chart-by-make'));
|
||||
option = {
|
||||
color: getColor("danger"),
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
position: (...params) => handleTooltipPosition(params),
|
||||
padding: [7, 10],
|
||||
axisPointer: {
|
||||
type: 'none'
|
||||
},
|
||||
},
|
||||
extraCssText: 'z-index: 1000',
|
||||
responsive: true,
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: carNames,
|
||||
axisLine: {
|
||||
show: true,
|
||||
lineStyle: {color: getColor('secondary-bg')}
|
||||
},
|
||||
axisTick: {
|
||||
show: false
|
||||
},
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'value',
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: 3,
|
||||
backgroundStyle: {
|
||||
borderRadius: [0.5, 0.5, 0, 0],
|
||||
},
|
||||
data: carCounts
|
||||
},
|
||||
],
|
||||
grid: {
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
left: 10,
|
||||
right: 10,
|
||||
containLabel: false
|
||||
}
|
||||
};
|
||||
|
||||
car_chart.setOption(option);
|
||||
|
||||
/* Car Status Chart */
|
||||
const chartElContainer = document.querySelector('.echart-cars-by-status-container');
|
||||
const car_status = echarts.init(document.getElementById('echart-cars-by-status'));
|
||||
const chartLabel = chartElContainer.querySelector('[data-label]');
|
||||
const data = [
|
||||
{value: {{available_cars}}, name: '{{ _("Available") }}'},
|
||||
{value: {{sold_cars}}, name: '{{ _("Sold")}}'},
|
||||
{value: {{reserved_cars}}, name: '{{ _("Reserved") }}'},
|
||||
{value: {{transfer_cars}}, name: '{{ _("Transfer") }}'},
|
||||
{value: {{hold_cars}}, name: '{{ _("Hold") }}'},
|
||||
{value: {{damaged_cars}}, name: '{{ _("Damaged") }}'}
|
||||
];
|
||||
const totalCars = data.reduce((acc, val) => val.value + acc, 0);
|
||||
if (chartLabel) {
|
||||
chartLabel.innerHTML = totalCars;
|
||||
}
|
||||
option = {
|
||||
color: [
|
||||
getColor('success'),
|
||||
getColor('warning'),
|
||||
getColor('danger'),
|
||||
getColor('primary'),
|
||||
getColor('warning-lighter'),
|
||||
getColor('secondary-dark')
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
padding: [7, 10],
|
||||
backgroundColor: getColor('body-highlight-bg'),
|
||||
borderColor: getColor('border-color'),
|
||||
textStyle: {color: getColor('light-text-emphasis')},
|
||||
borderWidth: 1,
|
||||
transitionDuration: 0,
|
||||
extraCssText: 'z-index: 1000'
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
|
||||
series: [
|
||||
{
|
||||
name: '',
|
||||
type: 'pie',
|
||||
radius: ['55%', '90%'],
|
||||
startAngle: 90,
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderColor: getColor('body-bg'),
|
||||
borderWidth: 3
|
||||
},
|
||||
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data
|
||||
}
|
||||
],
|
||||
grid: {
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
containLabel: false
|
||||
}
|
||||
};
|
||||
car_status.setOption(option);
|
||||
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,58 +1,63 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-5 pb-3 border-bottom">
|
||||
<h2 class="h3 fw-bold mb-0">Sales & Leads Dashboard <i class="fas fa-chart-area text-primary ms-2"></i></h2>
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Last 30 Days
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow">
|
||||
<li><a class="dropdown-item" href="#">Today</a></li>
|
||||
<li><a class="dropdown-item" href="#">Last 7 Days</a></li>
|
||||
<li><a class="dropdown-item" href="#">Last 90 Days</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
||||
<h2 class="h3 fw-bold mb-3 mb-md-0">{% trans "Sales Dashboard" %} <i class="fas fa-chart-area text-primary ms-2"></i></h2>
|
||||
<form method="GET" class="date-filter-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="start-date" class="form-label">{% trans "Start Date" %}</label>
|
||||
<input type="date" class="form-control" id="start-date" name="start_date"
|
||||
value="{{ start_date|date:'Y-m-d' }}" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<label for="end-date" class="form-label">{% trans "End Date" %}</label>
|
||||
<input type="date" class="form-control" id="end-date" name="end_date"
|
||||
value="{{ end_date|date:'Y-m-d' }}" required>
|
||||
</div>
|
||||
<div class="col-12 col-md-4 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-primary w-100">{% trans "Apply Filter" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-sm-6 col-md-4">
|
||||
<h3 class="fw-bold mb-3">{% trans "Inventory KPIs" %}</h3>
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Total Cars Sold</p>
|
||||
<h4 class="fw-bolder text-success mb-3">{{ sold_cars }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Cars in Inventory" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4">
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">Used Cars Sold</p>
|
||||
<h4 class="fw-bolder text-info mb-3">{{ sold_used_cars }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars in Inventory" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_new_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-md-4">
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body d-flex flex-column justify-content-between p-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">New Cars Sold</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ sold_new_cars }}</h4>
|
||||
</div>
|
||||
<span class="badge bg-success-subtle text-success fw-bold p-2 rounded-pill d-inline-flex align-self-start">
|
||||
+5 units from last month
|
||||
</span>
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars in Inventory" %}</p>
|
||||
<h4 class="fw-bolder text-primary mb-3">{{ total_used_cars_in_inventory }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6 col-md-4 col-lg-3">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body p-4">
|
||||
<p class="text-uppercase text-danger fw-bold small mb-1"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{% trans "Aging Inventory (> 60 days)" %}</a></p>
|
||||
<h4 class="fw-bolder text-danger mb-3"><a class="text-danger" href="{% url 'aging_inventory_list' request.dealer.slug %}">{{ aging_inventory_count }}</a></h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -62,60 +67,27 @@
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Monthly Cars Sold</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="CarsSoldByMonthChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Sales by Make</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="salesByBrandChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Top 5 Lead Sources</h5>
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Top Lead Sources" %}</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="leadSourcesChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Appointments by Weekday</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="appointmentsChart"></canvas>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">{% trans "Lead Conversion Funnel" %}</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="leadFunnelChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-header bg-white border-bottom-0">
|
||||
<h5 class="fw-bold mb-0 text-dark">Lead Conversion Funnel</h5>
|
||||
</div>
|
||||
<div class="card-body d-flex align-items-center justify-content-center" style="height: 400px;">
|
||||
<canvas id="leadFunnelChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
@ -124,233 +96,174 @@
|
||||
|
||||
{% block customJS%}
|
||||
<script>
|
||||
// Define a custom color palette that matches the Phoenix template
|
||||
// Define your color palette at the top
|
||||
const primaryColor = '#7249b6'; // A vibrant purple
|
||||
const secondaryColor = '#8193a6'; // A muted gray/blue
|
||||
const successColor = '#00d074'; // A bright green
|
||||
const infoColor = '#0085ff'; // A medium blue
|
||||
const warningColor = '#ffc83b'; // A yellow
|
||||
const dangerColor = '#e63757'; // A deep red
|
||||
const infoColor = '#17a2b8'; // Correcting the missing variable
|
||||
const warningColor = '#ffc107'; // Add other colors if needed
|
||||
|
||||
// Sales by Month Chart (Bar Chart)
|
||||
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
|
||||
new Chart(ctx1, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
|
||||
datasets: [{
|
||||
label: 'Total Cars Sold',
|
||||
data: [2, 3, 10, 4, 30, 12, 8, 9, 20, 12, 15, 35],
|
||||
backgroundColor: primaryColor,
|
||||
borderColor: primaryColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' },
|
||||
ticks: { color: secondaryColor }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false },
|
||||
ticks: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
const chartColors = [
|
||||
'#7249b6', '#00d074', '#e63757', '#17a2b8', '#ffc107',
|
||||
'#8193a6', '#28a745', '#6c757d', '#fd7e14', '#dc3545',
|
||||
'#20c997', '#6f42c1', '#e83e8c', '#6610f2', '#007bff',
|
||||
'#495057'
|
||||
];
|
||||
|
||||
// Pass translated strings from Django to JavaScript
|
||||
const translatedStrings = {
|
||||
numberOfLeads: "{% trans 'Number of Leads' %}",
|
||||
leads: "{% trans 'Leads' %}",
|
||||
numberOfOpportunities: "{% trans 'Number of Opportunities' %}"
|
||||
};
|
||||
|
||||
// Get the canvas and message elements
|
||||
const ctx_leadSources = document.getElementById('leadSourcesChart').getContext('2d');
|
||||
const leadSourcesMessage = document.getElementById('leadSourcesMessage');
|
||||
|
||||
// Parse the JSON data from Django
|
||||
const leadSourcesLabels = JSON.parse('{{ lead_sources_labels_json|safe }}');
|
||||
const leadSourcesCounts = JSON.parse('{{ lead_sources_counts_json|safe }}');
|
||||
|
||||
// Check if there is any data to display
|
||||
if (leadSourcesCounts.length > 0) {
|
||||
// Show the chart and hide the message
|
||||
ctx_leadSources.canvas.style.display = 'block';
|
||||
if (leadSourcesMessage) {
|
||||
leadSourcesMessage.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Sales by Make (Pie Chart)
|
||||
const ctx3 = document.getElementById('salesByBrandChart').getContext('2d');
|
||||
new Chart(ctx3, {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: ['Toyota', 'Ford', 'Honda', 'BMW', 'Other'],
|
||||
datasets: [{
|
||||
label: 'Car Count by Make',
|
||||
data: [45, 30, 25, 15, 10],
|
||||
backgroundColor: [primaryColor, successColor, warningColor, infoColor, secondaryColor],
|
||||
hoverOffset: 15,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right',
|
||||
labels: { color: secondaryColor, font: { size: 14 } }
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const label = context.label || '';
|
||||
const value = context.parsed || 0;
|
||||
const total = context.dataset.data.reduce((a, b) => a + b, 0);
|
||||
const percentage = ((value / total) * 100).toFixed(2);
|
||||
return `${label}: ${value} cars (${percentage}%)`;
|
||||
new Chart(ctx_leadSources, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: leadSourcesLabels,
|
||||
datasets: [{
|
||||
label: translatedStrings.numberOfLeads,
|
||||
data: leadSourcesCounts,
|
||||
backgroundColor: infoColor,
|
||||
borderColor: infoColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return `${translatedStrings.leads}: ${context.parsed.x}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
title: { display: true, text: translatedStrings.numberOfLeads, color: secondaryColor },
|
||||
ticks: {
|
||||
color: secondaryColor,
|
||||
callback: function(value) {
|
||||
if (Number.isInteger(value)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' }
|
||||
},
|
||||
y: {
|
||||
grid: { display: false },
|
||||
ticks: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Hide the chart and show the message
|
||||
ctx_leadSources.canvas.style.display = 'none';
|
||||
if (leadSourcesMessage) {
|
||||
leadSourcesMessage.style.display = 'flex';
|
||||
}
|
||||
});
|
||||
|
||||
// Top 5 Lead Sources (Horizontal Bar Chart)
|
||||
const ctx_leadSources = document.getElementById('leadSourcesChart').getContext('2d');
|
||||
new Chart(ctx_leadSources, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Showroom', 'Referrals', 'WhatsApp', 'Facebook', 'TikTok'],
|
||||
datasets: [{
|
||||
label: 'Number of Leads',
|
||||
data: [45, 35, 25, 20, 15],
|
||||
backgroundColor: infoColor,
|
||||
borderColor: infoColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
title: { display: true, text: 'Number of Leads', color: secondaryColor },
|
||||
ticks: { color: secondaryColor },
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' }
|
||||
},
|
||||
y: {
|
||||
grid: { display: false },
|
||||
ticks: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Appointments by Weekday (Bar Chart)
|
||||
const ctx_appointments = document.getElementById('appointmentsChart').getContext('2d');
|
||||
new Chart(ctx_appointments, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
|
||||
datasets: [{
|
||||
label: 'Number of Appointments',
|
||||
data: [10, 15, 20, 18, 25, 30, 12],
|
||||
backgroundColor: successColor,
|
||||
borderColor: successColor,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
title: { display: true, text: 'Day of the Week', color: secondaryColor },
|
||||
ticks: { color: secondaryColor },
|
||||
grid: { display: false }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: { display: true, text: 'Number of Appointments', color: secondaryColor },
|
||||
ticks: { color: secondaryColor },
|
||||
grid: { color: 'rgba(0, 0, 0, 0.05)' }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Lead Conversion Funnel (Horizontal Bar Chart)
|
||||
const ctx_funnel = document.getElementById('leadFunnelChart').getContext('2d');
|
||||
const funnelData = {
|
||||
labels: ['Initial Contact', 'Qualified Leads', 'Test Drives', 'Negotiation', 'Closed Deals'],
|
||||
data: [250, 180, 120, 80, 45],
|
||||
};
|
||||
new Chart(ctx_funnel, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: funnelData.labels,
|
||||
datasets: [{
|
||||
label: 'Number of Leads',
|
||||
data: funnelData.data,
|
||||
backgroundColor: [
|
||||
primaryColor,
|
||||
infoColor,
|
||||
successColor,
|
||||
warningColor,
|
||||
dangerColor
|
||||
],
|
||||
borderColor: [
|
||||
primaryColor,
|
||||
infoColor,
|
||||
successColor,
|
||||
warningColor,
|
||||
dangerColor
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const totalLeads = funnelData.data[0];
|
||||
const currentLeads = context.parsed.x;
|
||||
const percentage = ((currentLeads / totalLeads) * 100).toFixed(1);
|
||||
return `Leads: ${currentLeads} (${percentage}%)`;
|
||||
const leadFunnelMessage = document.getElementById('leadFunnelMessage');
|
||||
|
||||
// Parse the dynamic data from Django
|
||||
const opportunityStagesLabels = JSON.parse('{{ opportunity_stage_labels_json|safe }}');
|
||||
const opportunityStagesCounts = JSON.parse('{{ opportunity_stage_counts_json|safe }}');
|
||||
|
||||
if (opportunityStagesCounts.length > 0) {
|
||||
// Show the chart and hide the message
|
||||
ctx_funnel.canvas.style.display = 'block';
|
||||
if (leadFunnelMessage) {
|
||||
leadFunnelMessage.style.display = 'none';
|
||||
}
|
||||
|
||||
// Get a subset of colors based on the number of data points
|
||||
const backgroundColors = chartColors.slice(0, opportunityStagesCounts.length);
|
||||
|
||||
new Chart(ctx_funnel, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: opportunityStagesLabels,
|
||||
datasets: [{
|
||||
label: translatedStrings.numberOfOpportunities,
|
||||
data: opportunityStagesCounts,
|
||||
// Use the new backgroundColors array
|
||||
backgroundColor: backgroundColors,
|
||||
// Set borders to match the fill color
|
||||
borderColor: backgroundColors,
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
||||
titleColor: '#fff',
|
||||
bodyColor: '#fff',
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
const totalOpportunities = opportunityStagesCounts[0] || 0;
|
||||
const currentOpportunities = context.parsed.x;
|
||||
const percentage = totalOpportunities > 0 ? ((currentOpportunities / totalOpportunities) * 100).toFixed(1) : 0;
|
||||
return `${translatedStrings.leads}: ${currentOpportunities} (${percentage}%)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
grid: { display: false },
|
||||
ticks: { color: secondaryColor }
|
||||
scales: {
|
||||
x: {
|
||||
beginAtZero: true,
|
||||
display: false
|
||||
},
|
||||
y: {
|
||||
grid: { display: false },
|
||||
ticks: { color: secondaryColor }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Hide the chart and show the message
|
||||
ctx_funnel.canvas.style.display = 'none';
|
||||
if (leadFunnelMessage) {
|
||||
leadFunnelMessage.style.display = 'flex';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -125,7 +125,7 @@
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
{% comment %} <div class="d-flex justify-content-end gap-2">
|
||||
{% if dealer.user.userplan.is_expired %}
|
||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
|
||||
{% endif %}
|
||||
@ -135,6 +135,15 @@
|
||||
{% if not dealer.user.userplan %}
|
||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-success"><span class="fas fa-cart-plus me-2"></span>{{ _("Subscribe Now") }}</a>
|
||||
{% endif %}
|
||||
</div> {% endcomment %}
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
{% if not dealer.user.userplan %}
|
||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-success"><span class="fas fa-cart-plus me-2"></span>{{ _("Subscribe Now") }}</a>
|
||||
{% elif dealer.user.userplan.is_expired %}
|
||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
|
||||
{% elif dealer.user.userplan.plan.name != "Enterprise" %}
|
||||
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-primary"><span class="fas fa-rocket me-2"></span>{{ _("Upgrade Plan") }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -3,16 +3,10 @@
|
||||
{% block content %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<div id="dashboard-content"
|
||||
hx-get="{% if request.is_dealer %}
|
||||
{% url 'dealer_dashboard' %}
|
||||
{% elif request.is_manger %}
|
||||
{% url 'manager_dashboard' %}
|
||||
{% elif request.is_sales %}
|
||||
{% url 'sales_dashboard' %}
|
||||
{% elif request.is_inventory %}
|
||||
{% url 'inventory_dashboard' %}
|
||||
{% else %}
|
||||
{% url 'accountant_dashboard' %}
|
||||
hx-get="{% if request.is_sales and not request.is_manager %}
|
||||
{% url 'sales_dashboard' request.dealer.slug %}
|
||||
{% else %}
|
||||
{% url 'general_dashboard' request.dealer.slug %}
|
||||
{% endif %}"
|
||||
hx-trigger="load"
|
||||
hx-target="#dashboard-content"
|
||||
@ -23,4 +17,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@ -3,75 +3,72 @@
|
||||
{% block title %}
|
||||
{{ _("View Bank Account") }}
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade"
|
||||
id="deleteModal"
|
||||
data-bs-backdrop="static"
|
||||
data-bs-keyboard="false"
|
||||
tabindex="-1"
|
||||
aria-labelledby="deleteModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-sm ">
|
||||
<div class="modal-content rounded">
|
||||
<div class="modal-body d-flex justify-content-center">
|
||||
<h1 class="text-danger me-2">
|
||||
<i class="bi bi-exclamation-diamond-fill"></i>
|
||||
</h1>
|
||||
<span class="text-danger">{% trans "Are you sure you want to delete this bank account?" %}</span>
|
||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content rounded-4 shadow-lg border-0">
|
||||
<div class="modal-body p-5 text-center">
|
||||
<div class="d-flex justify-content-center align-items-center mb-4">
|
||||
<svg class="h-6 w-6 text-danger" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.398 16c-.77 1.333.192 3 1.732 3z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h5 class="mb-2">{% trans "Are you sure you want to delete this bank account?" %}</h5>
|
||||
<p class="text-secondary small">This action cannot be undone.</p>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-phoenix-secondary"
|
||||
data-bs-dismiss="modal">{% trans 'No' %}</button>
|
||||
<a type="button"
|
||||
class="btn btn-sm btn-phoenix-danger"
|
||||
href="{% url 'bank_account_delete' request.dealer.slug bank_account.pk %}">{% trans 'Yes' %}</a>
|
||||
<div class="modal-footer d-flex justify-content-center border-0 p-3 bg-200 rounded-bottom-4">
|
||||
<button type="button" class="btn btn-sm btn-phoenix-secondary me-2 rounded-pill" data-bs-dismiss="modal">{% trans 'No' %}</button>
|
||||
<a href="{% url 'bank_account_delete' request.dealer.slug bank_account.pk %}" class="btn btn-sm btn-phoenix-danger rounded-pill">{% trans 'Yes' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-5">
|
||||
<div class="card rounded ">
|
||||
<div class="card-header bg-primary text-white ">
|
||||
<p class="mb-0">{{ _("Bank Account Details") }}</p>
|
||||
|
||||
<!-- Main Content Container -->
|
||||
<div class="container my-5">
|
||||
<div class="card rounded-4 border-0">
|
||||
<!-- Card Header -->
|
||||
<div class="card-header p-4 rounded-top-4">
|
||||
<h4 class="mb-0 fw-bold">{{ _("Bank Account Details") }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
|
||||
<!-- Card Body - Details Section -->
|
||||
<div class="card-body p-5">
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<p>
|
||||
<strong>{{ _("Bank Account Name") }}:</strong> {{ bank_account.name }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ _("Cash Account") }}:</strong> {{ bank_account }}
|
||||
</p>
|
||||
<p class="mb-1 text-muted">{{ _("Bank Account Name") }}</p>
|
||||
<h5 class="fw-bold text-dark">{{ bank_account.name }}</h5>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p>
|
||||
<strong>{{ _("Amount") }}:</strong> {{ bank_account.amount }}
|
||||
</p>
|
||||
<p class="mb-1 text-muted">{{ _("Amount") }}</p>
|
||||
<h5 class="fw-bold text-success">{{ bank_account.amount }}</h5>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<p class="mb-1 text-muted">{{ _("Cash Account") }}</p>
|
||||
<h5 class="fw-bold text-dark">{{ bank_account }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex ">
|
||||
|
||||
<!-- Card Footer - Action Buttons -->
|
||||
<div class="card-footer bg-light border-0 p-4 d-flex justify-content-end align-items-center">
|
||||
{% if perms.django_ledger.change_bankaccountmodel %}
|
||||
<a class="btn btn-sm btn-phoenix-primary me-1"
|
||||
<a class="btn btn-sm btn-phoenix-primary me-2 rounded-pill"
|
||||
href="{% url 'bank_account_update' request.dealer.slug bank_account.pk %}">
|
||||
<!--<i class="bi bi-pencil-square"></i> -->
|
||||
{{ _("Edit") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.django_ledger.delete_bankaccountmodel %}
|
||||
<a class="btn btn-sm btn-phoenix-danger me-1"
|
||||
<a class="btn btn-sm btn-phoenix-danger me-2 rounded-pill"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#deleteModal">
|
||||
<!--<i class="bi bi-trash-fill"></i>-->
|
||||
{{ _("Delete") }}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-phoenix-secondary"
|
||||
<a class="btn btn-sm btn-phoenix-secondary rounded-pill"
|
||||
href="{% url 'bank_account_list' request.dealer.slug %}">
|
||||
<!--<i class="bi bi-arrow-left-square-fill"></i>-->
|
||||
{% trans "Back to List" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_filters %}
|
||||
{% block title %}
|
||||
{# Check if an 'object' exists in the context #}
|
||||
{% if object %}
|
||||
{% trans 'Update Purchase Order' %}
|
||||
{% else %}
|
||||
@ -11,39 +10,47 @@
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<!---->
|
||||
<div class="row justify-content-center mt-5 mb-3">
|
||||
|
||||
<div class="col-lg-8 col-md-10 ">
|
||||
<div class="card shadow-sm border-0 rounded-3">
|
||||
<div class="card-header bg-gray-300 py-3 border-0 rounded-top-3">
|
||||
<h3 class="mb-0 fs-4 text-center ">
|
||||
{{ _("Add New Purchase Order") }}<span class="fas fa-cart-plus ms-2 text-primary "></span>
|
||||
</h3>
|
||||
|
||||
<main class="d-flex align-items-center justify-content-center min-vh-100 py-5">
|
||||
<div class="col-12 col-lg-8 col-xl-7">
|
||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp">
|
||||
<div class="card-header bg-gradient py-4 border-0 rounded-top-4">
|
||||
<div class="d-flex justify-content-between align-items-center text-white">
|
||||
<a href="{% url 'purchase_order_list' request.dealer.slug request.entity.slug %}" class="btn btn-sm btn-light-subtle text-primary">
|
||||
<i class="fa-solid fa-circle-arrow-left me-2"></i>{% trans "Back" %}
|
||||
</a>
|
||||
<h3 class="mb-0 fs-4 fw-bold text-center flex-grow-1">
|
||||
{% if object %}
|
||||
{% trans 'Update Purchase Order' %}<i class="fa-solid fa-file-pen ms-2"></i>
|
||||
{% else %}
|
||||
{% trans 'Create Purchase Order' %}<i class="fa-solid fa-file-circle-plus ms-2"></i>
|
||||
{% endif %}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body bg-light-subtle">
|
||||
<form class="row g-3 " method="post" class="form" enctype="multipart/form-data" novalidate >
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form|crispy }}
|
||||
{% for error in form.errors %}
|
||||
<div class="text-danger">{{ error }}</div>
|
||||
{% endfor %}
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<form method="post" enctype="multipart/form-data" novalidate>
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
{{ form|crispy }}
|
||||
|
||||
<hr class="my-2">
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-center mt-3">
|
||||
<button class="btn btn-lg btn-phoenix-primary md-me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
||||
<a href="{% url 'purchase_order_list' request.dealer.slug request.entity.slug %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger mt-4" role="alert">
|
||||
<h4 class="alert-heading small">{% trans "Please correct the following errors:" %}</h4>
|
||||
<ul class="mb-0">
|
||||
{% for field, errors in form.errors.items %}
|
||||
<li><strong>{{ field|capfirst }}:</strong> {% for error in errors %}{{ error }}{% endfor %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-grid gap-3 d-sm-flex justify-content-sm-center mt-4">
|
||||
<button type="submit" class="btn btn-phoenix-primary btn-lg px-5"><i class="fa-solid fa-floppy-disk me-2"></i>{% trans "Save" %}</button>
|
||||
<a href="{% url 'purchase_order_list' request.dealer.slug request.entity.slug %}" class="btn btn-phoenix-secondary btn-lg px-5"><i class="fa-solid fa-ban me-2"></i>{% trans "Cancel" %}</a>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
{% endblock %}
|
||||
</main>
|
||||
{% endblock %}
|
||||
@ -141,7 +141,7 @@
|
||||
<div class="card shadow-sm border-0 rounded-3">
|
||||
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
|
||||
<h3 class="mb-0 fs-4 text-center">
|
||||
{% trans "Create Quotation" %}<i class="fa-regular fa-file-lines text-primary me-2"></i>
|
||||
{% trans "Create Quotation" %}<i class="fa-regular fa-file-lines text-primary ms-2"></i>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body bg-light-subtle">
|
||||
|
||||
@ -1,108 +1,118 @@
|
||||
{% extends "base.html" %}
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% block title %}
|
||||
{{ user_.name }}
|
||||
{% endblock title %}
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<a href="{% url 'user_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-sm me-3">
|
||||
<i class="fa-regular fa-circle-left me-2"></i>{% trans "Back to Staffs" %}
|
||||
</a>
|
||||
<h1 class="h5 fw-bold mb-0">{% trans "Staff Profile" %}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'user_update' request.dealer.slug user_.slug %}" class="btn btn-phoenix-primary btn-sm me-2">
|
||||
<i class="fa-solid fa-pen-to-square me-2"></i>{% trans "Edit" %}
|
||||
</a>
|
||||
<button class="btn btn-phoenix-danger btn-sm delete-btn" data-url="{% url 'user_delete' request.dealer.slug user_.slug %}"
|
||||
data-message='{% trans "Are you sure you want to delete this user?" %}'
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
<i class="fas fa-trash-alt me-2"></i>{% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
<div class="container py-4">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4">
|
||||
<div class="d-flex align-items-center mb-3 mb-md-0">
|
||||
<a href="{% url 'user_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-sm me-3">
|
||||
<i class="fa-regular fa-circle-left me-2"></i>{% trans "Back to Staff" %}
|
||||
</a>
|
||||
<h1 class="h4 fw-bold mb-0">{% trans "Staff Profile" %}</h1>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a href="{% url 'user_update' request.dealer.slug user_.slug %}" class="btn btn-phoenix-primary btn-sm">
|
||||
<i class="fa-solid fa-pen-to-square me-2"></i>{% trans "Edit Profile" %}
|
||||
</a>
|
||||
<button class="btn btn-phoenix-danger btn-sm delete-btn" data-url="{% url 'user_delete' request.dealer.slug user_.slug %}"
|
||||
data-message='{% trans "Are you sure you want to delete this user?" %}'
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal">
|
||||
<i class="fas fa-trash-alt me-2"></i>{% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-4">
|
||||
<div class="card h-100 border-0 shadow-sm rounded-3">
|
||||
<div class="card-body text-center p-3">
|
||||
<div class="position-relative d-inline-block mb-3">
|
||||
{% if user_.logo %}
|
||||
<img class="rounded-circle border border-4 border-body-tertiary"
|
||||
src="{{ user_.logo.url }}" alt="{{ user_.name }}"
|
||||
style="object-fit: cover; width: 100px; height: 100px;">
|
||||
{% else %}
|
||||
<div class="rounded-circle d-inline-flex align-items-center justify-content-center bg-primary text-white"
|
||||
style="width: 100px; height: 100px; font-size: 2.5rem;">
|
||||
{{ user_.name|first|upper }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h2 class="h6 fw-bold mb-1">{{ user_.name }}</h2>
|
||||
<p class="small mb-3"> <a href="mailto:{{ user_.email }}" class="text-secondary">{{ user_.email }}</a></p>
|
||||
<div class="d-grid gap-1">
|
||||
<a href="tel:{{ user_.phone_number }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-phone-alt me-2"></i>{{ user_.phone_number|default:"N/A" }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-4">
|
||||
<div class="card h-100 border-0 shadow-sm rounded-4 hover-lift">
|
||||
<div class="card-body d-flex flex-column align-items-center justify-content-center p-4">
|
||||
<div class="position-relative d-inline-block mb-3">
|
||||
{% if user_.logo %}
|
||||
<img class="rounded-circle border border-4 border-body-tertiary"
|
||||
src="{{ user_.logo.url }}" alt="{{ user_.name }}"
|
||||
style="object-fit: cover; width: 120px; height: 120px;">
|
||||
{% else %}
|
||||
<div class="rounded-circle d-inline-flex align-items-center justify-content-center bg-primary text-white border border-4 border-body-tertiary"
|
||||
style="width: 120px; height: 120px; font-size: 3rem;">
|
||||
{{ user_.name|first|upper }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h2 class="h5 fw-bold mb-1 text-center">{{ user_.name }}</h2>
|
||||
<p class="small text-secondary mb-2">
|
||||
<span class="badge rounded-pill bg-light text-dark fw-bold">{{ user_.groups.first.name|default:"No Role" }}</span>
|
||||
</p>
|
||||
<p class="small text-muted mb-3 text-center">
|
||||
<a href="mailto:{{ user_.email }}" >{{ user_.email }}</a>
|
||||
</p>
|
||||
<div class="d-grid w-40">
|
||||
<a href="tel:{{ user_.phone_number }}" class="btn btn-sm rounded-pill d-flex align-items-center justify-content-center gap-2">
|
||||
<i class="fas fa-phone-alt"></i><span>{{ user_.phone_number|default:"N/A" }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
<div class="card h-100 border-0 shadow-sm rounded-3">
|
||||
<div class="card-body p-3">
|
||||
<h4 class="mb-3 text-primary h6">{% trans "Personal Information" %}</h4>
|
||||
<div class="row g-2 mb-4">
|
||||
<div class="col-md-6">
|
||||
<p class="small mb-0">{% trans "Arabic Name" %}</p>
|
||||
<p class="fw-bold small mb-0">{{ user_.arabic_name|default:"N/A" }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="small mb-0">{% trans "Roles" %}</p>
|
||||
<p class="fw-bold small mb-0">
|
||||
{% for group in user_.groups %}
|
||||
{{ group.name }} {% if not forloop.last %}·{% endif %}
|
||||
{% empty %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<div class="card h-100 border-0 shadow-sm rounded-4">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="mb-4 text-primary fw-bold">{% trans "Personal Information" %}</h5>
|
||||
<dl class="row g-3 small">
|
||||
<dt class="col-sm-3 text-muted">{% trans "Name (Arabic)" %}</dt>
|
||||
<dd class="col-sm-9 fw-bold">{{ user_.arabic_name|default:"N/A" }}</dd>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h4 class="mb-0 text-primary h6">{% trans "Assigned Groups" %}</h4>
|
||||
<a class="btn btn-sm btn-phoenix-primary" href="{% url 'user_groups' request.dealer.slug user_.slug %}">
|
||||
<i class="fa-solid fa-users me-2"></i>{% trans "Manage Groups" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-responsive scrollbar">
|
||||
<table class="table table-hover table-striped fs--1 mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="text-nowrap">{% trans 'Group Name'|capfirst %}</th>
|
||||
<dt class="col-sm-3 text-muted">{% trans "Last Login" %}</dt>
|
||||
<dd class="col-sm-9 fw-bold">
|
||||
{{user_.user.last_login|date:"D, M d, Y H:i"|default:"N/A" }}
|
||||
</dd>
|
||||
|
||||
<dt class="col-sm-3 text-muted">{% trans "Status" %}</dt>
|
||||
<dd class="col-sm-9">
|
||||
{% if user_.user.is_active %}
|
||||
<span class="badge rounded-pill bg-success">{% trans "Active" %}</span>
|
||||
{% else %}
|
||||
<span class="badge rounded-pill bg-danger">{% trans "Inactive" %}</span>
|
||||
{% endif %}
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h5 class="mb-0 text-primary fw-bold">{% trans "Assigned Groups" %}</h5>
|
||||
<a class="btn btn-sm btn-phoenix-primary rounded-pill" href="{% url 'user_groups' request.dealer.slug user_.slug %}">
|
||||
<i class="fa-solid fa-users me-2"></i>{% trans "Manage Groups" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-borderless fs--1 mb-0">
|
||||
<thead class="bg-body-tertiary">
|
||||
<tr>
|
||||
<th scope="col">{% trans 'Group Name'|capfirst %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in user_.groups.all %}
|
||||
<tr class="align-middle">
|
||||
<td class="small text-dark">{{ group.name }}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in user_.groups %}
|
||||
<tr>
|
||||
<td class="small">{{ group }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td class="text-secondary small">{% trans "This user is not assigned to any groups." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td class="text-secondary small">{% trans "This user is not assigned to any groups." %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'modal/delete_modal.html' %}
|
||||
</div>
|
||||
{% include 'modal/delete_modal.html' %}
|
||||
|
||||
{% endblock %}
|
||||
@ -7,94 +7,96 @@
|
||||
{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
{% if users or request.GET.q %}
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-muted d-flex align-items-center">
|
||||
<i class="fa-solid fa-user-tie text-primary me-3 fs-2"></i>
|
||||
{% trans "Staffs" %}
|
||||
</h1>
|
||||
{% if request.user.userplan %}
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'user_create' request.dealer.slug %}" class="btn btn-phoenix-primary me-2 shadow-sm">
|
||||
<i class="fa-solid fa-plus me-2"></i>{% trans "Add New Staff" %}
|
||||
</a>
|
||||
<a href="{% url 'group_list' request.dealer.slug %}" class="btn btn-phoenix-secondary shadow-sm">
|
||||
<i class="fa-solid fa-users me-2"></i>{% trans "Manage Groups" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{%if users or request.GET.q%}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0">{% trans "Staffs" %}<i class="fa-solid fa-user-tie text-primary ms-2"></i></h1>
|
||||
{% if request.user.userplan %}
|
||||
<div>
|
||||
<a href="{% url 'user_create' request.dealer.slug %}" class="btn btn-phoenix-primary me-2">
|
||||
<i class="fa-solid fa-plus me-2"></i>{% trans "Add New Staff" %}
|
||||
</a>
|
||||
<a href="{% url 'group_list' request.dealer.slug %}" class="btn btn-phoenix-secondary">
|
||||
<i class="fa-solid fa-user-group me-2"></i>{% trans "Manage Groups" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive scrollbar">
|
||||
<table class="table table-hover table-striped mb-0">
|
||||
<thead>
|
||||
<tr class="bg-body-highlight">
|
||||
<th>{% trans 'name'|capfirst %}</th>
|
||||
<th>{% trans 'email'|capfirst %}</th>
|
||||
<th>{% trans 'phone number'|capfirst %}</th>
|
||||
<th>{% trans 'role'|capfirst %}</th>
|
||||
<th>{% trans 'actions'|capfirst %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<div class="card border-0 shadow-sm rounded-lg">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-borderless mb-0">
|
||||
<thead class="bg-light">
|
||||
<tr>
|
||||
<td class="align-middle white-space-nowrap ps-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="avatar avatar-tiny me-2">
|
||||
{% if user.logo %}
|
||||
<img class="avatar-img rounded-circle"
|
||||
src="{{ user.thumbnail.url }}"
|
||||
onerror="this.src='/static/user-logo.jpg'"
|
||||
alt="{{ user.name }}'s logo">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-light d-flex justify-content-center align-items-center" style="width: 2rem; height: 2rem; font-size: 0.8rem;">
|
||||
{{ user.name|first|upper }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="fw-bold text-decoration-none text-dark"
|
||||
href="{% url 'user_detail' request.dealer.slug user.slug %}">
|
||||
{{ user.name }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">{{ user.email }}</td>
|
||||
<td class="align-middle white-space-nowrap">{{ user.phone_number }}</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
{% for group in user.groups %}
|
||||
<span class="badge bg-primary me-1">{{ group.name|title }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="align-middle white-space-nowrap">
|
||||
<a class="btn btn-phoenix-success"
|
||||
href="{% url 'user_detail' request.dealer.slug user.slug %}">
|
||||
<i class="fa-solid fa-eye me-1"></i>{% trans 'View' %}
|
||||
</a>
|
||||
</td>
|
||||
<th class="py-3 px-4">{% trans 'Name'|capfirst %}</th>
|
||||
<th class="py-3 px-4">{% trans 'Email'|capfirst %}</th>
|
||||
<th class="py-3 px-4">{% trans 'Phone number'|capfirst %}</th>
|
||||
<th class="py-3 px-4">{% trans 'Role'|capfirst %}</th>
|
||||
<th class="py-3 px-4 text-center">{% trans 'Actions'|capfirst %}</th>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr class="border-bottom">
|
||||
<td class="align-middle py-3 px-4">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="avatar avatar-sm me-3">
|
||||
{% if user.logo %}
|
||||
<img class="avatar-img rounded-circle"
|
||||
src="{{ user.thumbnail.url }}"
|
||||
onerror="this.src='/static/user-logo.jpg'"
|
||||
alt="{{ user.name }}'s logo">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-light d-flex justify-content-center align-items-center text-secondary" style="width: 2.5rem; height: 2.5rem; font-size: 1rem;">
|
||||
{{ user.name|first|upper }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a class="fw-bold text-decoration-none text-dark"
|
||||
href="{% url 'user_detail' request.dealer.slug user.slug %}">
|
||||
{{ user.name }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="align-middle py-3 px-4 text-muted">{{ user.email }}</td>
|
||||
<td class="align-middle py-3 px-4 text-muted">{{ user.phone_number }}</td>
|
||||
<td class="align-middle py-3 px-4">
|
||||
{% for group in user.groups %}
|
||||
<span class="badge bg-primary-subtle text-primary border border-primary-subtle rounded-pill me-1 fw-normal">{{ group.name|title }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="align-middle py-3 px-4 text-center">
|
||||
<a class="btn btn-sm btn-phoenix-secondary"
|
||||
href="{% url 'user_detail' request.dealer.slug user.slug %}">
|
||||
<i class="fa-solid fa-eye me-1"></i>{% trans 'View' %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if is_paginated %}
|
||||
<div class="d-flex justify-content-center mt-3">
|
||||
<div class="d-flex justify-content-center mt-4">
|
||||
{% include 'partials/pagination.html' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{% if request.user.userplan %}
|
||||
{% url "user_create" request.dealer.slug as create_staff_url %}
|
||||
{% include "empty-illustration-page.html" with value="staff" url=create_staff_url %}
|
||||
{% else %}
|
||||
{% url "pricing_page" request.dealer.slug as pricing_page_url %}
|
||||
{% include "message-illustration.html" with value1=_("No active plan, Please create a subscription plan.") value2=_("Buy Plan") url=pricing_page_url %}
|
||||
{% if request.user.userplan %}
|
||||
{% url "user_create" request.dealer.slug as create_staff_url %}
|
||||
{% include "empty-illustration-page.html" with value="staff" url=create_staff_url %}
|
||||
{% else %}
|
||||
{% url "pricing_page" request.dealer.slug as pricing_page_url %}
|
||||
{% include "message-illustration.html" with value1=_("No active plan, Please create a subscription plan.") value2=_("Buy Plan") url=pricing_page_url %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,22 +1,39 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card mt-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Set New Password</h2>
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Change Password
|
||||
</button>
|
||||
</form>
|
||||
<main class="d-flex align-items-center justify-content-center min-vh-100 py-5">
|
||||
<div class="col-12 col-sm-10 col-md-8 col-lg-6 col-xl-5">
|
||||
<div class="card shadow-lg border-0 rounded-4 overflow-hidden animate__animated animate__fadeInUp" style="background-color: #f8f9fa;">
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<div class="text-center mb-4">
|
||||
<h1 class="h3 fw-bold text-dark mb-2">{% trans "Set New Password" %}</h1>
|
||||
<p class="text-muted">{% trans "Enter your new password below." %}</p>
|
||||
</div>
|
||||
<form method="post" class="needs-validation" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
{% for field in form %}
|
||||
<div class="form-floating mb-3">
|
||||
{{ field.errors }}
|
||||
{{ field }}
|
||||
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<div class="d-grid mt-4">
|
||||
<button type="submit" class="btn btn-primary btn-lg rounded-pill fw-bold">
|
||||
{% trans "Change Password" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer bg-light border-0 py-3 text-center">
|
||||
<small class="text-muted">
|
||||
{% trans "Remember to choose a strong password." %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user