changes to customer_view and the customer detail page

This commit is contained in:
Faheedkhan 2025-07-24 20:46:49 +03:00
commit 1ac8f76d8f
5 changed files with 205 additions and 71 deletions

View File

@ -2266,6 +2266,49 @@ class CustomerListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
return context
# class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
# """
# CustomerDetailView handles retrieving and presenting detailed information about
# a specific customer. It ensures that the user is authenticated and has the
# necessary permissions before accessing the customer's details. This view
# provides context data including estimates and invoices related to the customer.
# :ivar model: The model associated with the view.
# :type model: CustomerModel
# :ivar template_name: The path to the template used for rendering the view.
# :type template_name: str
# :ivar context_object_name: The name of the variable in the template context
# for the object being viewed.
# :type context_object_name: str
# :ivar permission_required: The list of permissions required to access this view.
# :type permission_required: list[str]
# """
# model = models.Customer
# template_name = "customers/view_customer.html"
# context_object_name = "customer"
# permission_required = ["inventory.view_customer"]
# def get_context_data(self, **kwargs):
# dealer = get_user_type(self.request)
# entity = dealer.entity
# context = super().get_context_data(**kwargs)
# context["notes"] = models.Notes.objects.filter(
# dealer=dealer,
# content_type__model="customer", object_id=self.object.id
# )
# estimates = entity.get_estimates().filter(customer=self.object.customer_model)
# invoices = entity.get_invoices().filter(customer=self.object.customer_model)
# total = estimates.count() + invoices.count()
# context["estimates"] = estimates
# context["invoices"] = invoices
# context["total"] = total
# context["note_form"] = forms.NoteForm()
# return context
class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
"""
CustomerDetailView handles retrieving and presenting detailed information about
@ -2293,17 +2336,22 @@ class CustomerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView
dealer = get_user_type(self.request)
entity = dealer.entity
context = super().get_context_data(**kwargs)
context["customer_notes"] = models.Notes.objects.filter(
object_id=self.object.pk
context["notes"] = models.Notes.objects.filter(
dealer=dealer,
content_type__model="customer", object_id=self.object.id
)
estimates = entity.get_estimates().filter(customer=self.object.customer_model)
invoices = entity.get_invoices().filter(customer=self.object.customer_model)
context['leads']=self.object.customer_leads.all()
total = estimates.count() + invoices.count()
context["estimates"] = estimates
context["invoices"] = invoices
context["total"] = total
context["note_form"] = forms.NoteForm()
return context
@ -4337,15 +4385,12 @@ def sales_list_view(request, dealer_slug):
except Exception as e:
print(e)
# query = request.GET.get('q')
# # if query:
# # qs = qs.filter(
# # Q(order_number__icontains=query) |
# # Q(customer__name__icontains=query) |
# # Q(item_details__icontains=query)
# # ).distinct()
#r
search_query = request.GET.get('q', None)
if search_query:
qs = qs.filter(
Q(order_number__icontains=search_query)|
Q(customer__customer_name__icontains=search_query)
).distinct()
paginator = Paginator(qs, 30)
page_number = request.GET.get("page")
@ -4445,23 +4490,32 @@ class EstimateListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
related_content_type=ContentType.objects.get_for_model(models.Staff),
related_object_id=self.request.staff.pk,
)
context["staff_estimates"] = EstimateModel.objects.filter(pk__in=[x.content_object.pk for x in qs])
qs = EstimateModel.objects.filter(pk__in=[x.content_object.pk for x in qs])
search_query = self.request.GET.get('q', None)
if search_query:
qs = qs.filter(
Q(estimate_number__icontains=search_query)|
Q(customer__customer_name__icontains=search_query)
).distinct()
context["staff_estimates"] = qs
return context
def get_queryset(self):
dealer = get_user_type(self.request)
entity = dealer.entity
status = self.request.GET.get("status")
queryset = entity.get_estimates()
if status:
queryset = queryset.filter(status=status)
search_query = self.request.GET.get('q', None)
if search_query:
queryset = queryset.filter(
Q(estimate_number__icontains=search_query)
Q(estimate_number__icontains=search_query)|
Q(customer__customer_name__icontains=search_query)
).distinct()
return queryset

View File

@ -249,3 +249,46 @@ const getDataTableInit = () => {
};
/*
// Register delete modal initializer
htmxInitializer.register(function initDeleteModals() {
const deleteModal = document.getElementById("deleteModal");
const confirmDeleteBtn = document.getElementById("deleteModalConfirm");
const deleteModalMessage = document.getElementById("deleteModalText");
// Clean up old listeners
document.querySelectorAll(".delete-btn").forEach(btn => {
btn.removeEventListener("click", handleDeleteClick);
});
// Add new listeners
document.querySelectorAll(".delete-btn").forEach(button => {
button.addEventListener("click", handleDeleteClick);
});
function handleDeleteClick() {
if (!deleteModal || !confirmDeleteBtn || !deleteModalMessage) return;
const deleteUrl = this.getAttribute("data-url");
const deleteMessage = this.getAttribute("data-message") || "Are you sure?";
confirmDeleteBtn.setAttribute("href", deleteUrl);
deleteModalMessage.textContent = deleteMessage;
if (typeof htmx !== 'undefined') htmx.process(confirmDeleteBtn);
if (typeof bootstrap !== 'undefined') new bootstrap.Modal(deleteModal).show();
}
}, "delete_modals");
// Register custom selects initializer
htmxInitializer.register(function initCustomSelects() {
// Your custom select initialization code
}, "custom_selects");
// Register form submission initializer
htmxInitializer.register(function initForms() {
// Your form handling code
}, "forms");
*/

View File

@ -3,7 +3,7 @@
<div class="content">
<h2 class="mb-5">{{ _("Notifications") }}</h2>
<div class="d-flex justify-content-end mb-3">
<a href="{% url 'mark_all_notifications_as_read' %}" class="btn btn-phoenix-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
<a href="{% url 'mark_all_notifications_as_read' %}" hx-select-oob="#notification-counter:outerHTML" class="btn btn-phoenix-primary"><i class="far fa-envelope fs-8 me-2"></i>{{ _("Mark all as read") }}</a>
</div>
{% if notifications %}
<div class="mx-n4 mx-lg-n6 mb-5 border-bottom">

View File

@ -88,16 +88,15 @@
<div class="card-body">
{% if perms.inventory.change_customer %}
<div class="d-flex align-items-center justify-content-end">
<a id="addBtn"
href="#"
class="btn btn-sm btn-phoenix-primary mb-3"
data-url="{% url 'add_note_to_customer' request.dealer.slug customer.slug %}"
data-bs-toggle="modal"
data-bs-target="#noteModal"
data-note-title="{{ _("Add") }}<i class='fa fa-plus-circle text-success ms-2'></i>">
<span class="fas fa-plus me-1"></span>
{% trans 'Add Note' %}
</a>
{% if perms.inventory.change_lead %}
<button class="btn btn-phoenix-primary btn-sm"
type="button"
onclick=""
data-bs-toggle="modal"
data-bs-target="#noteModal">
<span class="fas fa-plus me-1"></span>{{ _("Add Note") }}
</button>
{% endif %}
</div>
{% endif %}
<table class="table fs-9 mb-0 table-responsive">
@ -105,8 +104,8 @@
<th class="align-middle pe-6 text-start" scope="col">{{ _("Note") }}</th>
<th class="align-middle pe-6 text-start" scope="col">{{ _("Date") }}</th>
</tr>
<tbody>
{% for note in customer_notes %}
<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>
<td class="align-middle text-body-tertiary text-start white-space-nowrap">{{ note.created }}</td>
@ -221,30 +220,86 @@
</div>
</div>
</div>
</div>
</div>
<div class="modal fade"
id="noteModal"
tabindex="-1"
aria-labelledby="noteModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-sm">
<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="noteModalLabel">{% trans 'Notes' %}</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 class="modal-body">
<!-- Content will be loaded here via AJAX -->
<!---->
<div class="col-12">
<div class="mb-6">
<h3 class="mb-4">
{%trans 'INFO'%}
</h3>
<div class="border-top border-bottom border-translucent"
id="customerOrdersTable"
data-list='{"valueNames":["order","total","payment_status","fulfilment_status","delivery_type","date"],"page":6,"pagination":true}'>
<div class="table-responsive scrollbar">
<table class="table table-sm fs-9 mb-0">
<thead>
<tr>
<th class="sort white-space-nowrap align-middle" scope="col" data-sort="leads">{% trans 'Leads'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="opportunities">{% trans 'Opportunities'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="estimates">{% trans 'Estimates'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="sale_orders">{% trans 'Sale orders'|upper %}</th>
<th class="sort align-middle " scope="col" data-sort="invoices">{% trans 'Invoices'|upper %}</th>
</tr>
</thead>
<tbody class="list" id="customer-order-table-body">
{% for lead in leads %}
<tr>
<td><a href="#">{{lead}} ({{ forloop.counter }})<a></td>
<td>{{lead.opportunity}} ({{ forloop.counter }})</td>
<td>
{% for estimate in lead.customer.customer_model.estimatemodel_set.all %}
<div class="me-2">{{estimate}}</div>
<hr>
{% endfor %}
</td>
<td>
{% for estimate in lead.customer.customer_model.estimatemodel_set.all %}
<div>{{estimate.sale_orders.first}}</div>
<hr>
{% endfor %}
</td>
<td>
{% for invoice in lead.customer.customer_model.invoicemodel_set.all %}
{% if invoice.is_paid %}
<span class="badge badge-phoenix fs-10 badge-phoenix-success">
<div>{{invoice}}</div>
</span>
{%else%}
<span class="badge badge-phoenix fs-10 badge-phoenix-info">
<div>{{invoice}}</div>
</span>
{% endif %}
<hr>
{% endfor %}
</td>
<tr>
{% endfor %}
</tbody>
</table>
</div>
{% include 'partials/pagination.html' %}
</div>
</div>
</div>
<!---->
</div>
</div>
</div>
{% include "components/note_modal.html" with content_type="customer" slug=customer.slug %}
<script>
document.addEventListener("DOMContentLoaded", function () {
const noteModal = document.getElementById("noteModal");
@ -269,5 +324,6 @@
});
});
});
</script>
{% endblock %}

View File

@ -1,35 +1,16 @@
<div class="search-box me-2">
<form class="position-relative show" id="search-form">
<input name="q"
hx-get=""
hx-boost="true"
hx-trigger="keyup delay:500ms"
id="search-input"
class="form-control form-control-sm search-input search"
type="search"
aria-label="Search"
placeholder="{{ _("Search") }}..."
placeholder="{{ _('Search') }}..."
value="{{ request.GET.q }}" />
<span class="fa fa-magnifying-glass search-box-icon"></span>
{% if request.GET.q %}
<button type="button"
class="btn-close position-absolute end-0 top-50 translate-middle cursor-pointer shadow-none"
id="clear-search"
aria-label="Close"></button>
{% endif %}
</form>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const searchInput = document.getElementById("search-input");
const clearButton = document.getElementById("clear-search");
if (clearButton) {
clearButton.addEventListener("click", function(event) {
event.preventDefault();
searchInput.value = ""; // Clear input field
// Remove query parameter without reloading the page
const newUrl = window.location.pathname;
history.replaceState(null, "", newUrl);
window.location.reload();
});
}
});
</script>