rebase with marwan update
This commit is contained in:
parent
7a1b15bb97
commit
fa111b159f
@ -660,7 +660,7 @@ class Car(Base):
|
||||
remarks = models.TextField(blank=True, null=True, verbose_name=_("Remarks"))
|
||||
mileage = models.IntegerField(blank=True, null=True, verbose_name=_("Mileage"))
|
||||
receiving_date = models.DateTimeField(verbose_name=_("Receiving Date"))
|
||||
sold_date=models.DateTimeField(verbose_name=_("Sold Date"))
|
||||
sold_date=models.DateTimeField(verbose_name=_("Sold Date"),null=True,blank=True)
|
||||
hash = models.CharField(
|
||||
max_length=64, blank=True, null=True, verbose_name=_("Hash")
|
||||
)
|
||||
@ -3361,9 +3361,9 @@ class ExtraInfo(models.Model):
|
||||
)
|
||||
# qs = qs.select_related("customer","estimate","invoice")
|
||||
data = SaleOrder.objects.filter(pk__in=[x.content_object.sale_orders.select_related("customer","estimate","invoice").first().pk for x in qs if x.content_object.sale_orders.first()])
|
||||
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# return [
|
||||
# x.content_object.sale_orders.select_related(
|
||||
# "customer", "estimate", "invoice"
|
||||
|
||||
@ -1251,7 +1251,7 @@ urlpatterns = [
|
||||
name="po-action-mark-as-void",
|
||||
),
|
||||
|
||||
# reports
|
||||
# reports
|
||||
path(
|
||||
"<slug:dealer_slug>/purchase-report/",
|
||||
views.purchase_report_view,
|
||||
|
||||
@ -1494,7 +1494,7 @@ class CarFinanceCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVi
|
||||
form_class = forms.CarFinanceForm
|
||||
template_name = "inventory/car_finance_form.html"
|
||||
permission_required = ["inventory.add_carfinance"]
|
||||
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.car = get_object_or_404(models.Car, slug=self.kwargs["slug"])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
@ -4320,8 +4320,8 @@ def sales_list_view(request, dealer_slug):
|
||||
item transactions specific to the user's entity.
|
||||
:rtype: HttpResponse
|
||||
"""
|
||||
|
||||
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
|
||||
|
||||
dealer = get_object_or_404(models.Dealer, slug=dealer_slug)
|
||||
staff = getattr(request.user.staffmember, "staff", None)
|
||||
qs = []
|
||||
try:
|
||||
@ -4459,9 +4459,9 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
||||
if search_query:
|
||||
print("inside")
|
||||
queryset = queryset.filter(
|
||||
Q(estimate_number__icontains=search_query)
|
||||
Q(estimate_number__icontains=search_query)
|
||||
|
||||
).distinct()
|
||||
).distinct()
|
||||
|
||||
return queryset
|
||||
|
||||
@ -6104,6 +6104,7 @@ def update_lead_actions(request, dealer_slug):
|
||||
current_action = request.POST.get("current_action")
|
||||
next_action = request.POST.get("next_action")
|
||||
next_action_date = request.POST.get("next_action_date", None)
|
||||
lead = models.Lead.objects.get(id=lead_id)
|
||||
|
||||
if not all([lead_id, current_action, next_action]):
|
||||
# Log for missing required fields
|
||||
@ -6111,12 +6112,17 @@ def update_lead_actions(request, dealer_slug):
|
||||
f"User {user_username} submitted incomplete data to update lead actions "
|
||||
f"for dealer '{dealer_slug}'. Missing fields: lead_id='{lead_id}', current_action='{current_action}', next_action='{next_action}'."
|
||||
)
|
||||
return JsonResponse(
|
||||
{"success": False, "message": "All fields are required"}, status=400
|
||||
messages.error(
|
||||
request,
|
||||
_("All fields are required")
|
||||
)
|
||||
return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug)
|
||||
# return JsonResponse(
|
||||
# {"success": False, "message": "All fields are required"}, status=400
|
||||
# )
|
||||
|
||||
# Get the lead
|
||||
lead = models.Lead.objects.get(id=lead_id)
|
||||
|
||||
|
||||
# Update lead fields
|
||||
|
||||
@ -6146,9 +6152,14 @@ def update_lead_actions(request, dealer_slug):
|
||||
f"submitted invalid date format ('{next_action_date}') "
|
||||
f"for Lead ID: {lead.pk}. Error: {ve}"
|
||||
)
|
||||
return JsonResponse(
|
||||
{"success": False, "message": "Invalid date format"}, status=400
|
||||
messages.error(
|
||||
request,
|
||||
_("Invalid date format")
|
||||
)
|
||||
return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug)
|
||||
# return JsonResponse(
|
||||
# {"success": False, "message": "Invalid date format"}, status=400
|
||||
# )
|
||||
# Save the lead
|
||||
lead.save()
|
||||
# --- Logging for successful update (main try block success) ---
|
||||
@ -6156,9 +6167,14 @@ def update_lead_actions(request, dealer_slug):
|
||||
f"User {user_username} successfully updated Lead ID: {lead.pk} ('{lead.slug}'). "
|
||||
f"New Status: '{lead.status}', Next Action: '{lead.next_action}', Next Action Date: '{lead.next_action_date}'."
|
||||
)
|
||||
return JsonResponse(
|
||||
{"success": True, "message": "Actions updated successfully"}
|
||||
messages.success(
|
||||
request,
|
||||
_("Actions updated successfully")
|
||||
)
|
||||
return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug)
|
||||
# return JsonResponse(
|
||||
# {"success": True, "message": "Actions updated successfully"}
|
||||
# )
|
||||
|
||||
except models.Lead.DoesNotExist:
|
||||
# --- Logging for Lead not found ---
|
||||
@ -6166,7 +6182,12 @@ def update_lead_actions(request, dealer_slug):
|
||||
f"User {user_username} attempted to update non-existent Lead with ID: '{lead_id}' "
|
||||
f"for dealer '{dealer_slug}'. Returning 404."
|
||||
)
|
||||
return JsonResponse({"success": False, "message": "Lead not found"}, status=404)
|
||||
messages.error(
|
||||
request,
|
||||
_("Lead not found")
|
||||
)
|
||||
return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug)
|
||||
# return JsonResponse({"success": False, "message": "Lead not found"}, status=404)
|
||||
except Exception as e:
|
||||
involved_lead_id = request.POST.get("lead_id", "N/A")
|
||||
logger.error(
|
||||
@ -6174,7 +6195,12 @@ def update_lead_actions(request, dealer_slug):
|
||||
f"for dealer '{dealer_slug}'. Error: {e}",
|
||||
exc_info=True, # CRUCIAL: Includes the full traceback
|
||||
)
|
||||
return JsonResponse({"success": False, "message": str(e)}, status=500)
|
||||
messages.error(
|
||||
request,
|
||||
_("An error occurred while updating lead actions")
|
||||
)
|
||||
return redirect("lead_detail", dealer_slug=dealer_slug, slug=lead.slug)
|
||||
# return JsonResponse({"success": False, "message": str(e)}, status=500)
|
||||
|
||||
|
||||
class LeadUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
|
||||
@ -6558,7 +6584,7 @@ def lead_transfer(request, dealer_slug, slug):
|
||||
messages.success(request, _("Lead transferred successfully"))
|
||||
else:
|
||||
messages.error(request, f"Invalid form data: {str(form.errors)}")
|
||||
return redirect("lead_list", dealer_slug=dealer.slug)
|
||||
return redirect("lead_detail", dealer_slug=dealer.slug ,slug=lead.slug)
|
||||
|
||||
|
||||
@login_required
|
||||
@ -10399,7 +10425,7 @@ def upload_cars(request, dealer_slug, pk=None):
|
||||
|
||||
form = forms.CSVUploadForm()
|
||||
form.fields["vendor"].queryset = dealer.vendors.all()
|
||||
print(request)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"csv_upload.html",
|
||||
@ -10451,16 +10477,16 @@ def purchase_report_view(request,dealer_slug):
|
||||
pos = request.entity.get_purchase_orders()
|
||||
data = []
|
||||
total_po_amount=0
|
||||
total_po_cars=0
|
||||
total_po_cars=0
|
||||
for po in pos:
|
||||
items = [{"total":x.total_amount,"q":x.quantity} for x in po.get_itemtxs_data()[0].all()]
|
||||
|
||||
|
||||
po_amount=0
|
||||
po_quantity=0
|
||||
for item in items:
|
||||
po_amount+=item["total"]
|
||||
po_quantity+=item["q"]
|
||||
|
||||
|
||||
total_po_amount+=po_amount
|
||||
total_po_cars+=po_quantity
|
||||
bills=po.get_po_bill_queryset()
|
||||
@ -10468,7 +10494,7 @@ def purchase_report_view(request,dealer_slug):
|
||||
vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A"
|
||||
data.append({"po_number":po.po_number,"po_created":po.created,"po_status":po.po_status,"po_fulfilled_date":po.date_fulfilled,"po_amount":po_amount,
|
||||
"po_quantity":po_quantity,"vendors_str":vendors_str})
|
||||
|
||||
|
||||
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
context={
|
||||
"dealer":request.entity.name,
|
||||
@ -10479,23 +10505,23 @@ def purchase_report_view(request,dealer_slug):
|
||||
"current_time":current_time
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return render(request,'ledger/reports/purchase_report.html',context)
|
||||
|
||||
|
||||
def purchase_report_csv_export(request,dealer_slug):
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
|
||||
|
||||
|
||||
|
||||
current_time = timezone.now().strftime("%Y-%m-%d_%H%M%S")
|
||||
filename = f"purchase_report_{dealer_slug}_{current_time}.csv"
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
|
||||
writer = csv.writer(response)
|
||||
|
||||
|
||||
|
||||
header = [
|
||||
'PO Number',
|
||||
'Created Date',
|
||||
@ -10507,7 +10533,7 @@ def purchase_report_csv_export(request,dealer_slug):
|
||||
]
|
||||
writer.writerow(header)
|
||||
pos = request.entity.get_purchase_orders()
|
||||
|
||||
|
||||
for po in pos:
|
||||
po_amount = 0
|
||||
po_quantity = 0
|
||||
@ -10516,10 +10542,10 @@ def purchase_report_csv_export(request,dealer_slug):
|
||||
for item in items:
|
||||
po_amount += item["total"]
|
||||
po_quantity += item["q"]
|
||||
|
||||
|
||||
bills = po.get_po_bill_queryset()
|
||||
vendors = set([bill.vendor.vendor_name for bill in bills ])
|
||||
vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A"
|
||||
vendors_str = ", ".join(sorted(list(vendors))) if vendors else "N/A"
|
||||
|
||||
|
||||
writer.writerow([
|
||||
@ -10527,7 +10553,7 @@ def purchase_report_csv_export(request,dealer_slug):
|
||||
po.created.strftime("%Y-%m-%d %H:%M:%S") if po.created else '',
|
||||
po.get_po_status_display(),
|
||||
po.date_fulfilled.strftime("%Y-%m-%d") if po.date_fulfilled else '',
|
||||
f"{po_amount:.2f}",
|
||||
f"{po_amount:.2f}",
|
||||
po_quantity,
|
||||
vendors_str
|
||||
])
|
||||
@ -10540,13 +10566,13 @@ def car_sale_report_view(request,dealer_slug):
|
||||
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
context={'cars_sold':cars_sold,'current_time':current_time }
|
||||
return render(request,'ledger/reports/car_sale_report.html',context)
|
||||
|
||||
|
||||
|
||||
def car_sale_report_csv_export(request,dealer_slug):
|
||||
|
||||
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
|
||||
|
||||
|
||||
|
||||
current_time = timezone.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
filename = f"sales_report_{dealer_slug}_{current_time}.csv"
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
@ -10555,7 +10581,7 @@ def car_sale_report_csv_export(request,dealer_slug):
|
||||
|
||||
header=[
|
||||
'Make',
|
||||
'VIN',
|
||||
'VIN',
|
||||
'Model',
|
||||
'Year',
|
||||
'Serie',
|
||||
@ -10572,16 +10598,16 @@ def car_sale_report_csv_export(request,dealer_slug):
|
||||
'Invoice Number',
|
||||
]
|
||||
writer.writerow(header)
|
||||
|
||||
|
||||
dealer=get_object_or_404(models.Dealer,slug=dealer_slug)
|
||||
cars_sold=models.Car.objects.filter(dealer=dealer,status='sold')
|
||||
for car in cars_sold:
|
||||
writer.writerow([
|
||||
car.vin,
|
||||
car.vin,
|
||||
car.id_car_make.name,
|
||||
car.id_car_model.name,
|
||||
car.year,
|
||||
car.id_car_serie.name,
|
||||
car.id_car_serie.name,
|
||||
car.id_car_trim.name,
|
||||
car.mileage,
|
||||
car.stock_type,
|
||||
@ -10595,5 +10621,4 @@ def car_sale_report_csv_export(request,dealer_slug):
|
||||
car.item_model.invoicemodel_set.first().invoice_number
|
||||
])
|
||||
|
||||
return response
|
||||
|
||||
return response
|
||||
|
||||
@ -132,3 +132,45 @@ html[dir="rtl"] .form-icon-container .form-control {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
|
||||
#spinner {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#spinner-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
opacity: 0;
|
||||
transition: opacity 500ms ease-in;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#spinner-bg.htmx-request {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
|
||||
/* .fade-me-in.htmx-added {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-me-in {
|
||||
opacity: .9;
|
||||
transition: opacity 300ms ease-out;
|
||||
} */
|
||||
|
||||
#main_content.fade-me-in:not(.modal):not(.modal *) {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms ease-out;
|
||||
}
|
||||
|
||||
#main_content.fade-me-in.htmx-added:not(.modal):not(.modal *) {
|
||||
opacity: 0;
|
||||
}
|
||||
1
static/spinner.svg
Normal file
1
static/spinner.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'><rect fill='#09577F' stroke='#09577F' stroke-width='15' width='30' height='30' x='25' y='85'><animate attributeName='opacity' calcMode='spline' dur='2' values='1;0;1;' keySplines='.5 0 .5 1;.5 0 .5 1' repeatCount='indefinite' begin='-.4'></animate></rect><rect fill='#09577F' stroke='#09577F' stroke-width='15' width='30' height='30' x='85' y='85'><animate attributeName='opacity' calcMode='spline' dur='2' values='1;0;1;' keySplines='.5 0 .5 1;.5 0 .5 1' repeatCount='indefinite' begin='-.2'></animate></rect><rect fill='#09577F' stroke='#09577F' stroke-width='15' width='30' height='30' x='145' y='85'><animate attributeName='opacity' calcMode='spline' dur='2' values='1;0;1;' keySplines='.5 0 .5 1;.5 0 .5 1' repeatCount='indefinite' begin='0'></animate></rect></svg>
|
||||
@ -13,7 +13,7 @@
|
||||
<p class="text-body-tertiary">{{ _("Are you sure you want to sign out?") }}</p>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<form method="post" action="{% url 'account_logout' %}">
|
||||
<form hx-boost="false" method="post" action="{% url 'account_logout' %}">
|
||||
{% csrf_token %}
|
||||
{{ redirect_field }}
|
||||
<div class="d-grid gap-2 mt-3">
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
<script src="{% static 'js/main.js' %}"></script>
|
||||
<script src="{% static 'js/jquery.min.js' %}"></script>
|
||||
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
|
||||
{% block customCSS %}{% endblock %}
|
||||
{% comment %} {% block customCSS %}{% endblock %} {% endcomment %}
|
||||
</head>
|
||||
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||
{% include "toast-alert.html" %}
|
||||
@ -81,10 +81,20 @@
|
||||
{% include "plans/expiration_messages.html" %}
|
||||
{% block period_navigation %}
|
||||
{% endblock period_navigation %}
|
||||
<div id="main_content" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML" hx-select-oob="#toast-container" hx-history-elt>
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
|
||||
<div id="main_content" class="fade-me-in" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:false" hx-select-oob="#toast-container" hx-history-elt>
|
||||
<div id="spinner" class="htmx-indicator spinner-bg">
|
||||
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
|
||||
</div>
|
||||
{% block customCSS %}{% endblock %}
|
||||
{% block content %}{% endblock content %}
|
||||
{% block customJS %}{% endblock %}
|
||||
|
||||
{% comment %} <script src="{% static 'vendors/feather-icons/feather.min.js' %}"></script>
|
||||
<script src="{% static 'vendors/fontawesome/all.min.js' %}"></script>
|
||||
<script src="{% static 'vendors/popper/popper.min.js' %}"></script>
|
||||
<script src="{% static 'vendors/bootstrap/bootstrap.min.js' %}"></script>
|
||||
<script src="{% static 'js/phoenix.js' %}"></script> {% endcomment %}
|
||||
|
||||
</div>
|
||||
{% block body %}
|
||||
{% endblock body %}
|
||||
@ -136,7 +146,16 @@
|
||||
let datePickers = document.querySelectorAll("[id^='djl-datepicker']")
|
||||
datePickers.forEach(dp => djLedger.getCalendar(dp.attributes.id.value, dateNavigationUrl))
|
||||
{% endif %}
|
||||
|
||||
/*document.body.addEventListener('htmx:configRequest', function(evt) {
|
||||
evt.detail.indicators = [
|
||||
...(evt.detail.indicators || []),
|
||||
document.getElementById('global-indicator')
|
||||
];
|
||||
});*/
|
||||
|
||||
|
||||
</script>
|
||||
{% block customJS %}{% endblock %}
|
||||
{% comment %} {% block customJS %}{% endblock %} {% endcomment %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
{% load i18n crispy_forms_tags %}
|
||||
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||
<h4 class="modal-title" id="emailModalLabel">{% trans 'Send Email' %}</h4>
|
||||
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span class="fas fa-times"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="emailModalBody" class="modal-body">
|
||||
<h1>hi</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-dialog modal-xl">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header justify-content-between align-items-start gap-5 px-4 pt-4 pb-3 border-0">
|
||||
<h4 class="modal-title" id="emailModalLabel">{% trans 'Send Email' %}</h4>
|
||||
<button class="btn p-0 text-body-quaternary fs-6" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span class="fas fa-times"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div id="emailModalBody" class="modal-body">
|
||||
<h1>hi</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -16,6 +16,13 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="{% url 'add_note' request.dealer.slug content_type slug %}"
|
||||
hx-select="#notesTable"
|
||||
hx-target="#notesTable"
|
||||
hx-on::after-request="{
|
||||
resetSubmitButton(document.querySelector('.add_note_form button[type=submit]'));
|
||||
$('#noteModal').modal('hide');
|
||||
}"
|
||||
hx-swap="outerHTML"
|
||||
method="post"
|
||||
class="add_note_form">
|
||||
{% csrf_token %}
|
||||
@ -33,5 +40,6 @@
|
||||
document.querySelector('#id_note').value = note
|
||||
let form = document.querySelector('.add_note_form')
|
||||
form.action = url
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -23,6 +23,12 @@
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form action="{% url 'add_task' request.dealer.slug content_type slug %}"
|
||||
hx-select=".taskTable"
|
||||
hx-target=".taskTable"
|
||||
hx-on::after-request="{
|
||||
resetSubmitButton(document.querySelector('.add_task_form button[type=submit]'));
|
||||
$('#taskModal').modal('hide');
|
||||
}"
|
||||
method="post"
|
||||
class="add_task_form">
|
||||
{% csrf_token %}
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
border-bottom: 28px solid transparent;
|
||||
border-left: 20px solid #dee2e6;
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock customCSS %}
|
||||
{% block content %}
|
||||
@ -68,7 +69,7 @@
|
||||
<p>{{ lead.email|capfirst }}</p>
|
||||
</div>
|
||||
<div class="col-6 col-sm-auto flex-1">
|
||||
<h5 class="text-body-highlight mb-0 text-end">
|
||||
<h5 id="leadStatus" class="text-body-highlight mb-0 text-end">
|
||||
{{ _("Status") }}
|
||||
{% if lead.status == "new" %}
|
||||
<span class="badge badge-phoenix badge-phoenix-primary"><span class="badge-label">{{ _("New") }}</span><span class="fa fa-bell ms-1"></span></span>
|
||||
@ -103,7 +104,7 @@
|
||||
</div>
|
||||
<div class="card mb-2">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center g-3 text-center text-xxl-start">
|
||||
<div id="assignedTo" class="row align-items-center g-3 text-center text-xxl-start">
|
||||
<div class="col-6 col-sm-auto d-flex flex-column align-items-center text-center">
|
||||
<h5 class="fw-bolder mb-2 text-body-highlight">{{ _("Assigned To") }}</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
@ -219,7 +220,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-7 col-lg-7 col-xl-8">
|
||||
<div class="d-flex w-100 gap-5">
|
||||
<div id="currentStage" class="d-flex w-100 gap-5">
|
||||
<div class="kanban-header bg-success w-50 text-white fw-bold">
|
||||
<i class="fa-solid fa-circle-check me-2"></i>{{ lead.status|capfirst }}
|
||||
<br>
|
||||
@ -303,6 +304,11 @@
|
||||
<div class="modal-content">
|
||||
<form class="modal-content"
|
||||
action="{% url 'lead_transfer' request.dealer.slug lead.slug %}"
|
||||
hx-select-oob="#assignedTo:outerHTML,#toast-container:outerHTML"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="{
|
||||
resetSubmitButton(document.querySelector('#exampleModal button[type=submit]'));
|
||||
$('#exampleModal').modal('hide');}"
|
||||
method="post">
|
||||
{% csrf_token %}
|
||||
<div class="modal-header">
|
||||
@ -471,7 +477,7 @@
|
||||
<th class="align-middle pe-0 text-end" scope="col" style="width:10%;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody id="notesTable">
|
||||
{% for note in notes %}
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle text-start fw-bold text-body-tertiary ps-1">{{ note.note }}</td>
|
||||
@ -793,7 +799,7 @@
|
||||
style="min-width:100px">Completed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list" id="all-tasks-table-body">
|
||||
<tbody class="list taskTable" id="all-tasks-table-body">
|
||||
{% for task in schedules %}
|
||||
{% include "partials/task.html" %}
|
||||
{% endfor %}
|
||||
@ -862,7 +868,9 @@
|
||||
{% endif %}
|
||||
|
||||
function openActionModal(leadId, currentAction, nextAction, nextActionDate) {
|
||||
const modal = new bootstrap.Modal(document.getElementById('actionTrackingModal'));
|
||||
|
||||
const modal = new bootstrap.Modal(document.getElementById('actionTrackingModal'));
|
||||
document.getElementById('actionTrackingForm').setAttribute('hx-boost', 'false');
|
||||
document.getElementById('leadId').value = leadId;
|
||||
document.getElementById('currentAction').value = currentAction;
|
||||
document.getElementById('nextAction').value = nextAction;
|
||||
@ -870,7 +878,7 @@
|
||||
modal.show();
|
||||
}
|
||||
|
||||
document.getElementById('actionTrackingForm').addEventListener('submit', function(e) {
|
||||
/*document.getElementById('actionTrackingForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(this);
|
||||
|
||||
@ -951,7 +959,7 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});*/
|
||||
|
||||
// Helper function for notifications
|
||||
function notify(tag, msg) {
|
||||
@ -961,5 +969,25 @@
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
// Close modal after successful form submission
|
||||
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||
if (evt.detail.target.id === 'main_content') {
|
||||
var modal = bootstrap.Modal.getInstance(document.getElementById('exampleModal'));
|
||||
if (modal) {
|
||||
modal.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Cleanup modal backdrop if needed
|
||||
document.body.addEventListener('htmx:beforeSwap', function(evt) {
|
||||
if (evt.detail.target.id === 'main_content') {
|
||||
var backdrops = document.querySelectorAll('.modal-backdrop');
|
||||
backdrops.forEach(function(backdrop) {
|
||||
backdrop.remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock customJS %}
|
||||
|
||||
@ -239,10 +239,6 @@
|
||||
class="dropdown-item text-success-dark">{% trans "Edit" %}</a>
|
||||
{% endif %}
|
||||
{% if perms.inventory.change_lead %}
|
||||
<button class="dropdown-item text-primary"
|
||||
onclick="openActionModal('{{ lead.pk }}', '{{ lead.action }}', '{{ lead.next_action }}', '{{ lead.next_action_date|date:"Y-m-d\TH:i" }}')">
|
||||
{% trans "Update Actions" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if not lead.opportunity %}
|
||||
{% if perms.inventory.add_opportunity %}
|
||||
@ -335,69 +331,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
fetch("{% url 'update_lead_actions' request.dealer.slug %}", {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
Swal.close();
|
||||
if (data.success) {
|
||||
// Success notification
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'success',
|
||||
position: "top-end",
|
||||
text: data.message || 'Actions updated successfully',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
}).then(() => {
|
||||
location.reload(); // Refresh after user clicks OK
|
||||
});
|
||||
} else {
|
||||
// Error notification
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'error',
|
||||
position: "top-end",
|
||||
text: data.message || 'Failed to update actions',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
Swal.close();
|
||||
console.error('Error:', error);
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
icon: 'error',
|
||||
position: "top-end",
|
||||
text: 'An unexpected error occurred',
|
||||
showConfirmButton: false,
|
||||
timer: 2000,
|
||||
timerProgressBar: false,
|
||||
didOpen: (toast) => {
|
||||
toast.onmouseenter = Swal.stopTimer;
|
||||
toast.onmouseleave = Swal.resumeTimer;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Helper function for notifications
|
||||
function notify(tag, msg) {
|
||||
Toast.fire({
|
||||
|
||||
@ -12,7 +12,15 @@
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="actionTrackingForm" method="post">
|
||||
<form id="actionTrackingForm"
|
||||
action="{% url 'update_lead_actions' lead.dealer.slug %}"
|
||||
hx-select-oob="#currentStage:outerHTML,#leadStatus:outerHTML,#toast-container:outerHTML"
|
||||
hx-swap="none"
|
||||
hx-on::after-request="{
|
||||
resetSubmitButton(document.querySelector('#actionTrackingForm button[type=submit]'));
|
||||
$('#actionTrackingModal').modal('hide');
|
||||
}"
|
||||
method="post">
|
||||
<div class="modal-body">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="leadId" name="lead_id">
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<nav class="navbar navbar-vertical navbar-expand-lg ">
|
||||
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
|
||||
<div class="navbar-vertical-content d-flex flex-column">
|
||||
<ul class="navbar-nav flex-column" id="navbarVerticalNav" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML" hx-select-oob="#toast-container">
|
||||
<ul class="navbar-nav flex-column" id="navbarVerticalNav" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML" hx-select-oob="#toast-container" hx-indicator="#spinner">
|
||||
<li class="nav-item">
|
||||
<p class="navbar-vertical-label text-primary fs-8 text-truncate">{{request.dealer|default:"Apps"}}</p>
|
||||
<hr class="navbar-vertical-line" />
|
||||
@ -366,7 +366,7 @@
|
||||
<i class="fas fa-car"></i><span class="nav-link-text">{% trans 'Car Sale Report'|capfirst %}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -375,11 +375,11 @@
|
||||
</li>
|
||||
</ul>
|
||||
{# --- Support & Contact Section (New) --- #}
|
||||
<div class="mt-auto bg-info-subtle">
|
||||
|
||||
<div class="mt-auto bg-info-subtle">
|
||||
|
||||
<ul class="navbar-nav flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<a class="nav-link" href="#">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="nav-link-icon"><span class="fas fa-headphones"></span></span>
|
||||
<span class="nav-link-text">{% trans 'Haikal Support'|capfirst %}</span>
|
||||
@ -387,7 +387,7 @@
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<a class="nav-link" href="#">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="nav-link-icon"><span class="fas fa-phone"></span></span>
|
||||
<span class="nav-link-text">{% trans 'Haikal Contact'|capfirst %}</span>
|
||||
@ -396,7 +396,7 @@
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">
|
||||
<a class="nav-link" href="#">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="nav-link-icon"><span class="fas fa-robot"></span></span>
|
||||
<span class="nav-link-text">{% trans 'Haikal Bot'|capfirst %}</span>
|
||||
|
||||
@ -34,7 +34,7 @@
|
||||
{% endif %}
|
||||
<!---->
|
||||
<div class="row justify-content-center mt-5 mb-3
|
||||
{% if not vendor_exists %}disabled{% endif %}">
|
||||
{% if not vendor_exists %}disabled{% endif %}" hx-boost="false">
|
||||
<div class="col-lg-8 col-md-10">
|
||||
<div class="card shadow-sm border-0 rounded-3">
|
||||
<div class="card-header bg-gray-200 py-3 border-0 rounded-top-3">
|
||||
|
||||
@ -43,6 +43,10 @@
|
||||
let deleteMessage = this.getAttribute("data-message");
|
||||
|
||||
confirmDeleteBtn.setAttribute("href", deleteUrl);
|
||||
confirmDeleteBtn.setAttribute("hx-boost", "true");
|
||||
confirmDeleteBtn.setAttribute("hx-select-oob", "#notesTable:outerHTML,#toast-container:outerHTML");
|
||||
confirmDeleteBtn.setAttribute("hx-swap", "none");
|
||||
confirmDeleteBtn.setAttribute("hx-on::after-request", "$('#deleteModal').modal('hide');");
|
||||
deleteModalMessage.innerHTML = deleteMessage;
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user