Merge pull request 'Dashboards and Sale reports: Sale reports finances update and dashboards cards logic' (#180) from frontend into main

Reviewed-on: #180
This commit is contained in:
ismail 2025-08-17 14:00:59 +03:00
commit 870bcfdcec
23 changed files with 2892 additions and 1500 deletions

View File

@ -583,6 +583,7 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
"price_": str(self.price_), "price_": str(self.price_),
"taxable": self.taxable, "taxable": self.taxable,
"uom": self.uom, "uom": self.uom,
"service_tax":str(self.service_tax)
} }
@property @property
@ -593,6 +594,13 @@ class AdditionalServices(models.Model, LocalizedNameMixin):
if self.taxable if self.taxable
else self.price else self.price
) )
@property
def service_tax(self):
vat = VatRate.objects.filter(dealer=self.dealer, is_active=True).first()
return (
Decimal(self.price * vat.rate)
)
class Meta: class Meta:
verbose_name = _("Additional Services") verbose_name = _("Additional Services")
@ -865,19 +873,64 @@ class Car(Base):
# #
@property @property
def get_additional_services_amount(self): def get_additional_services_amount(self):
return sum([Decimal(x.price) for x in self.additional_services.all()])
@property
def get_additional_services_amount_(self):
return sum([Decimal(x.price_) for x in self.additional_services.all()]) return sum([Decimal(x.price_) for x in self.additional_services.all()])
@property
def get_additional_services_vat(self):
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return sum([Decimal((x.price)*(vat.rate)) for x in self.additional_services.all()])
def get_additional_services(self): def get_additional_services(self):
return {"services": [x for x in self.additional_services.all()],"total":self.get_additional_services_amount} vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return {"services": [[x,(x.price)*(vat.rate)] for x in self.additional_services.all()],
"total_":self.get_additional_services_amount_,
"total":self.get_additional_services_amount,
"services_vat":self.get_additional_services_vat}
@property @property
def final_price(self):
return Decimal(self.marked_price -self.discount)
@property
def vat_amount(self): def vat_amount(self):
vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first() vat = VatRate.objects.filter(dealer=self.dealer,is_active=True).first()
return Decimal(self.marked_price) * (vat.rate / 100) return Decimal(self.final_price) * (vat.rate)
@property @property
def total_vat(self): def total_services_and_car_vat(self):
return Decimal(self.marked_price) + Decimal(self.vat_amount) return self.vat_amount+self.get_additional_services()['services_vat']
@property
def final_price_plus_vat(self):
return Decimal(self.final_price) + Decimal(self.vat_amount)
@property
def final_price_plus_services_plus_vat(self):
return Decimal(self.final_price_plus_vat) + Decimal(self.get_additional_services()['total_']) #total services with vat and car_sell price with vat
# to be used after invoice is created
@property
def invoice(self):
return self.item_model.invoicemodel_set.first() or None
@property
def estimate(self):
return getattr(self.invoice,'ce_model',None)
@property
def discount(self):
if not self.estimate:
return 0
try:
instance = ExtraInfo.objects.get(
dealer=self.dealer,
content_type=ContentType.objects.get_for_model(EstimateModel),
object_id=self.estimate.pk,
)
return Decimal(instance.data.get('discount',0))
except ExtraInfo.DoesNotExist:
return Decimal(0)
# def get_discount_amount(self,estimate,user): # def get_discount_amount(self,estimate,user):
# try: # try:
# instance = models.ExtraInfo.objects.get( # instance = models.ExtraInfo.objects.get(

View File

@ -40,12 +40,15 @@ urlpatterns = [
views.assign_car_makes, views.assign_car_makes,
name="assign_car_makes", name="assign_car_makes",
), ),
path(
"dashboards/manager/",
views.ManagerDashboard.as_view(), #dashboards
name="manager_dashboard", 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/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"),
path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"), path("cars/inventory/table/", views.CarListViewTable.as_view(), name="car_table"),
path("export/format/", TableExport.export, name="export"), path("export/format/", TableExport.export, name="export"),
@ -772,6 +775,7 @@ urlpatterns = [
views.EstimateDetailView.as_view(), views.EstimateDetailView.as_view(),
name="estimate_detail", name="estimate_detail",
), ),
path('<slug:dealer_slug>/sales/estimates/print/<uuid:pk>/', views.EstimatePrintView.as_view(), name='estimate_print'),
path( path(
"<slug:dealer_slug>/sales/estimates/create/", "<slug:dealer_slug>/sales/estimates/create/",
views.create_estimate, views.create_estimate,

View File

@ -1288,18 +1288,24 @@ def get_finance_data(estimate,dealer):
) )
discount = extra_info.data.get("discount", 0) discount = extra_info.data.get("discount", 0)
discount = Decimal(discount) discount = Decimal(discount)
vat_amount = car.marked_price * vat.rate
additional_services = car.get_additional_services() additional_services = car.get_additional_services()
discounted_price=(Decimal(car.marked_price) - discount)
vat_amount = discounted_price * vat.rate
total_services_vat=sum([ x[1] for x in additional_services.get("services")])
total_vat=vat_amount+total_services_vat
return { return {
"car": car, "car": car,
"discounted_price": (Decimal(car.marked_price) - discount) or 0, "discounted_price": discounted_price or 0,
"price_before_discount": car.marked_price, "price_before_discount": car.marked_price,
"vat_amount": vat_amount, "vat_amount": vat_amount,
"vat_rate": vat.rate, "vat_rate": vat.rate,
"discount_amount": discount, "discount_amount": discount,
"additional_services": additional_services, "additional_services": additional_services,
"grand_total": (car.marked_price - discount) + vat_amount + additional_services.get("total") "final_price": discounted_price+ vat_amount,
"total_services_vat":total_services_vat,
"total_vat":total_vat,
"grand_total": discounted_price + vat_amount + additional_services.get("total")
} }

View File

@ -17,7 +17,7 @@ from random import randint
from decimal import Decimal from decimal import Decimal
from io import TextIOWrapper from io import TextIOWrapper
from django.apps import apps from django.apps import apps
from datetime import datetime, timedelta from datetime import datetime, timedelta,date
from calendar import month_name from calendar import month_name
from pyzbar.pyzbar import decode from pyzbar.pyzbar import decode
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
@ -396,7 +396,164 @@ class TestView(TemplateView):
template_name = "inventory/cars_list_api.html" 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
def DealerDashboard(request):
dealer = request.dealer
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
today_local = timezone.localdate()
start_date_str = request.GET.get('start_date')
end_date_str = request.GET.get('end_date')
if not start_date_str:
start_date = today_local - timedelta(days=30)
else:
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()
# The database query will automatically be handled in a timezone-aware manner
cars_sold_filtered = cars_sold.filter(
sold_date__date__gte=start_date,
sold_date__date__lte=end_date
)
print(cars_sold_filtered)
# # Calculate summary data for the filtered results
total_cars_sold=cars_sold_filtered.count()
print(total_cars_sold)
total_revenue_from_cars = sum([ car.final_price for car in cars_sold_filtered])
total_cost_of_cars_sold=sum([car.cost_price for car in cars_sold_filtered])
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_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])
context = {
'start_date': start_date,
'end_date': end_date,
'cars_sold': cars_sold_filtered,
'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,
'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,
}
return render(request, 'dashboards/dealer_dashboard.html', context)
class ManagerDashboard(LoginRequiredMixin, TemplateView): class ManagerDashboard(LoginRequiredMixin, TemplateView):
""" """
@ -412,7 +569,7 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
:type template_name: str :type template_name: str
""" """
template_name = "dashboards/manager.html" template_name = "dashboards/manager_dashboard.html"
# def dispatch(self, request, *args, **kwargs): # def dispatch(self, request, *args, **kwargs):
# if not request.user.is_authenticated: # if not request.user.is_authenticated:
@ -495,6 +652,9 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
return context return context
class SalesDashboard(LoginRequiredMixin, TemplateView): class SalesDashboard(LoginRequiredMixin, TemplateView):
""" """
SalesDashboard class provides a view for the sales dashboard. SalesDashboard class provides a view for the sales dashboard.
@ -513,7 +673,152 @@ class SalesDashboard(LoginRequiredMixin, TemplateView):
:type template_name: str :type template_name: str
""" """
template_name = "dashboards/sales.html" template_name = "dashboards/sales_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 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): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@ -4598,7 +4903,7 @@ def create_estimate(request, dealer_slug, slug=None):
"quantity": 1, "quantity": 1,
"unit_cost": round(float(i.marked_price)), "unit_cost": round(float(i.marked_price)),
"unit_revenue": round(float(i.marked_price)), "unit_revenue": round(float(i.marked_price)),
"total_amount": round(float(i.total_vat)),# TODO : check later "total_amount": round(float(i.final_price_plus_vat)),# TODO : check later
} }
) )
@ -4619,7 +4924,7 @@ def create_estimate(request, dealer_slug, slug=None):
# "unit_cost": instance.cost_price, # "unit_cost": instance.cost_price,
# "unit_revenue": instance.marked_price, # "unit_revenue": instance.marked_price,
# "quantity": Decimal(quantities), # "quantity": Decimal(quantities),
# "total_amount": instance.total_vat * int(quantities),# TODO : check later # "total_amount": instance.final_price_plus_vat * int(quantities),# TODO : check later
# } # }
# } # }
@ -4777,6 +5082,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
# calculator = CarFinanceCalculator(estimate) # calculator = CarFinanceCalculator(estimate)
# finance_data = calculator.get_finance_data() # finance_data = calculator.get_finance_data()
finance_data = get_finance_data(estimate,dealer) finance_data = get_finance_data(estimate,dealer)
invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first() invoice_obj = InvoiceModel.objects.all().filter(ce_model=estimate).first()
kwargs["data"] = finance_data kwargs["data"] = finance_data
@ -4785,7 +5091,7 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
car = estimate.get_itemtxs_data()[0].first().item_model.car car = estimate.get_itemtxs_data()[0].first().item_model.car
selected_items = car.additional_services.filter(dealer=dealer) selected_items = car.additional_services.filter(dealer=dealer)
form = forms.AdditionalFinancesForm() form = forms.AdditionalFinancesForm()
form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer) # TODO : check later form.fields["additional_finances"].queryset = form.fields["additional_finances"].queryset.filter(dealer=dealer) #
form.initial["additional_finances"] = selected_items form.initial["additional_finances"] = selected_items
kwargs["additionals_form"] = form kwargs["additionals_form"] = form
except Exception as e: except Exception as e:
@ -4793,6 +5099,16 @@ class EstimateDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class EstimatePrintView(EstimateDetailView):
"""
A view to render a printer-friendly version of the estimate.
It reuses the data-fetching logic from EstimateDetailView but
uses a dedicated, stripped-down print template.
"""
template_name = "sales/estimates/estimate_preview.html"
@login_required @login_required
@permission_required("inventory.add_saleorder", raise_exception=True) @permission_required("inventory.add_saleorder", raise_exception=True)
def create_sale_order(request, dealer_slug, pk): def create_sale_order(request, dealer_slug, pk):
@ -4844,7 +5160,8 @@ def create_sale_order(request, dealer_slug, pk):
f"KeyError: 'car_info' or 'status' key missing when attempting to update status to 'sold' for item.item_model PK: {getattr(item.item_model, 'pk', 'N/A')}." f"KeyError: 'car_info' or 'status' key missing when attempting to update status to 'sold' for item.item_model PK: {getattr(item.item_model, 'pk', 'N/A')}."
) )
pass pass
item.item_model.car.sold_date=timezone.now() # to be checked added by faheed
item.item_model.car.save()# to be checked added byfaheed
item.item_model.car.mark_as_sold() item.item_model.car.mark_as_sold()
messages.success(request, "Sale Order created successfully") messages.success(request, "Sale Order created successfully")
@ -10491,7 +10808,7 @@ def upload_cars(request, dealer_slug, pk=None):
response = redirect("upload_cars", dealer_slug=dealer_slug, pk=pk) response = redirect("upload_cars", dealer_slug=dealer_slug, pk=pk)
if po_item.status == "uploaded": if po_item.status == "uploaded":
messages.add_message(request, messages.ERROR, "Item already uploaded.") messages.add_message(request, messages.SUCCESS, "Item uploaded Sucessfully.")
return redirect( return redirect(
"view_items_inventory", "view_items_inventory",
dealer_slug=dealer_slug, dealer_slug=dealer_slug,
@ -10810,12 +11127,14 @@ def purchase_report_csv_export(request,dealer_slug):
def car_sale_report_view(request, dealer_slug): def car_sale_report_view(request, dealer_slug):
dealer = get_object_or_404(models.Dealer, slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
cars_sold = models.Car.objects.filter(dealer=dealer, status='sold') cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
# Get filter parameters from the request # Get filter parameters from the request
selected_make = request.GET.get('make') selected_make = request.GET.get('make')
selected_model = request.GET.get('model') selected_model = request.GET.get('model')
selected_serie = request.GET.get('serie') selected_serie = request.GET.get('serie')
selected_year = request.GET.get('year') selected_year = request.GET.get('year')
selected_stock_type=request.GET.get('stock_type')
# Apply filters to the queryset # Apply filters to the queryset
if selected_make: if selected_make:
@ -10826,78 +11145,115 @@ def car_sale_report_view(request, dealer_slug):
cars_sold = cars_sold.filter(id_car_serie__name=selected_serie) cars_sold = cars_sold.filter(id_car_serie__name=selected_serie)
if selected_year: if selected_year:
cars_sold = cars_sold.filter(year=selected_year) cars_sold = cars_sold.filter(year=selected_year)
if selected_stock_type:
cars_sold = cars_sold.filter(stock_type=selected_stock_type)
# Get distinct values for filter dropdowns # Get distinct values for filter dropdowns
makes = models.Car.objects.filter(dealer=dealer, status='sold').values_list('id_car_make__name', flat=True).distinct() 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() 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() 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') 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 # # Calculate summary data for the filtered results
total_cars_sold=cars_sold.count()
total_revenue_from_cars = sum([ car.final_price for car in cars_sold])
total_vat_on_cars=sum([car.vat_amount for car in cars_sold])
total_revenue = cars_sold.aggregate(total_revenue=Sum('finances__marked_price'))['total_revenue'] or 0 total_revenue_from_additonals=sum([car.get_additional_services()['total'] for car in cars_sold])
# total_vat = cars_sold.aggregate(total_vat=Sum('finances__vat_amount'))['total_vat'] or 0 total_vat_from_additonals=sum([car.get_additional_services()['services_vat'] for car in cars_sold])
total_discount = cars_sold.aggregate(total_discount=Sum('finances__discount_amount'))['total_discount'] or 0 total_vat_collected = total_vat_on_cars+total_vat_from_additonals
total_revenue_collected=total_revenue_from_cars+total_revenue_from_additonals
total_discount = sum([car.discount for car in cars_sold])
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S") current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
context = { context = {
'cars_sold': cars_sold, 'cars_sold': cars_sold,
'total_cars_sold':total_cars_sold,
'current_time': current_time, 'current_time': current_time,
'dealer': dealer, 'dealer': dealer,
'total_revenue': total_revenue, 'total_revenue_from_cars': total_revenue_from_cars,
# 'total_vat': total_vat, 'total_revenue_from_additonals':total_revenue_from_additonals,
'total_revenue_collected': total_revenue_collected,
'total_vat_on_cars':total_vat_on_cars,
'total_vat_from_additonals':total_vat_from_additonals,
'total_vat_collected':total_vat_collected,
'total_discount': total_discount, 'total_discount': total_discount,
'makes': makes, 'makes': makes,
'models': models_qs, 'models': models_qs,
'series': series, 'series': series,
'years': years, 'years': years,
'stock_types':stock_types,
'selected_make': selected_make, 'selected_make': selected_make,
'selected_model': selected_model, 'selected_model': selected_model,
'selected_serie': selected_serie, 'selected_serie': selected_serie,
'selected_year': selected_year, 'selected_year': selected_year,
'selected_stock_type':selected_stock_type,
} }
return render(request, 'ledger/reports/car_sale_report.html', context) return render(request, 'ledger/reports/car_sale_report.html', context)
def car_sale_report_csv_export(request,dealer_slug): @login_required
def car_sale_report_csv_export(request, dealer_slug):
response = HttpResponse(content_type='text/csv') response = HttpResponse(content_type='text/csv')
current_time = timezone.now().strftime("%Y-%m-%d_%H-%M-%S")
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
filename = f"sales_report_{dealer_slug}_{current_time}.csv" filename = f"sales_report_{dealer_slug}_{current_time}.csv"
response['Content-Disposition'] = f'attachment; filename="{filename}"' response['Content-Disposition'] = f'attachment; filename="{filename}"'
writer = csv.writer(response) writer = csv.writer(response)
header=[ # Define the CSV header based on your HTML table headers
'Make', header = [
'VIN', 'VIN', 'Make', 'Model', 'Year', 'Serie', 'Trim', 'Mileage',
'Model', 'Stock Type', 'Created Date', 'Sold Date', 'Cost Price',
'Year', 'Marked Price', 'Discount Amount', 'Selling Price',
'Serie', 'VAT on Car', 'Services Price', 'VAT on Services', 'Final Total',
'Trim', 'Invoice Number'
'Mileage',
'Stock Type',
'Created Date',
'Sold Date',
'Cost Price',
'Marked Price',
'Discount Amount',
'Selling Price',
'Tax Amount',
'Invoice Number',
] ]
writer.writerow(header) writer.writerow(header)
dealer=get_object_or_404(models.Dealer,slug=dealer_slug) dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
cars_sold=models.Car.objects.filter(dealer=dealer,status='sold') cars_sold = models.Car.objects.filter(dealer=dealer, status='sold')
# Apply filters from the request, just like in your HTML view
selected_make = request.GET.get('make')
selected_model = request.GET.get('model')
selected_serie = request.GET.get('serie')
selected_year = request.GET.get('year')
selected_stock_type = request.GET.get('stock_type')
if selected_make:
cars_sold = cars_sold.filter(id_car_make__name=selected_make)
if selected_model:
cars_sold = cars_sold.filter(id_car_model__name=selected_model)
if selected_serie:
cars_sold = cars_sold.filter(id_car_serie__name=selected_serie)
if selected_year:
cars_sold = cars_sold.filter(year=selected_year)
if selected_stock_type:
cars_sold = cars_sold.filter(stock_type=selected_stock_type)
# Write the data for the filtered cars
for car in cars_sold: for car in cars_sold:
writer.writerow([ # Fetching data for the additional services
additional_services = car.get_additional_services()
services_total_price = additional_services['total']
services_vat_amount = additional_services['services_vat']
# Checking for the invoice number to avoid errors on cars without one
invoice_number = None
sold_date = None
if car.invoice:
invoice_number = car.invoice.invoice_number
sold_date = car.invoice.date_paid
writer.writerow([
car.vin, car.vin,
car.id_car_make.name, car.id_car_make.name,
car.id_car_model.name, car.id_car_model.name,
@ -10907,13 +11263,16 @@ def car_sale_report_csv_export(request,dealer_slug):
car.mileage if car.mileage else '0', car.mileage if car.mileage else '0',
car.stock_type, car.stock_type,
car.created_at.strftime("%Y-%m-%d %H:%M:%S") if car.created_at else '', car.created_at.strftime("%Y-%m-%d %H:%M:%S") if car.created_at else '',
car.sold_date.strftime("%Y-%m-%d %H:%M:%S") if car.sold_date else '', sold_date.strftime("%Y-%m-%d %H:%M:%S") if sold_date else '',
car.cost_price, car.cost_price,
car.marked_price, car.marked_price,
car.discount_amount, car.discount, # Ensure this property returns a number
car.selling_price, car.final_price, # Selling Price without VAT
car.vat_amount, # TODO : check later car.vat_amount, # VAT on the car
car.item_model.invoicemodel_set.first().invoice_number services_total_price, # Total services without VAT
services_vat_amount, # VAT on services
car.final_price_plus_services_plus_vat,
invoice_number,
]) ])
return response return response

View File

@ -0,0 +1,218 @@
{% 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 %}

View File

@ -0,0 +1,349 @@
{% 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">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">
-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">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 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>
<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: [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 }
}
}
}
});
// 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: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
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: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
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: '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 %}

View File

@ -0,0 +1,300 @@
{% 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 %}

View File

@ -1,641 +0,0 @@
{% extends 'base.html' %}
{% block content %}
<div class="main-content flex-grow-1 mt-4 mb-3">
<div class="d-flex justify-content-between align-items-center mb-4">
<p class="fs-2">Dashboard Overview<i class="fas fa-chart-area ms-3 text-primary fs-3"></i></p>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
Last 30 Days
</button>
<ul class="dropdown-menu">
<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-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Total Revenue</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Net Profit</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Gross Profit</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-danger">Total Expense</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-primary">Total VAT collected</h5>
<p class="stat-value">$1.25M</p>
<span class="text-success small">+8% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-success">Cars Sold</h5>
<p class="stat-value">{{sold_cars}}</p>
<span class="text-success small">+5 units from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-danger">Average Time on Lot</h5>
<p class="stat-value">10 days</p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-warning">Inventory Value</h5>
<p class="stat-value">$5.8M</p>
<span class="text-danger small">-2% from last month</span>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card h-100 p-3">
<div class="card-body">
<h5 class="card-title text-danger">Aging Inventory</h5>
<p class="stat-value">12</p>
<span class="text-success small">-4 cars from last month</span>
</div>
</div>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8">
<div class="card h-100">
<div class="card-header">Monthly Revenue & Profit</div>
<div class="card-body chart-container">
<canvas id="revenueProfitChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card h-100">
<div class="card-header">Monthly Cars Sold</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="CarsSoldByMonthChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Inventory By Make</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="salesByBrandChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Sales By Make</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="inventoryByBrandChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Salesperson Performance</div>
<div class="card-body chart-container">
<canvas id="salespersonChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card h-100">
<div class="card-header">Top 5 Lead Sources</div>
<div class="card-body chart-container">
<canvas id="leadSourcesChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card h-100">
<div class="card-header">Appointments by Weekday</div>
<div class="card-body chart-container">
<canvas id="appointmentsChart"></canvas>
</div>
</div>
</div>
<div class="col-lg-12">
<div class="card h-100">
<div class="card-header">Lead Conversion Funnel</div>
<div class="card-body d-flex align-items-center justify-content-center">
<canvas id="leadFunnelChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
{% endblock content %}
{% block customJS%}
<script>
ctx1=document.getElementById('CarsSoldByMonthChart')
new Chart(ctx1,{
type: 'bar',
data: {
labels:['January','February','March','April','May','June','July','August','September','October', 'November','December' ],
datasets: [{
label: 'Total Cars Sold',
data: [2,3,10,4,30,12,8,9,20,12,15,35],
borderWidth: 1
}]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
// Get the canvas context for the chart
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
// Chart.js configuration
new Chart(ctx2, {
type: 'line', // Use a line chart
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: '#007bff', // A vibrant blue
backgroundColor: 'rgba(0, 123, 255, 0.2)',
tension: 0.4, // Smooths the line
fill: true, // Fills the area under the line
pointBackgroundColor: '#007bff',
pointRadius: 5,
pointHoverRadius: 8
},
{
label: 'Monthly Net Profit',
data: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
borderColor: '#28a745', // A strong green
backgroundColor: 'rgba(40, 167, 69, 0.2)',
tension: 0.4,
fill: true,
pointBackgroundColor: '#28a745',
pointRadius: 5,
pointHoverRadius: 8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: {
color: '#495057', // Darker legend text
boxWidth: 20
}
},
tooltip: {
backgroundColor: 'rgba(52, 58, 64, 0.9)', // Darker tooltip background
titleColor: 'white',
bodyColor: 'white',
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.1)' // Lighter grid lines for better contrast
},
ticks: {
color: '#495057' // Darker text for x-axis labels
},
border: {
color: '#adb5bd' // A subtle border color
}
},
y: {
grid: {
color: 'rgba(0, 0, 0, 0.1)'
},
ticks: {
color: '#495057' // Darker text for y-axis labels
},
border: {
color: '#adb5bd'
}
}
}
}
});
// Get the canvas context for the chart
const ctx3 = document.getElementById('salesByBrandChart').getContext('2d');
// Chart.js configuration for the Pie Chart
new Chart(ctx3, {
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: [
'#007bff', // Blue
'#28a745', // Green
'#ffc107', // Yellow
'#dc3545', // Red
'#6c757d' // Gray
],
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right', // Places the legend on the right side
labels: {
color: '#343a40', // Dark text color for labels
font: {
size: 14
}
}
},
tooltip: {
backgroundColor: 'rgba(52, 58, 64, 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}%)`;
}
}
}
}
}
});
// Get the canvas context for the chart
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: [
'#007bff', // Blue
'#28a745', // Green
'#ffc107', // Yellow
'#dc3545', // Red
'#6c757d' // Gray
],
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right', // Places the legend on the right side
labels: {
color: '#343a40', // Dark text color for labels
font: {
size: 14
}
}
},
tooltip: {
backgroundColor: 'rgba(52, 58, 64, 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}%)`;
}
}
}
}
}
});
// Get the canvas context for the chart
const ctx_salesperson = document.getElementById('salespersonChart').getContext('2d');
// Chart.js configuration for the Salesperson Performance Bar Chart
new Chart(ctx_salesperson, {
type: 'bar',
data: {
labels: ['John Doe', 'Jane Smith', 'Peter Jones', 'Mary Brown'],
datasets: [{
label: 'Cars Sold',
data: [15, 22, 18, 25], // Sample data for cars sold by each salesperson
backgroundColor: [
'#007bff', // Blue
'#28a745', // Green
'#ffc107', // Yellow
'#dc3545' // Red
],
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false // Hide the legend for a single dataset
},
title: {
display: true,
text: 'Salesperson Performance',
font: {
size: 16
}
}
},
scales: {
x: {
title: {
display: true,
text: 'Salesperson Name'
},
ticks: {
color: '#495057' // Dark text for x-axis labels
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Cars Sold'
},
ticks: {
color: '#495057' // Dark text for y-axis labels
}
}
}
}
});
// Get the canvas context for the chart
const ctx_leadSources = document.getElementById('leadSourcesChart').getContext('2d');
// Chart.js configuration for the Top 5 Lead Sources Bar Chart
new Chart(ctx_leadSources, {
type: 'bar',
data: {
labels: ['Showroom', 'Referrals', 'WhatsApp', 'Facebook', 'TikTok'], // Labels from the provided list
datasets: [{
label: 'Number of Leads',
data: [45, 35, 25, 20, 15], // Sample data for leads from each source
backgroundColor: 'rgba(54, 162, 235, 0.8)', // A consistent blue color for the bars
borderColor: 'rgba(54, 162, 235, 1)',
borderWidth: 1
}]
},
options: {
indexAxis: 'y', // Makes it a horizontal bar chart
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
title: {
display: true,
text: 'Top 5 Lead Sources',
font: {
size: 16
}
}
},
scales: {
x: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Leads',
color: '#495057'
},
ticks: {
color: '#495057'
}
},
y: {
ticks: {
color: '#495057',
font: {
size: 8 // Decreases the font size to fit more text
}
}
}
}
}
});
// Get the canvas context for the chart
const ctx_appointments = document.getElementById('appointmentsChart').getContext('2d');
// Chart.js configuration for the Appointments by Weekday Bar Chart
new Chart(ctx_appointments, {
type: 'bar',
data: {
labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
datasets: [{
label: 'Number of Appointments',
data: [10, 15, 20, 18, 25, 30, 12], // Sample data for appointments per day
backgroundColor: 'rgba(75, 192, 192, 0.8)', // A consistent color for the bars
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
title: {
display: true,
text: 'Appointments by Weekday',
font: {
size: 16
}
}
},
scales: {
x: {
title: {
display: true,
text: 'Day of the Week',
color: '#495057'
},
ticks: {
color: '#495057'
}
},
y: {
beginAtZero: true,
title: {
display: true,
text: 'Number of Appointments',
color: '#495057'
},
ticks: {
color: '#495057'
}
}
}
}
});
// Get the canvas context for the funnel chart
const ctx_funnel = document.getElementById('leadFunnelChart').getContext('2d');
// Sample data for the funnel stages
const funnelData = {
labels: ['Initial Contact', 'Qualified Leads', 'Test Drives', 'Negotiation', 'Closed Deals'],
data: [250, 180, 120, 80, 45], // Example lead counts at each stage
};
new Chart(ctx_funnel, {
type: 'bar',
data: {
labels: funnelData.labels,
datasets: [{
label: 'Number of Leads',
data: funnelData.data,
backgroundColor: [
'rgba(0, 123, 255, 0.8)',
'rgba(0, 123, 255, 0.7)',
'rgba(0, 123, 255, 0.6)',
'rgba(0, 123, 255, 0.5)',
'rgba(0, 123, 255, 0.4)'
],
borderColor: 'rgba(0, 123, 255, 1)',
borderWidth: 1
}]
},
options: {
indexAxis: 'y', // Makes it a horizontal bar chart
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
title: {
display: true,
text: 'Lead Conversion Funnel',
font: {
size: 16
}
},
tooltip: {
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}%)`;
}
}
}
},
scales: {
x: {
beginAtZero: true,
display: false // Hide the x-axis to make it look like a funnel
},
y: {
ticks: {
color: '#495057'
}
}
}
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,367 @@
{% 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">Manager 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">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">
+3% 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">{{ 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>
</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">Avg. Time on Lot</p>
<h4 class="fw-bolder text-warning mb-3">10 days</h4>
</div>
<span class="badge bg-danger-subtle text-danger fw-bold p-2 rounded-pill d-inline-flex align-self-start">
+2 days 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">Inventory Value</p>
<h4 class="fw-bolder text-primary 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 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-danger mb-3">12 units</h4>
</div>
<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>
<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>
<div class="row g-4 mb-5">
<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 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">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">
<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 Salesperson Performance</h5>
</div>
<div class="card-body" style="height: 400px;">
<canvas id="salespersonChart"></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 warningColor = '#ffc107'; // A strong yellow
const dangerColor = '#e63757'; // A deep red
const chartColors = ['#00d27a', '#7249b6', '#32b9ff', '#e63757', '#ffc107'];
// 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: [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 }
}
}
}
});
// 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: [120000, 150000, 130000, 180000, 200000, 175000, 190000, 220000, 210000, 250000, 240000, 280000],
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: [25000, 35000, 28000, 40000, 45000, 38000, 42000, 50000, 48000, 55000, 52000, 60000],
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: '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 }
}
}
}
});
// 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: chartColors,
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} cars (${percentage}%)`;
}
}
}
}
}
});
// Salesperson Performance (Bar Chart)
const ctx_salesperson = document.getElementById('salespersonChart').getContext('2d');
new Chart(ctx_salesperson, {
type: 'bar',
data: {
labels: ['John Doe', 'Jane Smith', 'Peter Jones', 'Mary Brown'],
datasets: [{
label: 'Cars Sold',
data: [15, 22, 18, 25],
backgroundColor: chartColors,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
title: { display: true, text: 'Top Salesperson Performance', font: { size: 16 } }
},
scales: {
x: {
title: { display: true, text: 'Salesperson Name', color: secondaryColor },
ticks: { color: secondaryColor }
},
y: {
beginAtZero: true,
title: { display: true, text: 'Number of Cars Sold', color: secondaryColor },
ticks: { color: secondaryColor }
}
}
}
});
</script>
{% endblock %}

View File

@ -0,0 +1,356 @@
{% 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">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>
<div class="row g-4 mb-5">
<div class="col-sm-6 col-md-4">
<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>
</div>
</div>
<div class="col-sm-6 col-md-4">
<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>
</div>
</div>
<div class="col-sm-6 col-md-4">
<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>
</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">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>
</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>
</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>
{% 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
// 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 }
}
}
}
});
// 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}%)`;
}
}
}
}
}
});
// 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}%)`;
}
}
}
},
scales: {
x: {
beginAtZero: true,
display: false
},
y: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
</script>
{% endblock %}

View File

@ -3,10 +3,16 @@
{% block content %} {% block content %}
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div id="dashboard-content" <div id="dashboard-content"
hx-get="{% if request.dealer %} hx-get="{% if request.is_dealer %}
{% url 'dealer_dashboard' %}
{% elif request.is_manger %}
{% url 'manager_dashboard' %} {% url 'manager_dashboard' %}
{% else %} {% elif request.is_sales %}
{% url 'sales_dashboard' %} {% url 'sales_dashboard' %}
{% elif request.is_inventory %}
{% url 'inventory_dashboard' %}
{% else %}
{% url 'accountant_dashboard' %}
{% endif %}" {% endif %}"
hx-trigger="load" hx-trigger="load"
hx-target="#dashboard-content" hx-target="#dashboard-content"

View File

@ -88,13 +88,13 @@
} }
.color-radio:checked + .color-display { .color-radio:checked + .color-display {
border: 2px solid #0d6efd; border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25); box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
} }
.color-radio:focus + .color-display { .color-radio:focus + .color-display {
border-color: #86b7fe; border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25); box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
} }
.color-display { .color-display {

View File

@ -189,23 +189,12 @@
hx-on::before-request="on_before_request()" hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()"></div> hx-on::after-request="on_after_request()"></div>
<div class="w-100 list table-responsive"> <div class="w-100 list table-responsive">
<div class="form-check">
<input class="form-check-input ms-4" type="checkbox" id="select-all" />
<span class="ms-1 text-body-tertiary">{{ _("Select All") }}</span>
</div>
{% for car in cars %} {% for car in cars %}
<div class="card border mb-3 py-0 px-0" id="project-list-table-body"> <div class="card border mb-3 py-0 px-0" id="project-list-table-body">
<div class="card-body"> <div class="card-body">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-auto">
<div class="form-check">
<input class="form-check-input car-checkbox"
type="checkbox"
name="car"
value="{{ car.pk }}"
id="car-{{ car.pk }}" />
</div>
</div>
<!-- Vehicle Image/Icon --> <!-- Vehicle Image/Icon -->
<div class="col-auto"> <div class="col-auto">
<div class="avatar avatar-3xl"> <div class="avatar avatar-3xl">
@ -322,68 +311,5 @@
{% endblock %} {% endblock %}
{% block customJS %}
<script>
links = document.querySelectorAll(".nav-link");
links.forEach((link) => {
link.addEventListener("click", () => {
links.forEach((link) => {
link.classList.remove("active");
});
link.classList.add("active");
});
});
function on_before_request() {
document.querySelector(".table").classList.toggle("on-before-request");
document.querySelector(".model-select").classList.add("on-after-request");
}
function on_after_request() {
document.querySelector(".table").classList.remove("on-before-request");
document.querySelector(".model-select").classList.remove("on-after-request");
}
function toggle_filter() {
document.querySelector(".filter").classList.toggle("d-none");
document.querySelector(".filter-icon").classList.toggle("fa-caret-down");
document.querySelector(".filter-icon").classList.toggle("fa-caret-up");
}
function filter_before_request() {
document.querySelector(".model-select").setAttribute("disabled", true);
document.querySelector(".year").setAttribute("disabled", true);
document.querySelector(".car_status").setAttribute("disabled", true);
}
function filter_after_request() {
document.querySelector(".model-select").removeAttribute("disabled");
document.querySelector(".year").removeAttribute("disabled");
document.querySelector(".car_status").removeAttribute("disabled");
}
document.getElementById("select-all").addEventListener("change", function () {
const checkboxes = document.querySelectorAll('#project-list-table-body input[type="checkbox"]');
if (this.checked) {
checkboxes.forEach((checkbox) => (checkbox.checked = true));
} else {
checkboxes.forEach((checkbox) => (checkbox.checked = false));
}
updateFormVisibility();
});
const cbox = document.querySelectorAll(".car-checkbox");
cbox.forEach((checkbox) => {
checkbox.addEventListener("change", function () {
updateFormVisibility();
});
});
function updateFormVisibility() {
const form = document.querySelector(".update-price-form");
const checkedCount = document.querySelectorAll(".car-checkbox:checked").length;
const submitButton = form.querySelector('button[type="submit"]');
if (checkedCount > 0) {
form.classList.remove("d-none");
submitButton.textContent = `Update Cost Price (${checkedCount})`;
} else {
form.classList.add("d-none");
}
}
</script>
{% endblock customJS %}

View File

@ -51,7 +51,7 @@
<section id="filters" class="mb-5 p-4 rounded border border-primary"> <section id="filters" class="mb-5 p-4 rounded border border-primary">
<h2 class="section-heading mb-4">{% trans 'Filters' %} <i class="fas fa-sliders-h ms-2"></i></h2> <h2 class="section-heading mb-4">{% trans 'Filters' %} <i class="fas fa-sliders-h ms-2"></i></h2>
<form method="GET" class="row g-3 align-items-end"> <form method="GET" class="row g-3 align-items-end">
<div class="col-md-3"> <div class="col-md-2">
<label for="make-select" class="form-label">{% trans 'Make' %}</label> <label for="make-select" class="form-label">{% trans 'Make' %}</label>
<select id="make-select" name="make" class="form-select"> <select id="make-select" name="make" class="form-select">
<option value="">{% trans 'All Makes' %}</option> <option value="">{% trans 'All Makes' %}</option>
@ -60,7 +60,7 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label for="model-select" class="form-label">{% trans 'Model' %}</label> <label for="model-select" class="form-label">{% trans 'Model' %}</label>
<select id="model-select" name="model" class="form-select"> <select id="model-select" name="model" class="form-select">
<option value="">{% trans 'All Models' %}</option> <option value="">{% trans 'All Models' %}</option>
@ -69,7 +69,7 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="col-md-3"> <div class="col-md-2">
<label for="serie-select" class="form-label">{% trans 'Serie' %}</label> <label for="serie-select" class="form-label">{% trans 'Serie' %}</label>
<select id="serie-select" name="serie" class="form-select"> <select id="serie-select" name="serie" class="form-select">
<option value="">{% trans 'All Series' %}</option> <option value="">{% trans 'All Series' %}</option>
@ -87,36 +87,98 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="col-md-1"> <div class="col-md-2">
<label for="stock_type-select" class="form-label">{% trans 'Stock Types' %}</label>
<select id="stock_type-select" name="stock_type" class="form-select">
<option value="">{% trans 'Stock Types' %}</option>
{% for stock_type in stock_types %}
<option value="{{ stock_type }}" {% if stock_type == selected_stock_type %}selected{% endif %}>{{ stock_type }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100"><i class="fas fa-filter me-2"></i>{% trans 'Filter' %}</button> <button type="submit" class="btn btn-primary w-100"><i class="fas fa-filter me-2"></i>{% trans 'Filter' %}</button>
</div> </div>
</form> </form>
</section> </section>
<!---->
{% comment %} 'cars_sold': cars_sold,
'current_time': current_time,
'dealer': dealer,
'total_revenue_from_cars': total_revenue_from_cars,
'total_revenue_from_additonals':total_revenue_from_additonals,
'total_revenue_collected': total_revenue_collected,
'total_vat_on_cars':total_vat_on_cars,
'total_vat_from_additonals':total_vat_from_additonals,
'total_vat_collected':total_vat_collected,
'total_discount': total_discount,
'makes': makes,
'models': models_qs,
'series': series,
'years': years,
'selected_make': selected_make,
'selected_model': selected_model,
'selected_serie': selected_serie,
'selected_year': selected_year, {% endcomment %}
<!---->
<section id="summary" class="mb-5"> <section id="summary" class="mb-5">
<h2 class="section-heading mb-4 border-start border-5 border-primary p-2">{% trans 'Report Summary' %}</h2> <h2 class="section-heading mb-4 border-start border-5 border-primary p-2">{% trans 'Report Summary' %}</h2>
<div class="row g-4"> <div class="row g-4">
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="card summary-card"> <div class="card summary-card">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{% trans 'Total Revenue' %}<span class="icon-saudi_riyal"></span></h5> <h5 class="card-title"><span>{% trans 'Total Revenue from Cars' %}<span class="icon-saudi_riyal"></span></span></h5>
<p class="card-text">{{ total_revenue|floatformat:2 }} <span class="icon-saudi_riyal"></span></p> <p class="card-text"><span>{{ total_revenue_from_cars|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="card summary-card"> <div class="card summary-card">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{% trans 'Total VAT Amount' %}<i class="fas fa-percent ms-2"></i></h5> <h5 class="card-title"><span>{% trans 'Total Revenue from Services' %}<span class="icon-saudi_riyal"></span></span></h5>
<p class="card-text">{{ 10000|floatformat:2 }} <span class="icon-saudi_riyal"></span></p> <p class="card-text"><span>{{ total_revenue_from_additonals|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title"><span>{% trans 'Total Revenue' %}<span class="icon-saudi_riyal"></span></span></h5>
<p class="card-text"><span>{{total_revenue_collected |floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total VAT from Cars' %}<i class="fas fa-percent ms-2"></i></h5>
<p class="card-text"><span>{{ total_vat_on_cars|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total VAT from Services' %}<i class="fas fa-percent ms-2"></i></h5>
<p class="card-text"><span>{{ total_vat_from_additonals|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3">
<div class="card summary-card">
<div class="card-body">
<h5 class="card-title">{% trans 'Total VAT' %}<i class="fas fa-percent ms-2"></i></h5>
<p class="card-text"><span>{{ total_vat_collected|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div>
</div>
</div>
<div class="col-md-6 col-lg-3"> <div class="col-md-6 col-lg-3">
<div class="card summary-card"> <div class="card summary-card">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{% trans 'Total Discount Amount' %}<i class="fas fa-tag ms-2"></i></h5> <h5 class="card-title">{% trans 'Total Discount Amount' %}<i class="fas fa-tag ms-2"></i></h5>
<p class="card-text">{{ total_discount|floatformat:2 }} <span class="icon-saudi_riyal"></span></p> <p class="card-text"><span>{{ total_discount|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></p>
</div> </div>
</div> </div>
</div> </div>
@ -124,13 +186,10 @@
<div class="card summary-card"> <div class="card summary-card">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{% trans 'Total Cars Sold' %}<i class="fas fa-car ms-2"></i></h5> <h5 class="card-title">{% trans 'Total Cars Sold' %}<i class="fas fa-car ms-2"></i></h5>
<p class="card-text">{{ cars_sold|length }}</p> <p class="card-text">{{ total_cars_sold }}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
@ -142,7 +201,7 @@
<i class="bi bi-download me-2"></i>{% trans 'Download as CSV' %} <i class="bi bi-download me-2"></i>{% trans 'Download as CSV' %}
</a> </a>
</div> </div>
<div class="table-responsive"> <div class="table-responsive table-container">
<table class="table table-striped table-hover table-bordered table-sm"> <table class="table table-striped table-hover table-bordered table-sm">
<thead> <thead>
<tr> <tr>
@ -160,7 +219,10 @@
<th class="fs-9">{% trans 'Marked Price' %}</th> <th class="fs-9">{% trans 'Marked Price' %}</th>
<th class="fs-9">{% trans 'Discount Amount' %}</th> <th class="fs-9">{% trans 'Discount Amount' %}</th>
<th class="fs-9">{% trans 'Selling Price' %}</th> <th class="fs-9">{% trans 'Selling Price' %}</th>
<th class="fs-9">{% trans 'Tax Amount' %}</th> <th class="fs-9">{% trans 'VAT on Car' %}</th>
<th class="fs-9">{% trans 'Services Price' %}</th>
<th class="fs-9">{% trans 'VAT on Services' %}</th>
<th class="fs-9">{% trans 'Final Total' %}</th>
<th class="fs-9">{% trans 'Invoice Number' %}</th> <th class="fs-9">{% trans 'Invoice Number' %}</th>
</tr> </tr>
</thead> </thead>
@ -178,12 +240,15 @@
<td class="fs-9">{{ car.stock_type|capfirst }}</td> <td class="fs-9">{{ car.stock_type|capfirst }}</td>
<td class="fs-9">{{ car.created_at|date }}</td> <td class="fs-9">{{ car.created_at|date }}</td>
<td class="fs-9">{{ car.invoice.date_paid|date|default_if_none:"-" }}</td> <td class="fs-9">{{ car.invoice.date_paid|date|default_if_none:"-" }}</td>
<td class="fs-9">{{ car.cost_price }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9"><span>{{ car.cost_price }}<span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9">{{ car.marked_price }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9"><span>{{ car.marked_price }} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9">{{ car.total_discount }} <span class="icon-saudi_riyal"></span></td># TODO : check later <td class="fs-9"><span>{{ car.discount}} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9">{{ car.selling_price }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9"><span>{{ car.final_price}} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9">{{ car.vat_amount }} <span class="icon-saudi_riyal"></span></td># TODO : check later <td class="fs-9"><span>{{ car.vat_amount|floatformat:2 }} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9">{{ car.invoice.invoice_number }}</td> <td class="fs-9"><span>{{ car.get_additional_services.total|floatformat:2}} <span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.get_additional_services.services_vat|floatformat:2}}<span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.final_price_plus_services_plus_vat|floatformat:2 }}<span class="icon-saudi_riyal"></span></span></td>
<td class="fs-9"><span>{{ car.invoice.invoice_number }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -36,13 +36,13 @@
} }
.color-radio:checked + .color-display { .color-radio:checked + .color-display {
border: 2px solid #0d6efd; border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25); box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
} }
.color-radio:focus + .color-display { .color-radio:focus + .color-display {
border-color: #86b7fe; border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25); box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
} }
.color-display { .color-display {

View File

@ -17,8 +17,7 @@
<table class="table table-hover table-bordered"> <table class="table table-hover table-bordered">
<thead class=""> <thead class="">
<tr> <tr>
<th style="min-width: 600px" <th class="d-flex justify-content-between align-items-center">
class="d-flex justify-content-between align-items-center">
{% trans 'Item' %} {% trans 'Item' %}
{% if po_model.is_draft %} {% if po_model.is_draft %}
<button type="button" <button type="button"

View File

@ -124,6 +124,7 @@
data-bs-target="#confirmModal"> data-bs-target="#confirmModal">
<span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Approved' %}</span> <span class="d-none d-sm-inline-block"><i class="fa-solid fa-check-double"></i> {% trans 'Mark As Approved' %}</span>
</button> </button>
{% endif %} {% endif %}
{% if estimate.can_approve and not request.is_manager %} {% if estimate.can_approve and not request.is_manager %}
<button class="btn btn-phoenix-warning" disabled> <button class="btn btn-phoenix-warning" disabled>
@ -134,6 +135,9 @@
{% if perms.django_ledger.change_estimatemodel %} {% if perms.django_ledger.change_estimatemodel %}
<a href="{% url 'send_email' request.dealer.slug estimate.pk %}" <a href="{% url 'send_email' request.dealer.slug estimate.pk %}"
class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a> class="btn btn-phoenix-primary me-2"><span class="fa-regular fa-paper-plane me-sm-2"></span><span class="d-none d-sm-inline-block">{% trans 'Send Quotation' %}</span></a>
<a href="{% url 'estimate_print' request.dealer.slug estimate.pk %}" class="btn btn-phoenix-secondary" target="_blank">
<span class="d-none d-sm-inline-block"><i class="fas fa-print me-2"></i>{% trans 'Print' %}</span>
</a>
{% endif %} {% endif %}
{% if estimate.sale_orders.first %} {% if estimate.sale_orders.first %}
<!--if sale order exist--> <!--if sale order exist-->
@ -283,7 +287,7 @@
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td> <td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-semibold"> <td class="align-middle text-start fw-semibold">
{% for service in data.additional_services.services %} {% for service in data.additional_services.services %}
<small><span class="fw-semibold">+ {{ service.name }} - {{ service.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small> <small><span class="fw-semibold">+ {{ service.0.name }} - {{ service.0.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
<br> <br>
{% endfor %} {% endfor %}
{% if estimate.is_draft %} {% if estimate.is_draft %}

View File

@ -1,11 +1,10 @@
{% load i18n static custom_filters num2words_tags %} {% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ar"> <html lang="ar" dir="rtl">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice</title> <title>Quotation</title>
<!-- CSS -->
<link href="{% static 'css/theme.min.css' %}" <link href="{% static 'css/theme.min.css' %}"
type="text/css" type="text/css"
rel="stylesheet" rel="stylesheet"
@ -15,70 +14,80 @@
rel="stylesheet" rel="stylesheet"
id="user-style-default"> id="user-style-default">
<link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet"> <link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet">
<!-- Google Fonts - Roboto -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
rel="stylesheet"> rel="stylesheet">
<!-- Custom CSS -->
<style> <style>
body { body {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
margin: 0; margin: 0;
padding: 10mm; padding: 0;
background-color: #f8f9fa; background-color: #f8f9fa;
} }
.invoice-container { .invoice-container {
width: 210mm; width: 210mm;
min-height: 297mm; min-height: 297mm;
padding: 10mm; padding: 10mm;
margin: auto; margin: 10mm auto;
background: white; background: white;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
position: relative; display: flex;
flex-direction: column;
} }
.invoice-content {
flex-grow: 1;
}
.invoice-header { .invoice-header {
text-align: center; text-align: center;
border-bottom: 2px solid #dee2e6; border-bottom: 2px solid #dee2e6;
padding-bottom: 10px; padding-bottom: 10px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.qr-code { .qr-code {
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
} }
.qr-code img { .qr-code img {
width: 3cm; width: 3cm;
height: 3cm; height: 3cm;
border-radius: 0.3333333333rem; border-radius: 0.3333333333rem;
} }
.invoice-details, .invoice-table { .invoice-details, .invoice-table {
font-size: 12px; font-size: 14px;
} }
.invoice-table th { .invoice-table th {
background-color: #f8f9fa; background-color: #f8f9fa;
font-weight: 600; font-weight: 600;
} }
.invoice-total { .invoice-total {
text-align: right; text-align: right;
font-size: 13px; font-size: 16px;
font-weight: 600; font-weight: 600;
margin-top: 10px; margin-top: 10px;
} }
.footer-note { .footer-note {
position: absolute; margin-top: auto;
bottom: 10mm; padding-top: 10mm;
left: 10mm; font-size: 13px;
right: 10mm;
font-size: 10px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-top: 2px solid #dee2e6;
} }
.logo-img img { .logo-img img {
width: 10mm; width: 10mm;
height: 10mm; height: 10mm;
} }
</style> </style>
</head> </head>
<body> <body>
@ -92,267 +101,262 @@
</div> </div>
<div class="col-8"></div> <div class="col-8"></div>
</div> </div>
<div class="invoice-container" id="invoice-content">
<div class="invoice-header"> <div class="invoice-container" id="estimate-content">
<h5 class="fs-5"> <div class="invoice-content">
<span>Quotation</span>&nbsp;/&nbsp;<span>عرض سعر</span> <div class="invoice-header">
</h5> <h5 class="fs-5">
</div> <span>Quotation</span>&nbsp;/&nbsp;<span>عرض سعر</span>
<div class="invoice-details p-1"> </h5>
<table class="table table-sm table-responsive border-gray-50"> </div>
<tr> <div class="invoice-details p-1">
<td></td> <div class="d-flex justify-content-end align-items-end">
<td></td> <div class="dealer-logo ">
<td> {% if request.dealer.logo %}
<div class="qr-code"> <img class="rounded-soft" style="max-width:200px; max-height:200px;"
{% if dealer.logo %} src="{{ request.dealer.logo.url|default:'' }}"
<img class="rounded-soft" alt="Dealer Logo" />
src="{{ dealer.logo.url|default:'' }}" {% endif %}
alt="Dealer Logo" /> </div>
{% endif %} </div>
</div>
</td> <table class="table table-sm table-responsive border-gray-50">
</tr>
<tr>
<td class="ps-1">
<strong>Customer Name</strong>
</td>
<td class="text-center">
{{ dealer.arabic_name }}
<br>
{{ dealer.name }}
</td>
<td class="text-end">
<strong>{{ dealer.arabic_name }}</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Address</strong>
</td>
<td class="text-center">{{ dealer.address }}</td>
<td class="text-end">
<strong>العنوان</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Phone</strong>
</td>
<td class="text-center">{{ dealer.phone_number }}</td>
<td class="text-end">
<strong>جوال</strong>
</td>
</tr>
<tr>
<td>
<strong>VAT Number</strong>
</td>
<td>{{ dealer.vrn }}</td>
<td class="text-end">
<strong>الرقم الضريبي</strong>
</td>
</tr>
</table>
<table class="table table-sm table-bordered border-gray-50">
<tr>
<td class="ps-1">
<strong>Quotation&nbsp;Number</strong>
</td>
<td class="text-center">{{ estimate.estimate_number }}</td>
<td class="text-end p-1">
<strong>رقم عرض السعر</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Date</strong>
</td>
<td class="text-center">{{ estimate.date_approved| date:"Y/m/d" }}</td>
<td class="text-end p-1">
<strong>التاريخ</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer</strong>
</td>
<td class="text-center">{{ estimate.customer.customer_name }}</td>
<td class="text-end p-1">
<strong>العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>VAT&nbsp;ID</strong>
</td>
<td class="text-center">{{ estimate.customer.vrn|default:"-" }}</td>
<td class="text-end p-1">
<strong>الرقم الضريبي</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Email</strong>
</td>
<td class="text-center">{{ estimate.customer.email |default:"N/A" }}</td>
<td class="text-end p-1">
<strong>البريد الإلكتروني</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Terms</strong>
</td>
<td class="text-center">{{ estimate.get_terms_display }}</td>
<td class="text-end p-1">
<strong>طريقة&nbsp;الدفع</strong>
</td>
</tr>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Car Details</span>
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
</div>
<div class="invoice-table p-1">
<table class="table table-sm table-bordered m-1">
<thead>
<tr> <tr>
<th class="text-wrap text-center align-content-center"> <td class="ps-1">
<span class="fs-10">Make</span> / <span class="fs-10">الصانع</span> <strong>Dealership Name</strong>
</th> </td>
<th class="text-wrap text-center align-content-center"> <td class="text-center">
<span class="fs-10">Model</span> / <span class="fs-10">الموديل</span> {{ request.dealer.name }}
</th> </td>
<th class="text-wrap text-center align-content-center"> <td class="text-end">
<span class="fs-10">Trim</span> / <span class="fs-10">الفئة</span> <strong>اسم الوكالة</strong>
</th> </td>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Year</span> / <span class="fs-10">السنة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VIN</span> / <span class="fs-10">الهيكل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Quantity</span> / <span class="fs-10">الكمية</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Unit Price</span> / <span class="fs-10">سعر الوحدة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Including VAT</span> / <span class="fs-10">شامل الضريبة</span>
</th>
</tr> </tr>
</thead>
<tbody>
{% for item in data.cars %}
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="3">{{ item.make }} - {{ item.model }} - {{ item.trim }}</td>
<td class="text-center fs-10 align-content-center">{{ item.year }}</td>
<td class="ps-1 fs-10 align-content-center">{{ item.vin }}</td>
<td class="text-center fs-10 align-content-center">{{ item.quantity|floatformat:-1 }}</td>
<td class="text-center fs-10 align-content-center">{{ item.unit_price|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ item.total_vat|floatformat:2 }}</td>
</tr>
{% endfor %}
<tr> <tr>
<td class="ps-1 fs-10 align-content-center" colspan="5"></td> <td class="ps-1">
<td class="text-center fs-10 align-content-center">{{ data.quantity|floatformat:-1 }}</td> <strong>Dealership Address</strong>
<td class="text-center fs-10 align-content-center">{{ data.total_price_before_discount|floatformat:2 }}</td> </td>
<td class="text-center fs-10 align-content-center">{{ data.grand_total|floatformat:2 }}</td> <td class="text-center">{{ request.dealer.address }}</td>
<td class="text-end">
<strong>العنوان</strong>
</td>
</tr> </tr>
</tbody> <tr>
</table> <td class="ps-1">
</div> <strong>Phone</strong>
<div class="d-flex justify-content-between"> </td>
<span class="fs-9 fw-thin">Additional&nbsp;Services</span> <td class="text-center">{{ request.dealer.phone_number }}</td>
<span class="fs-9 fw-thin">الخدمات&nbsp;الإضافية</span> <td class="text-end">
</div> <strong>جوال</strong>
{% if data.additionals %} </td>
</tr>
<tr>
<td>
<strong>VAT Number</strong>
</td>
<td class="text-center">{{ request.dealer.vrn }}</td>
<td class="text-end">
<strong>الرقم الضريبي</strong>
</td>
</tr>
</table>
<table class="table table-sm table-bordered border-gray-50 ">
<tr>
<td class="ps-1">
<strong>Quotation&nbsp;Number</strong>
</td>
<td class="text-center">{{ estimate.estimate_number }}</td>
<td class="text-end p-1">
<strong>رقم عرض السعر</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Date</strong>
</td>
<td class="text-center">{{ estimate.date_approved| date:"Y/m/d" }}</td>
<td class="text-end p-1">
<strong>التاريخ</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer Name</strong>
</td>
<td class="text-center">{{ estimate.customer.customer_name }}</td>
<td class="text-end p-1">
<strong>العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Email</strong>
</td>
<td class="text-center">{{ estimate.customer.email |default:"N/A" }}</td>
<td class="text-end p-1">
<strong>البريد الإلكتروني</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Terms</strong>
</td>
<td class="text-center">{{ estimate.get_terms_display }}</td>
<td class="text-end p-1">
<strong>طريقة&nbsp;الدفع</strong>
</td>
</tr>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Car Details</span>
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
</div>
<div class="invoice-table p-1"> <div class="invoice-table p-1">
<table class="table table-sm table-bordered m-1"> <table class="table table-sm table-bordered m-1">
<thead> <thead>
<tr> <tr>
<th class="text-center fs-10 align-content-center">Type&nbsp;/&nbsp;النوع</th> <th class="text-wrap text-center align-content-center">
<th class="text-center fs-10 align-content-center">Price&nbsp;/&nbsp;القيمة</th> <span class="fs-10">Make</span> / <span class="fs-10">الصانع</span>
<th class="text-center fs-10 align-content-center"> </th>
<span class="fs-10">Including VAT</span> / <span class="fs-10">شامل الضريبة</span> <th class="text-wrap text-center align-content-center">
<span class="fs-10">Model</span> / <span class="fs-10">الموديل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Series</span> / <span class="fs-10">السلسلة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Trim</span> / <span class="fs-10">الفئة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Year</span> / <span class="fs-10">السنة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VIN</span> / <span class="fs-10">رقم الهيكل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Quantity</span> / <span class="fs-10">الكمية</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Unit Price</span> / <span class="fs-10">سعر الوحدة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Discount</span> / <span class="fs-10">الخصم</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VAT</span> / <span class="fs-10">الضريبة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in data.additionals %} <tr>
<tr> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_make.name }}</td>
<td class="ps-1 text-start fs-10 align-content-center">{{ item.name }}</td> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_model.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price|floatformat }}</td> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_serie.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price_|floatformat }}</td> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_trim.name }}</td>
</tr> <td class="text-center fs-10 align-content-center">{{ data.car.year }}</td>
{% endfor %} <td class="ps-1 fs-10 align-content-center">{{ data.car.vin }}</td>
<td class="text-center fs-10 align-content-center">1</td>
<td class="text-center fs-10 align-content-center">{{ data.car.marked_price |floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{  data.discount_amount |floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.vat_amount|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.final_price|floatformat:2 }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
{% endif %} <div class="d-flex justify-content-between">
<div class="invoice-total d-flex justify-content-end"> <span class="fs-9 fw-thin">Additional&nbsp;Services</span>
<div class="table-responsive"> <span class="fs-9 fw-thin">الخدمات&nbsp;الإضافية</span>
<table class="table table-sm table-responsive"> </div>
<tr> {% if data.additional_services %}
<td class="text-start ps-1"> <div class="invoice-table p-1">
<strong class="fs-9">VAT</strong> <table class="table table-sm table-bordered m-1">
</td> <thead>
<td class="text-center"> <tr>
<span class="fs-9">{{ data.total_vat_amount|floatformat }} <span class="icon-saudi_riyal"></span></span> <th class="text-center fs-10 align-content-center">Type&nbsp;/&nbsp;النوع</th>
</td> <th class="text-center fs-10 align-content-center">Price&nbsp;/&nbsp;القيمة</th>
<td class="text-end"> <th class="text-center fs-10 align-content-center">VAT&nbsp;/&nbsp;ضريبة الخدمة</th>
<strong class="fs-9">ضريبة&nbsp;القيمة&nbsp;المضافة</strong> <th class="text-center fs-10 align-content-center">
</td> <span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
</tr> </th>
<tr> </tr>
<td class="text-start ps-1"> </thead>
<strong class="fs-9">Total</strong> <tbody>
</td> {% for service in data.additional_services.services %}
<td class="text-center"> <tr>
<span class="fs-9">{{ data.grand_total|floatformat }}&nbsp;<span class="icon-saudi_riyal"></span></span> <td class="ps-1 text-start fs-10 align-content-center">{{ service.0.name }}</td>
</td> <td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price|floatformat }}</td>
<td class="text-end"> <td class="ps-1 text-center fs-10 align-content-center">{{ service.1|floatformat }}</td>
<strong class="fs-9">الإجمالي</strong> <td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price_|floatformat }}</td>
</td> </tr>
</tr> {% endfor %}
<tr> </tbody>
<td class="text-end" colspan="3"> </table>
<span class="fs-9 fw-bold">كتابةً:&nbsp;</span><span class="fs-9">{{ data.grand_total|num_to_words }}&nbsp;<span class="icon-saudi_riyal"></span></span> </div>
</td> {% endif %}
</tr> <div class="invoice-total d-flex justify-content-end">
</table> <div class="table-responsive">
<table class="table table-sm table-responsive">
<tr>
<td class="text-start ps-1">
<strong class="fs-9">Total VAT</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.total_vat|floatformat }} <span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">إجمالي&nbsp;ضريبة&nbsp;القيمة&nbsp;المضافة</strong>
</td>
</tr>
<tr>
<td class="text-start ps-1">
<strong class="fs-9">Grand Total</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.grand_total|floatformat }}&nbsp;<span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">الإجمالي</strong>
</td>
</tr>
<tr>
<td class="text-end" colspan="3">
<span class="fs-9 fw-bold">كتابةً:&nbsp;</span><span class="fs-9">{{ data.grand_total|num_to_words }}&nbsp;<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
</table>
</div>
</div> </div>
</div> </div>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="footer-note"> <div class="footer-note">
<div class="logo-img text-center"> <div class="logo-img text-center">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" /> <img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p class="fs-11 fw-bold"> <p class="fs-9 fw-bold">
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span> <span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p> </p>
</div> </div>
<p class="fs-11"> <p class="fs-11">
<span class="fw-thin">Powered&nbsp;by&nbsp;</span><a class="text-decoration-none" <span class="fw-thin">Powered&nbsp;by&nbsp;</span>
href="https://tenhal.sa" <a class="text-decoration-none fs-9"
style="color: #112e40"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a> href="https://tenhal.sa"
style="color: #112e40;"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p> </p>
</div> </div>
</div> </div>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> <script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'js/html2pdf.bundle.min.js' %}"></script> <script src="{% static 'js/html2pdf.bundle.min.js' %}"></script>
<script> <script>
document.getElementById('download-pdf').addEventListener('click', function () { document.getElementById('download-pdf').addEventListener('click', function () {
html2pdf().from(document.getElementById('invoice-content')).set({ html2pdf().from(document.getElementById('estimate-content')).set({
margin: 0, margin: 0,
filename: "{{ invoice.invoice_number }}_{{ invoice.customer.customer_name }}_{{ invoice.date_in_review|date:'Y-m-d' }}.pdf", filename: "{{ estimate.estimate_number }}_{{ estimate.customer.customer_name  }}_{{estimate.date_approved|date:'Y-m-d' }}.pdf",
image: { type: 'jpeg', quality: 0.98 }, image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 3 }, html2canvas: { scale: 3 },
jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' } jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
@ -360,4 +364,4 @@
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -358,7 +358,7 @@
<td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td> <td class="align-middle ps-4 fw-semibold text-body-highlight" colspan="7">{% trans "Additional Services" %}</td>
<td class="align-middle text-start fw-bold"> <td class="align-middle text-start fw-bold">
{% for service in data.additional_services.services %} {% for service in data.additional_services.services %}
<small><span class="fw-bold">+ {{ service.name }} - {{ service.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small> <small><span class="fw-bold">+ {{ service.0.name }} - {{ service.0.price_|floatformat }}<span class="icon-saudi_riyal"></span></span></small>
<br> <br>
{% endfor %} {% endfor %}
</td> </td>

View File

@ -1,11 +1,10 @@
{% load i18n static custom_filters num2words_tags %} {% load i18n static custom_filters num2words_tags %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="ar"> <html lang="ar" dir="rtl">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Invoice</title> <title>Invoice</title>
<!-- CSS -->
<link href="{% static 'css/theme.min.css' %}" <link href="{% static 'css/theme.min.css' %}"
type="text/css" type="text/css"
rel="stylesheet" rel="stylesheet"
@ -15,75 +14,80 @@
rel="stylesheet" rel="stylesheet"
id="user-style-default"> id="user-style-default">
<link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet"> <link href="{% static 'css/custom.css' %}" type="text/css" rel="stylesheet">
<!-- Google Fonts - Roboto -->
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap"
rel="stylesheet"> rel="stylesheet">
<!-- Custom CSS -->
<style> <style>
body { body {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
margin: 0; margin: 0;
padding: 10mm; padding: 0;
background-color: #f8f9fa; background-color: #f8f9fa;
} }
.invoice-container { .invoice-container {
width: 210mm; width: 210mm;
min-height: 297mm; min-height: 297mm;
padding: 10mm; padding: 10mm;
margin: auto; margin: 10mm auto;
background: white; background: white;
border-radius: 5px; border-radius: 5px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
position: relative; display: flex;
flex-direction: column;
} }
.invoice-content {
flex-grow: 1;
}
.invoice-header { .invoice-header {
text-align: center; text-align: center;
border-bottom: 2px solid #dee2e6; border-bottom: 2px solid #dee2e6;
padding-bottom: 10px; padding-bottom: 10px;
margin-bottom: 20px; margin-bottom: 20px;
} }
.qr-code { .qr-code {
text-align: center; text-align: center;
margin-top: 10px; margin-top: 10px;
} }
.qr-code img { .qr-code img {
width: 3cm; width: 3cm;
height: 3cm; height: 3cm;
border-radius: 0.3333333333rem; border-radius: 0.3333333333rem;
} }
.dealer-logo img {
width: 3cm;
height: 1cm;
position: relative;
}
.invoice-details, .invoice-table { .invoice-details, .invoice-table {
font-size: 12px; font-size: 14px;
} }
.invoice-table th { .invoice-table th {
background-color: #f8f9fa; background-color: #f8f9fa;
font-weight: 600; font-weight: 600;
} }
.invoice-total { .invoice-total {
text-align: right; text-align: right;
font-size: 13px; font-size: 16px;
font-weight: 600; font-weight: 600;
margin-top: 10px; margin-top: 10px;
} }
.footer-note { .footer-note {
position: absolute; margin-top: auto;
bottom: 10mm; padding-top: 10mm;
left: 10mm; font-size: 13px;
right: 10mm;
font-size: 10px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-top: 2px solid #dee2e6; /* Add a top border to separate from content */
} }
.logo-img img { .logo-img img {
width: 10mm; width: 10mm;
height: 10mm; height: 10mm;
} }
</style> </style>
</head> </head>
<body> <body>
@ -97,271 +101,274 @@
</div> </div>
<div class="col-8"></div> <div class="col-8"></div>
</div> </div>
<div class="invoice-container" id="invoice-content"> <div class="invoice-container" id="invoice-content">
<div class="invoice-header"> <div class="invoice-content">
<h5 class="fs-5">Tax&nbsp;Invoice&nbsp;&nbsp;/&nbsp;&nbsp;فاتورة&nbsp;ضريبية</h5> <div class="invoice-header">
</div> <h5 class="fs-5">
<div class="invoice-details p-1"> <span>Invoice</span>&nbsp;/&nbsp;<span>فاتورة</span>
<div class="d-flex justify-content-center align-items-center"> </h5>
<div class="qr-code">
<img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
</div>
</div> </div>
<div class="d-flex justify-content-end align-items-end"> <div class="invoice-details p-1">
<div class="dealer-logo "> <div class="d-flex justify-content-around align-items-end">
{% if dealer.logo %} <div class="d-flex justify-content-start align-items-center">
<img class="rounded-soft" <div class="qr-code">
src="{{ dealer.logo.url|default:'' }}" <img src="{% static 'qr_code/Marwan_qr.png' %}" alt="QR Code">
alt="Dealer Logo" /> </div>
{% endif %} </div>
<div class="dealer-logo ">
{% if request.dealer.logo %}
<img class="rounded-soft" style="max-width:150px; max-height:150px;"
src="{{ request.dealer.logo.url|default:'' }}"
alt="Dealer Logo" />
{% endif %}
</div>
</div> </div>
<table class="table table-sm table-responsive border-gray-50">
<tr>
<td class="ps-1">
<strong>Dealership Name</strong>
</td>
<td class="text-center">
{{ request.dealer.name }}
</td>
<td class="text-end">
<strong>اسم الوكالة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Dealership Address</strong>
</td>
<td class="text-center">{{ request.dealer.address }}</td>
<td class="text-end">
<strong>عنوان الوكالة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Phone</strong>
</td>
<td class="text-center">{{ request.dealer.phone_number }}</td>
<td class="text-end">
<strong>جوال</strong>
</td>
</tr>
<tr>
<td>
<strong>VAT Number</strong>
</td>
<td class="text-center">{{ request.dealer.vrn }}</td>
<td class="text-end">
<strong>الرقم الضريبي</strong>
</td>
</tr>
</table>
<table class="table table-sm table-bordered border-gray-50 ">
<tr>
<td class="ps-1">
<strong>Invoice&nbsp;Number</strong>
</td>
<td class="text-center">{{ invoice.invoice_number }}</td>
<td class="text-end p-1">
<strong>رقم الفاتورة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Date</strong>
</td>
<td class="text-center">{{ invoice.date_approved| date:"Y/m/d" }}</td>
<td class="text-end p-1">
<strong>التاريخ</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer Name</strong>
</td>
<td class="text-center">{{ invoice.customer.customer_name }}</td>
<td class="text-end p-1">
<strong>اسم العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Email</strong>
</td>
<td class="text-center">{{ invoice.customer.email |default:"N/A" }}</td>
<td class="text-end p-1">
<strong>البريد الإلكتروني</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Terms</strong>
</td>
<td class="text-center">{{ invoice.get_terms_display }}</td>
<td class="text-end p-1">
<strong>شروط الدفع</strong>
</td>
</tr>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Car Details</span>
<span class="fs-9 fw-thin">تفاصيل السيارة</span>
</div> </div>
<table class="table table-sm table-bordered border-gray-50">
<tr>
<td class="ps-1">
<strong>Customer Name</strong>
</td>
<td class="text-center">
{{ dealer.arabic_name }}
<br>
{{ dealer.name }}
</td>
<td class="text-end">
<strong>اسم&nbsp;العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Address</strong>
</td>
<td class="text-center">{{ dealer.address }}</td>
<td class="text-end">
<strong>العنوان</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Phone</strong>
</td>
<td class="text-center">{{ dealer.phone_number }}</td>
<td class="text-end">
<strong>جوال</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>VAT Number</strong>
</td>
<td class="text-center">{{ dealer.vrn }}</td>
<td class="text-end p-1">
<strong>الرقم الضريبي</strong>
</td>
</tr>
</table>
<table class="table table-sm table-bordered border-gray-50">
<tr>
<td class="ps-1">
<strong>Invoice&nbsp;Number</strong>
</td>
<td class="text-center">{{ invoice.invoice_number }}</td>
<td class="text-end p-1">
<strong>رقم&nbsp;الفاتورة</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Date</strong>
</td>
<td class="text-center">{{ invoice.date_in_review| date:"Y/m/d" }}</td>
<td class="text-end p-1">
<strong>التاريخ</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Customer</strong>
</td>
<td class="text-center">{{ invoice.customer.customer_name }}</td>
<td class="text-end p-1">
<strong>العميل</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>VAT&nbsp;ID</strong>
</td>
<td class="text-center">{{ invoice.customer.vrn|default:"-" }}</td>
<td class="text-end p-1">
<strong>الرقم&nbsp;الضريبي</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Email</strong>
</td>
<td class="text-center">{{ invoice.customer.email|default:"N/A" }}</td>
<td class="text-end p-1">
<strong>البريد&nbsp;الالكتروني</strong>
</td>
</tr>
<tr>
<td class="ps-1">
<strong>Terms</strong>
</td>
<td class="text-center">{{ invoice.get_terms_display }}</td>
<td class="text-end p-1">
<strong>طريقة&nbsp;الدفع</strong>
</td>
</tr>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Car&nbsp;Details</span>
<span class="fs-9 fw-thin">تفاصيل&nbsp;السيارة</span>
</div>
<div class="invoice-table p-1">
<table class="table table-sm table-bordered m-1">
<thead>
<tr>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Make</span> / <span class="fs-10">الصانع</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Model</span> / <span class="fs-10">الموديل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Trim</span> / <span class="fs-10">الفئة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Year</span> / <span class="fs-10">السنة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VIN</span> / <span class="fs-10">الهيكل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Quantity</span> / <span class="fs-10">الكمية</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Unit Price</span> / <span class="fs-10">سعر الوحدة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Including VAT</span> / <span class="fs-10">شامل الضريبة</span>
</th>
</tr>
</thead>
<tbody>
{% for item in data.cars %}
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="3">{{ item.make }} - {{ item.model }} - {{ item.trim }}</td>
<td class="text-center fs-10 align-content-center">{{ item.year }}</td>
<td class="ps-1 fs-10 align-content-center">{{ item.vin }}</td>
<td class="text-center fs-10 align-content-center">{{ item.quantity|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ item.unit_price|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ item.total_vat|floatformat }}</td>
</tr>
{% endfor %}
<tr>
<td class="ps-1 fs-10 align-content-center" colspan="5"></td>
<td class="text-center fs-10 align-content-center">{{ data.quantity|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.total_price_before_discount|floatformat }}</td>
<td class="text-center fs-10 align-content-center">{{ data.grand_total|floatformat }}</td>
</tr>
</tbody>
</table>
</div>
<div class="d-flex justify-content-between">
<span class="fs-9 fw-thin">Additional&nbsp;Services</span>
<span class="fs-9 fw-thin">الخدمات&nbsp;الإضافية</span>
</div>
{% if data.additionals %}
<div class="invoice-table p-1"> <div class="invoice-table p-1">
<table class="table table-sm table-bordered m-1"> <table class="table table-sm table-bordered m-1">
<thead> <thead>
<tr> <tr>
<th class="text-center fs-10 align-content-center">Type&nbsp;/&nbsp;النوع</th> <th class="text-wrap text-center align-content-center">
<th class="text-center fs-10 align-content-center">Price&nbsp;/&nbsp;القيمة</th> <span class="fs-10">Make</span> / <span class="fs-10">الصانع</span>
<th class="text-center fs-10 align-content-center"> </th>
<span class="fs-10">Including VAT</span> / <span class="fs-10">شامل الضريبة</span> <th class="text-wrap text-center align-content-center">
<span class="fs-10">Model</span> / <span class="fs-10">الموديل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Series</span> / <span class="fs-10">السلسلة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Trim</span> / <span class="fs-10">الفئة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Year</span> / <span class="fs-10">السنة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VIN</span> / <span class="fs-10">رقم الهيكل</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Quantity</span> / <span class="fs-10">الكمية</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Unit Price</span> / <span class="fs-10">سعر الوحدة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Discount</span> / <span class="fs-10">الخصم</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">VAT</span> / <span class="fs-10">الضريبة</span>
</th>
<th class="text-wrap text-center align-content-center">
<span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for item in data.additionals %}
<tr> <tr>
<td class="ps-1 text-start fs-10 align-content-center">{{ item.name }}</td> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_make.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price|floatformat }}</td> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_model.name }}</td>
<td class="ps-1 text-center fs-10 align-content-center">{{ item.price_|floatformat }}</td> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_serie.name }}</td>
</tr> <td class="ps-1 fs-10 align-content-center">{{ data.car.id_car_trim.name }}</td>
{% endfor %} <td class="text-center fs-10 align-content-center">{{ data.car.year }}</td>
<td class="ps-1 fs-10 align-content-center">{{ data.car.vin }}</td>
<td class="text-center fs-10 align-content-center">1</td>
<td class="text-center fs-10 align-content-center">{{ data.car.marked_price |floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.discount_amount |floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.vat_amount|floatformat:2 }}</td>
<td class="text-center fs-10 align-content-center">{{ data.final_price|floatformat:2 }}</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
{% endif %} <div class="d-flex justify-content-between">
<div class="invoice-total d-flex justify-content-end"> <span class="fs-9 fw-thin">Additional&nbsp;Services</span>
<div class="table-responsive"> <span class="fs-9 fw-thin">الخدمات&nbsp;الإضافية</span>
<table class="table table-sm table-responsive"> </div>
<tr> {% if data.additional_services %}
<td class="text-start ps-1"> <div class="invoice-table p-1">
<strong class="fs-9">VAT</strong> <table class="table table-sm table-bordered m-1">
</td> <thead>
<td class="text-center"> <tr>
<span class="fs-9">{{ data.total_vat_amount|floatformat }} <span class="icon-saudi_riyal"></span></span> <th class="text-center fs-10 align-content-center">Type&nbsp;/&nbsp;النوع</th>
</td> <th class="text-center fs-10 align-content-center">Price&nbsp;/&nbsp;السعر</th>
<td class="text-end"> <th class="text-center fs-10 align-content-center">Service VAT&nbsp;/&nbsp;ضريبة الخدمة</th>
<strong class="fs-9">ضريبة&nbsp;القيمة&nbsp;المضافة</strong> <th class="text-center fs-10 align-content-center">
</td> <span class="fs-10">Total</span> / <span class="fs-10">الإجمالي</span>
</tr> </th>
<tr> </tr>
<td class="text-start ps-1"> </thead>
<strong class="fs-9">Total</strong> <tbody>
</td> {% for service in data.additional_services.services %}
<td class="text-center"> <tr>
<span class="fs-9">{{ data.grand_total|floatformat }}&nbsp;<span class="icon-saudi_riyal"></span></span> <td class="ps-1 text-start fs-10 align-content-center">{{ service.0.name }}</td>
</td> <td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price|floatformat }}</td>
<td class="text-end"> <td class="ps-1 text-center fs-10 align-content-center">{{ service.1|floatformat }}</td>
<strong class="fs-9">الإجمالي</strong> <td class="ps-1 text-center fs-10 align-content-center">{{ service.0.price_|floatformat }}</td>
</td> </tr>
</tr> {% endfor %}
<tr> </tbody>
<td class="text-end" colspan="3"> </table>
<span class="fs-9 fw-bold">كتابةً:&nbsp;</span><span class="fs-9">{{ data.grand_total|num_to_words }}&nbsp;<span class="icon-saudi_riyal"></span></span> </div>
</td> {% endif %}
</tr> <div class="invoice-total d-flex justify-content-end">
</table> <div class="table-responsive">
<table class="table table-sm table-responsive">
<tr>
<td class="text-start ps-1">
<strong class="fs-9">Total VAT</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.total_vat|floatformat }} <span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">إجمالي&nbsp;ضريبة&nbsp;القيمة&nbsp;المضافة</strong>
</td>
</tr>
<tr>
<td class="text-start ps-1">
<strong class="fs-9">Grand Total</strong>
</td>
<td class="text-center">
<span class="fs-9">{{ data.grand_total|floatformat }}&nbsp;<span class="icon-saudi_riyal"></span></span>
</td>
<td class="text-end">
<strong class="fs-9">الإجمالي&nbsp;الكلي</strong>
</td>
</tr>
<tr>
<td class="text-end" colspan="3">
<span class="fs-9 fw-bold">كتابةً:&nbsp;</span><span class="fs-9">{{ data.grand_total|num_to_words }}&nbsp;<span class="icon-saudi_riyal"></span></span>
</td>
</tr>
</table>
</div>
</div> </div>
</div> </div>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="footer-note"> <div class="footer-note">
<div class="logo-img text-center"> <div class="logo-img text-center">
<img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" /> <img src="{% static 'images/logos/logo-d-pdf.png' %}" alt="Logo" />
<p class="fs-11 fw-bold"> <p class="fs-9 fw-bold">
<span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span> <span>Haikal</span>&nbsp;|&nbsp;<span>هيكل</span>
</p> </p>
</div> </div>
<p class="fs-11"> <p class="fs-11">
<span class="fw-thin">Powered&nbsp;by&nbsp;</span><a class="text-decoration-none" <span class="fw-thin">Powered&nbsp;by&nbsp;</span>
href="https://tenhal.sa" <a class="text-decoration-none fs-9"
style="color: #112e40"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a> href="https://tenhal.sa"
style="color: #112e40;"><span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span></a>
</p> </p>
</div> </div>
</div> </div>
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> <script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
<script src="{% static 'js/html2pdf.bundle.min.js' %}"></script> <script src="{% static 'js/html2pdf.bundle.min.js' %}"></script>
<script> <script>
document.getElementById('download-pdf').addEventListener('click', function () { document.getElementById('download-pdf').addEventListener('click', function () {
html2pdf().from(document.getElementById('invoice-content')).set({ html2pdf().from(document.getElementById('invoice-content')).set({
margin: 0, margin: 0,
filename: "{{ invoice.invoice_number }}_{{ invoice.customer.customer_name }}_{{ invoice.date_in_review|date:'Y-m-d' }}.pdf", filename: "{{ invoice.invoice_number }}_{{ invoice.customer.customer_name  }}_{{invoice.date_approved|date:'Y-m-d' }}.pdf",
image: { type: 'jpeg', quality: 0.98 }, image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 3 }, html2canvas: { scale: 3 },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } jsPDF: { unit: 'mm', format: 'a3', orientation: 'portrait' }
}).save(); }).save();
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@ -2,108 +2,107 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% block title %} {% block title %}
{{ _("View Staff") }} {{ user_.name }}
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
<div class="row my-5"> <div class="container py-4">
<div class="card rounded "> <div class="d-flex justify-content-between align-items-center mb-4">
<div class="card-header "> <div class="d-flex align-items-center">
<p class="mb-0"> <a href="{% url 'user_list' request.dealer.slug %}" class="btn btn-phoenix-secondary btn-sm me-3">
<i class="fa-solid fa-user"></i> {{ _("User Details") }} <i class="fa-regular fa-circle-left me-2"></i>{% trans "Back to Staffs" %}
</p> </a>
<h1 class="h5 fw-bold mb-0">{% trans "Staff Profile" %}</h1>
</div> </div>
<div class="card-body"> <div>
<div class="row"> <a href="{% url 'user_update' request.dealer.slug user_.slug %}" class="btn btn-phoenix-primary btn-sm me-2">
<div class="col-md-2"> <i class="fa-solid fa-pen-to-square me-2"></i>{% trans "Edit" %}
{% if user_.logo %} </a>
<img class="rounded-circle" <button class="btn btn-phoenix-danger btn-sm delete-btn" data-url="{% url 'user_delete' request.dealer.slug user_.slug %}"
src="{{ user_.logo.url }}" data-message='{% trans "Are you sure you want to delete this user?" %}'
style="width: 100px; data-bs-toggle="modal" data-bs-target="#deleteModal">
height: 100px" <i class="fas fa-trash-alt me-2"></i>{% trans "Delete" %}
alt="User Image"> </button>
{% else %} </div>
<img class="rounded-circle" </div>
src="{% static 'img/default-user.png' %}"
style="width: 100px; <div class="row g-3">
height: 100px" <div class="col-lg-4">
alt="Default User Image"> <div class="card h-100 border-0 shadow-sm rounded-3">
{% endif %} <div class="card-body text-center p-3">
</div> <div class="position-relative d-inline-block mb-3">
<div class="col-md-5"> {% if user_.logo %}
<p> <img class="rounded-circle border border-4 border-body-tertiary"
<strong>{{ _("Name") }}:</strong> {{ user_.name }} src="{{ user_.logo.url }}" alt="{{ user_.name }}"
</p> style="object-fit: cover; width: 100px; height: 100px;">
<p> {% else %}
<strong>{{ _("Arabic Name") }}:</strong> {{ user_.arabic_name }} <div class="rounded-circle d-inline-flex align-items-center justify-content-center bg-primary text-white"
</p> style="width: 100px; height: 100px; font-size: 2.5rem;">
<p> {{ user_.name|first|upper }}
<strong>{{ _("Email") }}:</strong> {{ user_.email }} </div>
</p> {% endif %}
</div> </div>
<div class="col-md-5"> <h2 class="h6 fw-bold mb-1">{{ user_.name }}</h2>
<p> <p class="small mb-3"> <a href="mailto:{{ user_.email }}" class="text-secondary">{{ user_.email }}</a></p>
<strong>{{ _("Phone Number") }}:</strong> {{ user_.phone_number }} <div class="d-grid gap-1">
</p> <a href="tel:{{ user_.phone_number }}" class="btn btn-outline-primary btn-sm">
<div> <i class="fas fa-phone-alt me-2"></i>{{ user_.phone_number|default:"N/A" }}
<span><strong>{{ _("Roles") }}:</strong></span> </a>
{% for group in user_.groups %}
<span><strong>{{ group.name }}</strong></span>
{% if not forloop.last %}<span>&amp;</span>{% endif %}
{% endfor %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="table-responsive scrollbar mx-n1 px-1">
<div class="card-header "></div> <div class="col-lg-8">
<h4 class="my-4">Groups</h4> <div class="card h-100 border-0 shadow-sm rounded-3">
<a class="btn btn-sm btn-phoenix-primary mt-2 mb-4" <div class="card-body p-3">
href="{% url 'user_groups' request.dealer.slug user_.slug %}"><i class="fa-solid fa-users"></i> Manage Groups</a> <h4 class="mb-3 text-primary h6">{% trans "Personal Information" %}</h4>
<table class="table table-hover table-responsive-sm fs-9 mb-0"> <div class="row g-2 mb-4">
<thead> <div class="col-md-6">
<tr> <p class="small mb-0">{% trans "Arabic Name" %}</p>
<th>{% trans 'name'|capfirst %}</th> <p class="fw-bold small mb-0">{{ user_.arabic_name|default:"N/A" }}</p>
</tr> </div>
</thead> <div class="col-md-6">
<tbody> <p class="small mb-0">{% trans "Roles" %}</p>
{% for group in user_.groups %} <p class="fw-bold small mb-0">
<tr> {% for group in user_.groups %}
<td>{{ group }}</td> {{ group.name }} {% if not forloop.last %}&middot;{% endif %}
</tr> {% empty %}
{% empty %} <span class="text-muted">N/A</span>
<tr> {% endfor %}
<td>{% trans "No Group" %}</td> </p>
</tr> </div>
{% endfor %} </div>
</tbody>
</table> <div class="d-flex justify-content-between align-items-center mb-3">
</div> <h4 class="mb-0 text-primary h6">{% trans "Assigned Groups" %}</h4>
<div class="card-footer d-flex "> <a class="btn btn-sm btn-phoenix-primary" href="{% url 'user_groups' request.dealer.slug user_.slug %}">
<a class="btn btn-sm btn-phoenix-primary me-3" <i class="fa-solid fa-users me-2"></i>{% trans "Manage Groups" %}
href="{% url 'user_update' request.dealer.slug user_.slug %}"> </a>
{{ _("Edit") }} </div>
<i class="fa-solid fa-pen-to-square"></i> <div class="table-responsive scrollbar">
</a> <table class="table table-hover table-striped fs--1 mb-0">
<button class="btn btn-phoenix-danger btn-sm delete-btn me-3" <thead>
data-url="{% url 'user_delete' request.dealer.slug user_.slug %}" <tr>
data-message='{{ _("Are you sure you want to delete this user?") }}' <th scope="col" class="text-nowrap">{% trans 'Group Name'|capfirst %}</th>
data-bs-toggle="modal" </tr>
data-bs-target="#deleteModal"> </thead>
{{ _("Delete") }} <tbody>
<i class="fas fa-trash"></i> {% for group in user_.groups %}
</button> <tr>
<a class="btn btn-sm btn-phoenix-secondary me-3" <td class="small">{{ group }}</td>
href="{% url 'user_list' request.dealer.slug %}"> </tr>
{{ _("Back to Staffs List") }} {% empty %}
<i class="fa-regular fa-circle-left"></i> <tr>
</a> <td class="text-secondary small">{% trans "This user is not assigned to any groups." %}</td>
<a class="btn btn-sm btn-phoenix-secondary" </tr>
href="{% url 'staff_password_reset' request.dealer.slug user_.pk %}"> {% endfor %}
{{ _("Reset Password") }} </tbody>
<i class="fa-solid fa-key"></i> </table>
</a> </div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
{% include 'modal/delete_modal.html' %} {% include 'modal/delete_modal.html' %}
{% endblock %} {% endblock %}

View File

@ -5,32 +5,37 @@
{% trans "Group" %} {% trans "Group" %}
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
<div class="row">
<div class="row"> <div class="row justify-content-center mt-5 mb-3">
<div class="col-sm-9">
<div class="d-sm-flex justify-content-between"> <div class="col-lg-8 col-md-10">
<h3 class="mb-3"><i class="fa-solid fa-users"></i> {{ _("Manage Groups") }}</h3> <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-5 text-center">
{{ _("Manage Groups") }}<i class="fa-solid fa-users ms-2 text-primary"></i>
</h3>
</div>
<div class="card-body bg-light-subtle">
<form class="row g-3 mb-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 %}
<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 'user_detail' request.dealer.slug staff.slug %}" class="btn btn-lg btn-phoenix-secondary"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
</div>
</form>
</div>
</div> </div>
</div>
</div> </div>
<div class="row">
<div class="col-sm-9">
<form class="row g-3 mb-9" method="post" class="form" novalidate>
{% csrf_token %}
{{ redirect_field }}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex mb-3">
<a href="{% url 'user_detail' request.dealer.slug staff.slug %}" class="btn btn-phoenix-primary me-2 px-6"><i class="fa-solid fa-ban"></i> {% trans "Cancel" %}</a>
<button class="btn btn-phoenix-primary" type="submit">
<i class="fa-solid fa-floppy-disk"></i>
{{ _("Save") }}
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,94 +1,100 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% load render_table from django_tables2 %} {% load render_table from django_tables2 %}
{% block title %} {% block title %}
{% trans "Staffs" %} {% trans "Staffs" %}
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
{%if users or request.GET.q%} {%if users or request.GET.q%}
<section class=""> <div class="container py-4">
<div class="row mt-4"> <div class="d-flex align-items-center justify-content-between mb-4">
<div class="col-auto"> <h1 class="h3 mb-0">{% trans "Staffs" %}<i class="fa-solid fa-user-tie text-primary ms-2"></i></h1>
<div class="d-md-flex justify-content-between"> {% if request.user.userplan %}
<div> <div>
{% if request.user.userplan %} <a href="{% url 'user_create' request.dealer.slug %}" class="btn btn-phoenix-primary me-2">
<a href="{% url 'user_create' request.dealer.slug %}" <i class="fa-solid fa-plus me-2"></i>{% trans "Add New Staff" %}
class="btn btn-sm btn-phoenix-primary me-4"><i class="fa-solid fa-user-tie me-1"></i> {% trans "Add New Staff" %}</a> </a>
<a href="{% url 'group_list' request.dealer.slug %}" <a href="{% url 'group_list' request.dealer.slug %}" class="btn btn-phoenix-secondary">
class="btn btn-sm btn-phoenix-success"><i class="fa-solid fa-user-group me-1"></i> {% trans "Manage Groups & Permissions" %}</a> <i class="fa-solid fa-user-group me-2"></i>{% trans "Manage Groups" %}
{% else %} </a>
</div>
{% endif %}
</div>
{% url "pricing_page" request.dealer.slug as pricing_page_url %} <div class="card shadow-sm border-0">
{% include "message-illustration.html" with value1="No Active Plan, please create your subscription plan." value2="Manage Subscription" url=pricing_page_url %} <div class="card-body p-0">
<div class="table-responsive scrollbar">
{% endif %} <table class="table table-hover table-striped mb-0">
</div> <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 %}
<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>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
</div> </div>
<div class="table-responsive scrollbar mx-n1 px-1 mt-3">
<table class="table align-items-center table-flush table-hover"> {% if is_paginated %}
<thead> <div class="d-flex justify-content-center mt-3">
<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 %}
<tr>
<td class="align-middle white-space-nowrap ps-1">
<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="Logo">
{% endif %}
</div>
<a class="fs-8 fw-bold"
href="{% url 'user_detail' request.dealer.slug user.slug %}">{{ user.arabic_name }}</a>
</div>
</td>
<td class="align-middle white-space-nowrap align-items-center">{{ user.email }}</td>
<td class="align-middle white-space-nowrap align-items-center justify-content-center">{{ user.phone_number }}</td>
<td>
{% for group in user.groups %}
<span class="badge badge-sm bg-primary text-center"><i class="fa-solid fa-scroll"></i> {% trans 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"></i>
{% trans 'view'|capfirst %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex justify-content-center">
{% if is_paginated %}
{% include 'partials/pagination.html' %} {% include 'partials/pagination.html' %}
{% endif %} </div>
</div> {% endif %}
</div> </div>
</section>
{% else %} {% else %}
{% if request.user.userplan %} {% if request.user.userplan %}
{% url "user_create" request.dealer.slug as create_staff_url %} {% url "user_create" request.dealer.slug as create_staff_url %}
{% include "empty-illustration-page.html" with value="staff" url=create_staff_url %} {% include "empty-illustration-page.html" with value="staff" url=create_staff_url %}
{% else %} {% else %}
{% url "pricing_page" request.dealer.slug as pricing_page_url %} {% 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 %} {% include "message-illustration.html" with value1=_("No active plan, Please create a subscription plan.") value2=_("Buy Plan") url=pricing_page_url %}
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endblock %}
{% endblock %}