Compare commits

...

20 Commits

Author SHA1 Message Date
70807da5fa new chnages 2025-08-11 13:01:27 +03:00
069a595b52 dealer and staff detail, dashboard 2025-08-11 12:58:56 +03:00
d43a119092 small changes 2025-08-11 12:14:58 +03:00
Marwan Alwali
2868a4ebac update 2025-08-10 19:07:13 +03:00
b752b4ed9b add unique slug to opportunity 2025-08-07 17:28:49 +03:00
f482d3f2bd Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend 2025-08-07 16:14:24 +03:00
22b87d17f6 update lead create view #commented the user_create for customer and organization 2025-08-07 16:14:13 +03:00
988ea128f2 Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend 2025-08-07 16:09:40 +03:00
84d2c9b980 update customer and organization user account 2025-08-07 16:09:19 +03:00
3a7c2d39a8 Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend 2025-08-07 16:01:58 +03:00
3416f97078 update1 2025-08-07 16:01:30 +03:00
f545f84f5c Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend 2025-08-07 15:57:56 +03:00
d5ba1d767e small fix 2025-08-07 15:57:31 +03:00
d74940a4e0 add ticket permission 2025-08-07 15:54:24 +03:00
41228a21ac Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend 2025-08-07 15:51:25 +03:00
704354be1f update organization 2025-08-07 15:51:13 +03:00
e18f1da115 Merge branch 'main' of http://10.10.1.136:3000/ismail/haikal into frontend 2025-08-07 15:46:27 +03:00
01a1bdbd81 update 2025-08-07 15:46:03 +03:00
223f11133c update the customer and vendor 2025-08-07 15:40:09 +03:00
1189418511 email internationalization and dealer plan active condition 2025-08-07 14:34:07 +03:00
39 changed files with 1873 additions and 969 deletions

View File

@ -56,6 +56,7 @@ from .models import (
DealerSettings, DealerSettings,
Tasks, Tasks,
Recall, Recall,
Ticket
) )
from django_ledger import models as ledger_models from django_ledger import models as ledger_models
from django.forms import ( from django.forms import (
@ -2202,3 +2203,31 @@ class RecallCreateForm(forms.ModelForm):
'year_to': forms.NumberInput(attrs={'class': 'form-control'}), 'year_to': forms.NumberInput(attrs={'class': 'form-control'}),
} }
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
fields = ['subject', 'description', 'priority']
widgets = {
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 10}),
}
class TicketResolutionForm(forms.ModelForm):
resolution_notes = forms.CharField(
widget=forms.Textarea(attrs={'rows': 3}),
required=False,
help_text="Optional notes about how the issue was resolved."
)
class Meta:
model = Ticket
fields = ['status']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Limit status choices to resolution options
self.fields['status'].choices = [
('resolved', 'Resolved'),
('closed', 'Closed')
]

View File

@ -155,9 +155,12 @@ class DealerSlugMiddleware:
"/ar/signup/", "/en/signup/", "/ar/login/", "/en/login/", "/ar/signup/", "/en/signup/", "/ar/login/", "/en/login/",
"/ar/logout/", "/en/logout/", "/en/ledger/", "/ar/ledger/", "/ar/logout/", "/en/logout/", "/en/ledger/", "/ar/ledger/",
"/en/notifications/", "/ar/notifications/", "/en/appointment/", "/en/notifications/", "/ar/notifications/", "/en/appointment/",
"/ar/appointment/", "/en/feature/recall/","ar/feature/recall/" "/ar/appointment/", "/en/feature/recall/","/ar/feature/recall/",
"/ar/help_center/", "/en/help_center/",
] ]
if any(request.path_info.startswith(path) for path in paths): print("------------------------------------")
print(request.path in paths)
if request.path in paths:
return None return None
if not request.user.is_authenticated: if not request.user.is_authenticated:

View File

@ -381,7 +381,17 @@ class CarEquipment(models.Model, LocalizedNameMixin):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.slug: if not self.slug:
self.slug = slugify(self.name) base_slug = slugify(self.name)
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs) super().save(*args, **kwargs)
def __str__(self): def __str__(self):
@ -1208,7 +1218,17 @@ class Dealer(models.Model, LocalizedNameMixin):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.slug: if not self.slug:
self.slug = slugify(self.name) base_slug = slugify(self.name)
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs) super().save(*args, **kwargs)
@property @property
@ -1222,6 +1242,9 @@ class Dealer(models.Model, LocalizedNameMixin):
return None return None
@property @property
def customers(self):
return models.Customer.objects.filter(dealer=self)
@property
def user_quota(self): def user_quota(self):
try: try:
quota_dict = get_user_quota(self.user) quota_dict = get_user_quota(self.user)
@ -1502,7 +1525,7 @@ class Customer(models.Model):
CustomerModel, on_delete=models.SET_NULL, null=True CustomerModel, on_delete=models.SET_NULL, null=True
) )
user = models.OneToOneField( user = models.OneToOneField(
User, on_delete=models.CASCADE, related_name="customer_profile" User, on_delete=models.CASCADE, related_name="customer_profile", null=True, blank=True
) )
title = models.CharField( title = models.CharField(
choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title") choices=Title.choices, default=Title.NA, max_length=10, verbose_name=_("Title")
@ -1649,22 +1672,22 @@ class Customer(models.Model):
def deactivate_account(self): def deactivate_account(self):
self.active = False self.active = False
self.customer_model.active = False self.customer_model.active = False
self.user.is_active = False # self.user.is_active = False
self.customer_model.save() self.customer_model.save()
self.user.save() # self.user.save()
self.save() self.save()
def activate_account(self): def activate_account(self):
self.active = True self.active = True
self.customer_model.active = True self.customer_model.active = True
self.user.is_active = True # self.user.is_active = True
self.customer_model.save() self.customer_model.save()
self.user.save() # self.user.save()
self.save() self.save()
def permenant_delete(self): def permenant_delete(self):
self.customer_model.delete() self.customer_model.delete()
self.user.delete() # self.user.delete()
self.delete() self.delete()
@ -1676,7 +1699,7 @@ class Organization(models.Model, LocalizedNameMixin):
CustomerModel, on_delete=models.SET_NULL, null=True CustomerModel, on_delete=models.SET_NULL, null=True
) )
user = models.OneToOneField( user = models.OneToOneField(
User, on_delete=models.CASCADE, related_name="organization_profile" User, on_delete=models.CASCADE, related_name="organization_profile", null=True, blank=True
) )
name = models.CharField(max_length=255, verbose_name=_("Name")) name = models.CharField(max_length=255, verbose_name=_("Name"))
arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name")) arabic_name = models.CharField(max_length=255, verbose_name=_("Arabic Name"))
@ -1790,22 +1813,22 @@ class Organization(models.Model, LocalizedNameMixin):
def deactivate_account(self): def deactivate_account(self):
self.active = False self.active = False
self.user.is_active = False # self.user.is_active = False
self.customer_model.active = False self.customer_model.active = False
self.user.save() # self.user.save()
self.customer_model.save() self.customer_model.save()
self.save() self.save()
def activate_account(self): def activate_account(self):
self.active = True self.active = True
self.customer_model.active = True self.customer_model.active = True
self.user.is_active = True # self.user.is_active = True
self.customer_model.save() self.customer_model.save()
self.user.save() # self.user.save()
self.save() self.save()
def permenant_delete(self): def permenant_delete(self):
self.user.delete() # self.user.delete()
self.customer_model.delete() self.customer_model.delete()
self.delete() self.delete()
@ -2284,7 +2307,17 @@ class Opportunity(models.Model):
opportinity_for = self.organization.name opportinity_for = self.organization.name
if not self.slug: if not self.slug:
self.slug = slugify(f"opportunity {opportinity_for}") base_slug = slugify(f"opportinity {opportinity_for}")
self.slug = base_slug
counter = 1
while (
self.__class__.objects.filter(slug=self.slug)
.exclude(pk=self.pk)
.exists()
):
self.slug = f"{base_slug}-{counter}"
counter += 1
super().save(*args, **kwargs) super().save(*args, **kwargs)
class Meta: class Meta:
@ -3079,7 +3112,7 @@ class CustomGroup(models.Model):
"view_saleorder", "view_saleorder",
"view_leads", "view_leads",
"view_opportunity", "view_opportunity",
"view_customers", "view_customer",
], ],
) )
self.set_permissions( self.set_permissions(
@ -3507,3 +3540,63 @@ class RecallNotification(models.Model):
def __str__(self): def __str__(self):
return f"Notification for {self.dealer} about {self.recall}" return f"Notification for {self.dealer} about {self.recall}"
class Ticket(models.Model):
STATUS_CHOICES = [
('open', 'Open'),
('in_progress', 'In Progress'),
('resolved', 'Resolved'),
('closed', 'Closed'),
]
PRIORITY_CHOICES = [
('low', 'Low'),
('medium', 'Medium'),
('high', 'High'),
('critical', 'Critical'),
]
dealer = models.ForeignKey(Dealer, on_delete=models.CASCADE, related_name='tickets')
subject = models.CharField(max_length=200)
description = models.TextField()
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open')
priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='medium')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@property
def time_to_resolution(self):
"""
Calculate the time taken to resolve the ticket.
Returns None if ticket isn't resolved/closed.
Returns timedelta if resolved/closed.
"""
if self.status in ['resolved', 'closed'] and self.created_at:
return self.updated_at - self.created_at
return None
@property
def time_to_resolution_display(self):
"""
Returns a human-readable version of time_to_resolution
"""
resolution_time = self.time_to_resolution
if not resolution_time:
return "Not resolved yet"
days = resolution_time.days
hours, remainder = divmod(resolution_time.seconds, 3600)
minutes, _ = divmod(remainder, 60)
parts = []
if days > 0:
parts.append(f"{days} day{'s' if days != 1 else ''}")
if hours > 0:
parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
if minutes > 0 or not parts:
parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
return ", ".join(parts)
def __str__(self):
return f"#{self.id} - {self.subject} ({self.status})"

View File

@ -27,7 +27,8 @@ from django.db import transaction
from django_q.tasks import async_task from django_q.tasks import async_task
from plans.models import UserPlan from plans.models import UserPlan
from plans.signals import order_completed, activate_user_plan from plans.signals import order_completed, activate_user_plan
from inventory.tasks import send_email
from django.conf import settings
# logging # logging
import logging import logging
@ -1216,3 +1217,28 @@ def bill_model_in_approve_notification(sender, instance, created, **kwargs):
# ), # ),
# ), # ),
# ) # )
@receiver(post_save, sender=models.Ticket)
def send_ticket_notification(sender, instance, created, **kwargs):
if created:
subject = f"New Support Ticket: {instance.subject}"
message = f"""
A new support ticket has been created:
Ticket ID: #{instance.id}
Subject: {instance.subject}
Priority: {instance.get_priority_display()}
Description:
{instance.description}
Please log in to the admin panel to respond.
"""
send_email(
settings.DEFAULT_FROM_EMAIL,
[settings.SUPPORT_EMAIL],
subject,
message,
)

View File

@ -13,6 +13,17 @@ from django.db.models import Case, Value, When, IntegerField
register = template.Library() register = template.Library()
@register.filter
def get_percentage(value, total):
try:
value = int(value)
total = int(total)
if total == 0:
return 0
return (value / total) * 100
except (ValueError, TypeError):
return 0
@register.filter(name="percentage") @register.filter(name="percentage")
def percentage(value): def percentage(value):

View File

@ -46,7 +46,7 @@ urlpatterns = [
name="manager_dashboard", name="manager_dashboard",
), ),
path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"), path("dashboards/sales/", views.SalesDashboard.as_view(), name="sales_dashboard"),
path("test/", views.TestView.as_view(), name="test"),
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"),
# Dealer URLs # Dealer URLs
@ -1292,7 +1292,13 @@ urlpatterns = [
# staff profile # staff profile
path('<slug:dealer_slug>/staff/<slug:slug>detail/', views.StaffDetailView.as_view(), name='staff_detail'), path('<slug:dealer_slug>/staff/<slug:slug>detail/', views.StaffDetailView.as_view(), name='staff_detail'),
# tickets
path('help_center/view/', views.help_center, name='help_center'),
path('help_center/tickets/', views.ticket_list, name='ticket_list'),
path('help_center/tickets/create/', views.create_ticket, name='create_ticket'),
path('help_center/tickets/<int:ticket_id>/', views.ticket_detail, name='ticket_detail'),
path('help_center/tickets/<int:ticket_id>/update/', views.ticket_update, name='ticket_update'),
# path('help_center/tickets/<int:ticket_id>/ticket_mark_resolved/', views.ticket_mark_resolved, name='ticket_mark_resolved'),
] ]

View File

@ -106,10 +106,10 @@ from django_ledger.forms.bank_account import (
BankAccountUpdateForm, BankAccountUpdateForm,
) )
from django_ledger.views.bill import ( from django_ledger.views.bill import (
# BillModelCreateView, BillModelCreateView,
# BillModelDetailView, BillModelDetailView,
# BillModelUpdateView, BillModelUpdateView,
# BaseBillActionView as BaseBillActionViewBase, BaseBillActionView as BaseBillActionViewBase,
BillModelModelBaseView, BillModelModelBaseView,
) )
from django_ledger.forms.bill import ( from django_ledger.forms.bill import (
@ -277,11 +277,6 @@ def switch_language(request):
logger.warning(f"Invalid language code: {language}") logger.warning(f"Invalid language code: {language}")
return redirect("/") return redirect("/")
def testview(request):
return HttpResponse("test")
def dealer_signup(request): def dealer_signup(request):
from django_q.tasks import async_task from django_q.tasks import async_task
""" """
@ -356,7 +351,6 @@ def dealer_signup(request):
"account/signup-wizard.html", "account/signup-wizard.html",
) )
class HomeView(LoginRequiredMixin, TemplateView): class HomeView(LoginRequiredMixin, TemplateView):
""" """
HomeView class responsible for rendering the home page. HomeView class responsible for rendering the home page.
@ -393,6 +387,7 @@ class TestView(TemplateView):
template_name = "inventory/cars_list_api.html" template_name = "inventory/cars_list_api.html"
class ManagerDashboard(LoginRequiredMixin, TemplateView): class ManagerDashboard(LoginRequiredMixin, TemplateView):
""" """
ManagerDashboard class is a view handling the dashboard for a manager. ManagerDashboard class is a view handling the dashboard for a manager.
@ -465,20 +460,19 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
context["pending_leads"] = pending_leads context["pending_leads"] = pending_leads
context["canceled_leads"] = canceled_leads context["canceled_leads"] = canceled_leads
context["car"] = json.dumps(car_by_make) context["car"] = json.dumps(car_by_make)
context["available_cars"] = car_status.get(
models.CarStatusChoices.AVAILABLE, 0
) context["available_cars"] =qs.filter(status='available').count()
context["sold_cars"] = car_status.get(models.CarStatusChoices.SOLD, 0) context["sold_cars"] = qs.filter(status='sold').count()
context["reserved_cars"] = car_status.get( context["reserved_cars"] = qs.filter(status='reserved').count()
models.CarStatusChoices.RESERVED, 0 context["hold_cars"] =qs.filter(status='hold').count()
) context["damaged_cars"] = qs.filter(status='damaged').count()
context["hold_cars"] = car_status.get(models.CarStatusChoices.HOLD, 0) context["transfer_cars"] = qs.filter(status='transfer').count()
context["damaged_cars"] = car_status.get( context["present_inventory_count"]=total_cars-context["sold_cars"]-context["damaged_cars"]
models.CarStatusChoices.DAMAGED, 0 cars_sold=qs.filter(status='sold')
) # cars_sold.aggregate(total_inventory_value=sum())
context["transfer_cars"] = car_status.get(
models.CarStatusChoices.TRANSFER, 0
)
context["customers"] = entity.get_customers().count() context["customers"] = entity.get_customers().count()
context["staff"] = models.Staff.objects.filter(dealer=dealer).count() context["staff"] = models.Staff.objects.filter(dealer=dealer).count()
context["total_leads"] = models.Lead.objects.filter(dealer=dealer).count() context["total_leads"] = models.Lead.objects.filter(dealer=dealer).count()
@ -486,6 +480,8 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
context["estimates"] = entity.get_estimates().count() context["estimates"] = entity.get_estimates().count()
context["purchase_orders"] = entity.get_purchase_orders().count() context["purchase_orders"] = entity.get_purchase_orders().count()
return context return context
@ -512,7 +508,7 @@ class SalesDashboard(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
dealer = self.request.dealer dealer = self.request.dealer
staff = self.request.staff staff = getattr(self.request, "staff", None)
total_cars = models.Car.objects.filter(dealer=dealer).count() total_cars = models.Car.objects.filter(dealer=dealer).count()
total_reservations = models.CarReservation.objects.filter( total_reservations = models.CarReservation.objects.filter(
reserved_by=self.request.user, reserved_until__gte=timezone.now() reserved_by=self.request.user, reserved_until__gte=timezone.now()
@ -714,6 +710,17 @@ class AjaxHandlerView(LoginRequiredMixin, View):
if result := decodevin(vin_no): if result := decodevin(vin_no):
manufacturer_name, model_name, year_model = result.values() manufacturer_name, model_name, year_model = result.values()
car_make = get_make(manufacturer_name) car_make = get_make(manufacturer_name)
if not car_make:
logger.info(
f"VIN decoded using {decoding_method}: Make={manufacturer_name}, Model={model_name}, Year={year_model}"
)
return JsonResponse(
{
"success": False,
"error": _("Manufacturer not found in the database"),
},
status=404,
)
car_model = get_model(model_name, car_make) car_model = get_model(model_name, car_make)
logger.info( logger.info(
@ -2466,7 +2473,9 @@ class CustomerCreateView(
success_message = _("Customer created successfully") success_message = _("Customer created successfully")
def form_valid(self, form): def form_valid(self, form):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
if customer := models.Customer.objects.filter( if customer := models.Customer.objects.filter(
dealer=dealer,
email=form.instance.email email=form.instance.email
).first(): ).first():
if not customer.active: if not customer.active:
@ -2483,31 +2492,31 @@ class CustomerCreateView(
return redirect("customer_create") return redirect("customer_create")
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
form.instance.dealer = dealer form.instance.dealer = dealer
try: # try:
user = form.instance.create_user_model() # user = form.instance.create_user_model()
logger.info( # logger.info(
f"Successfully created Customer with '{user.username}' (ID: {user.id}) " # f"Successfully created Customer with '{user.username}' (ID: {user.id}) "
f"with email '{user.email}' for dealer '{dealer.name}'." # f"with email '{user.email}' for dealer '{dealer.name}'."
) # )
except IntegrityError as e: # except IntegrityError as e:
if "UNIQUE constraint" in str(e): # if "UNIQUE constraint" in str(e):
messages.error(self.request, _("Email already exists")) # messages.error(self.request, _("Email already exists"))
logger.info( # logger.info(
f"Attempted to create user with existing email '{form.instance.email}' " # f"Attempted to create user with existing email '{form.instance.email}' "
f"for dealer '{dealer.name}'. Message: '{e}'" # f"for dealer '{dealer.name}'. Message: '{e}'"
) # )
else: # else:
logger.error( # logger.error(
f"An unexpected IntegrityError occurred while creating user(customer) with email '{form.instance.email}' " # f"An unexpected IntegrityError occurred while creating user(customer) with email '{form.instance.email}' "
f"for dealer '{dealer.name}'. Error: {e}", # f"for dealer '{dealer.name}'. Error: {e}",
exc_info=True, # exc_info=True,
) # )
messages.error(self.request, str(e)) # messages.error(self.request, str(e))
return redirect("customer_create") # return redirect("customer_create")
customer = form.instance.create_customer_model() customer = form.instance.create_customer_model()
form.instance.user = user # form.instance.user = user
# form.instance.customer_model = customer form.instance.customer_model = customer
return super().form_valid(form) return super().form_valid(form)
@ -2552,7 +2561,7 @@ class CustomerUpdateView(
success_message = _("Customer updated successfully") success_message = _("Customer updated successfully")
def form_valid(self, form): def form_valid(self, form):
form.instance.update_user_model() # form.instance.update_user_model()
form.instance.update_customer_model() form.instance.update_customer_model()
return super().form_valid(form) return super().form_valid(form)
@ -2688,7 +2697,8 @@ class VendorCreateView(
permission_required = ["inventory.add_vendor"] permission_required = ["inventory.add_vendor"]
def form_valid(self, form): def form_valid(self, form):
if vendor := models.Vendor.objects.filter(email=form.instance.email).first(): dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
if vendor := models.Vendor.objects.filter(dealer=dealer,email=form.instance.email).first():
if not vendor.active: if not vendor.active:
messages.error( messages.error(
self.request, self.request,
@ -3451,7 +3461,7 @@ class UserCreateView(
return self.form_invalid(form) return self.form_invalid(form)
email = form.cleaned_data["email"] email = form.cleaned_data["email"]
if models.Staff.objects.filter(dealer=dealer, user__email=email).exists(): if models.Staff.objects.filter(user__email=email).exists():
messages.error( messages.error(
self.request, self.request,
_( _(
@ -3668,7 +3678,9 @@ class OrganizationCreateView(LoginRequiredMixin, PermissionRequiredMixin, Create
success_message = _("Organization created successfully") success_message = _("Organization created successfully")
def form_valid(self, form): def form_valid(self, form):
dealer = get_object_or_404(models.Dealer, slug=self.kwargs["dealer_slug"])
if organization := models.Organization.objects.filter( if organization := models.Organization.objects.filter(
dealer=dealer,
email=form.instance.email email=form.instance.email
).first(): ).first():
if not organization.active: if not organization.active:
@ -3685,9 +3697,9 @@ class OrganizationCreateView(LoginRequiredMixin, PermissionRequiredMixin, Create
return redirect("organization_create") return redirect("organization_create")
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
form.instance.dealer = dealer form.instance.dealer = dealer
user = form.instance.create_user_model() # user = form.instance.create_user_model()
customer = form.instance.create_customer_model() customer = form.instance.create_customer_model()
form.instance.user = user # form.instance.user = user
form.instance.customer_model = customer form.instance.customer_model = customer
return super().form_valid(form) return super().form_valid(form)
@ -3724,7 +3736,7 @@ class OrganizationUpdateView(LoginRequiredMixin, PermissionRequiredMixin, Update
success_message = _("Organization updated successfully") success_message = _("Organization updated successfully")
def form_valid(self, form): def form_valid(self, form):
form.instance.update_user_model() # form.instance.update_user_model()
form.instance.update_customer_model() form.instance.update_customer_model()
return super().form_valid(form) return super().form_valid(form)
@ -6044,6 +6056,7 @@ def lead_create(request, dealer_slug):
if instance.lead_type == "customer": if instance.lead_type == "customer":
customer = models.Customer.objects.filter( customer = models.Customer.objects.filter(
dealer=dealer,
email=instance.email email=instance.email
).first() ).first()
if not customer: if not customer:
@ -6056,13 +6069,14 @@ def lead_create(request, dealer_slug):
last_name=instance.last_name, last_name=instance.last_name,
active=False, active=False,
) )
customer.create_user_model(for_lead=True) # customer.create_user_model(for_lead=True)
customer.create_customer_model(for_lead=True) customer.create_customer_model(for_lead=True)
customer.save() customer.save()
instance.customer = customer instance.customer = customer
if instance.lead_type == "organization": if instance.lead_type == "organization":
organization = models.Organization.objects.filter( organization = models.Organization.objects.filter(
dealer=dealer,
email=instance.email email=instance.email
).first() ).first()
if not organization: if not organization:
@ -6074,7 +6088,7 @@ def lead_create(request, dealer_slug):
name=instance.first_name + " " + instance.last_name, name=instance.first_name + " " + instance.last_name,
active=False, active=False,
) )
organization.create_user_model(for_lead=True) # organization.create_user_model(for_lead=True)
organization.create_customer_model(for_lead=True) organization.create_customer_model(for_lead=True)
organization.save() organization.save()
instance.organization = organization instance.organization = organization
@ -9477,10 +9491,15 @@ def ledger_unpost_all_journals(request, dealer_slug, entity_slug, pk):
@login_required @login_required
@permission_required("inventory.change_dealer", raise_exception=True) @permission_required("inventory.change_dealer", raise_exception=True)
def pricing_page(request, dealer_slug): def pricing_page(request, dealer_slug):
get_object_or_404(models.Dealer, slug=dealer_slug) dealer=get_object_or_404(models.Dealer, slug=dealer_slug)
plan_list = PlanPricing.objects.all() if not dealer.active_plan:
form = forms.PaymentPlanForm() plan_list = PlanPricing.objects.all()
return render(request, "pricing_page.html", {"plan_list": plan_list, "form": form}) form = forms.PaymentPlanForm()
return render(request, "pricing_page.html", {"plan_list": plan_list, "form": form})
else:
messages.info(request,_("You already have an plan!!"))
return redirect('home',dealer_slug=dealer_slug)
@login_required @login_required
@ -10882,7 +10901,8 @@ def car_sale_report_view(request, dealer_slug):
# 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()
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
@ -11138,3 +11158,78 @@ def schedule_calendar(request,dealer_slug):
'upcoming_schedules':upcoming_schedules 'upcoming_schedules':upcoming_schedules
} }
return render(request, 'schedule_calendar.html', context) return render(request, 'schedule_calendar.html', context)
# Support
@login_required
def help_center(request):
return render(request, 'support/help_center.html')
@login_required
@permission_required('inventory.add_ticket')
def create_ticket(request):
if not request.is_dealer:
return redirect('home')
if request.method == 'POST':
form = forms.TicketForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.dealer = request.dealer
instance.save()
messages.success(request, 'Your support ticket has been submitted successfully!')
return redirect('ticket_list')
else:
form = forms.TicketForm()
return render(request, 'support/create_ticket.html', {'form': form})
@login_required
@permission_required('inventory.view_ticket')
def ticket_list(request):
tickets = models.Ticket.objects.all().order_by('-created_at')
if request.is_dealer:
tickets = tickets = tickets.filter(dealer=request.dealer)
return render(request, 'support/ticket_list.html', {'tickets': tickets})
@login_required
@permission_required('inventory.change_ticket')
def ticket_detail(request, ticket_id):
ticket = models.Ticket.objects.get(id=ticket_id)
return render(request, 'support/ticket_detail.html', {'ticket': ticket})
@login_required
@permission_required('inventory.change_ticket')
def ticket_mark_resolved(request, ticket_id):
ticket = models.Ticket.objects.get(id=ticket_id)
ticket.status = 'resolved'
ticket.save()
messages.success(request, 'Ticket marked as resolved successfully!')
subject = 'Ticket Resolved'
message = f"Your support ticket has been resolved. Please check the details below:\n\nTicket ID: {ticket.id}\nSubject: {ticket.subject}\nDescription: {ticket.description}"
send_email(
settings.SUPPORT_EMAIL,
ticket.dealer.user.email,
subject,
message
)
return render(request, 'support/ticket_detail.html', {'ticket': ticket})
@login_required
@permission_required('inventory.change_ticket')
def ticket_update(request, ticket_id):
ticket = models.Ticket.objects.get(id=ticket_id)
if request.method == 'POST':
form = forms.TicketResolutionForm(request.POST, instance=ticket)
if form.is_valid():
form.save()
messages.success(request, f'Ticket has been marked as {ticket.get_status_display()}.')
return redirect('ticket_detail', ticket_id=ticket.id)
else:
form = forms.TicketResolutionForm(instance=ticket)
return render(request, 'support/ticket_update.html', {
'ticket': ticket,
'form': form
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 MiB

View File

@ -1,123 +1,177 @@
{% load static i18n %} {% load i18n %}
<html> <!DOCTYPE html>
<head> <html lang="en">
<meta charset="utf-8" /> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta charset="UTF-8">
<title>Access Forbidden</title> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="" /> <title>403 - Access Forbidden</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="apple-touch-icon" <style>
sizes="180x180" :root {
href="{% static 'assets/img/favicons/apple-touch-icon.png' %}" /> --dark-bg: #121212;
<link rel="icon" --main-color: #ff3864;
type="image/png" --secondary-color: #e6e6e6;
sizes="32x32" }
href="{% static 'assets/img/favicons/favicon-32x32.png' %}" /> body, html {
<link rel="icon" height: 100%;
type="image/png" margin: 0;
sizes="16x16" font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
href="{% static 'assets/img/favicons/favicon-16x16.png' %}" /> background-color: var(--dark-bg);
<link rel="shortcut icon" color: var(--secondary-color);
type="image/x-icon" overflow: hidden; /* Hide overflow for the particles */
href="{% static 'assets/img/favicons/favicon.ico' %}" /> }
<link rel="manifest" .center-content {
href="{% static 'assets/img/favicons/manifest.json' %}" /> height: 100%;
<meta name="msapplication-TileImage" display: flex;
content="{% static 'assets/img/favicons/mstile-150x150.png' %}" /> align-items: center;
<meta name="theme-color" content="#ffffff" /> justify-content: center;
<script src="{% static 'vendors/simplebar/simplebar.min.js' %}"></script> text-align: center;
<script src="{% static 'assets/js/config.js' %}"></script> flex-direction: column;
<!-- =============================================== --> z-index: 2; /* Ensure content is on top of particles */
<!-- Stylesheets --> position: relative;
<!-- =============================================== --> }
<link rel="preconnect" href="https://fonts.googleapis.com" /> .glitch {
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" /> font-size: 10rem;
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@300;400;600;700;800;900&amp;display=swap" font-weight: bold;
rel="stylesheet" /> color: var(--main-color);
<link href="{% static 'vendors/simplebar/simplebar.min.css' %}" position: relative;
rel="stylesheet" /> animation: glitch-animation 2.5s infinite;
<link rel="stylesheet" }
href="https://unicons.iconscout.com/release/v4.0.8/css/line.css" /> @keyframes glitch-animation {
<link href="{% static 'assets/css/theme-rtl.min.css' %}" 0% { text-shadow: 2px 2px var(--secondary-color); }
type="text/css" 20% { text-shadow: -2px -2px var(--secondary-color); }
rel="stylesheet" 40% { text-shadow: 4px 4px var(--main-color); }
id="style-rtl" /> 60% { text-shadow: -4px -4px var(--main-color); }
<link href="{% static 'assets/css/theme.min.css' %}" 80% { text-shadow: 6px 6px var(--secondary-color); }
type="text/css" 100% { text-shadow: -6px -6px var(--secondary-color); }
rel="stylesheet" }
id="style-default" /> .main-message {
<link href="{% static 'assets/css/user-rtl.min.css' %}" font-size: 2.5rem;
type="text/css" margin-top: -20px;
rel="stylesheet" letter-spacing: 5px;
id="user-style-rtl" /> text-transform: uppercase;
<link href="{% static 'assets/css/user.min.css' %}" }
type="text/css" .sub-message {
rel="stylesheet" font-size: 1.2rem;
id="user-style-default" /> color: #8c8c8c;
<style> margin-top: 10px;
body, html { }
height: 100%; .home-button {
margin: 0; margin-top: 30px;
display: flex; padding: 12px 30px;
justify-content: center; background-color: var(--main-color);
align-items: center; border: none;
text-align: center; color: #fff;
} border-radius: 5px;
.main { font-size: 1rem;
width: 100%; text-decoration: none;
max-width: 1200px; transition: background-color 0.3s, transform 0.3s;
margin: auto; }
} .home-button:hover {
.flex-center { background-color: #d12e52;
display: flex; transform: scale(1.05);
justify-content: center; }
align-items: center;
} /* Particle Background styles */
.text-center { #particles-js {
text-align: center; position: absolute;
} width: 100%;
</style> height: 100%;
</head> top: 0;
<body> left: 0;
<main class="main" id="top"> z-index: 1;
<div id="main_content" class="px-3"> }
<div class="row min-vh-100 flex-center p-5"> </style>
<div class="col-12 col-xl-10 col-xxl-8"> </head>
<div class="row justify-content-center align-items-center g-5"> <body>
<div class="col-12 col-lg-6 text-center order-lg-1">
<img class="img-fluid w-md-50 w-lg-100 d-light-none" <div id="particles-js"></div>
src="{% static 'images/spot-illustrations/dark_403-illustration.png' %}"
alt="" <div class="center-content container-fluid">
width="540" /> <h1 class="glitch">403</h1>
</div> <h2 class="main-message">{% trans "Access Forbidden" %}</h2>
<div class="col-12 col-lg-6 text-center text-lg-start"> <p class="sub-message">{% trans "You do not have permission to view this page."%}</p>
<img class="img-fluid mb-6 w-50 w-lg-75 d-dark-none" <p class="sub-message fs-2">{% trans "Powered By Tenhal, Riyadh Saudi Arabia"%}</p>
src="{% static 'images/spot-illustrations/403.png' %}" <a href="{% url 'home' %}" class="home-button">{% trans "Go Home" %}</a>
alt="" /> </div>
<h2 class="text-body-secondary fw-bolder mb-3">Access Forbidden!</h2>
<p class="text-body mb-5"> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
Halt! Thou art endeavouring to trespass upon a realm not granted unto thee. <script src="https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js"></script>
<br class="d-none d-md-block d-lg-none" /> <script>
granted unto thee. /* Particles.js Configuration */
</p> particlesJS("particles-js", {
<a class="btn btn-lg btn-phoenix-primary" href="{% url 'home' %}">Go Home</a> "particles": {
</div> "number": {
</div> "value": 80,
</div> "density": {
</div> "enable": true,
</div> "value_area": 800
</main> }
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> },
<script src="{% static 'js/phoenix.js' %}"></script> "color": {
<script src="{% static 'vendors/popper/popper.min.js' %}"></script> "value": "#ff3864"
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script> },
<script src="{% static 'vendors/anchorjs/anchor.min.js' %}"></script> "shape": {
<script src="{% static 'vendors/is/is.min.js' %}"></script> "type": "circle",
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script> },
<script src="{% static 'vendors/lodash/lodash.min.js' %}"></script> "opacity": {
<script src="{% static 'vendors/list.js/list.min.js' %}"></script> "value": 0.5,
<script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script> "random": false,
<script src="{% static 'vendors/dayjs/dayjs.min.js' %}"></script> "anim": {
<script src="{% static 'assets/js/phoenix.js' %}"></script> "enable": false
</body> }
},
"size": {
"value": 3,
"random": true,
},
"line_linked": {
"enable": true,
"distance": 150,
"color": "#e6e6e6",
"opacity": 0.4,
"width": 1
},
"move": {
"enable": true,
"speed": 6,
"direction": "none",
"random": false,
"straight": false,
"out_mode": "out",
"bounce": false,
"attract": {
"enable": false,
}
}
},
"interactivity": {
"detect_on": "canvas",
"events": {
"onhover": {
"enable": true,
"mode": "grab"
},
"onclick": {
"enable": true,
"mode": "push"
},
"resize": true
},
"modes": {
"grab": {
"distance": 140,
"line_linked": {
"opacity": 1
}
},
"push": {
"particles_nb": 4
}
}
},
"retina_detect": true
});
</script>
</body>
</html> </html>

View File

@ -218,7 +218,7 @@
{% endif %} {% endif %}
<!-- Mark as Review --> <!-- Mark as Review -->
{% if bill.can_review %} {% if bill.can_review %}
{{request.is_accountant}}
<button class="btn btn-phoenix-warning" <button class="btn btn-phoenix-warning"
onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')"> onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' dealer_slug=request.dealer.slug entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')">
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %} <i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}

View File

@ -1,340 +1,641 @@
{% load i18n static custom_filters django_ledger %} {% extends 'base.html' %}
{% block content %} {% block content %}
<div class="row justify-content-between mb-2"> <div class="main-content flex-grow-1 mt-4 mb-3">
<h3 class="fs-4 fs-md-4 fs-xl-4 fw-black mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<span class="text-gradient-info me-3">{{ dealer.get_local_name }}</span> <p class="fs-2">Dashboard Overview<i class="fas fa-chart-area ms-3 text-primary fs-3"></i></p>
</h3> <div class="dropdown">
<p> <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<span class="badge badge-phoenix badge-phoenix-success me-2 fs-10"> Last 30 Days
<span class="fs-10 text-body-secondary me-1">{{ _("As of") }}</span>{% now "SHORT_DATETIME_FORMAT" %} </button>
</span> <ul class="dropdown-menu">
</p> <li><a class="dropdown-item" href="#">Today</a></li>
</div> <li><a class="dropdown-item" href="#">Last 7 Days</a></li>
<div class="row justify-content-between mb-2"> <li><a class="dropdown-item" href="#">Last 90 Days</a></li>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0"> </ul>
<span class="uil fs-5 lh-1 uil-users-alt text-success"></span>
<a href="{% url 'user_list' request.dealer.slug %}">
<h4 class="fs-6 pt-3">{{ staff }}</h4>
</a>
<p class="fs-9 mb-0">{{ _("Staff") }}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0">
<span class="uil fs-5 lh-1 uil-bolt-alt text-primary"></span>
<a href="{% url 'lead_list' request.dealer.slug %}">
<h4 class="fs-6 pt-3">{{ total_leads }}</h4>
</a>
<p class="fs-9 mb-0">{{ _("Leads") }}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0">
<span class="uil fs-5 lh-1 uil-user-plus text-warning"></span>
<a href="{% url 'customer_list' request.dealer.slug %}">
<h4 class="fs-6 pt-3">{{ customers }}</h4>
</a>
<p class="fs-9 mb-0">{{ _("Customers") }}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0">
<span class="uil fs-5 lh-1 uil-bill text-info"></span>
<a href="{% url 'invoice_list' request.dealer.slug %}">
<h4 class="fs-6 pt-3">{{ invoices }}</h4>
</a>
<p class="fs-9 mb-0">{{ _("Invoices") }}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0">
<span class="uil fs-5 lh-1 uil-comment-alt-question text-success-dark"></span>
<a href="{% url 'estimate_list' request.dealer.slug %}">
<h4 class="fs-6 pt-3">{{ estimates }}</h4>
</a>
<p class="fs-9 mb-0">{{ _("Quotations") }}</p>
</div>
<div class="col-6 col-md-4 col-xxl-2 text-center border-translucent border-start-xxl border-end-xxl-0 border-bottom-xxl-0 border-end border-bottom pb-4 pb-xxl-0">
<span class="uil fs-5 lh-1 uil-receipt-alt text-secondary"></span>
<a href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}">
<h4 class="fs-6 pt-3">{{ purchase_orders }}</h4>
</a>
<p class="fs-9 mb-0">{{ _("Purchase Orders") }}</p>
</div> </div>
</div> </div>
<div class="row g-3 pe-xxl-3 my-3">
<div class="col-12 col-xl-6 col-xxl-12"> <div class="row g-4 mb-5">
<div class="row"> <div class="col-md-6 col-lg-3">
<div class="col-4 col-xl-12 col-xxl-4 border-end border-end-xl-0 border-end-xxl pb-4 pt-4 pt-xl-0 pt-xxl-4 pe-4 pe-sm-5 pe-xl-0 pe-xxl-5"> <div class="card h-100 p-3">
<h4 class="text-body mb-4">{% trans 'inventory'|upper %}</h4> <div class="card-body">
<div class="d-md-flex flex-between-center"> <h5 class="card-title text-primary">Total Revenue</h5>
<div id="car-chart-by-make" <p class="stat-value">$1.25M</p>
class="order-sm-0 order-md-1" <span class="text-success small">+8% from last month</span>
style="height:64px;
width: 128px"></div>
<div class="mt-4 mt-md-0">
<h1 class="text-body-highlight">
{{ total_cars }} <span class="fs-6 text-body-highlight">{{ _("Car") }}</span>
</h1>
</div>
</div>
</div> </div>
<div class="col-4 col-xl-12 col-xxl-4 border-end border-end-xl-0 border-end-xxl py-4 ps-4 ps-sm-5 ps-xl-0 ps-xxl-5"> </div>
<h4 class="text-body mb-4">{% trans 'inventory value'|upper %}</h4> </div>
<div class="d-md-flex flex-between-center"> <div class="col-md-6 col-lg-3">
<div class="d-md-flex align-items-center gap-2"> <div class="card h-100 p-3">
<span class="fas fa-money-check-alt fs-5 text-success-light dark__text-opacity-75"></span> <div class="card-body">
<div class="d-flex d-md-block gap-2 align-items-center mt-1 mt-md-0"> <h5 class="card-title text-primary">Net Profit</h5>
<p class="fs-9 mb-0 mb-md-2 text-body-tertiary text-nowrap"></p> <p class="stat-value">$1.25M</p>
<h4 class="text-body-highlight mb-0"></h4> <span class="text-success small">+8% from last month</span>
</div>
</div>
<div class="mt-3 mt-md-0">
<h3 class="text-body-highlight mb-2">
{{ total_selling_price|currency_format }} <span class="icon-saudi_riyal"></span>
</h3>
</div>
</div>
</div> </div>
<div class="col-4 col-xl-12 col-xxl-4 border-end border-end-xl-0 border-end-xxl py-4 pe-4 pe-sm-5 pe-xl-0 pe-xxl-5"> </div>
<h4 class="text-body mb-4">{% trans "Profits"|upper %}</h4> </div>
<div class="d-md-flex flex-between-center"> <div class="col-md-6 col-lg-3">
<div class="d-md-flex align-items-center gap-2"> <div class="card h-100 p-3">
<span class="fa-solid fa-money-bill-trend-up fs-5 text-warning-light dark__text-opacity-75" <div class="card-body">
data-bs-theme="light"></span> <h5 class="card-title text-primary">Gross Profit</h5>
<div class="d-flex d-md-block gap-2 align-items-center mt-1 mt-md-0"> <p class="stat-value">$1.25M</p>
<p class="fs-9 mb-0 mb-md-2 text-body-tertiary text-nowrap"></p> <span class="text-success small">+8% from last month</span>
<h4 class="text-body-highlight mb-0"></h4> </div>
</div> </div>
</div> </div>
<div class="mt-3 mt-md-0"> <div class="col-md-6 col-lg-3">
<h3 class="text-body-highlight mb-2"> <div class="card h-100 p-3">
{{ total_profit|currency_format }} <span class="icon-saudi_riyal"></span> <div class="card-body">
</h3> <h5 class="card-title text-danger">Total Expense</h5>
</div> <p class="stat-value">$1.25M</p>
</div> <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> </div>
</div> </div>
<div class="row justify-content-between">
<div class="col-12 col-lg-12"> <div class="row g-4">
<div class="row"> <div class="col-lg-8">
<div class="card mb-3"> <div class="card h-100">
<div class="bg-holder" <div class="card-header">Monthly Revenue & Profit</div>
style="background-image:url({% static 'images/bg/38.png' %}); <div class="card-body chart-container">
background-position:left bottom; <canvas id="revenueProfitChart"></canvas>
background-size:auto"></div> </div>
<div class="card-body d-flex justify-content-between position-relative"> </div>
<div class="col-sm-7 col-md-8 col-xxl-8 mb-md-3 mb-lg-0"> </div>
<h3 class="mb-3">{{ _("Inventory by Status") }}</h3> <div class="col-lg-4">
<div class="row g-0"> <div class="card h-100">
<div class="col-6 col-xl-4"> <div class="card-header">Monthly Cars Sold</div>
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end border-translucent"> <div class="card-body d-flex align-items-center justify-content-center">
<div class="d-flex align-items-center mb-1"> <canvas id="CarsSoldByMonthChart"></canvas>
<span class="fa-solid fa-square fs-11 me-2 text-success" </div>
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Available") }}</span> </div>
</div> </div>
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ available_cars }}</h3> <div class="col-lg-6">
</div> <div class="card h-100">
</div> <div class="card-header">Inventory By Make</div>
<div class="col-6 col-xl-4"> <div class="card-body d-flex align-items-center justify-content-center">
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end-md-0 border-end-xl border-translucent"> <canvas id="salesByBrandChart"></canvas>
<div class="d-flex align-items-center mb-1"> </div>
<span class="fa-solid fa-square fs-11 me-2 text-warning" </div>
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Sold") }}</span> </div>
</div> <div class="col-lg-6">
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ sold_cars }}</h3> <div class="card h-100">
</div> <div class="card-header">Sales By Make</div>
</div> <div class="card-body d-flex align-items-center justify-content-center">
<div class="col-6 col-xl-4"> <canvas id="inventoryByBrandChart"></canvas>
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-bottom border-end border-end-md border-end-xl-0 border-translucent"> </div>
<div class="d-flex align-items-center mb-1"> </div>
<span class="fa-solid fa-square fs-11 me-2 text-danger" </div>
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Reserved") }}</span>
</div> <div class="col-lg-6">
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ reserved_cars }}</h3> <div class="card h-100">
</div> <div class="card-header">Salesperson Performance</div>
</div> <div class="card-body chart-container">
<div class="col-6 col-xl-4"> <canvas id="salespersonChart"></canvas>
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-end-xl border-bottom border-bottom-xl-0 border-translucent"> </div>
<div class="d-flex align-items-center mb-1"> </div>
<span class="fa-solid fa-square fs-11 me-2 text-primary" </div>
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Transfer") }}</span> <div class="col-lg-6">
</div> <div class="card h-100">
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ transfer_cars }}</h3> <div class="card-header">Top 5 Lead Sources</div>
</div> <div class="card-body chart-container">
</div> <canvas id="leadSourcesChart"></canvas>
<div class="col-6 col-xl-4"> </div>
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100 border-1 border-end border-translucent"> </div>
<div class="d-flex align-items-center mb-1"> </div>
<span class="fa-solid fa-square fs-11 me-2 text-warning-light" <div class="col-lg-12">
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Hold") }}</span> <div class="card h-100">
</div> <div class="card-header">Appointments by Weekday</div>
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ hold_cars }}</h3> <div class="card-body chart-container">
</div> <canvas id="appointmentsChart"></canvas>
</div> </div>
<div class="col-6 col-xl-4"> </div>
<div class="d-flex flex-column flex-center align-items-sm-start flex-md-row justify-content-md-between flex-xxl-column p-3 ps-sm-3 ps-md-4 p-md-3 h-100"> </div>
<div class="d-flex align-items-center mb-1">
<span class="fa-solid fa-square fs-11 me-2 text-secondary-dark" <div class="col-lg-12">
data-fa-transform="up-2"></span><span class="mb-0 fs-9 text-body">{{ _("Damaged") }}</span> <div class="card h-100">
</div> <div class="card-header">Lead Conversion Funnel</div>
<h3 class="fw-semibold ms-xl-3 ms-xxl-0 pe-md-2 pe-xxl-0 mb-0 mb-sm-3">{{ damaged_cars }}</h3> <div class="card-body d-flex align-items-center justify-content-center">
</div> <canvas id="leadFunnelChart"></canvas>
</div>
</div>
</div>
<div class="col-sm-5 col-md-4 col-xxl-4 my-3 my-sm-0">
<div class="position-relative d-flex flex-center mb-sm-4 mb-xl-0 echart-cars-by-status-container mt-sm-7 mt-lg-4 mt-xl-0">
<div id="echart-cars-by-status"
class="mx-auto mt-3 mt-md-0 mt-xl-3 mt-xxl-0"
style="min-height:245px;
width:100%"></div>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script> </div>
document.addEventListener("DOMContentLoaded", function () { <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
/* Car Chart By Make */ {% endblock content %}
const { getColor, rgbaColor } = window.phoenix.utils;
const handleTooltipPosition = ([pos, , dom, , size]) => {
// only for mobile device {% block customJS%}
if (window.innerWidth <= 540) {
const tooltipHeight = dom.offsetHeight; <script>
const obj = {top: pos[1] - tooltipHeight - 20}; ctx1=document.getElementById('CarsSoldByMonthChart')
obj[pos[0] < size.viewSize[0] / 2 ? 'left' : 'right'] = 5; new Chart(ctx1,{
return obj; 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
} }
return null; // else default behaviour },
}; tooltip: {
backgroundColor: 'rgba(52, 58, 64, 0.9)', // Darker tooltip background
titleColor: 'white',
const carData = {{ car|safe }} bodyColor: 'white',
const carNames = carData.map(item => item.id_car_make__name); callbacks: {
const carCounts = carData.map(item => item.count); label: function(context) {
let label = context.dataset.label || '';
const car_chart = echarts.init(document.getElementById('car-chart-by-make')); if (label) {
option = { label += ': ';
color: getColor("danger"), }
tooltip: { if (context.parsed.y !== null) {
trigger: 'axis', label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
position: (...params) => handleTooltipPosition(params), }
padding: [7, 10], return label;
axisPointer: {
type: 'none'
},
},
extraCssText: 'z-index: 1000',
responsive: true,
xAxis: [
{
type: 'category',
boundaryGap: false,
data: carNames,
axisLine: {
show: true,
lineStyle: {color: getColor('secondary-bg')}
},
axisTick: {
show: false
},
} }
], }
yAxis: [ }
{ },
show: false, scales: {
type: 'value', x: {
}
],
series: [
{
type: 'bar',
barWidth: 3,
backgroundStyle: {
borderRadius: [0.5, 0.5, 0, 0],
},
data: carCounts
},
],
grid: { grid: {
bottom: 0, color: 'rgba(0, 0, 0, 0.1)' // Lighter grid lines for better contrast
top: 0,
left: 10,
right: 10,
containLabel: false
}
};
car_chart.setOption(option);
/* Car Status Chart */
const car_status = echarts.init(document.getElementById('echart-cars-by-status'));
const data = [
{value: {{available_cars}}, name: '{{ _("Available") }}'},
{value: {{sold_cars}}, name: '{{ _("Sold")}}'},
{value: {{reserved_cars}}, name: '{{ _("Reserved") }}'},
{value: {{transfer_cars}}, name: '{{ _("Transfer") }}'},
{value: {{hold_cars}}, name: '{{ _("Hold") }}'},
{value: {{damaged_cars}}, name: '{{ _("Damaged") }}'}
];
option = {
color: [
rgbaColor(getColor('success'),0.7),
rgbaColor(getColor('warning'),0.7),
rgbaColor(getColor('danger'),0.7),
rgbaColor(getColor('primary'),0.7),
rgbaColor(getColor('warning-light'),0.7),
rgbaColor(getColor('secondary-light'),0.7),
],
tooltip: {
trigger: 'item',
padding: [7, 10],
backgroundColor: getColor('body-highlight-bg'),
borderColor: getColor('body-bg'),
textStyle: {color: getColor('light-text-emphasis')},
borderWidth: 1,
transitionDuration: 0,
extraCssText: 'z-index: 1000'
}, },
responsive: true, ticks: {
maintainAspectRatio: false, color: '#495057' // Darker text for x-axis labels
},
series: [ border: {
{ color: '#adb5bd' // A subtle border color
name: '',
type: 'pie',
radius: ['55%', '90%'],
startAngle: 90,
avoidLabelOverlap: false,
itemStyle: {
borderColor: getColor('body-bg'),
borderWidth: 3
},
label: {
show: false
},
emphasis: {
label: {
show: false
}
},
labelLine: {
show: false
},
data
}
],
grid: {
bottom: 0,
top: 0,
left: 0,
right: 0,
containLabel: false
} }
}; },
car_status.setOption(option); 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
</script> 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 %} {% endblock %}

View File

@ -33,7 +33,7 @@
height: 0; height: 0;
} }
.car-make-option input[type="checkbox"]:checked + .car-make-image-container .car-make-image { .car-make-option input[type="checkbox"]:checked + .car-make-image-container .car-make-image {
border: 2px solid #2c7be5; border: 3px solid rgb(44, 229, 44);
box-shadow: 0 0 10px rgba(44, 123, 229, 0.5); box-shadow: 0 0 10px rgba(44, 123, 229, 0.5);
background: #f0f7ff; background: #f0f7ff;
} }

View File

@ -1,239 +1,66 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n static custom_filters crispy_forms_filters %} {% load i18n static custom_filters crispy_forms_filters %}
{% block title %} {% block title %}
{% trans 'Profile' %} {% endblock %} {% trans 'Profile' %}
{% block content %} {% endblock %}
<div class="container-fluid">
<div class="row align-items-center justify-content-between g-3 mb-4">
<div class="col-auto">
<h2 class="mb-0">{% trans 'Profile' %}</h2>
</div> {% block content %}
<div class="col-auto"> <div class="container-fluid mb-3">
<div class="row g-2 g-sm-3"> <div class="row align-items-center justify-content-between g-3 mb-4">
<div class="col-auto"> <div class="col-auto">
<a href="{% url 'order_list' %}" class="btn btn-phoenix-success"><span class="fas fa-clipboard-list me-2"></span>{{ _("Plans History") }}</a> <h2 class="mb-0">{% trans 'Profile' %}</h2>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<a href="{% url 'billing_info' %}" class="btn btn-phoenix-info"><span class="fas fa-credit-card me-2"></span>{{ _("Billing Information") }}</a> <div class="dropdown">
</div> <button class="btn btn-phoenix-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<div class="col-auto"> <span class="fas fa-cog me-2"></span>{{ _("Manage Profile") }}
<a href="{% url 'account_change_password' %}" </button>
class="btn btn-phoenix-danger"><span class="fas fa-key me-2"></span>{{ _("Change Password") }}</a> <ul class="dropdown-menu dropdown-menu-end">
</div> <li><a class="dropdown-item" href="{% url 'dealer_update' dealer.slug %}"><span class="fas fa-edit me-2"></span>{{ _("Edit Profile") }}</a></li>
<div class="col-auto"> <li><a class="dropdown-item" href="{% url 'billing_info' %}"><span class="fas fa-credit-card me-2"></span>{{ _("Billing Information") }}</a></li>
<a class="btn btn-phoenix-primary" <li><a class="dropdown-item" href="{% url 'order_list' %}"><span class="fas fa-clipboard-list me-2"></span>{{ _("Plans History") }}</a></li>
href="{% url 'dealer_update' dealer.slug %}"><span class="fas fa-edit me-2 text-primary"></span>{{ _("Edit") }} </a> <li><hr class="dropdown-divider"></li>
</div> <li><a class="dropdown-item text-danger" href="{% url 'account_change_password' %}"><span class="fas fa-key me-2"></span>{{ _("Change Password") }}</a></li>
</div> </ul>
</div>
</div> </div>
<div class="row g-3"> </div>
<div class="col-12 col-lg-8"> </div>
<div class="card h-100">
<div class="card-body"> <div class="row g-3 mb-4">
<div class="border-bottom border-dashed pb-4"> <div class="col-12">
<div class="row align-items-center g-3 g-sm-5 text-center text-sm-start"> <div class="card shadow-sm h-100">
<div class="col-12 col-sm-auto"> <div class="card-body">
<input class="d-none" id="avatarFile" type="file" /> <div class="d-flex flex-column flex-sm-row align-items-sm-center g-3 g-sm-5 text-center text-sm-start">
<label class="cursor-pointer avatar avatar-5xl" for="avatarFile"> <div class="col-12 col-sm-auto mb-3 mb-sm-0">
{% if dealer.logo %} <input class="d-none" id="avatarFile" type="file" />
<img src="{{ dealer.logo.url }}" <label class="cursor-pointer avatar avatar-5xl border rounded-circle shadow-sm" for="avatarFile">
alt="{{ dealer.get_local_name }}" {% if dealer.logo %}
class="rounded-circle" <img src="{{ dealer.logo.url }}" alt="{{ dealer.get_local_name }}" class="rounded-circle" style="max-width: 150px" />
style="max-width: 150px" /> {% else %}
{% else %} <img src="{% static 'images/logos/logo.png' %}" alt="{{ dealer.get_local_name }}" class="rounded-circle" style="max-width: 150px" />
<span class="rounded-circle feather feather-user text-body-tertiary" {% endif %}
style="max-width: 150px"></span> </label>
<img src="{% static 'images/logos/logo.png' %}"
alt="{{ dealer.get_local_name }}"
class=""
style="max-width: 150px" />
{% endif %}
</label>
</div>
<div class="col-12 col-sm-auto flex-1">
<h3>{{ dealer.get_local_name }}</h3>
<p class="text-body-secondary">{% trans 'Joined' %} {{ dealer.joined_at|timesince }} {% trans 'ago' %}</p>
<div></div>
</div>
</div>
</div>
<div class="d-flex flex-between-center pt-4">
<div>
<h6 class="mb-2 text-body-secondary">{% trans 'last login'|capfirst %}</h6>
<h4 class="fs-7 text-body-highlight mb-0">{{ dealer.user.last_login|date:"D M d, Y H:i" }}</h4>
</div>
<div class="text-center me-1">
<h6 class="mb-2 text-body-secondary">{% trans 'Total users'|capfirst %}</h6>
<h4 class="fs-7 text-body-highlight mb-0">{{ dealer.staff_count }} / {{ allowed_users }}</h4>
</div>
<div class="text-center">
<h6 class="mb-2 text-body-secondary">{% trans 'Total cars'|capfirst %}</h6>
<h4 class="fs-7 text-body-highlight mb-0">{{ cars_count }} / {{ allowed_cars }}</h4>
</div>
</div>
</div> </div>
</div>
</div> <div class="flex-1 col-12 col-sm ms-2">
<div class="col-12 col-lg-4"> <h3>{{ dealer.get_local_name }}</h3>
<div class="card h-100"> <p class="text-body-secondary mb-1">{% trans 'Joined' %} {{ dealer.joined_at|timesince }} {% trans 'ago' %}</p>
<div class="card-body"> <span class="badge bg-primary-subtle text-primary">{% trans 'Last login' %}: {{ dealer.user.last_login|date:"D M d, Y H:i" }}</span>
<div class="border-bottom border-dashed">
<h4 class="mb-3">{% trans 'Default Address' %}</h4>
</div>
<div class="pt-4 mb-7 mb-lg-4 mb-xl-7">
<div class="row justify-content-between">
<div class="col-auto">
<h5 class="text-body-highlight">{% trans 'Address' %}</h5>
</div>
<div class="col-auto">
<p class="text-body-secondary">{{ dealer.address }}</p>
</div>
</div>
</div>
<div class="border-top border-dashed pt-4">
<div class="row flex-between-center mb-2">
<div class="col-auto">
<h5 class="text-body-highlight mb-0">{% trans 'Email' %}</h5>
</div>
<div class="col-auto">{{ dealer.user.email }}</div>
</div>
<div class="row flex-between-center">
<div class="col-auto">
<h5 class="text-body-highlight mb-0">{% trans 'Phone' %}</h5>
</div>
<div class="col-auto" dir="ltr">{{ dealer.phone_number }}</div>
</div>
</div>
</div> </div>
</div>
</div> <div class="col-12 col-sm-auto d-flex align-items-center justify-content-around flex-wrap mt-3 mt-sm-0">
</div> <div class="text-center mx-3 mb-2 mb-sm-0">
<div class="row g-3 my-2"> <h6 class="mb-2 text-body-secondary">{% trans 'Total users'|capfirst %}</h6>
<div class="col-12 col-lg-6"> <h4 class="fs-7 text-body-highlight mb-2">{{ dealer.staff_count }} / {{ allowed_users }}</h4>
<div class="card h-100"> <div class="progress" style="height: 5px; width: 100px;">
<div class="bg-holder d-dark-none" <div class="progress-bar bg-success" role="progressbar" style="width: {{ dealer.staff_count|get_percentage:allowed_users }}%;" aria-valuenow="{{ dealer.staff_count|get_percentage:allowed_users }}" aria-valuemin="0" aria-valuemax="100"></div>
style="background-image:url({% static 'images/bg/8.png' %});
background-position:left bottom;
background-size:auto"></div>
<div class="bg-holder d-light-none"
style="background-image:url({% static 'images/bg/8-dark.png' %});
background-position:left bottom;
background-size:auto"></div>
<div class="card-body d-flex flex-column justify-content-between position-relative">
<div class="d-flex justify-content-between">
<div class="mb-5 mb-md-0 mb-lg-5">
<div class="d-sm-flex d-md-block d-lg-flex align-items-center mb-3">
<h3 class="mb-0 me-2">{{ dealer.user.userplan.plan|capfirst }}</h3>
{% if dealer.user.userplan %}
{% if not dealer.user.userplan.is_expired %}
<span class="badge badge-phoenix fs-9 badge-phoenix-success"> <span class="badge-label">{% trans 'Active' %}</span><span class="ms-1" data-feather="check" style="height:16px;width:16px;"></span> </span>
{% else %}
<span class="badge badge-phoenix fs-9 badge-phoenix-danger"> <span class="badge-label">{% trans 'Expired' %}</span><span class="ms-1" data-feather="times" style="height:16px;width:16px;"></span> </span>
<a href="{% url 'pricing_page' request.dealer.slug %}"
class="btn btn-phoenix-secondary ms-2"><span class="fas fa-arrow-right me-2"></span>{{ _("Renew") }}</a>
{% endif %}
{% if dealer.user.userplan.plan.name != "Enterprise" %}
<a href="{% url 'pricing_page' request.dealer.slug %}"
class="btn btn-sm btn-phoenix-primary ms-2"><span class="fas fa-rocket me-2"></span>{{ _("Upgrade") }}</a>
{% endif %}
{% else %}
<span class="text-body-tertiary fw-semibold">You have no active plan.</span>
<a href="{% url 'pricing_page' request.dealer.slug %}"
class="btn btn-phoenix-secondary ms-2"><span class="fas fa-arrow-right me-2"></span>{{ _("Subscribe") }}</a>
{% endif %}
</div>
<p class="fs-9 text-body-tertiary">
{% trans 'Active until' %}: {{ dealer.user.userplan.expire }}&nbsp;&nbsp; <small>{% trans 'Days left' %}: {{ dealer.user.userplan.days_left }}</small>
</p>
<div class="d-flex align-items-end mb-md-5 mb-lg-0">
<h4 class="fw-bolder me-1">
{{ dealer.user.userplan.plan.planpricing_set.first.price }} <span class="icon-saudi_riyal"></span>
</h4>
<h5 class="fs-9 fw-normal text-body-tertiary ms-1">{{ _("Per month") }}</h5>
</div>
</div>
<img class="d-dark-none"
src="{% static 'images/logos/logo-d.png' %}"
style="opacity: 0.2;
height: 64px"
alt="" />
<img class="d-light-none"
src="{% static 'images/logos/logo.png' %}"
style="opacity: 0.2;
height: 64px"
alt="" />
</div>
<div class="row justify-content-end">
<div class="col-sm-8 col-md-12">
<div class="d-sm-flex d-md-block d-lg-flex justify-content-end align-items-end h-100">
<div class="list-unstyled mb-0 border-start-sm border-start-md-0 border-start-lg ps-sm-5 ps-md-0 ps-lg-5 border-secondary-subtle">
<div class="d-flex flex-column">
{% for line in dealer.user.userplan.plan.description|splitlines %}
<div class="d-flex align-items-center">
<span class="uil uil-check-circle text-success me-2"></span>
<span class="text-body-tertiary fw-semibold">{{ line }}</span>
</div>
{% endfor %}
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> <div class="text-center mx-3 mb-2 mb-sm-0">
</div> <h6 class="mb-2 text-body-secondary">{% trans 'Total cars'|capfirst %}</h6>
</div> <h4 class="fs-7 text-body-highlight mb-2">{{ cars_count }} / {{ allowed_cars }}</h4>
<div class="col-12 col-lg-6"> <div class="progress" style="height: 5px; width: 100px;">
<div class="card h-100"> <div class="progress-bar bg-info" role="progressbar" style="width: {{ cars_count|get_percentage:allowed_cars }}%;" aria-valuenow="{{ cars_count|get_percentage:allowed_cars }}" aria-valuemin="0" aria-valuemax="100"></div>
<div class="bg-holder"
style="background-image:url({% static 'images/bg/bg-left-20.png' %});
background-position:left bottom;
background-size:auto"></div>
<div class="card-body d-flex flex-column justify-content-center position-relative">
<h4 class="mb-3">{{ _("Makes you are selling") }}</h4>
<div class="d-flex justify-content-center ">
<div class="text-center me-3">
<div class="row">
{% for make in car_makes %}
<div class="col my-1">
{% if make.logo %}
<img src="{{ make.logo.url }}"
alt="{{ make.get_local_name }}"
class="rounded rounded-1"
style="height: 64px" />
{% endif %}
<p class="fs-10">{{ make.get_local_name }}</p>
</div>
{% endfor %}
</div>
<div class="row">
<a class="btn btn-sm btn-phoenix-warning"
href="{% url 'assign_car_makes' request.dealer.slug %}">{{ _("Select Makes") }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card h-100">
<div class="bg-holder"
style="background-image:url({% static 'images/bg/bg-left-20.png' %});
background-position:left bottom;
background-size:auto"></div>
<div class="card-body d-flex flex-column justify-content-center position-relative">
<h4 class="mb-3">{{ _("VAT") }}</h4>
<div class="d-flex justify-content-center ">
<div class="text-center me-3">
<div class="row">
<form action="{% url 'dealer_vat_rate_update' request.dealer.slug %}"
method="post">
{% csrf_token %}
{{ vatform|crispy }}
<button class="btn btn-sm btn-phoenix-primary" type="submit"><i class="fa-solid fa-pen-to-square me-1"></i>{% trans 'Update' %}</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -241,4 +68,179 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} </div>
<div class="row g-3">
<div class="col-12">
<div class="card shadow-sm">
<div class="card-body">
<ul class="nav nav-tabs nav-justified" id="profileTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="subscription-tab" data-bs-toggle="tab" data-bs-target="#subscription-pane" type="button" role="tab" aria-controls="subscription-pane" aria-selected="true"><span class="fas fa-star me-2"></span>{{ _("Plan & Subscription") }}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="contact-tab" data-bs-toggle="tab" data-bs-target="#contact-pane" type="button" role="tab" aria-controls="contact-pane" aria-selected="false"><span class="fas fa-info-circle me-2"></span>{{ _("Company Details") }}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="makes-tab" data-bs-toggle="tab" data-bs-target="#makes-pane" type="button" role="tab" aria-controls="makes-pane" aria-selected="false"><span class="fas fa-car me-2"></span>{{ _("Car Brands") }}</button>
</li>
</ul>
<div class="tab-content pt-4" id="profileTabsContent">
<div class="tab-pane fade show active" id="subscription-pane" role="tabpanel" aria-labelledby="subscription-tab">
<div class="row g-3">
<div class="col-12 col-lg-6">
<div class="card h-100 shadow-sm">
<div class="card-body">
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">{{ dealer.user.userplan.plan|capfirst }}</h3>
{% if dealer.user.userplan and not dealer.user.userplan.is_expired %}
<span class="badge bg-success-subtle text-success">{{ _("Active") }}</span>
{% elif dealer.user.userplan and dealer.user.userplan.is_expired %}
<span class="badge bg-danger-subtle text-danger">{{ _("Expired") }}</span>
{% else %}
<span class="badge bg-warning-subtle text-warning">{{ _("No Active Plan") }}</span>
{% endif %}
</div>
<p class="fs-9 text-body-secondary">
{% if dealer.user.userplan and not dealer.user.userplan.is_expired %}
{% trans 'Active until' %}: {{ dealer.user.userplan.expire }} &nbsp; <small>{% trans 'Days left' %}: {{ dealer.user.userplan.days_left }}</small>
{% else %}
{% trans 'Please subscribe or renew your plan to continue using our services.' %}
{% endif %}
</p>
<div class="d-flex align-items-end mb-3">
<h4 class="fw-bolder me-1">
{{ dealer.user.userplan.plan.planpricing_set.first.price }} <span class="icon-saudi_riyal"></span>
</h4>
<h5 class="fs-9 fw-normal text-body-tertiary ms-1">{{ _("Per month") }}</h5>
</div>
<ul class="list-unstyled mb-4">
{% for line in dealer.user.userplan.plan.description|splitlines %}
<li class="d-flex align-items-center mb-1">
<span class="uil uil-check-circle text-success me-2"></span>
<span class="text-body-secondary">{{ line }}</span>
</li>
{% endfor %}
</ul>
<div class="d-flex justify-content-end gap-2">
{% if dealer.user.userplan.is_expired %}
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-warning"><span class="fas fa-redo-alt me-2"></span>{{ _("Renew") }}</a>
{% endif %}
{% if dealer.user.userplan.plan.name != "Enterprise" %}
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-primary"><span class="fas fa-rocket me-2"></span>{{ _("Upgrade Plan") }}</a>
{% endif %}
{% if not dealer.user.userplan %}
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-success"><span class="fas fa-cart-plus me-2"></span>{{ _("Subscribe Now") }}</a>
{% endif %}
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card h-100 shadow-sm">
<div class="card-body d-flex flex-column justify-content-center">
<div class="d-flex justify-content-between mb-4">
<h5 class="mb-0 text-body-highlight">{{ _("Manage Users & Cars") }}</h5>
</div>
<div class="mb-4">
<h6 class="text-body-secondary">{{ _("Total users") }}</h6>
<div class="progress" style="height: 10px;">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ dealer.staff_count|get_percentage:allowed_users }}%;" aria-valuenow="{{ dealer.staff_count|get_percentage:allowed_users }}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
<span>{{ _("Used") }}: {{ dealer.staff_count }}</span>
<span>{{ _("Limit") }}: {{ allowed_users }}</span>
</div>
</div>
<div class="mb-4">
<h6 class="text-body-secondary">{{ _("Total cars") }}</h6>
<div class="progress" style="height: 10px;">
<div class="progress-bar bg-info" role="progressbar" style="width: {{ cars_count|get_percentage:allowed_cars }}%;" aria-valuenow="{{ cars_count|get_percentage:allowed_cars }}" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="d-flex justify-content-between text-body-secondary fs-9 mt-2">
<span>{{ _("Used") }}: {{ cars_count }}</span>
<span>{{ _("Limit") }}: {{ allowed_cars }}</span>
</div>
</div>
<small class="text-body-secondary mt-auto">{{ _("Contact support to increase your limits") }}</small>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="contact-pane" role="tabpanel" aria-labelledby="contact-tab">
<div class="row g-3">
<div class="col-12 col-lg-6">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="mb-3">{% trans 'Contact Information' %}</h5>
<div class="d-flex align-items-center mb-3">
<span class="fas fa-location-dot me-3 text-primary"></span>
<div>
<h6 class="mb-0">{% trans 'Address' %}</h6>
<p class="mb-0 text-body-secondary">{{ dealer.address }}</p>
</div>
</div>
<div class="d-flex align-items-center mb-3">
<span class="fas fa-envelope me-3 text-info"></span>
<div>
<h6 class="mb-0">{% trans 'Email' %}</h6>
<p class="mb-0 text-body-secondary">{{ dealer.user.email }}</p>
</div>
</div>
<div class="d-flex align-items-center">
<span class="fas fa-phone me-3 text-success"></span>
<div>
<h6 class="mb-0">{% trans 'Phone' %}</h6>
<p class="mb-0 text-body-secondary" dir="ltr">{{ dealer.phone_number }}</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="mb-3">{{ _("VAT Information") }}</h5>
<form action="{% url 'dealer_vat_rate_update' request.dealer.slug %}" method="post">
{% csrf_token %}
{{ vatform|crispy }}
<button class="btn btn-phoenix-primary mt-3" type="submit"><i class="fa-solid fa-pen-to-square me-1"></i>{% trans 'Update VAT' %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="makes-pane" role="tabpanel" aria-labelledby="makes-tab">
<div class="card h-100 shadow-sm">
<div class="card-body">
<h5 class="mb-4">{{ _("Makes you are selling") }}</h5>
<div class="d-flex flex-wrap gap-3 mb-4">
{% for make in car_makes %}
<div class="text-center p-2 border rounded-3">
{% if make.logo %}
<img src="{{ make.logo.url }}" alt="{{ make.get_local_name }}" class="rounded" style="height: 48px; width: auto; background-color:white;" />
{% endif %}
<p class="fs-8 text-body-secondary mt-1 mb-0">{{ make.get_local_name }}</p>
</div>
{% empty %}
<p class="text-body-secondary">{{ _("No car makes selected.") }}</p>
{% endfor %}
</div>
<a class="btn btn-phoenix-warning" href="{% url 'assign_car_makes' request.dealer.slug %}"><span class="fas fa-plus me-2"></span>{{ _("Select Makes") }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -13,6 +13,6 @@
</p> </p>
<p>مع أطيب التحيات،<br> <p>مع أطيب التحيات،<br>
فريق {{ SITE_NAME }}</p> فريق تنحل</p>
</body> </body>
</html> </html>

View File

@ -5,4 +5,4 @@
جدد اشتراكك الآن: {{ RENEWAL_URL }} جدد اشتراكك الآن: {{ RENEWAL_URL }}
مع أطيب التحيات، مع أطيب التحيات،
فريق {{ SITE_NAME }} فريق تنحل

View File

@ -9,6 +9,6 @@
<p><a href="{{ RENEWAL_URL }}">Renew now</a> to continue service.</p> <p><a href="{{ RENEWAL_URL }}">Renew now</a> to continue service.</p>
<p>Best regards,<br> <p>Best regards,<br>
The {{ SITE_NAME }} Team</p> The Team at Tenhal</p>
</body> </body>
</html> </html>

View File

@ -5,4 +5,4 @@ Your {{ plan.name }} subscription will expire in {{ days_until_expire }} days on
Renew now: {{ RENEWAL_URL }} Renew now: {{ RENEWAL_URL }}
Best regards, Best regards,
{{ SITE_NAME }} Team The Team at Tenhal

View File

@ -7,4 +7,4 @@ Please settle your outstanding balance of {{ amount_due }} .
If you have already paid, please disregard this notice. If you have already paid, please disregard this notice.
Best regards, Best regards,
{{ SITE_NAME }} Team The Team at Tenhal

View File

@ -7,5 +7,5 @@ Please settle your outstanding balance of {{ amount_due }} before the due date t
If you have already paid, please disregard this notice. If you have already paid, please disregard this notice.
Best regards, Best regards,
{{ SITE_NAME }} Team The Team at Tenhal

View File

@ -14,19 +14,21 @@
<body> <body>
<div class="container"> <div class="container">
<h2>Hello {{ user_name }},</h2> <h2>Hello {{ user_name }},</h2>
<p>This is a friendly reminder for your upcoming schedule:</p> <p>{% trans "This is a friendly reminder for your upcoming schedule" %}:</p>
<p> <p>
<span class="highlight">Purpose:</span> {{ schedule_purpose }}<br> <span class="highlight">{% trans "Purpose" %}:</span> {{ schedule_purpose }}<br>
<span class="highlight">Scheduled At:</span> {{ scheduled_at }}<br> <span class="highlight">{% trans "Scheduled At" %}:</span> {{ scheduled_at }}<br>
<span class="highlight">Type:</span> {{ schedule_type }}<br> <span class="highlight">{% trans "Type" %}:</span> {{ schedule_type }}<br>
{% if customer_name != 'N/A' %}<span class="highlight">Customer:</span> {{ customer_name }}<br>{% endif %} {% if customer_name != 'N/A' %}<span class="highlight">{% trans "Customer" %}:</span> {{ customer_name }}<br>{% endif %}
{% if notes %}<span class="highlight">Notes:</span> {{ notes }}<br>{% endif %} {% if notes %}<span class="highlight">{% trans "Notes" %}:</span> {{ notes }}<br>{% endif %}
</p> </p>
<p>Please be prepared for your schedule.</p> <p>{% trans "Please be prepared for your schedule" %}.</p>
<p>Thank you!</p> <p>{% trans "Thank you" %}!</p>
<p class="fs-4">{% trans "The team at Tenhal" %}.</p>
<div class="footer"> <div class="footer">
<p>This is an automated reminder. Please do not reply to this email.</p> <p>{% trans "This is an automated reminder. Please do not reply to this email." %}</p>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,16 +1,16 @@
Hello {{ user_name }}, {% trans "Hello" %} {{ user_name }},
This is a friendly reminder for your upcoming schedule: {% trans "This is a friendly reminder for your upcoming schedule" %}:
Purpose: {{ schedule_purpose }} {% trans "Purpose" %}: {{ schedule_purpose }}
Scheduled At: {{ scheduled_at }} {% trans "Scheduled At" %}: {{ scheduled_at }}
Type: {{ schedule_type }} {% trans "Type" %}: {{ schedule_type }}
{% if customer_name != 'N/A' %}Customer: {{ customer_name }}{% endif %} {% if customer_name != 'N/A' %}{% trans "Customer" %}: {{ customer_name }}{% endif %}
{% if notes %}Notes: {{ notes }}{% endif %} {% if notes %}{% trans "Notes" %}: {{ notes }}{% endif %}
Please be prepared for your schedule. {% trans "Please be prepared for your schedule" %}.
Thank you!
{% trans "Thank you" %}!
--- ---
This is an automated reminder. Please do not reply to this email. {% trans " The Team at Tenhal" %}
{% trans "This is an automated reminder. Please do not reply to this email" %}.

View File

@ -46,8 +46,9 @@
{% else %} {% else %}
{% static 'images/no_content/no_item.jpg' as final_image_path %} {% static 'images/no_content/no_item.jpg' as final_image_path %}
{% endif %} {% endif %}
<p class="sm">
<img src="{{ final_image_path }}" alt="No-empty-state-image" class="empty-state-image"> <img src="{{ final_image_path }}" alt="No-empty-state-image" class="empty-state-image">
<p>
<!-- Title --> <!-- Title -->
<h3 class="empty-state-title"> <h3 class="empty-state-title">

View File

@ -33,7 +33,7 @@
</div> </div>
</footer> {% endcomment %} </footer> {% endcomment %}
<footer class="footer position-absolute fs-9 bg-white text-secondary"> {% comment %} <footer class="footer position-absolute fs-9 bg-white text-secondary">
<div class="row g-0 justify-content-between align-items-center h-100"> <div class="row g-0 justify-content-between align-items-center h-100">
<div class="col-12 col-sm-auto text-center"> <div class="col-12 col-sm-auto text-center">
<span class="text-body"> © 2025 {{ _("All right reserved")}}</span> <span class="text-body"> © 2025 {{ _("All right reserved")}}</span>
@ -50,4 +50,74 @@
<span class="fas fa-registered fs-10 fw-light text-opacity-85 text-secondary"></span> <span class="fas fa-registered fs-10 fw-light text-opacity-85 text-secondary"></span>
</div> </div>
</div> </div>
</footer> </footer> {% endcomment %}
<style>
.improved-footer {
/* Kept `position-absolute` and adjusted padding */
position: absolute;
bottom: 0;
width: 90%;
padding: 1.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.05);
color: var(--text-color);
}
.improved-footer .text-body {
color: var(--text-color) !important;
font-weight: 400;
}
.improved-footer .fw-bold {
font-weight: 600 !important;
color: var(--link-color);
}
.improved-footer a {
color: var(--link-color) !important;
text-decoration: none;
transition: color 0.3s ease-in-out, transform 0.3s ease-in-out;
}
.improved-footer a:hover {
color: #d1d5db !important; /* A slightly softer white on hover */
transform: translateY(-2px);
}
.improved-footer .fas.fa-registered {
font-size: 0.8rem;
color: var(--text-color);
opacity: 0.6;
}
</style>
<footer class="improved-footer">
<div class="container">
<div class="row g-0 justify-content-between align-items-center h-100">
<div class="col-12 col-sm-auto text-center">
<span class="text-body"> © 2025 All rights reserved</span>
<span class="fw-bold">Haikal</span>&nbsp;|&nbsp;<span class="fw-bold">هيكل</span>
</div>
<div class="col-12 col-sm-auto text-center">
<span class="text-body">Powered by </span>
<span>
<a class="mx-1 text-secondary" href="https://tenhal.sa">
<span>TENHAL</span>&nbsp;|&nbsp;<span>تنحل</span>
</a>
</span>
<span class="fas fa-registered fw-light"></span>
</div>
</div>
</div>
</footer>

View File

@ -46,15 +46,15 @@
</li> </li>
{% endif %} {% endif %}
{% if perms.inventory.add_car %} {% comment %} {% if perms.inventory.add_car %}
{% comment %} <li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}"> <a class="nav-link" href="{% url 'upload_cars' request.dealer.slug %}">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-file-import"></span></span><span class="nav-link-text">{% trans "Bulk Upload"|capfirst %}</span> <span class="nav-link-icon"><span class="fas fa-file-import"></span></span><span class="nav-link-text">{% trans "Bulk Upload"|capfirst %}</span>
</div> </div>
</a> </a>
</li> {% endcomment %} </li>
{% endif %} {% endif %} {% endcomment %}
{% if perms.django_ledger.view_purchaseordermodel %} {% if perms.django_ledger.view_purchaseordermodel %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}"> <a class="nav-link" href="{% url 'purchase_order_list' request.dealer.slug request.dealer.entity.slug %}">
@ -294,6 +294,8 @@
</div> </div>
</div> </div>
{% endif %} {% endif %}
<!---->
{% if perms.django_ledger.can_view_reports %} {% if perms.django_ledger.can_view_reports %}
<div class="nav-item-wrapper"> <div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-reports" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-reports"> <a class="nav-link dropdown-indicator label-1" href="#nv-reports" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-reports">
@ -303,79 +305,69 @@
</div> </div>
</a> </a>
{% if perms.django_ledger.view_accountmodel %} <div class="parent-wrapper label-1">
<div class="parent-wrapper label-1"> <ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-reports">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-reports"> <li class="collapsed-nav-item-title d-none">{% trans 'Financials' %}</li>
{% if perms.django_ledger.view_accountmodel %}
<li class="nav-item"> <li class="nav-item">
{% comment %} {% if request.user.is_authenticated and request.is_dealer %}
<a class="nav-link" href="{% url 'entity-dashboard' request.dealer.entity.slug %}"> <a class="nav-link" href="{% url 'entity-cf' request.dealer.slug request.dealer.entity.slug %}">
{% elif request.user.is_authenticated and request.is_staff %} <div class="d-flex align-items-center">
<a class="nav-link" href="{% url 'entity-dashboard' request.user.staffmember.staff.dealer.entity.slug %}"> <span class="nav-link-icon"><span class="fas fa-solid fa-sack-dollar"></span></span><span class="nav-link-text">{% trans 'Cash Flow'|capfirst %}</span>
{% else %} </div>
<a class="nav-link" href="#"> </a>
{% endif %} {% endcomment %}
<div class="d-flex align-items-center">
{% comment %} <i class="fa-solid fa-chart-line"></i><span class="nav-link-text">{% trans 'Dashboard'|capfirst %}</span> {% endcomment %} </li>
</div>
</a> <li class="nav-item">
{% if request.user.is_authenticated %} <a class="nav-link" href="{% url 'entity-ic' request.dealer.slug request.dealer.entity.slug %}">
<a class="nav-link" href="{% url 'entity-cf' request.dealer.slug request.dealer.entity.slug %}"> <div class="d-flex align-items-center">
{% else %} <span class="nav-link-icon"><span class="fa-solid fa-sheet-plastic"></span></span><span class="nav-link-text">{% trans 'Income Statement'|capfirst %}</span>
<a class="nav-link" href="#"> </div>
{% endif %} </a>
<div class="d-flex align-items-center"> </li>
<i class="fa-solid fa-sack-dollar"></i><span class="nav-link-text">{% trans 'Cash Flow'|capfirst %}</span>
</div>
</a>
{% if request.user.is_authenticated %} <li class="nav-item">
<a class="nav-link" href="{% url 'entity-ic' request.dealer.slug request.dealer.entity.slug %}"> <a class="nav-link" href="{% url 'entity-bs' request.dealer.slug request.dealer.entity.slug %}">
{% else %} <div class="d-flex align-items-center">
<a class="nav-link" href="#"> <span class="nav-link-icon"><span class="fas fa-solid fa-scale-balanced"></span></span><span class="nav-link-text">{% trans 'Balance Sheet'|capfirst %}</span>
</div>
</a>
</li>
<li class="nav-item">
<!--car purchase report-->
<a class="nav-link" href="{% url 'po-report' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-chart-pie"></span></span><span class="nav-link-text">{% trans 'Car purchase Report'|capfirst %}</span>
</div>
</a>
</li>
<li class="nav-item">
<!--car sale report-->
<a class="nav-link" href="{% url 'car-sale-report' request.dealer.slug %}">
<div class="d-flex align-items-center">
<span class="nav-link-icon"><span class="fas fa-chart-pie"></span></span><span class="nav-link-text">{% trans 'Car Sale Report'|capfirst %}</span>
</div>
</a>
</li>
{% endif %} {% endif %}
<div class="d-flex align-items-center">
<i class="fa-solid fa-sheet-plastic"></i><span class="nav-link-text">{% trans 'Income Statement'|capfirst %}</span>
</div>
</a>
{% if request.user.is_authenticated %}
<a class="nav-link" href="{% url 'entity-bs' request.dealer.slug request.dealer.entity.slug %}">
{% else %}
<a class="nav-link" href="#">
{% endif %}
<div class="d-flex align-items-center">
<i class="fa-solid fa-scale-balanced"></i><span class="nav-link-text">{% trans 'Balance Sheet'|capfirst %}</span>
</div>
</a>
<!--car purchase report-->
{% if request.user.is_authenticated %}
<a class="nav-link" href="{% url 'po-report' request.dealer.slug %}">
{% else %}
<a class="nav-link" href="#">
{% endif %}
<div class="d-flex align-items-center">
<i class="fas fa-chart-pie"></i><span class="nav-link-text">{% trans 'Car purchase Report'|capfirst %}</span>
</div>
</a>
<!--car sale report-->
{% if request.user.is_authenticated %}
<a class="nav-link" href="{% url 'car-sale-report' request.dealer.slug %}">
{% else %}
<a class="nav-link" href="#">
{% endif %}
<div class="d-flex align-items-center">
<i class="fas fa-chart-pie"></i><span class="nav-link-text">{% trans 'Car Sale Report'|capfirst %}</span>
</div>
</a>
</li> </ul>
</ul>
</div>
{% endif %}
</div> </div>
</div>
{% endif %}
<!---->
</li> </li>
</ul> </ul>
{# --- Support & Contact Section (New) --- #} {# --- Support & Contact Section (New) --- #}
<div class="mt-auto bg-info-subtle"> <div class="mt-auto">
<ul class="navbar-nav flex-column"> <ul class="navbar-nav flex-column">
<li class="nav-item"> <li class="nav-item">
@ -408,15 +400,16 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %}
<div class="navbar-vertical-footer"> <div class="navbar-vertical-footer">
<button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center"> <button class="btn navbar-vertical-toggle border-0 fw-semibold w-100 white-space-nowrap d-flex align-items-center">
<span class="fas fa-angle-double-left fs-8"></span><span class="fas fa-angle-double-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span> <span class="fas fa-angle-double-left fs-8"></span><span class="fas fa-angle-double-right fs-8"></span><span class="navbar-vertical-footer-text ms-2">Collapsed View</span>
</button> </button>
</div> </div>
</nav>
</nav>
<nav class="navbar navbar-top fixed-top navbar-expand" id="navbarDefault"> <nav class="navbar navbar-top fixed-top navbar-expand" id="navbarDefault">
@ -492,7 +485,7 @@
{% if request.is_dealer %} {% if request.is_dealer %}
<h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6> <h6 class="mt-2 text-body-emphasis">{{ user.dealer.get_local_name }}</h6>
{% else %} {% else %}
<h6 class="mt-2 text-body-emphasis">{{ user.staff.get_local_name }}</h6> <h6 class="mt-2 text-body-emphasis">{{ user.staffmember.staff.get_local_name }}</h6>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -542,7 +535,7 @@
<a class="nav-link px-3 d-block" href=""> <span class="me-2 text-body align-bottom" data-feather="user-plus"></span>Add another account</a> <a class="nav-link px-3 d-block" href=""> <span class="me-2 text-body align-bottom" data-feather="user-plus"></span>Add another account</a>
</li>--> </li>-->
</ul> </ul>
<hr /> </hr>
<div class="px-3"> <div class="px-3">
<a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100" href="{% url 'account_logout' %}"> <span class="me-2" data-feather="log-out"> </span>{% trans 'Sign Out' %}</a> <a class="btn btn-sm btn-phoenix-danger d-flex flex-center w-100" href="{% url 'account_logout' %}"> <span class="me-2" data-feather="log-out"> </span>{% trans 'Sign Out' %}</a>
</div> </div>

View File

@ -180,7 +180,7 @@
<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.finances.cost_price }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9">{{ car.finances.cost_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.marked_price }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9">{{ car.finances.marked_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.discount_amount }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9">{{ car.finances.total_discount }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.selling_price }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9">{{ car.finances.selling_price }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.finances.vat_amount }} <span class="icon-saudi_riyal"></span></td> <td class="fs-9">{{ car.finances.vat_amount }} <span class="icon-saudi_riyal"></span></td>
<td class="fs-9">{{ car.invoice.invoice_number }}</td> <td class="fs-9">{{ car.invoice.invoice_number }}</td>

View File

@ -9,5 +9,5 @@
{% trans "Thank you" %} {% trans "Thank you" %}
-- --
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %} {% blocktrans %}The Team at Tenhal{% endblocktrans %}
{% endautoescape %} {% endautoescape %}

View File

@ -10,5 +10,5 @@ http://{{ site_domain }}{% url 'upgrade_plan' %}
{% trans "Thank you" %} {% trans "Thank you" %}
-- --
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %} {% blocktrans %}The Team at Tenhal{% endblocktrans %}
{% endautoescape %} {% endautoescape %}

View File

@ -1 +1,3 @@
{% load i18n %}{% blocktrans %}Your account {{ user }} has just expired{% endblocktrans %} {% load i18n %}{% blocktrans %}Your account {{ user }} has just expired{% endblocktrans %}

View File

@ -7,5 +7,5 @@
{% trans "Thank you" %} {% trans "Thank you" %}
-- --
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %} {% blocktrans %}The Team at Tenhal{% endblocktrans %}
{% endautoescape %} {% endautoescape %}

View File

@ -10,5 +10,5 @@ http://{{ site_domain }}{% url 'order' pk=order %}
{% trans "Thank you" %} {% trans "Thank you" %}
-- --
{% blocktrans %}The Team at {{ site_name }}{% endblocktrans %} {% blocktrans %}The Team at Tenhal{% endblocktrans %}
{% endautoescape %} {% endautoescape %}

View File

@ -8,3 +8,8 @@ http://{{ site_domain }}{% url 'current_plan' %}
{% blocktrans %}or you can upgrade your plan here:{% endblocktrans %} {% blocktrans %}or you can upgrade your plan here:{% endblocktrans %}
http://{{ site_domain }}{% url 'upgrade_plan' %} http://{{ site_domain }}{% url 'upgrade_plan' %}
{% trans "Thank you" %}
--
{% blocktrans %}The Team at Tenhal{% endblocktrans %}
{% endautoescape %}

View File

@ -39,8 +39,9 @@
</style> </style>
<div class="empty-state-container alert mb-2" role="alert"> <div class="empty-state-container alert mb-2" role="alert">
<div class="sm">
<img src="{% static 'images/no_content/no_item.jpg' %}" alt="message-image" class="empty-state-image mb-2"> <img src="{% static 'images/no_content/no_item.jpg' %}" alt="message-image" class="empty-state-image mb-2">
</div>
<!-- Title --> <!-- Title -->
<h3 class="empty-state-title mb-2"> <h3 class="empty-state-title mb-2">
{% blocktrans %}{{ value1}}{% endblocktrans %} {% blocktrans %}{{ value1}}{% endblocktrans %}

View File

@ -2,107 +2,124 @@
{% load i18n static custom_filters crispy_forms_filters %} {% load i18n static custom_filters crispy_forms_filters %}
{% block title %} {% block title %}
{% trans 'Profile' %} {% endblock %} {% trans 'Profile' %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid py-4">
<div class="row align-items-center justify-content-between g-3 mb-4"> <div class="row align-items-center justify-content-between g-3 mb-5">
<div class="col-auto"> <div class="col-auto">
<h2 class="mb-0">{% trans 'Profile' %}</h2> <h1 class="display-5 fw-bolder mb-0">{% trans 'User Profile' %}</h1>
</div>
<div class="col-auto">
<div class="row g-2 g-sm-3">
<div class="col-auto">
<a href="{% url 'staff_password_reset' request.dealer.slug staff.pk %}"
class="btn btn-phoenix-danger"><span class="fas fa-key me-2"></span>{{ _("Change Password") }}</a>
</div>
</div>
</div>
</div> </div>
<div class="row g-3"> <div class="col-auto">
<div class="col-12 col-lg-8"> <a href="{% url 'staff_password_reset' request.dealer.slug staff.pk %}"
<div class="card h-100"> class="btn btn-phoenix-primary btn-lg shadow-sm">
<div class="card-body"> <i class="fas fa-key me-2"></i>{{ _("Change Password") }}
<div class="border-bottom border-dashed pb-4"> </a>
<div class="row align-items-center g-3 g-sm-5 text-center text-sm-start">
<div class="col-12 col-sm-auto">
<input class="d-none" id="avatarFile" type="file" />
<label class="cursor-pointer avatar avatar-5xl" for="avatarFile">
{% if staff.logo %}
<img src="{{ staff.logo.url }}"
alt="{{ staff.get_local_name }}"
class="rounded-circle"
style="max-width: 150px" />
{% else %}
<span class="rounded-circle feather feather-user text-body-tertiary"
style="max-width: 150px"></span>
<img src="{% static 'images/logos/logo.png' %}"
alt="{{ staff.get_local_name }}"
class=""
style="max-width: 150px" />
{% endif %}
</label>
</div>
<div class="col-12 col-sm-auto flex-1">
<h3>{{ staff.get_local_name }}</h3>
<p>
{% trans 'Role' %}:
{% for group in staff.groups%}
<span class="text-body-secondary me-2">&nbsp;{{group}}</span>
{% endfor %}
</p>
<p class="text-body-secondary">{% trans 'Joined' %} {{ staff.created|timesince }} {% trans 'ago' %}</p>
</div>
</div>
</div>
<div class="d-flex flex-between-center pt-4">
<div>
<h6 class="mb-2 text-body-secondary">{% trans 'last login'|capfirst %}</h6>
<h4 class="fs-7 text-body-highlight mb-0">{{ staff.user.last_login|date:"D M d, Y H:i" }}</h4>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-lg-4">
<div class="card h-100">
<div class="card-body">
<div class="border-bottom border-dashed">
<h4 class="mb-3">{% trans 'Default Address' %}</h4>
</div>
<div class="pt-4 mb-7 mb-lg-4 mb-xl-7">
<div class="row justify-content-between">
<div class="col-auto">
<h5 class="text-body-highlight">{% trans 'Address' %}</h5>
</div>
<div class="col-auto">
<p class="text-body-secondary">{{ staff.address }}</p>
</div>
</div>
</div>
<div class="border-top border-dashed pt-4">
<div class="row flex-between-center mb-2">
<div class="col-auto">
<h5 class="text-body-highlight mb-0">{% trans 'Email' %}</h5>
</div>
<div class="col-auto">{{ staff.user.email }}</div>
</div>
<div class="row flex-between-center">
<div class="col-auto">
<h5 class="text-body-highlight mb-0">{% trans 'Phone' %}</h5>
</div>
<div class="col-auto" dir="ltr">{{ staff.phone_number }}</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
{% endblock %}
<div class="row g-4">
<div class="col-12 col-xl-4">
<div class="card h-100 shadow-sm border-0">
<div class="card-body text-center p-5">
<div class="avatar avatar-10xl mb-4 position-relative">
{% if staff.logo %}
<img src="{{ staff.logo.url }}"
alt="{{ staff.get_local_name }}"
class="rounded-circle img-fluid border border-5 border-body-tertiary"
style="object-fit: cover; width: 160px; height: 160px;"/>
{% else %}
<span class="rounded-circle d-inline-flex align-items-center justify-content-center bg-primary text-white"
style="width: 160px; height: 160px; font-size: 4rem;">
{{ staff.get_local_name|first|upper }}
</span>
{% endif %}
{% comment %} <input class="d-none" id="avatarFile" type="file"/>
<label class="btn btn-primary btn-sm position-absolute bottom-0 end-0 rounded-circle"
for="avatarFile" data-bs-toggle="tooltip" title="{% trans 'Change Profile picture' %}">
<i class="fas fa-camera"></i>
</label> {% endcomment %}
</div>
<h2 class="h3 mb-1 fw-bold">{{ staff.get_local_name }}</h2>
<p class="text-body-secondary mt-3 mb-0">
<i class="fas fa-clock me-1"></i>{% trans 'Joined' %} {{ staff.created|timesince }} {% trans 'ago' %}
</p>
</div>
</div>
</div>
<div class="col-12 col-xl-8">
<div class="card h-100 shadow-sm border-0">
<div class="card-header bg-body-tertiary border-bottom-0 py-4 px-5">
<h4 class="mb-0">{% trans 'Personal Details' %}</h4>
</div>
<div class="card-body p-5">
<div class="row g-4 mb-4">
<div class="col-md-6">
<div class="d-flex align-items-center">
<div class="icon-shape icon-md rounded-circle bg-primary-subtle text-primary me-3">
<i class="fas fa-envelope"></i>
</div>
<div>
<h6 class="mb-0 text-body-secondary">{% trans 'Email Address' %}</h6>
<p class="mb-0">{{ staff.user.email }}</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center">
<div class="icon-shape icon-md rounded-circle bg-success-subtle text-success me-3">
<i class="fas fa-phone"></i>
</div>
<div>
<h6 class="mb-0 text-body-secondary">{% trans 'Phone Number' %}</h6>
<p class="mb-0" dir="ltr">{{ staff.phone_number }}</p>
</div>
</div>
</div>
<div class="col-12">
<div class="d-flex align-items-start">
<div class="icon-shape icon-md rounded-circle bg-warning-subtle text-warning me-3">
<i class="fas fa-location-dot"></i>
</div>
<div>
<h6 class="mb-0 text-body-secondary">{% trans 'Address' %}</h6>
<p class="mb-0">{{ staff.address|default:"N/A" }}</p>
</div>
</div>
</div>
</div>
<hr class="my-4">
<div class="row g-4">
<div class="col-md-6">
<div class="d-flex align-items-center">
<div class="icon-shape icon-md rounded-circle bg-info-subtle text-info me-3">
<i class="fas fa-sign-in-alt"></i>
</div>
<div>
<h6 class="mb-0 text-body-secondary">{% trans 'Last Login' %}</h6>
<p class="mb-0">{{ staff.user.last_login|date:"D, M d, Y H:i" }}</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="d-flex align-items-center">
<div class="icon-shape icon-md rounded-circle bg-danger-subtle text-danger me-3">
<i class="fas fa-user-shield"></i>
</div>
<div>
<h6 class="mb-0 text-body-secondary">{% trans 'Role' %}</h6>
{% for group in staff.groups %}
<p class="mb-0 text-success fw-bold">{{group}}</p>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,21 @@
{% extends "base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card">
<div class="card-header bg-primary text-white">
<h2 class="h4 mb-0">Create Support Ticket</h2>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{form|crispy}}
<button type="submit" class="btn btn-primary">Submit Ticket</button>
<a href="{% url 'ticket_list' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">Need help?</h5>
<p class="card-text">Raise a ticket and we will get back to you as soon as possible.</p>
<a href="{% url 'create_ticket' %}" class="btn btn-phoenix-primary">Raise a Ticket</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,59 @@
{% extends "base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h2 class="h4 mb-0">Ticket #{{ ticket.id }}: {{ ticket.subject }}</h2>
<div>
{% if ticket.status != 'resolved' %}
<a href="{% url 'ticket_update' ticket.id %}" class="btn btn-sm btn-outline-success">
Update Ticket Status
</a>
{% endif %}
<a href="{% url 'ticket_list' %}" class="btn btn-sm btn-outline-secondary">
Back to List
</a>
</div>
</div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<p><strong>Status:</strong>
<span class="badge
{% if ticket.status == 'open' %}bg-primary
{% elif ticket.status == 'in_progress' %}bg-info
{% elif ticket.status == 'resolved' %}bg-success
{% else %}bg-secondary{% endif %}">
{{ ticket.get_status_display }}
</span>
</p>
<p><strong>Priority:</strong>
<span class="badge
{% if ticket.priority == 'low' %}bg-success
{% elif ticket.priority == 'medium' %}bg-warning
{% elif ticket.priority == 'high' %}bg-danger
{% else %}bg-dark{% endif %}">
{{ ticket.get_priority_display }}
</span>
</p>
</div>
<div class="col-md-6">
<p><strong>Created:</strong> {{ ticket.created_at|date:"M d, Y H:i" }}</p>
<p><strong>Last Updated:</strong> {{ ticket.updated_at|date:"M d, Y H:i" }}</p>
</div>
</div>
<div class="mb-4">
<h3 class="h5">Description</h3>
<div class="p-3 bg-light rounded">
{{ ticket.description|linebreaks }}
</div>
</div>
<!-- You can add comments/replies section here later -->
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,80 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">My Support Tickets</h1>
<a href="{% url 'create_ticket' %}" class="btn btn-primary">
<i class="bi bi-plus-circle"></i> New Ticket
</a>
</div>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Subject</th>
<th>Status</th>
<th>Priority</th>
<th>Created</th>
<th>Resolved At</th>
<th>Time To Resolution</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for ticket in tickets %}
<tr>
<td>#{{ ticket.id }}</td>
<td>{{ ticket.subject }}</td>
<td>
<span class="badge
{% if ticket.status == 'open' %}bg-primary
{% elif ticket.status == 'in_progress' %}bg-info
{% elif ticket.status == 'resolved' %}bg-success
{% else %}bg-secondary{% endif %}">
{{ ticket.get_status_display }}
</span>
</td>
<td>
<span class="badge
{% if ticket.priority == 'low' %}bg-success
{% elif ticket.priority == 'medium' %}bg-warning
{% elif ticket.priority == 'high' %}bg-danger
{% else %}bg-dark{% endif %}">
{{ ticket.get_priority_display }}
</span>
</td>
<td>{{ ticket.created_at|date:"M d, Y H:i" }}</td>
<td>{{ ticket.updated_at|date:"M d, Y H:i" }}</td>
<td>
<p>
<i class="fa fa-clock"></i>&nbsp;
{{ ticket.time_to_resolution_display }}
</p>
<td>
<a href="{% url 'ticket_detail' ticket.id %}" class="btn btn-sm btn-outline-primary">
View
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center">No tickets found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "base.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="card">
<div class="card-header bg-primary text-white">
<h2 class="h4 mb-0">Update Ticket</h2>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{form|crispy}}
<button type="submit" class="btn btn-primary">Save</button>
<a href="{% url 'ticket_list' %}" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
{% endblock %}